Compare commits

..

64 Commits

Author SHA1 Message Date
Michele Caini
d14b0b6843 updated version 2018-05-30 22:46:47 +02:00
Michele Caini
f763c8a777 docs 2018-05-30 22:40:06 +02:00
Michele Caini
4231b040d8 documented an already existent feature (#86) 2018-05-30 22:19:04 +02:00
Michele Caini
67c587e673 updated README.md 2018-05-23 23:35:39 +02:00
Michele Caini
3978e24fd8 added CONTRIBUTING.md 2018-05-23 23:12:23 +02:00
Michele Caini
c45de0c032 tests + bug fixing 2018-05-23 12:28:10 +02:00
Michele Caini
ff935efea1 updated README 2018-05-23 12:27:53 +02:00
Michele Caini
808464f47d more on save/restore (#67) 2018-05-23 12:26:53 +02:00
Michele Caini
c986a6c4dd added clone functionality 2018-05-22 22:49:24 +02:00
Michele Caini
17c0479343 minor changes 2018-05-22 22:45:09 +02:00
Michele Caini
d93238912a workaround for an ICE of g++-5 (see #79) 2018-05-18 09:27:57 +02:00
Michele Caini
d6ef0956e6 performance improvements 2018-05-17 17:47:02 +02:00
Michele Caini
05db0aad29 fixed constness of iterators' member functions 2018-05-16 17:04:44 +02:00
Michele Caini
89bbdfa1fe benchmark: clean up 2018-05-16 16:59:45 +02:00
Michele Caini
871bda6198 fixed toc 2018-05-15 23:30:45 +02:00
Michele Caini
20732c9206 added const begin/end on views + tests 2018-05-15 22:51:47 +02:00
Michele Caini
2a34a3ebb6 fixed and added more tests for prototype 2018-05-15 22:31:27 +02:00
Michele Caini
3f04247a53 fixed errors with vs2017 + const begin/end on iterators for sparse set 2018-05-15 22:21:51 +02:00
Michele Caini
ca0a1f8f8b review: multi component standard view 2018-05-15 17:30:22 +02:00
Michele Caini
d8a9f0ca12 minor changes 2018-05-15 17:29:05 +02:00
Michele Caini
c2116b841e fixed typo 2018-05-15 14:16:24 +02:00
Michele Caini
700cf69f18 prefer a clean api instead of cumbersome ones 2018-05-11 23:19:11 +02:00
Michele Caini
ed5dee5218 fixed error with msvc - logic flipped upside down :-( 2018-05-11 22:51:50 +02:00
Michele Caini
b224dfdfac trying to work around msvc limitations 2018-05-11 22:25:03 +02:00
Michele Caini
e7da68547f fixed errors with clang 2018-05-11 22:24:10 +02:00
Michele Caini
9a785ceb2e added prototype class 2018-05-11 22:12:43 +02:00
Indi Kernick
fca1cd5a1c Implement entt::Prototype (#74)
first draft - to be reviewed
2018-05-08 13:37:21 +02:00
Michele Caini
24b862e32e more flexible 2018-05-06 13:58:50 +02:00
Michele Caini
2ec9043cf2 fixed comments 2018-05-04 17:18:53 +02:00
Michele Caini
1102d63469 fix #73 2018-05-02 22:50:55 +02:00
Michele Caini
b89f39d78c added has for tag + entity to test ownership 2018-05-02 17:07:20 +02:00
Michele Caini
7ba479c9c9 minor changes 2018-05-02 13:17:50 +02:00
Michele Caini
8ad6a2980c updated doc 2018-05-01 17:32:11 +02:00
Michele Caini
d3b6ed78d9 custom sort function (#72) 2018-04-30 17:14:36 +02:00
Michele Caini
8bd5605c2a now works also with VS2017 and GCC6 2018-04-27 23:19:08 +02:00
Michele Caini
08dc2fcf33 performance improvement 2018-04-27 15:27:16 +02:00
Michele Caini
caa8d16371 updated README 2018-04-24 14:01:18 +02:00
Michele Caini
bce92b3d85 updated README + AUTHORS 2018-04-22 23:23:15 +02:00
Michele Caini
67858bf300 faster accommodate 2018-04-22 13:32:38 +02:00
Michele Caini
7157e7e77d minor changes 2018-04-20 17:11:57 +02:00
Michele Caini
cf5074bdc5 added Registry::data/::raw 2018-04-20 16:23:39 +02:00
Michele Caini
fda44063ce removed Registry::persistent/Registry::raw 2018-04-20 15:32:30 +02:00
Michele Caini
f9becda02c more tests 2018-04-20 14:57:21 +02:00
Michele Caini
fb9fc952c6 more tests 2018-04-20 14:28:40 +02:00
Michele Caini
18451edfe9 review: views and sparse sets 2018-04-20 13:17:50 +02:00
Michele Caini
8c73cac72f added ENTT_NOEXCEPT macro to fully disable exception handling (ie required with UE4) 2018-04-18 16:36:14 +02:00
Michele Caini
c54cedf14b readme: updated benchmarks 2018-04-18 16:16:15 +02:00
Michele Caini
8ef4cdc9c3 improvements 2018-04-18 13:48:50 +02:00
Michele Caini
c0213e84f6 signals on tags 2018-04-16 17:14:22 +02:00
Michele Caini
29de6d89d4 improvements 2018-04-13 22:54:20 +02:00
Michele Caini
c9bf38ce36 fixed dispatcher + minor changes 2018-04-13 14:01:34 +02:00
Michele Caini
338eb75bab review: actor + tests 2018-04-12 08:18:10 +02:00
Michele Caini
31b1b453b0 updated doc and tests 2018-04-10 15:12:42 +02:00
Michele Caini
aaf0e145eb fixed - no more anonymous namespaces 2018-04-10 11:25:17 +02:00
Michele Caini
089b3e13fd no more anonymous namespaces 2018-04-10 09:00:15 +02:00
Michele Caini
e9da2ce12a (maybe) fixed 2018-04-10 08:45:54 +02:00
Michele Caini
92048ac17b removed signal + added dependency function(s) 2018-04-09 23:26:39 +02:00
Michele Caini
5e8561a578 more on tags and components 2018-04-09 08:14:35 +02:00
Michele Caini
d2f5e13074 typo 2018-04-08 23:25:32 +02:00
Michele Caini
820178f006 tag dispatching to disambiguate tags and components (#65) 2018-04-07 00:29:09 +02:00
Michele Caini
0a36a91e6d review + docs 2018-04-06 23:30:13 +02:00
Michele Caini
5013a92795 review snapshot/loaders 2018-04-06 22:08:23 +02:00
Michele Caini
d81ecfec32 removed extra create functions from the registry (#52) 2018-04-06 22:06:44 +02:00
Michele Caini
e99d7e2c3c signals on component creation/destruction (#62) 2018-04-06 22:04:58 +02:00
50 changed files with 4655 additions and 2900 deletions

View File

@@ -5,3 +5,5 @@ Michele Caini aka skypjack
# Contributors
Paolo Monteverde aka morbo84
David Nerjes aka DavidHamburg
Indi Kernick aka Kerndog73

View File

@@ -16,7 +16,7 @@ endif()
# Project configuration
#
project(entt VERSION 2.5.0)
project(entt VERSION 2.6.0)
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Debug)
@@ -120,6 +120,7 @@ add_custom_target(
SOURCES
appveyor.yml
AUTHORS
CONTRIBUTING
LICENSE
README.md
TODO

43
CONTRIBUTING Normal file
View File

@@ -0,0 +1,43 @@
# Contributing
First of all, thank you very much for taking the time to contribute to the
`EnTT` framework.<br/>
How to do it mostly depends on the type of contribution:
* If you have a question, **please** ensure there isn't already an answer for
you by searching on GitHub under
[issues](https://github.com/skypjack/entt/issues). Do not forget to search
also through the closed ones. If you are unable to find a proper answer, feel
free to [open a new issue](https://github.com/skypjack/entt/issues/new).
Usually, questions are marked as such and closed in a few days.
* If you want to fix a typo in the inline documentation or in the README file,
if you want to add some new sections or if you want to help me with the
language by reviewing what I wrote so far (I'm not a native speaker after
all), **please** open a new
[pull request](https://github.com/skypjack/entt/pulls) with your changes.
* If you found a bug, **please** ensure there isn't already an answer for you by
searching on GitHub under [issues](https://github.com/skypjack/entt/issues).
If you are unable to find an open issue addressing the problem, feel free to
[open a new one](https://github.com/skypjack/entt/issues/new). **Please**, do
not forget to carefully describe how to reproduce the problem, then add all
the informaion about the system on which you are experiencing it and point out
the version of `EnTT` you are using (tag or commit).
* If you found a bug and you wrote a patch to fix it, open a new
[pull request](https://github.com/skypjack/entt/pulls) with your code.
**Please**, add some tests to avoid regressions in future if possible, it
would be really appreciated. Note that the `EnTT` framework has a
[coverage at 100%](https://coveralls.io/github/skypjack/entt?branch=master)
(at least it was at 100% at the time I wrote this file) and this is the reason
for which you can be confident with using it in a production environment.
* If you want to propose a new feature and you know how to code it, **please**
do not issue directly a pull request. Before to do it,
[create a new issue](https://github.com/skypjack/entt/issues/new) to discuss
your proposal. Other users could be interested in your idea and the discussion
that will follow can refine it and therefore give us a better solution.
* If you want to request a new feature, I'm available for hiring. Take a look at
[my profile](https://github.com/skypjack) and feel free to write me.

733
README.md

File diff suppressed because it is too large Load Diff

12
TODO
View File

@@ -1,9 +1,13 @@
* custom allocators and EnTT allocator-aware in general (long term feature, I don't actually need it at the moment) - see #22
* to analyze, long term feature: systems organizer based on dependency graphs for implicit parallelism (I don't want to think anymore in future :-))
* scene management (I prefer the concept of spaces, that is a kind of scene anyway)
* blueprint registry - kind of factory to create entitites template for initialization (get rid of the extra versions of Registry::create)
* review doc: separate it in multiple md/dox files, reduce the readme to a minimum and provide users with links to the online documentation on gh-pages
* debugging tools (#60): the issue online already contains interesting tips on this, look at it
* signals on component creation/destruction: crtp + internal detection, probably it works - test it!!
* define a macro for the noexcept policy, so as to provide users with an easy way to disable exception handling
* dynamic view, useful for runtime ecs, can be filled with the desired pool at runtime and are not constrained to a compile-time list of components
* define basic reactive systems (track entities to which component is attached, track entities from which component is removed, and so on)
* define systems as composable mixins (initializazion, reactive, update, whatever) with flexible auto-detected arguments (registry, views, etc)
* create dedicated flat map based on types implementation (sort of "type map") for types to use within the registry and so on...
* ease the assignment of tags as string (use a template class with a non-type template parameter behind the scene)
* improve CMake interface, see mail from Malte
* is registry/utility.hpp really required?
* "singleton mode" for tags (see #66)
* AOB

10
src/entt/config/config.h Normal file
View File

@@ -0,0 +1,10 @@
#ifndef ENTT_CONFIG_CONFIG_H
#define ENTT_CONFIG_CONFIG_H
#ifndef ENTT_NOEXCEPT
#define ENTT_NOEXCEPT noexcept
#endif
#endif // ENTT_CONFIG_CONFIG_H

View File

@@ -0,0 +1,76 @@
#ifndef ENTT_CORE_ALGORITHM_HPP
#define ENTT_CORE_ALGORITHM_HPP
#include <functional>
#include <algorithm>
#include <utility>
namespace entt {
/**
* @brief Function object to wrap `std::sort` in a class type.
*
* Unfortunately, `std::sort` cannot be passed as template argument to a class
* template or a function template.<br/>
* This class fills the gap by wrapping some flavors of `std::sort` in a
* function object.
*/
struct StdSort {
/**
* @brief Sorts the element in a range.
*
* Sorts the element in a range using the given binary comparison function.
*
* @tparam It Type of random access iterator.
* @tparam Compare Type of comparison function object.
* @param first An iterator to the first element of the range to sort.
* @param last An iterator past the last element of the range to sort.
* @param compare A valid comparison function object.
*/
template<typename It, typename Compare = std::less<>>
void operator()(It first, It last, Compare compare = Compare{}) {
std::sort(std::move(first), std::move(last), std::move(compare));
}
};
/*! @brief Function object for performing insertion sort. */
struct InsertionSort {
/**
* @brief Sorts the element in a range.
*
* Sorts the element in a range using the given binary comparison function.
*
* @tparam It Type of random access iterator.
* @tparam Compare Type of comparison function object.
* @param first An iterator to the first element of the range to sort.
* @param last An iterator past the last element of the range to sort.
* @param compare A valid comparison function object.
*/
template<typename It, typename Compare = std::less<>>
void operator()(It first, It last, Compare compare = Compare{}) {
auto it = first + 1;
while(it != last) {
auto value = *it;
auto pre = it;
while(pre != first && compare(value, *(pre-1))) {
*pre = *(pre-1);
--pre;
}
*pre = value;
++it;
}
}
};
}
#endif // ENTT_CORE_ALGORITHM_HPP

View File

@@ -5,6 +5,7 @@
#include<type_traits>
#include<cstddef>
#include<atomic>
#include "../config/config.h"
namespace entt {
@@ -22,7 +23,7 @@ class Family {
static std::atomic<std::size_t> identifier;
template<typename...>
static std::size_t family() noexcept {
static std::size_t family() ENTT_NOEXCEPT {
static const std::size_t value = identifier.fetch_add(1);
return value;
}
@@ -36,7 +37,7 @@ public:
* @return Statically generated unique identifier for the given type.
*/
template<typename... Type>
inline static family_type type() noexcept {
inline static family_type type() ENTT_NOEXCEPT {
return family<std::decay_t<Type>...>();
}
};

View File

@@ -4,6 +4,7 @@
#include <cstddef>
#include <cstdint>
#include "../config/config.h"
namespace entt {
@@ -21,7 +22,7 @@ namespace entt {
class HashedString final {
struct ConstCharWrapper final {
// non-explicit constructor on purpose
constexpr ConstCharWrapper(const char *str) noexcept: str{str} {}
constexpr ConstCharWrapper(const char *str) ENTT_NOEXCEPT: str{str} {}
const char *str;
};
@@ -29,7 +30,7 @@ class HashedString final {
static constexpr std::uint64_t prime = 1099511628211ull;
// FowlerNollVo hash function v. 1a - the good
static constexpr std::uint64_t helper(std::uint64_t partial, const char *str) noexcept {
static constexpr std::uint64_t helper(std::uint64_t partial, const char *str) ENTT_NOEXCEPT {
return str[0] == 0 ? partial : helper((partial^str[0])*prime, str+1);
}
@@ -52,7 +53,7 @@ public:
* @param str Human-readable identifer.
*/
template <std::size_t N>
constexpr HashedString(const char (&str)[N]) noexcept
constexpr HashedString(const char (&str)[N]) ENTT_NOEXCEPT
: hash{helper(offset, str)}, str{str}
{}
@@ -62,7 +63,7 @@ public:
*
* @param wrapper Helps achieving the purpose by relying on overloading.
*/
explicit constexpr HashedString(ConstCharWrapper wrapper) noexcept
explicit constexpr HashedString(ConstCharWrapper wrapper) ENTT_NOEXCEPT
: hash{helper(offset, wrapper.str)}, str{wrapper.str}
{}
@@ -70,20 +71,20 @@ public:
* @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; }
constexpr operator const char *() const ENTT_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; }
constexpr operator hash_type() const ENTT_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 {
constexpr bool operator==(const HashedString &other) const ENTT_NOEXCEPT {
return hash == other.hash;
}
@@ -99,7 +100,7 @@ private:
* @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 {
constexpr bool operator!=(const HashedString &lhs, const HashedString &rhs) ENTT_NOEXCEPT {
return !(lhs == rhs);
}

View File

@@ -10,7 +10,13 @@
namespace entt {
namespace {
namespace internal {
/**
* @cond TURN_OFF_DOXYGEN
* Internal details not to be documented.
*/
template<typename... Types>
@@ -47,6 +53,12 @@ private:
};
/**
* Internal details not to be documented.
* @endcond TURN_OFF_DOXYGEN
*/
}
@@ -87,7 +99,7 @@ private:
* @tparam Types List of types for which to generate identifiers.
*/
template<typename... Types>
constexpr auto ident = Identifier<std::decay_t<Types>...>{std::make_index_sequence<sizeof...(Types)>{}};
constexpr auto ident = internal::Identifier<std::decay_t<Types>...>{std::make_index_sequence<sizeof...(Types)>{}};
}

View File

@@ -2,7 +2,9 @@
#define ENTT_ENTITY_ACTOR_HPP
#include <cassert>
#include <utility>
#include "../config/config.h"
#include "registry.hpp"
@@ -16,28 +18,25 @@ namespace entt {
* 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>
template<typename Entity>
struct Actor {
/*! @brief Type of registry used internally. */
using registry_type = Registry<Entity>;
/*! @brief Underlying entity identifier. */
using entity_type = 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()}
: reg{reg}, entt{reg.create()}
{}
/*! @brief Default destructor. */
virtual ~Actor() {
reg.destroy(entity);
reg.destroy(entt);
}
/*! @brief Default copy constructor. */
@@ -50,6 +49,24 @@ struct Actor {
/*! @brief Default move assignment operator. @return This actor. */
Actor & operator=(Actor &&) = default;
/**
* @brief Assigns the given tag to an actor.
*
* A new instance of the given tag is created and initialized with the
* arguments provided (the tag must have a proper constructor or be of
* aggregate type). Then the tag is removed from its previous owner (if any)
* and assigned to the actor.
*
* @tparam Tag Type of the tag to create.
* @tparam Args Types of arguments to use to construct the tag.
* @param args Parameters to use to initialize the tag.
* @return A reference to the newly created tag.
*/
template<typename Tag, typename... Args>
Tag & assign(tag_t, Args &&... args) {
return (reg.template remove<Tag>(), reg.template assign<Tag>(tag_t{}, entt, std::forward<Args>(args)...));
}
/**
* @brief Assigns the given component to an actor.
*
@@ -65,8 +82,18 @@ struct Actor {
* @return A reference to the newly created component.
*/
template<typename Component, typename... Args>
Component & set(Args &&... args) {
return reg.template accommodate<Component>(entity, std::forward<Args>(args)...);
Component & assign(Args &&... args) {
return reg.template accommodate<Component>(entt, std::forward<Args>(args)...);
}
/**
* @brief Removes the given tag from an actor.
* @tparam Tag Type of the tag to remove.
*/
template<typename Tag>
void remove(tag_t) {
assert(has<Tag>(tag_t{}));
reg.template remove<Tag>();
}
/**
@@ -74,8 +101,18 @@ struct Actor {
* @tparam Component Type of the component to remove.
*/
template<typename Component>
void unset() {
reg.template remove<Component>(entity);
void remove() {
reg.template remove<Component>(entt);
}
/**
* @brief Checks if an actor owns the given tag.
* @tparam Tag Type of the tag for which to perform the check.
* @return True if the actor owns the tag, false otherwise.
*/
template<typename Tag>
bool has(tag_t) const ENTT_NOEXCEPT {
return (reg.template has<Tag>() && (reg.template attachee<Tag>() == entt));
}
/**
@@ -84,55 +121,78 @@ struct Actor {
* @return True if the actor has the component, false otherwise.
*/
template<typename Component>
bool has() const noexcept {
return reg.template has<Component>(entity);
bool has() const ENTT_NOEXCEPT {
return reg.template has<Component>(entt);
}
/**
* @brief Returns a reference to the given tag for an actor.
* @tparam Tag Type of the tag to get.
* @return A reference to the instance of the tag owned by the actor.
*/
template<typename Tag>
const Tag & get(tag_t) const ENTT_NOEXCEPT {
assert(has<Tag>(tag_t{}));
return reg.template get<Tag>();
}
/**
* @brief Returns a reference to the given tag for an actor.
* @tparam Tag Type of the tag to get.
* @return A reference to the instance of the tag owned by the actor.
*/
template<typename Tag>
inline Tag & get(tag_t) ENTT_NOEXCEPT {
return const_cast<Tag &>(const_cast<const Actor *>(this)->get<Tag>(tag_t{}));
}
/**
* @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.
* @return A reference to the instance of the component owned by the actor.
*/
template<typename Component>
const Component & get() const noexcept {
return reg.template get<Component>(entity);
const Component & get() const ENTT_NOEXCEPT {
return reg.template get<Component>(entt);
}
/**
* @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.
* @return A reference to the instance of the component owned by the actor.
*/
template<typename Component>
Component & get() noexcept {
inline Component & get() ENTT_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
* @return A reference to the underlying registry.
*/
const registry_type & registry() const noexcept {
const registry_type & registry() const ENTT_NOEXCEPT {
return reg;
}
/**
* @brief Returns a reference to the underlying registry.
* @return A reference to the underlying registry
* @return A reference to the underlying registry.
*/
registry_type & registry() noexcept {
inline registry_type & registry() ENTT_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.
* @brief Returns the entity associated with an actor.
* @return The entity associated with the actor.
*/
virtual void update(delta_type delta) = 0;
entity_type entity() const ENTT_NOEXCEPT {
return entt;
}
private:
registry_type &reg;
Entity entity;
Entity entt;
};
@@ -141,11 +201,8 @@ private:
*
* 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<DefaultRegistry::entity_type, Delta>;
using DefaultActor = Actor<DefaultRegistry::entity_type>;
}

View File

@@ -0,0 +1,84 @@
#ifndef ENTT_ENTITY_HELPER_HPP
#define ENTT_ENTITY_HELPER_HPP
#include "../signal/sigh.hpp"
#include "registry.hpp"
#include "utility.hpp"
namespace entt {
/**
* @brief Dependency function prototype.
*
* A _dependency function_ is a built-in listener to use to automatically assign
* components to an entity when a type has a dependency on some other types.
*
* This is a prototype function to use to create dependencies.<br/>
* It isn't intended for direct use, although nothing forbids using it freely.
*
* @tparam Entity A valid entity type (see entt_traits for more details).
* @tparam Component Types of components to assign to an entity if triggered.
* @param registry A valid reference to a registry.
* @param entity A valid entity identifier.
*/
template<typename Entity, typename... Component>
void dependency(Registry<Entity> &registry, const Entity entity) {
using accumulator_type = int[];
accumulator_type accumulator = { ((registry.template has<Component>(entity) ? void() : (registry.template assign<Component>(entity), void())), 0)... };
(void)accumulator;
}
/**
* @brief Connects a dependency function to the given sink.
*
* A _dependency function_ is a built-in listener to use to automatically assign
* components to an entity when a type has a dependency on some other types.
*
* The following adds components `AType` and `AnotherType` whenever `MyType` is
* assigned to an entity:
* @code{.cpp}
* entt::DefaultRegistry registry;
* entt::dependency<AType, AnotherType>(registry.construction<MyType>());
* @endcode
*
* @tparam Dependency Types of components to assign to an entity if triggered.
* @tparam Entity A valid entity type (see entt_traits for more details).
* @param sink A sink object properly initialized.
*/
template<typename... Dependency, typename Entity>
void dependency(Sink<void(Registry<Entity> &, const Entity)> sink) {
sink.template connect<dependency<Entity, Dependency...>>();
}
/**
* @brief Disconnects a dependency function from the given sink.
*
* A _dependency function_ is a built-in listener to use to automatically assign
* components to an entity when a type has a dependency on some other types.
*
* The following breaks the dependency between the component `MyType` and the
* components `AType` and `AnotherType`:
* @code{.cpp}
* entt::DefaultRegistry registry;
* entt::dependency<AType, AnotherType>(entt::break_t{}, registry.construction<MyType>());
* @endcode
*
* @tparam Dependency Types of components used to create the dependency.
* @tparam Entity A valid entity type (see entt_traits for more details).
* @param sink A sink object properly initialized.
*/
template<typename... Dependency, typename Entity>
void dependency(break_t, Sink<void(Registry<Entity> &, const Entity)> sink) {
sink.template disconnect<dependency<Entity, Dependency...>>();
}
}
#endif // ENTT_ENTITY_HELPER_HPP

View File

@@ -0,0 +1,330 @@
#ifndef ENTT_ENTITY_PROTOTYPE_HPP
#define ENTT_ENTITY_PROTOTYPE_HPP
#include <tuple>
#include <memory>
#include <vector>
#include <utility>
#include <cstddef>
#include <algorithm>
#include "registry.hpp"
namespace entt {
/**
* @brief Prototype container for _concepts_.
*
* A prototype is used to define a _concept_ in terms of components.<br/>
* Prototypes act as templates for those specific types of an application which
* users would otherwise define through a series of component assignments to
* entities. In other words, prototypes can be used to assign components to
* entities of a registry at once.
*
* @note
* Components used along with prototypes must be copy constructible.
*
* @tparam Entity A valid entity type (see entt_traits for more details).
*/
template<typename Entity>
class Prototype {
using component_type = typename Registry<Entity>::component_type;
using fn_type = void(*)(Registry<Entity> &, const Entity, const void *);
using deleter_type = void(*)(void *);
using ptr_type = std::unique_ptr<void, deleter_type>;
template<typename Component>
static void accommodate(Registry<Entity> &registry, const Entity entity, const void *component) {
const auto &ref = *static_cast<const Component *>(component);
registry.template accommodate<Component>(entity, ref);
}
template<typename Component>
static void assign(Registry<Entity> &registry, const Entity entity, const void *component) {
if(!registry.template has<Component>(entity)) {
const auto &ref = *static_cast<const Component *>(component);
registry.template assign<Component>(entity, ref);
}
}
struct Handler final {
Handler(ptr_type component, const fn_type accommodate, const fn_type assign, const component_type type)
: component{std::move(component)},
accommodate{accommodate},
assign{assign},
type{type}
{}
ptr_type component{nullptr, +[](void *) {}};
fn_type accommodate{nullptr};
fn_type assign{nullptr};
component_type type;
};
public:
/*! @brief Registry type. */
using registry_type = Registry<Entity>;
/*! @brief Underlying entity identifier. */
using entity_type = Entity;
/*! @brief Unsigned integer type. */
using size_type = std::size_t;
/**
* @brief Assigns to or replaces the given component of a prototype.
* @tparam Component Type of component to assign or replace.
* @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) {
const auto ctype = registry_type::template type<Component>();
auto it = std::find_if(handlers.begin(), handlers.end(), [ctype](const auto &handler) {
return handler.type == ctype;
});
const auto deleter = +[](void *component) { delete static_cast<Component *>(component); };
ptr_type component{new Component{std::forward<Args>(args)...}, deleter};
if(it == handlers.cend()) {
handlers.emplace_back(std::move(component), &Prototype::accommodate<Component>, &Prototype::assign<Component>, ctype);
} else {
it->component = std::move(component);
}
return get<Component>();
}
/**
* @brief Removes the given component from a prototype.
* @tparam Component Type of component to remove.
*/
template<typename Component>
void unset() ENTT_NOEXCEPT {
handlers.erase(std::remove_if(handlers.begin(), handlers.end(), [](const auto &handler) {
return handler.type == registry_type::template type<Component>();
}), handlers.end());
}
/**
* @brief Checks if a prototype owns all the given components.
* @tparam Component Components for which to perform the check.
* @return True if the prototype owns all the components, false otherwise.
*/
template<typename... Component>
bool has() const ENTT_NOEXCEPT {
auto found = [this](const auto ctype) {
return std::find_if(handlers.cbegin(), handlers.cend(), [ctype](const auto &handler) {
return handler.type == ctype;
}) != handlers.cend();
};
bool all = true;
using accumulator_type = bool[];
accumulator_type accumulator = { all, (all = all && found(registry_type::template type<Component>()))... };
(void)accumulator;
return all;
}
/**
* @brief Returns a reference to the given component.
*
* @warning
* Attempting to get a component from a prototype that doesn't own it
* results in undefined behavior.<br/>
* An assertion will abort the execution at runtime in debug mode if the
* prototype doesn't own an instance of the given component.
*
* @tparam Component Type of component to get.
* @return A reference to the component owned by the prototype.
*/
template<typename Component>
const Component & get() const ENTT_NOEXCEPT {
assert(has<Component>());
auto it = std::find_if(handlers.cbegin(), handlers.cend(), [](const auto &handler) {
return handler.type == registry_type::template type<Component>();
});
return *static_cast<Component *>(it->component.get());
}
/**
* @brief Returns a reference to the given component.
*
* @warning
* Attempting to get a component from a prototype that doesn't own it
* results in undefined behavior.<br/>
* An assertion will abort the execution at runtime in debug mode if the
* prototype doesn't own an instance of the given component.
*
* @tparam Component Type of component to get.
* @return A reference to the component owned by the prototype.
*/
template<typename Component>
inline Component & get() ENTT_NOEXCEPT {
return const_cast<Component &>(const_cast<const Prototype *>(this)->get<Component>());
}
/**
* @brief Returns a reference to the given components.
*
* @warning
* Attempting to get components from a prototype that doesn't own them
* results in undefined behavior.<br/>
* An assertion will abort the execution at runtime in debug mode if the
* prototype doesn't own instances of the given components.
*
* @tparam Component Type of components to get.
* @return References to the components owned by the prototype.
*/
template<typename... Component>
std::enable_if_t<(sizeof...(Component) > 1), std::tuple<const Component &...>>
get() const ENTT_NOEXCEPT {
return std::tuple<const Component &...>{get<Component>()...};
}
/**
* @brief Returns a reference to the given components.
*
* @warning
* Attempting to get components from a prototype that doesn't own them
* results in undefined behavior.<br/>
* An assertion will abort the execution at runtime in debug mode if the
* prototype doesn't own instances of the given components.
*
* @tparam Component Type of components to get.
* @return References to the components owned by the prototype.
*/
template<typename... Component>
std::enable_if_t<(sizeof...(Component) > 1), std::tuple<Component &...>>
get() ENTT_NOEXCEPT {
return std::tuple<Component &...>{get<Component>()...};
}
/**
* @brief Creates a new entity using a given prototype.
*
* Utility shortcut, equivalent to the following snippet:
*
* @code{.cpp}
* const auto entity = registry.create();
* prototype(registry, entity);
* @endcode
*
* @warning
* Attempting to use an invalid entity results in undefined behavior.<br/>
* An assertion will abort the execution at runtime in debug mode in case of
* invalid entity.
*
* @param registry A valid reference to a registry.
* @return A valid entity identifier.
*/
entity_type create(registry_type &registry) {
const auto entity = registry.create();
assign(registry, entity);
return entity;
}
/**
* @brief Assigns the components of a prototype to a given entity.
*
* Assigning a prototype to an entity won't overwrite existing components
* under any circumstances.<br/>
* In other words, only those components that the entity doesn't own yet are
* copied over. All the other components remain unchanged.
*
* @warning
* Attempting to use an invalid entity results in undefined behavior.<br/>
* An assertion will abort the execution at runtime in debug mode in case of
* invalid entity.
*
* @param registry A valid reference to a registry.
* @param entity A valid entity identifier.
*/
void assign(registry_type &registry, const entity_type entity) {
std::for_each(handlers.begin(), handlers.end(), [&registry, entity](auto &&handler) {
handler.assign(registry, entity, handler.component.get());
});
}
/**
* @brief Assigns or replaces the components of a prototype for an entity.
*
* Existing components are overwritten, if any. All the other components
* will be copied over to the target entity.
*
* @warning
* Attempting to use an invalid entity results in undefined behavior.<br/>
* An assertion will abort the execution at runtime in debug mode in case of
* invalid entity.
*
* @param registry A valid reference to a registry.
* @param entity A valid entity identifier.
*/
void accommodate(registry_type &registry, const entity_type entity) {
std::for_each(handlers.begin(), handlers.end(), [&registry, entity](auto &&handler) {
handler.accommodate(registry, entity, handler.component.get());
});
}
/**
* @brief Assigns the components of a prototype to an entity.
*
* Assigning a prototype to an entity won't overwrite existing components
* under any circumstances.<br/>
* In other words, only the components that the entity doesn't own yet are
* copied over. All the other components remain unchanged.
*
* @warning
* Attempting to use an invalid entity results in undefined behavior.<br/>
* An assertion will abort the execution at runtime in debug mode in case of
* invalid entity.
*
* @param registry A valid reference to a registry.
* @param entity A valid entity identifier.
*/
inline void operator()(registry_type &registry, const entity_type entity) ENTT_NOEXCEPT {
assign(registry, entity);
}
/**
* @brief Creates a new entity using a given prototype.
*
* Utility shortcut, equivalent to the following snippet:
*
* @code{.cpp}
* const auto entity = registry.create();
* prototype(registry, entity);
* @endcode
*
* @warning
* Attempting to use an invalid entity results in undefined behavior.<br/>
* An assertion will abort the execution at runtime in debug mode in case of
* invalid entity.
*
* @param registry A valid reference to a registry.
* @return A valid entity identifier.
*/
inline entity_type operator()(registry_type &registry) ENTT_NOEXCEPT {
return create(registry);
}
private:
std::vector<Handler> handlers;
};
/**
* @brief Default prototype
*/
using DefaultPrototype = Prototype<uint32_t>;
}
#endif // ENTT_ENTITY_PROTOTYPE_HPP

File diff suppressed because it is too large Load Diff

View File

@@ -2,14 +2,16 @@
#define ENTT_ENTITY_SNAPSHOT_HPP
#include <unordered_map>
#include <algorithm>
#include <array>
#include <cstddef>
#include <utility>
#include <cassert>
#include <iterator>
#include <type_traits>
#include <unordered_map>
#include "../config/config.h"
#include "entt_traits.hpp"
#include "utility.hpp"
namespace entt {
@@ -37,52 +39,55 @@ class Snapshot final {
/*! @brief A registry is allowed to create snapshots. */
friend class Registry<Entity>;
using follow_fn_type = Entity(*)(const Registry<Entity> &, Entity);
using raw_fn_type = const Entity *(*)(const Registry<Entity> &, typename Registry<Entity>::component_type);
using follow_fn_type = Entity(*)(const Registry<Entity> &, const Entity);
Snapshot(const Registry<Entity> &registry, Entity seed, std::size_t size, follow_fn_type follow, raw_fn_type raw) noexcept
Snapshot(const Registry<Entity> &registry, Entity seed, follow_fn_type follow) ENTT_NOEXCEPT
: registry{registry},
seed{seed},
size{size},
follow{follow},
raw{raw}
follow{follow}
{}
Snapshot(const Snapshot &) = default;
Snapshot(Snapshot &&) = default;
Snapshot & operator=(const Snapshot &) = default;
Snapshot & operator=(Snapshot &&) = default;
template<typename Component, typename Archive>
void get(Archive &archive, const Registry<Entity> &registry) {
const auto component = registry.template component<Component>();
const auto sz = registry.template size<Component>();
const auto *entities = raw(registry, component);
template<typename Component, typename Archive, typename It>
void get(Archive &archive, std::size_t sz, It first, It last) {
archive(static_cast<Entity>(sz));
for(std::remove_const_t<decltype(sz)> i{}; i < sz; ++i) {
const auto entity = entities[i];
archive(entity);
archive(registry.template get<Component>(entity));
};
}
while(first != last) {
const auto entity = *(first++);
template<typename Tag, typename Archive>
void get(Archive &archive) {
const bool has = registry.template has<Tag>();
// numerical length is forced for tags to facilitate loading
archive(has ? Entity(1): Entity{});
if(has) {
archive(registry.template attachee<Tag>());
archive(registry.template get<Tag>());
if(registry.template has<Component>(entity)) {
archive(entity, registry.template get<Component>(entity));
}
}
}
template<typename... Component, typename Archive, typename It, std::size_t... Indexes>
void component(Archive &archive, It first, It last, std::index_sequence<Indexes...>) {
std::array<std::size_t, sizeof...(Indexes)> size{};
auto begin = first;
while(begin != last) {
const auto entity = *(begin++);
using accumulator_type = std::size_t[];
accumulator_type accumulator = { (registry.template has<Component>(entity) ? ++size[Indexes] : size[Indexes])... };
(void)accumulator;
}
using accumulator_type = int[];
accumulator_type accumulator = { (get<Component>(archive, size[Indexes], first, last), 0)... };
(void)accumulator;
}
public:
/*! @brief Copying a snapshot isn't allowed. */
Snapshot(const Snapshot &) = delete;
/*! @brief Default move constructor. */
Snapshot(Snapshot &&) = default;
/*! @brief Copying a snapshot isn't allowed. @return This snapshot. */
Snapshot & operator=(const Snapshot &) = delete;
/*! @brief Default move assignment operator. @return This snapshot. */
Snapshot & operator=(Snapshot &&) = default;
/**
* @brief Puts aside all the entities that are still in use.
*
@@ -94,9 +99,9 @@ public:
* @return An object of this type to continue creating the snapshot.
*/
template<typename Archive>
Snapshot entities(Archive &archive) && {
Snapshot & entities(Archive &archive) {
archive(static_cast<Entity>(registry.size()));
registry.each([&archive, this](auto entity) { archive(entity); });
registry.each([&archive](const auto entity) { archive(entity); });
return *this;
}
@@ -111,27 +116,50 @@ public:
* @return An object of this type to continue creating the snapshot.
*/
template<typename Archive>
Snapshot destroyed(Archive &archive) && {
Snapshot & destroyed(Archive &archive) {
auto size = registry.capacity() - registry.size();
archive(static_cast<Entity>(size));
auto curr = seed;
if(size) {
auto curr = seed;
for(; size; --size) {
archive(curr);
for(auto i = size - 1; i; --i) {
curr = follow(registry, curr);
archive(curr);
}
curr = follow(registry, curr);
}
return *this;
}
/**
* @brief Puts aside the given component.
*
* Each instance is serialized together with the entity to which it belongs.
* Entities are serialized along with their versions.
*
* @tparam Component Type of component to serialize.
* @tparam Archive Type of output archive.
* @param archive A valid reference to an output archive.
* @return An object of this type to continue creating the snapshot.
*/
template<typename Component, typename Archive>
Snapshot & component(Archive &archive) {
const auto sz = registry.template size<Component>();
const auto *entities = registry.template data<Component>();
archive(static_cast<Entity>(sz));
for(std::remove_const_t<decltype(sz)> i{}; i < sz; ++i) {
const auto entity = entities[i];
archive(entity, registry.template get<Component>(entity));
};
return *this;
}
/**
* @brief Puts aside the given components.
*
* Each component is serialized together with the entity to which it
* belongs. Entities are serialized along with their versions.
* Each instance is serialized together with the entity to which it belongs.
* Entities are serialized along with their versions.
*
* @tparam Component Types of components to serialize.
* @tparam Archive Type of output archive.
@@ -139,17 +167,63 @@ public:
* @return An object of this type to continue creating the snapshot.
*/
template<typename... Component, typename Archive>
Snapshot component(Archive &archive) && {
std::enable_if_t<(sizeof...(Component) > 1), Snapshot &>
component(Archive &archive) {
using accumulator_type = int[];
accumulator_type accumulator = { 0, (get<Component>(archive, registry), 0)... };
accumulator_type accumulator = { 0, (component<Component>(archive), 0)... };
(void)accumulator;
return *this;
}
/**
* @brief Puts aside the given components for the entities in a range.
*
* Each instance is serialized together with the entity to which it belongs.
* Entities are serialized along with their versions.
*
* @tparam Component Types of components to serialize.
* @tparam Archive Type of output archive.
* @tparam It Type of input iterator.
* @param archive A valid reference to an output archive.
* @param first An iterator to the first element of the range to serialize.
* @param last An iterator past the last element of the range to serialize.
* @return An object of this type to continue creating the snapshot.
*/
template<typename... Component, typename Archive, typename It>
Snapshot & component(Archive &archive, It first, It last) {
component<Component...>(archive, first, last, std::make_index_sequence<sizeof...(Component)>{});
return *this;
}
/**
* @brief Puts aside the given tag.
*
* Each instance is serialized together with the entity to which it belongs.
* Entities are serialized along with their versions.
*
* @tparam Tag Type of tag to serialize.
* @tparam Archive Type of output archive.
* @param archive A valid reference to an output archive.
* @return An object of this type to continue creating the snapshot.
*/
template<typename Tag, typename Archive>
Snapshot & tag(Archive &archive) {
const bool has = registry.template has<Tag>();
// numerical length is forced for tags to facilitate loading
archive(has ? Entity(1): Entity{});
if(has) {
archive(registry.template attachee<Tag>(), registry.template get<Tag>());
}
return *this;
}
/**
* @brief Puts aside the given tags.
*
* Each tag is serialized together with the entity to which it belongs.
* Each instance is serialized together with the entity to which it belongs.
* Entities are serialized along with their versions.
*
* @tparam Tag Types of tags to serialize.
@@ -158,9 +232,10 @@ public:
* @return An object of this type to continue creating the snapshot.
*/
template<typename... Tag, typename Archive>
Snapshot tag(Archive &archive) && {
std::enable_if_t<(sizeof...(Tag) > 1), Snapshot &>
tag(Archive &archive) {
using accumulator_type = int[];
accumulator_type accumulator = { 0, (get<Tag>(archive), 0)... };
accumulator_type accumulator = { 0, (tag<Tag>(archive), 0)... };
(void)accumulator;
return *this;
}
@@ -168,9 +243,7 @@ public:
private:
const Registry<Entity> &registry;
const Entity seed;
const std::size_t size;
follow_fn_type follow;
raw_fn_type raw;
};
@@ -189,9 +262,9 @@ class SnapshotLoader final {
/*! @brief A registry is allowed to create snapshot loaders. */
friend class Registry<Entity>;
using assure_fn_type = void(*)(Registry<Entity> &, Entity, bool);
using assure_fn_type = void(*)(Registry<Entity> &, const Entity, const bool);
SnapshotLoader(Registry<Entity> &registry, assure_fn_type assure_fn) noexcept
SnapshotLoader(Registry<Entity> &registry, assure_fn_type assure_fn) ENTT_NOEXCEPT
: registry{registry},
assure_fn{assure_fn}
{
@@ -199,44 +272,44 @@ class SnapshotLoader final {
assert(!registry.capacity());
}
SnapshotLoader(const SnapshotLoader &) = default;
SnapshotLoader(SnapshotLoader &&) = default;
SnapshotLoader & operator=(const SnapshotLoader &) = default;
SnapshotLoader & operator=(SnapshotLoader &&) = default;
template<typename Archive, typename Func>
void each(Archive &archive, Func func) {
template<typename Archive>
void assure(Archive &archive, bool destroyed) {
Entity length{};
archive(length);
while(length) {
while(length--) {
Entity entity{};
archive(entity);
func(entity);
--length;
assure_fn(registry, entity, destroyed);
}
}
template<typename Component, typename Archive>
void assign(Archive &archive) {
each(archive, [&archive, this](auto entity) {
static constexpr auto destroyed = false;
assure_fn(registry, entity, destroyed);
archive(registry.template assign<Component>(entity));
});
}
template<typename Type, typename Archive, typename... Args>
void assign(Archive &archive, Args... args) {
Entity length{};
archive(length);
template<typename Tag, typename Archive>
void attach(Archive &archive) {
each(archive, [&archive, this](auto entity) {
while(length--) {
Entity entity{};
Type instance{};
archive(entity, instance);
static constexpr auto destroyed = false;
assure_fn(registry, entity, destroyed);
archive(registry.template attach<Tag>(entity));
});
registry.template assign<Type>(args..., entity, static_cast<const Type &>(instance));
}
}
public:
/*! @brief Copying a snapshot loader isn't allowed. */
SnapshotLoader(const SnapshotLoader &) = delete;
/*! @brief Default move constructor. */
SnapshotLoader(SnapshotLoader &&) = default;
/*! @brief Copying a snapshot loader isn't allowed. @return This loader. */
SnapshotLoader & operator=(const SnapshotLoader &) = delete;
/*! @brief Default move assignment operator. @return This loader. */
SnapshotLoader & operator=(SnapshotLoader &&) = default;
/**
* @brief Restores entities that were in use during serialization.
*
@@ -248,12 +321,9 @@ public:
* @return A valid loader to continue restoring data.
*/
template<typename Archive>
SnapshotLoader entities(Archive &archive) && {
each(archive, [this](auto entity) {
static constexpr auto destroyed = false;
assure_fn(registry, entity, destroyed);
});
SnapshotLoader & entities(Archive &archive) {
static constexpr auto destroyed = false;
assure(archive, destroyed);
return *this;
}
@@ -268,12 +338,9 @@ public:
* @return A valid loader to continue restoring data.
*/
template<typename Archive>
SnapshotLoader destroyed(Archive &archive) && {
each(archive, [this](auto entity) {
static constexpr auto destroyed = true;
assure_fn(registry, entity, destroyed);
});
SnapshotLoader & destroyed(Archive &archive) {
static constexpr auto destroyed = true;
assure(archive, destroyed);
return *this;
}
@@ -291,7 +358,7 @@ public:
* @return A valid loader to continue restoring data.
*/
template<typename... Component, typename Archive>
SnapshotLoader component(Archive &archive) && {
SnapshotLoader & component(Archive &archive) {
using accumulator_type = int[];
accumulator_type accumulator = { 0, (assign<Component>(archive), 0)... };
(void)accumulator;
@@ -312,14 +379,13 @@ public:
* @return A valid loader to continue restoring data.
*/
template<typename... Tag, typename Archive>
SnapshotLoader tag(Archive &archive) && {
SnapshotLoader & tag(Archive &archive) {
using accumulator_type = int[];
accumulator_type accumulator = { 0, (attach<Tag>(archive), 0)... };
accumulator_type accumulator = { 0, (assign<Tag>(archive, tag_t{}), 0)... };
(void)accumulator;
return *this;
}
/**
* @brief Destroys those entities that have neither components nor tags.
*
@@ -330,8 +396,8 @@ public:
*
* @return A valid loader to continue restoring data.
*/
SnapshotLoader orphans() && {
registry.orphans([this](auto entity) {
SnapshotLoader & orphans() {
registry.orphans([this](const auto entity) {
registry.destroy(entity);
});
@@ -364,7 +430,7 @@ template<typename Entity>
class ContinuousLoader final {
using traits_type = entt_traits<Entity>;
Entity destroy(Entity entity) {
void destroy(Entity entity) {
const auto it = remloc.find(entity);
if(it == remloc.cend()) {
@@ -372,11 +438,9 @@ class ContinuousLoader final {
remloc.emplace(entity, std::make_pair(local, true));
registry.destroy(local);
}
return remloc[entity].first;
}
Entity restore(Entity entity) {
void restore(Entity entity) {
const auto it = remloc.find(entity);
if(it == remloc.cend()) {
@@ -391,34 +455,35 @@ class ContinuousLoader final {
// set the dirty flag
remloc[entity].second = true;
}
return remloc[entity].first;
}
template<typename Instance, typename Type>
std::enable_if_t<std::is_same<Type, Entity>::value>
update(Instance &instance, Type Instance::*member) {
template<typename Type, typename Member>
std::enable_if_t<std::is_same<Member, Entity>::value>
update(Type &instance, Member Type:: *member) {
instance.*member = map(instance.*member);
}
template<typename Instance, typename Type>
std::enable_if_t<std::is_same<typename std::iterator_traits<typename Type::iterator>::value_type, Entity>::value>
update(Instance &instance, Type Instance::*member) {
for(auto &entity: (instance.*member)) {
template<typename Type, typename Member>
std::enable_if_t<std::is_same<typename std::iterator_traits<typename Member::iterator>::value_type, Entity>::value>
update(Type &instance, Member Type:: *member) {
for(auto &entity: instance.*member) {
entity = map(entity);
}
}
template<typename Archive, typename Func>
void each(Archive &archive, Func func) {
template<typename Other, typename Type, typename Member>
std::enable_if_t<!std::is_same<Other, Type>::value>
update(Other &, Member Type:: *) {}
template<typename Archive>
void assure(Archive &archive, void(ContinuousLoader:: *member)(Entity)) {
Entity length{};
archive(length);
while(length) {
while(length--) {
Entity entity{};
archive(entity);
func(entity);
--length;
(this->*member)(entity);
}
}
@@ -433,54 +498,24 @@ class ContinuousLoader final {
}
}
template<typename Component, typename Archive>
void assign(Archive &archive) {
reset<Component>();
template<typename Other, typename Archive, typename Func, typename... Type, typename... Member>
void assign(Archive &archive, Func func, Member Type:: *... member) {
Entity length{};
archive(length);
each(archive, [&archive, this](auto entity) {
entity = restore(entity);
archive(registry.template accommodate<Component>(entity));
});
}
while(length--) {
Entity entity{};
Other instance{};
template<typename Component, typename Archive, typename... Type>
void assign(Archive &archive, Type Component::*... member) {
reset<Component>();
each(archive, [&archive, member..., this](auto entity) {
entity = restore(entity);
auto &component = registry.template accommodate<Component>(entity);
archive(component);
archive(entity, instance);
restore(entity);
using accumulator_type = int[];
accumulator_type accumulator = { 0, (update(component, member), 0)... };
accumulator_type accumulator = { 0, (update(instance, member), 0)... };
(void)accumulator;
});
}
template<typename Tag, typename Archive>
void attach(Archive &archive) {
registry.template remove<Tag>();
each(archive, [&archive, this](auto entity) {
entity = restore(entity);
archive(registry.template attach<Tag>(entity));
});
}
template<typename Tag, typename Archive, typename... Type>
void attach(Archive &archive, Type Tag::*... member) {
registry.template remove<Tag>();
each(archive, [&archive, member..., this](auto entity) {
entity = restore(entity);
auto &tag = registry.template attach<Tag>(entity);
archive(tag);
using accumulator_type = int[];
accumulator_type accumulator = { 0, (update(tag, member), 0)... };
(void)accumulator;
});
func(map(entity), instance);
}
}
public:
@@ -491,17 +526,17 @@ public:
* @brief Constructs a loader that is bound to a given registry.
* @param registry A valid reference to a registry.
*/
ContinuousLoader(Registry<entity_type> &registry) noexcept
ContinuousLoader(Registry<entity_type> &registry) ENTT_NOEXCEPT
: registry{registry}
{}
/*! @brief Default copy constructor. */
ContinuousLoader(const ContinuousLoader &) = default;
/*! @brief Copying a snapshot loader isn't allowed. */
ContinuousLoader(const ContinuousLoader &) = delete;
/*! @brief Default move constructor. */
ContinuousLoader(ContinuousLoader &&) = default;
/*! @brief Default copy assignment operator. @return This loader. */
ContinuousLoader & operator=(const ContinuousLoader &) = default;
/*! @brief Copying a snapshot loader isn't allowed. @return This loader. */
ContinuousLoader & operator=(const ContinuousLoader &) = delete;
/*! @brief Default move assignment operator. @return This loader. */
ContinuousLoader & operator=(ContinuousLoader &&) = default;
@@ -517,7 +552,7 @@ public:
*/
template<typename Archive>
ContinuousLoader & entities(Archive &archive) {
each(archive, [this](auto entity) { restore(entity); });
assure(archive, &ContinuousLoader::restore);
return *this;
}
@@ -533,28 +568,7 @@ public:
*/
template<typename Archive>
ContinuousLoader & destroyed(Archive &archive) {
each(archive, [this](auto entity) { destroy(entity); });
return *this;
}
/**
* @brief Restores components and assigns them to the right entities.
*
* The template parameter list must be exactly the same used during
* serialization. In the event that the entity to which the component is
* assigned doesn't exist yet, the loader will take care to create a local
* counterpart for it.
*
* @tparam Component Types of components to restore.
* @tparam Archive Type of input archive.
* @param archive A valid reference to an input archive.
* @return A non-const reference to this loader.
*/
template<typename... Component, typename Archive>
ContinuousLoader & component(Archive &archive) {
using accumulator_type = int[];
accumulator_type accumulator = { 0, (assign<Component>(archive), 0)... };
(void)accumulator;
assure(archive, &ContinuousLoader::destroy);
return *this;
}
@@ -571,34 +585,20 @@ public:
*
* @tparam Component Type of component to restore.
* @tparam Archive Type of input archive.
* @tparam Type Types of members to update with their local counterparts.
* @tparam Type Types of components to update with local counterparts.
* @tparam Member Types of members to update with their local counterparts.
* @param archive A valid reference to an input archive.
* @param member Members to update with their local counterparts.
* @return A non-const reference to this loader.
*/
template<typename Component, typename Archive, typename... Type>
ContinuousLoader & component(Archive &archive, Type Component::*... member) {
assign(archive, member...);
return *this;
}
template<typename... Component, typename Archive, typename... Type, typename... Member>
ContinuousLoader & component(Archive &archive, Member Type:: *... member) {
auto apply = [this](const auto entity, const auto &component) {
registry.template accommodate<std::decay_t<decltype(component)>>(entity, component);
};
/**
* @brief Restores tags and assigns them to the right entities.
*
* The template parameter list must be exactly the same used during
* serialization. In the event that the entity to which the tag is assigned
* doesn't exist yet, the loader will take care to create a local
* counterpart for it.
*
* @tparam Tag Types of tags to restore.
* @tparam Archive Type of input archive.
* @param archive A valid reference to an input archive.
* @return A non-const reference to this loader.
*/
template<typename... Tag, typename Archive>
ContinuousLoader & tag(Archive &archive) {
using accumulator_type = int[];
accumulator_type accumulator = { 0, (attach<Tag>(archive), 0)... };
accumulator_type accumulator = { 0, (reset<Component>(), assign<Component>(archive, apply, member...), 0)... };
(void)accumulator;
return *this;
}
@@ -616,14 +616,21 @@ public:
*
* @tparam Tag Type of tag to restore.
* @tparam Archive Type of input archive.
* @tparam Type Types of members to update with their local counterparts.
* @tparam Type Types of components to update with local counterparts.
* @tparam Member Types of members to update with their local counterparts.
* @param archive A valid reference to an input archive.
* @param member Members to update with their local counterparts.
* @return A non-const reference to this loader.
*/
template<typename Tag, typename Archive, typename... Type>
ContinuousLoader & tag(Archive &archive, Type Tag::*... member) {
attach<Tag>(archive, member...);
template<typename... Tag, typename Archive, typename... Type, typename... Member>
ContinuousLoader & tag(Archive &archive, Member Type:: *... member) {
auto apply = [this](const auto entity, const auto &tag) {
registry.template assign<std::decay_t<decltype(tag)>>(tag_t{}, entity, tag);
};
using accumulator_type = int[];
accumulator_type accumulator = { 0, (registry.template remove<Tag>(), assign<Tag>(archive, apply, member...), 0)... };
(void)accumulator;
return *this;
}
@@ -668,7 +675,7 @@ public:
* @return A non-const reference to this loader.
*/
ContinuousLoader & orphans() {
registry.orphans([this](auto entity) {
registry.orphans([this](const auto entity) {
registry.destroy(entity);
});
@@ -681,7 +688,7 @@ public:
* @return True if `entity` is managed by the loader, false otherwise.
*/
bool has(entity_type entity) {
return !(remloc.find(entity) == remloc.cend());
return (remloc.find(entity) != remloc.cend());
}
/**

View File

@@ -10,6 +10,8 @@
#include <cstddef>
#include <cassert>
#include <type_traits>
#include "../config/config.h"
#include "../core/algorithm.hpp"
#include "entt_traits.hpp"
@@ -60,50 +62,50 @@ class SparseSet<Entity> {
struct Iterator final {
using difference_type = std::size_t;
using value_type = Entity;
using pointer = value_type *;
using pointer = const value_type *;
using reference = value_type;
using iterator_category = std::input_iterator_tag;
Iterator(const std::vector<value_type> &direct, std::size_t pos)
Iterator(pointer direct, std::size_t pos)
: direct{direct}, pos{pos}
{}
Iterator & operator++() noexcept {
Iterator & operator++() ENTT_NOEXCEPT {
return --pos, *this;
}
Iterator operator++(int) noexcept {
Iterator operator++(int) ENTT_NOEXCEPT {
Iterator orig = *this;
return ++(*this), orig;
}
Iterator & operator+=(difference_type value) noexcept {
Iterator & operator+=(const difference_type value) ENTT_NOEXCEPT {
pos -= value;
return *this;
}
Iterator operator+(difference_type value) noexcept {
Iterator operator+(const difference_type value) const ENTT_NOEXCEPT {
return Iterator{direct, pos-value};
}
bool operator==(const Iterator &other) const noexcept {
bool operator==(const Iterator &other) const ENTT_NOEXCEPT {
return other.pos == pos;
}
bool operator!=(const Iterator &other) const noexcept {
inline bool operator!=(const Iterator &other) const ENTT_NOEXCEPT {
return !(*this == other);
}
reference operator*() const noexcept {
reference operator*() const ENTT_NOEXCEPT {
return direct[pos-1];
}
private:
const std::vector<value_type> &direct;
pointer direct;
std::size_t pos;
};
static constexpr Entity in_use = (Entity{1} << traits_type::entity_shift);
static constexpr auto pending = ~typename traits_type::entity_type{};
public:
/*! @brief Underlying entity identifier. */
@@ -114,12 +116,14 @@ public:
using size_type = std::size_t;
/*! @brief Input iterator type. */
using iterator_type = Iterator;
/*! @brief Constant input iterator type. */
using const_iterator_type = Iterator;
/*! @brief Default constructor. */
SparseSet() noexcept = default;
SparseSet() ENTT_NOEXCEPT = default;
/*! @brief Default destructor. */
virtual ~SparseSet() noexcept = default;
virtual ~SparseSet() ENTT_NOEXCEPT = default;
/*! @brief Copying a sparse set isn't allowed. */
SparseSet(const SparseSet &) = delete;
@@ -139,7 +143,7 @@ public:
*
* @param cap Desired capacity.
*/
void reserve(size_type cap) {
void reserve(const size_type cap) {
direct.reserve(cap);
}
@@ -153,7 +157,7 @@ public:
*
* @return Extent of the sparse set.
*/
size_type extent() const noexcept {
size_type extent() const ENTT_NOEXCEPT {
return reverse.size();
}
@@ -167,7 +171,7 @@ public:
*
* @return Number of elements.
*/
size_type size() const noexcept {
size_type size() const ENTT_NOEXCEPT {
return direct.size();
}
@@ -175,7 +179,7 @@ public:
* @brief Checks whether a sparse set is empty.
* @return True if the sparse set is empty, false otherwise.
*/
bool empty() const noexcept {
bool empty() const ENTT_NOEXCEPT {
return direct.empty();
}
@@ -194,7 +198,7 @@ public:
*
* @return A pointer to the internal packed array.
*/
const entity_type * data() const noexcept {
const entity_type * data() const ENTT_NOEXCEPT {
return direct.data();
}
@@ -210,8 +214,40 @@ public:
*
* @return An iterator to the first entity of the internal packed array.
*/
iterator_type begin() const noexcept {
return Iterator{direct, direct.size()};
const_iterator_type cbegin() const ENTT_NOEXCEPT {
return const_iterator_type{direct.data(), direct.size()};
}
/**
* @brief Returns an iterator to the beginning.
*
* The returned iterator points to the first entity of the internal packed
* array. If the sparse set is empty, the returned iterator will be equal to
* `end()`.
*
* @note
* Input iterators stay true to the order imposed by a call to `respect`.
*
* @return An iterator to the first entity of the internal packed array.
*/
inline const_iterator_type begin() const ENTT_NOEXCEPT {
return cbegin();
}
/**
* @brief Returns an iterator to the beginning.
*
* The returned iterator points to the first entity of the internal packed
* array. If the sparse set is empty, the returned iterator will be equal to
* `end()`.
*
* @note
* Input iterators stay true to the order imposed by a call to `respect`.
*
* @return An iterator to the first entity of the internal packed array.
*/
inline iterator_type begin() ENTT_NOEXCEPT {
return cbegin();
}
/**
@@ -227,8 +263,42 @@ public:
* @return An iterator to the element following the last entity of the
* internal packed array.
*/
iterator_type end() const noexcept {
return Iterator{direct, 0};
const_iterator_type cend() const ENTT_NOEXCEPT {
return const_iterator_type{direct.data(), {}};
}
/**
* @brief Returns an iterator to the end.
*
* The returned iterator points to the element following the last entity in
* the internal packed array. Attempting to dereference the returned
* iterator results in undefined behavior.
*
* @note
* Input iterators stay true to the order imposed by a call to `respect`.
*
* @return An iterator to the element following the last entity of the
* internal packed array.
*/
inline const_iterator_type end() const ENTT_NOEXCEPT {
return cend();
}
/**
* @brief Returns an iterator to the end.
*
* The returned iterator points to the element following the last entity in
* the internal packed array. Attempting to dereference the returned
* iterator results in undefined behavior.
*
* @note
* Input iterators stay true to the order imposed by a call to `respect`.
*
* @return An iterator to the element following the last entity of the
* internal packed array.
*/
inline iterator_type end() ENTT_NOEXCEPT {
return cend();
}
/**
@@ -236,10 +306,10 @@ public:
* @param entity A valid entity identifier.
* @return True if the sparse set contains the entity, false otherwise.
*/
bool has(entity_type entity) const noexcept {
bool has(const entity_type entity) const ENTT_NOEXCEPT {
const auto pos = size_type(entity & traits_type::entity_mask);
// the in-use control bit permits to avoid accessing the direct vector
return (pos < reverse.size()) && (reverse[pos] & in_use);
// testing against pending permits to avoid accessing the direct vector
return (pos < reverse.size()) && (reverse[pos] != pending);
}
/**
@@ -259,11 +329,11 @@ public:
* @param entity A valid entity identifier.
* @return True if the sparse set contains the entity, false otherwise.
*/
bool fast(entity_type entity) const noexcept {
bool fast(const entity_type entity) const ENTT_NOEXCEPT {
const auto pos = size_type(entity & traits_type::entity_mask);
assert(pos < reverse.size());
// the in-use control bit permits to avoid accessing the direct vector
return (reverse[pos] & in_use);
// testing against pending permits to avoid accessing the direct vector
return (reverse[pos] != pending);
}
/**
@@ -278,11 +348,9 @@ public:
* @param entity A valid entity identifier.
* @return The position of the entity in the sparse set.
*/
pos_type get(entity_type entity) const noexcept {
pos_type get(const entity_type entity) const ENTT_NOEXCEPT {
assert(has(entity));
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] & traits_type::entity_mask;
return reverse[entity & traits_type::entity_mask];
}
/**
@@ -296,18 +364,39 @@ public:
*
* @param entity A valid entity identifier.
*/
void construct(entity_type entity) {
void construct(const entity_type entity) {
assert(!has(entity));
const auto pos = size_type(entity & traits_type::entity_mask);
if(!(pos < reverse.size())) {
reverse.resize(pos+1, pos_type{});
const auto value = pending;
reverse.resize(pos+1, value);
}
// 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[pos] = pos_type(direct.size()) | in_use;
direct.emplace_back(entity);
reverse[pos] = pos_type(direct.size());
direct.push_back(entity);
}
/**
* @brief Assigns an entity to a sparse set by cloning another entity.
*
* @warning
* Attempting to clone an entity that doesn't belong to the sparse set or to
* assign an entity that already belongs to the sparse set results in
* undefined behavior.<br/>
* An assertion will abort the execution at runtime in debug mode if the
* sparse set doesn't contain the entity to clone or if it already contains
* the given entity.
*
* @param entity A valid entity identifier.
* @param source A valid entity identifier from which to clone.
*/
inline virtual void clone(const entity_type entity, const entity_type source) {
assert(has(source));
assert(!has(entity));
construct(entity);
// useful to suppress warnings when asserts are disabled
(void)source;
}
/**
@@ -321,16 +410,14 @@ public:
*
* @param entity A valid entity identifier.
*/
virtual void destroy(entity_type entity) {
virtual void destroy(const entity_type entity) {
assert(has(entity));
const auto entt = entity & traits_type::entity_mask;
const auto back = direct.back() & traits_type::entity_mask;
// we must get rid of the in-use bit for it's not part of the position
const auto pos = reverse[entt] & traits_type::entity_mask;
reverse[back] = reverse[entt];
reverse[entt] = pos;
const auto back = direct.back();
auto &candidate = reverse[entity & traits_type::entity_mask];
// swapping isn't required here, we are getting rid of the last element
direct[pos] = direct.back();
reverse[back & traits_type::entity_mask] = candidate;
direct[candidate] = back;
candidate = pending;
direct.pop_back();
}
@@ -349,13 +436,13 @@ public:
* @param lhs A valid position within the sparse set.
* @param rhs A valid position within the sparse set.
*/
void swap(pos_type lhs, pos_type rhs) noexcept {
void swap(const pos_type lhs, const pos_type rhs) ENTT_NOEXCEPT {
assert(lhs < direct.size());
assert(rhs < direct.size());
const auto src = direct[lhs] & traits_type::entity_mask;
const auto dst = direct[rhs] & traits_type::entity_mask;
std::swap(reverse[src], reverse[dst]);
std::swap(direct[lhs], direct[rhs]);
auto &src = direct[lhs];
auto &dst = direct[rhs];
std::swap(reverse[src & traits_type::entity_mask], reverse[dst & traits_type::entity_mask]);
std::swap(src, dst);
}
/**
@@ -377,9 +464,9 @@ public:
*
* @param other The sparse sets that imposes the order of the entities.
*/
void respect(const SparseSet<Entity> &other) noexcept {
auto from = other.begin();
auto to = other.end();
void respect(const SparseSet<Entity> &other) ENTT_NOEXCEPT {
auto from = other.cbegin();
auto to = other.cend();
pos_type pos = direct.size() - 1;
@@ -436,53 +523,54 @@ template<typename Entity, typename Type>
class SparseSet<Entity, Type>: public SparseSet<Entity> {
using underlying_type = SparseSet<Entity>;
template<bool Const>
struct Iterator final {
using difference_type = std::size_t;
using value_type = Type;
using value_type = std::conditional_t<Const, const Type, Type>;
using pointer = value_type *;
using reference = value_type &;
using iterator_category = std::input_iterator_tag;
Iterator(std::vector<value_type> &instances, std::size_t pos)
Iterator(pointer instances, std::size_t pos)
: instances{instances}, pos{pos}
{}
Iterator & operator++() noexcept {
Iterator & operator++() ENTT_NOEXCEPT {
return --pos, *this;
}
Iterator operator++(int) noexcept {
Iterator operator++(int) ENTT_NOEXCEPT {
Iterator orig = *this;
return ++(*this), orig;
}
Iterator & operator+=(difference_type value) noexcept {
Iterator & operator+=(const difference_type value) ENTT_NOEXCEPT {
pos -= value;
return *this;
}
Iterator operator+(difference_type value) noexcept {
Iterator operator+(const difference_type value) const ENTT_NOEXCEPT {
return Iterator{instances, pos-value};
}
bool operator==(const Iterator &other) const noexcept {
bool operator==(const Iterator &other) const ENTT_NOEXCEPT {
return other.pos == pos;
}
bool operator!=(const Iterator &other) const noexcept {
inline bool operator!=(const Iterator &other) const ENTT_NOEXCEPT {
return !(*this == other);
}
reference operator*() noexcept {
reference operator*() const ENTT_NOEXCEPT {
return instances[pos-1];
}
pointer operator->() noexcept {
return &instances.data()[pos-1];
pointer operator->() const ENTT_NOEXCEPT {
return (instances+pos-1);
}
private:
std::vector<value_type> &instances;
pointer instances;
std::size_t pos;
};
@@ -496,10 +584,12 @@ public:
/*! @brief Unsigned integer type. */
using size_type = typename underlying_type::size_type;
/*! @brief Input iterator type. */
using iterator_type = Iterator;
using iterator_type = Iterator<false>;
/*! @brief Constant input iterator type. */
using const_iterator_type = Iterator<true>;
/*! @brief Default constructor. */
SparseSet() noexcept = default;
SparseSet() ENTT_NOEXCEPT = default;
/*! @brief Copying a sparse set isn't allowed. */
SparseSet(const SparseSet &) = delete;
@@ -519,7 +609,7 @@ public:
*
* @param cap Desired capacity.
*/
void reserve(size_type cap) {
void reserve(const size_type cap) {
underlying_type::reserve(cap);
instances.reserve(cap);
}
@@ -539,7 +629,7 @@ public:
*
* @return A pointer to the array of objects.
*/
const object_type * raw() const noexcept {
const object_type * raw() const ENTT_NOEXCEPT {
return instances.data();
}
@@ -558,7 +648,7 @@ public:
*
* @return A pointer to the array of objects.
*/
object_type * raw() noexcept {
object_type * raw() ENTT_NOEXCEPT {
return instances.data();
}
@@ -574,8 +664,40 @@ public:
*
* @return An iterator to the first instance of the given type.
*/
iterator_type begin() noexcept {
return Iterator{instances, instances.size()};
const_iterator_type cbegin() const ENTT_NOEXCEPT {
return const_iterator_type{instances.data(), instances.size()};
}
/**
* @brief Returns an iterator to the beginning.
*
* The returned iterator points to the first instance of the given type. If
* the sparse set is empty, the returned iterator will be equal to `end()`.
*
* @note
* Input iterators stay true to the order imposed by a call to either `sort`
* or `respect`.
*
* @return An iterator to the first instance of the given type.
*/
inline const_iterator_type begin() const ENTT_NOEXCEPT {
return cbegin();
}
/**
* @brief Returns an iterator to the beginning.
*
* The returned iterator points to the first instance of the given type. If
* the sparse set is empty, the returned iterator will be equal to `end()`.
*
* @note
* Input iterators stay true to the order imposed by a call to either `sort`
* or `respect`.
*
* @return An iterator to the first instance of the given type.
*/
iterator_type begin() ENTT_NOEXCEPT {
return iterator_type{instances.data(), instances.size()};
}
/**
@@ -592,8 +714,44 @@ public:
* @return An iterator to the element following the last instance of the
* given type.
*/
iterator_type end() noexcept {
return Iterator{instances, 0};
const_iterator_type cend() const ENTT_NOEXCEPT {
return const_iterator_type{instances.data(), {}};
}
/**
* @brief Returns an iterator to the end.
*
* The returned iterator points to the element following the last instance
* of the given type. Attempting to dereference the returned iterator
* results in undefined behavior.
*
* @note
* Input iterators stay true to the order imposed by a call to either `sort`
* or `respect`.
*
* @return An iterator to the element following the last instance of the
* given type.
*/
inline const_iterator_type end() const ENTT_NOEXCEPT {
return cend();
}
/**
* @brief Returns an iterator to the end.
*
* The returned iterator points to the element following the last instance
* of the given type. Attempting to dereference the returned iterator
* results in undefined behavior.
*
* @note
* Input iterators stay true to the order imposed by a call to either `sort`
* or `respect`.
*
* @return An iterator to the element following the last instance of the
* given type.
*/
iterator_type end() ENTT_NOEXCEPT {
return iterator_type{instances.data(), {}};
}
/**
@@ -608,7 +766,7 @@ public:
* @param entity A valid entity identifier.
* @return The object associated to the entity.
*/
const object_type & get(entity_type entity) const noexcept {
const object_type & get(const entity_type entity) const ENTT_NOEXCEPT {
return instances[underlying_type::get(entity)];
}
@@ -624,7 +782,7 @@ public:
* @param entity A valid entity identifier.
* @return The object associated to the entity.
*/
object_type & get(entity_type entity) noexcept {
inline object_type & get(const entity_type entity) ENTT_NOEXCEPT {
return const_cast<object_type &>(const_cast<const SparseSet *>(this)->get(entity));
}
@@ -650,7 +808,7 @@ public:
*/
template<typename... Args>
std::enable_if_t<std::is_constructible<Type, Args...>::value, object_type &>
construct(entity_type entity, Args &&... args) {
construct(const entity_type entity, Args &&... args) {
underlying_type::construct(entity);
instances.emplace_back(std::forward<Args>(args)...);
return instances.back();
@@ -678,12 +836,30 @@ public:
*/
template<typename... Args>
std::enable_if_t<!std::is_constructible<Type, Args...>::value, object_type &>
construct(entity_type entity, Args &&... args) {
construct(const entity_type entity, Args &&... args) {
underlying_type::construct(entity);
instances.emplace_back(Type{std::forward<Args>(args)...});
return instances.back();
}
/**
* @brief Assigns an entity to a sparse set by cloning another entity.
*
* @warning
* Attempting to clone an entity that doesn't belong to the sparse set or to
* assign an entity that already belongs to the sparse set results in
* undefined behavior.<br/>
* An assertion will abort the execution at runtime in debug mode if the
* sparse set doesn't contain the entity to clone or if it already contains
* the given entity.
*
* @param entity A valid entity identifier.
* @param source A valid entity identifier from which to clone.
*/
inline void clone(const entity_type entity, const entity_type source) override {
construct(entity, get(source));
}
/**
* @brief Removes an entity from a sparse set and destroies its object.
*
@@ -695,7 +871,7 @@ public:
*
* @param entity A valid entity identifier.
*/
void destroy(entity_type entity) override {
void destroy(const entity_type entity) override {
// swapping isn't required here, we are getting rid of the last element
// however, we must protect ourselves from self assignments (see #37)
auto tmp = std::move(instances.back());
@@ -719,20 +895,36 @@ public:
* bool(const Type &, const Type &)
* @endcode
*
* Moreover, the comparison function object shall induce a
* _strict weak ordering_ on the values.
*
* The sort function oject must offer a member function template
* `operator()` that accepts three arguments:
*
* * An iterator to the first element of the range to sort.
* * An iterator past the last element of the range to sort.
* * A comparison function to use to compare the elements.
*
* The comparison funtion object received by the sort function object hasn't
* necessarily the type of the one passed along with the other parameters to
* this member function.
*
* @note
* Attempting to iterate elements using a raw pointer returned by a call to
* either `data` or `raw` gives no guarantees on the order, even though
* `sort` has been invoked.
*
* @tparam Compare Type of comparison function object.
* @tparam Sort Type of sort function object.
* @param compare A valid comparison function object.
* @param sort A valid sort function object.
*/
template<typename Compare>
void sort(Compare compare) {
template<typename Compare, typename Sort = StdSort>
void sort(Compare compare, Sort sort = Sort{}) {
std::vector<pos_type> copy(instances.size());
std::iota(copy.begin(), copy.end(), 0);
std::sort(copy.begin(), copy.end(), [this, compare = std::move(compare)](auto lhs, auto rhs) {
sort(copy.begin(), copy.end(), [this, compare = std::move(compare)](const auto lhs, const auto rhs) {
return compare(const_cast<const object_type &>(instances[rhs]), const_cast<const object_type &>(instances[lhs]));
});
@@ -775,9 +967,9 @@ public:
*
* @param other The sparse sets that imposes the order of the entities.
*/
void respect(const SparseSet<Entity> &other) noexcept {
auto from = other.begin();
auto to = other.end();
void respect(const SparseSet<Entity> &other) ENTT_NOEXCEPT {
auto from = other.cbegin();
auto to = other.cend();
pos_type pos = underlying_type::size() - 1;
const auto *local = underlying_type::data();

View File

@@ -0,0 +1,27 @@
#ifndef ENTT_ENTITY_UTILITY_HPP
#define ENTT_ENTITY_UTILITY_HPP
namespace entt {
/*! @brief Tag class type used to disambiguate overloads. */
struct tag_t final {};
/*! @brief Persistent view type used to disambiguate overloads. */
struct persistent_t final {};
/*! @brief Raw view type used to disambiguate overloads. */
struct raw_t final {};
/*! @brief Break type used to disambiguate overloads. */
struct break_t final {};
}
#endif // ENTT_ENTITY_UTILITY_HPP

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +1,15 @@
#include "core/algorithm.hpp"
#include "core/family.hpp"
#include "core/hashed_string.hpp"
#include "core/ident.hpp"
#include "entity/actor.hpp"
#include "entity/entt_traits.hpp"
#include "entity/helper.hpp"
#include "entity/prototype.hpp"
#include "entity/registry.hpp"
#include "entity/snapshot.hpp"
#include "entity/sparse_set.hpp"
#include "entity/utility.hpp"
#include "entity/view.hpp"
#include "locator/locator.hpp"
#include "process/process.hpp"
@@ -13,9 +17,7 @@
#include "resource/cache.hpp"
#include "resource/handle.hpp"
#include "resource/loader.hpp"
#include "signal/bus.hpp"
#include "signal/delegate.hpp"
#include "signal/dispatcher.hpp"
#include "signal/emitter.hpp"
#include "signal/sigh.hpp"
#include "signal/signal.hpp"

View File

@@ -5,6 +5,7 @@
#include <memory>
#include <utility>
#include <cassert>
#include "../config/config.h"
namespace entt {
@@ -35,7 +36,7 @@ struct ServiceLocator final {
* @brief Tests if a valid service implementation is set.
* @return True if the service is set, false otherwise.
*/
inline static bool empty() noexcept {
inline static bool empty() ENTT_NOEXCEPT {
return !static_cast<bool>(service);
}
@@ -49,7 +50,7 @@ struct ServiceLocator final {
*
* @return A reference to the service implementation currently set, if any.
*/
inline static std::weak_ptr<Service> get() noexcept {
inline static std::weak_ptr<Service> get() ENTT_NOEXCEPT {
return service;
}
@@ -67,7 +68,7 @@ struct ServiceLocator final {
*
* @return A reference to the service implementation currently set, if any.
*/
inline static Service & ref() noexcept {
inline static Service & ref() ENTT_NOEXCEPT {
return *service;
}

View File

@@ -5,6 +5,7 @@
#include <type_traits>
#include <functional>
#include <utility>
#include "../config/config.h"
namespace entt {
@@ -123,7 +124,7 @@ protected:
* The function is idempotent and it does nothing if the process isn't
* alive.
*/
void succeed() noexcept {
void succeed() ENTT_NOEXCEPT {
if(alive()) {
current = State::SUCCEEDED;
}
@@ -135,7 +136,7 @@ protected:
* The function is idempotent and it does nothing if the process isn't
* alive.
*/
void fail() noexcept {
void fail() ENTT_NOEXCEPT {
if(alive()) {
current = State::FAILED;
}
@@ -147,7 +148,7 @@ protected:
* The function is idempotent and it does nothing if the process isn't
* running.
*/
void pause() noexcept {
void pause() ENTT_NOEXCEPT {
if(current == State::RUNNING) {
current = State::PAUSED;
}
@@ -159,7 +160,7 @@ protected:
* The function is idempotent and it does nothing if the process isn't
* paused.
*/
void unpause() noexcept {
void unpause() ENTT_NOEXCEPT {
if(current == State::PAUSED) {
current = State::RUNNING;
}
@@ -170,7 +171,7 @@ public:
using delta_type = Delta;
/*! @brief Default destructor. */
virtual ~Process() noexcept {
virtual ~Process() ENTT_NOEXCEPT {
static_assert(std::is_base_of<Process, Derived>::value, "!");
}
@@ -182,7 +183,7 @@ public:
*
* @param immediately Requests an immediate operation.
*/
void abort(bool immediately = false) noexcept {
void abort(const bool immediately = false) ENTT_NOEXCEPT {
if(alive()) {
current = State::ABORTED;
@@ -196,7 +197,7 @@ public:
* @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 {
bool alive() const ENTT_NOEXCEPT {
return current == State::RUNNING || current == State::PAUSED;
}
@@ -204,7 +205,7 @@ public:
* @brief Returns true if a process is already terminated.
* @return True if the process is terminated, false otherwise.
*/
bool dead() const noexcept {
bool dead() const ENTT_NOEXCEPT {
return current == State::FINISHED;
}
@@ -212,7 +213,7 @@ public:
* @brief Returns true if a process is currently paused.
* @return True if the process is paused, false otherwise.
*/
bool paused() const noexcept {
bool paused() const ENTT_NOEXCEPT {
return current == State::PAUSED;
}
@@ -220,7 +221,7 @@ public:
* @brief Returns true if a process terminated with errors.
* @return True if the process terminated with errors, false otherwise.
*/
bool rejected() const noexcept {
bool rejected() const ENTT_NOEXCEPT {
return stopped;
}
@@ -229,7 +230,7 @@ public:
* @param delta Elapsed time.
* @param data Optional data.
*/
void tick(Delta delta, void *data = nullptr) {
void tick(const Delta delta, void *data = nullptr) {
switch (current) {
case State::UNINITIALIZED:
tick(0, tag<State::UNINITIALIZED>{}, data);
@@ -326,7 +327,7 @@ struct ProcessAdaptor: Process<ProcessAdaptor<Func, Delta>, Delta>, private Func
* @param delta Elapsed time.
* @param data Optional data.
*/
void update(Delta delta, void *data) {
void update(const Delta delta, void *data) {
Func::operator()(delta, data, [this]() { this->succeed(); }, [this]() { this->fail(); });
}
};

View File

@@ -8,6 +8,7 @@
#include <iterator>
#include <algorithm>
#include <type_traits>
#include "../config/config.h"
#include "process.hpp"
@@ -43,7 +44,7 @@ namespace entt {
template<typename Delta>
class Scheduler final {
template<typename T>
struct tag { using type = T; };
struct type_t { using type = T; };
struct ProcessHandler final {
using instance_type = std::unique_ptr<void, void(*)(void *)>;
@@ -66,7 +67,7 @@ class Scheduler final {
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)...);
handler = Lambda::operator()(handler, type_t<Proc>{}, std::forward<Args>(args)...);
return std::move(*this);
}
@@ -81,7 +82,7 @@ class Scheduler final {
};
template<typename Proc>
static bool update(ProcessHandler &handler, Delta delta, void *data) {
static bool update(ProcessHandler &handler, const Delta delta, void *data) {
auto *process = static_cast<Proc *>(handler.instance.get());
process->tick(delta, data);
@@ -100,7 +101,7 @@ class Scheduler final {
}
template<typename Proc>
static void abort(ProcessHandler &handler, bool immediately) {
static void abort(ProcessHandler &handler, const bool immediately) {
static_cast<Proc *>(handler.instance.get())->abort(immediately);
}
@@ -130,7 +131,7 @@ public:
using size_type = typename std::vector<ProcessHandler>::size_type;
/*! @brief Default constructor. */
Scheduler() noexcept= default;
Scheduler() ENTT_NOEXCEPT = default;
/*! @brief Copying a scheduler isn't allowed. */
Scheduler(const Scheduler &) = delete;
@@ -139,14 +140,14 @@ public:
/*! @brief Copying a scheduler isn't allowed. @return This scheduler. */
Scheduler & operator=(const Scheduler &) = delete;
/*! @brief Default move assignament operator. @return This scheduler. */
/*! @brief Default move assignment 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 {
size_type size() const ENTT_NOEXCEPT {
return handlers.size();
}
@@ -154,7 +155,7 @@ public:
* @brief Returns true if at least a process is currently scheduled.
* @return True if there are scheduled processes, false otherwise.
*/
bool empty() const noexcept {
bool empty() const ENTT_NOEXCEPT {
return handlers.empty();
}
@@ -271,7 +272,7 @@ public:
* @param delta Elapsed time.
* @param data Optional data.
*/
void update(Delta delta, void *data = nullptr) {
void update(const Delta delta, void *data = nullptr) {
bool clean = false;
for(auto pos = handlers.size(); pos; --pos) {
@@ -297,7 +298,7 @@ public:
*
* @param immediately Requests an immediate operation.
*/
void abort(bool immediately = false) {
void abort(const bool immediately = false) {
decltype(handlers) exec;
exec.swap(handlers);

View File

@@ -6,6 +6,7 @@
#include <utility>
#include <type_traits>
#include <unordered_map>
#include "../config/config.h"
#include "../core/hashed_string.hpp"
#include "handle.hpp"
#include "loader.hpp"
@@ -38,20 +39,20 @@ public:
ResourceCache() = default;
/*! @brief Copying a cache isn't allowed. */
ResourceCache(const ResourceCache &) noexcept = delete;
ResourceCache(const ResourceCache &) ENTT_NOEXCEPT = delete;
/*! @brief Default move constructor. */
ResourceCache(ResourceCache &&) noexcept = default;
ResourceCache(ResourceCache &&) ENTT_NOEXCEPT = default;
/*! @brief Copying a cache isn't allowed. @return This cache. */
ResourceCache & operator=(const ResourceCache &) noexcept = delete;
ResourceCache & operator=(const ResourceCache &) ENTT_NOEXCEPT = delete;
/*! @brief Default move assignment operator. @return This cache. */
ResourceCache & operator=(ResourceCache &&) noexcept = default;
ResourceCache & operator=(ResourceCache &&) ENTT_NOEXCEPT = default;
/**
* @brief Number of resources managed by a cache.
* @return Number of resources currently stored.
*/
size_type size() const noexcept {
size_type size() const ENTT_NOEXCEPT {
return resources.size();
}
@@ -59,7 +60,7 @@ public:
* @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 {
bool empty() const ENTT_NOEXCEPT {
return resources.empty();
}
@@ -69,7 +70,7 @@ public:
* 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 {
void clear() ENTT_NOEXCEPT {
resources.clear();
}
@@ -92,7 +93,7 @@ public:
* @return True if the resource is ready to use, false otherwise.
*/
template<typename Loader, typename... Args>
bool load(resource_type id, Args &&... args) {
bool load(const resource_type id, Args &&... args) {
static_assert(std::is_base_of<ResourceLoader<Loader, Resource>, Loader>::value, "!");
bool loaded = true;
@@ -125,7 +126,7 @@ public:
* @return True if the resource is ready to use, false otherwise.
*/
template<typename Loader, typename... Args>
bool reload(resource_type id, Args &&... args) {
bool reload(const resource_type id, Args &&... args) {
return (discard(id), load<Loader>(id, std::forward<Args>(args)...));
}
@@ -159,7 +160,7 @@ public:
* @param id Unique resource identifier.
* @return A handle for the given resource.
*/
ResourceHandle<Resource> handle(resource_type id) const {
ResourceHandle<Resource> handle(const resource_type id) const {
auto it = resources.find(id);
return { it == resources.end() ? nullptr : it->second };
}
@@ -169,8 +170,8 @@ public:
* @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());
bool contains(const resource_type id) const ENTT_NOEXCEPT {
return (resources.find(id) != resources.cend());
}
/**
@@ -181,7 +182,7 @@ public:
*
* @param id Unique resource identifier.
*/
void discard(resource_type id) noexcept {
void discard(const resource_type id) ENTT_NOEXCEPT {
auto it = resources.find(id);
if(it != resources.end()) {

View File

@@ -5,6 +5,7 @@
#include <memory>
#include <utility>
#include <cassert>
#include "../config/config.h"
namespace entt {
@@ -31,20 +32,20 @@ class ResourceHandle final {
/*! @brief Resource handles are friends of their caches. */
friend class ResourceCache<Resource>;
ResourceHandle(std::shared_ptr<Resource> res) noexcept
ResourceHandle(std::shared_ptr<Resource> res) ENTT_NOEXCEPT
: resource{std::move(res)}
{}
public:
/*! @brief Default copy constructor. */
ResourceHandle(const ResourceHandle &) noexcept = default;
ResourceHandle(const ResourceHandle &) ENTT_NOEXCEPT = default;
/*! @brief Default move constructor. */
ResourceHandle(ResourceHandle &&) noexcept = default;
ResourceHandle(ResourceHandle &&) ENTT_NOEXCEPT = default;
/*! @brief Default copy assignment operator. @return This handle. */
ResourceHandle & operator=(const ResourceHandle &) noexcept = default;
ResourceHandle & operator=(const ResourceHandle &) ENTT_NOEXCEPT = default;
/*! @brief Default move assignment operator. @return This handle. */
ResourceHandle & operator=(ResourceHandle &&) noexcept = default;
ResourceHandle & operator=(ResourceHandle &&) ENTT_NOEXCEPT = default;
/**
* @brief Gets a reference to the managed resource.
@@ -56,7 +57,7 @@ public:
*
* @return A reference to the managed resource.
*/
const Resource & get() const noexcept {
const Resource & get() const ENTT_NOEXCEPT {
assert(static_cast<bool>(resource));
return *resource;
}
@@ -69,7 +70,7 @@ public:
* An assertion will abort the execution at runtime in debug mode if the
* handle is empty.
*/
inline operator const Resource & () const noexcept { return get(); }
inline operator const Resource &() const ENTT_NOEXCEPT { return get(); }
/**
* @brief Dereferences a handle to obtain the managed resource.
@@ -81,7 +82,7 @@ public:
*
* @return A reference to the managed resource.
*/
inline const Resource & operator *() const noexcept { return get(); }
inline const Resource & operator *() const ENTT_NOEXCEPT { return get(); }
/**
* @brief Gets a pointer to the managed resource from a handle .
@@ -94,7 +95,7 @@ public:
* @return A pointer to the managed resource or `nullptr` if the handle
* contains no resource at all.
*/
inline const Resource * operator ->() const noexcept {
inline const Resource * operator ->() const ENTT_NOEXCEPT {
assert(static_cast<bool>(resource));
return resource.get();
}

View File

@@ -1,305 +0,0 @@
#ifndef ENTT_SIGNAL_BUS_HPP
#define ENTT_SIGNAL_BUS_HPP
#include <cstddef>
#include <utility>
#include "signal.hpp"
#include "sigh.hpp"
namespace entt {
/**
* @brief Minimal event bus.
*
* Primary template isn't defined on purpose. The main reason for which it
* exists is to work around the doxygen's parsing capabilities. In fact, there
* is no need to declare it actually.
*/
template<template<typename...> class, typename...>
class Bus;
/**
* @brief Event bus specialization for multiple types.
*
* The event bus is designed to allow an easy registration of specific member
* functions to a bunch of signal handlers (either manager or unmanaged).
* Classes must publicly expose the required member functions to allow the bus
* to detect them for the purpose of registering and unregistering
* instances.<br/>
* In particular, for each event type `E`, a matching member function has the
* following signature: `void receive(const E &)`. Events will be properly
* redirected to all the listeners by calling the right member functions, if
* any.
*
* @tparam Sig Type of signal handler to use.
* @tparam Event The list of events managed by the bus.
*/
template<template<typename...> class Sig, typename Event, typename... Other>
class Bus<Sig, Event, Other...>
: private Bus<Sig, Event>, private Bus<Sig, Other>...
{
public:
/*! @brief Unsigned integer type. */
using size_type = std::size_t;
/**
* @brief Unregisters all the member functions of an instance.
*
* A bus is used to convey a certain set of events. This method detects
* and unregisters from the bus all the matching member functions of an
* instance.<br/>
* For each event type `E`, a matching member function has the following
* signature: `void receive(const E &)`.
*
* @tparam Instance Type of instance to unregister.
* @param instance A valid instance of the right type.
*/
template<typename Instance>
void unreg(Instance instance) {
using accumulator_type = int[];
accumulator_type accumulator = {
(Bus<Sig, Event>::unreg(instance), 0),
(Bus<Sig, Other>::unreg(instance), 0)...
};
return void(accumulator);
}
/**
* @brief Registers all the member functions of an instance.
*
* A bus is used to convey a certain set of events. This method detects
* and registers to the bus all the matching member functions of an
* instance.<br/>
* For each event type `E`, a matching member function has the following
* signature: `void receive(const E &)`.
*
* @tparam Instance Type of instance to register.
* @param instance A valid instance of the right type.
*/
template<typename Instance>
void reg(Instance instance) {
using accumulator_type = int[];
accumulator_type accumulator = {
(Bus<Sig, Event>::reg(instance), 0),
(Bus<Sig, Other>::reg(instance), 0)...
};
return void(accumulator);
}
/**
* @brief Number of listeners connected to the bus.
* @return Number of listeners currently connected.
*/
size_type size() const noexcept {
using accumulator_type = std::size_t[];
std::size_t sz = Bus<Sig, Event>::size();
accumulator_type accumulator = { sz, (sz += Bus<Sig, Other>::size())... };
return void(accumulator), sz;
}
/**
* @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 {
using accumulator_type = bool[];
bool ret = Bus<Sig, Event>::empty();
accumulator_type accumulator = { ret, (ret = ret && Bus<Sig, Other>::empty())... };
return void(accumulator), ret;
}
/**
* @brief Connects a free function to the bus.
* @tparam Type Type of event to which to connect the function.
* @tparam Function A valid free function pointer.
*/
template<typename Type, void(*Function)(const Type &)>
void connect() {
Bus<Sig, Type>::template connect<Function>();
}
/**
* @brief Disconnects a free function from the bus.
* @tparam Type Type of event from which to disconnect the function.
* @tparam Function A valid free function pointer.
*/
template<typename Type, void(*Function)(const Type &)>
void disconnect() {
Bus<Sig, Type>::template disconnect<Function>();
}
/**
* @brief Publishes an event.
*
* All the listeners are notified. Order isn't guaranteed.
*
* @tparam Type Type of event to publish.
* @tparam Args Types of arguments to use to construct the event.
* @param args Arguments to use to construct the event.
*/
template<typename Type, typename... Args>
void publish(Args &&... args) {
Bus<Sig, Type>::publish(std::forward<Args>(args)...);
}
};
/**
* @brief Event bus specialization for a single type.
*
* The event bus is designed to allow an easy registration of a specific member
* function to a signal handler (either manager or unmanaged).
* Classes must publicly expose the required member function to allow the bus to
* detect it for the purpose of registering and unregistering instances.<br/>
* In particular, a matching member function has the following signature:
* `void receive(const Event &)`. Events of the given type will be properly
* redirected to all the listeners by calling the right member function, if any.
*
* @tparam Sig Type of signal handler to use.
* @tparam Event Type of event managed by the bus.
*/
template<template<typename...> class Sig, typename Event>
class Bus<Sig, Event> {
using signal_type = Sig<void(const Event &)>;
template<typename Class>
using instance_type = typename signal_type::template instance_type<Class>;
template<typename Class>
auto disconnect(int, instance_type<Class> instance)
-> decltype(std::declval<Class>().receive(std::declval<Event>()), void()) {
signal.template disconnect<Class, &Class::receive>(std::move(instance));
}
template<typename Class>
auto connect(int, instance_type<Class> instance)
-> decltype(std::declval<Class>().receive(std::declval<Event>()), void()) {
signal.template connect<Class, &Class::receive>(std::move(instance));
}
template<typename Class> void disconnect(char, instance_type<Class>) {}
template<typename Class> void connect(char, instance_type<Class>) {}
public:
/*! @brief Unsigned integer type. */
using size_type = typename signal_type::size_type;
/**
* @brief Unregisters member functions of instances.
*
* This method tries to detect and unregister from the bus matching member
* functions of instances.<br/>
* A matching member function has the following signature:
* `void receive(const Event &)`.
*
* @tparam Class Type of instance to unregister.
* @param instance A valid instance of the right type.
*/
template<typename Class>
void unreg(instance_type<Class> instance) {
disconnect(0, std::move(instance));
}
/**
* @brief Tries to register an instance.
*
* This method tries to detect and register to the bus matching member
* functions of instances.<br/>
* A matching member function has the following signature:
* `void receive(const Event &)`.
*
* @tparam Class Type of instance to register.
* @param instance A valid instance of the right type.
*/
template<typename Class>
void reg(instance_type<Class> instance) {
connect(0, std::move(instance));
}
/**
* @brief Number of listeners connected to the bus.
* @return Number of listeners currently connected.
*/
size_type size() const noexcept {
return signal.size();
}
/**
* @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 {
return signal.empty();
}
/**
* @brief Connects a free function to the bus.
* @tparam Function A valid free function pointer.
*/
template<void(*Function)(const Event &)>
void connect() {
signal.template connect<Function>();
}
/**
* @brief Disconnects a free function from the bus.
* @tparam Function A valid free function pointer.
*/
template<void(*Function)(const Event &)>
void disconnect() {
signal.template disconnect<Function>();
}
/**
* @brief Publishes an event.
*
* All the listeners are notified. Order isn't guaranteed.
*
* @tparam Args Types of arguments to use to construct the event.
* @param args Arguments to use to construct the event.
*/
template<typename... Args>
void publish(Args &&... args) {
signal.publish({ std::forward<Args>(args)... });
}
private:
signal_type signal;
};
/**
* @brief Managed event bus.
*
* A managed event bus uses the Signal class template as an underlying type. The
* type of the instances is the one required by the signal handler:
* `std::shared_ptr<Class>` (a shared pointer).
*
* @tparam Event The list of events managed by the bus.
*/
template<typename... Event>
using ManagedBus = Bus<Signal, Event...>;
/**
* @brief Unmanaged event bus.
*
* An unmanaged event bus uses the SigH class template as an underlying type.
* The type of the instances is the one required by the signal handler:
* `Class *` (a naked pointer).<br/>
* When it comes to work with this kind of bus, users must guarantee that the
* lifetimes of the instances overcome the one of the bus itself.
*
* @tparam Event The list of events managed by the bus.
*/
template<typename... Event>
using UnmanagedBus = Bus<SigH, Event...>;
}
#endif // ENTT_SIGNAL_BUS_HPP

View File

@@ -3,6 +3,7 @@
#include <utility>
#include "../config/config.h"
namespace entt {
@@ -36,21 +37,21 @@ class Delegate<Ret(Args...)> final {
using proto_type = Ret(*)(void *, Args...);
using stub_type = std::pair<void *, proto_type>;
static Ret fallback(void *, Args...) noexcept { return {}; }
static Ret fallback(void *, Args...) ENTT_NOEXCEPT { return {}; }
template<Ret(*Function)(Args...)>
static Ret proto(void *, Args... args) {
return (Function)(args...);
}
template<typename Class, Ret(Class::*Member)(Args...)>
template<typename Class, Ret(Class:: *Member)(Args...)>
static Ret proto(void *instance, Args... args) {
return (static_cast<Class *>(instance)->*Member)(args...);
}
public:
/*! @brief Default constructor. */
Delegate() noexcept
Delegate() ENTT_NOEXCEPT
: stub{std::make_pair(nullptr, &fallback)}
{}
@@ -59,7 +60,7 @@ public:
* @tparam Function A valid free function pointer.
*/
template<Ret(*Function)(Args...)>
void connect() noexcept {
void connect() ENTT_NOEXCEPT {
stub = std::make_pair(nullptr, &proto<Function>);
}
@@ -74,8 +75,8 @@ public:
* @tparam Member Member function to connect to the delegate.
* @param instance A valid instance of type pointer to `Class`.
*/
template<typename Class, Ret(Class::*Member)(Args...)>
void connect(Class *instance) noexcept {
template<typename Class, Ret(Class:: *Member)(Args...)>
void connect(Class *instance) ENTT_NOEXCEPT {
stub = std::make_pair(instance, &proto<Class, Member>);
}
@@ -84,7 +85,7 @@ public:
*
* After a reset, a delegate can be safely invoked with no effect.
*/
void reset() noexcept {
void reset() ENTT_NOEXCEPT {
stub = std::make_pair(nullptr, &fallback);
}
@@ -105,7 +106,7 @@ public:
* @param other Delegate with which to compare.
* @return True if the two delegates are identical, false otherwise.
*/
bool operator==(const Delegate<Ret(Args...)> &other) const noexcept {
bool operator==(const Delegate<Ret(Args...)> &other) const ENTT_NOEXCEPT {
return stub.first == other.stub.first && stub.second == other.stub.second;
}
@@ -126,7 +127,7 @@ private:
* @return True if the two delegates are different, false otherwise.
*/
template<typename Ret, typename... Args>
bool operator!=(const Delegate<Ret(Args...)> &lhs, const Delegate<Ret(Args...)> &rhs) noexcept {
bool operator!=(const Delegate<Ret(Args...)> &lhs, const Delegate<Ret(Args...)> &rhs) ENTT_NOEXCEPT {
return !(lhs == rhs);
}

View File

@@ -6,8 +6,10 @@
#include <memory>
#include <utility>
#include <cstdint>
#include <algorithm>
#include <type_traits>
#include "../config/config.h"
#include "../core/family.hpp"
#include "signal.hpp"
#include "sigh.hpp"
@@ -20,42 +22,40 @@ namespace entt {
* A dispatcher can be used either to trigger an immediate event or to enqueue
* events to be published all together once per tick.<br/>
* Listeners are provided in the form of member functions. For each event of
* type `Event`, listeners must have the following signature:
* `void(const Event &)`. Member functions named `receive` are automatically
* detected and registered or unregistered by the dispatcher.
* type `Event`, listeners must have the following function type:
* @code{.cpp}
* void(const Event &)
* @endcode
*
* @tparam Sig Type of the signal handler to use.
* Member functions named `receive` are automatically detected and registered or
* unregistered by the dispatcher. The type of the instances is `Class *` (a
* naked pointer). It means that users must guarantee that the lifetimes of the
* instances overcome the one of the dispatcher itself to avoid crashes.
*/
template<template<typename...> class Sig>
class Dispatcher final {
using event_family = Family<struct InternalDispatcherEventFamily>;
template<typename Class, typename Event>
using instance_type = typename Sig<void(const Event &)>::template instance_type<Class>;
using instance_type = typename SigH<void(const Event &)>::template instance_type<Class>;
struct BaseSignalWrapper {
virtual ~BaseSignalWrapper() = default;
virtual void publish(std::size_t) = 0;
virtual void publish() = 0;
};
template<typename Event>
struct SignalWrapper final: BaseSignalWrapper {
void publish(std::size_t current) override {
for(const auto &event: events[current]) {
signal.publish(event);
}
using sink_type = typename SigH<void(const Event &)>::sink_type;
events[current].clear();
void publish() override {
const auto &curr = current++;
current %= std::extent<decltype(events)>::value;
std::for_each(events[curr].cbegin(), events[curr].cend(), [this](const auto &event) { signal.publish(event); });
events[curr].clear();
}
template<typename Class, void(Class::*Member)(const Event &)>
inline void connect(instance_type<Class, Event> instance) noexcept {
signal.template connect<Class, Member>(std::move(instance));
}
template<typename Class, void(Class::*Member)(const Event &)>
inline void disconnect(instance_type<Class, Event> instance) noexcept {
signal.template disconnect<Class, Member>(std::move(instance));
inline sink_type sink() ENTT_NOEXCEPT {
return signal.sink();
}
template<typename... Args>
@@ -64,19 +64,16 @@ class Dispatcher final {
}
template<typename... Args>
inline void enqueue(std::size_t current, Args &&... args) {
inline void enqueue(Args &&... args) {
events[current].push_back({ std::forward<Args>(args)... });
}
private:
Sig<void(const Event &)> signal{};
SigH<void(const Event &)> signal{};
std::vector<Event> events[2];
int current{};
};
inline static std::size_t buffer(bool mode) {
return mode ? 0 : 1;
}
template<typename Event>
SignalWrapper<Event> & wrapper() {
const auto type = event_family::type<Event>();
@@ -93,52 +90,30 @@ class Dispatcher final {
}
public:
/*! @brief Default constructor. */
Dispatcher() noexcept
: wrappers{}, mode{false}
{}
/*! @brief Type of sink for the given event. */
template<typename Event>
using sink_type = typename SignalWrapper<Event>::sink_type;
/**
* @brief Registers a listener given in the form of a member function.
* @brief Returns a sink object for the given event.
*
* A matching member function has the following signature:
* `void receive(const Event &)`. Member functions named `receive` are
* automatically detected and registered if available.
* A sink is an opaque object used to connect listeners to events.
*
* @warning
* Connecting a listener during an update may lead to unexpected behavior.
* Register listeners before or after invoking the update if possible.
* The function type for a listener is:
* @code{.cpp}
* void(const Event &)
* @endcode
*
* @tparam Event Type of event to which to connect the function.
* @tparam Class Type of class to which the member function belongs.
* @tparam Member Member function to connect to the signal.
* @param instance A valid instance of the right type.
* The order of invocation of the listeners isn't guaranteed.
*
* @sa SigH::Sink
*
* @tparam Event Type of event of which to get the sink.
* @return A temporary sink object.
*/
template<typename Event, typename Class, void(Class::*Member)(const Event &) = &Class::receive>
void connect(instance_type<Class, Event> instance) noexcept {
wrapper<Event>().template connect<Class, Member>(std::move(instance));
}
/**
* @brief Unregisters a listener given in the form of a member function.
*
* A matching member function has the following signature:
* `void receive(const Event &)`. Member functions named `receive` are
* automatically detected and unregistered if available.
*
* @warning
* Disconnecting a listener during an update may lead to unexpected
* behavior. Unregister listeners before or after invoking the update if
* possible.
*
* @tparam Event Type of event from which to disconnect the function.
* @tparam Class Type of class to which the member function belongs.
* @tparam Member Member function to connect to the signal.
* @param instance A valid instance of the right type.
*/
template<typename Event, typename Class, void(Class::*Member)(const Event &) = &Class::receive>
void disconnect(instance_type<Class, Event> instance) noexcept {
wrapper<Event>().template disconnect<Class, Member>(std::move(instance));
template<typename Event>
inline sink_type<Event> sink() ENTT_NOEXCEPT {
return wrapper<Event>().sink();
}
/**
@@ -152,7 +127,7 @@ public:
* @param args Arguments to use to construct the event.
*/
template<typename Event, typename... Args>
void trigger(Args &&... args) {
inline void trigger(Args &&... args) {
wrapper<Event>().trigger(std::forward<Args>(args)...);
}
@@ -167,58 +142,46 @@ public:
* @param args Arguments to use to construct the event.
*/
template<typename Event, typename... Args>
void enqueue(Args &&... args) {
wrapper<Event>().enqueue(buffer(mode), std::forward<Args>(args)...);
inline void enqueue(Args &&... args) {
wrapper<Event>().enqueue(std::forward<Args>(args)...);
}
/**
* @brief Delivers all the pending events of the given type.
*
* This method is blocking and it doesn't return until all the events are
* delivered to the registered listeners. It's responsibility of the users
* to reduce at a minimum the time spent in the bodies of the listeners.
*
* @tparam Event Type of events to send.
*/
template<typename Event>
inline void update() {
wrapper<Event>().publish();
}
/**
* @brief Delivers all the pending events.
*
* This method is blocking and it doesn't return until all the events are
* delivered to the registered listeners. It's responsability of the users
* delivered to the registered listeners. It's responsibility of the users
* to reduce at a minimum the time spent in the bodies of the listeners.
*/
void update() {
const auto buf = buffer(mode);
mode = !mode;
inline void update() {
for(auto pos = wrappers.size(); pos; --pos) {
auto &wrapper = wrappers[pos-1];
if(wrapper) {
wrapper->publish(buf);
wrapper->publish();
}
}
}
private:
std::vector<std::unique_ptr<BaseSignalWrapper>> wrappers;
bool mode;
};
/**
* @brief Managed dispatcher.
*
* A managed dispatcher uses the Signal class template as an underlying type.
* The type of the instances is the one required by the signal handler:
* `std::shared_ptr<Class>` (a shared pointer).
*/
using ManagedDispatcher = Dispatcher<Signal>;
/**
* @brief Unmanaged dispatcher.
*
* An unmanaged dispatcher uses the SigH class template as an underlying type.
* The type of the instances is the one required by the signal handler:
* `Class *` (a naked pointer).<br/>
* When it comes to work with this kind of dispatcher, users must guarantee that
* the lifetimes of the instances overcome the one of the dispatcher itself.
*/
using UnmanagedDispatcher = Dispatcher<SigH>;
}

View File

@@ -10,6 +10,7 @@
#include <memory>
#include <vector>
#include <list>
#include "../config/config.h"
namespace entt {
@@ -40,8 +41,8 @@ template<typename Derived>
class Emitter {
struct BaseHandler {
virtual ~BaseHandler() = default;
virtual bool empty() const noexcept = 0;
virtual void clear() noexcept = 0;
virtual bool empty() const ENTT_NOEXCEPT = 0;
virtual void clear() ENTT_NOEXCEPT = 0;
};
template<typename Event>
@@ -51,14 +52,14 @@ class Emitter {
using container_type = std::list<element_type>;
using connection_type = typename container_type::iterator;
bool empty() const noexcept override {
bool empty() const ENTT_NOEXCEPT override {
auto pred = [](auto &&element) { return element.first; };
return std::all_of(onceL.cbegin(), onceL.cend(), pred) &&
std::all_of(onL.cbegin(), onL.cend(), pred);
}
void clear() noexcept override {
void clear() ENTT_NOEXCEPT override {
if(publishing) {
auto func = [](auto &&element) { element.first = true; };
std::for_each(onceL.begin(), onceL.end(), func);
@@ -77,7 +78,7 @@ class Emitter {
return onL.emplace(onL.cend(), false, std::move(listener));
}
void erase(connection_type conn) noexcept {
void erase(connection_type conn) ENTT_NOEXCEPT {
conn->first = true;
if(!publishing) {
@@ -111,19 +112,19 @@ class Emitter {
container_type onL{};
};
static std::size_t next() noexcept {
static std::size_t next() ENTT_NOEXCEPT {
static std::size_t counter = 0;
return counter++;
}
template<typename>
static std::size_t type() noexcept {
static std::size_t type() ENTT_NOEXCEPT {
static std::size_t value = next();
return value;
}
template<typename Event>
Handler<Event> & handler() noexcept {
Handler<Event> & handler() ENTT_NOEXCEPT {
const std::size_t family = type<Event>();
if(!(family < handlers.size())) {
@@ -138,7 +139,7 @@ class Emitter {
}
public:
/** @brief Type of listeners accepted for the given type of event. */
/** @brief Type of listeners accepted for the given event. */
template<typename Event>
using Listener = typename Handler<Event>::listener_type;
@@ -157,7 +158,7 @@ public:
friend class Emitter;
/*! @brief Default constructor. */
Connection() noexcept = default;
Connection() ENTT_NOEXCEPT = default;
/**
* @brief Creates a connection that wraps its underlying instance.
@@ -173,7 +174,7 @@ public:
Connection(Connection &&) = default;
/**
* @brief Default copy assignament operator.
* @brief Default copy assignment operator.
* @return This connection.
*/
Connection & operator=(const Connection &) = default;
@@ -186,10 +187,10 @@ public:
};
/*! @brief Default constructor. */
Emitter() noexcept = default;
Emitter() ENTT_NOEXCEPT = default;
/*! @brief Default destructor. */
virtual ~Emitter() noexcept {
virtual ~Emitter() ENTT_NOEXCEPT {
static_assert(std::is_base_of<Emitter<Derived>, Derived>::value, "!");
}
@@ -200,7 +201,7 @@ public:
/*! @brief Copying an emitter isn't allowed. @return This emitter. */
Emitter & operator=(const Emitter &) = delete;
/*! @brief Default move assignament operator. @return This emitter. */
/*! @brief Default move assignment operator. @return This emitter. */
Emitter & operator=(Emitter &&) = default;
/**
@@ -279,7 +280,7 @@ public:
* @param conn A valid connection.
*/
template<typename Event>
void erase(Connection<Event> conn) noexcept {
void erase(Connection<Event> conn) ENTT_NOEXCEPT {
handler<Event>().erase(std::move(conn));
}
@@ -292,7 +293,7 @@ public:
* @tparam Event Type of event to reset.
*/
template<typename Event>
void clear() noexcept {
void clear() ENTT_NOEXCEPT {
handler<Event>().clear();
}
@@ -302,7 +303,7 @@ public:
* All the connections previously returned are invalidated. Using them
* results in undefined behavior.
*/
void clear() noexcept {
void clear() ENTT_NOEXCEPT {
std::for_each(handlers.begin(), handlers.end(),
[](auto &&handler) { if(handler) { handler->clear(); } });
}
@@ -313,7 +314,7 @@ public:
* @return True if there are no listeners registered, false otherwise.
*/
template<typename Event>
bool empty() const noexcept {
bool empty() const ENTT_NOEXCEPT {
const std::size_t family = type<Event>();
return (!(family < handlers.size()) ||
@@ -325,7 +326,7 @@ public:
* @brief Checks if there are listeners registered with the event emitter.
* @return True if there are no listeners registered, false otherwise.
*/
bool empty() const noexcept {
bool empty() const ENTT_NOEXCEPT {
return std::all_of(handlers.cbegin(), handlers.cend(),
[](auto &&handler) { return !handler || handler->empty(); });
}

View File

@@ -5,12 +5,19 @@
#include <algorithm>
#include <utility>
#include <vector>
#include "../config/config.h"
namespace entt {
namespace {
namespace internal {
/**
* @cond TURN_OFF_DOXYGEN
* Internal details not to be documented.
*/
template<typename, typename>
@@ -46,14 +53,14 @@ struct Invoker<void(Args...), Collector> {
template<typename Ret>
struct NullCollector final {
using result_type = Ret;
bool operator()(result_type) const noexcept { return true; }
bool operator()(result_type) const ENTT_NOEXCEPT { return true; }
};
template<>
struct NullCollector<void> final {
using result_type = void;
bool operator()() const noexcept { return true; }
bool operator()() const ENTT_NOEXCEPT { return true; }
};
@@ -71,9 +78,27 @@ template<typename Function>
using DefaultCollectorType = typename DefaultCollector<Function>::collector_type;
/**
* Internal details not to be documented.
* @endcond TURN_OFF_DOXYGEN
*/
}
/**
* @brief Sink implementation.
*
* Primary template isn't defined on purpose. All the specializations give a
* compile-time error unless the template parameter is a function type.
*
* @tparam Function A valid function type.
*/
template<typename Function>
class Sink;
/**
* @brief Unmanaged signal handler declaration.
*
@@ -83,10 +108,126 @@ using DefaultCollectorType = typename DefaultCollector<Function>::collector_type
* @tparam Function A valid function type.
* @tparam Collector Type of collector to use, if any.
*/
template<typename Function, typename Collector = DefaultCollectorType<Function>>
template<typename Function, typename Collector = internal::DefaultCollectorType<Function>>
class SigH;
/**
* @brief Sink implementation.
*
* A sink is an opaque object used to connect listeners to signals.<br/>
* The function type for a listener is the one of the signal to which it
* belongs.
*
* The clear separation between a signal and a sink permits to store the
* former as private data member without exposing the publish functionality
* to the users of a class.
*
* @tparam Ret Return type of a function type.
* @tparam Args Types of arguments of a function type.
*/
template<typename Ret, typename... Args>
class Sink<Ret(Args...)> final {
/*! @brief A signal is allowed to create sinks. */
template<typename, typename>
friend class SigH;
using proto_type = Ret(*)(void *, Args...);
using call_type = std::pair<void *, proto_type>;
template<Ret(*Function)(Args...)>
static Ret proto(void *, Args... args) {
return (Function)(args...);
}
template<typename Class, Ret(Class:: *Member)(Args... args)>
static Ret proto(void *instance, Args... args) {
return (static_cast<Class *>(instance)->*Member)(args...);
}
Sink(std::vector<call_type> &calls)
: calls{calls}
{}
public:
/**
* @brief Connects a free function to a signal.
*
* The signal handler performs checks to avoid multiple connections for
* free functions.
*
* @tparam Function A valid free function pointer.
*/
template<Ret(*Function)(Args...)>
void connect() {
disconnect<Function>();
calls.emplace_back(nullptr, &proto<Function>);
}
/**
* @brief Connects a member function for a given instance to a signal.
*
* The signal isn't responsible for the connected object. Users must
* guarantee that the lifetime of the instance overcomes the one of the
* signal. On the other side, the signal handler performs checks to
* avoid multiple connections for the same member function of a given
* instance.
*
* @tparam Class Type of class to which the member function belongs.
* @tparam Member Member function to connect to the signal.
* @param instance A valid instance of type pointer to `Class`.
*/
template <typename Class, Ret(Class:: *Member)(Args...) = &Class::receive>
void connect(Class *instance) {
disconnect<Class, Member>(instance);
calls.emplace_back(instance, &proto<Class, Member>);
}
/**
* @brief Disconnects a free function from a signal.
* @tparam Function A valid free function pointer.
*/
template<Ret(*Function)(Args...)>
void disconnect() {
call_type target{nullptr, &proto<Function>};
calls.erase(std::remove(calls.begin(), calls.end(), std::move(target)), calls.end());
}
/**
* @brief Disconnects the given member function from a signal.
* @tparam Class Type of class to which the member function belongs.
* @tparam Member Member function to connect to the signal.
* @param instance A valid instance of type pointer to `Class`.
*/
template<typename Class, Ret(Class:: *Member)(Args...)>
void disconnect(Class *instance) {
call_type target{instance, &proto<Class, Member>};
calls.erase(std::remove(calls.begin(), calls.end(), std::move(target)), calls.end());
}
/**
* @brief Removes all existing connections for the given instance.
* @tparam Class Type of class to which the member function belongs.
* @param instance A valid instance of type pointer to `Class`.
*/
template<typename Class>
void disconnect(Class *instance) {
auto func = [instance](const call_type &call) { return call.first == instance; };
calls.erase(std::remove_if(calls.begin(), calls.end(), std::move(func)), calls.end());
}
/**
* @brief Disconnects all the listeners from a signal.
*/
void disconnect() {
calls.clear();
}
private:
std::vector<call_type> &calls;
};
/**
* @brief Unmanaged signal handler definition.
*
@@ -111,24 +252,16 @@ class SigH;
* @tparam Collector Type of collector to use, if any.
*/
template<typename Ret, typename... Args, typename Collector>
class SigH<Ret(Args...), Collector> final: private Invoker<Ret(Args...), Collector> {
using typename Invoker<Ret(Args...), Collector>::call_type;
template<Ret(*Function)(Args...)>
static Ret proto(void *, Args... args) {
return (Function)(args...);
}
template<typename Class, Ret(Class::*Member)(Args... args)>
static Ret proto(void *instance, Args... args) {
return (static_cast<Class *>(instance)->*Member)(args...);
}
class SigH<Ret(Args...), Collector> final: private internal::Invoker<Ret(Args...), Collector> {
using call_type = typename internal::Invoker<Ret(Args...), Collector>::call_type;
public:
/*! @brief Unsigned integer type. */
using size_type = typename std::vector<call_type>::size_type;
/*! @brief Collector type. */
using collector_type = Collector;
/*! @brief Sink type. */
using sink_type = Sink<Ret(Args...)>;
/**
* @brief Instance type when it comes to connecting member functions.
@@ -141,7 +274,7 @@ public:
* @brief Number of listeners connected to the signal.
* @return Number of listeners currently connected.
*/
size_type size() const noexcept {
size_type size() const ENTT_NOEXCEPT {
return calls.size();
}
@@ -149,80 +282,21 @@ public:
* @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 {
bool empty() const ENTT_NOEXCEPT {
return calls.empty();
}
/**
* @brief Disconnects all the listeners from a signal.
*/
void clear() noexcept {
calls.clear();
}
/**
* @brief Connects a free function to a signal.
* @brief Returns a sink object for the given signal.
*
* The signal handler performs checks to avoid multiple connections for free
* functions.
* A sink is an opaque object used to connect listeners to signals.<br/>
* The function type for a listener is the one of the signal to which it
* belongs. The order of invocation of the listeners isn't guaranteed.
*
* @tparam Function A valid free function pointer.
* @return A temporary sink object.
*/
template<Ret(*Function)(Args...)>
void connect() {
disconnect<Function>();
calls.emplace_back(nullptr, &proto<Function>);
}
/**
* @brief Connects a member function for a given instance to a signal.
*
* The signal isn't responsible for the connected object. Users must
* guarantee that the lifetime of the instance overcomes the one of the
* signal. On the other side, the signal handler performs checks to avoid
* multiple connections for the same member function of a given instance.
*
* @tparam Class Type of class to which the member function belongs.
* @tparam Member Member function to connect to the signal.
* @param instance A valid instance of type pointer to `Class`.
*/
template <typename Class, Ret(Class::*Member)(Args...)>
void connect(instance_type<Class> instance) {
disconnect<Class, Member>(instance);
calls.emplace_back(instance, &proto<Class, Member>);
}
/**
* @brief Disconnects a free function from a signal.
* @tparam Function A valid free function pointer.
*/
template<Ret(*Function)(Args...)>
void disconnect() {
call_type target{nullptr, &proto<Function>};
calls.erase(std::remove(calls.begin(), calls.end(), std::move(target)), calls.end());
}
/**
* @brief Disconnects the given member function from a signal.
* @tparam Class Type of class to which the member function belongs.
* @tparam Member Member function to connect to the signal.
* @param instance A valid instance of type pointer to `Class`.
*/
template<typename Class, Ret(Class::*Member)(Args...)>
void disconnect(instance_type<Class> instance) {
call_type target{instance, &proto<Class, Member>};
calls.erase(std::remove(calls.begin(), calls.end(), std::move(target)), calls.end());
}
/**
* @brief Removes all existing connections for the given instance.
* @tparam Class Type of class to which the member function belongs.
* @param instance A valid instance of type pointer to `Class`.
*/
template<typename Class>
void disconnect(instance_type<Class> instance) {
auto func = [instance](const call_type &call) { return call.first == instance; };
calls.erase(std::remove_if(calls.begin(), calls.end(), std::move(func)), calls.end());
sink_type sink() {
return { calls };
}
/**
@@ -247,9 +321,7 @@ public:
collector_type collect(Args... args) {
collector_type collector;
for(auto pos = calls.size(); pos; --pos) {
auto &call = calls[pos-1];
for(auto &&call: calls) {
if(!this->invoke(collector, call.second, call.first, args...)) {
break;
}
@@ -277,7 +349,7 @@ public:
* @param other Signal with which to compare.
* @return True if the two signals are identical, false otherwise.
*/
bool operator==(const SigH &other) const noexcept {
bool operator==(const SigH &other) const ENTT_NOEXCEPT {
return std::equal(calls.cbegin(), calls.cend(), other.calls.cbegin(), other.calls.cend());
}
@@ -299,7 +371,7 @@ private:
* @return True if the two signals are different, false otherwise.
*/
template<typename Ret, typename... Args>
bool operator!=(const SigH<Ret(Args...)> &lhs, const SigH<Ret(Args...)> &rhs) noexcept {
bool operator!=(const SigH<Ret(Args...)> &lhs, const SigH<Ret(Args...)> &rhs) ENTT_NOEXCEPT {
return !(lhs == rhs);
}

View File

@@ -1,233 +0,0 @@
#ifndef ENTT_SIGNAL_SIGNAL_HPP
#define ENTT_SIGNAL_SIGNAL_HPP
#include <memory>
#include <vector>
#include <utility>
#include <cstdint>
#include <iterator>
#include <algorithm>
namespace entt {
/**
* @brief Managed signal handler declaration.
*
* Primary template isn't defined on purpose. All the specializations give a
* compile-time error unless the template parameter is a function type.
*/
template<typename>
class Signal;
/**
* @brief Managed signal handler definition.
*
* Managed signal handler. It works with weak pointers to classes and pointers
* to member functions as well as pointers to free functions. References are
* automatically removed when the instances to which they point are freed.
*
* This class can be used to create signals used later to notify a bunch of
* listeners.
*
* @tparam Args Types of arguments of a function type.
*/
template<typename... Args>
class Signal<void(Args...)> final {
using proto_type = bool(*)(std::weak_ptr<void> &, Args...);
using call_type = std::pair<std::weak_ptr<void>, proto_type>;
template<void(*Function)(Args...)>
static bool proto(std::weak_ptr<void> &, Args... args) {
Function(args...);
return true;
}
template<typename Class, void(Class::*Member)(Args...)>
static bool proto(std::weak_ptr<void> &wptr, Args... args) {
bool ret = false;
if(!wptr.expired()) {
auto ptr = std::static_pointer_cast<Class>(wptr.lock());
(ptr.get()->*Member)(args...);
ret = true;
}
return ret;
}
public:
/*! @brief Unsigned integer type. */
using size_type = std::size_t;
/**
* @brief Instance type when it comes to connecting member functions.
* @tparam Class Type of class to which the member function belongs.
*/
template<typename Class>
using instance_type = std::shared_ptr<Class>;
/**
* @brief Number of listeners connected to the signal.
* @return Number of listeners currently connected.
*/
size_type size() const noexcept {
return calls.size();
}
/**
* @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 {
return calls.empty();
}
/**
* @brief Disconnects all the listeners from a signal.
*/
void clear() noexcept {
calls.clear();
}
/**
* @brief Connects a free function to a signal.
*
* The signal handler performs checks to avoid multiple connections for free
* functions.
*
* @tparam Function A valid free function pointer.
*/
template<void(*Function)(Args...)>
void connect() {
disconnect<Function>();
calls.emplace_back(std::weak_ptr<void>{}, &proto<Function>);
}
/**
* @brief Connects a member function for a given instance to a signal.
*
* The signal handler performs checks to avoid multiple connections for the
* same member function of a given instance.
*
* @tparam Class Type of class to which the member function belongs.
* @tparam Member Member function to connect to the signal.
* @param instance A valid instance of type pointer to `Class`.
*/
template<typename Class, void(Class::*Member)(Args...)>
void connect(instance_type<Class> instance) {
disconnect<Class, Member>(instance);
calls.emplace_back(std::move(instance), &proto<Class, Member>);
}
/**
* @brief Disconnects a free function from a signal.
* @tparam Function A valid free function pointer.
*/
template<void(*Function)(Args...)>
void disconnect() {
calls.erase(std::remove_if(calls.begin(), calls.end(),
[](const call_type &call) { return call.second == &proto<Function> && !call.first.lock(); }
), calls.end());
}
/**
* @brief Disconnects the given member function from a signal.
* @tparam Class Type of class to which the member function belongs.
* @tparam Member Member function to connect to the signal.
* @param instance A valid instance of type pointer to `Class`.
*/
template<typename Class, void(Class::*Member)(Args...)>
void disconnect(instance_type<Class> instance) {
calls.erase(std::remove_if(calls.begin(), calls.end(),
[instance{std::move(instance)}](const call_type &call) { return call.second == &proto<Class, Member> && call.first.lock() == instance; }
), calls.end());
}
/**
* @brief Removes all existing connections for the given instance.
* @tparam Class Type of class to which the member function belongs.
* @param instance A valid instance of type pointer to `Class`.
*/
template<typename Class>
void disconnect(instance_type<Class> instance) {
calls.erase(std::remove_if(calls.begin(), calls.end(),
[instance{std::move(instance)}](const call_type &call) { return call.first.lock() == instance; }
), calls.end());
}
/**
* @brief Triggers a signal.
*
* All the listeners are notified. Order isn't guaranteed.
*
* @param args Arguments to use to invoke listeners.
*/
void publish(Args... args) {
std::vector<call_type> next;
for(auto pos = calls.size(); pos; --pos) {
auto &call = calls[pos-1];
if((call.second)(call.first, args...)) {
next.push_back(call);
}
}
calls.swap(next);
}
/**
* @brief Swaps listeners between the two signals.
* @param lhs A valid signal object.
* @param rhs A valid signal object.
*/
friend void swap(Signal &lhs, Signal &rhs) {
using std::swap;
swap(lhs.calls, rhs.calls);
}
/**
* @brief Checks if the contents of the two signals are identical.
*
* Two signals are identical if they have the same size and the same
* listeners registered exactly in the same order.
*
* @param other Signal with which to compare.
* @return True if the two signals are identical, false otherwise.
*/
bool operator==(const Signal &other) const noexcept {
return std::equal(calls.cbegin(), calls.cend(), other.calls.cbegin(), other.calls.cend(), [](const auto &lhs, const auto &rhs) {
return (lhs.second == rhs.second) && (lhs.first.lock() == rhs.first.lock());
});
}
private:
std::vector<call_type> calls;
};
/**
* @brief Checks if the contents of the two signals are different.
*
* Two signals are identical if they have the same size and the same
* listeners registered exactly in the same order.
*
* @tparam Args Types of arguments of a function type.
* @param lhs A valid signal object.
* @param rhs A valid signal object.
* @return True if the two signals are different, false otherwise.
*/
template<typename... Args>
bool operator!=(const Signal<void(Args...)> &lhs, const Signal<void(Args...)> &rhs) noexcept {
return !(lhs == rhs);
}
}
#endif // ENTT_SIGNAL_SIGNAL_HPP

View File

@@ -4,16 +4,16 @@
add_library(odr OBJECT odr.cpp)
macro(ADD_ENTT_TEST TEST_NAME TEST_SOURCE)
add_executable(${TEST_NAME} $<TARGET_OBJECTS:odr> ${TEST_SOURCE})
target_link_libraries(${TEST_NAME} PRIVATE gtest_main Threads::Threads)
add_test(NAME ${TEST_NAME} COMMAND ${TEST_NAME})
endmacro()
# Test benchmark
if(BUILD_BENCHMARK)
add_executable(
benchmark
$<TARGET_OBJECTS:odr>
benchmark/benchmark.cpp
)
target_link_libraries(benchmark PRIVATE gtest_main Threads::Threads)
add_test(NAME benchmark COMMAND benchmark)
ADD_ENTT_TEST(benchmark benchmark/benchmark.cpp)
endif()
# Test mod
@@ -25,15 +25,9 @@ if(BUILD_MOD)
execute_process(COMMAND ${CMAKE_COMMAND} --build . WORKING_DIRECTORY ${DUKTAPE_DEPS_DIR})
set(DUKTAPE_SRC_DIR ${DUKTAPE_DEPS_DIR}/src/src)
add_executable(
mod
$<TARGET_OBJECTS:odr>
${DUKTAPE_SRC_DIR}/duktape.c
mod/mod.cpp
)
set(MOD_TEST_SOURCE ${DUKTAPE_SRC_DIR}/duktape.c mod/mod.cpp)
ADD_ENTT_TEST(mod ${MOD_TEST_SOURCE})
target_include_directories(mod PRIVATE ${DUKTAPE_SRC_DIR})
target_link_libraries(mod PRIVATE gtest_main Threads::Threads m)
add_test(NAME mod COMMAND mod)
endif()
# Test snapshot
@@ -45,84 +39,43 @@ if(BUILD_SNAPSHOT)
execute_process(COMMAND ${CMAKE_COMMAND} --build . WORKING_DIRECTORY ${CEREAL_DEPS_DIR})
set(CEREAL_SRC_DIR ${CEREAL_DEPS_DIR}/src/include)
add_executable(
snapshot
$<TARGET_OBJECTS:odr>
snapshot/snapshot.cpp
)
target_include_directories(snapshot PRIVATE ${CEREAL_SRC_DIR})
target_link_libraries(snapshot PRIVATE gtest_main Threads::Threads)
add_test(NAME snapshot COMMAND snapshot)
ADD_ENTT_TEST(cereal snapshot/snapshot.cpp)
target_include_directories(cereal PRIVATE ${CEREAL_SRC_DIR})
endif()
# Test core
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)
add_test(NAME core COMMAND core)
ADD_ENTT_TEST(algorithm entt/core/algorithm.cpp)
ADD_ENTT_TEST(family entt/core/family.cpp)
ADD_ENTT_TEST(hashed_string entt/core/hashed_string.cpp)
ADD_ENTT_TEST(ident entt/core/ident.cpp)
# Test entity
add_executable(
entity
$<TARGET_OBJECTS:odr>
entt/entity/actor.cpp
entt/entity/registry.cpp
entt/entity/snapshot.cpp
entt/entity/sparse_set.cpp
entt/entity/view.cpp
)
target_link_libraries(entity PRIVATE gtest_main Threads::Threads)
add_test(NAME entity COMMAND entity)
ADD_ENTT_TEST(actor entt/entity/actor.cpp)
ADD_ENTT_TEST(helper entt/entity/helper.cpp)
ADD_ENTT_TEST(prototype entt/entity/prototype.cpp)
ADD_ENTT_TEST(registry entt/entity/registry.cpp)
ADD_ENTT_TEST(snapshot entt/entity/snapshot.cpp)
ADD_ENTT_TEST(sparse_set entt/entity/sparse_set.cpp)
ADD_ENTT_TEST(view entt/entity/view.cpp)
# Test locator
add_executable(
locator
$<TARGET_OBJECTS:odr>
entt/locator/locator.cpp
)
target_link_libraries(locator PRIVATE gtest_main Threads::Threads)
add_test(NAME locator COMMAND locator)
ADD_ENTT_TEST(locator entt/locator/locator.cpp)
# 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)
ADD_ENTT_TEST(process entt/process/process.cpp)
ADD_ENTT_TEST(scheduler entt/process/scheduler.cpp)
# 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)
ADD_ENTT_TEST(resource entt/resource/resource.cpp)
# Test signal
add_executable(
signal
$<TARGET_OBJECTS:odr>
entt/signal/bus.cpp
entt/signal/delegate.cpp
entt/signal/dispatcher.cpp
entt/signal/emitter.cpp
entt/signal/sigh.cpp
entt/signal/signal.cpp
)
target_link_libraries(signal PRIVATE gtest_main Threads::Threads)
add_test(NAME signal COMMAND signal)
ADD_ENTT_TEST(delegate entt/signal/delegate.cpp)
ADD_ENTT_TEST(dispatcher entt/signal/dispatcher.cpp)
ADD_ENTT_TEST(emitter entt/signal/emitter.cpp)
ADD_ENTT_TEST(sigh entt/signal/sigh.cpp)

View File

@@ -73,17 +73,15 @@ TEST(Benchmark, IterateCreateDeleteSingleComponent) {
for(int i = 0; i < 10000; i++) {
for(int j = 0; j < 10000; j++) {
registry.create<Position>();
const auto entity = registry.create();
registry.assign<Position>(entity);
}
for(auto entity: view) {
const auto &position = view.get(entity);
(void)position;
if(rand() % 2 == 0) {
registry.destroy(entity);
}
};
}
}
timer.elapsed();
@@ -95,12 +93,22 @@ TEST(Benchmark, IterateSingleComponent1M) {
std::cout << "Iterating over 1000000 entities, one component" << std::endl;
for(std::uint64_t i = 0; i < 1000000L; i++) {
registry.create<Position>();
const auto entity = registry.create();
registry.assign<Position>(entity);
}
Timer timer;
registry.view<Position>().each([](auto, auto &) {});
timer.elapsed();
auto test = [&registry](auto func) {
Timer timer;
registry.view<Position>().each(func);
timer.elapsed();
};
test([](auto, const auto &) {});
test([](auto, auto &... comp) {
using accumulator_type = int[];
accumulator_type accumulator = { (comp.x = {}, 0)... };
(void)accumulator;
});
}
TEST(Benchmark, IterateSingleComponentRaw1M) {
@@ -109,14 +117,22 @@ TEST(Benchmark, IterateSingleComponentRaw1M) {
std::cout << "Iterating over 1000000 entities, one component, raw view" << std::endl;
for(std::uint64_t i = 0; i < 1000000L; i++) {
registry.create<Position>();
const auto entity = registry.create();
registry.assign<Position>(entity);
}
Timer timer;
for(auto &&component: registry.raw<Position>()) {
(void)component;
}
timer.elapsed();
auto test = [&registry](auto func) {
Timer timer;
registry.view<Position>(entt::raw_t{}).each(func);
timer.elapsed();
};
test([](const auto &) {});
test([](auto &... comp) {
using accumulator_type = int[];
accumulator_type accumulator = { (comp.x = {}, 0)... };
(void)accumulator;
});
}
TEST(Benchmark, IterateTwoComponents1M) {
@@ -125,12 +141,23 @@ TEST(Benchmark, IterateTwoComponents1M) {
std::cout << "Iterating over 1000000 entities, two components" << std::endl;
for(std::uint64_t i = 0; i < 1000000L; i++) {
registry.create<Position, Velocity>();
const auto entity = registry.create();
registry.assign<Position>(entity);
registry.assign<Velocity>(entity);
}
Timer timer;
registry.view<Position, Velocity>().each([](auto, auto &...) {});
timer.elapsed();
auto test = [&registry](auto func) {
Timer timer;
registry.view<Position, Velocity>().each(func);
timer.elapsed();
};
test([](auto, const auto &...) {});
test([](auto, auto &... comp) {
using accumulator_type = int[];
accumulator_type accumulator = { (comp.x = {}, 0)... };
(void)accumulator;
});
}
TEST(Benchmark, IterateTwoComponents1MHalf) {
@@ -139,13 +166,26 @@ TEST(Benchmark, IterateTwoComponents1MHalf) {
std::cout << "Iterating over 1000000 entities, two components, half of the entities have all the components" << std::endl;
for(std::uint64_t i = 0; i < 1000000L; i++) {
auto entity = registry.create<Velocity>();
if(i % 2) { registry.assign<Position>(entity); }
const auto entity = registry.create();
registry.assign<Velocity>(entity);
if(i % 2) {
registry.assign<Position>(entity);
}
}
Timer timer;
registry.view<Position, Velocity>().each([](auto, auto &...) {});
timer.elapsed();
auto test = [&registry](auto func) {
Timer timer;
registry.view<Position, Velocity>().each(func);
timer.elapsed();
};
test([](auto, const auto &...) {});
test([](auto, auto &... comp) {
using accumulator_type = int[];
accumulator_type accumulator = { (comp.x = {}, 0)... };
(void)accumulator;
});
}
TEST(Benchmark, IterateTwoComponents1MOne) {
@@ -154,13 +194,26 @@ TEST(Benchmark, IterateTwoComponents1MOne) {
std::cout << "Iterating over 1000000 entities, two components, only one entity has all the components" << std::endl;
for(std::uint64_t i = 0; i < 1000000L; i++) {
auto entity = registry.create<Velocity>();
if(i == 5000000L) { registry.assign<Position>(entity); }
const auto entity = registry.create();
registry.assign<Velocity>(entity);
if(i == 5000000L) {
registry.assign<Position>(entity);
}
}
Timer timer;
registry.view<Position, Velocity>().each([](auto, auto &...) {});
timer.elapsed();
auto test = [&registry](auto func) {
Timer timer;
registry.view<Position, Velocity>().each(func);
timer.elapsed();
};
test([](auto, const auto &...) {});
test([](auto, auto &... comp) {
using accumulator_type = int[];
accumulator_type accumulator = { (comp.x = {}, 0)... };
(void)accumulator;
});
}
TEST(Benchmark, IterateTwoComponentsPersistent1M) {
@@ -170,12 +223,23 @@ TEST(Benchmark, IterateTwoComponentsPersistent1M) {
std::cout << "Iterating over 1000000 entities, two components, persistent view" << std::endl;
for(std::uint64_t i = 0; i < 1000000L; i++) {
registry.create<Position, Velocity>();
const auto entity = registry.create();
registry.assign<Position>(entity);
registry.assign<Velocity>(entity);
}
Timer timer;
registry.persistent<Position, Velocity>().each([](auto, auto &...) {});
timer.elapsed();
auto test = [&registry](auto func) {
Timer timer;
registry.view<Position, Velocity>(entt::persistent_t{}).each(func);
timer.elapsed();
};
test([](auto, const auto &...) {});
test([](auto, auto &... comp) {
using accumulator_type = int[];
accumulator_type accumulator = { (comp.x = {}, 0)... };
(void)accumulator;
});
}
TEST(Benchmark, IterateFiveComponents1M) {
@@ -184,12 +248,26 @@ TEST(Benchmark, IterateFiveComponents1M) {
std::cout << "Iterating over 1000000 entities, five components" << std::endl;
for(std::uint64_t i = 0; i < 1000000L; i++) {
registry.create<Position, Velocity, Comp<1>, Comp<2>, Comp<3>>();
const auto entity = registry.create();
registry.assign<Position>(entity);
registry.assign<Velocity>(entity);
registry.assign<Comp<1>>(entity);
registry.assign<Comp<2>>(entity);
registry.assign<Comp<3>>(entity);
}
Timer timer;
registry.view<Position, Velocity, Comp<1>, Comp<2>, Comp<3>>().each([](auto, auto &...) {});
timer.elapsed();
auto test = [&registry](auto func) {
Timer timer;
registry.view<Position, Velocity, Comp<1>, Comp<2>, Comp<3>>().each(func);
timer.elapsed();
};
test([](auto, const auto &...) {});
test([](auto, auto &... comp) {
using accumulator_type = int[];
accumulator_type accumulator = { (comp.x = {}, 0)... };
(void)accumulator;
});
}
TEST(Benchmark, IterateFiveComponents1MHalf) {
@@ -198,13 +276,29 @@ TEST(Benchmark, IterateFiveComponents1MHalf) {
std::cout << "Iterating over 1000000 entities, five components, half of the entities have all the components" << std::endl;
for(std::uint64_t i = 0; i < 1000000L; i++) {
auto entity = registry.create<Velocity, Comp<1>, Comp<2>, Comp<3>>();
if(i % 2) { registry.assign<Position>(entity); }
const auto entity = registry.create();
registry.assign<Velocity>(entity);
registry.assign<Comp<1>>(entity);
registry.assign<Comp<2>>(entity);
registry.assign<Comp<3>>(entity);
if(i % 2) {
registry.assign<Position>(entity);
}
}
Timer timer;
registry.view<Position, Velocity, Comp<1>, Comp<2>, Comp<3>>().each([](auto, auto &...) {});
timer.elapsed();
auto test = [&registry](auto func) {
Timer timer;
registry.view<Position, Velocity, Comp<1>, Comp<2>, Comp<3>>().each(func);
timer.elapsed();
};
test([](auto, const auto &...) {});
test([](auto, auto &... comp) {
using accumulator_type = int[];
accumulator_type accumulator = { (comp.x = {}, 0)... };
(void)accumulator;
});
}
TEST(Benchmark, IterateFiveComponents1MOne) {
@@ -213,13 +307,29 @@ TEST(Benchmark, IterateFiveComponents1MOne) {
std::cout << "Iterating over 1000000 entities, five components, only one entity has all the components" << std::endl;
for(std::uint64_t i = 0; i < 1000000L; i++) {
auto entity = registry.create<Velocity, Comp<1>, Comp<2>, Comp<3>>();
if(i == 5000000L) { registry.assign<Position>(entity); }
const auto entity = registry.create();
registry.assign<Velocity>(entity);
registry.assign<Comp<1>>(entity);
registry.assign<Comp<2>>(entity);
registry.assign<Comp<3>>(entity);
if(i == 5000000L) {
registry.assign<Position>(entity);
}
}
Timer timer;
registry.view<Position, Velocity, Comp<1>, Comp<2>, Comp<3>>().each([](auto, auto &...) {});
timer.elapsed();
auto test = [&registry](auto func) {
Timer timer;
registry.view<Position, Velocity, Comp<1>, Comp<2>, Comp<3>>().each(func);
timer.elapsed();
};
test([](auto, const auto &...) {});
test([](auto, auto &... comp) {
using accumulator_type = int[];
accumulator_type accumulator = { (comp.x = {}, 0)... };
(void)accumulator;
});
}
TEST(Benchmark, IterateFiveComponentsPersistent1M) {
@@ -229,12 +339,26 @@ TEST(Benchmark, IterateFiveComponentsPersistent1M) {
std::cout << "Iterating over 1000000 entities, five components, persistent view" << std::endl;
for(std::uint64_t i = 0; i < 1000000L; i++) {
registry.create<Position, Velocity, Comp<1>, Comp<2>, Comp<3>>();
const auto entity = registry.create();
registry.assign<Position>(entity);
registry.assign<Velocity>(entity);
registry.assign<Comp<1>>(entity);
registry.assign<Comp<2>>(entity);
registry.assign<Comp<3>>(entity);
}
Timer timer;
registry.persistent<Position, Velocity, Comp<1>, Comp<2>, Comp<3>>().each([](auto, auto &...) {});
timer.elapsed();
auto test = [&registry](auto func) {
Timer timer;
registry.view<Position, Velocity, Comp<1>, Comp<2>, Comp<3>>(entt::persistent_t{}).each(func);
timer.elapsed();
};
test([](auto, const auto &...) {});
test([](auto, auto &... comp) {
using accumulator_type = int[];
accumulator_type accumulator = { (comp.x = {}, 0)... };
(void)accumulator;
});
}
TEST(Benchmark, IterateTenComponents1M) {
@@ -243,12 +367,31 @@ TEST(Benchmark, IterateTenComponents1M) {
std::cout << "Iterating over 1000000 entities, ten components" << std::endl;
for(std::uint64_t i = 0; i < 1000000L; i++) {
registry.create<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
const auto entity = registry.create();
registry.assign<Position>(entity);
registry.assign<Velocity>(entity);
registry.assign<Comp<1>>(entity);
registry.assign<Comp<2>>(entity);
registry.assign<Comp<3>>(entity);
registry.assign<Comp<4>>(entity);
registry.assign<Comp<5>>(entity);
registry.assign<Comp<6>>(entity);
registry.assign<Comp<7>>(entity);
registry.assign<Comp<8>>(entity);
}
Timer timer;
registry.view<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>().each([](auto, auto &...) {});
timer.elapsed();
auto test = [&registry](auto func) {
Timer timer;
registry.view<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>().each(func);
timer.elapsed();
};
test([](auto, const auto &...) {});
test([](auto, auto &... comp) {
using accumulator_type = int[];
accumulator_type accumulator = { (comp.x = {}, 0)... };
(void)accumulator;
});
}
TEST(Benchmark, IterateTenComponents1MHalf) {
@@ -257,13 +400,34 @@ TEST(Benchmark, IterateTenComponents1MHalf) {
std::cout << "Iterating over 1000000 entities, ten components, half of the entities have all the components" << std::endl;
for(std::uint64_t i = 0; i < 1000000L; i++) {
auto entity = registry.create<Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
if(i % 2) { registry.assign<Position>(entity); }
const auto entity = registry.create();
registry.assign<Velocity>(entity);
registry.assign<Comp<1>>(entity);
registry.assign<Comp<2>>(entity);
registry.assign<Comp<3>>(entity);
registry.assign<Comp<4>>(entity);
registry.assign<Comp<5>>(entity);
registry.assign<Comp<6>>(entity);
registry.assign<Comp<7>>(entity);
registry.assign<Comp<8>>(entity);
if(i % 2) {
registry.assign<Position>(entity);
}
}
Timer timer;
registry.view<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>().each([](auto, auto &...) {});
timer.elapsed();
auto test = [&registry](auto func) {
Timer timer;
registry.view<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>().each(func);
timer.elapsed();
};
test([](auto, auto &...) {});
test([](auto, auto &... comp) {
using accumulator_type = int[];
accumulator_type accumulator = { (comp.x = {}, 0)... };
(void)accumulator;
});
}
TEST(Benchmark, IterateTenComponents1MOne) {
@@ -272,13 +436,34 @@ TEST(Benchmark, IterateTenComponents1MOne) {
std::cout << "Iterating over 1000000 entities, ten components, only one entity has all the components" << std::endl;
for(std::uint64_t i = 0; i < 1000000L; i++) {
auto entity = registry.create<Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
if(i == 5000000L) { registry.assign<Position>(entity); }
const auto entity = registry.create();
registry.assign<Velocity>(entity);
registry.assign<Comp<1>>(entity);
registry.assign<Comp<2>>(entity);
registry.assign<Comp<3>>(entity);
registry.assign<Comp<4>>(entity);
registry.assign<Comp<5>>(entity);
registry.assign<Comp<6>>(entity);
registry.assign<Comp<7>>(entity);
registry.assign<Comp<8>>(entity);
if(i == 5000000L) {
registry.assign<Position>(entity);
}
}
Timer timer;
registry.view<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>().each([](auto, auto &...) {});
timer.elapsed();
auto test = [&registry](auto func) {
Timer timer;
registry.view<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>().each(func);
timer.elapsed();
};
test([](auto, const auto &...) {});
test([](auto, auto &... comp) {
using accumulator_type = int[];
accumulator_type accumulator = { (comp.x = {}, 0)... };
(void)accumulator;
});
}
TEST(Benchmark, IterateTenComponentsPersistent1M) {
@@ -288,12 +473,31 @@ TEST(Benchmark, IterateTenComponentsPersistent1M) {
std::cout << "Iterating over 1000000 entities, ten components, persistent view" << std::endl;
for(std::uint64_t i = 0; i < 1000000L; i++) {
registry.create<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
const auto entity = registry.create();
registry.assign<Position>(entity);
registry.assign<Velocity>(entity);
registry.assign<Comp<1>>(entity);
registry.assign<Comp<2>>(entity);
registry.assign<Comp<3>>(entity);
registry.assign<Comp<4>>(entity);
registry.assign<Comp<5>>(entity);
registry.assign<Comp<6>>(entity);
registry.assign<Comp<7>>(entity);
registry.assign<Comp<8>>(entity);
}
Timer timer;
registry.persistent<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>().each([](auto, auto &...) {});
timer.elapsed();
auto test = [&registry](auto func) {
Timer timer;
registry.view<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>(entt::persistent_t{}).each(func);
timer.elapsed();
};
test([](auto, const auto &...) {});
test([](auto, auto &... comp) {
using accumulator_type = int[];
accumulator_type accumulator = { (comp.x = {}, 0)... };
(void)accumulator;
});
}
TEST(Benchmark, SortSingle) {
@@ -302,7 +506,8 @@ TEST(Benchmark, SortSingle) {
std::cout << "Sort 150000 entities, one component" << std::endl;
for(std::uint64_t i = 0; i < 150000L; i++) {
registry.create<Position>({ i, i });
const auto entity = registry.create();
registry.assign<Position>(entity, i, i);
}
Timer timer;
@@ -320,7 +525,9 @@ TEST(Benchmark, SortMulti) {
std::cout << "Sort 150000 entities, two components" << std::endl;
for(std::uint64_t i = 0; i < 150000L; i++) {
registry.create<Position, Velocity>({ i, i }, { i, i });
const auto entity = registry.create();
registry.assign<Position>(entity, i, i);
registry.assign<Velocity>(entity, i, i);
}
registry.sort<Position>([](const auto &lhs, const auto &rhs) {
@@ -333,3 +540,63 @@ TEST(Benchmark, SortMulti) {
timer.elapsed();
}
TEST(Benchmark, AlmostSortedStdSort) {
entt::DefaultRegistry registry;
entt::DefaultRegistry::entity_type entities[3];
std::cout << "Sort 150000 entities, almost sorted, std::sort" << std::endl;
for(std::uint64_t i = 0; i < 150000L; i++) {
const auto entity = registry.create();
registry.assign<Position>(entity, i, i);
if(!(i % 50000)) {
entities[i / 50000] = entity;
}
}
for(std::uint64_t i = 0; i < 3; ++i) {
registry.destroy(entities[i]);
const auto entity = registry.create();
registry.assign<Position>(entity, 50000 * i, 50000 * i);
}
Timer timer;
registry.sort<Position>([](const auto &lhs, const auto &rhs) {
return lhs.x > rhs.x && lhs.y > rhs.y;
});
timer.elapsed();
}
TEST(Benchmark, AlmostSortedInsertionSort) {
entt::DefaultRegistry registry;
entt::DefaultRegistry::entity_type entities[3];
std::cout << "Sort 150000 entities, almost sorted, insertion sort" << std::endl;
for(std::uint64_t i = 0; i < 150000L; i++) {
const auto entity = registry.create();
registry.assign<Position>(entity, i, i);
if(!(i % 50000)) {
entities[i / 50000] = entity;
}
}
for(std::uint64_t i = 0; i < 3; ++i) {
registry.destroy(entities[i]);
const auto entity = registry.create();
registry.assign<Position>(entity, 50000 * i, 50000 * i);
}
Timer timer;
registry.sort<Position>([](const auto &lhs, const auto &rhs) {
return lhs.x > rhs.x && lhs.y > rhs.y;
}, entt::InsertionSort{});
timer.elapsed();
}

View File

@@ -0,0 +1,26 @@
#include <array>
#include <gtest/gtest.h>
#include <entt/core/algorithm.hpp>
TEST(Algorithm, StdSort) {
// well, I'm pretty sure it works, it's std::sort!!
std::array<int, 5> arr{{4, 1, 3, 2, 0}};
entt::StdSort sort;
sort(arr.begin(), arr.end());
for(auto i = 0; i < 4; ++i) {
ASSERT_LT(arr[i], arr[i+1]);
}
}
TEST(Algorithm, InsertionSort) {
std::array<int, 5> arr{{4, 1, 3, 2, 0}};
entt::InsertionSort sort;
sort(arr.begin(), arr.end());
for(auto i = 0; i < 4; ++i) {
ASSERT_LT(arr[i], arr[i+1]);
}
}

View File

@@ -2,7 +2,7 @@
#include <gtest/gtest.h>
#include <entt/core/hashed_string.hpp>
constexpr bool ptr(const char *str) {
static constexpr bool ptr(const char *str) {
using hash_type = entt::HashedString::hash_type;
return (static_cast<hash_type>(entt::HashedString{str}) == entt::HashedString{str}
@@ -12,7 +12,7 @@ constexpr bool ptr(const char *str) {
}
template<std::size_t N>
constexpr bool ref(const char (&str)[N]) {
static constexpr bool ref(const char (&str)[N]) {
using hash_type = entt::HashedString::hash_type;
return (static_cast<hash_type>(entt::HashedString{str}) == entt::HashedString{str}

View File

@@ -2,32 +2,32 @@
#include <gtest/gtest.h>
#include <entt/core/ident.hpp>
struct A {};
struct B {};
struct AType {};
struct AnotherType {};
TEST(Identifier, Uniqueness) {
constexpr auto ID = entt::ident<A, B>;
constexpr A a;
constexpr B b;
constexpr auto ID = entt::ident<AType, AnotherType>;
constexpr AType anInstance;
constexpr AnotherType anotherInstance;
ASSERT_NE(ID.get<A>(), ID.get<B>());
ASSERT_EQ(ID.get<A>(), ID.get<decltype(a)>());
ASSERT_NE(ID.get<A>(), ID.get<decltype(b)>());
ASSERT_EQ(ID.get<A>(), ID.get<A>());
ASSERT_EQ(ID.get<B>(), ID.get<B>());
ASSERT_NE(ID.get<AType>(), ID.get<AnotherType>());
ASSERT_EQ(ID.get<AType>(), ID.get<decltype(anInstance)>());
ASSERT_NE(ID.get<AType>(), ID.get<decltype(anotherInstance)>());
ASSERT_EQ(ID.get<AType>(), ID.get<AType>());
ASSERT_EQ(ID.get<AnotherType>(), ID.get<AnotherType>());
// test uses in constant expressions
switch(ID.get<B>()) {
case ID.get<A>():
switch(ID.get<AnotherType>()) {
case ID.get<AType>():
FAIL();
break;
case ID.get<B>():
case ID.get<AnotherType>():
SUCCEED();
}
}
TEST(Identifier, SingleType) {
constexpr auto ID = entt::ident<A>;
constexpr auto ID = entt::ident<AType>;
std::integral_constant<decltype(ID)::identifier_type, ID.get()> ic;
(void)ic;
}

View File

@@ -3,55 +3,74 @@
#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 ActorComponent final {};
struct ActorTag final {};
struct Position final {};
struct Velocity final {};
TEST(Actor, Functionalities) {
TEST(Actor, Component) {
entt::DefaultRegistry registry;
TestActor *actor = new TestActor{registry};
const auto &cactor = *actor;
entt::DefaultActor actor{registry};
const auto &cactor = actor;
ASSERT_EQ(&registry, &actor->registry());
ASSERT_EQ(&registry, &actor.registry());
ASSERT_EQ(&registry, &cactor.registry());
ASSERT_TRUE(registry.empty<Position>());
ASSERT_TRUE(registry.empty<Velocity>());
ASSERT_TRUE(registry.empty<ActorComponent>());
ASSERT_FALSE(registry.empty());
ASSERT_FALSE(actor->has<Position>());
ASSERT_FALSE(actor->has<Velocity>());
ASSERT_FALSE(actor.has<ActorComponent>());
const auto &position = actor->set<Position>();
const auto &component = actor.assign<ActorComponent>();
ASSERT_EQ(&position, &actor->get<Position>());
ASSERT_EQ(&position, &cactor.get<Position>());
ASSERT_FALSE(registry.empty<Position>());
ASSERT_TRUE(registry.empty<Velocity>());
ASSERT_EQ(&component, &actor.get<ActorComponent>());
ASSERT_EQ(&component, &cactor.get<ActorComponent>());
ASSERT_FALSE(registry.empty<ActorComponent>());
ASSERT_FALSE(registry.empty());
ASSERT_TRUE(actor->has<Position>());
ASSERT_FALSE(actor->has<Velocity>());
ASSERT_TRUE(actor.has<ActorComponent>());
actor->unset<Position>();
actor.remove<ActorComponent>();
ASSERT_TRUE(registry.empty<Position>());
ASSERT_TRUE(registry.empty<Velocity>());
ASSERT_TRUE(registry.empty<ActorComponent>());
ASSERT_FALSE(registry.empty());
ASSERT_FALSE(actor->has<Position>());
ASSERT_FALSE(actor->has<Velocity>());
ASSERT_FALSE(actor.has<ActorComponent>());
}
actor->set<Position>();
actor->set<Velocity>();
TEST(Actor, Tag) {
entt::DefaultRegistry registry;
entt::DefaultActor actor{registry};
const auto &cactor = actor;
ASSERT_EQ(&registry, &actor.registry());
ASSERT_EQ(&registry, &cactor.registry());
ASSERT_FALSE(registry.has<ActorTag>());
ASSERT_FALSE(actor.has<ActorTag>(entt::tag_t{}));
const auto &tag = actor.assign<ActorTag>(entt::tag_t{});
ASSERT_EQ(&tag, &actor.get<ActorTag>(entt::tag_t{}));
ASSERT_EQ(&tag, &cactor.get<ActorTag>(entt::tag_t{}));
ASSERT_TRUE(registry.has<ActorTag>());
ASSERT_FALSE(registry.empty());
ASSERT_FALSE(registry.empty<Position>());
ASSERT_FALSE(registry.empty<Velocity>());
ASSERT_TRUE(actor.has<ActorTag>(entt::tag_t{}));
actor.remove<ActorTag>(entt::tag_t{});
ASSERT_FALSE(registry.has<ActorTag>());
ASSERT_FALSE(registry.empty());
ASSERT_FALSE(actor.has<ActorTag>(entt::tag_t{}));
}
TEST(Actor, EntityLifetime) {
entt::DefaultRegistry registry;
auto *actor = new entt::DefaultActor{registry};
actor->assign<ActorComponent>();
ASSERT_FALSE(registry.empty<ActorComponent>());
ASSERT_FALSE(registry.empty());
registry.each([actor](const auto entity) {
ASSERT_EQ(actor->entity(), entity);
});
delete actor;
ASSERT_TRUE(registry.empty<ActorComponent>());
ASSERT_TRUE(registry.empty());
ASSERT_TRUE(registry.empty<Position>());
ASSERT_TRUE(registry.empty<Velocity>());
}

View File

@@ -0,0 +1,49 @@
#include <gtest/gtest.h>
#include <entt/entity/helper.hpp>
#include <entt/entity/registry.hpp>
TEST(Dependency, Functionalities) {
entt::DefaultRegistry registry;
const auto entity = registry.create();
entt::dependency<double, float>(registry.construction<int>());
ASSERT_FALSE(registry.has<double>(entity));
ASSERT_FALSE(registry.has<float>(entity));
registry.assign<char>(entity);
ASSERT_FALSE(registry.has<double>(entity));
ASSERT_FALSE(registry.has<float>(entity));
registry.assign<int>(entity);
ASSERT_TRUE(registry.has<double>(entity));
ASSERT_TRUE(registry.has<float>(entity));
ASSERT_EQ(registry.get<double>(entity), .0);
ASSERT_EQ(registry.get<float>(entity), .0f);
registry.get<double>(entity) = .3;
registry.get<float>(entity) = .1f;
registry.remove<int>(entity);
registry.assign<int>(entity);
ASSERT_EQ(registry.get<double>(entity), .3);
ASSERT_EQ(registry.get<float>(entity), .1f);
registry.remove<int>(entity);
registry.remove<float>(entity);
registry.assign<int>(entity);
ASSERT_TRUE(registry.has<float>(entity));
ASSERT_EQ(registry.get<double>(entity), .3);
ASSERT_EQ(registry.get<float>(entity), .0f);
registry.remove<int>(entity);
registry.remove<double>(entity);
registry.remove<float>(entity);
entt::dependency<double, float>(entt::break_t{}, registry.construction<int>());
registry.assign<int>(entity);
ASSERT_FALSE(registry.has<double>(entity));
ASSERT_FALSE(registry.has<float>(entity));
}

View File

@@ -0,0 +1,63 @@
#include <gtest/gtest.h>
#include <entt/entity/prototype.hpp>
#include <entt/entity/registry.hpp>
TEST(Prototype, Functionalities) {
entt::DefaultRegistry registry;
entt::DefaultPrototype prototype;
const auto &cprototype = prototype;
ASSERT_FALSE((prototype.has<int, char>()));
ASSERT_TRUE(registry.empty());
ASSERT_EQ(prototype.set<int>(2), 2);
ASSERT_EQ(prototype.set<int>(3), 3);
ASSERT_EQ(prototype.set<char>('c'), 'c');
ASSERT_EQ(prototype.get<int>(), 3);
ASSERT_EQ(cprototype.get<char>(), 'c');
ASSERT_EQ(std::get<0>(prototype.get<int, char>()), 3);
ASSERT_EQ(std::get<1>(cprototype.get<int, char>()), 'c');
const auto e0 = prototype(registry);
ASSERT_TRUE((prototype.has<int, char>()));
ASSERT_FALSE(registry.orphan(e0));
ASSERT_FALSE(registry.empty());
const auto e1 = prototype(registry);
prototype(registry, e0);
ASSERT_FALSE(registry.orphan(e0));
ASSERT_FALSE(registry.orphan(e1));
ASSERT_TRUE((registry.has<int, char>(e0)));
ASSERT_TRUE((registry.has<int, char>(e1)));
registry.remove<int>(e0);
registry.remove<int>(e1);
prototype.unset<int>();
ASSERT_FALSE((prototype.has<int, char>()));
ASSERT_FALSE((prototype.has<int>()));
ASSERT_TRUE((prototype.has<char>()));
prototype(registry, e0);
prototype(registry, e1);
ASSERT_FALSE(registry.has<int>(e0));
ASSERT_FALSE(registry.has<int>(e1));
ASSERT_EQ(registry.get<char>(e0), 'c');
ASSERT_EQ(registry.get<char>(e1), 'c');
registry.get<char>(e0) = '*';
prototype.assign(registry, e0);
ASSERT_EQ(registry.get<char>(e0), '*');
registry.get<char>(e1) = '*';
prototype.accommodate(registry, e1);
ASSERT_EQ(registry.get<char>(e1), 'c');
}

View File

@@ -6,6 +6,55 @@
#include <entt/entity/entt_traits.hpp>
#include <entt/entity/registry.hpp>
struct Listener {
template<typename Component>
void incrComponent(entt::DefaultRegistry &registry, entt::DefaultRegistry::entity_type entity) {
ASSERT_TRUE(registry.valid(entity));
ASSERT_TRUE(registry.has<Component>(entity));
last = entity;
++counter;
}
template<typename Tag>
void incrTag(entt::DefaultRegistry &registry, entt::DefaultRegistry::entity_type entity) {
ASSERT_TRUE(registry.valid(entity));
ASSERT_TRUE(registry.has<Tag>());
ASSERT_EQ(registry.attachee<Tag>(), entity);
last = entity;
++counter;
}
template<typename Component>
void decrComponent(entt::DefaultRegistry &registry, entt::DefaultRegistry::entity_type entity) {
ASSERT_TRUE(registry.valid(entity));
ASSERT_TRUE(registry.has<Component>(entity));
last = entity;
--counter;
}
template<typename Tag>
void decrTag(entt::DefaultRegistry &registry, entt::DefaultRegistry::entity_type entity) {
ASSERT_TRUE(registry.valid(entity));
ASSERT_TRUE(registry.has<Tag>());
ASSERT_EQ(registry.attachee<Tag>(), entity);
last = entity;
--counter;
}
entt::DefaultRegistry::entity_type last;
int counter{0};
};
TEST(DefaultRegistry, Types) {
entt::DefaultRegistry registry;
ASSERT_EQ(registry.type<int>(entt::tag_t{}), registry.type<int>(entt::tag_t{}));
ASSERT_EQ(registry.type<int>(), registry.type<int>());
ASSERT_NE(registry.type<int>(entt::tag_t{}), registry.type<double>(entt::tag_t{}));
ASSERT_NE(registry.type<int>(), registry.type<double>(entt::tag_t{}));
}
TEST(DefaultRegistry, Functionalities) {
entt::DefaultRegistry registry;
@@ -21,8 +70,11 @@ TEST(DefaultRegistry, Functionalities) {
ASSERT_TRUE(registry.empty<int>());
ASSERT_TRUE(registry.empty<char>());
auto e0 = registry.create();
auto e1 = registry.create<int, char>();
const auto e0 = registry.create();
const auto e1 = registry.create();
registry.assign<int>(e1);
registry.assign<char>(e1);
ASSERT_TRUE(registry.has<>(e0));
ASSERT_TRUE(registry.has<>(e1));
@@ -54,7 +106,7 @@ TEST(DefaultRegistry, Functionalities) {
ASSERT_TRUE((registry.has<int, char>(e0)));
ASSERT_FALSE((registry.has<int, char>(e1)));
auto e2 = registry.create();
const auto e2 = registry.create();
registry.accommodate<int>(e2, registry.get<int>(e0));
registry.accommodate<char>(e2, registry.get<char>(e0));
@@ -106,7 +158,10 @@ TEST(DefaultRegistry, Functionalities) {
ASSERT_EQ(registry.size(), entt::DefaultRegistry::size_type{0});
ASSERT_TRUE(registry.empty());
registry.create<int, char>();
const auto e3 = registry.create();
registry.assign<int>(e3);
registry.assign<char>(e3);
ASSERT_EQ(registry.size<int>(), entt::DefaultRegistry::size_type{1});
ASSERT_EQ(registry.size<char>(), entt::DefaultRegistry::size_type{1});
@@ -127,22 +182,44 @@ TEST(DefaultRegistry, Functionalities) {
ASSERT_TRUE(registry.empty<int>());
ASSERT_TRUE(registry.empty<char>());
e0 = registry.create<int>();
e1 = registry.create();
const auto e4 = registry.create();
const auto e5 = registry.create();
ASSERT_NO_THROW(registry.reset<int>(e0));
ASSERT_NO_THROW(registry.reset<int>(e1));
registry.assign<int>(e4);
ASSERT_NO_THROW(registry.reset<int>(e4));
ASSERT_NO_THROW(registry.reset<int>(e5));
ASSERT_EQ(registry.size<int>(), entt::DefaultRegistry::size_type{0});
ASSERT_EQ(registry.size<char>(), entt::DefaultRegistry::size_type{0});
ASSERT_TRUE(registry.empty<int>());
}
TEST(DefaultRegistry, RawData) {
entt::DefaultRegistry registry;
const entt::DefaultRegistry &cregistry = registry;
const auto entity = registry.create();
ASSERT_EQ(registry.raw<int>(), nullptr);
ASSERT_EQ(cregistry.raw<int>(), nullptr);
ASSERT_EQ(cregistry.data<int>(), nullptr);
registry.assign<int>(entity, 42);
ASSERT_NE(registry.raw<int>(), nullptr);
ASSERT_NE(cregistry.raw<int>(), nullptr);
ASSERT_NE(cregistry.data<int>(), nullptr);
ASSERT_EQ(*registry.raw<int>(), 42);
ASSERT_EQ(*cregistry.raw<int>(), 42);
ASSERT_EQ(*cregistry.data<int>(), entity);
}
TEST(DefaultRegistry, CreateDestroyCornerCase) {
entt::DefaultRegistry registry;
auto e0 = registry.create();
auto e1 = registry.create();
const auto e0 = registry.create();
const auto e1 = registry.create();
registry.destroy(e0);
registry.destroy(e1);
@@ -156,7 +233,7 @@ TEST(DefaultRegistry, CreateDestroyCornerCase) {
TEST(DefaultRegistry, VersionOverflow) {
entt::DefaultRegistry registry;
auto entity = registry.create();
const auto entity = registry.create();
registry.destroy(entity);
ASSERT_EQ(registry.version(entity), entt::DefaultRegistry::version_type{});
@@ -175,9 +252,9 @@ TEST(DefaultRegistry, Each) {
entt::DefaultRegistry::size_type match;
registry.create();
registry.create<int>();
registry.assign<int>(registry.create());
registry.create();
registry.create<int>();
registry.assign<int>(registry.create());
registry.create();
tot = 0u;
@@ -226,11 +303,11 @@ TEST(DefaultRegistry, Orphans) {
entt::DefaultRegistry registry;
entt::DefaultRegistry::size_type tot{};
registry.create<int>();
registry.assign<int>(registry.create());
registry.create();
registry.create<int>();
registry.assign<int>(registry.create());
registry.create();
registry.attach<double>(registry.create());
registry.assign<double>(entt::tag_t{}, registry.create());
registry.orphans([&](auto) { ++tot; });
ASSERT_EQ(tot, 2u);
@@ -246,35 +323,27 @@ TEST(DefaultRegistry, Orphans) {
ASSERT_EQ(tot, 0u);
}
TEST(DefaultRegistry, Types) {
entt::DefaultRegistry registry;
ASSERT_EQ(registry.tag<int>(), registry.tag<int>());
ASSERT_EQ(registry.component<int>(), registry.component<int>());
ASSERT_NE(registry.tag<int>(), registry.tag<double>());
ASSERT_NE(registry.component<int>(), registry.component<double>());
}
TEST(DefaultRegistry, CreateDestroyEntities) {
entt::DefaultRegistry registry;
entt::DefaultRegistry::entity_type pre{}, post{};
for(int i = 0; i < 10; ++i) {
registry.create<double>();
const auto entity = registry.create();
registry.assign<double>(entity);
}
registry.reset();
for(int i = 0; i < 7; ++i) {
auto entity = registry.create<int>();
const auto entity = registry.create();
registry.assign<int>(entity);
if(i == 3) { pre = entity; }
}
registry.reset();
for(int i = 0; i < 5; ++i) {
auto entity = registry.create();
const auto entity = registry.create();
if(i == 3) { post = entity; }
}
@@ -285,31 +354,52 @@ TEST(DefaultRegistry, CreateDestroyEntities) {
ASSERT_EQ(registry.current(pre), registry.current(post));
}
TEST(DefaultRegistry, CloneEntities) {
entt::DefaultRegistry registry;
const auto entity = registry.create();
registry.assign<int>(entity, 42);
registry.assign<char>(entity, 'c');
const auto other = registry.clone(entity);
ASSERT_TRUE(registry.has<int>(other));
ASSERT_TRUE(registry.has<char>(other));
ASSERT_EQ(registry.get<int>(entity), registry.get<int>(other));
ASSERT_EQ(registry.get<char>(entity), registry.get<char>(other));
ASSERT_EQ(registry.get<int>(other), 42);
ASSERT_EQ(registry.get<char>(other), 'c');
}
TEST(DefaultRegistry, AttachSetRemoveTags) {
entt::DefaultRegistry registry;
const auto &cregistry = registry;
ASSERT_FALSE(registry.has<int>());
auto entity = registry.create();
registry.attach<int>(entity, 42);
const auto entity = registry.create();
registry.assign<int>(entt::tag_t{}, entity, 42);
ASSERT_TRUE(registry.has<int>());
ASSERT_TRUE(registry.has<int>(entt::tag_t{}, entity));
ASSERT_EQ(registry.get<int>(), 42);
ASSERT_EQ(cregistry.get<int>(), 42);
ASSERT_EQ(registry.attachee<int>(), entity);
registry.set<int>(3);
registry.replace<int>(entt::tag_t{}, 3);
ASSERT_TRUE(registry.has<int>());
ASSERT_TRUE(registry.has<int>(entt::tag_t{}, entity));
ASSERT_EQ(registry.get<int>(), 3);
ASSERT_EQ(cregistry.get<int>(), 3);
ASSERT_EQ(registry.attachee<int>(), entity);
auto other = registry.create();
const auto other = registry.create();
registry.move<int>(other);
ASSERT_TRUE(registry.has<int>());
ASSERT_FALSE(registry.has<int>(entt::tag_t{}, entity));
ASSERT_TRUE(registry.has<int>(entt::tag_t{}, other));
ASSERT_EQ(registry.get<int>(), 3);
ASSERT_EQ(cregistry.get<int>(), 3);
ASSERT_EQ(registry.attachee<int>(), other);
@@ -317,22 +407,33 @@ TEST(DefaultRegistry, AttachSetRemoveTags) {
registry.remove<int>();
ASSERT_FALSE(registry.has<int>());
ASSERT_FALSE(registry.has<int>(entt::tag_t{}, entity));
ASSERT_FALSE(registry.has<int>(entt::tag_t{}, other));
registry.attach<int>(entity, 42);
registry.assign<int>(entt::tag_t{}, entity, 42);
registry.destroy(entity);
ASSERT_FALSE(registry.has<int>());
ASSERT_FALSE(registry.has<int>(entt::tag_t{}, entity));
ASSERT_FALSE(registry.has<int>(entt::tag_t{}, other));
}
TEST(DefaultRegistry, StandardViews) {
TEST(DefaultRegistry, StandardView) {
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');
const auto e0 = registry.create();
registry.assign<int>(e0, 0);
registry.assign<char>(e0, 'c');
const auto e1 = registry.create();
registry.assign<int>(e1, 0);
const auto e2 = registry.create();
registry.assign<int>(e2, 0);
registry.assign<char>(e2, 'c');
ASSERT_EQ(iview.size(), decltype(iview)::size_type{3});
ASSERT_EQ(cview.size(), decltype(cview)::size_type{2});
@@ -343,9 +444,9 @@ TEST(DefaultRegistry, StandardViews) {
ASSERT_EQ(cnt, decltype(mview)::size_type{2});
}
TEST(DefaultRegistry, PersistentViews) {
TEST(DefaultRegistry, PersistentView) {
entt::DefaultRegistry registry;
auto view = registry.persistent<int, char>();
auto view = registry.view<int, char>(entt::persistent_t{});
ASSERT_TRUE((registry.contains<int, char>()));
ASSERT_FALSE((registry.contains<int, double>()));
@@ -358,9 +459,16 @@ TEST(DefaultRegistry, PersistentViews) {
ASSERT_FALSE((registry.contains<int, double>()));
registry.create(0, 'c');
registry.create(0);
registry.create(0, 'c');
const auto e0 = registry.create();
registry.assign<int>(e0, 0);
registry.assign<char>(e0, 'c');
const auto e1 = registry.create();
registry.assign<int>(e1, 0);
const auto e2 = registry.create();
registry.assign<int>(e2, 0);
registry.assign<char>(e2, 'c');
decltype(view)::size_type cnt{0};
view.each([&cnt](auto...) { ++cnt; });
@@ -368,10 +476,28 @@ TEST(DefaultRegistry, PersistentViews) {
ASSERT_EQ(cnt, decltype(view)::size_type{2});
}
TEST(DefaultRegistry, CleanStandardViewsAfterReset) {
TEST(DefaultRegistry, RawView) {
entt::DefaultRegistry registry;
auto view = registry.view<int>(entt::raw_t{});
const auto e0 = registry.create();
registry.assign<int>(e0, 0);
registry.assign<char>(e0, 'c');
const auto e1 = registry.create();
registry.assign<int>(e1, 0);
registry.assign<char>(e1, 'c');
decltype(view)::size_type cnt{0};
view.each([&cnt](auto &...) { ++cnt; });
ASSERT_EQ(cnt, decltype(view)::size_type{2});
}
TEST(DefaultRegistry, CleanStandardViewAfterReset) {
entt::DefaultRegistry registry;
auto view = registry.view<int>();
registry.create(0);
registry.assign<int>(registry.create(), 0);
ASSERT_EQ(view.size(), entt::DefaultRegistry::size_type{1});
@@ -380,10 +506,25 @@ TEST(DefaultRegistry, CleanStandardViewsAfterReset) {
ASSERT_EQ(view.size(), entt::DefaultRegistry::size_type{0});
}
TEST(DefaultRegistry, CleanPersistentViewsAfterReset) {
TEST(DefaultRegistry, CleanPersistentViewAfterReset) {
entt::DefaultRegistry registry;
auto view = registry.persistent<int, char>();
registry.create(0, 'c');
auto view = registry.view<int, char>(entt::persistent_t{});
const auto entity = registry.create();
registry.assign<int>(entity, 0);
registry.assign<char>(entity, 'c');
ASSERT_EQ(view.size(), entt::DefaultRegistry::size_type{1});
registry.reset();
ASSERT_EQ(view.size(), entt::DefaultRegistry::size_type{0});
}
TEST(DefaultRegistry, CleanRawViewAfterReset) {
entt::DefaultRegistry registry;
auto view = registry.view<int>(entt::raw_t{});
registry.assign<int>(registry.create(), 0);
ASSERT_EQ(view.size(), entt::DefaultRegistry::size_type{1});
@@ -394,8 +535,8 @@ TEST(DefaultRegistry, CleanPersistentViewsAfterReset) {
TEST(DefaultRegistry, CleanTagsAfterReset) {
entt::DefaultRegistry registry;
auto entity = registry.create();
registry.attach<int>(entity);
const auto entity = registry.create();
registry.assign<int>(entt::tag_t{}, entity);
ASSERT_TRUE(registry.has<int>());
@@ -409,9 +550,9 @@ TEST(DefaultRegistry, SortSingle) {
int val = 0;
registry.create(val++);
registry.create(val++);
registry.create(val++);
registry.assign<int>(registry.create(), val++);
registry.assign<int>(registry.create(), val++);
registry.assign<int>(registry.create(), val++);
for(auto entity: registry.view<int>()) {
ASSERT_EQ(registry.get<int>(entity), --val);
@@ -430,9 +571,11 @@ TEST(DefaultRegistry, SortMulti) {
unsigned int uval = 0u;
int ival = 0;
registry.create(uval++, ival++);
registry.create(uval++, ival++);
registry.create(uval++, ival++);
for(auto i = 0; i < 3; ++i) {
const auto entity = registry.create();
registry.assign<unsigned int>(entity, uval++);
registry.assign<int>(entity, ival++);
}
for(auto entity: registry.view<unsigned int>()) {
ASSERT_EQ(registry.get<unsigned int>(entity), --uval);
@@ -457,7 +600,7 @@ TEST(DefaultRegistry, SortMulti) {
TEST(DefaultRegistry, ComponentsWithTypesFromStandardTemplateLibrary) {
// see #37 - the test shouldn't crash, that's all
entt::DefaultRegistry registry;
auto entity = registry.create();
const auto entity = registry.create();
registry.assign<std::unordered_set<int>>(entity).insert(42);
registry.destroy(entity);
}
@@ -466,7 +609,7 @@ TEST(DefaultRegistry, ConstructWithComponents) {
// it should compile, that's all
entt::DefaultRegistry registry;
const auto value = 0;
registry.create(value);
registry.assign<int>(registry.create(), value);
}
TEST(DefaultRegistry, MergeTwoRegistries) {
@@ -480,7 +623,9 @@ TEST(DefaultRegistry, MergeTwoRegistries) {
auto merge = [&ref](const auto &view, auto &dst) {
view.each([&](auto entity, const auto &component) {
if(ref.find(entity) == ref.cend()) {
ref.emplace(entity, dst.create(component));
const auto other = dst.create();
dst.template assign<std::decay_t<decltype(component)>>(other, component);
ref.emplace(entity, other);
} else {
using component_type = std::decay_t<decltype(component)>;
dst.template assign<component_type>(ref[entity], component);
@@ -488,11 +633,24 @@ TEST(DefaultRegistry, MergeTwoRegistries) {
});
};
src.create<int, float, double>();
src.create<char, float, int>();
auto e0 = src.create();
src.assign<int>(e0);
src.assign<float>(e0);
src.assign<double>(e0);
dst.create<int, char, double>();
dst.create<float, int>();
auto e1 = src.create();
src.assign<char>(e1);
src.assign<float>(e1);
src.assign<int>(e1);
auto e2 = dst.create();
dst.assign<int>(e2);
dst.assign<char>(e2);
dst.assign<double>(e2);
auto e3 = dst.create();
dst.assign<float>(e3);
dst.assign<int>(e3);
auto eq = [](auto begin, auto end) { ASSERT_EQ(begin, end); };
auto ne = [](auto begin, auto end) { ASSERT_NE(begin, end); };
@@ -508,3 +666,100 @@ TEST(DefaultRegistry, MergeTwoRegistries) {
ne(dst.view<int, float, double>().begin(), dst.view<int, float, double>().end());
ne(dst.view<char, float, int>().begin(), dst.view<char, float, int>().end());
}
TEST(DefaultRegistry, ComponentSignals) {
entt::DefaultRegistry registry;
Listener listener;
registry.construction<int>().connect<Listener, &Listener::incrComponent<int>>(&listener);
registry.destruction<int>().connect<Listener, &Listener::decrComponent<int>>(&listener);
auto e0 = registry.create();
auto e1 = registry.create();
registry.assign<int>(e0);
registry.assign<int>(e1);
ASSERT_EQ(listener.counter, 2);
ASSERT_EQ(listener.last, e1);
registry.remove<int>(e0);
ASSERT_EQ(listener.counter, 1);
ASSERT_EQ(listener.last, e0);
registry.destruction<int>().disconnect<Listener, &Listener::decrComponent<int>>(&listener);
registry.remove<int>(e1);
ASSERT_EQ(listener.counter, 1);
ASSERT_EQ(listener.last, e0);
registry.construction<int>().disconnect<Listener, &Listener::incrComponent<int>>(&listener);
registry.assign<int>(e1);
ASSERT_EQ(listener.counter, 1);
ASSERT_EQ(listener.last, e0);
registry.construction<int>().connect<Listener, &Listener::incrComponent<int>>(&listener);
registry.destruction<int>().connect<Listener, &Listener::decrComponent<int>>(&listener);
registry.assign<int>(e0);
registry.reset<int>(e1);
ASSERT_EQ(listener.counter, 1);
ASSERT_EQ(listener.last, e1);
registry.reset<int>();
ASSERT_EQ(listener.counter, 0);
ASSERT_EQ(listener.last, e0);
registry.assign<int>(e0);
registry.assign<int>(e1);
registry.destroy(e1);
ASSERT_EQ(listener.counter, 1);
ASSERT_EQ(listener.last, e1);
e1 = registry.clone(e0);
ASSERT_EQ(listener.counter, 2);
ASSERT_EQ(listener.last, e1);
}
TEST(DefaultRegistry, TagSignals) {
entt::DefaultRegistry registry;
Listener listener;
registry.construction<int>(entt::tag_t{}).connect<Listener, &Listener::incrTag<int>>(&listener);
registry.destruction<int>(entt::tag_t{}).connect<Listener, &Listener::decrTag<int>>(&listener);
auto e0 = registry.create();
registry.assign<int>(entt::tag_t{}, e0);
ASSERT_EQ(listener.counter, 1);
ASSERT_EQ(listener.last, e0);
auto e1 = registry.create();
registry.move<int>(e1);
registry.remove<int>();
ASSERT_EQ(listener.counter, 0);
ASSERT_EQ(listener.last, e1);
registry.construction<int>(entt::tag_t{}).disconnect<Listener, &Listener::incrTag<int>>(&listener);
registry.destruction<int>(entt::tag_t{}).disconnect<Listener, &Listener::decrTag<int>>(&listener);
registry.assign<int>(entt::tag_t{}, e0);
registry.remove<int>();
ASSERT_EQ(listener.counter, 0);
ASSERT_EQ(listener.last, e1);
registry.construction<int>(entt::tag_t{}).connect<Listener, &Listener::incrTag<int>>(&listener);
registry.destruction<int>(entt::tag_t{}).connect<Listener, &Listener::decrTag<int>>(&listener);
registry.assign<int>(entt::tag_t{}, e0);
registry.destroy(e0);
ASSERT_EQ(listener.counter, 0);
ASSERT_EQ(listener.last, e0);
}

View File

@@ -10,9 +10,11 @@ struct OutputArchive {
: storage{storage}
{}
template<typename Value>
void operator()(const Value &value) {
std::get<std::queue<Value>>(storage).push(value);
template<typename... Value>
void operator()(const Value &... value) {
using accumulator_type = int[];
accumulator_type accumulator = { (std::get<std::queue<Value>>(storage).push(value), 0)... };
(void)accumulator;
}
private:
@@ -25,11 +27,17 @@ struct InputArchive {
: storage{storage}
{}
template<typename Value>
void operator()(Value &value) {
auto &queue = std::get<std::queue<Value>>(storage);
value = queue.front();
queue.pop();
template<typename... Value>
void operator()(Value &... value) {
auto assign = [this](auto &value) {
auto &queue = std::get<std::queue<std::decay_t<decltype(value)>>>(storage);
value = queue.front();
queue.pop();
};
using accumulator_type = int[];
accumulator_type accumulator = { (assign(value), 0)... };
(void)accumulator;
}
private:
@@ -43,7 +51,7 @@ struct AnotherComponent {
int value;
};
struct Foo {
struct WhatAComponent {
entt::DefaultRegistry::entity_type bar;
std::vector<entt::DefaultRegistry::entity_type> quux;
};
@@ -51,22 +59,22 @@ struct Foo {
TEST(Snapshot, Dump) {
entt::DefaultRegistry registry;
auto e0 = registry.create();
const auto e0 = registry.create();
registry.assign<int>(e0, 42);
registry.assign<char>(e0, 'c');
registry.assign<double>(e0, .1);
auto e1 = registry.create();
const auto e1 = registry.create();
auto e2 = registry.create();
const auto e2 = registry.create();
registry.assign<int>(e2, 3);
auto e3 = registry.create();
const auto e3 = registry.create();
registry.assign<char>(e3, '0');
registry.attach<float>(e3, .3f);
registry.assign<float>(entt::tag_t{}, e3, .3f);
auto e4 = registry.create();
registry.attach<AComponent>(e4);
const auto e4 = registry.create();
registry.assign<AComponent>(entt::tag_t{}, e4);
registry.destroy(e1);
auto v1 = registry.current(e1);
@@ -80,7 +88,7 @@ TEST(Snapshot, Dump) {
std::queue<bool>,
std::queue<AComponent>,
std::queue<AnotherComponent>,
std::queue<Foo>
std::queue<WhatAComponent>
>;
storage_type storage;
@@ -140,22 +148,22 @@ TEST(Snapshot, Dump) {
TEST(Snapshot, Partial) {
entt::DefaultRegistry registry;
auto e0 = registry.create();
const auto e0 = registry.create();
registry.assign<int>(e0, 42);
registry.assign<char>(e0, 'c');
registry.assign<double>(e0, .1);
auto e1 = registry.create();
const auto e1 = registry.create();
auto e2 = registry.create();
const auto e2 = registry.create();
registry.assign<int>(e2, 3);
auto e3 = registry.create();
const auto e3 = registry.create();
registry.assign<char>(e3, '0');
registry.attach<float>(e3, .3f);
registry.assign<float>(entt::tag_t{}, e3, .3f);
auto e4 = registry.create();
registry.attach<AComponent>(e4);
const auto e4 = registry.create();
registry.assign<AComponent>(entt::tag_t{}, e4);
registry.destroy(e1);
auto v1 = registry.current(e1);
@@ -168,7 +176,7 @@ TEST(Snapshot, Partial) {
std::queue<float>,
std::queue<bool>,
std::queue<AComponent>,
std::queue<Foo>
std::queue<WhatAComponent>
>;
storage_type storage;
@@ -240,6 +248,41 @@ TEST(Snapshot, Partial) {
ASSERT_FALSE(registry.valid(e4));
}
TEST(Snapshot, Iterator) {
entt::DefaultRegistry registry;
for(auto i = 0; i < 50; ++i) {
const auto entity = registry.create();
registry.assign<AnotherComponent>(entity, i, i);
if(i % 2) {
registry.assign<AComponent>(entity);
}
}
using storage_type = std::tuple<
std::queue<entt::DefaultRegistry::entity_type>,
std::queue<AnotherComponent>
>;
storage_type storage;
OutputArchive<storage_type> output{storage};
InputArchive<storage_type> input{storage};
const auto view = registry.view<AComponent>();
const auto size = view.size();
registry.snapshot().component<AnotherComponent>(output, view.cbegin(), view.cend());
registry.reset();
registry.restore().component<AnotherComponent>(input);
ASSERT_EQ(registry.view<AnotherComponent>().size(), size);
registry.view<AnotherComponent>().each([](const auto entity, auto &&...) {
ASSERT_TRUE(entity % 2);
});
}
TEST(Snapshot, Continuous) {
using entity_type = entt::DefaultRegistry::entity_type;
@@ -255,7 +298,7 @@ TEST(Snapshot, Continuous) {
std::queue<entity_type>,
std::queue<AComponent>,
std::queue<AnotherComponent>,
std::queue<Foo>,
std::queue<WhatAComponent>,
std::queue<double>
>;
@@ -267,9 +310,7 @@ TEST(Snapshot, Continuous) {
src.create();
}
src.each([&src](auto entity) {
src.destroy(entity);
});
src.reset();
for(int i = 0; i < 5; ++i) {
entity = src.create();
@@ -279,14 +320,14 @@ TEST(Snapshot, Continuous) {
src.assign<AnotherComponent>(entity, i, i);
if(i % 2) {
src.assign<Foo>(entity, entity);
src.assign<WhatAComponent>(entity, entity);
} else if(i == 2) {
src.attach<double>(entity, .3);
src.assign<double>(entt::tag_t{}, entity, .3);
}
}
src.view<Foo>().each([&entities](auto, auto &foo) {
foo.quux.insert(foo.quux.begin(), entities.begin(), entities.end());
src.view<WhatAComponent>().each([&entities](auto, auto &whatAComponent) {
whatAComponent.quux.insert(whatAComponent.quux.begin(), entities.begin(), entities.end());
});
entity = dst.create();
@@ -296,19 +337,18 @@ TEST(Snapshot, Continuous) {
src.snapshot()
.entities(output)
.destroyed(output)
.component<AComponent, AnotherComponent, Foo>(output)
.component<AComponent, AnotherComponent, WhatAComponent>(output)
.tag<double>(output);
loader.entities(input)
.destroyed(input)
.component<AComponent, AnotherComponent>(input)
.component<Foo>(input, &Foo::bar, &Foo::quux)
.component<AComponent, AnotherComponent, WhatAComponent>(input, &WhatAComponent::bar, &WhatAComponent::quux)
.tag<double>(input)
.orphans();
decltype(dst.size()) aComponentCnt{};
decltype(dst.size()) anotherComponentCnt{};
decltype(dst.size()) fooCnt{};
decltype(dst.size()) whatAComponentCnt{};
dst.each([&dst, &aComponentCnt](auto entity) {
ASSERT_TRUE(dst.has<AComponent>(entity));
@@ -320,14 +360,14 @@ TEST(Snapshot, Continuous) {
++anotherComponentCnt;
});
dst.view<Foo>().each([&dst, &fooCnt](auto entity, const auto &component) {
dst.view<WhatAComponent>().each([&dst, &whatAComponentCnt](auto entity, const auto &component) {
ASSERT_EQ(entity, component.bar);
for(auto entity: component.quux) {
ASSERT_TRUE(dst.valid(entity));
}
++fooCnt;
++whatAComponentCnt;
});
ASSERT_TRUE(dst.has<double>());
@@ -342,13 +382,12 @@ TEST(Snapshot, Continuous) {
src.snapshot()
.entities(output)
.destroyed(output)
.component<AComponent, AnotherComponent, Foo>(output)
.component<AComponent, WhatAComponent, AnotherComponent>(output)
.tag<double>(output);
loader.entities(input)
.destroyed(input)
.component<AComponent, AnotherComponent>(input)
.component<Foo>(input, &Foo::bar, &Foo::quux)
.component<AComponent, WhatAComponent, AnotherComponent>(input, &WhatAComponent::bar, &WhatAComponent::quux)
.tag<double>(input)
.orphans();
@@ -356,7 +395,7 @@ TEST(Snapshot, Continuous) {
ASSERT_EQ(dst.size<AComponent>(), aComponentCnt);
ASSERT_EQ(dst.size<AnotherComponent>(), anotherComponentCnt);
ASSERT_EQ(dst.size<Foo>(), fooCnt);
ASSERT_EQ(dst.size<WhatAComponent>(), whatAComponentCnt);
ASSERT_TRUE(dst.has<double>());
dst.view<AnotherComponent>().each([](auto, auto &component) {
@@ -365,24 +404,23 @@ TEST(Snapshot, Continuous) {
entity = src.create();
src.view<Foo>().each([entity](auto, auto &component) {
src.view<WhatAComponent>().each([entity](auto, auto &component) {
component.bar = entity;
});
src.snapshot()
.entities(output)
.destroyed(output)
.component<AComponent, AnotherComponent, Foo>(output)
.component<WhatAComponent, AComponent, AnotherComponent>(output)
.tag<double>(output);
loader.entities(input)
.destroyed(input)
.component<AComponent, AnotherComponent>(input)
.component<Foo>(input, &Foo::bar, &Foo::quux)
.component<WhatAComponent, AComponent, AnotherComponent>(input, &WhatAComponent::bar, &WhatAComponent::quux)
.tag<double>(input)
.orphans();
dst.view<Foo>().each([&loader, entity](auto, auto &component) {
dst.view<WhatAComponent>().each([&loader, entity](auto, auto &component) {
ASSERT_EQ(component.bar, loader.map(entity));
});
@@ -397,18 +435,17 @@ TEST(Snapshot, Continuous) {
src.snapshot()
.entities(output)
.destroyed(output)
.component<AComponent, AnotherComponent, Foo>(output)
.component<AComponent, AnotherComponent, WhatAComponent>(output)
.tag<double>(output);
loader.entities(input)
.destroyed(input)
.component<AComponent, AnotherComponent>(input)
.component<Foo>(input, &Foo::bar, &Foo::quux)
.component<AComponent, AnotherComponent, WhatAComponent>(input, &WhatAComponent::bar, &WhatAComponent::quux)
.tag<double>(input)
.orphans()
.shrink();
dst.view<Foo>().each([&dst, &loader, entity](auto, auto &component) {
dst.view<WhatAComponent>().each([&dst](auto, auto &component) {
ASSERT_FALSE(dst.valid(component.bar));
});
@@ -416,7 +453,7 @@ TEST(Snapshot, Continuous) {
entity = src.create();
src.view<Foo>().each([entity](auto, auto &component) {
src.view<WhatAComponent>().each([entity](auto, auto &component) {
component.bar = entity;
});
@@ -426,13 +463,12 @@ TEST(Snapshot, Continuous) {
src.snapshot()
.entities(output)
.destroyed(output)
.component<AComponent, AnotherComponent, Foo>(output)
.component<AComponent, WhatAComponent, AnotherComponent>(output)
.tag<double>(output);
loader.entities(input)
.destroyed(input)
.component<AComponent, AnotherComponent>(input)
.component<Foo>(input, &Foo::bar, &Foo::quux)
.component<AComponent, WhatAComponent, AnotherComponent>(input, &WhatAComponent::bar, &WhatAComponent::quux)
.tag<double>(input)
.orphans();
@@ -446,13 +482,12 @@ TEST(Snapshot, Continuous) {
src.snapshot()
.entities(output)
.destroyed(output)
.component<AComponent, AnotherComponent, Foo>(output)
.component<WhatAComponent, AComponent, AnotherComponent>(output)
.tag<double>(output);
loader.entities(input)
.destroyed(input)
.component<AComponent, AnotherComponent>(input)
.component<Foo>(input, &Foo::bar, &Foo::quux)
.component<WhatAComponent, AComponent, AnotherComponent>(input, &WhatAComponent::bar, &WhatAComponent::quux)
.tag<double>(input)
.orphans();
@@ -487,3 +522,54 @@ TEST(Snapshot, ContinuousMoreOnShrink) {
ASSERT_FALSE(dst.valid(entity));
}
TEST(Snapshot, SyncDataMembers) {
using entity_type = entt::DefaultRegistry::entity_type;
entt::DefaultRegistry src;
entt::DefaultRegistry dst;
entt::ContinuousLoader<entity_type> loader{dst};
using storage_type = std::tuple<
std::queue<entity_type>,
std::queue<WhatAComponent>
>;
storage_type storage;
OutputArchive<storage_type> output{storage};
InputArchive<storage_type> input{storage};
src.create();
src.create();
src.reset();
auto parent = src.create();
auto child = src.create();
src.assign<WhatAComponent>(entt::tag_t{}, child, parent).quux.push_back(parent);
src.assign<WhatAComponent>(child, child).quux.push_back(child);
src.snapshot().entities(output)
.component<WhatAComponent>(output)
.tag<WhatAComponent>(output);
loader.entities(input)
.component<WhatAComponent>(input, &WhatAComponent::bar, &WhatAComponent::quux)
.tag<WhatAComponent>(input, &WhatAComponent::bar, &WhatAComponent::quux);
ASSERT_FALSE(dst.valid(parent));
ASSERT_FALSE(dst.valid(child));
ASSERT_TRUE(dst.has<WhatAComponent>());
ASSERT_EQ(dst.attachee<WhatAComponent>(), loader.map(child));
ASSERT_EQ(dst.get<WhatAComponent>().bar, loader.map(parent));
ASSERT_EQ(dst.get<WhatAComponent>().quux[0], loader.map(parent));
ASSERT_TRUE(dst.has<WhatAComponent>(loader.map(child)));
const auto &component = dst.get<WhatAComponent>(loader.map(child));
ASSERT_EQ(component.bar, loader.map(child));
ASSERT_EQ(component.quux[0], loader.map(child));
}

View File

@@ -4,10 +4,12 @@
TEST(SparseSetNoType, Functionalities) {
entt::SparseSet<unsigned int> set;
const auto &cset = set;
ASSERT_NO_THROW(set.reserve(42));
ASSERT_TRUE(set.empty());
ASSERT_EQ(set.size(), 0u);
ASSERT_EQ(cset.begin(), cset.end());
ASSERT_EQ(set.begin(), set.end());
ASSERT_FALSE(set.has(0));
ASSERT_FALSE(set.has(42));
@@ -18,6 +20,7 @@ TEST(SparseSetNoType, Functionalities) {
ASSERT_FALSE(set.empty());
ASSERT_EQ(set.size(), 1u);
ASSERT_NE(cset.begin(), cset.end());
ASSERT_NE(set.begin(), set.end());
ASSERT_FALSE(set.has(0));
ASSERT_TRUE(set.has(42));
@@ -28,6 +31,7 @@ TEST(SparseSetNoType, Functionalities) {
ASSERT_TRUE(set.empty());
ASSERT_EQ(set.size(), 0u);
ASSERT_EQ(cset.begin(), cset.end());
ASSERT_EQ(set.begin(), set.end());
ASSERT_FALSE(set.has(0));
ASSERT_FALSE(set.has(42));
@@ -40,6 +44,7 @@ TEST(SparseSetNoType, Functionalities) {
ASSERT_TRUE(set.empty());
ASSERT_EQ(set.size(), 0u);
ASSERT_EQ(cset.begin(), cset.end());
ASSERT_EQ(set.begin(), set.end());
ASSERT_FALSE(set.has(0));
ASSERT_FALSE(set.has(42));
@@ -49,6 +54,23 @@ TEST(SparseSetNoType, Functionalities) {
other = std::move(set);
}
TEST(SparseSetNoType, Clone) {
entt::SparseSet<unsigned int> set;
ASSERT_FALSE(set.has(0));
ASSERT_FALSE(set.has(42));
set.construct(0);
ASSERT_TRUE(set.has(0));
ASSERT_FALSE(set.has(42));
set.clone(42, 0);
ASSERT_TRUE(set.has(0));
ASSERT_TRUE(set.has(42));
}
TEST(SparseSetNoType, DataBeginEnd) {
entt::SparseSet<unsigned int> set;
@@ -79,6 +101,15 @@ TEST(SparseSetNoType, DataBeginEnd) {
ASSERT_EQ(*(begin++), 3u);
ASSERT_EQ(begin, end);
auto cbegin = set.cbegin();
auto cend = set.cend();
ASSERT_NE(cbegin, cend);
ASSERT_EQ(cbegin+3, cend);
ASSERT_NE(cbegin, cend);
ASSERT_EQ(cbegin += 3, cend);
ASSERT_EQ(cbegin, cend);
}
TEST(SparseSetNoType, RespectDisjoint) {
@@ -245,19 +276,21 @@ TEST(SparseSetNoType, RespectUnordered) {
TEST(SparseSetWithType, Functionalities) {
entt::SparseSet<unsigned int, int> set;
const auto &cset = set;
ASSERT_NO_THROW(set.reserve(42));
ASSERT_TRUE(set.empty());
ASSERT_EQ(set.size(), 0u);
ASSERT_EQ(cset.begin(), cset.end());
ASSERT_EQ(set.begin(), set.end());
ASSERT_FALSE(set.has(0));
ASSERT_FALSE(set.has(42));
set.construct(42, 3);
ASSERT_EQ(set.get(42), 3);
ASSERT_FALSE(set.empty());
ASSERT_EQ(set.size(), 1u);
ASSERT_NE(cset.begin(), cset.end());
ASSERT_NE(set.begin(), set.end());
ASSERT_FALSE(set.has(0));
ASSERT_TRUE(set.has(42));
@@ -268,6 +301,7 @@ TEST(SparseSetWithType, Functionalities) {
ASSERT_TRUE(set.empty());
ASSERT_EQ(set.size(), 0u);
ASSERT_EQ(cset.begin(), cset.end());
ASSERT_EQ(set.begin(), set.end());
ASSERT_FALSE(set.has(0));
ASSERT_FALSE(set.has(42));
@@ -280,6 +314,7 @@ TEST(SparseSetWithType, Functionalities) {
ASSERT_TRUE(set.empty());
ASSERT_EQ(set.size(), 0u);
ASSERT_EQ(cset.begin(), cset.end());
ASSERT_EQ(set.begin(), set.end());
ASSERT_FALSE(set.has(0));
ASSERT_FALSE(set.has(42));
@@ -289,6 +324,26 @@ TEST(SparseSetWithType, Functionalities) {
other = std::move(set);
}
TEST(SparseSetWithType, Clone) {
entt::SparseSet<unsigned int, int> set;
ASSERT_FALSE(set.has(0));
ASSERT_FALSE(set.has(42));
set.construct(0, 3);
ASSERT_TRUE(set.has(0));
ASSERT_FALSE(set.has(42));
ASSERT_EQ(set.get(0), 3);
set.clone(42, 0);
ASSERT_TRUE(set.has(0));
ASSERT_TRUE(set.has(42));
ASSERT_EQ(set.get(0), set.get(42));
ASSERT_EQ(set.get(42), 3);
}
TEST(SparseSetWithType, AggregatesMustWork) {
struct AggregateType { int value; };
// the goal of this test is to enforce the requirements for aggregate types
@@ -324,6 +379,15 @@ TEST(SparseSetWithType, RawBeginEnd) {
ASSERT_EQ(*(begin++), 6);
ASSERT_EQ(*(begin++), 3);
ASSERT_EQ(begin, end);
auto cbegin = set.cbegin();
auto cend = set.cend();
ASSERT_NE(cbegin, cend);
ASSERT_EQ(cbegin+3, cend);
ASSERT_NE(cbegin, cend);
ASSERT_EQ(cbegin += 3, cend);
ASSERT_EQ(cbegin, cend);
}
TEST(SparseSetWithType, SortOrdered) {
@@ -631,9 +695,9 @@ TEST(SparseSetWithType, RespectUnordered) {
}
TEST(SparseSetWithType, ReferencesGuaranteed) {
struct Type { int value; };
struct InternalType { int value; };
entt::SparseSet<unsigned int, Type> set;
entt::SparseSet<unsigned int, InternalType> set;
set.construct(0, 0);
set.construct(1, 1);

View File

@@ -4,17 +4,22 @@
TEST(View, SingleComponent) {
entt::DefaultRegistry registry;
auto view = registry.view<char>();
auto e0 = registry.create();
auto e1 = registry.create<int, char>();
const auto e0 = registry.create();
const auto e1 = registry.create();
ASSERT_TRUE(view.empty());
registry.assign<int>(e1);
registry.assign<char>(e1);
ASSERT_NO_THROW(registry.view<char>().begin()++);
ASSERT_NO_THROW(++registry.view<char>().begin());
auto view = registry.view<char>();
ASSERT_NE(view.begin(), view.end());
ASSERT_EQ(view.size(), typename decltype(view)::size_type{1});
ASSERT_FALSE(view.empty());
registry.assign<char>(e0);
@@ -38,13 +43,41 @@ TEST(View, SingleComponent) {
registry.remove<char>(e1);
ASSERT_EQ(view.begin(), view.end());
ASSERT_TRUE(view.empty());
}
TEST(View, SingleComponentBeginEnd) {
entt::DefaultRegistry registry;
auto view = registry.view<int>();
const auto &cview = view;
for(auto i = 0; i < 3; ++i) {
registry.assign<int>(registry.create());
}
auto test = [](auto begin, auto end) {
ASSERT_NE(begin, end);
ASSERT_NE(++begin, end);
ASSERT_NE(begin++, end);
ASSERT_EQ(begin+1, end);
ASSERT_NE(begin, end);
ASSERT_EQ((begin += 1), end);
ASSERT_EQ(begin, end);
};
test(view.begin(), view.end());
test(cview.begin(), cview.end());
test(view.cbegin(), view.cend());
}
TEST(View, SingleComponentContains) {
entt::DefaultRegistry registry;
auto e0 = registry.create<int>();
auto e1 = registry.create<int>();
const auto e0 = registry.create();
registry.assign<int>(e0);
const auto e1 = registry.create();
registry.assign<int>(e1);
registry.destroy(e0);
@@ -57,8 +90,12 @@ TEST(View, SingleComponentContains) {
TEST(View, SingleComponentEmpty) {
entt::DefaultRegistry registry;
registry.create<char, double>();
registry.create<char>();
const auto e0 = registry.create();
registry.assign<char>(e0);
registry.assign<double>(e0);
const auto e1 = registry.create();
registry.assign<char>(e1);
auto view = registry.view<int>();
@@ -73,8 +110,8 @@ TEST(View, SingleComponentEmpty) {
TEST(View, SingleComponentEach) {
entt::DefaultRegistry registry;
registry.create<int, char>();
registry.create<int, char>();
registry.assign<int>(registry.create());
registry.assign<int>(registry.create());
auto view = registry.view<int>();
const auto &cview = static_cast<const decltype(view) &>(view);
@@ -91,9 +128,19 @@ TEST(View, SingleComponentEach) {
TEST(View, MultipleComponent) {
entt::DefaultRegistry registry;
auto view = registry.view<int, char>();
auto e0 = registry.create<char>();
auto e1 = registry.create<int, char>();
ASSERT_TRUE(view.empty());
const auto e0 = registry.create();
registry.assign<char>(e0);
const auto e1 = registry.create();
registry.assign<int>(e1);
ASSERT_FALSE(view.empty());
registry.assign<char>(e1);
auto it = registry.view<char>().begin();
@@ -104,8 +151,6 @@ TEST(View, MultipleComponent) {
ASSERT_NO_THROW((registry.view<int, char>().begin()++));
ASSERT_NO_THROW((++registry.view<int, char>().begin()));
auto view = registry.view<int, char>();
ASSERT_NE(view.begin(), view.end());
ASSERT_EQ(view.begin()+1, view.end());
ASSERT_EQ(view.size(), decltype(view.size()){1});
@@ -123,16 +168,44 @@ TEST(View, MultipleComponent) {
registry.remove<char>(e0);
registry.remove<char>(e1);
view.reset();
}
ASSERT_EQ(view.begin(), view.end());
TEST(View, MultipleComponentBeginEnd) {
entt::DefaultRegistry registry;
auto view = registry.view<int, char>();
const auto &cview = view;
for(auto i = 0; i < 3; ++i) {
const auto entity = registry.create();
registry.assign<int>(entity);
registry.assign<char>(entity);
}
auto test = [](auto begin, auto end) {
ASSERT_NE(begin, end);
ASSERT_NE(++begin, end);
ASSERT_NE(begin++, end);
ASSERT_EQ(begin+1, end);
ASSERT_NE(begin, end);
ASSERT_EQ((begin += 1), end);
ASSERT_EQ(begin, end);
};
test(cview.begin(), cview.end());
test(view.begin(), view.end());
test(view.cbegin(), view.cend());
}
TEST(View, MultipleComponentContains) {
entt::DefaultRegistry registry;
auto e0 = registry.create<int, char>();
auto e1 = registry.create<int, char>();
const auto e0 = registry.create();
registry.assign<int>(e0);
registry.assign<char>(e0);
const auto e1 = registry.create();
registry.assign<int>(e1);
registry.assign<char>(e1);
registry.destroy(e0);
@@ -145,8 +218,14 @@ TEST(View, MultipleComponentContains) {
TEST(View, MultipleComponentEmpty) {
entt::DefaultRegistry registry;
registry.create<double, int, float>();
registry.create<char, float>();
const auto e0 = registry.create();
registry.assign<double>(e0);
registry.assign<int>(e0);
registry.assign<float>(e0);
const auto e1 = registry.create();
registry.assign<char>(e1);
registry.assign<float>(e1);
auto view = registry.view<char, int, float>();
@@ -159,8 +238,13 @@ TEST(View, MultipleComponentEmpty) {
TEST(View, MultipleComponentEach) {
entt::DefaultRegistry registry;
registry.create<int, char>();
registry.create<int, char>();
const auto e0 = registry.create();
registry.assign<int>(e0);
registry.assign<char>(e0);
const auto e1 = registry.create();
registry.assign<int>(e1);
registry.assign<char>(e1);
auto view = registry.view<int, char>();
const auto &cview = static_cast<const decltype(view) &>(view);
@@ -175,17 +259,48 @@ TEST(View, MultipleComponentEach) {
ASSERT_EQ(cnt, std::size_t{0});
}
TEST(View, MultipleComponentEachWithHoles) {
entt::DefaultRegistry registry;
const auto e0 = registry.create();
const auto e1 = registry.create();
const auto e2 = registry.create();
registry.assign<char>(e0, '0');
registry.assign<char>(e1, '1');
registry.assign<int>(e0, 0);
registry.assign<int>(e2, 2);
auto view = registry.view<char, int>();
view.each([e0](auto entity, const char &c, const int &i) {
if(e0 == entity) {
ASSERT_EQ(c, '0');
ASSERT_EQ(i, 0);
} else {
FAIL();
}
});
}
TEST(PersistentView, Prepare) {
entt::DefaultRegistry registry;
registry.prepare<int, char>();
auto view = registry.view<int, char>(entt::persistent_t{});
auto e0 = registry.create<char>();
auto e1 = registry.create<int, char>();
ASSERT_TRUE(view.empty());
ASSERT_NO_THROW((registry.persistent<int, char>().begin()++));
ASSERT_NO_THROW((++registry.persistent<int, char>().begin()));
const auto e0 = registry.create();
registry.assign<char>(e0);
auto view = registry.persistent<int, char>();
const auto e1 = registry.create();
registry.assign<int>(e1);
registry.assign<char>(e1);
ASSERT_FALSE(view.empty());
ASSERT_NO_THROW((registry.view<int, char>(entt::persistent_t{}).begin()++));
ASSERT_NO_THROW((++registry.view<int, char>(entt::persistent_t{}).begin()));
ASSERT_NE(view.begin(), view.end());
ASSERT_EQ(view.size(), typename decltype(view)::size_type{1});
@@ -215,18 +330,25 @@ TEST(PersistentView, Prepare) {
registry.remove<char>(e1);
ASSERT_EQ(view.begin(), view.end());
ASSERT_TRUE(view.empty());
}
TEST(PersistentView, NoPrepare) {
entt::DefaultRegistry registry;
auto view = registry.view<int, char>(entt::persistent_t{});
auto e0 = registry.create<char>();
auto e1 = registry.create<int, char>();
ASSERT_TRUE(view.empty());
ASSERT_NO_THROW((registry.persistent<int, char>().begin()++));
ASSERT_NO_THROW((++registry.persistent<int, char>().begin()));
const auto e0 = registry.create();
registry.assign<char>(e0);
auto view = registry.persistent<int, char>();
const auto e1 = registry.create();
registry.assign<int>(e1);
registry.assign<char>(e1);
ASSERT_FALSE(view.empty());
ASSERT_NO_THROW((registry.view<int, char>(entt::persistent_t{}).begin()++));
ASSERT_NO_THROW((++registry.view<int, char>(entt::persistent_t{}).begin()));
ASSERT_NE(view.begin(), view.end());
ASSERT_EQ(view.size(), typename decltype(view)::size_type{1});
@@ -256,17 +378,49 @@ TEST(PersistentView, NoPrepare) {
registry.remove<char>(e1);
ASSERT_EQ(view.begin(), view.end());
ASSERT_TRUE(view.empty());
}
TEST(PersistentView, BeginEnd) {
entt::DefaultRegistry registry;
auto view = registry.view<int, char>(entt::persistent_t{});
const auto &cview = view;
for(auto i = 0; i < 3; ++i) {
const auto entity = registry.create();
registry.assign<int>(entity);
registry.assign<char>(entity);
}
auto test = [](auto begin, auto end) {
ASSERT_NE(begin, end);
ASSERT_NE(++begin, end);
ASSERT_NE(begin++, end);
ASSERT_EQ(begin+1, end);
ASSERT_NE(begin, end);
ASSERT_EQ((begin += 1), end);
ASSERT_EQ(begin, end);
};
test(cview.begin(), cview.end());
test(view.begin(), view.end());
test(view.cbegin(), view.cend());
}
TEST(PersistentView, Contains) {
entt::DefaultRegistry registry;
auto e0 = registry.create<int, char>();
auto e1 = registry.create<int, char>();
const auto e0 = registry.create();
registry.assign<int>(e0);
registry.assign<char>(e0);
const auto e1 = registry.create();
registry.assign<int>(e1);
registry.assign<char>(e1);
registry.destroy(e0);
auto view = registry.persistent<int, char>();
auto view = registry.view<int, char>(entt::persistent_t{});
ASSERT_FALSE(view.contains(e0));
ASSERT_TRUE(view.contains(e1));
@@ -275,15 +429,21 @@ TEST(PersistentView, Contains) {
TEST(PersistentView, Empty) {
entt::DefaultRegistry registry;
registry.create<double, int, float>();
registry.create<char, float>();
const auto e0 = registry.create();
registry.assign<double>(e0);
registry.assign<int>(e0);
registry.assign<float>(e0);
for(auto entity: registry.persistent<char, int, float>()) {
const auto e1 = registry.create();
registry.assign<char>(e1);
registry.assign<float>(e1);
for(auto entity: registry.view<char, int, float>(entt::persistent_t{})) {
(void)entity;
FAIL();
}
for(auto entity: registry.persistent<double, char, int, float>()) {
for(auto entity: registry.view<double, char, int, float>(entt::persistent_t{})) {
(void)entity;
FAIL();
}
@@ -293,10 +453,15 @@ TEST(PersistentView, Each) {
entt::DefaultRegistry registry;
registry.prepare<int, char>();
registry.create<int, char>();
registry.create<int, char>();
const auto e0 = registry.create();
registry.assign<int>(e0);
registry.assign<char>(e0);
auto view = registry.persistent<int, char>();
const auto e1 = registry.create();
registry.assign<int>(e1);
registry.assign<char>(e1);
auto view = registry.view<int, char>(entt::persistent_t{});
const auto &cview = static_cast<const decltype(view) &>(view);
std::size_t cnt = 0;
@@ -313,9 +478,9 @@ TEST(PersistentView, Sort) {
entt::DefaultRegistry registry;
registry.prepare<int, unsigned int>();
auto e0 = registry.create();
auto e1 = registry.create();
auto e2 = registry.create();
const auto e0 = registry.create();
const auto e1 = registry.create();
const auto e2 = registry.create();
auto uval = 0u;
auto ival = 0;
@@ -328,7 +493,7 @@ TEST(PersistentView, Sort) {
registry.assign<int>(e1, ival++);
registry.assign<int>(e2, ival++);
auto view = registry.persistent<int, unsigned int>();
auto view = registry.view<int, unsigned int>(entt::persistent_t{});
for(auto entity: view) {
ASSERT_EQ(view.get<unsigned int>(entity), --uval);
@@ -346,14 +511,19 @@ TEST(PersistentView, Sort) {
TEST(RawView, Functionalities) {
entt::DefaultRegistry registry;
auto view = registry.view<char>(entt::raw_t{});
auto e0 = registry.create();
auto e1 = registry.create<int, char>();
ASSERT_TRUE(view.empty());
ASSERT_NO_THROW(registry.raw<char>().begin()++);
ASSERT_NO_THROW(++registry.raw<char>().begin());
const auto e0 = registry.create();
const auto e1 = registry.create();
auto view = registry.raw<char>();
registry.assign<int>(e1);
registry.assign<char>(e1);
ASSERT_FALSE(view.empty());
ASSERT_NO_THROW(registry.view<char>(entt::raw_t{}).begin()++);
ASSERT_NO_THROW(++registry.view<char>(entt::raw_t{}).begin());
ASSERT_NE(view.begin(), view.end());
ASSERT_EQ(view.size(), typename decltype(view)::size_type{1});
@@ -388,15 +558,44 @@ TEST(RawView, Functionalities) {
registry.remove<char>(e1);
ASSERT_EQ(view.begin(), view.end());
ASSERT_TRUE(view.empty());
}
TEST(RawView, BeginEnd) {
entt::DefaultRegistry registry;
auto view = registry.view<int>(entt::raw_t{});
const auto &cview = view;
for(auto i = 0; i < 3; ++i) {
registry.assign<int>(registry.create());
}
auto test = [](auto begin, auto end) {
ASSERT_NE(begin, end);
ASSERT_NE(++begin, end);
ASSERT_NE(begin++, end);
ASSERT_EQ(begin+1, end);
ASSERT_NE(begin, end);
ASSERT_EQ((begin += 1), end);
ASSERT_EQ(begin, end);
};
test(cview.begin(), cview.end());
test(view.begin(), view.end());
test(view.cbegin(), view.cend());
}
TEST(RawView, Empty) {
entt::DefaultRegistry registry;
registry.create<char, double>();
registry.create<char>();
const auto e0 = registry.create();
registry.assign<char>(e0);
registry.assign<double>(e0);
auto view = registry.raw<int>();
const auto e1 = registry.create();
registry.assign<char>(e1);
auto view = registry.view<int>(entt::raw_t{});
ASSERT_EQ(view.size(), entt::DefaultRegistry::size_type{0});
@@ -405,3 +604,22 @@ TEST(RawView, Empty) {
FAIL();
}
}
TEST(RawView, Each) {
entt::DefaultRegistry registry;
registry.assign<int>(registry.create(), 1);
registry.assign<int>(registry.create(), 3);
auto view = registry.view<int>(entt::raw_t{});
const auto &cview = static_cast<const decltype(view) &>(view);
std::size_t cnt = 0;
view.each([&cnt](int &v) { cnt += (v % 2); });
ASSERT_EQ(cnt, std::size_t{2});
cview.each([&cnt](const int &v) { cnt -= (v % 2); });
ASSERT_EQ(cnt, std::size_t{0});
}

View File

@@ -1,49 +1,49 @@
#include <gtest/gtest.h>
#include <entt/locator/locator.hpp>
struct A {};
struct AService {};
struct B {
struct AnotherService {
virtual void f(bool) = 0;
bool check{false};
};
struct D: B {
D(int): B{} {}
struct DerivedService: AnotherService {
DerivedService(int): AnotherService{} {}
void f(bool b) override { check = b; }
};
TEST(ServiceLocator, Functionalities) {
using entt::ServiceLocator;
ASSERT_TRUE(ServiceLocator<A>::empty());
ASSERT_TRUE(ServiceLocator<B>::empty());
ASSERT_TRUE(ServiceLocator<AService>::empty());
ASSERT_TRUE(ServiceLocator<AnotherService>::empty());
ServiceLocator<A>::set();
ServiceLocator<AService>::set();
ASSERT_FALSE(ServiceLocator<A>::empty());
ASSERT_TRUE(ServiceLocator<B>::empty());
ASSERT_FALSE(ServiceLocator<AService>::empty());
ASSERT_TRUE(ServiceLocator<AnotherService>::empty());
ServiceLocator<A>::reset();
ServiceLocator<AService>::reset();
ASSERT_TRUE(ServiceLocator<A>::empty());
ASSERT_TRUE(ServiceLocator<B>::empty());
ASSERT_TRUE(ServiceLocator<AService>::empty());
ASSERT_TRUE(ServiceLocator<AnotherService>::empty());
ServiceLocator<A>::set(std::make_shared<A>());
ServiceLocator<AService>::set(std::make_shared<AService>());
ASSERT_FALSE(ServiceLocator<A>::empty());
ASSERT_TRUE(ServiceLocator<B>::empty());
ASSERT_FALSE(ServiceLocator<AService>::empty());
ASSERT_TRUE(ServiceLocator<AnotherService>::empty());
ServiceLocator<B>::set<D>(42);
ServiceLocator<AnotherService>::set<DerivedService>(42);
ASSERT_FALSE(ServiceLocator<A>::empty());
ASSERT_FALSE(ServiceLocator<B>::empty());
ASSERT_FALSE(ServiceLocator<AService>::empty());
ASSERT_FALSE(ServiceLocator<AnotherService>::empty());
ServiceLocator<B>::get().lock()->f(!ServiceLocator<B>::get().lock()->check);
ServiceLocator<AnotherService>::get().lock()->f(!ServiceLocator<AnotherService>::get().lock()->check);
ASSERT_TRUE(ServiceLocator<B>::get().lock()->check);
ASSERT_TRUE(ServiceLocator<AnotherService>::get().lock()->check);
ServiceLocator<B>::ref().f(!ServiceLocator<B>::get().lock()->check);
ServiceLocator<AnotherService>::ref().f(!ServiceLocator<AnotherService>::get().lock()->check);
ASSERT_FALSE(ServiceLocator<B>::get().lock()->check);
ASSERT_FALSE(ServiceLocator<AnotherService>::get().lock()->check);
}

View File

@@ -1,141 +0,0 @@
#include <memory>
#include <gtest/gtest.h>
#include <entt/signal/bus.hpp>
struct EventA
{
EventA(int x, int y): value{x+y} {}
int value;
};
struct EventB {};
struct EventC {};
struct MyListener
{
void receive(const EventA &) { A++; }
static void listen(const EventB &) { B++; }
void receive(const EventC &) { C++; }
void reset() { A = 0; B = 0; C = 0; }
int A{0};
static int B;
int C{0};
};
int MyListener::B = 0;
template<typename Bus, typename Listener>
void testRegUnregEmit(Listener listener) {
Bus bus;
listener->reset();
bus.template publish<EventA>(40, 2);
bus.template publish<EventB>();
bus.template publish<EventC>();
ASSERT_EQ(bus.size(), (decltype(bus.size()))0);
ASSERT_TRUE(bus.empty());
ASSERT_EQ(listener->A, 0);
ASSERT_EQ(listener->B, 0);
ASSERT_EQ(listener->C, 0);
bus.reg(listener);
bus.template connect<EventB, &MyListener::listen>();
listener->reset();
bus.template publish<EventA>(40, 2);
bus.template publish<EventB>();
bus.template publish<EventC>();
ASSERT_EQ(bus.size(), (decltype(bus.size()))3);
ASSERT_FALSE(bus.empty());
ASSERT_EQ(listener->A, 1);
ASSERT_EQ(listener->B, 1);
ASSERT_EQ(listener->C, 1);
bus.unreg(listener);
listener->reset();
bus.template publish<EventA>(40, 2);
bus.template publish<EventB>();
bus.template publish<EventC>();
ASSERT_EQ(bus.size(), (decltype(bus.size()))1);
ASSERT_FALSE(bus.empty());
ASSERT_EQ(listener->A, 0);
ASSERT_EQ(listener->B, 1);
ASSERT_EQ(listener->C, 0);
bus.template disconnect<EventB, MyListener::listen>();
listener->reset();
bus.template publish<EventA>(40, 2);
bus.template publish<EventB>();
bus.template publish<EventC>();
ASSERT_EQ(bus.size(), (decltype(bus.size()))0);
ASSERT_TRUE(bus.empty());
ASSERT_EQ(listener->A, 0);
ASSERT_EQ(listener->B, 0);
ASSERT_EQ(listener->C, 0);
}
TEST(ManagedBus, RegUnregEmit) {
using MyManagedBus = entt::ManagedBus<EventA, EventB, EventC>;
testRegUnregEmit<MyManagedBus>(std::make_shared<MyListener>());
}
TEST(ManagedBus, ExpiredListeners) {
entt::ManagedBus<EventA, EventB, EventC> bus;
auto listener = std::make_shared<MyListener>();
listener->reset();
bus.reg(listener);
bus.template publish<EventA>(40, 2);
bus.template publish<EventB>();
ASSERT_EQ(bus.size(), (decltype(bus.size()))2);
ASSERT_FALSE(bus.empty());
ASSERT_EQ(listener->A, 1);
ASSERT_EQ(listener->B, 0);
listener->reset();
listener = nullptr;
ASSERT_EQ(bus.size(), (decltype(bus.size()))2);
ASSERT_FALSE(bus.empty());
EXPECT_NO_THROW(bus.template publish<EventA>(40, 2));
EXPECT_NO_THROW(bus.template publish<EventC>());
ASSERT_EQ(bus.size(), (decltype(bus.size()))0);
ASSERT_TRUE(bus.empty());
}
TEST(UnmanagedBus, RegUnregEmit) {
using MyUnmanagedBus = entt::UnmanagedBus<EventA, EventB, EventC>;
auto ptr = std::make_unique<MyListener>();
testRegUnregEmit<MyUnmanagedBus>(ptr.get());
}
TEST(UnmanagedBus, ExpiredListeners) {
entt::UnmanagedBus<EventA, EventB, EventC> bus;
auto listener = std::make_unique<MyListener>();
listener->reset();
bus.reg(listener.get());
bus.template publish<EventA>(40, 2);
bus.template publish<EventB>();
ASSERT_EQ(bus.size(), (decltype(bus.size()))2);
ASSERT_FALSE(bus.empty());
ASSERT_EQ(listener->A, 1);
ASSERT_EQ(listener->B, 0);
listener->reset();
listener = nullptr;
// dangling pointer inside ... well, unmanaged means unmanaged!! :-)
ASSERT_EQ(bus.size(), (decltype(bus.size()))2);
ASSERT_FALSE(bus.empty());
}

View File

@@ -1,12 +1,12 @@
#include <gtest/gtest.h>
#include <entt/signal/delegate.hpp>
int f(int i) {
int delegateFunction(int i) {
return i*i;
}
struct S {
int f(int i) {
struct DelegateFunctor {
int operator()(int i) {
return i+i;
}
};
@@ -14,13 +14,13 @@ struct S {
TEST(Delegate, Functionalities) {
entt::Delegate<int(int)> ffdel;
entt::Delegate<int(int)> mfdel;
S test;
DelegateFunctor functor;
ASSERT_EQ(ffdel(42), int{});
ASSERT_EQ(mfdel(42), int{});
ffdel.connect<&f>();
mfdel.connect<S, &S::f>(&test);
ffdel.connect<&delegateFunction>();
mfdel.connect<DelegateFunctor, &DelegateFunctor::operator()>(&functor);
ASSERT_EQ(ffdel(3), 9);
ASSERT_EQ(mfdel(3), 6);
@@ -35,7 +35,7 @@ TEST(Delegate, Functionalities) {
TEST(Delegate, Comparison) {
entt::Delegate<int(int)> delegate;
entt::Delegate<int(int)> def;
delegate.connect<&f>();
delegate.connect<&delegateFunction>();
ASSERT_EQ(def, entt::Delegate<int(int)>{});
ASSERT_NE(def, delegate);

View File

@@ -2,46 +2,39 @@
#include <gtest/gtest.h>
#include <entt/signal/dispatcher.hpp>
struct Event {};
struct AnEvent {};
struct AnotherEvent {};
struct Receiver {
void receive(const Event &) { ++cnt; }
void receive(const AnEvent &) { ++cnt; }
void reset() { cnt = 0; }
std::size_t cnt{0};
int cnt{0};
};
template<typename Dispatcher, typename Rec>
void testDispatcher(Rec receiver) {
Dispatcher dispatcher;
TEST(Dispatcher, Functionalities) {
entt::Dispatcher dispatcher;
Receiver receiver;
dispatcher.template connect<Event>(receiver);
dispatcher.template trigger<Event>();
dispatcher.template enqueue<Event>();
dispatcher.template sink<AnEvent>().connect(&receiver);
dispatcher.template trigger<AnEvent>();
dispatcher.template enqueue<AnEvent>();
dispatcher.template enqueue<AnotherEvent>();
dispatcher.update<AnotherEvent>();
ASSERT_EQ(receiver->cnt, static_cast<decltype(receiver->cnt)>(1));
ASSERT_EQ(receiver.cnt, 1);
dispatcher.update<AnEvent>();
dispatcher.template trigger<AnEvent>();
ASSERT_EQ(receiver.cnt, 3);
receiver.reset();
dispatcher.template sink<AnEvent>().disconnect(&receiver);
dispatcher.template trigger<AnEvent>();
dispatcher.template enqueue<AnEvent>();
dispatcher.update();
dispatcher.update();
dispatcher.template trigger<Event>();
dispatcher.template trigger<AnEvent>();
ASSERT_EQ(receiver->cnt, static_cast<decltype(receiver->cnt)>(3));
receiver->reset();
dispatcher.template disconnect<Event>(receiver);
dispatcher.template trigger<Event>();
dispatcher.template enqueue<Event>();
dispatcher.update();
dispatcher.template trigger<Event>();
ASSERT_EQ(receiver->cnt, static_cast<decltype(receiver->cnt)>(0));
}
TEST(ManagedDispatcher, Basics) {
testDispatcher<entt::ManagedDispatcher>(std::make_shared<Receiver>());
}
TEST(UnmanagedDispatcher, Basics) {
auto ptr = std::make_unique<Receiver>();
testDispatcher<entt::UnmanagedDispatcher>(ptr.get());
ASSERT_EQ(receiver.cnt, 0);
}

View File

@@ -3,163 +3,18 @@
#include <gtest/gtest.h>
#include <entt/signal/sigh.hpp>
TEST(SigH, Lifetime) {
using signal = entt::SigH<void(void)>;
ASSERT_NO_THROW(signal{});
signal src{}, other{};
ASSERT_NO_THROW(signal{src});
ASSERT_NO_THROW(signal{std::move(other)});
ASSERT_NO_THROW(src = other);
ASSERT_NO_THROW(src = std::move(other));
ASSERT_NO_THROW(delete new signal{});
}
TEST(SigH, Comparison) {
struct S {
void f() {}
void g() {}
};
entt::SigH<void()> sig1;
entt::SigH<void()> sig2;
S s1;
S s2;
sig1.connect<S, &S::f>(&s1);
sig2.connect<S, &S::f>(&s2);
ASSERT_FALSE(sig1 == sig2);
ASSERT_TRUE(sig1 != sig2);
sig1.disconnect<S, &S::f>(&s1);
sig2.disconnect<S, &S::f>(&s2);
sig1.connect<S, &S::f>(&s1);
sig2.connect<S, &S::g>(&s1);
ASSERT_FALSE(sig1 == sig2);
ASSERT_TRUE(sig1 != sig2);
sig1.disconnect<S, &S::f>(&s1);
sig2.disconnect<S, &S::g>(&s1);
ASSERT_TRUE(sig1 == sig2);
ASSERT_FALSE(sig1 != sig2);
sig1.connect<S, &S::f>(&s1);
sig1.connect<S, &S::g>(&s1);
sig2.connect<S, &S::f>(&s1);
sig2.connect<S, &S::g>(&s1);
ASSERT_TRUE(sig1 == sig2);
sig1.disconnect<S, &S::f>(&s1);
sig1.disconnect<S, &S::g>(&s1);
sig2.disconnect<S, &S::f>(&s1);
sig2.disconnect<S, &S::g>(&s1);
sig1.connect<S, &S::f>(&s1);
sig1.connect<S, &S::g>(&s1);
sig2.connect<S, &S::g>(&s1);
sig2.connect<S, &S::f>(&s1);
ASSERT_FALSE(sig1 == sig2);
}
struct S {
struct SigHListener {
static void f(int &v) { v = 42; }
bool g(int) { k = !k; return true; }
bool h(int) { return k; }
void i() {}
void l() {}
bool k{false};
};
TEST(SigH, Clear) {
entt::SigH<void(int &)> sigh;
sigh.connect<&S::f>();
ASSERT_FALSE(sigh.empty());
sigh.clear();
ASSERT_TRUE(sigh.empty());
}
TEST(SigH, Swap) {
entt::SigH<void(int &)> sigh1;
entt::SigH<void(int &)> sigh2;
sigh1.connect<&S::f>();
ASSERT_FALSE(sigh1.empty());
ASSERT_TRUE(sigh2.empty());
std::swap(sigh1, sigh2);
ASSERT_TRUE(sigh1.empty());
ASSERT_FALSE(sigh2.empty());
}
TEST(SigH, Functions) {
entt::SigH<void(int &)> sigh;
int v = 0;
sigh.connect<&S::f>();
sigh.publish(v);
ASSERT_FALSE(sigh.empty());
ASSERT_EQ((entt::SigH<bool(int)>::size_type)1, sigh.size());
ASSERT_EQ(42, v);
v = 0;
sigh.disconnect<&S::f>();
sigh.publish(v);
ASSERT_TRUE(sigh.empty());
ASSERT_EQ((entt::SigH<bool(int)>::size_type)0, sigh.size());
ASSERT_EQ(0, v);
sigh.connect<&S::f>();
}
TEST(SigH, Members) {
struct S {
bool f(int) { b = !b; return true; }
bool g(int) { return b; }
bool b{false};
};
S s;
S *ptr = &s;
entt::SigH<bool(int)> sigh;
sigh.connect<S, &S::f>(ptr);
sigh.publish(42);
ASSERT_TRUE(s.b);
ASSERT_FALSE(sigh.empty());
ASSERT_EQ((entt::SigH<bool(int)>::size_type)1, sigh.size());
sigh.disconnect<S, &S::f>(ptr);
sigh.publish(42);
ASSERT_TRUE(s.b);
ASSERT_TRUE(sigh.empty());
ASSERT_EQ((entt::SigH<bool(int)>::size_type)0, sigh.size());
sigh.connect<S, &S::f>(ptr);
sigh.connect<S, &S::g>(ptr);
ASSERT_FALSE(sigh.empty());
ASSERT_EQ((entt::SigH<bool(int)>::size_type)2, sigh.size());
sigh.disconnect(ptr);
ASSERT_TRUE(sigh.empty());
ASSERT_EQ((entt::SigH<bool(int)>::size_type)0, sigh.size());
}
template<typename Ret>
struct TestCollectAll {
std::vector<Ret> vec{};
@@ -190,10 +45,152 @@ struct TestCollectFirst {
}
};
TEST(SigH, Lifetime) {
using signal = entt::SigH<void(void)>;
ASSERT_NO_THROW(signal{});
signal src{}, other{};
ASSERT_NO_THROW(signal{src});
ASSERT_NO_THROW(signal{std::move(other)});
ASSERT_NO_THROW(src = other);
ASSERT_NO_THROW(src = std::move(other));
ASSERT_NO_THROW(delete new signal{});
}
TEST(SigH, Comparison) {
entt::SigH<void()> sig1;
entt::SigH<void()> sig2;
SigHListener s1;
SigHListener s2;
sig1.sink().connect<SigHListener, &SigHListener::i>(&s1);
sig2.sink().connect<SigHListener, &SigHListener::i>(&s2);
ASSERT_FALSE(sig1 == sig2);
ASSERT_TRUE(sig1 != sig2);
sig1.sink().disconnect<SigHListener, &SigHListener::i>(&s1);
sig2.sink().disconnect<SigHListener, &SigHListener::i>(&s2);
sig1.sink().connect<SigHListener, &SigHListener::i>(&s1);
sig2.sink().connect<SigHListener, &SigHListener::l>(&s1);
ASSERT_FALSE(sig1 == sig2);
ASSERT_TRUE(sig1 != sig2);
sig1.sink().disconnect<SigHListener, &SigHListener::i>(&s1);
sig2.sink().disconnect<SigHListener, &SigHListener::l>(&s1);
ASSERT_TRUE(sig1 == sig2);
ASSERT_FALSE(sig1 != sig2);
sig1.sink().connect<SigHListener, &SigHListener::i>(&s1);
sig1.sink().connect<SigHListener, &SigHListener::l>(&s1);
sig2.sink().connect<SigHListener, &SigHListener::i>(&s1);
sig2.sink().connect<SigHListener, &SigHListener::l>(&s1);
ASSERT_TRUE(sig1 == sig2);
sig1.sink().disconnect<SigHListener, &SigHListener::i>(&s1);
sig1.sink().disconnect<SigHListener, &SigHListener::l>(&s1);
sig2.sink().disconnect<SigHListener, &SigHListener::i>(&s1);
sig2.sink().disconnect<SigHListener, &SigHListener::l>(&s1);
sig1.sink().connect<SigHListener, &SigHListener::i>(&s1);
sig1.sink().connect<SigHListener, &SigHListener::l>(&s1);
sig2.sink().connect<SigHListener, &SigHListener::l>(&s1);
sig2.sink().connect<SigHListener, &SigHListener::i>(&s1);
ASSERT_FALSE(sig1 == sig2);
}
TEST(SigH, Clear) {
entt::SigH<void(int &)> sigh;
sigh.sink().connect<&SigHListener::f>();
ASSERT_FALSE(sigh.empty());
sigh.sink().disconnect();
ASSERT_TRUE(sigh.empty());
}
TEST(SigH, Swap) {
entt::SigH<void(int &)> sigh1;
entt::SigH<void(int &)> sigh2;
sigh1.sink().connect<&SigHListener::f>();
ASSERT_FALSE(sigh1.empty());
ASSERT_TRUE(sigh2.empty());
std::swap(sigh1, sigh2);
ASSERT_TRUE(sigh1.empty());
ASSERT_FALSE(sigh2.empty());
}
TEST(SigH, Functions) {
entt::SigH<void(int &)> sigh;
int v = 0;
sigh.sink().connect<&SigHListener::f>();
sigh.publish(v);
ASSERT_FALSE(sigh.empty());
ASSERT_EQ((entt::SigH<bool(int)>::size_type)1, sigh.size());
ASSERT_EQ(42, v);
v = 0;
sigh.sink().disconnect<&SigHListener::f>();
sigh.publish(v);
ASSERT_TRUE(sigh.empty());
ASSERT_EQ((entt::SigH<bool(int)>::size_type)0, sigh.size());
ASSERT_EQ(0, v);
sigh.sink().connect<&SigHListener::f>();
}
TEST(SigH, Members) {
SigHListener s;
SigHListener *ptr = &s;
entt::SigH<bool(int)> sigh;
sigh.sink().connect<SigHListener, &SigHListener::g>(ptr);
sigh.publish(42);
ASSERT_TRUE(s.k);
ASSERT_FALSE(sigh.empty());
ASSERT_EQ((entt::SigH<bool(int)>::size_type)1, sigh.size());
sigh.sink().disconnect<SigHListener, &SigHListener::g>(ptr);
sigh.publish(42);
ASSERT_TRUE(s.k);
ASSERT_TRUE(sigh.empty());
ASSERT_EQ((entt::SigH<bool(int)>::size_type)0, sigh.size());
sigh.sink().connect<SigHListener, &SigHListener::g>(ptr);
sigh.sink().connect<SigHListener, &SigHListener::h>(ptr);
ASSERT_FALSE(sigh.empty());
ASSERT_EQ((entt::SigH<bool(int)>::size_type)2, sigh.size());
sigh.sink().disconnect(ptr);
ASSERT_TRUE(sigh.empty());
ASSERT_EQ((entt::SigH<bool(int)>::size_type)0, sigh.size());
}
TEST(SigH, Collector) {
entt::SigH<void(), TestCollectAll<void>> sigh_void;
sigh_void.connect<&TestCollectAll<void>::h>();
sigh_void.sink().connect<&TestCollectAll<void>::h>();
auto collector_void = sigh_void.collect();
ASSERT_FALSE(sigh_void.empty());
@@ -201,9 +198,9 @@ TEST(SigH, Collector) {
entt::SigH<int(), TestCollectAll<int>> sigh_all;
sigh_all.connect<&TestCollectAll<int>::f>();
sigh_all.connect<&TestCollectAll<int>::f>();
sigh_all.connect<&TestCollectAll<int>::g>();
sigh_all.sink().connect<&TestCollectAll<int>::f>();
sigh_all.sink().connect<&TestCollectAll<int>::f>();
sigh_all.sink().connect<&TestCollectAll<int>::g>();
auto collector_all = sigh_all.collect();
ASSERT_FALSE(sigh_all.empty());
@@ -214,8 +211,8 @@ TEST(SigH, Collector) {
entt::SigH<int(), TestCollectFirst<int>> sigh_first;
sigh_first.connect<&TestCollectFirst<int>::f>();
sigh_first.connect<&TestCollectFirst<int>::f>();
sigh_first.sink().connect<&TestCollectFirst<int>::f>();
sigh_first.sink().connect<&TestCollectFirst<int>::f>();
auto collector_first = sigh_first.collect();
ASSERT_FALSE(sigh_first.empty());

View File

@@ -1,179 +0,0 @@
#include <memory>
#include <utility>
#include <gtest/gtest.h>
#include <entt/signal/signal.hpp>
struct S {
static void f(const int &j) { i = j; }
void g(const int &j) { i = j; }
void h(const int &) {}
static int i;
};
int S::i = 0;
TEST(Signal, Lifetime) {
using signal = entt::Signal<void(void)>;
ASSERT_NO_THROW(signal{});
signal src{}, other{};
ASSERT_NO_THROW(signal{src});
ASSERT_NO_THROW(signal{std::move(other)});
ASSERT_NO_THROW(src = other);
ASSERT_NO_THROW(src = std::move(other));
ASSERT_NO_THROW(delete new signal{});
}
TEST(Signal, Comparison) {
struct S {
void f() {}
void g() {}
};
entt::Signal<void()> sig1;
entt::Signal<void()> sig2;
auto s1 = std::make_shared<S>();
auto s2 = std::make_shared<S>();
sig1.connect<S, &S::f>(s1);
sig2.connect<S, &S::f>(s2);
ASSERT_FALSE(sig1 == sig2);
ASSERT_TRUE(sig1 != sig2);
sig1.disconnect<S, &S::f>(s1);
sig2.disconnect<S, &S::f>(s2);
sig1.connect<S, &S::f>(s1);
sig2.connect<S, &S::g>(s1);
ASSERT_FALSE(sig1 == sig2);
ASSERT_TRUE(sig1 != sig2);
sig1.disconnect<S, &S::f>(s1);
sig2.disconnect<S, &S::g>(s1);
ASSERT_TRUE(sig1 == sig2);
ASSERT_FALSE(sig1 != sig2);
sig1.connect<S, &S::f>(s1);
sig1.connect<S, &S::g>(s1);
sig2.connect<S, &S::f>(s1);
sig2.connect<S, &S::g>(s1);
ASSERT_TRUE(sig1 == sig2);
sig1.disconnect<S, &S::f>(s1);
sig1.disconnect<S, &S::g>(s1);
sig2.disconnect<S, &S::f>(s1);
sig2.disconnect<S, &S::g>(s1);
sig1.connect<S, &S::f>(s1);
sig1.connect<S, &S::g>(s1);
sig2.connect<S, &S::g>(s1);
sig2.connect<S, &S::f>(s1);
ASSERT_FALSE(sig1 == sig2);
}
TEST(Signal, Clear) {
entt::Signal<void(const int &)> signal;
signal.connect<&S::f>();
ASSERT_FALSE(signal.empty());
signal.clear();
ASSERT_TRUE(signal.empty());
}
TEST(Signal, Swap) {
entt::Signal<void(const int &)> sig1;
entt::Signal<void(const int &)> sig2;
sig1.connect<&S::f>();
ASSERT_FALSE(sig1.empty());
ASSERT_TRUE(sig2.empty());
std::swap(sig1, sig2);
ASSERT_TRUE(sig1.empty());
ASSERT_FALSE(sig2.empty());
}
TEST(Signal, Functions) {
entt::Signal<void(const int &)> signal;
auto val = S::i + 1;
signal.connect<&S::f>();
signal.publish(val);
ASSERT_FALSE(signal.empty());
ASSERT_EQ(entt::Signal<void(const int &)>::size_type{1}, signal.size());
ASSERT_EQ(S::i, val);
signal.disconnect<&S::f>();
signal.publish(val+1);
ASSERT_TRUE(signal.empty());
ASSERT_EQ(entt::Signal<void(const int &)>::size_type{0}, signal.size());
ASSERT_EQ(S::i, val);
}
TEST(Signal, Members) {
entt::Signal<void(const int &)> signal;
auto ptr = std::make_shared<S>();
auto val = S::i + 1;
signal.connect<S, &S::g>(ptr);
signal.publish(val);
ASSERT_FALSE(signal.empty());
ASSERT_EQ(entt::Signal<void(const int &)>::size_type{1}, signal.size());
ASSERT_EQ(S::i, val);
signal.disconnect<S, &S::g>(ptr);
signal.publish(val+1);
ASSERT_TRUE(signal.empty());
ASSERT_EQ(entt::Signal<void(const int &)>::size_type{0}, signal.size());
ASSERT_EQ(S::i, val);
++val;
signal.connect<S, &S::g>(ptr);
signal.connect<S, &S::h>(ptr);
signal.publish(val);
ASSERT_FALSE(signal.empty());
ASSERT_EQ(entt::Signal<void(const int &)>::size_type{2}, signal.size());
ASSERT_EQ(S::i, val);
signal.disconnect(ptr);
signal.publish(val+1);
ASSERT_TRUE(signal.empty());
ASSERT_EQ(entt::Signal<void(const int &)>::size_type{0}, signal.size());
ASSERT_EQ(S::i, val);
}
TEST(Signal, Cleanup) {
entt::Signal<void(const int &)> signal;
auto ptr = std::make_shared<S>();
signal.connect<S, &S::g>(ptr);
auto val = S::i;
ptr = nullptr;
ASSERT_FALSE(signal.empty());
ASSERT_EQ(S::i, val);
signal.publish(val);
ASSERT_TRUE(signal.empty());
ASSERT_EQ(S::i, val);
}

View File

@@ -135,7 +135,6 @@ duk_ret_t get<DuktapeRuntime>(duk_context *ctx, entt::DefaultRegistry &registry)
duk_push_string(ctx, runtime.components[type].c_str());
duk_json_decode(ctx, -1);
return 1;
}
@@ -157,7 +156,7 @@ class DuktapeRegistry {
template<typename... Comp>
void reg() {
using accumulator_type = int[];
accumulator_type acc = { (func[registry.component<Comp>()] = {
accumulator_type acc = { (func[registry.type<Comp>()] = {
&::set<Comp>,
&::unset<Comp>,
&::has<Comp>,
@@ -186,7 +185,7 @@ class DuktapeRegistry {
auto type = duk_require_uint(ctx, 1);
if(type >= udef) {
type = registry.component<DuktapeRuntime>();
type = registry.type<DuktapeRuntime>();
}
assert(func.find(type) != func.cend());
@@ -248,7 +247,7 @@ public:
assert(func.find(type) != func.cend());
match = (registry.*func[type].test)(entity);
} else {
const auto ctype = registry.component<DuktapeRuntime>();
const auto ctype = registry.type<DuktapeRuntime>();
assert(func.find(ctype) != func.cend());
match = (registry.*func[ctype].test)(entity);
@@ -287,7 +286,7 @@ const duk_function_list_entry js_DuktapeRegistry_methods[] = {
void exportTypes(duk_context *ctx, entt::DefaultRegistry &registry) {
auto exportType = [](auto *ctx, auto &registry, auto idx, auto type, const auto *name) {
duk_push_string(ctx, name);
duk_push_uint(ctx, registry.template component<typename decltype(type)::type>());
duk_push_uint(ctx, registry.template type<typename decltype(type)::type>());
duk_def_prop(ctx, idx, DUK_DEFPROP_HAVE_VALUE | DUK_DEFPROP_CLEAR_WRITABLE);
};
@@ -331,8 +330,12 @@ TEST(Mod, Duktape) {
FAIL();
}
registry.create(Position{ 0., 0. }, Renderable{});
registry.create(Position{ 0., 0. });
const auto e0 = registry.create();
registry.assign<Position>(e0, 0., 0.);
registry.assign<Renderable>(e0);
const auto e1 = registry.create();
registry.assign<Position>(e1, 0., 0.);
const char *s1 = ""
"Registry.entities(Types.POSITION, Types.RENDERABLE).forEach(function(entity) {"

View File

@@ -18,17 +18,17 @@ struct Relationship {
entt::DefaultRegistry::entity_type parent;
};
template<class Archive>
template<typename Archive>
void serialize(Archive &archive, Position &position) {
archive(position.x, position.y);
}
template<class Archive>
template<typename Archive>
void serialize(Archive &archive, Timer &timer) {
archive(timer.duration);
}
template<class Archive>
template<typename Archive>
void serialize(Archive &archive, Relationship &relationship) {
archive(relationship.parent);
}
@@ -128,8 +128,7 @@ TEST(Snapshot, Continuous) {
cereal::JSONInputArchive input{storage};
entt::ContinuousLoader<entt::DefaultRegistry::entity_type> loader{destination};
loader.entities(input)
.component<Position>(input)
.component<Relationship>(input, &Relationship::parent)
.component<Position, Relationship>(input, &Relationship::parent)
.component<Timer>(input);
ASSERT_FALSE(destination.valid(e0));