Compare commits

...

14 Commits

Author SHA1 Message Date
Michele Caini
ed6adbbfd7 Update README.md 2017-11-15 22:45:35 +01:00
Michele Caini
b6c950ffc5 tests, tags and few other features 2017-11-15 22:25:37 +01:00
Michele Caini
8b89c69d5f fixed #20 2017-11-14 22:48:37 +01:00
Michele Caini
290dda50fe now it works with MSVC2017 (#19)
#18
2017-11-13 10:39:55 +01:00
Michele Caini
a7278573a8 review: hashed_string 2017-11-13 08:49:04 +01:00
Michele Caini
68ce4dc689 added actor class 2017-11-12 16:11:32 +01:00
Michele Caini
a9f5118013 updated documentation 2017-11-11 23:48:08 +01:00
Michele Caini
d1f2e8ecf9 updated tests 2017-11-11 23:47:31 +01:00
Michele Caini
fe6873b61a updated version 2017-11-11 23:46:29 +01:00
Michele Caini
7c7bcf80cf added stuff for resource management 2017-11-11 23:46:10 +01:00
Michele Caini
cf6022866d added process and scheduler 2017-11-11 23:42:52 +01:00
Michele Caini
c630cb1de2 added core/hashed_string 2017-11-11 23:41:48 +01:00
Michele Caini
2e6c8d542c updated signal module 2017-11-11 23:41:16 +01:00
Michele Caini
2f781906b5 updated entity module 2017-11-11 23:40:50 +01:00
29 changed files with 2514 additions and 396 deletions

View File

@@ -16,7 +16,7 @@ endif()
# Project configuration
#
project(entt VERSION 2.1.0)
project(entt VERSION 2.2.0)
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Debug)

129
README.md
View File

@@ -21,6 +21,11 @@ a while the codebase has grown and more and more classes have become part
of the repository.<br/>
That's why today it's called _the EnTT Framework_.
Currently, `EnTT` is tested on Linux, Microsoft Windows and OS X. It has proven
to work also on both Android and iOS.<br/>
Most likely it will not be problematic on other systems as well, but has not
been sufficiently tested so far.
## The framework
`EnTT` was written initially as a faster alternative to other well known and
@@ -42,13 +47,17 @@ Here is a brief list of what it offers today:
* Statically generated integer identifiers for types (assigned either at
compile-time or at runtime).
* A constexpr utility for human readable resource identifiers.
* An incredibly fast entity-component system based on sparse sets, with its own
views and a _pay for what you use_ policy to adjust performance and memory
pressure according to the users' requirements.
* Actor class for those who aren't confident with entity-component systems.
* The smallest and most basic implementation of a service locator ever seen.
* A cooperative scheduler for processes of any type.
* All what is needed for resource management (cache, loaders, handles).
* Signal handlers of any type, delegates and an event bus.
* A general purpose event emitter, that is a CRTP idiom based class template.
* An event dispatcher for immediate and delayed events to integrate in loops.
* The smallest and most basic implementation of a service locator ever seen.
* ...
* Any other business.
@@ -144,23 +153,23 @@ compared to my first choice (that was already amazingly fast indeed).<br/>
Here is a comparision between the two (both of them compiled with GCC 7.2.0 on a
Dell XPS 13 out of the mid 2014):
| Benchmark | EntityX (experimental/compile_time) | EnTT |
| Benchmark | EntityX (compile-time) | EnTT |
|-----------|-------------|-------------|
| Creating 10M entities | 0.128881s | **0.0408754s** |
| Destroying 10M entities | **0.0531374s** | 0.0545839s |
| Iterating over 10M entities, unpacking one component, standard view | 0.010661s | **1.58e-07s** |
| Iterating over 10M entities, unpacking two components, standard view | **0.0112664s** | 0.0840068s |
| Iterating over 10M entities, unpacking two components, standard view, half of the entities have all the components | **0.0077951s** | 0.042168s |
| Iterating over 10M entities, unpacking two components, standard view, one of the entities has all the components | 0.00713398s | **8.93e-07s** |
| Iterating over 10M entities, unpacking two components, persistent view | 0.0112664s | **5.68e-07s** |
| Iterating over 10M entities, unpacking five components, standard view | **0.00905084s** | 0.137757s |
| Iterating over 10M entities, unpacking five components, persistent view | 0.00905084s | **2.9e-07s** |
| Iterating over 10M entities, unpacking ten components, standard view | **0.0104708s** | 0.388602s |
| Iterating over 10M entities, unpacking ten components, standard view, half of the entities have all the components | **0.00899859s** | 0.200752s |
| Iterating over 10M entities, unpacking ten components, standard view, one of the entities has all the components | 0.00700349s | **2.565e-06s** |
| Iterating over 10M entities, unpacking ten components, persistent view | 0.0104708s | **6.23e-07s** |
| Sort 150k entities, one component | - | **0.0080046s** |
| Sort 150k entities, match two components | - | **0.00608322s** |
| Create 10M entities | 0.1289s | **0.0409s** |
| Destroy 10M entities | **0.0531s** | 0.0546s |
| Standard view, 10M entities, one component | 0.0107s | **1.6e-07s** |
| Standard view, 10M entities, two components | **0.0113s** | 0.0295s |
| Standard view, 10M entities, two components<br/>Half of the entities have all the components | **0.0078s** | 0.0150s |
| Standard view, 10M entities, two components<br/>One of the entities has all the components | 0.0071s | **8.8e-07s** |
| Persistent view, 10M entities, two components | 0.0113s | **5.7e-07s** |
| Standard view, 10M entities, five components | **0.0091s** | 0.0688s |
| Persistent view, 10M entities, five components | 0.0091s | **2.9e-07s** |
| Standard view, 10M entities, ten components | **0.0105s** | 0.1403s |
| Standard view, 10M entities, ten components<br/>Half of the entities have all the components | **0.0090s** | 0.0620s |
| Standard view, 10M entities, ten components<br/>One of the entities has all the components | 0.0070s | **1.3e-06s** |
| Persistent view, 10M entities, ten components | 0.0105s | **6.2e-07s** |
| Sort 150k entities, one component | - | **0.0084s** |
| Sort 150k entities, enforce permutation | - | **0.0067s** |
`EnTT` includes its own tests and benchmarks. See
[benchmark.cpp](https://github.com/skypjack/entt/blob/master/test/benchmark.cpp)
@@ -247,6 +256,24 @@ Benchmarks are compiled only in release mode currently.
## Design choices
### A bitset-free entity-component system
`EnTT` is a _bitset-free_ entity-component system that doesn't require users to
specify the component set at compile-time.<br/>
That's the reason for which users can instantiate the core class simply as:
```cpp
entt::DefaultRegistry registry;
```
In place of its more annoying and error-prone counterpart:
```cpp
entt::DefaultRegistry<Comp0, Comp1, ..., CompN> registry;
```
### Pay per use
`EnTT` is entirely designed around the principle that users have to pay only for
what they want.
@@ -262,7 +289,7 @@ The disadvantage of this approach is that users need to know the systems they
are working on and the tools they are using. Otherwise, the risk to ruin the
performance along critical paths is high.
So far, this choice has proved to be a good one and I really hope it can be for
So far, this choice has proven to be a good one and I really hope it can be for
many others besides me.
## Vademecum
@@ -434,11 +461,11 @@ 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 ...
DefaultRegistry registry;
entt::DefaultRegistry registry;
auto &position = registry.get<Position>(entity);
// ... or a const one
@@ -449,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

@@ -0,0 +1,109 @@
#ifndef ENTT_CORE_HASHED_STRING_HPP
#define ENTT_CORE_HASHED_STRING_HPP
#include <cstdint>
namespace entt {
/**
* @brief Zero overhead resource identifier.
*
* A hashed string is a compile-time tool that allows users to use
* human-readable identifers in the codebase while using their numeric
* counterparts at runtime.<br/>
* Because of that, a hashed string can also be used in constant expressions if
* required.
*/
class HashedString final {
struct ConstCharWrapper final {
// non-explicit constructor on purpose
constexpr ConstCharWrapper(const char *str) noexcept: str{str} {}
const char *str;
};
static constexpr std::uint64_t offset = 14695981039346656037u;
static constexpr std::uint64_t prime = 1099511628211u;
// FowlerNollVo hash function v. 1a - the good
static constexpr std::uint64_t helper(std::uint64_t partial, const char *str) noexcept {
return str[0] == 0 ? partial : helper((partial^str[0])*prime, str+1);
}
public:
/*! @brief Unsigned integer type. */
using hash_type = std::uint64_t;
/**
* @brief Constructs a hashed string from an array of const chars.
*
* Forcing template resolution avoids implicit conversions. An
* human-readable identifier can be anything but a plain, old bunch of
* characters.<br/>
* Example of use:
* @code{.cpp}
* HashedString sh{"my.png"};
* @endcode
*
* @tparam N Number of characters of the identifier.
* @param str Human-readable identifer.
*/
template <std::size_t N>
constexpr HashedString(const char (&str)[N]) noexcept
: hash{helper(offset, str)}, str{str}
{}
/**
* @brief Explicit constructor on purpose to avoid constructing a hashed
* string directly from a `const char *`.
*
* @param wrapper Helps achieving the purpose by relying on overloading.
*/
explicit constexpr HashedString(ConstCharWrapper wrapper) noexcept
: hash{helper(offset, wrapper.str)}, str{wrapper.str}
{}
/**
* @brief Returns the human-readable representation of a hashed string.
* @return The string used to initialize the instance.
*/
constexpr operator const char *() const noexcept { return str; }
/**
* @brief Returns the numeric representation of a hashed string.
* @return The numeric representation of the instance.
*/
constexpr operator hash_type() const noexcept { return hash; }
/**
* @brief Compares two hashed strings.
* @param other Hashed string with which to compare.
* @return True if the two hashed strings are identical, false otherwise.
*/
constexpr bool operator==(const HashedString &other) const noexcept {
return hash == other.hash;
}
private:
const hash_type hash;
const char *str;
};
/**
* @brief Compares two hashed strings.
* @param lhs A valid hashed string.
* @param rhs A valid hashed string.
* @return True if the two hashed strings are identical, false otherwise.
*/
constexpr bool operator!=(const HashedString &lhs, const HashedString &rhs) noexcept {
return !(lhs == rhs);
}
}
#endif // ENTT_CORE_HASHED_STRING_HPP

152
src/entt/entity/actor.hpp Normal file
View File

@@ -0,0 +1,152 @@
#ifndef ENTT_ENTITY_ACTOR_HPP
#define ENTT_ENTITY_ACTOR_HPP
#include <utility>
#include "registry.hpp"
namespace entt {
/**
* @brief Dedicated to those who aren't confident with entity-component systems.
*
* Tiny wrapper around a registry, for all those users that aren't confident
* with entity-component systems and prefer to iterate objects directly.
*
* @tparam Entity A valid entity type (see entt_traits for more details).
* @tparam Delta Type to use to provide elapsed time.
*/
template<typename Entity, typename Delta>
struct Actor {
/*! @brief Type of registry used internally. */
using registry_type = Registry<Entity>;
/*! @brief Type used to provide elapsed time. */
using delta_type = Delta;
/**
* @brief Constructs an actor by using the given registry.
* @param reg An entity-component system properly initialized.
*/
Actor(Registry<Entity> &reg)
: reg{reg}, entity{reg.create()}
{}
/*! @brief Default destructor. */
virtual ~Actor() {
reg.destroy(entity);
}
/*! @brief Default copy constructor. */
Actor(const Actor &) = default;
/*! @brief Default move constructor. */
Actor(Actor &&) = default;
/*! @brief Default copy assignment operator. @return This actor. */
Actor & operator=(const Actor &) = default;
/*! @brief Default move assignment operator. @return This actor. */
Actor & operator=(Actor &&) = default;
/**
* @brief Assigns the given component to an actor.
*
* A new instance of the given component is created and initialized with the
* arguments provided (the component must have a proper constructor or be of
* aggregate type). Then the component is assigned to the actor.<br/>
* In case the actor already has a component of the given type, it's
* replaced with the new one.
*
* @tparam Component Type of the component to create.
* @tparam Args Types of arguments to use to construct the component.
* @param args Parameters to use to initialize the component.
* @return A reference to the newly created component.
*/
template<typename Component, typename... Args>
Component & set(Args&&... args) {
return reg.template accomodate<Component>(entity, std::forward<Args>(args)...);
}
/**
* @brief Removes the given component from an actor.
* @tparam Component Type of the component to remove.
*/
template<typename Component>
void unset() {
reg.template remove<Component>(entity);
}
/**
* @brief Checks if an actor has the given component.
* @tparam Component Type of the component for which to perform the check.
* @return True if the actor has the component, false otherwise.
*/
template<typename Component>
bool has() const noexcept {
return reg.template has<Component>(entity);
}
/**
* @brief Returns a reference to the given component for an actor.
* @tparam Component Type of the component to get.
* @return A reference to the instance of the component owned by the entity.
*/
template<typename Component>
const Component & get() const noexcept {
return reg.template get<Component>(entity);
}
/**
* @brief Returns a reference to the given component for an actor.
* @tparam Component Type of the component to get.
* @return A reference to the instance of the component owned by the entity.
*/
template<typename Component>
Component & get() noexcept {
return const_cast<Component &>(const_cast<const Actor *>(this)->get<Component>());
}
/**
* @brief Returns a reference to the underlying registry.
* @return A reference to the underlying registry
*/
const registry_type & registry() const noexcept {
return reg;
}
/**
* @brief Returns a reference to the underlying registry.
* @return A reference to the underlying registry
*/
registry_type & registry() noexcept {
return const_cast<registry_type &>(const_cast<const Actor *>(this)->registry());
}
/**
* @brief Updates an actor, whatever it means to update it.
* @param delta Elapsed time.
*/
virtual void update(delta_type delta) = 0;
private:
registry_type &reg;
Entity entity;
};
/**
* @brief Default actor class.
*
* The default actor is the best choice for almost all the applications.<br/>
* Users should have a really good reason to choose something different.
*
* @tparam Delta Type to use to provide elapsed time.
*/
template<typename Delta>
using DefaultActor = Actor<std::uint32_t, Delta>;
}
#endif // ENTT_ENTITY_ACTOR_HPP

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>
@@ -102,6 +125,35 @@ class Registry {
return pool<Component>();
}
template<typename... Component>
SparseSet<Entity> & handler() {
static_assert(sizeof...(Component) > 1, "!");
const auto vtype = view_family::type<Component...>();
if(!(vtype < handlers.size())) {
handlers.resize(vtype + 1);
}
if(!handlers[vtype]) {
using accumulator_type = int[];
auto set = std::make_unique<SparseSet<Entity>>();
for(auto entity: view<Component...>()) {
set->construct(entity);
}
accumulator_type accumulator = {
(ensure<Component>().append(set.get(), &Registry::has<Component...>), 0)...
};
handlers[vtype] = std::move(set);
(void)accumulator;
}
return *handlers[vtype];
}
public:
/*! @brief Underlying entity identifier. */
using entity_type = typename traits_type::entity_type;
@@ -110,20 +162,18 @@ public:
/*! @brief Unsigned integer type. */
using size_type = std::size_t;
/*! @brief Default constructor, explicit on purpose. */
explicit Registry() = default;
/*! @brief Default destructor. */
~Registry() = default;
/*! @brief Default constructor. */
Registry() = default;
/*! @brief Copying a registry isn't allowed. */
Registry(const Registry &) = delete;
/*! @brief Moving a registry isn't allowed. */
Registry(Registry &&) = delete;
/*! @brief Default move constructor. */
Registry(Registry &&) = default;
/*! @brief Copying a registry isn't allowed. @return This registry. */
Registry & operator=(const Registry &) = delete;
/*! @brief Moving a registry isn't allowed. @return This registry. */
Registry & operator=(Registry &&) = delete;
/*! @brief Default move assignment operator. @return This registry. */
Registry & operator=(Registry &&) = default;
/**
* @brief Returns the number of existing components of the given type.
@@ -171,7 +221,7 @@ public:
}
/**
* @brief Verifies if the entity identifier still refers to a valid entity.
* @brief Verifies if an entity identifier still refers to a valid entity.
* @param entity An entity identifier, either valid or not.
* @return True if the identifier is still valid, false otherwise.
*/
@@ -181,16 +231,16 @@ public:
}
/**
* @brief Returns the version stored along with the given entity identifier.
* @brief Returns the version stored along with an entity identifier.
* @param entity An entity identifier, either valid or not.
* @return Version stored along with the given entity identifier.
*/
version_type version(entity_type entity) const noexcept {
return version_type((entity >> traits_type::version_shift) & traits_type::version_mask);
return version_type((entity >> traits_type::entity_shift) & traits_type::version_mask);
}
/**
* @brief Returns the actual version for the given entity identifier.
* @brief Returns the actual version for an entity identifier.
*
* In case entity identifers are stored around, this function can be used to
* know if they are still valid or the entity has been destroyed and
@@ -209,13 +259,14 @@ public:
version_type current(entity_type entity) const noexcept {
const auto entt = entity & traits_type::entity_mask;
assert(entt < entities.size());
return version_type((entities[entt] >> traits_type::version_shift) & traits_type::version_mask);
return version_type((entities[entt] >> traits_type::entity_shift) & traits_type::version_mask);
}
/**
* @brief Returns a new entity initialized with the given components.
*
* There are two kinds of entity identifiers:
*
* * Newly created ones in case no entities have been previously destroyed.
* * Recycled one with updated versions.
*
@@ -243,6 +294,7 @@ public:
* @brief Returns a new entity to which the given components are assigned.
*
* There are two kinds of entity identifiers:
*
* * Newly created ones in case no entities have been previously destroyed.
* * Recycled one with updated versions.
*
@@ -269,6 +321,7 @@ public:
* @brief Creates a new entity and returns it.
*
* There are two kinds of entity identifiers:
*
* * Newly created ones in case no entities have been previously destroyed.
* * Recycled one with updated versions.
*
@@ -287,7 +340,7 @@ public:
if(available.empty()) {
entity = entity_type(entities.size());
assert(entity < traits_type::entity_mask);
assert((entity >> traits_type::version_shift) == entity_type{});
assert((entity >> traits_type::entity_shift) == entity_type{});
entities.push_back(entity);
} else {
entity = available.back();
@@ -316,8 +369,8 @@ public:
assert(valid(entity));
const auto entt = entity & traits_type::entity_mask;
const auto version = 1 + ((entity >> traits_type::version_shift) & traits_type::version_mask);
const auto next = entt | (version << traits_type::version_shift);
const auto version = 1 + ((entity >> traits_type::entity_shift) & traits_type::version_mask);
const auto next = entt | (version << traits_type::entity_shift);
entities[entt] = next;
available.push_back(next);
@@ -329,7 +382,123 @@ public:
}
/**
* @brief Assigns the given component to the given entity.
* @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.
*
* A new instance of the given component is created and initialized with the
* arguments provided (the component must have a proper constructor or be of
@@ -342,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.
@@ -355,7 +524,7 @@ public:
}
/**
* @brief Removes the given component from the given entity.
* @brief Removes the given component from an entity.
*
* @warning
* Attempting to use an invalid entity or to remove a component from an
@@ -364,17 +533,17 @@ 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>
void remove(entity_type entity) {
assert(valid(entity));
return pool<Component>().destroy(entity);
pool<Component>().destroy(entity);
}
/**
* @brief Checks if the given entity has all the given components.
* @brief Checks if an entity has all the given components.
*
* @warning
* Attempting to use an invalid entity results in undefined behavior.<br/>
@@ -397,7 +566,7 @@ public:
}
/**
* @brief Gets a reference to the given component owned by the given entity.
* @brief Returns a reference to the given component for an entity.
*
* @warning
* Attempting to use an invalid entity or to get a component from an entity
@@ -406,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.
*/
@@ -417,7 +586,7 @@ public:
}
/**
* @brief Gets a reference to the given component owned by the given entity.
* @brief Returns a reference to the given component for an entity.
*
* @warning
* Attempting to use an invalid entity or to get a component from an entity
@@ -426,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.
*/
@@ -436,7 +605,7 @@ public:
}
/**
* @brief Replaces the given component for the given entity.
* @brief Replaces the given component for an entity.
*
* A new instance of the given component is created and initialized with the
* arguments provided (the component must have a proper constructor or be of
@@ -449,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.
@@ -462,7 +631,7 @@ public:
}
/**
* @brief Assigns or replaces the given component to the given entity.
* @brief Assigns or replaces the given component for an entity.
*
* Equivalent to the following snippet (pseudocode):
*
@@ -482,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.
@@ -499,7 +668,7 @@ public:
}
/**
* @brief Sorts the pool of the given component.
* @brief Sorts the pool of entities for the given component.
*
* The order of the elements in a pool is highly affected by assignements
* of components to entities and deletions. Components are arranged to
@@ -519,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>
@@ -559,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() {
@@ -568,7 +737,7 @@ public:
}
/**
* @brief Resets the given component for the given entity.
* @brief Resets the given component for an entity.
*
* If the entity has an instance of the component, this function removes the
* component from the entity. Otherwise it does nothing.
@@ -578,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>
@@ -600,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() {
@@ -616,7 +785,7 @@ public:
}
/**
* @brief Resets the whole registry.
* @brief Resets a whole registry.
*
* Destroys all the entities. After a call to `reset`, all the entities
* previously created are recycled with a new version number. In case entity
@@ -625,13 +794,28 @@ public:
*/
void reset() {
available.clear();
pools.clear();
for(auto &&entity: entities) {
const auto version = 1 + ((entity >> traits_type::version_shift) & traits_type::version_mask);
entity = (entity & traits_type::entity_mask) | (version << traits_type::version_shift);
const auto version = 1 + ((entity >> traits_type::entity_shift) & traits_type::version_mask);
entity = (entity & traits_type::entity_mask) | (version << traits_type::entity_shift);
available.push_back(entity);
}
for(auto &&handler: handlers) {
if(handler) {
handler->reset();
}
}
for(auto &&pool: pools) {
if(pool) {
pool->reset();
}
}
for(auto &&tag: tags) {
tag.reset();
}
}
/**
@@ -646,6 +830,7 @@ public:
*
* Standard views do their best to iterate the smallest set of candidate
* entites. In particular:
*
* * Single component views are incredibly fast and iterate a packed array
* of entities, all of which has the given component.
* * Multi component views look at the number of entities available for each
@@ -662,7 +847,7 @@ public:
* @see View<Entity, Component>
* @see PersistentView
*
* @tparam Component Type of the components used to construct the view.
* @tparam Component Type of components used to construct the view.
* @return A newly created standard view.
*/
template<typename... Component>
@@ -684,35 +869,53 @@ public:
* can be prepared with this function. Just use the same set of components
* that would have been used otherwise to contruct the view.
*
* @tparam Component Types of the components used to prepare the view.
* @tparam Component Types of components used to prepare the view.
*/
template<typename... Component>
void prepare() {
static_assert(sizeof...(Component) > 1, "!");
const auto vtype = view_family::type<Component...>();
handler<Component...>();
}
if(!(vtype < handlers.size())) {
handlers.resize(vtype + 1);
}
if(!handlers[vtype]) {
/**
* @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[];
auto handler = std::make_unique<SparseSet<Entity>>();
for(auto entity: view<Component...>()) {
handler->construct(entity);
}
accumulator_type accumulator = {
(ensure<Component>().append(*handler, &Registry::has<Component...>), 0)...
};
handlers[vtype] = std::move(handler);
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.
*
@@ -727,6 +930,7 @@ public:
* of components grows up and the most of the entities have all the given
* components.<br/>
* However they have also drawbacks:
*
* * Each kind of persistent view requires a dedicated data structure that
* is allocated within the registry and it increases memory pressure.
* * Internal data structures used to construct persistent views must be
@@ -746,19 +950,19 @@ public:
* @see View<Entity, Component>
* @see PersistentView
*
* @tparam Component Types of the components used to construct the view.
* @tparam Component Types of components used to construct the view.
* @return A newly created persistent view.
*/
template<typename... Component>
PersistentView<Entity, Component...> persistent() {
static_assert(sizeof...(Component) > 1, "!");
prepare<Component...>();
return PersistentView<Entity, Component...>{*handlers[view_family::type<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

@@ -87,6 +87,8 @@ class SparseSet<Entity> {
std::size_t pos;
};
static constexpr Entity in_use = 1 << traits_type::entity_shift;
public:
/*! @brief Underlying entity identifier. */
using entity_type = Entity;
@@ -97,24 +99,24 @@ public:
/*! @brief Input iterator type. */
using iterator_type = Iterator;
/*! @brief Default constructor, explicit on purpose. */
explicit SparseSet() noexcept = default;
/*! @brief Default constructor. */
SparseSet() noexcept = default;
/*! @brief Default destructor. */
virtual ~SparseSet() noexcept = default;
/*! @brief Copying a sparse set isn't allowed. */
SparseSet(const SparseSet &) = delete;
/*! @brief Default move constructor. */
SparseSet(SparseSet &&) = default;
/*! @brief Default destructor. */
virtual ~SparseSet() noexcept = default;
/*! @brief Copying a sparse set isn't allowed. @return This sparse set. */
SparseSet & operator=(const SparseSet &) = delete;
/*! @brief Default move assignment operator. @return This sparse set. */
SparseSet & operator=(SparseSet &&) = default;
/**
* @brief Returns the number of elements in the sparse set.
* @brief Returns the number of elements in a sparse set.
*
* The number of elements is also the size of the internal packed array.
* There is no guarantee that the internal sparse array has the same size.
@@ -128,8 +130,8 @@ public:
}
/**
* @brief Checks whether the sparse set is empty.
* @return True is the sparse set is empty, false otherwise.
* @brief Checks whether a sparse set is empty.
* @return True if the sparse set is empty, false otherwise.
*/
bool empty() const noexcept {
return direct.empty();
@@ -188,17 +190,18 @@ public:
}
/**
* @brief Checks if the sparse set contains the given entity.
* @brief Checks if a sparse set contains an entity.
* @param entity A valid entity identifier.
* @return True if the sparse set contains the entity, false otherwise.
*/
bool has(entity_type entity) const noexcept {
const auto entt = entity & traits_type::entity_mask;
return entt < reverse.size() && reverse[entt] < direct.size() && direct[reverse[entt]] == entity;
// the in-use control bit permits to avoid accessing the direct vector
return (entt < reverse.size()) && (reverse[entt] & in_use);
}
/**
* @brief Returns the position of the entity in the sparse set.
* @brief Returns the position of an entity in a sparse set.
*
* @warning
* Attempting to get the position of an entity that doesn't belong to the
@@ -211,11 +214,13 @@ public:
*/
pos_type get(entity_type entity) const noexcept {
assert(has(entity));
return reverse[entity & traits_type::entity_mask];
const auto entt = entity & traits_type::entity_mask;
// we must get rid of the in-use bit for it's not part of the position
return reverse[entt] & ~in_use;
}
/**
* @brief Assigns an entity to the sparse set.
* @brief Assigns an entity to a sparse set.
*
* @warning
* Attempting to assign an entity that already belongs to the sparse set
@@ -224,25 +229,23 @@ public:
* sparse set already contains the given entity.
*
* @param entity A valid entity identifier.
* @return The position of the entity in the internal packed array.
*/
pos_type construct(entity_type entity) {
void construct(entity_type entity) {
assert(!has(entity));
const auto entt = entity & traits_type::entity_mask;
if(!(entt < reverse.size())) {
reverse.resize(entt+1);
reverse.resize(entt+1, pos_type{});
}
const auto pos = pos_type(direct.size());
reverse[entt] = pos;
// we exploit the fact that pos_type is equal to entity_type and pos has
// traits_type::version_mask bits unused we can use to mark it as in-use
reverse[entt] = pos_type(direct.size()) | in_use;
direct.emplace_back(entity);
return pos;
}
/**
* @brief Removes the given entity from the sparse set.
* @brief Removes an entity from a sparse set.
*
* @warning
* Attempting to remove an entity that doesn't belong to the sparse set
@@ -256,14 +259,18 @@ public:
assert(has(entity));
const auto entt = entity & traits_type::entity_mask;
const auto back = direct.back() & traits_type::entity_mask;
const auto pos = reverse[entt];
reverse[back] = pos;
const auto pos = reverse[entt] & ~in_use;
// the order matters: if back and entt are the same (for the sparse set
// has size 1), switching the two lines below doesn't work as expected
reverse[back] = pos | in_use;
reverse[entt] = pos;
// swapping isn't required here, we are getting rid of the last element
direct[pos] = direct.back();
direct.pop_back();
}
/**
* @brief Swaps the position of the entities in the internal packed array.
* @brief Swaps the position of two entities in the internal packed array.
*
* For what it's worth, this function affects both the internal sparse array
* and the internal packed array. Users should not care of that anyway.
@@ -280,10 +287,11 @@ public:
virtual void swap(entity_type lhs, entity_type rhs) {
assert(has(lhs));
assert(has(rhs));
const auto le = lhs & traits_type::entity_mask;
const auto re = rhs & traits_type::entity_mask;
std::swap(direct[reverse[le]], direct[reverse[re]]);
std::swap(reverse[le], reverse[re]);
auto &le = reverse[lhs & traits_type::entity_mask];
auto &re = reverse[rhs & traits_type::entity_mask];
// we must get rid of the in-use bit for it's not part of the position
std::swap(direct[le & ~in_use], direct[re & ~in_use]);
std::swap(le, re);
}
/**
@@ -316,7 +324,7 @@ public:
}
/**
* @brief Sort entities according to their order in the given sparse set.
* @brief Sort entities according to their order in a sparse set.
*
* Entities that are part of both the sparse sets are ordered internally
* according to the order they have in `other`. All the other entities goes
@@ -363,7 +371,7 @@ public:
}
/**
* @brief Resets the sparse set.
* @brief Resets a sparse set.
*/
virtual void reset() {
reverse.clear();
@@ -371,7 +379,7 @@ public:
}
private:
std::vector<entity_type> reverse;
std::vector<pos_type> reverse;
std::vector<entity_type> direct;
};
@@ -404,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. */
@@ -414,8 +422,8 @@ public:
/*! @brief Input iterator type. */
using iterator_type = typename underlying_type::iterator_type;
/*! @brief Default constructor, explicit on purpose. */
explicit SparseSet() noexcept = default;
/*! @brief Default constructor. */
SparseSet() noexcept = default;
/*! @brief Copying a sparse set isn't allowed. */
SparseSet(const SparseSet &) = delete;
@@ -442,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();
}
@@ -461,12 +469,12 @@ public:
*
* @return A pointer to the array of objects.
*/
type * raw() noexcept {
object_type * raw() noexcept {
return instances.data();
}
/**
* @brief Returns the object associated to the given entity.
* @brief Returns the object associated to an entity.
*
* @warning
* Attempting to use an entity that doesn't belong to the sparse set results
@@ -477,12 +485,12 @@ 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)];
}
/**
* @brief Returns the object associated to the given entity.
* @brief Returns the object associated to an entity.
*
* @warning
* Attempting to use an entity that doesn't belong to the sparse set results
@@ -493,12 +501,12 @@ 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));
}
/**
* @brief Assigns an entity to the sparse set and constructs its object.
* @brief Assigns an entity to a sparse set and constructs its object.
*
* @warning
* Attempting to use an entity that already belongs to the sparse set
@@ -512,14 +520,15 @@ 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();
}
/**
* @brief Removes an entity from the sparse set and destroies its object.
* @brief Removes an entity from a sparse set and destroies its object.
*
* @warning
* Attempting to use an entity that doesn't belong to the sparse set results
@@ -530,13 +539,14 @@ public:
* @param entity A valid entity identifier.
*/
void destroy(entity_type entity) override {
// 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);
}
/**
* @brief Swaps the two entities and their objects.
* @brief Swaps two entities and their objects.
*
* @note
* This function doesn't swap objects between entities. It exchanges entity
@@ -557,7 +567,7 @@ public:
}
/**
* @brief Resets the sparse set.
* @brief Resets a sparse set.
*/
void reset() override {
underlying_type::reset();
@@ -565,7 +575,7 @@ public:
}
private:
std::vector<type> instances;
std::vector<object_type> instances;
};

View File

@@ -22,6 +22,7 @@ struct entt_traits;
* @brief Entity traits for a 16 bits entity identifier.
*
* A 16 bits entity identifier guarantees:
*
* * 12 bits for the entity number (up to 4k entities).
* * 4 bit for the version (resets in [0-15]).
*/
@@ -37,7 +38,7 @@ struct entt_traits<std::uint16_t> {
/*! @brief Mask to use to get the version out of an identifier. */
static constexpr auto version_mask = 0xF;
/*! @brief Extent of the entity number within an identifier. */
static constexpr auto version_shift = 12;
static constexpr auto entity_shift = 12;
};
@@ -45,6 +46,7 @@ struct entt_traits<std::uint16_t> {
* @brief Entity traits for a 32 bits entity identifier.
*
* A 32 bits entity identifier guarantees:
*
* * 24 bits for the entity number (suitable for almost all the games).
* * 8 bit for the version (resets in [0-255]).
*/
@@ -60,7 +62,7 @@ struct entt_traits<std::uint32_t> {
/*! @brief Mask to use to get the version out of an identifier. */
static constexpr auto version_mask = 0xFF;
/*! @brief Extent of the entity number within an identifier. */
static constexpr auto version_shift = 24;
static constexpr auto entity_shift = 24;
};
@@ -68,6 +70,7 @@ struct entt_traits<std::uint32_t> {
* @brief Entity traits for a 64 bits entity identifier.
*
* A 64 bits entity identifier guarantees:
*
* * 40 bits for the entity number (an indecently large number).
* * 24 bit for the version (an indecently large number).
*/
@@ -83,7 +86,7 @@ struct entt_traits<std::uint64_t> {
/*! @brief Mask to use to get the version out of an identifier. */
static constexpr auto version_mask = 0xFFFFFF;
/*! @brief Extent of the entity number within an identifier. */
static constexpr auto version_shift = 40;
static constexpr auto entity_shift = 40;
};

View File

@@ -70,6 +70,7 @@ public:
* @brief Constructs a persistent view around a dedicated pool of entities.
*
* A persistent view is created out of:
*
* * A dedicated pool of entities that is shared between all the persistent
* views of the same type.
* * A bunch of pools of components to which to refer to get instances.
@@ -77,7 +78,7 @@ public:
* @param view Shared reference to a dedicated pool of entities.
* @param pools References to pools of components.
*/
explicit PersistentView(view_type &view, pool_type<Component>&... pools) noexcept
PersistentView(view_type &view, pool_type<Component>&... pools) noexcept
: view{view}, pools{pools...}
{}
@@ -240,9 +241,9 @@ public:
*
* @note
* The shared pool of entities and thus its order is affected by the changes
* to each and every pool of components that it tracks. Therefore changes to
* the pools of components can quickly ruin the order imposed to the pool of
* entities shared between the persistent views.
* to each and every pool that it tracks. Therefore changes to those pools
* can quickly ruin the order imposed to the pool of entities shared between
* the persistent views.
*
* @tparam Comp Type of the component to use to impose the order.
*/
@@ -368,7 +369,7 @@ public:
* @param pool A reference to a pool of components.
* @param other Other references to pools of components.
*/
explicit View(pool_type<First> &pool, pool_type<Other>&... other) noexcept
View(pool_type<First> &pool, pool_type<Other>&... other) noexcept
: pools{pool, other...}, view{nullptr}
{
reset();
@@ -570,13 +571,13 @@ 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.
* @param pool A reference to a pool of components.
*/
explicit View(pool_type &pool) noexcept
View(pool_type &pool) noexcept
: pool{pool}
{}

View File

@@ -1,10 +1,17 @@
#include "core/family.hpp"
#include "core/hashed_string.hpp"
#include "core/ident.hpp"
#include "entity/actor.hpp"
#include "entity/registry.hpp"
#include "entity/sparse_set.hpp"
#include "entity/traits.hpp"
#include "entity/view.hpp"
#include "locator/locator.hpp"
#include "process/process.hpp"
#include "process/scheduler.hpp"
#include "resource/cache.hpp"
#include "resource/handle.hpp"
#include "resource/loader.hpp"
#include "signal/bus.hpp"
#include "signal/delegate.hpp"
#include "signal/emitter.hpp"

View File

@@ -0,0 +1,335 @@
#ifndef ENTT_PROCESS_PROCESS_HPP
#define ENTT_PROCESS_PROCESS_HPP
#include <type_traits>
#include <functional>
#include <utility>
namespace entt {
namespace {
struct BaseProcess {
enum class State: unsigned int {
UNINITIALIZED = 0,
RUNNING,
PAUSED,
SUCCEEDED,
FAILED,
ABORTED,
FINISHED
};
template<State state>
using tag = std::integral_constant<State, state>;
};
}
/**
* @brief Base class for processes.
*
* This class stays true to the CRTP idiom. Derived classes must specify what's
* the intended type for elapsed times.<br/>
* A process should expose publicly the following member functions whether
* required:
*
* * @code{.cpp}
* void update(Delta);
* @endcode
* It's invoked once per tick until a process is explicitly aborted or it
* terminates either with or without errors. Even though it's not mandatory to
* declare this member function, as a rule of thumb each process should at
* least define it to work properly.
*
* * @code{.cpp}
* void init();
* @endcode
* It's invoked at the first tick, immediately before an update.
*
* * @code{.cpp}
* void succeeded();
* @endcode
* It's invoked in case of success, immediately after an update and during the
* same tick.
*
* * @code{.cpp}
* void failed();
* @endcode
* It's invoked in case of errors, immediately after an update and during the
* same tick.
*
* * @code{.cpp}
* void aborted();
* @endcode
* It's invoked only if a process is explicitly aborted. There is no guarantee
* that it executes in the same tick, this depends solely on whether the
* process is aborted immediately or not.
*
* Derived classes can change the internal state of a process by invoking the
* `succeed` and `fail` protected member functions and even pause or unpause the
* process itself.
*
* @sa Scheduler
*
* @tparam Derived Actual type of process that extends the class template.
* @tparam Delta Type to use to provide elapsed time.
*/
template<typename Derived, typename Delta>
class Process: private BaseProcess {
template<typename Target = Derived>
auto tick(int, tag<State::UNINITIALIZED>)
-> decltype(std::declval<Target>().init()) {
static_cast<Target *>(this)->init();
}
template<typename Target = Derived>
auto tick(int, tag<State::RUNNING>, Delta delta)
-> decltype(std::declval<Target>().update(delta)) {
static_cast<Target *>(this)->update(delta);
}
template<typename Target = Derived>
auto tick(int, tag<State::SUCCEEDED>)
-> decltype(std::declval<Target>().succeeded()) {
static_cast<Target *>(this)->succeeded();
}
template<typename Target = Derived>
auto tick(int, tag<State::FAILED>)
-> decltype(std::declval<Target>().failed()) {
static_cast<Target *>(this)->failed();
}
template<typename Target = Derived>
auto tick(int, tag<State::ABORTED>)
-> decltype(std::declval<Target>().aborted()) {
static_cast<Target *>(this)->aborted();
}
template<State S, typename... Args>
void tick(char, tag<S>, Args&&...) {}
protected:
/**
* @brief Terminates a process with success if it's still alive.
*
* The function is idempotent and it does nothing if the process isn't
* alive.
*/
void succeed() noexcept {
if(alive()) {
current = State::SUCCEEDED;
}
}
/**
* @brief Terminates a process with errors if it's still alive.
*
* The function is idempotent and it does nothing if the process isn't
* alive.
*/
void fail() noexcept {
if(alive()) {
current = State::FAILED;
}
}
/**
* @brief Stops a process if it's in a running state.
*
* The function is idempotent and it does nothing if the process isn't
* running.
*/
void pause() noexcept {
if(current == State::RUNNING) {
current = State::PAUSED;
}
}
/**
* @brief Restarts a process if it's paused.
*
* The function is idempotent and it does nothing if the process isn't
* paused.
*/
void unpause() noexcept {
if(current == State::PAUSED) {
current = State::RUNNING;
}
}
public:
/*! @brief Type used to provide elapsed time. */
using delta_type = Delta;
/*! @brief Default destructor. */
~Process() noexcept {
static_assert(std::is_base_of<Process, Derived>::value, "!");
}
/**
* @brief Aborts a process if it's still alive.
*
* The function is idempotent and it does nothing if the process isn't
* alive.
*
* @param immediately Requests an immediate operation.
*/
void abort(bool immediately = false) noexcept {
if(alive()) {
current = State::ABORTED;
if(immediately) {
tick(0);
}
}
}
/**
* @brief Returns true if a process is either running or paused.
* @return True if the process is still alive, false otherwise.
*/
bool alive() const noexcept {
return current == State::RUNNING || current == State::PAUSED;
}
/**
* @brief Returns true if a process is already terminated.
* @return True if the process is terminated, false otherwise.
*/
bool dead() const noexcept {
return current == State::FINISHED;
}
/**
* @brief Returns true if a process is currently paused.
* @return True if the process is paused, false otherwise.
*/
bool paused() const noexcept {
return current == State::PAUSED;
}
/**
* @brief Returns true if a process terminated with errors.
* @return True if the process terminated with errors, false otherwise.
*/
bool rejected() const noexcept {
return stopped;
}
/**
* @brief Updates a process and its internal state if required.
* @param delta Elapsed time.
*/
void tick(Delta delta) {
switch (current) {
case State::UNINITIALIZED:
tick(0, tag<State::UNINITIALIZED>{});
current = State::RUNNING;
// no break on purpose, tasks are executed immediately
case State::RUNNING:
tick(0, tag<State::RUNNING>{}, delta);
default:
// suppress warnings
break;
}
// if it's dead, it must be notified and removed immediately
switch(current) {
case State::SUCCEEDED:
tick(0, tag<State::SUCCEEDED>{});
current = State::FINISHED;
break;
case State::FAILED:
tick(0, tag<State::FAILED>{});
current = State::FINISHED;
stopped = true;
break;
case State::ABORTED:
tick(0, tag<State::ABORTED>{});
current = State::FINISHED;
stopped = true;
break;
default:
// suppress warnings
break;
}
}
private:
State current{State::UNINITIALIZED};
bool stopped{false};
};
/**
* @brief Adaptor for lambdas and functors to turn them into processes.
*
* Lambdas and functors can't be used directly with a scheduler for they are not
* properly defined processes with managed life cycles.<br/>
* This class helps in filling the gap and turning lambdas and functors into
* full featured processes usable by a scheduler.
*
* The signature of the function call operator should be equivalent to the
* following:
*
* @code{.cpp}
* void(Delta delta, auto succeed, auto fail);
* @endcode
*
* Where:
*
* * `delta` is the elapsed time.
* * `succeed` is a function to call when a process terminates with success.
* * `fail` is a function to call when a process terminates with errors.
*
* The signature of the function call operator of both `succeed` and `fail`
* is equivalent to the following:
*
* @code{.cpp}
* void();
* @endcode
*
* Usually users shouldn't worry about creating adaptors. A scheduler will
* create them internally each and avery time a lambda or a functor is used as
* a process.
*
* @sa Process
* @sa Scheduler
*
* @tparam Func Actual type of process.
* @tparam Delta Type to use to provide elapsed time.
*/
template<typename Func, typename Delta>
struct ProcessAdaptor: Process<ProcessAdaptor<Func, Delta>, Delta>, private Func {
/**
* @brief Constructs a process adaptor from a lambda or a functor.
* @tparam Args Types of arguments to use to initialize the actual process.
* @param args Parameters to use to initialize the actual process.
*/
template<typename... Args>
ProcessAdaptor(Args&&... args)
: Func{std::forward<Args>(args)...}
{}
/**
* @brief Updates a process and its internal state if required.
* @param delta Elapsed time.
*/
void update(Delta delta) {
Func::operator()(delta, [this](){ this->succeed(); }, [this](){ this->fail(); });
}
};
}
#endif // ENTT_PROCESS_PROCESS_HPP

View File

@@ -0,0 +1,319 @@
#ifndef ENTT_PROCESS_SCHEDULER_HPP
#define ENTT_PROCESS_SCHEDULER_HPP
#include <vector>
#include <memory>
#include <utility>
#include <iterator>
#include <algorithm>
#include <type_traits>
#include "process.hpp"
namespace entt {
/**
* @brief Cooperative scheduler for processes.
*
* A cooperative scheduler runs processes and helps managing their life cycles.
*
* Each process is invoked once per tick. If a process terminates, it's
* removed automatically from the scheduler and it's never invoked again.<br/>
* A process can also have a child. In this case, the process is replaced with
* its child when it terminates if it returns with success. In case of errors,
* both the process and its child are discarded.
*
* Example of use (pseudocode):
*
* @code{.cpp}
* scheduler.attach([](auto delta, auto succeed, auto fail) {
* // code
* }).then<MyProcess>(arguments...);
* @endcode
*
* In order to invoke all scheduled processes, call the `update` member function
* passing it the elapsed time to forward to the tasks.
*
* @sa Process
*
* @tparam Delta Type to use to provide elapsed time.
*/
template<typename Delta>
class Scheduler final {
template<typename T>
struct tag { using type = T; };
struct ProcessHandler final {
using instance_type = std::unique_ptr<void, void(*)(void *)>;
using update_type = bool(*)(ProcessHandler &, Delta);
using abort_type = void(*)(ProcessHandler &, bool);
using next_type = std::unique_ptr<ProcessHandler>;
instance_type instance;
update_type update;
abort_type abort;
next_type next;
};
template<typename Lambda>
struct Then final: Lambda {
Then(Lambda &&lambda, ProcessHandler *handler)
: Lambda{std::forward<Lambda>(lambda)}, handler{handler}
{}
template<typename Proc, typename... Args>
decltype(auto) then(Args&&... args) && {
static_assert(std::is_base_of<Process<Proc, Delta>, Proc>::value, "!");
handler = Lambda::operator()(handler, tag<Proc>{}, std::forward<Args>(args)...);
return std::move(*this);
}
template<typename Func>
decltype(auto) then(Func &&func) && {
using Proc = ProcessAdaptor<std::decay_t<Func>, Delta>;
return std::move(*this).template then<Proc>(std::forward<Func>(func));
}
private:
ProcessHandler *handler;
};
template<typename Proc>
static bool update(ProcessHandler &handler, Delta delta) {
auto *process = static_cast<Proc *>(handler.instance.get());
process->tick(delta);
auto dead = process->dead();
if(dead) {
if(handler.next && !process->rejected()) {
handler = std::move(*handler.next);
dead = handler.update(handler, delta);
} else {
handler.instance.reset();
}
}
return dead;
}
template<typename Proc>
static void abort(ProcessHandler &handler, bool immediately) {
static_cast<Proc *>(handler.instance.get())->abort(immediately);
}
template<typename Proc>
static void deleter(void *proc) {
delete static_cast<Proc *>(proc);
}
auto then(ProcessHandler *handler) {
auto lambda = [this](ProcessHandler *handler, auto next, auto... args) {
using Proc = typename decltype(next)::type;
if(handler) {
auto proc = typename ProcessHandler::instance_type{ new Proc{std::forward<decltype(args)>(args)...}, &Scheduler::deleter<Proc> };
handler->next.reset(new ProcessHandler{std::move(proc), &Scheduler::update<Proc>, &Scheduler::abort<Proc>, nullptr});
handler = handler->next.get();
}
return handler;
};
return Then<decltype(lambda)>{std::move(lambda), handler};
}
public:
/*! @brief Unsigned integer type. */
using size_type = typename std::vector<ProcessHandler>::size_type;
/*! @brief Default constructor. */
Scheduler() noexcept= default;
/*! @brief Copying a scheduler isn't allowed. */
Scheduler(const Scheduler &) = delete;
/*! @brief Default move constructor. */
Scheduler(Scheduler &&) = default;
/*! @brief Copying a scheduler isn't allowed. @return This scheduler. */
Scheduler & operator=(const Scheduler &) = delete;
/*! @brief Default move assignament operator. @return This scheduler. */
Scheduler & operator=(Scheduler &&) = default;
/**
* @brief Number of processes currently scheduled.
* @return Number of processes currently scheduled.
*/
size_type size() const noexcept {
return handlers.size();
}
/**
* @brief Returns true if at least a process is currently scheduled.
* @return True if there are scheduled processes, false otherwise.
*/
bool empty() const noexcept {
return handlers.empty();
}
/**
* @brief Discards all scheduled processes.
*
* Processes aren't aborted. They are discarded along with their children
* and never executed again.
*/
void clear() {
handlers.clear();
}
/**
* @brief Schedules a process for the next tick.
*
* Returned value is an opaque object that can be used to attach a child to
* the given process. The child is automatically scheduled when the process
* terminates and only if the process returns with success.
*
* Example of use (pseudocode):
*
* @code{.cpp}
* // schedules a task in the form of a process class
* scheduler.attach<MyProcess>(arguments...)
* // appends a child in the form of a lambda function
* .then([](auto delta, auto succeed, auto fail) {
* // code
* })
* // appends a child in the form of another process class
* .then<MyOtherProcess>();
* @endcode
*
* @tparam Proc Type of process to schedule.
* @tparam Args Types of arguments to use to initialize the process.
* @param args Parameters to use to initialize the process.
* @return An opaque object to use to concatenate processes.
*/
template<typename Proc, typename... Args>
auto attach(Args&&... args) {
static_assert(std::is_base_of<Process<Proc, Delta>, Proc>::value, "!");
auto proc = typename ProcessHandler::instance_type{ new Proc{std::forward<Args>(args)...}, &Scheduler::deleter<Proc> };
ProcessHandler handler{std::move(proc), &Scheduler::update<Proc>, &Scheduler::abort<Proc>, nullptr};
handlers.push_back(std::move(handler));
return then(&handlers.back());
}
/**
* @brief Schedules a process for the next tick.
*
* A process can be either a lambda or a functor. The scheduler wraps both
* of them in a process adaptor internally.<br/>
* The signature of the function call operator should be equivalent to the
* following:
*
* @code{.cpp}
* void(Delta delta, auto succeed, auto fail);
* @endcode
*
* Where:
*
* * `delta` is the elapsed time.
* * `succeed` is a function to call when a process terminates with success.
* * `fail` is a function to call when a process terminates with errors.
*
* The signature of the function call operator of both `succeed` and `fail`
* is equivalent to the following:
*
* @code{.cpp}
* void();
* @endcode
*
* Returned value is an opaque object that can be used to attach a child to
* the given process. The child is automatically scheduled when the process
* terminates and only if the process returns with success.
*
* Example of use (pseudocode):
*
* @code{.cpp}
* // schedules a task in the form of a lambda function
* scheduler.attach([](auto delta, auto succeed, auto fail) {
* // code
* })
* // appends a child in the form of another lambda function
* .then([](auto delta, auto succeed, auto fail) {
* // code
* })
* // appends a child in the form of a process class
* .then<MyProcess>(arguments...);
* @endcode
*
* @sa ProcessAdaptor
*
* @tparam Func Type of process to schedule.
* @param func Either a lambda or a functor to use as a process.
* @return An opaque object to use to concatenate processes.
*/
template<typename Func>
auto attach(Func &&func) {
using Proc = ProcessAdaptor<std::decay_t<Func>, Delta>;
return attach<Proc>(std::forward<Func>(func));
}
/**
* @brief Updates all scheduled processes.
*
* All scheduled processes are executed in no specific order.<br/>
* If a process terminates with success, it's replaced with its child, if
* any. Otherwise, if a process terminates with an error, it's removed along
* with its child.
*
* @param delta Elapsed time.
*/
void update(Delta delta) {
bool clean = false;
for(auto i = handlers.size(); i > 0; --i) {
auto &handler = handlers[i-1];
const bool dead = handler.update(handler, delta);
clean = clean || dead;
}
if(clean) {
handlers.erase(std::remove_if(handlers.begin(), handlers.end(), [delta](auto &handler) {
return !handler.instance;
}), handlers.end());
}
}
/**
* @brief Aborts all scheduled processes.
*
* 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.
*
* @param immediately Requests an immediate operation.
*/
void abort(bool immediately = false) {
decltype(handlers) exec;
exec.swap(handlers);
std::for_each(exec.begin(), exec.end(), [immediately](auto &handler) {
handler.abort(handler, immediately);
});
std::move(handlers.begin(), handlers.end(), std::back_inserter(exec));
handlers.swap(exec);
}
private:
std::vector<ProcessHandler> handlers{};
};
}
#endif // ENTT_PROCESS_SCHEDULER_HPP

183
src/entt/resource/cache.hpp Normal file
View File

@@ -0,0 +1,183 @@
#ifndef ENTT_RESOURCE_CACHE_HPP
#define ENTT_RESOURCE_CACHE_HPP
#include <memory>
#include <utility>
#include <type_traits>
#include <unordered_map>
#include "../core/hashed_string.hpp"
#include "handle.hpp"
#include "loader.hpp"
namespace entt {
/**
* @brief Simple cache for resources of a given type.
*
* Minimal implementation of a cache for resources of a given type. It doesn't
* offer much functionalities but it's suitable for small or medium sized
* applications and can be freely inherited to add targeted functionalities for
* large sized applications.
*
* @tparam Resource Type of resources managed by a cache.
*/
template<typename Resource>
class ResourceCache {
using container_type = std::unordered_map<HashedString::hash_type, std::shared_ptr<Resource>>;
public:
/*! @brief Unsigned integer type. */
using size_type = typename container_type::size_type;
/*! @brief Type of resources managed by a cache. */
using resource_type = HashedString;
/*! @brief Default constructor. */
ResourceCache() = default;
/*! @brief Copying a cache isn't allowed. */
ResourceCache(const ResourceCache &) noexcept = delete;
/*! @brief Default move constructor. */
ResourceCache(ResourceCache &&) noexcept = default;
/*! @brief Copying a cache isn't allowed. @return This cache. */
ResourceCache & operator=(const ResourceCache &) noexcept = delete;
/*! @brief Default move assignment operator. @return This cache. */
ResourceCache & operator=(ResourceCache &&) noexcept = default;
/**
* @brief Number of resources managed by a cache.
* @return Number of resources currently stored.
*/
size_type size() const noexcept {
return resources.size();
}
/**
* @brief Returns true if a cache contains no resources, false otherwise.
* @return True if the cache contains no resources, false otherwise.
*/
bool empty() const noexcept {
return resources.empty();
}
/**
* @brief Clears a cache and discards all its resources.
*
* Handles are not invalidated and the memory used by a resource isn't
* freed as long as at least a handle keeps the resource itself alive.
*/
void clear() noexcept {
resources.clear();
}
/**
* @brief Loads the resource that corresponds to the given identifier.
*
* In case an identifier isn't already present in the cache, it loads its
* resource and stores it aside for future uses. Arguments are forwarded
* directly to the loader in order to construct properly the requested
* resource.
*
* @note
* If the identifier is already present in the cache, this function does
* nothing and the arguments are simply discarded.
*
* @tparam Loader Type of loader to use to load the resource if required.
* @tparam Args Types of arguments to use to load the resource if required.
* @param id Unique resource identifier.
* @param args Arguments to use to load the resource if required.
* @return True if the resource is ready to use, false otherwise.
*/
template<typename Loader, typename... Args>
bool load(resource_type id, Args&&... args) {
static_assert(std::is_base_of<ResourceLoader<Loader, Resource>, Loader>::value, "!");
bool loaded = true;
if(resources.find(id) == resources.cend()) {
std::shared_ptr<Resource> resource = Loader{}.get(std::forward<Args>(args)...);
loaded = (static_cast<bool>(resource) ? (resources[id] = std::move(resource), loaded) : false);
}
return loaded;
}
/**
* @brief Reloads a resource or loads it for the first time if not present.
*
* Equivalent to the following snippet (pseudocode):
*
* @code{.cpp}
* cache.discard(id);
* cache.load(id, args...);
* @endcode
*
* Arguments are forwarded directly to the loader in order to construct
* properly the requested resource.
*
* @tparam Loader Type of loader to use to load the resource.
* @tparam Args Types of arguments to use to load the resource.
* @param id Unique resource identifier.
* @param args Arguments to use to load the resource.
* @return True if the resource is ready to use, false otherwise.
*/
template<typename Loader, typename... Args>
void reload(resource_type id, Args&&... args) {
return (discard(id), load(id, std::forward<Args>(args)...));
}
/**
* @brief Creates a handle for the given resource identifier.
*
* A resource handle can be in a either valid or invalid state. In other
* terms, a resource handle is properly initialized with a resource if the
* cache contains the resource itself. Otherwise the returned handle is
* uninitialized and accessing it results in undefined behavior.
*
* @sa ResourceHandle
*
* @param id Unique resource identifier.
* @return A handle for the given resource.
*/
ResourceHandle<Resource> handle(resource_type id) const {
auto it = resources.find(id);
return { it == resources.end() ? nullptr : it->second };
}
/**
* @brief Checks if a cache contains the given identifier.
* @param id Unique resource identifier.
* @return True if the cache contains the resource, false otherwise.
*/
bool contains(resource_type id) const noexcept {
return !(resources.find(id) == resources.cend());
}
/**
* @brief Discards the resource that corresponds to the given identifier.
*
* Handles are not invalidated and the memory used by the resource isn't
* freed as long as at least a handle keeps the resource itself alive.
*
* @param id Unique resource identifier.
*/
void discard(resource_type id) noexcept {
auto it = resources.find(id);
if(it != resources.end()) {
resources.erase(it);
}
}
private:
container_type resources;
};
}
#endif // ENTT_RESOURCE_CACHE_HPP

View File

@@ -0,0 +1,115 @@
#ifndef ENTT_RESOURCE_HANDLE_HPP
#define ENTT_RESOURCE_HANDLE_HPP
#include <memory>
#include <utility>
#include <cassert>
namespace entt {
template<typename Resource>
class ResourceCache;
/**
* @brief Shared resource handle.
*
* A shared resource handle is a small class that wraps a resource and keeps it
* alive even if it's deleted from the cache. It can be either copied or
* moved. A handle shares a reference to the same resource with all the other
* handles constructed for the same identifier.<br/>
* As a rule of thumb, resources should never be copied nor moved. Handles are
* the way to go to keep references to them.
*
* @tparam Resource Type of resource managed by a handle.
*/
template<typename Resource>
class ResourceHandle final {
/*! @brief Resource handles are friends of their caches. */
friend class ResourceCache<Resource>;
ResourceHandle(std::shared_ptr<Resource> res) noexcept
: resource{std::move(res)}
{}
public:
/*! @brief Default copy constructor. */
ResourceHandle(const ResourceHandle &) noexcept = default;
/*! @brief Default move constructor. */
ResourceHandle(ResourceHandle &&) noexcept = default;
/*! @brief Default copy assignment operator. @return This handle. */
ResourceHandle & operator=(const ResourceHandle &) noexcept = default;
/*! @brief Default move assignment operator. @return This handle. */
ResourceHandle & operator=(ResourceHandle &&) noexcept = default;
/**
* @brief Gets a reference to the managed resource.
*
* @warning
* The behavior is undefined if the handle doesn't contain a resource.<br/>
* An assertion will abort the execution at runtime in debug mode if the
* handle is empty.
*
* @return A reference to the managed resource.
*/
const Resource & get() const noexcept {
assert(static_cast<bool>(resource));
return *resource;
}
/**
* @brief Casts a handle and gets a reference to the managed resource.
*
* @warning
* The behavior is undefined if the handle doesn't contain a resource.<br/>
* An assertion will abort the execution at runtime in debug mode if the
* handle is empty.
*/
inline operator const Resource & () const noexcept { return get(); }
/**
* @brief Dereferences a handle to obtain the managed resource.
*
* @warning
* The behavior is undefined if the handle doesn't contain a resource.<br/>
* An assertion will abort the execution at runtime in debug mode if the
* handle is empty.
*
* @return A reference to the managed resource.
*/
inline const Resource & operator *() const noexcept { return get(); }
/**
* @brief Gets a pointer to the managed resource from a handle .
*
* @warning
* The behavior is undefined if the handle doesn't contain a resource.<br/>
* An assertion will abort the execution at runtime in debug mode if the
* handle is empty.
*
* @return A pointer to the managed resource or `nullptr` if the handle
* contains no resource at all.
*/
inline const Resource * operator ->() const noexcept {
assert(static_cast<bool>(resource));
return resource.get();
}
/**
* @brief Returns true if the handle contains a resource, false otherwise.
*/
explicit operator bool() const { return static_cast<bool>(resource); }
private:
std::shared_ptr<Resource> resource;
};
}
#endif // ENTT_RESOURCE_HANDLE_HPP

View File

@@ -0,0 +1,62 @@
#ifndef ENTT_RESOURCE_LOADER_HPP
#define ENTT_RESOURCE_LOADER_HPP
#include <memory>
namespace entt {
template<typename Resource>
class ResourceCache;
/**
* @brief Base class for resource loaders.
*
* Resource loaders must inherit from this class and stay true to the CRTP
* idiom. Moreover, a resource loader must expose a public, const member
* function named `load` that accepts a variable number of arguments and return
* a shared pointer to the resource just created.<br/>
* As an example:
*
* @code{.cpp}
* struct MyResource {};
*
* struct MyLoader: entt::ResourceLoader<MyLoader, MyResource> {
* std::shared_ptr<MyResource> load(int) const {
* // use the integer value somehow
* return std::make_shared<MyResource>();
* }
* };
* @endcode
*
* In general, resource loaders should not have a state or retain data of any
* type. They should let the cache manage their resources instead.
*
* @note
* Base class and CRTP idiom aren't strictly required with the current
* implementation. One could argue that a cache can easily work with loaders of
* any type. However, future changes won't be breaking ones by forcing the use
* of a base class today and that's why the model is already in its place.
*
* @tparam Loader Type of the derived class.
* @tparam Resource Type of resource for which to use the loader.
*/
template<typename Loader, typename Resource>
class ResourceLoader {
/*! @brief Resource loaders are friends of their caches. */
friend class ResourceCache<Resource>;
template<typename... Args>
std::shared_ptr<Resource> get(Args&&... args) const {
return static_cast<const Loader *>(this)->load(std::forward<Args>(args)...);
}
};
}
#endif // ENTT_RESOURCE_LOADER_HPP

View File

@@ -46,21 +46,6 @@ public:
/*! @brief Unsigned integer type. */
using size_type = std::size_t;
/*! @brief Default constructor, explicit on purpose. */
explicit Bus() noexcept = default;
/*! @brief Default destructor. */
~Bus() noexcept = default;
/*! @brief Default copy constructor. */
Bus(const Bus &) = default;
/*! @brief Default move constructor. */
Bus(Bus &&) = default;
/*! @brief Default copy assignment operator. @return This bus. */
Bus & operator=(const Bus &) = default;
/*! @brief Default move assignment operator. @return This bus. */
Bus & operator=(Bus &&) = default;
/**
* @brief Unregisters all the member functions of an instance.
*
@@ -117,7 +102,7 @@ public:
}
/**
* @brief Returns false is at least a listener is connected to the bus.
* @brief Returns false if at least a listener is connected to the bus.
* @return True if the bus has no listeners connected, false otherwise.
*/
bool empty() const noexcept {
@@ -203,21 +188,6 @@ public:
/*! @brief Unsigned integer type. */
using size_type = typename signal_type::size_type;
/*! @brief Default constructor, explicit on purpose. */
explicit Bus() noexcept = default;
/*! @brief Default destructor. */
virtual ~Bus() noexcept = default;
/*! @brief Default copy constructor. */
Bus(const Bus &) = default;
/*! @brief Default move constructor. */
Bus(Bus &&) = default;
/*! @brief Default copy assignment operator. @return This bus. */
Bus & operator=(const Bus &) = default;
/*! @brief Default move assignment operator. @return This bus. */
Bus & operator=(Bus &&) = default;
/**
* @brief Unregisters member functions of instances.
*
@@ -259,7 +229,7 @@ public:
}
/**
* @brief Returns false is at least a listener is connected to the bus.
* @brief Returns false if at least a listener is connected to the bus.
* @return True if the bus has no listeners connected, false otherwise.
*/
bool empty() const noexcept {

View File

@@ -19,7 +19,7 @@ class Delegate;
/**
* @brief A delegate class to send around functions and member functions.
* @brief Utility class to send around functions and member functions.
*
* Unmanaged delegate for function pointers and member functions. Users of this
* class are in charge of disconnecting instances before deleting them.
@@ -49,8 +49,8 @@ class Delegate<Ret(Args...)> final {
}
public:
/*! @brief Default constructor, explicit on purpose. */
explicit Delegate() noexcept
/*! @brief Default constructor. */
Delegate() noexcept
: stub{std::make_pair(nullptr, &fallback)}
{}

View File

@@ -93,24 +93,11 @@ class Dispatcher final {
}
public:
/*! @brief Default constructor, explicit on purpose. */
explicit Dispatcher() noexcept
/*! @brief Default constructor. */
Dispatcher() noexcept
: wrappers{}, mode{false}
{}
/*! @brief Default destructor. */
~Dispatcher() = default;
/*! @brief Default copy constructor. */
Dispatcher(const Dispatcher &) = default;
/*! @brief Default move constructor. */
Dispatcher(Dispatcher &&) = default;
/*! @brief Default copy assignment operator. @return This dispatcher. */
Dispatcher & operator=(const Dispatcher &) = default;
/*! @brief Default move assignment operator. @return This dispatcher. */
Dispatcher & operator=(Dispatcher &&) = default;
/**
* @brief Registers a listener given in the form of a member function.
*

View File

@@ -156,16 +156,8 @@ public:
/** @brief Event emitters are friend classes of connections. */
friend class Emitter;
/*! @brief Default constructor, explicit on purpose. */
explicit Connection() = default;
/*! @brief Default copy constructor. */
Connection(const Connection &) = default;
/*! @brief Default move constructor. */
Connection(Connection &&) = default;
/*! @brief Default destructor. */
~Connection() = default;
/*! @brief Default constructor. */
Connection() noexcept = default;
/**
* @brief Creates a connection that wraps its underlying instance.
@@ -175,6 +167,11 @@ public:
: Handler<Event>::connection_type{std::move(conn)}
{}
/*! @brief Default copy constructor. */
Connection(const Connection &) = default;
/*! @brief Default move constructor. */
Connection(Connection &&) = default;
/**
* @brief Default copy assignament operator.
* @return This connection.
@@ -188,19 +185,19 @@ public:
Connection & operator=(Connection &&) = default;
};
/*! @brief Default constructor, explicit on purpose. */
explicit Emitter() noexcept = default;
/*! @brief Copying an emitter isn't allowed. */
Emitter(const Emitter &) = delete;
/*! @brief Default move constructor. */
Emitter(Emitter &&) = default;
/*! @brief Default constructor. */
Emitter() noexcept = default;
/*! @brief Default destructor. */
virtual ~Emitter() noexcept {
static_assert(std::is_base_of<Emitter<Derived>, Derived>::value, "!");
}
/*! @brief Copying an emitter isn't allowed. */
Emitter(const Emitter &) = delete;
/*! @brief Default move constructor. */
Emitter(Emitter &&) = default;
/*! @brief Copying an emitter isn't allowed. @return This emitter. */
Emitter & operator=(const Emitter &) = delete;
/*! @brief Default move assignament operator. @return This emitter. */

View File

@@ -91,11 +91,13 @@ class SigH;
* of this class are in charge of disconnecting instances before deleting them.
*
* This class serves mainly two purposes:
*
* * Creating signals used later to notify a bunch of listeners.
* * Collecting results from a set of functions like in a voting system.
*
* The default collector does nothing. To properly collect data, define and use
* a class that has a call operator the signature of which is `bool(Param)` and:
*
* * `Param` is a type to which `Ret` can be converted.
* * The return type is true if the handler must stop collecting data, false
* otherwise.
@@ -131,51 +133,6 @@ public:
template<typename Class>
using instance_type = Class *;
/*! @brief Default constructor, explicit on purpose. */
explicit SigH() noexcept = default;
/*! @brief Default destructor. */
~SigH() noexcept = default;
/**
* @brief Copy constructor, listeners are also connected to this signal.
* @param other A signal to use as source to initialize this instance.
*/
SigH(const SigH &other)
: calls{other.calls}
{}
/**
* @brief Default move constructor.
* @param other A signal to use as source to initialize this instance.
*/
SigH(SigH &&other): SigH{} {
swap(*this, other);
}
/**
* @brief Copy assignment operator.
*
* Listeners are also connected to this signal.
*
* @param other A signal to use as source to initialize this instance.
* @return This signal.
*/
SigH & operator=(const SigH &other) {
calls = other.calls;
return *this;
}
/**
* @brief Move assignment operator.
* @param other A signal to use as source to initialize this instance.
* @return This signal.
*/
SigH & operator=(SigH &&other) {
swap(*this, other);
return *this;
}
/**
* @brief Number of listeners connected to the signal.
* @return Number of listeners currently connected.
@@ -185,7 +142,7 @@ public:
}
/**
* @brief Returns false is at least a listener is connected to the signal.
* @brief Returns false if at least a listener is connected to the signal.
* @return True if the signal has no listeners connected, false otherwise.
*/
bool empty() const noexcept {

View File

@@ -70,51 +70,6 @@ public:
template<typename Class>
using instance_type = std::shared_ptr<Class>;
/*! @brief Default constructor, explicit on purpose. */
explicit Signal() noexcept = default;
/*! @brief Default destructor. */
~Signal() noexcept = default;
/**
* @brief Copy constructor, listeners are also connected to this signal.
* @param other A signal to use as source to initialize this instance.
*/
Signal(const Signal &other)
: calls{other.calls}
{}
/**
* @brief Default move constructor.
* @param other A signal to use as source to initialize this instance.
*/
Signal(Signal &&other): Signal{} {
swap(*this, other);
}
/**
* @brief Copy assignment operator.
*
* Listeners are also connected to this signal.
*
* @param other A signal to use as source to initialize this instance.
* @return This signal.
*/
Signal & operator=(const Signal &other) {
calls = other.calls;
return *this;
}
/**
* @brief Move assignment operator.
* @param other A signal to use as source to initialize this instance.
* @return This signal.
*/
Signal & operator=(Signal &&other) {
swap(*this, other);
return *this;
}
/**
* @brief Number of listeners connected to the signal.
* @return Number of listeners currently connected.
@@ -124,7 +79,7 @@ public:
}
/**
* @brief Returns false is at least a listener is connected to the signal.
* @brief Returns false if at least a listener is connected to the signal.
* @return True if the signal has no listeners connected, false otherwise.
*/
bool empty() const noexcept {

View File

@@ -24,6 +24,7 @@ add_executable(
core
$<TARGET_OBJECTS:odr>
entt/core/family.cpp
entt/core/hashed_string.cpp
entt/core/ident.cpp
)
target_link_libraries(core PRIVATE gtest_main Threads::Threads)
@@ -34,6 +35,7 @@ add_test(NAME core COMMAND core)
add_executable(
entity
$<TARGET_OBJECTS:odr>
entt/entity/actor.cpp
entt/entity/registry.cpp
entt/entity/sparse_set.cpp
entt/entity/view.cpp
@@ -51,7 +53,28 @@ add_executable(
target_link_libraries(locator PRIVATE gtest_main Threads::Threads)
add_test(NAME locator COMMAND locator)
# Test locator
# Test process
add_executable(
process
$<TARGET_OBJECTS:odr>
entt/process/process.cpp
entt/process/scheduler.cpp
)
target_link_libraries(process PRIVATE gtest_main Threads::Threads)
add_test(NAME process COMMAND process)
# Test resource
add_executable(
resource
$<TARGET_OBJECTS:odr>
entt/resource/resource.cpp
)
target_link_libraries(resource PRIVATE gtest_main Threads::Threads)
add_test(NAME resource COMMAND resource)
# Test signal
add_executable(
signal

View File

@@ -0,0 +1,37 @@
#include <gtest/gtest.h>
#include <entt/core/hashed_string.hpp>
constexpr bool check(const char *str) {
using hash_type = entt::HashedString::hash_type;
return (static_cast<hash_type>(entt::HashedString{str}) == entt::HashedString{str}
&& static_cast<const char *>(entt::HashedString{str}) == str
&& entt::HashedString{str} == entt::HashedString{str}
&& !(entt::HashedString{str} != entt::HashedString{str}));
}
TEST(HashedString, Constexprness) {
// how would you test a constepxr otherwise?
static_assert(check("foobar"), "!");
ASSERT_TRUE(true);
}
TEST(HashedString, Functionalities) {
using hash_type = entt::HashedString::hash_type;
const char *bar = "bar";
auto fooHs = entt::HashedString("foo");
auto barHs = entt::HashedString(bar);
ASSERT_NE(static_cast<hash_type>(fooHs), static_cast<hash_type>(barHs));
ASSERT_EQ(static_cast<const char *>(fooHs), "foo");
ASSERT_EQ(static_cast<const char *>(barHs), bar);
ASSERT_TRUE(fooHs == fooHs);
ASSERT_FALSE(fooHs == barHs);
entt::HashedString hs{"foobar"};
ASSERT_EQ(static_cast<hash_type>(hs), 0x85944171f73967e8);
}

View File

@@ -0,0 +1,57 @@
#include <functional>
#include <gtest/gtest.h>
#include <entt/entity/actor.hpp>
#include <entt/entity/registry.hpp>
struct TestActor: entt::DefaultActor<unsigned int> {
using entt::DefaultActor<unsigned int>::DefaultActor;
void update(unsigned int) {}
};
struct Position final {};
struct Velocity final {};
TEST(Actor, Functionalities) {
entt::DefaultRegistry registry;
TestActor *actor = new TestActor{registry};
const auto &cactor = *actor;
ASSERT_EQ(&registry, &actor->registry());
ASSERT_EQ(&registry, &cactor.registry());
ASSERT_TRUE(registry.empty<Position>());
ASSERT_TRUE(registry.empty<Velocity>());
ASSERT_FALSE(registry.empty());
ASSERT_FALSE(actor->has<Position>());
ASSERT_FALSE(actor->has<Velocity>());
const auto &position = actor->set<Position>();
ASSERT_EQ(&position, &actor->get<Position>());
ASSERT_EQ(&position, &cactor.get<Position>());
ASSERT_FALSE(registry.empty<Position>());
ASSERT_TRUE(registry.empty<Velocity>());
ASSERT_FALSE(registry.empty());
ASSERT_TRUE(actor->has<Position>());
ASSERT_FALSE(actor->has<Velocity>());
actor->unset<Position>();
ASSERT_TRUE(registry.empty<Position>());
ASSERT_TRUE(registry.empty<Velocity>());
ASSERT_FALSE(registry.empty());
ASSERT_FALSE(actor->has<Position>());
ASSERT_FALSE(actor->has<Velocity>());
actor->set<Position>();
actor->set<Velocity>();
ASSERT_FALSE(registry.empty());
ASSERT_FALSE(registry.empty<Position>());
ASSERT_FALSE(registry.empty<Velocity>());
delete actor;
ASSERT_TRUE(registry.empty());
ASSERT_TRUE(registry.empty<Position>());
ASSERT_TRUE(registry.empty<Velocity>());
}

View File

@@ -30,7 +30,7 @@ private:
std::chrono::time_point<std::chrono::system_clock> start;
};
TEST(DefaultRegistry, Construct) {
TEST(Benchmark, Construct) {
entt::DefaultRegistry registry;
std::cout << "Constructing 10000000 entities" << std::endl;
@@ -44,7 +44,7 @@ TEST(DefaultRegistry, Construct) {
timer.elapsed();
}
TEST(DefaultRegistry, Destroy) {
TEST(Benchmark, Destroy) {
entt::DefaultRegistry registry;
std::vector<entt::DefaultRegistry::entity_type> entities{};
@@ -63,7 +63,7 @@ TEST(DefaultRegistry, Destroy) {
timer.elapsed();
}
TEST(DefaultRegistry, IterateCreateDeleteSingleComponent) {
TEST(Benchmark, IterateCreateDeleteSingleComponent) {
entt::DefaultRegistry registry;
std::cout << "Looping 10000 times creating and deleting a random number of entities" << std::endl;
@@ -87,7 +87,7 @@ TEST(DefaultRegistry, IterateCreateDeleteSingleComponent) {
timer.elapsed();
}
TEST(DefaultRegistry, IterateSingleComponent10M) {
TEST(Benchmark, IterateSingleComponent10M) {
entt::DefaultRegistry registry;
std::cout << "Iterating over 10000000 entities, one component" << std::endl;
@@ -101,7 +101,7 @@ TEST(DefaultRegistry, IterateSingleComponent10M) {
timer.elapsed();
}
TEST(DefaultRegistry, IterateTwoComponents10M) {
TEST(Benchmark, IterateTwoComponents10M) {
entt::DefaultRegistry registry;
std::cout << "Iterating over 10000000 entities, two components" << std::endl;
@@ -115,7 +115,7 @@ TEST(DefaultRegistry, IterateTwoComponents10M) {
timer.elapsed();
}
TEST(DefaultRegistry, IterateTwoComponents10MHalf) {
TEST(Benchmark, IterateTwoComponents10MHalf) {
entt::DefaultRegistry registry;
std::cout << "Iterating over 10000000 entities, two components, half of the entities have all the components" << std::endl;
@@ -130,7 +130,7 @@ TEST(DefaultRegistry, IterateTwoComponents10MHalf) {
timer.elapsed();
}
TEST(DefaultRegistry, IterateTwoComponents10MOne) {
TEST(Benchmark, IterateTwoComponents10MOne) {
entt::DefaultRegistry registry;
std::cout << "Iterating over 10000000 entities, two components, only one entity has all the components" << std::endl;
@@ -145,7 +145,7 @@ TEST(DefaultRegistry, IterateTwoComponents10MOne) {
timer.elapsed();
}
TEST(DefaultRegistry, IterateTwoComponentsPersistent10M) {
TEST(Benchmark, IterateTwoComponentsPersistent10M) {
entt::DefaultRegistry registry;
registry.prepare<Position, Velocity>();
@@ -160,7 +160,7 @@ TEST(DefaultRegistry, IterateTwoComponentsPersistent10M) {
timer.elapsed();
}
TEST(DefaultRegistry, IterateTwoComponentsPersistent10MHalf) {
TEST(Benchmark, IterateTwoComponentsPersistent10MHalf) {
entt::DefaultRegistry registry;
registry.prepare<Position, Velocity>();
@@ -176,7 +176,7 @@ TEST(DefaultRegistry, IterateTwoComponentsPersistent10MHalf) {
timer.elapsed();
}
TEST(DefaultRegistry, IterateTwoComponentsPersistent10MOne) {
TEST(Benchmark, IterateTwoComponentsPersistent10MOne) {
entt::DefaultRegistry registry;
registry.prepare<Position, Velocity>();
@@ -192,7 +192,7 @@ TEST(DefaultRegistry, IterateTwoComponentsPersistent10MOne) {
timer.elapsed();
}
TEST(DefaultRegistry, IterateFiveComponents10M) {
TEST(Benchmark, IterateFiveComponents10M) {
entt::DefaultRegistry registry;
std::cout << "Iterating over 10000000 entities, five components" << std::endl;
@@ -206,7 +206,7 @@ TEST(DefaultRegistry, IterateFiveComponents10M) {
timer.elapsed();
}
TEST(DefaultRegistry, IterateTenComponents10M) {
TEST(Benchmark, IterateTenComponents10M) {
entt::DefaultRegistry registry;
std::cout << "Iterating over 10000000 entities, ten components" << std::endl;
@@ -220,7 +220,7 @@ TEST(DefaultRegistry, IterateTenComponents10M) {
timer.elapsed();
}
TEST(DefaultRegistry, IterateTenComponents10MHalf) {
TEST(Benchmark, IterateTenComponents10MHalf) {
entt::DefaultRegistry registry;
std::cout << "Iterating over 10000000 entities, ten components, half of the entities have all the components" << std::endl;
@@ -235,7 +235,7 @@ TEST(DefaultRegistry, IterateTenComponents10MHalf) {
timer.elapsed();
}
TEST(DefaultRegistry, IterateTenComponents10MOne) {
TEST(Benchmark, IterateTenComponents10MOne) {
entt::DefaultRegistry registry;
std::cout << "Iterating over 10000000 entities, ten components, only one entity has all the components" << std::endl;
@@ -250,7 +250,7 @@ TEST(DefaultRegistry, IterateTenComponents10MOne) {
timer.elapsed();
}
TEST(DefaultRegistry, IterateFiveComponentsPersistent10M) {
TEST(Benchmark, IterateFiveComponentsPersistent10M) {
entt::DefaultRegistry registry;
registry.prepare<Position, Velocity, Comp<1>, Comp<2>, Comp<3>>();
@@ -265,7 +265,7 @@ TEST(DefaultRegistry, IterateFiveComponentsPersistent10M) {
timer.elapsed();
}
TEST(DefaultRegistry, IterateTenComponentsPersistent10M) {
TEST(Benchmark, IterateTenComponentsPersistent10M) {
entt::DefaultRegistry registry;
registry.prepare<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
@@ -280,7 +280,7 @@ TEST(DefaultRegistry, IterateTenComponentsPersistent10M) {
timer.elapsed();
}
TEST(DefaultRegistry, IterateTenComponentsPersistent10MHalf) {
TEST(Benchmark, IterateTenComponentsPersistent10MHalf) {
entt::DefaultRegistry registry;
registry.prepare<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
@@ -296,7 +296,7 @@ TEST(DefaultRegistry, IterateTenComponentsPersistent10MHalf) {
timer.elapsed();
}
TEST(DefaultRegistry, IterateTenComponentsPersistent10MOne) {
TEST(Benchmark, IterateTenComponentsPersistent10MOne) {
entt::DefaultRegistry registry;
registry.prepare<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
@@ -312,7 +312,7 @@ TEST(DefaultRegistry, IterateTenComponentsPersistent10MOne) {
timer.elapsed();
}
TEST(DefaultRegistry, SortSingle) {
TEST(Benchmark, SortSingle) {
entt::DefaultRegistry registry;
std::vector<entt::DefaultRegistry::entity_type> entities{};
@@ -332,7 +332,7 @@ TEST(DefaultRegistry, SortSingle) {
timer.elapsed();
}
TEST(DefaultRegistry, SortMulti) {
TEST(Benchmark, SortMulti) {
entt::DefaultRegistry registry;
std::vector<entt::DefaultRegistry::entity_type> entities{};

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;

View File

@@ -10,7 +10,9 @@ TEST(SparseSetNoType, Functionalities) {
ASSERT_FALSE(set.has(0));
ASSERT_FALSE(set.has(42));
ASSERT_EQ(set.construct(42), 0u);
set.construct(42);
ASSERT_EQ(set.get(42), 0u);
ASSERT_FALSE(set.empty());
ASSERT_EQ(set.size(), 1u);
@@ -27,7 +29,9 @@ TEST(SparseSetNoType, Functionalities) {
ASSERT_FALSE(set.has(0));
ASSERT_FALSE(set.has(42));
ASSERT_EQ(set.construct(42), 0u);
set.construct(42);
ASSERT_EQ(set.get(42), 0u);
set.reset();
@@ -45,9 +49,13 @@ TEST(SparseSetNoType, Functionalities) {
TEST(SparseSetNoType, DataBeginEnd) {
entt::SparseSet<unsigned int> set;
ASSERT_EQ(set.construct(3), 0u);
ASSERT_EQ(set.construct(12), 1u);
ASSERT_EQ(set.construct(42), 2u);
set.construct(3);
set.construct(12);
set.construct(42);
ASSERT_EQ(set.get(3), 0u);
ASSERT_EQ(set.get(12), 1u);
ASSERT_EQ(set.get(42), 2u);
ASSERT_EQ(*(set.data() + 0u), 3u);
ASSERT_EQ(*(set.data() + 1u), 12u);
@@ -62,6 +70,12 @@ TEST(SparseSetNoType, DataBeginEnd) {
ASSERT_EQ(begin, end);
}
TEST(SparseSetWithType, AggregatesMustWork) {
struct AggregateType { int value; };
// the goal of this test is to enforce the requirements for aggregate types
entt::SparseSet<unsigned int, AggregateType>{}.construct(0, 42);
}
TEST(SparseSetWithType, Functionalities) {
entt::SparseSet<unsigned int, int> set;
@@ -71,8 +85,9 @@ TEST(SparseSetWithType, Functionalities) {
ASSERT_FALSE(set.has(0));
ASSERT_FALSE(set.has(42));
ASSERT_EQ(set.construct(42, 3), 3);
set.construct(42, 3);
ASSERT_EQ(set.get(42), 3);
ASSERT_FALSE(set.empty());
ASSERT_EQ(set.size(), 1u);
ASSERT_NE(set.begin(), set.end());
@@ -88,7 +103,9 @@ TEST(SparseSetWithType, Functionalities) {
ASSERT_FALSE(set.has(0));
ASSERT_FALSE(set.has(42));
ASSERT_EQ(set.construct(42, 12), 12);
set.construct(42, 12);
ASSERT_EQ(set.get(42), 12);
set.reset();
@@ -106,9 +123,13 @@ TEST(SparseSetWithType, Functionalities) {
TEST(SparseSetWithType, RawBeginEnd) {
entt::SparseSet<unsigned int, int> set;
ASSERT_EQ(set.construct(3, 3), 3);
ASSERT_EQ(set.construct(12, 6), 6);
ASSERT_EQ(set.construct(42, 9), 9);
set.construct(3, 3);
set.construct(12, 6);
set.construct(42, 9);
ASSERT_EQ(set.get(3), 3);
ASSERT_EQ(set.get(12), 6);
ASSERT_EQ(set.get(42), 9);
ASSERT_EQ(*(set.raw() + 0u), 3);
ASSERT_EQ(*(set.raw() + 1u), 6);
@@ -126,11 +147,17 @@ TEST(SparseSetWithType, RawBeginEnd) {
TEST(SparseSetWithType, SortOrdered) {
entt::SparseSet<unsigned int, int> set;
ASSERT_EQ(set.construct(12, 12), 12);
ASSERT_EQ(set.construct(42, 9), 9);
ASSERT_EQ(set.construct(7, 6), 6);
ASSERT_EQ(set.construct(3, 3), 3);
ASSERT_EQ(set.construct(9, 1), 1);
set.construct(12, 12);
set.construct(42, 9);
set.construct(7, 6);
set.construct(3, 3);
set.construct(9, 1);
ASSERT_EQ(set.get(12), 12);
ASSERT_EQ(set.get(42), 9);
ASSERT_EQ(set.get(7), 6);
ASSERT_EQ(set.get(3), 3);
ASSERT_EQ(set.get(9), 1);
set.sort([&set](auto lhs, auto rhs) {
return set.get(lhs) < set.get(rhs);
@@ -156,11 +183,17 @@ TEST(SparseSetWithType, SortOrdered) {
TEST(SparseSetWithType, SortReverse) {
entt::SparseSet<unsigned int, int> set;
ASSERT_EQ(set.construct(12, 1), 1);
ASSERT_EQ(set.construct(42, 3), 3);
ASSERT_EQ(set.construct(7, 6), 6);
ASSERT_EQ(set.construct(3, 9), 9);
ASSERT_EQ(set.construct(9, 12), 12);
set.construct(12, 1);
set.construct(42, 3);
set.construct(7, 6);
set.construct(3, 9);
set.construct(9, 12);
ASSERT_EQ(set.get(12), 1);
ASSERT_EQ(set.get(42), 3);
ASSERT_EQ(set.get(7), 6);
ASSERT_EQ(set.get(3), 9);
ASSERT_EQ(set.get(9), 12);
set.sort([&set](auto lhs, auto rhs) {
return set.get(lhs) < set.get(rhs);
@@ -186,11 +219,17 @@ TEST(SparseSetWithType, SortReverse) {
TEST(SparseSetWithType, SortUnordered) {
entt::SparseSet<unsigned int, int> set;
ASSERT_EQ(set.construct(12, 6), 6);
ASSERT_EQ(set.construct(42, 3), 3);
ASSERT_EQ(set.construct(7, 1), 1);
ASSERT_EQ(set.construct(3, 9), 9);
ASSERT_EQ(set.construct(9, 12), 12);
set.construct(12, 6);
set.construct(42, 3);
set.construct(7, 1);
set.construct(3, 9);
set.construct(9, 12);
ASSERT_EQ(set.get(12), 6);
ASSERT_EQ(set.get(42), 3);
ASSERT_EQ(set.get(7), 1);
ASSERT_EQ(set.get(3), 9);
ASSERT_EQ(set.get(9), 12);
set.sort([&set](auto lhs, auto rhs) {
return set.get(lhs) < set.get(rhs);
@@ -218,9 +257,13 @@ TEST(SparseSetWithType, RespectDisjoint) {
entt::SparseSet<unsigned int, int> rhs;
const auto &clhs = lhs;
ASSERT_EQ(lhs.construct(3, 3), 3);
ASSERT_EQ(lhs.construct(12, 6), 6);
ASSERT_EQ(lhs.construct(42, 9), 9);
lhs.construct(3, 3);
lhs.construct(12, 6);
lhs.construct(42, 9);
ASSERT_EQ(lhs.get(3), 3);
ASSERT_EQ(lhs.get(12), 6);
ASSERT_EQ(lhs.get(42), 9);
lhs.respect(rhs);
@@ -242,10 +285,15 @@ TEST(SparseSetWithType, RespectOverlap) {
entt::SparseSet<unsigned int, int> rhs;
const auto &clhs = lhs;
ASSERT_EQ(lhs.construct(3, 3), 3);
ASSERT_EQ(lhs.construct(12, 6), 6);
ASSERT_EQ(lhs.construct(42, 9), 9);
ASSERT_EQ(rhs.construct(12, 6), 6);
lhs.construct(3, 3);
lhs.construct(12, 6);
lhs.construct(42, 9);
rhs.construct(12, 6);
ASSERT_EQ(lhs.get(3), 3);
ASSERT_EQ(lhs.get(12), 6);
ASSERT_EQ(lhs.get(42), 9);
ASSERT_EQ(rhs.get(12), 6);
lhs.respect(rhs);
@@ -266,18 +314,31 @@ TEST(SparseSetWithType, RespectOrdered) {
entt::SparseSet<unsigned int, int> lhs;
entt::SparseSet<unsigned int, int> rhs;
ASSERT_EQ(lhs.construct(1, 0), 0);
ASSERT_EQ(lhs.construct(2, 0), 0);
ASSERT_EQ(lhs.construct(3, 0), 0);
ASSERT_EQ(lhs.construct(4, 0), 0);
ASSERT_EQ(lhs.construct(5, 0), 0);
lhs.construct(1, 0);
lhs.construct(2, 0);
lhs.construct(3, 0);
lhs.construct(4, 0);
lhs.construct(5, 0);
ASSERT_EQ(rhs.construct(6, 0), 0);
ASSERT_EQ(rhs.construct(1, 0), 0);
ASSERT_EQ(rhs.construct(2, 0), 0);
ASSERT_EQ(rhs.construct(3, 0), 0);
ASSERT_EQ(rhs.construct(4, 0), 0);
ASSERT_EQ(rhs.construct(5, 0), 0);
ASSERT_EQ(lhs.get(1), 0);
ASSERT_EQ(lhs.get(2), 0);
ASSERT_EQ(lhs.get(3), 0);
ASSERT_EQ(lhs.get(4), 0);
ASSERT_EQ(lhs.get(5), 0);
rhs.construct(6, 0);
rhs.construct(1, 0);
rhs.construct(2, 0);
rhs.construct(3, 0);
rhs.construct(4, 0);
rhs.construct(5, 0);
ASSERT_EQ(rhs.get(6), 0);
ASSERT_EQ(rhs.get(1), 0);
ASSERT_EQ(rhs.get(2), 0);
ASSERT_EQ(rhs.get(3), 0);
ASSERT_EQ(rhs.get(4), 0);
ASSERT_EQ(rhs.get(5), 0);
rhs.respect(lhs);
@@ -299,18 +360,31 @@ TEST(SparseSetWithType, RespectReverse) {
entt::SparseSet<unsigned int, int> lhs;
entt::SparseSet<unsigned int, int> rhs;
ASSERT_EQ(lhs.construct(1, 0), 0);
ASSERT_EQ(lhs.construct(2, 0), 0);
ASSERT_EQ(lhs.construct(3, 0), 0);
ASSERT_EQ(lhs.construct(4, 0), 0);
ASSERT_EQ(lhs.construct(5, 0), 0);
lhs.construct(1, 0);
lhs.construct(2, 0);
lhs.construct(3, 0);
lhs.construct(4, 0);
lhs.construct(5, 0);
ASSERT_EQ(rhs.construct(5, 0), 0);
ASSERT_EQ(rhs.construct(4, 0), 0);
ASSERT_EQ(rhs.construct(3, 0), 0);
ASSERT_EQ(rhs.construct(2, 0), 0);
ASSERT_EQ(rhs.construct(1, 0), 0);
ASSERT_EQ(rhs.construct(6, 0), 0);
ASSERT_EQ(lhs.get(1), 0);
ASSERT_EQ(lhs.get(2), 0);
ASSERT_EQ(lhs.get(3), 0);
ASSERT_EQ(lhs.get(4), 0);
ASSERT_EQ(lhs.get(5), 0);
rhs.construct(5, 0);
rhs.construct(4, 0);
rhs.construct(3, 0);
rhs.construct(2, 0);
rhs.construct(1, 0);
rhs.construct(6, 0);
ASSERT_EQ(rhs.get(5), 0);
ASSERT_EQ(rhs.get(4), 0);
ASSERT_EQ(rhs.get(3), 0);
ASSERT_EQ(rhs.get(2), 0);
ASSERT_EQ(rhs.get(1), 0);
ASSERT_EQ(rhs.get(6), 0);
rhs.respect(lhs);
@@ -332,18 +406,31 @@ TEST(SparseSetWithType, RespectUnordered) {
entt::SparseSet<unsigned int, int> lhs;
entt::SparseSet<unsigned int, int> rhs;
ASSERT_EQ(lhs.construct(1, 0), 0);
ASSERT_EQ(lhs.construct(2, 0), 0);
ASSERT_EQ(lhs.construct(3, 0), 0);
ASSERT_EQ(lhs.construct(4, 0), 0);
ASSERT_EQ(lhs.construct(5, 0), 0);
lhs.construct(1, 0);
lhs.construct(2, 0);
lhs.construct(3, 0);
lhs.construct(4, 0);
lhs.construct(5, 0);
ASSERT_EQ(rhs.construct(3, 0), 0);
ASSERT_EQ(rhs.construct(2, 0), 0);
ASSERT_EQ(rhs.construct(6, 0), 0);
ASSERT_EQ(rhs.construct(1, 0), 0);
ASSERT_EQ(rhs.construct(4, 0), 0);
ASSERT_EQ(rhs.construct(5, 0), 0);
ASSERT_EQ(lhs.get(1), 0);
ASSERT_EQ(lhs.get(2), 0);
ASSERT_EQ(lhs.get(3), 0);
ASSERT_EQ(lhs.get(4), 0);
ASSERT_EQ(lhs.get(5), 0);
rhs.construct(3, 0);
rhs.construct(2, 0);
rhs.construct(6, 0);
rhs.construct(1, 0);
rhs.construct(4, 0);
rhs.construct(5, 0);
ASSERT_EQ(rhs.get(3), 0);
ASSERT_EQ(rhs.get(2), 0);
ASSERT_EQ(rhs.get(6), 0);
ASSERT_EQ(rhs.get(1), 0);
ASSERT_EQ(rhs.get(4), 0);
ASSERT_EQ(rhs.get(5), 0);
rhs.respect(lhs);

View File

@@ -0,0 +1,162 @@
#include <gtest/gtest.h>
#include <entt/process/process.hpp>
struct FakeProcess: entt::Process<FakeProcess, int> {
using process_type = entt::Process<FakeProcess, int>;
void succeed() noexcept { process_type::succeed(); }
void fail() noexcept { process_type::fail(); }
void pause() noexcept { process_type::pause(); }
void unpause() noexcept { process_type::unpause(); }
void init() { initInvoked = true; }
void update(delta_type) { updateInvoked = true; }
void succeeded() { succeededInvoked = true; }
void failed() { failedInvoked = true; }
void aborted() { abortedInvoked = true; }
bool initInvoked{false};
bool updateInvoked{false};
bool succeededInvoked{false};
bool failedInvoked{false};
bool abortedInvoked{false};
};
TEST(Process, Basics) {
FakeProcess process;
ASSERT_FALSE(process.alive());
ASSERT_FALSE(process.dead());
ASSERT_FALSE(process.paused());
process.succeed();
process.fail();
process.abort();
process.pause();
process.unpause();
ASSERT_FALSE(process.alive());
ASSERT_FALSE(process.dead());
ASSERT_FALSE(process.paused());
process.tick(0);
ASSERT_TRUE(process.alive());
ASSERT_FALSE(process.dead());
ASSERT_FALSE(process.paused());
process.pause();
ASSERT_TRUE(process.alive());
ASSERT_FALSE(process.dead());
ASSERT_TRUE(process.paused());
process.unpause();
ASSERT_TRUE(process.alive());
ASSERT_FALSE(process.dead());
ASSERT_FALSE(process.paused());
}
TEST(Process, Succeeded) {
FakeProcess process;
process.tick(0);
process.succeed();
process.tick(0);
ASSERT_FALSE(process.alive());
ASSERT_TRUE(process.dead());
ASSERT_FALSE(process.paused());
ASSERT_TRUE(process.initInvoked);
ASSERT_TRUE(process.updateInvoked);
ASSERT_TRUE(process.succeededInvoked);
ASSERT_FALSE(process.failedInvoked);
ASSERT_FALSE(process.abortedInvoked);
}
TEST(Process, Fail) {
FakeProcess process;
process.tick(0);
process.fail();
process.tick(0);
ASSERT_FALSE(process.alive());
ASSERT_TRUE(process.dead());
ASSERT_FALSE(process.paused());
ASSERT_TRUE(process.initInvoked);
ASSERT_TRUE(process.updateInvoked);
ASSERT_FALSE(process.succeededInvoked);
ASSERT_TRUE(process.failedInvoked);
ASSERT_FALSE(process.abortedInvoked);
}
TEST(Process, AbortNextTick) {
FakeProcess process;
process.tick(0);
process.abort();
process.tick(0);
ASSERT_FALSE(process.alive());
ASSERT_TRUE(process.dead());
ASSERT_FALSE(process.paused());
ASSERT_TRUE(process.initInvoked);
ASSERT_TRUE(process.updateInvoked);
ASSERT_FALSE(process.succeededInvoked);
ASSERT_FALSE(process.failedInvoked);
ASSERT_TRUE(process.abortedInvoked);
}
TEST(Process, AbortImmediately) {
FakeProcess process;
process.tick(0);
process.abort(true);
ASSERT_FALSE(process.alive());
ASSERT_TRUE(process.dead());
ASSERT_FALSE(process.paused());
ASSERT_TRUE(process.initInvoked);
ASSERT_TRUE(process.updateInvoked);
ASSERT_FALSE(process.succeededInvoked);
ASSERT_FALSE(process.failedInvoked);
ASSERT_TRUE(process.abortedInvoked);
}
TEST(ProcessAdaptor, Resolved) {
bool updated = false;
auto lambda = [&updated](uint64_t, auto resolve, auto) {
ASSERT_FALSE(updated);
updated = true;
resolve();
};
auto process = entt::ProcessAdaptor<decltype(lambda), uint64_t>{lambda};
process.tick(0);
ASSERT_TRUE(process.dead());
ASSERT_TRUE(updated);
}
TEST(ProcessAdaptor, Rejected) {
bool updated = false;
auto lambda = [&updated](uint64_t, auto, auto rejected) {
ASSERT_FALSE(updated);
updated = true;
rejected();
};
auto process = entt::ProcessAdaptor<decltype(lambda), uint64_t>{lambda};
process.tick(0);
ASSERT_TRUE(process.rejected());
ASSERT_TRUE(updated);
}

View File

@@ -0,0 +1,113 @@
#include <functional>
#include <gtest/gtest.h>
#include <entt/process/scheduler.hpp>
#include <entt/process/process.hpp>
struct FooProcess: entt::Process<FooProcess, int> {
FooProcess(std::function<void()> onUpdate, std::function<void()> onAborted)
: onUpdate{onUpdate}, onAborted{onAborted}
{}
void update(delta_type) { onUpdate(); }
void aborted() { onAborted(); }
std::function<void()> onUpdate;
std::function<void()> onAborted;
};
struct SucceededProcess: entt::Process<SucceededProcess, int> {
void update(delta_type) {
ASSERT_FALSE(updated);
updated = true;
++invoked;
succeed();
}
static unsigned int invoked;
bool updated = false;
};
unsigned int SucceededProcess::invoked = 0;
struct FailedProcess: entt::Process<FailedProcess, int> {
void update(delta_type) {
ASSERT_FALSE(updated);
updated = true;
fail();
}
bool updated = false;
};
TEST(Scheduler, Functionalities) {
entt::Scheduler<int> scheduler{};
bool updated = false;
bool aborted = false;
ASSERT_EQ(scheduler.size(), entt::Scheduler<int>::size_type{});
ASSERT_TRUE(scheduler.empty());
scheduler.attach<FooProcess>(
[&updated](){ updated = true; },
[&aborted](){ aborted = true; }
);
ASSERT_NE(scheduler.size(), entt::Scheduler<int>::size_type{});
ASSERT_FALSE(scheduler.empty());
scheduler.update(0);
scheduler.abort(true);
ASSERT_TRUE(updated);
ASSERT_TRUE(aborted);
ASSERT_NE(scheduler.size(), entt::Scheduler<int>::size_type{});
ASSERT_FALSE(scheduler.empty());
scheduler.clear();
ASSERT_EQ(scheduler.size(), entt::Scheduler<int>::size_type{});
ASSERT_TRUE(scheduler.empty());
}
TEST(Scheduler, Then) {
entt::Scheduler<int> scheduler;
scheduler.attach<SucceededProcess>()
.then<SucceededProcess>()
.then<FailedProcess>()
.then<SucceededProcess>();
for(auto i = 0; i < 8; ++i) {
scheduler.update(0);
}
ASSERT_EQ(SucceededProcess::invoked, 2u);
}
TEST(Scheduler, Functor) {
entt::Scheduler<int> scheduler;
bool firstFunctor = false;
bool secondFunctor = false;
scheduler.attach([&firstFunctor](auto, auto resolve, auto){
ASSERT_FALSE(firstFunctor);
firstFunctor = true;
resolve();
}).then([&secondFunctor](auto, auto, auto reject){
ASSERT_FALSE(secondFunctor);
secondFunctor = true;
reject();
}).then([](auto...){
FAIL();
});
for(auto i = 0; i < 8; ++i) {
scheduler.update(0);
}
ASSERT_TRUE(firstFunctor);
ASSERT_TRUE(secondFunctor);
}

View File

@@ -0,0 +1,80 @@
#include <gtest/gtest.h>
#include <entt/resource/cache.hpp>
struct Resource { const int value; };
struct Loader: entt::ResourceLoader<Loader, Resource> {
std::shared_ptr<Resource> load(int value) const {
return std::shared_ptr<Resource>(new Resource{ value });
}
};
struct BrokenLoader: entt::ResourceLoader<BrokenLoader, Resource> {
std::shared_ptr<Resource> load(int) const {
return nullptr;
}
};
TEST(ResourceCache, Functionalities) {
entt::ResourceCache<Resource> cache;
constexpr auto hs1 = entt::HashedString{"res1"};
constexpr auto hs2 = entt::HashedString{"res2"};
ASSERT_EQ(cache.size(), entt::ResourceCache<Resource>::size_type{});
ASSERT_TRUE(cache.empty());
ASSERT_FALSE(cache.contains(hs1));
ASSERT_FALSE(cache.contains(hs2));
ASSERT_FALSE(cache.load<BrokenLoader>(hs1, 42));
ASSERT_EQ(cache.size(), entt::ResourceCache<Resource>::size_type{});
ASSERT_TRUE(cache.empty());
ASSERT_FALSE(cache.contains(hs1));
ASSERT_FALSE(cache.contains(hs2));
ASSERT_TRUE(cache.load<Loader>(hs1, 42));
ASSERT_NE(cache.size(), entt::ResourceCache<Resource>::size_type{});
ASSERT_FALSE(cache.empty());
ASSERT_TRUE(cache.contains(hs1));
ASSERT_FALSE(cache.contains(hs2));
ASSERT_EQ((*cache.handle(hs1)).value, 42);
ASSERT_TRUE(cache.load<Loader>(hs2, 42));
ASSERT_NE(cache.size(), entt::ResourceCache<Resource>::size_type{});
ASSERT_FALSE(cache.empty());
ASSERT_TRUE(cache.contains(hs1));
ASSERT_TRUE(cache.contains(hs2));
ASSERT_EQ((*cache.handle(hs1)).value, 42);
ASSERT_EQ(cache.handle(hs2)->value, 42);
ASSERT_NO_THROW(cache.discard(hs1));
ASSERT_FALSE(cache.contains(hs1));
ASSERT_TRUE(cache.contains(hs2));
ASSERT_EQ(cache.handle(hs2)->value, 42);
ASSERT_TRUE(cache.load<Loader>(hs1, 42));
ASSERT_NO_THROW(cache.clear());
ASSERT_EQ(cache.size(), entt::ResourceCache<Resource>::size_type{});
ASSERT_TRUE(cache.empty());
ASSERT_FALSE(cache.contains(hs1));
ASSERT_FALSE(cache.contains(hs2));
ASSERT_TRUE(cache.load<Loader>(hs1, 42));
ASSERT_NE(cache.size(), entt::ResourceCache<Resource>::size_type{});
ASSERT_FALSE(cache.empty());
ASSERT_TRUE(cache.handle(hs1));
ASSERT_FALSE(cache.handle(hs2));
ASSERT_TRUE(cache.handle(hs1));
ASSERT_EQ(&cache.handle(hs1).get(), &static_cast<const Resource &>(cache.handle(hs1)));
ASSERT_NO_THROW(cache.clear());
ASSERT_EQ(cache.size(), entt::ResourceCache<Resource>::size_type{});
ASSERT_TRUE(cache.empty());
}