Compare commits

...

59 Commits

Author SHA1 Message Date
Michele Caini
d417984ff3 review: iterators + fixed bug on raw views 2018-03-30 14:54:14 +02:00
Michele Caini
d38b3e641b added a note about thread safety (see #64) 2018-03-29 23:38:50 +02:00
Michele Caini
28ce491dd5 review sigh + added set/move for tags 2018-03-29 22:30:23 +02:00
Michele Caini
c260d72125 fixed typo 2018-03-28 22:35:30 +02:00
Michele Caini
d1d1b3156d fixed doc for #27 2018-03-28 22:29:01 +02:00
Michele Caini
472064b751 ensure -> assure (#63) 2018-03-28 22:04:16 +02:00
Michele Caini
95ab9a0b70 updated TODO 2018-03-28 17:20:31 +02:00
Michele Caini
4b03f6a039 minor changes 2018-03-28 15:49:02 +02:00
Michele Caini
c3460727fa updated TODO 2018-03-28 10:24:01 +02:00
Michele Caini
2cc1850212 save/restore - see #27 2018-03-28 10:23:47 +02:00
Matteo Galeotti
2d7443acaf Add Dispatcher header to EnTT global include (#59)
Add dispatcher.hpp to entt.hpp
2018-03-22 14:05:55 +01:00
Michele Caini
13d0b0940c raw views 2018-03-15 22:09:43 +01:00
Michele Caini
c101797924 minor changes 2018-03-15 22:03:18 +01:00
Michele Caini
83b55f8e3f fixed benchmark 2018-03-14 14:11:29 +01:00
Michele Caini
b3b6362cd9 minor changes 2018-03-14 14:00:48 +01:00
Nicholas Farshidmehr
fc9af32d5f Fix spelling mistakes. (#57)
Fix spelling mistakes.
2018-03-14 13:47:23 +01:00
Michele Caini
4cd1025011 cleanup 2018-03-14 08:41:31 +01:00
Michele Caini
5233fe8abc updated TODO 2018-03-14 08:39:33 +01:00
Michele Caini
041e31ea78 removed spaces: not satisfied with the current implementation 2018-03-14 08:38:20 +01:00
Michele Caini
7a3e881099 review: benchmark 2018-03-14 08:28:56 +01:00
Michele Caini
631bf42f84 cleanup 2018-03-13 13:55:37 +01:00
Michele Caini
1f704a7019 updated TODO list 2018-03-12 11:28:00 +01:00
Michele Caini
d295c88474 spaces 2018-03-11 23:11:45 +01:00
Michele Caini
1dd9da4dff improved views (extended API + better performance) 2018-03-11 23:07:10 +01:00
Michele Caini
f2eb0c8427 added Registry::fast 2018-03-11 23:05:56 +01:00
Michele Caini
c8ba11faf8 more tests 2018-03-11 23:04:32 +01:00
Michele Caini
a2e243d992 improved multi component standard view 2018-03-07 22:38:21 +01:00
Michele Caini
c588fff5ca minor changes 2018-03-07 17:24:44 +01:00
Michele Caini
87f9599fea minor changes 2018-03-07 08:52:13 +01:00
Michele Caini
0459599b1d added estimated number of entities for multi component standard view 2018-03-07 08:31:03 +01:00
Michele Caini
9447b1a696 fixed 2018-03-06 22:34:46 +01:00
Michele Caini
0ccb7443c2 only the registry should create views 2018-03-06 22:34:38 +01:00
Michele Caini
02cf27091f coding style 2018-03-06 22:22:52 +01:00
Michele Caini
fdfbd04503 review 2018-03-06 13:27:29 +01:00
Michele Caini
866c18200a iterators from sparse sets and views have now operator+/operator+= 2018-03-04 16:45:24 +01:00
Michele Caini
c1cada49d4 review 2018-03-04 16:03:59 +01:00
Michele Caini
7bf550a75f id/version review 2018-03-04 15:32:08 +01:00
Michele Caini
9c540c03aa thread safe family class + minor changes 2018-03-04 15:21:10 +01:00
Michele Caini
b3df46db19 better test 2018-03-02 08:58:09 +01:00
Michele Caini
7ca615a1c1 sfinaed construct in sparse set to favor emplace_back with arguments when possible (#48) 2018-03-02 08:45:59 +01:00
Michele Caini
c83db557a6 more tests + minor changes 2018-03-01 19:24:12 +01:00
Michele Caini
d54594f11d orphans/orphan + minor changes 2018-03-01 19:23:42 +01:00
Michele Caini
434e38608f fixed typo 2018-03-01 19:22:26 +01:00
Michele Caini
871f090ca0 bug fixing 2018-03-01 16:18:23 +01:00
Michele Caini
d1d235e025 Fixed #46 (#47)
Use libc++ if possible
2018-02-28 19:02:12 +01:00
Michele Caini
e822a5fd53 more tests 2018-02-27 23:42:19 +01:00
Michele Caini
7b82a4ae50 updated TODO 2018-02-27 23:42:05 +01:00
Michele Caini
c532e9f2eb updated TODO list 2018-02-25 22:53:57 +01:00
Michele Caini
3fd034816e more tests on sparse set 2018-02-22 23:23:46 +01:00
Michele Caini
bb4b868c79 improvement: standard multi component view 2018-02-22 22:46:42 +01:00
Michele Caini
3b3da11a36 cleanup 2018-02-22 13:13:02 +01:00
Nick Lange
f2cbb5306b Fix MSVC 2017 warning C4458 (#43)
Fix MSVC 2017 warning C4458.
2018-02-22 13:08:36 +01:00
Michele Caini
94ede1b324 updated TODO 2018-02-21 22:24:23 +01:00
Michele Caini
0367248338 Documentation (#42)
Everything is finally documented.
2018-02-21 22:16:02 +01:00
Michele Caini
936db30e58 Fewer allocations, faster destroy (#41)
Overall improvement of the registry.
2018-02-20 10:10:13 +01:00
Michele Caini
4822f0dd11 fixed 2018-02-18 23:19:26 +01:00
Michele Caini
456d220829 updated TODO 2018-02-15 09:27:39 +01:00
Michele Caini
b459ba6ea7 TODO list 2018-02-15 08:46:15 +01:00
Michele Caini
a19ef9bd16 slightly improved 2018-02-12 19:24:07 +01:00
35 changed files with 4940 additions and 769 deletions

1
.gitignore vendored
View File

@@ -1,2 +1 @@
*.user
TODO

View File

@@ -16,7 +16,7 @@ endif()
# Project configuration
#
project(entt VERSION 2.4.2)
project(entt VERSION 2.5.0)
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Debug)
@@ -37,13 +37,28 @@ message("*")
#
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
if(NOT MSVC)
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined")
include(CheckCXXSourceCompiles)
set(OLD_CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS})
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")
check_cxx_source_compiles("
#include<type_traits>
int main() { return std::is_same<int, int>::value ? 0 : 1; }
" HAS_LIBCPP)
if(NOT HAS_LIBCPP)
set(CMAKE_CXX_FLAGS "${OLD_CMAKE_CXX_FLAGS}")
endif()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pedantic -Wall")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -DRELEASE")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0 -g -DDEBUG")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined")
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
# it seems that -O3 ruins the performance when using clang ...
@@ -72,6 +87,7 @@ if(BUILD_TESTING)
option(BUILD_BENCHMARK "Build benchmark." OFF)
option(BUILD_MOD "Build mod example." OFF)
option(BUILD_SNAPSHOT "Build snapshot example." OFF)
# gtest, gtest_main, gmock and gmock_main targets are available from now on
set(GOOGLETEST_DEPS_DIR ${entt_SOURCE_DIR}/deps/googletest)
@@ -106,5 +122,6 @@ add_custom_target(
AUTHORS
LICENSE
README.md
TODO
.travis.yml
)

1881
README.md

File diff suppressed because it is too large Load Diff

9
TODO Normal file
View File

@@ -0,0 +1,9 @@
* 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
* AOB

19
cmake/in/cereal.in Normal file
View File

@@ -0,0 +1,19 @@
project(cereal-download NONE)
cmake_minimum_required(VERSION 3.2)
include(ExternalProject)
ExternalProject_Add(
cereal
GIT_REPOSITORY https://github.com/USCiLab/cereal.git
GIT_TAG v1.2.2
DOWNLOAD_DIR ${CEREAL_DEPS_DIR}
TMP_DIR ${CEREAL_DEPS_DIR}/tmp
STAMP_DIR ${CEREAL_DEPS_DIR}/stamp
SOURCE_DIR ${CEREAL_DEPS_DIR}/src
BINARY_DIR ${CEREAL_DEPS_DIR}/build
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""
TEST_COMMAND ""
)

View File

@@ -4,6 +4,7 @@
#include<type_traits>
#include<cstddef>
#include<atomic>
namespace entt {
@@ -18,14 +19,11 @@ namespace entt {
*/
template<typename...>
class Family {
static std::size_t identifier() noexcept {
static std::size_t value = 0;
return value++;
}
static std::atomic<std::size_t> identifier;
template<typename...>
static std::size_t family() noexcept {
static const std::size_t value = identifier();
static const std::size_t value = identifier.fetch_add(1);
return value;
}
@@ -38,12 +36,16 @@ public:
* @return Statically generated unique identifier for the given type.
*/
template<typename... Type>
static family_type type() noexcept {
inline static family_type type() noexcept {
return family<std::decay_t<Type>...>();
}
};
template<typename... Types>
std::atomic<std::size_t> Family<Types...>::identifier{};
}

View File

@@ -65,8 +65,8 @@ struct Actor {
* @return A reference to the newly created component.
*/
template<typename Component, typename... Args>
Component & set(Args&&... args) {
return reg.template accomodate<Component>(entity, std::forward<Args>(args)...);
Component & set(Args &&... args) {
return reg.template accommodate<Component>(entity, std::forward<Args>(args)...);
}
/**
@@ -145,7 +145,7 @@ private:
* @tparam Delta Type to use to provide elapsed time.
*/
template<typename Delta>
using DefaultActor = Actor<std::uint32_t, Delta>;
using DefaultActor = Actor<DefaultRegistry::entity_type, Delta>;
}

View File

@@ -58,11 +58,11 @@ struct entt_traits<std::uint32_t> {
using version_type = std::uint16_t;
/*! @brief Mask to use to get the entity number out of an identifier. */
static constexpr auto entity_mask = 0xFFFFFF;
static constexpr auto entity_mask = 0xFFFFF;
/*! @brief Mask to use to get the version out of an identifier. */
static constexpr auto version_mask = 0xFF;
static constexpr auto version_mask = 0xFFF;
/*! @brief Extent of the entity number within an identifier. */
static constexpr auto entity_shift = 24;
static constexpr auto entity_shift = 20;
};

View File

@@ -13,6 +13,7 @@
#include <type_traits>
#include "../core/family.hpp"
#include "entt_traits.hpp"
#include "snapshot.hpp"
#include "sparse_set.hpp"
#include "view.hpp"
@@ -57,7 +58,7 @@ class Registry {
using test_fn_type = bool(Registry::*)(Entity) const;
template<typename... Args>
Component & construct(Registry &registry, Entity entity, Args&&... args) {
Component & construct(Registry &registry, Entity entity, Args &&... args) {
auto &component = SparseSet<Entity, Component>::construct(entity, std::forward<Args>(args)...);
for(auto &&listener: listeners) {
@@ -113,7 +114,7 @@ class Registry {
}
template<typename Component>
Pool<Component> & ensure() {
Pool<Component> & assure() {
const auto ctype = component_family::type<Component>();
if(!(ctype < pools.size())) {
@@ -138,7 +139,6 @@ class Registry {
if(!handlers[vtype]) {
using accumulator_type = int[];
auto set = std::make_unique<SparseSet<Entity>>();
for(auto entity: view<Component...>()) {
@@ -146,7 +146,7 @@ class Registry {
}
accumulator_type accumulator = {
(ensure<Component>().append(set.get(), &Registry::has<Component...>), 0)...
(assure<Component>().append(set.get(), &Registry::has<Component...>), 0)...
};
handlers[vtype] = std::move(set);
@@ -230,7 +230,7 @@ public:
* @return Number of entities still in use.
*/
size_type size() const noexcept {
return entities.size() - available.size();
return entities.size() - available;
}
/**
@@ -244,7 +244,7 @@ public:
*/
template<typename Component>
void reserve(size_type cap) {
ensure<Component>().reserve(cap);
assure<Component>().reserve(cap);
}
/**
@@ -257,7 +257,6 @@ public:
*/
void reserve(size_type cap) {
entities.reserve(cap);
available.reserve(cap);
}
/**
@@ -284,19 +283,41 @@ public:
* @return True if at least an entity is still in use, false otherwise.
*/
bool empty() const noexcept {
return entities.size() == available.size();
return entities.size() == available;
}
/**
* @brief Verifies if an entity identifier still refers to a valid entity.
* @brief Checks if an entity identifier refers to a valid entity.
* @param entity An entity identifier, either valid or not.
* @return True if the identifier is still valid, false otherwise.
* @return True if the identifier is valid, false otherwise.
*/
bool valid(entity_type entity) const noexcept {
using promotion_type = std::conditional_t<sizeof(size_type) >= sizeof(entity_type), size_type, entity_type>;
// explicit promotion to avoid warnings with std::uint16_t
const entity_type entt = promotion_type{entity} & traits_type::entity_mask;
return (entt < entities.size() && entities[entt] == entity);
const auto pos = size_type(entity & traits_type::entity_mask);
return (pos < entities.size() && entities[pos] == entity);
}
/**
* @brief Checks if an entity identifier refers to a valid entity.
*
* Alternative version of `valid`. It accesses the internal data structures
* without bounds checking and thus it's both unsafe and risky to use.<br/>
* You should not invoke directly this function unless you know exactly what
* you are doing. Prefer the `valid` member function instead.
*
* @warning
* Attempting to use an entity that doesn't belong to the registry can
* result in undefined behavior.<br/>
* An assertion will abort the execution at runtime in debug mode in case of
* bounds violation.
*
* @param entity A valid entity identifier.
* @return True if the identifier is valid, false otherwise.
*/
bool fast(entity_type entity) const noexcept {
const auto pos = size_type(entity & traits_type::entity_mask);
assert(pos < entities.size());
// the in-use control bit permits to avoid accessing the direct vector
return (entities[pos] == entity);
}
/**
@@ -326,11 +347,9 @@ public:
* @return Actual version for the given entity identifier.
*/
version_type current(entity_type entity) const noexcept {
using promotion_type = std::conditional_t<sizeof(size_type) >= sizeof(entity_type), size_type, entity_type>;
// explicit promotion to avoid warnings with std::uint16_t
const auto entt = promotion_type{entity} & traits_type::entity_mask;
assert(entt < entities.size());
return version_type((entities[entt] >> traits_type::entity_shift) & traits_type::version_mask);
const auto pos = size_type(entity & traits_type::entity_mask);
assert(pos < entities.size());
return version_type((entities[pos] >> traits_type::entity_shift) & traits_type::version_mask);
}
/**
@@ -353,10 +372,10 @@ public:
* @return A valid entity identifier.
*/
template<typename... Component>
entity_type create(Component&&... components) noexcept {
entity_type create(Component &&... components) noexcept {
using accumulator_type = int[];
const auto entity = create();
accumulator_type accumulator = { 0, (ensure<Component>().construct(*this, entity, std::forward<Component>(components)), 0)... };
accumulator_type accumulator = { 0, (assure<std::decay_t<Component>>().construct(*this, entity, std::forward<Component>(components)), 0)... };
(void)accumulator;
return entity;
}
@@ -383,7 +402,7 @@ public:
entity_type create() noexcept {
using accumulator_type = int[];
const auto entity = create();
accumulator_type accumulator = { 0, (ensure<Component>().construct(*this, entity), 0)... };
accumulator_type accumulator = { 0, (assure<Component>().construct(*this, entity), 0)... };
(void)accumulator;
return entity;
}
@@ -408,14 +427,18 @@ public:
entity_type create() noexcept {
entity_type entity;
if(available.empty()) {
if(available) {
const auto entt = next;
const auto version = entities[entt] & (~traits_type::entity_mask);
entity = entt | version;
next = entities[entt] & traits_type::entity_mask;
entities[entt] = entity;
--available;
} else {
entity = entity_type(entities.size());
assert(entity < traits_type::entity_mask);
assert((entity >> traits_type::entity_shift) == entity_type{});
entities.push_back(entity);
} else {
entity = available.back();
available.pop_back();
}
return entity;
@@ -439,11 +462,12 @@ public:
void destroy(entity_type entity) {
assert(valid(entity));
const auto entt = entity & traits_type::entity_mask;
const auto version = version_type{1} + ((entity >> traits_type::entity_shift) & traits_type::version_mask);
const auto next = entt | (version << traits_type::entity_shift);
const auto version = (((entity >> traits_type::entity_shift) + 1) & traits_type::version_mask) << traits_type::entity_shift;
const auto node = (available ? next : ((entt + 1) & traits_type::entity_mask)) | version;
entities[entt] = next;
available.push_back(next);
entities[entt] = node;
next = entt;
++available;
for(auto &&cpool: pools) {
if(cpool && cpool->has(entity)) {
@@ -453,7 +477,7 @@ public:
}
/**
* @brief Attaches a tag to an entity.
* @brief Attaches the given tag to an entity.
*
* Usually, pools of components allocate enough memory to store a bunch of
* elements even if only one of them is used. On the other hand, there are
@@ -474,7 +498,7 @@ public:
* @return A reference to the newly created tag.
*/
template<typename Tag, typename... Args>
Tag & attach(entity_type entity, Args&&... args) {
Tag & attach(entity_type entity, Args &&... args) {
assert(valid(entity));
assert(!has<Tag>());
const auto ttype = tag_family::type<Tag>();
@@ -483,13 +507,13 @@ public:
tags.resize(ttype + 1);
}
tags[ttype].reset(new Attaching<Tag>{entity, { std::forward<Args>(args)... }});
tags[ttype].reset(new Attaching<Tag>{entity, Tag{std::forward<Args>(args)...}});
return static_cast<Attaching<Tag> *>(tags[ttype].get())->tag;
}
/**
* @brief Removes a tag from its owner, if any.
* @brief Removes the given tag from its owner, if any.
* @tparam Tag Type of tag to remove.
*/
template<typename Tag>
@@ -500,7 +524,7 @@ public:
}
/**
* @brief Checks if a tag has an owner.
* @brief Checks if the given tag has an owner.
* @tparam Tag Type of tag for which to perform the check.
* @return True if the tag already has an owner, false otherwise.
*/
@@ -515,7 +539,7 @@ public:
}
/**
* @brief Returns a reference to a tag.
* @brief Returns a reference to the given tag.
*
* @warning
* Attempting to get a tag that hasn't an owner results in undefined
@@ -533,7 +557,7 @@ public:
}
/**
* @brief Returns a reference to a tag.
* @brief Returns a reference to the given tag.
*
* @warning
* Attempting to get a tag that hasn't an owner results in undefined
@@ -550,7 +574,56 @@ public:
}
/**
* @brief Gets the owner of a tag, if any.
* @brief Replaces the given tag.
*
* 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).
*
* @warning
* Attempting to replace a tag that hasn't an owner results in undefined
* behavior.<br/>
* An assertion will abort the execution at runtime in debug mode if the
* tag hasn't been previously attached to an entity.
*
* @tparam Tag Type of tag to replace.
* @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 tag.
*/
template<typename Tag, typename... Args>
Tag & set(Args &&... args) {
return get<Tag>() = Tag{std::forward<Args>(args)...};
}
/**
* @brief Changes the owner of the given tag.
*
* The ownership of the tag is transferred from one entity to another.
*
* @warning
* Attempting to use an invalid entity or to transfer the ownership of a tag
* that hasn't an owner results in undefined behavior.<br/>
* An assertion will abort the execution at runtime in debug mode in case of
* invalid entity or if the tag hasn't been previously attached to an
* entity.
*
* @tparam Tag Type of tag of which to transfer the ownership.
* @param entity A valid entity identifier.
* @return A valid entity identifier.
*/
template<typename Tag>
entity_type move(entity_type entity) {
assert(valid(entity));
assert(has<Tag>());
const auto ttype = tag_family::type<Tag>();
const auto owner = tags[ttype]->entity;
tags[ttype]->entity = entity;
return owner;
}
/**
* @brief Gets the owner of the given tag, if any.
*
* @warning
* Attempting to get the owner of a tag that hasn't been previously attached
@@ -588,9 +661,9 @@ public:
* @return A reference to the newly created component.
*/
template<typename Component, typename... Args>
Component & assign(entity_type entity, Args&&... args) {
Component & assign(entity_type entity, Args &&... args) {
assert(valid(entity));
return ensure<Component>().construct(*this, entity, std::forward<Args>(args)...);
return assure<Component>().construct(*this, entity, std::forward<Args>(args)...);
}
/**
@@ -690,7 +763,7 @@ public:
template<typename... Component>
std::enable_if_t<(sizeof...(Component) > 1), std::tuple<const Component &...>>
get(entity_type entity) const noexcept {
return std::tuple<const Component &...>{ get<Component>(entity)... };
return std::tuple<const Component &...>{get<Component>(entity)...};
}
/**
@@ -710,7 +783,7 @@ public:
template<typename... Component>
std::enable_if_t<(sizeof...(Component) > 1), std::tuple<Component &...>>
get(entity_type entity) noexcept {
return std::tuple<Component &...>{ get<Component>(entity)... };
return std::tuple<Component &...>{get<Component>(entity)...};
}
/**
@@ -734,9 +807,8 @@ public:
* @return A reference to the newly created component.
*/
template<typename Component, typename... Args>
Component & replace(entity_type entity, Args&&... args) {
assert(valid(entity));
return (pool<Component>().get(entity) = Component{std::forward<Args>(args)...});
Component & replace(entity_type entity, Args &&... args) {
return (get<Component>(entity) = Component{std::forward<Args>(args)...});
}
/**
@@ -752,7 +824,7 @@ public:
* }
* @endcode
*
* Prefer this function anyway because it has slighlty better
* Prefer this function anyway because it has slightly better
* performance.
*
* @warning
@@ -767,9 +839,9 @@ public:
* @return A reference to the newly created component.
*/
template<typename Component, typename... Args>
Component & accomodate(entity_type entity, Args&&... args) {
Component & accommodate(entity_type entity, Args &&... args) {
assert(valid(entity));
auto &cpool = ensure<Component>();
auto &cpool = assure<Component>();
return (cpool.has(entity)
? (cpool.get(entity) = Component{std::forward<Args>(args)...})
@@ -779,7 +851,7 @@ public:
/**
* @brief Sorts the pool of entities for the given component.
*
* The order of the elements in a pool is highly affected by assignements
* The order of the elements in a pool is highly affected by assignments
* of components to entities and deletions. Components are arranged to
* maximize the performance during iterations and users should not make any
* assumption on the order.<br/>
@@ -801,13 +873,13 @@ public:
*/
template<typename Component, typename Compare>
void sort(Compare compare) {
ensure<Component>().sort(std::move(compare));
assure<Component>().sort(std::move(compare));
}
/**
* @brief Sorts two pools of components in the same way.
*
* The order of the elements in a pool is highly affected by assignements
* The order of the elements in a pool is highly affected by assignments
* of components to entities and deletions. Components are arranged to
* maximize the performance during iterations and users should not make any
* assumption on the order.
@@ -825,9 +897,9 @@ public:
* the following rules:
*
* * All the entities in `A` that are also in `B` are returned first
* according to the order they have in `B`.
* according to the order they have in `B`.
* * All the entities in `A` that are not in `B` are returned in no
* particular order after all the other entities.
* particular order after all the other entities.
*
* Any subsequent change to `B` won't affect the order in `A`.
*
@@ -836,7 +908,7 @@ public:
*/
template<typename To, typename From>
void sort() {
ensure<To>().respect(ensure<From>());
assure<To>().respect(assure<From>());
}
/**
@@ -879,11 +951,11 @@ public:
if(managed<Component>()) {
auto &cpool = pool<Component>();
for(auto entity: entities) {
each([&cpool](auto entity) {
if(cpool.has(entity)) {
cpool.destroy(entity);
}
}
});
}
}
@@ -891,61 +963,106 @@ public:
* @brief Resets a whole registry.
*
* Destroys all the entities. After a call to `reset`, all the entities
* previously created are recycled with a new version number. In case entity
* still in use are recycled with a new version number. In case entity
* identifers are stored around, the `current` member function can be used
* to know if they are still valid.
*/
void reset() {
available.clear();
for(auto &&entity: entities) {
const auto version = version_type{1} + ((entity >> traits_type::entity_shift) & traits_type::version_mask);
entity = (entity & traits_type::entity_mask) | (version << traits_type::entity_shift);
available.push_back(entity);
}
for(auto &&handler: handlers) {
if(handler) {
handler->reset();
}
}
for(auto &&pool: pools) {
if(pool) {
pool->reset();
}
}
for(auto &&tag: tags) {
tag.reset();
}
each([this](auto entity) {
destroy(entity);
});
}
/**
* @brief Iterate entities and applies them the given function object.
* @brief Iterates all the entities that are still in use.
*
* The function object is invoked for each entity, no matter if it's in use
* or not.<br/>
* The function object is invoked for each entity that is still in use.<br/>
* The signature of the function should be equivalent to the following:
*
* @code{.cpp}
* void(entity_type);
* @endcode
*
* This function is fairly slow and should not be used frequently.<br/>
* Consider using a view if the goal is to iterate entities that have a
* determinate set of components. A view is usually faster than combining
* this function with a bunch of custom tests.
* this function with a bunch of custom tests.<br/>
* On the other side, this function can be used to iterate all the entities
* that are in use, regardless of their components.
*
* @tparam Func Type of the function object to invoke.
* @param func A valid function object.
*/
template<typename Func>
void each(Func func) const {
for(auto pos = entities.size(); pos > size_type{0}; --pos) {
func(entities[pos-1]);
if(available) {
for(auto pos = entities.size(); pos; --pos) {
const entity_type curr = pos - 1;
const auto entity = entities[curr];
const auto entt = entity & traits_type::entity_mask;
if(curr == entt) {
func(entity);
}
}
} else {
for(auto pos = entities.size(); pos; --pos) {
func(entities[pos-1]);
}
}
}
/**
* @brief Checks if an entity is an orphan.
*
* An orphan is an entity that has neither assigned components nor
* tags.
*
* @param entity A valid entity identifier.
* @return True if the entity is an orphan, false otherwise.
*/
bool orphan(entity_type entity) const {
assert(valid(entity));
bool orphan = true;
for(std::size_t i = 0; i < pools.size() && orphan; ++i) {
const auto &pool = pools[i];
orphan = !(pool && pool->has(entity));
}
for(std::size_t i = 0; i < tags.size() && orphan; ++i) {
const auto &tag = tags[i];
orphan = !(tag && (tag->entity == entity));
}
return orphan;
}
/**
* @brief Iterates orphans and applies them the given function object.
*
* The function object is invoked for each entity that is still in use and
* has neither assigned components nor tags.<br/>
* The signature of the function should be equivalent to the following:
*
* @code{.cpp}
* void(entity_type);
* @endcode
*
* This function can be very slow and should not be used frequently.
*
* @tparam Func Type of the function object to invoke.
* @param func A valid function object.
*/
template<typename Func>
void orphans(Func func) const {
each([func = std::move(func), this](auto entity) {
if(orphan(entity)) {
func(entity);
}
});
}
/**
* @brief Returns a standard view for the given components.
*
@@ -957,13 +1074,13 @@ public:
* As a rule of thumb, storing a view should never be an option.
*
* Standard views do their best to iterate the smallest set of candidate
* entites. In particular:
* entities. In particular:
*
* * Single component views are incredibly fast and iterate a packed array
* of entities, all of which has the given component.
* of entities, all of which has the given component.
* * Multi component views look at the number of entities available for each
* component and pick up a reference to the smallest set of candidates to
* test for the given components.
* component and pick up a reference to the smallest set of candidates to
* test for the given components.
*
* @note
* Multi component views are pretty fast. However their performance tend to
@@ -974,13 +1091,14 @@ public:
* @see View
* @see View<Entity, Component>
* @see PersistentView
* @see RawView
*
* @tparam Component Type of components used to construct the view.
* @return A newly created standard view.
*/
template<typename... Component>
View<Entity, Component...> view() {
return View<Entity, Component...>{ensure<Component>()...};
return View<Entity, Component...>{assure<Component>()...};
}
/**
@@ -995,7 +1113,7 @@ public:
* requested.<br/>
* To avoid costly operations, internal data structures for persistent views
* can be prepared with this function. Just use the same set of components
* that would have been used otherwise to contruct the view.
* that would have been used otherwise to construct the view.
*
* @tparam Component Types of components used to prepare the view.
*/
@@ -1054,16 +1172,16 @@ public:
* initialization.<br/>
* As a rule of thumb, storing a view should never be an option.
*
* Persistent views are the right choice to iterate entites when the number
* Persistent views are the right choice to iterate entities when the number
* of components grows up and the most of the entities have all the given
* components.<br/>
* However they have also drawbacks:
*
* * Each kind of persistent view requires a dedicated data structure that
* is allocated within the registry and it increases memory pressure.
* is allocated within the registry and it increases memory pressure.
* * Internal data structures used to construct persistent views must be
* kept updated and it affects slightly construction and destruction of
* entities and components.
* kept updated and it affects slightly construction and destruction of
* entities and components.
*
* That being said, persistent views are an incredibly powerful tool if used
* with care and offer a boost of performance undoubtedly.
@@ -1077,6 +1195,7 @@ public:
* @see View
* @see View<Entity, Component>
* @see PersistentView
* @see RawView
*
* @tparam Component Types of components used to construct the view.
* @return A newly created persistent view.
@@ -1087,12 +1206,112 @@ public:
return PersistentView<Entity, Component...>{handler<Component...>(), pool<Component>()...};
}
/**
* @brief Returns a raw view for the given component.
*
* This kind of views are created on the fly and share with the registry its
* internal data structures.<br/>
* Feel free to discard a view after the use. Creating and destroying a view
* is an incredibly cheap operation because they do not require any type of
* initialization.<br/>
* As a rule of thumb, storing a view should never be an option.
*
* Raw views are incredibly fast and must be considered the best tool to
* iterate components whenever knowing the entities to which they belong
* isn't required.
*
* @see View
* @see View<Entity, Component>
* @see PersistentView
* @see RawView
*
* @tparam Component Type of component used to construct the view.
* @return A newly created raw view.
*/
template<typename Component>
RawView<Entity, Component> raw() {
return RawView<Entity, Component>{assure<Component>()};
}
/**
* @brief Returns a temporary object to use to create snapshots.
*
* A snapshot is either a full or a partial dump of a registry.<br/>
* It can be used to save and restore its internal state or to keep two or
* more instances of this class in sync, as an example in a client-server
* architecture.
*
* @return A not movable and not copyable object to use to take snasphosts.
*/
Snapshot<Entity> snapshot() const {
using follow_fn_type = entity_type(*)(const Registry &, entity_type);
using raw_fn_type = const entity_type *(*)(const Registry &, component_type);
const entity_type seed = available ? (next | (entities[next] & ~traits_type::entity_mask)) : next;
follow_fn_type follow = [](const Registry &registry, entity_type entity) -> entity_type {
const auto &entities = registry.entities;
const auto entt = entity & traits_type::entity_mask;
const auto next = entities[entt] & traits_type::entity_mask;
return (next | (entities[next] & ~traits_type::entity_mask));
};
raw_fn_type raw = [](const Registry &registry, component_type component) -> const entity_type * {
const auto &pools = registry.pools;
return (component < pools.size() && pools[component]) ? pools[component]->data() : nullptr;
};
return { *this, seed, available, follow, raw };
}
/**
* @brief Returns a temporary object to use to load snapshots.
*
* A snapshot is either a full or a partial dump of a registry.<br/>
* It can be used to save and restore its internal state or to keep two or
* more instances of this class in sync, as an example in a client-server
* architecture.
*
* @warning
* The loader returned by this function requires that the registry be empty.
* In case it isn't, all the data will be automatically deleted before to
* return.
*
* @return A not movable and not copyable object to use to load snasphosts.
*/
SnapshotLoader<Entity> restore() {
using assure_fn_type = void(*)(Registry &, entity_type, bool);
assure_fn_type assure = [](Registry &registry, entity_type entity, bool destroyed) {
using promotion_type = std::conditional_t<sizeof(size_type) >= sizeof(entity_type), size_type, entity_type>;
// explicit promotion to avoid warnings with std::uint16_t
const auto entt = promotion_type{entity} & traits_type::entity_mask;
auto &entities = registry.entities;
if(!(entt < entities.size())) {
auto curr = entities.size();
entities.resize(entt + 1);
std::iota(entities.data() + curr, entities.data() + entt, entity_type(curr));
}
entities[entt] = entity;
if(destroyed) {
registry.destroy(entity);
const auto version = (entity & (~traits_type::entity_mask));
entities[entt] = ((entities[entt] & traits_type::entity_mask) | version);
}
};
return { (*this = {}), assure };
}
private:
std::vector<std::unique_ptr<SparseSet<Entity>>> handlers;
std::vector<std::unique_ptr<SparseSet<Entity>>> pools;
std::vector<std::unique_ptr<Attachee>> tags;
std::vector<entity_type> available;
std::vector<entity_type> entities;
size_type available{};
entity_type next{};
};

View File

@@ -0,0 +1,713 @@
#ifndef ENTT_ENTITY_SNAPSHOT_HPP
#define ENTT_ENTITY_SNAPSHOT_HPP
#include <unordered_map>
#include <algorithm>
#include <cstddef>
#include <utility>
#include <cassert>
#include <iterator>
#include <type_traits>
#include "entt_traits.hpp"
namespace entt {
/**
* @brief Forward declaration of the registry class.
*/
template<typename>
class Registry;
/**
* @brief Utility class to create snapshots from a registry.
*
* A _snapshot_ can be either a dump of the entire registry or a narrower
* selection of components and tags of interest.<br/>
* This type can be used in both cases if provided with a correctly configured
* output archive.
*
* @tparam Entity A valid entity type (see entt_traits for more details).
*/
template<typename Entity>
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);
Snapshot(const Registry<Entity> &registry, Entity seed, std::size_t size, follow_fn_type follow, raw_fn_type raw) noexcept
: registry{registry},
seed{seed},
size{size},
follow{follow},
raw{raw}
{}
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);
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));
};
}
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>());
}
}
public:
/**
* @brief Puts aside all the entities that are still in use.
*
* Entities are serialized along with their versions. Destroyed entities are
* not taken in consideration by this function.
*
* @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 Archive>
Snapshot entities(Archive &archive) && {
archive(static_cast<Entity>(registry.size()));
registry.each([&archive, this](auto entity) { archive(entity); });
return *this;
}
/**
* @brief Puts aside destroyed entities.
*
* Entities are serialized along with their versions. Entities that are
* still in use are not taken in consideration by this function.
*
* @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 Archive>
Snapshot destroyed(Archive &archive) && {
archive(static_cast<Entity>(size));
if(size) {
auto curr = seed;
archive(curr);
for(auto i = size - 1; i; --i) {
curr = follow(registry, curr);
archive(curr);
}
}
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.
*
* @tparam Component Types of components 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) && {
using accumulator_type = int[];
accumulator_type accumulator = { 0, (get<Component>(archive, registry), 0)... };
(void)accumulator;
return *this;
}
/**
* @brief Puts aside the given tags.
*
* Each tag is serialized together with the entity to which it belongs.
* Entities are serialized along with their versions.
*
* @tparam Tag Types of tags 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) && {
using accumulator_type = int[];
accumulator_type accumulator = { 0, (get<Tag>(archive), 0)... };
(void)accumulator;
return *this;
}
private:
const Registry<Entity> &registry;
const Entity seed;
const std::size_t size;
follow_fn_type follow;
raw_fn_type raw;
};
/**
* @brief Utility class to restore a snapshot as a whole.
*
* A snapshot loader requires that the destination registry be empty and loads
* all the data at once while keeping intact the identifiers that the entities
* originally had.<br/>
* An example of use is the implementation of a save/restore utility.
*
* @tparam Entity A valid entity type (see entt_traits for more details).
*/
template<typename Entity>
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);
SnapshotLoader(Registry<Entity> &registry, assure_fn_type assure_fn) noexcept
: registry{registry},
assure_fn{assure_fn}
{
// restore a snapshot as a whole requires a clean registry
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) {
Entity length{};
archive(length);
while(length) {
Entity entity{};
archive(entity);
func(entity);
--length;
}
}
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 Tag, typename Archive>
void attach(Archive &archive) {
each(archive, [&archive, this](auto entity) {
static constexpr auto destroyed = false;
assure_fn(registry, entity, destroyed);
archive(registry.template attach<Tag>(entity));
});
}
public:
/**
* @brief Restores entities that were in use during serialization.
*
* This function restores the entities that were in use during serialization
* and gives them the versions they originally had.
*
* @tparam Archive Type of input archive.
* @param archive A valid reference to an input archive.
* @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);
});
return *this;
}
/**
* @brief Restores entities that were destroyed during serialization.
*
* This function restores the entities that were destroyed during
* serialization and gives them the versions they originally had.
*
* @tparam Archive Type of input archive.
* @param archive A valid reference to an input archive.
* @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);
});
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 it with
* the version it originally had.
*
* @tparam Component Types of components to restore.
* @tparam Archive Type of input archive.
* @param archive A valid reference to an input archive.
* @return A valid loader to continue restoring data.
*/
template<typename... Component, typename Archive>
SnapshotLoader component(Archive &archive) && {
using accumulator_type = int[];
accumulator_type accumulator = { 0, (assign<Component>(archive), 0)... };
(void)accumulator;
return *this;
}
/**
* @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 it with the
* version it originally had.
*
* @tparam Tag Types of tags to restore.
* @tparam Archive Type of input archive.
* @param archive A valid reference to an input archive.
* @return A valid loader to continue restoring data.
*/
template<typename... Tag, typename Archive>
SnapshotLoader tag(Archive &archive) && {
using accumulator_type = int[];
accumulator_type accumulator = { 0, (attach<Tag>(archive), 0)... };
(void)accumulator;
return *this;
}
/**
* @brief Destroys those entities that have neither components nor tags.
*
* In case all the entities were serialized but only part of the components
* and tags was saved, it could happen that some of the entities have
* neither components nor tags once restored.<br/>
* This functions helps to identify and destroy those entities.
*
* @return A valid loader to continue restoring data.
*/
SnapshotLoader orphans() && {
registry.orphans([this](auto entity) {
registry.destroy(entity);
});
return *this;
}
private:
Registry<Entity> &registry;
assure_fn_type assure_fn;
};
/**
* @brief Utility class for _continuous loading_.
*
* A _continuous loader_ is designed to load data from a source registry to a
* (possibly) non-empty destination. The loader can accomodate in a registry
* more than one snapshot in a sort of _continuous loading_ that updates the
* destination one step at a time.<br/>
* Identifiers that entities originally had are not transferred to the target.
* Instead, the loader maps remote identifiers to local ones while restoring a
* snapshot.<br/>
* An example of use is the implementation of a client-server applications with
* the requirement of transferring somehow parts of the representation side to
* side.
*
* @tparam Entity A valid entity type (see entt_traits for more details).
*/
template<typename Entity>
class ContinuousLoader final {
using traits_type = entt_traits<Entity>;
Entity destroy(Entity entity) {
const auto it = remloc.find(entity);
if(it == remloc.cend()) {
const auto local = registry.create();
remloc.emplace(entity, std::make_pair(local, true));
registry.destroy(local);
}
return remloc[entity].first;
}
Entity restore(Entity entity) {
const auto it = remloc.find(entity);
if(it == remloc.cend()) {
const auto local = registry.create();
remloc.emplace(entity, std::make_pair(local, true));
} else {
remloc[entity].first =
registry.valid(remloc[entity].first)
? remloc[entity].first
: registry.create();
// 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) {
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)) {
entity = map(entity);
}
}
template<typename Archive, typename Func>
void each(Archive &archive, Func func) {
Entity length{};
archive(length);
while(length) {
Entity entity{};
archive(entity);
func(entity);
--length;
}
}
template<typename Component>
void reset() {
for(auto &&ref: remloc) {
const auto local = ref.second.first;
if(registry.valid(local)) {
registry.template reset<Component>(local);
}
}
}
template<typename Component, typename Archive>
void assign(Archive &archive) {
reset<Component>();
each(archive, [&archive, this](auto entity) {
entity = restore(entity);
archive(registry.template accommodate<Component>(entity));
});
}
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);
using accumulator_type = int[];
accumulator_type accumulator = { 0, (update(component, 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;
});
}
public:
/*! @brief Underlying entity identifier. */
using entity_type = Entity;
/**
* @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
: registry{registry}
{}
/*! @brief Default copy constructor. */
ContinuousLoader(const ContinuousLoader &) = default;
/*! @brief Default move constructor. */
ContinuousLoader(ContinuousLoader &&) = default;
/*! @brief Default copy assignment operator. @return This loader. */
ContinuousLoader & operator=(const ContinuousLoader &) = default;
/*! @brief Default move assignment operator. @return This loader. */
ContinuousLoader & operator=(ContinuousLoader &&) = default;
/**
* @brief Restores entities that were in use during serialization.
*
* This function restores the entities that were in use during serialization
* and creates local counterparts for them if required.
*
* @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 Archive>
ContinuousLoader & entities(Archive &archive) {
each(archive, [this](auto entity) { restore(entity); });
return *this;
}
/**
* @brief Restores entities that were destroyed during serialization.
*
* This function restores the entities that were destroyed during
* serialization and creates local counterparts for them if required.
*
* @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 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;
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.<br/>
* Members can be either data members of type entity_type or containers of
* entities. In both cases, the loader will visit them and update the
* entities by replacing each one with its local counterpart.
*
* @tparam Component Type of component to restore.
* @tparam Archive Type of input archive.
* @tparam Type 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;
}
/**
* @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)... };
(void)accumulator;
return *this;
}
/**
* @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.<br/>
* Members can be either data members of type entity_type or containers of
* entities. In both cases, the loader will visit them and update the
* entities by replacing each one with its local counterpart.
*
* @tparam Tag Type of tag to restore.
* @tparam Archive Type of input archive.
* @tparam Type 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...);
return *this;
}
/**
* @brief Helps to purge entities that no longer have a conterpart.
*
* Users should invoke this member function after restoring each snapshot,
* unless they know exactly what they are doing.
*
* @return A non-const reference to this loader.
*/
ContinuousLoader & shrink() {
auto it = remloc.begin();
while(it != remloc.cend()) {
const auto local = it->second.first;
bool &dirty = it->second.second;
if(dirty) {
dirty = false;
++it;
} else {
if(registry.valid(local)) {
registry.destroy(local);
}
it = remloc.erase(it);
}
}
return *this;
}
/**
* @brief Destroys those entities that have neither components nor tags.
*
* In case all the entities were serialized but only part of the components
* and tags was saved, it could happen that some of the entities have
* neither components nor tags once restored.<br/>
* This functions helps to identify and destroy those entities.
*
* @return A non-const reference to this loader.
*/
ContinuousLoader & orphans() {
registry.orphans([this](auto entity) {
registry.destroy(entity);
});
return *this;
}
/**
* @brief Tests if a loader knows about a given entity.
* @param entity An entity identifier.
* @return True if `entity` is managed by the loader, false otherwise.
*/
bool has(entity_type entity) {
return !(remloc.find(entity) == remloc.cend());
}
/**
* @brief Returns the identifier to which an entity refers.
*
* @warning
* Attempting to use an entity that isn't managed by the loader results in
* undefined behavior.<br/>
* An assertion will abort the execution at runtime in debug mode if the
* loader doesn't knows about the entity.
*
* @param entity An entity identifier.
* @return The identifier to which `entity` refers in the target registry.
*/
entity_type map(entity_type entity) {
assert(has(entity));
return remloc[entity].first;
}
private:
std::unordered_map<Entity, std::pair<Entity, bool>> remloc;
Registry<Entity> &registry;
};
}
#endif // ENTT_ENTITY_SNAPSHOT_HPP

View File

@@ -3,6 +3,7 @@
#include <algorithm>
#include <iterator>
#include <numeric>
#include <utility>
#include <vector>
@@ -57,9 +58,13 @@ class SparseSet<Entity> {
using traits_type = entt_traits<Entity>;
struct Iterator final {
using difference_type = std::size_t;
using value_type = Entity;
using pointer = 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(const std::vector<value_type> &direct, std::size_t pos)
: direct{direct}, pos{pos}
{}
@@ -72,20 +77,29 @@ class SparseSet<Entity> {
return ++(*this), orig;
}
Iterator & operator+=(difference_type value) noexcept {
pos -= value;
return *this;
}
Iterator operator+(difference_type value) noexcept {
return Iterator{direct, pos-value};
}
bool operator==(const Iterator &other) const noexcept {
return other.pos == pos && other.direct == direct;
return other.pos == pos;
}
bool operator!=(const Iterator &other) const noexcept {
return !(*this == other);
}
value_type operator*() const noexcept {
return (*direct)[pos-1];
reference operator*() const noexcept {
return direct[pos-1];
}
private:
const std::vector<value_type> *direct;
const std::vector<value_type> &direct;
std::size_t pos;
};
@@ -126,10 +140,23 @@ public:
* @param cap Desired capacity.
*/
void reserve(size_type cap) {
reverse.reserve(cap);
direct.reserve(cap);
}
/**
* @brief Returns the extent of a sparse set.
*
* The extent of a sparse set is also the size of the internal sparse array.
* There is no guarantee that the internal packed array has the same size.
* Usually the size of the internal sparse array is equal or greater than
* the one of the internal packed array.
*
* @return Extent of the sparse set.
*/
size_type extent() const noexcept {
return reverse.size();
}
/**
* @brief Returns the number of elements in a sparse set.
*
@@ -159,7 +186,7 @@ public:
* always a valid range, even if the container is empty.
*
* @note
* There are no guarantees on the order, even though `sort` has been
* There are no guarantees on the order, even though `respect` has been
* previously invoked. Internal data structures arrange elements to maximize
* performance. Accessing them directly gives a performance boost but less
* guarantees. Use `begin` and `end` if you want to iterate the sparse set
@@ -174,34 +201,34 @@ public:
/**
* @brief Returns an iterator to the beginning.
*
* The returned iterator points to the first element of the internal packed
* 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 `sort`.
* Input iterators stay true to the order imposed by a call to `respect`.
*
* @return An iterator to the first element of the internal packed array.
* @return An iterator to the first entity of the internal packed array.
*/
iterator_type begin() const noexcept {
return Iterator{&direct, direct.size()};
return Iterator{direct, direct.size()};
}
/**
* @brief Returns an iterator to the end.
*
* The returned iterator points to the element following the last element in
* 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 `sort`.
* Input iterators stay true to the order imposed by a call to `respect`.
*
* @return An iterator to the element following the last element of the
* @return An iterator to the element following the last entity of the
* internal packed array.
*/
iterator_type end() const noexcept {
return Iterator{&direct, 0};
return Iterator{direct, 0};
}
/**
@@ -210,11 +237,33 @@ public:
* @return True if the sparse set contains the entity, false otherwise.
*/
bool has(entity_type entity) const noexcept {
using promotion_type = std::conditional_t<sizeof(size_type) >= sizeof(entity_type), size_type, entity_type>;
// explicit promotion to avoid warnings with std::uint16_t
const auto entt = promotion_type{entity} & traits_type::entity_mask;
const auto pos = size_type(entity & traits_type::entity_mask);
// the in-use control bit permits to avoid accessing the direct vector
return (entt < reverse.size()) && (reverse[entt] & in_use);
return (pos < reverse.size()) && (reverse[pos] & in_use);
}
/**
* @brief Checks if a sparse set contains an entity (unsafe).
*
* Alternative version of `has`. It accesses the underlying data structures
* without bounds checking and thus it's both unsafe and risky to use.<br/>
* You should not invoke directly this function unless you know exactly what
* you are doing. Prefer the `has` member function instead.
*
* @warning
* Attempting to use an entity that doesn't belong to the sparse set can
* result in undefined behavior.<br/>
* An assertion will abort the execution at runtime in debug mode in case of
* bounds violation.
*
* @param entity A valid entity identifier.
* @return True if the sparse set contains the entity, false otherwise.
*/
bool fast(entity_type entity) const 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);
}
/**
@@ -233,7 +282,7 @@ public:
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] & ~in_use;
return reverse[entt] & traits_type::entity_mask;
}
/**
@@ -249,17 +298,15 @@ public:
*/
void construct(entity_type entity) {
assert(!has(entity));
using promotion_type = std::conditional_t<sizeof(size_type) >= sizeof(entity_type), size_type, entity_type>;
// explicit promotion to avoid warnings with std::uint16_t
const auto entt = promotion_type{entity} & traits_type::entity_mask;
const auto pos = size_type(entity & traits_type::entity_mask);
if(!(entt < reverse.size())) {
reverse.resize(entt+1, pos_type{});
if(!(pos < reverse.size())) {
reverse.resize(pos+1, pos_type{});
}
// we exploit the fact that pos_type is equal to entity_type and pos has
// traits_type::version_mask bits unused we can use to mark it as in-use
reverse[entt] = pos_type(direct.size()) | in_use;
reverse[pos] = pos_type(direct.size()) | in_use;
direct.emplace_back(entity);
}
@@ -278,10 +325,9 @@ public:
assert(has(entity));
const auto entt = entity & traits_type::entity_mask;
const auto back = direct.back() & traits_type::entity_mask;
const auto pos = reverse[entt] & ~in_use;
// the order matters: if back and entt are the same (for the sparse set
// has size 1), switching the two lines below doesn't work as expected
reverse[back] = pos | in_use;
// 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;
// swapping isn't required here, we are getting rid of the last element
direct[pos] = direct.back();
@@ -322,22 +368,22 @@ public:
* sets by using one of them as a master and the other one as a slave.
*
* Iterating the sparse set with a couple of iterators returns elements in
* the expected order after a call to `sort`. See `begin` and `end` for more
* details.
* the expected order after a call to `respect`. See `begin` and `end` for
* more details.
*
* @note
* Attempting to iterate elements using the raw pointer returned by `data`
* gives no guarantees on the order, even though `sort` has been invoked.
* gives no guarantees on the order, even though `respect` has been invoked.
*
* @param other The sparse sets that imposes the order of the entities.
*/
virtual void respect(const SparseSet<Entity> &other) noexcept {
void respect(const SparseSet<Entity> &other) noexcept {
auto from = other.begin();
auto to = other.end();
pos_type pos = direct.size() - 1;
while(pos > 0 && from != to) {
while(pos && from != to) {
if(has(*from)) {
if(*from != direct[pos]) {
swap(pos, get(*from));
@@ -390,6 +436,56 @@ template<typename Entity, typename Type>
class SparseSet<Entity, Type>: public SparseSet<Entity> {
using underlying_type = SparseSet<Entity>;
struct Iterator final {
using difference_type = std::size_t;
using value_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)
: instances{instances}, pos{pos}
{}
Iterator & operator++() noexcept {
return --pos, *this;
}
Iterator operator++(int) noexcept {
Iterator orig = *this;
return ++(*this), orig;
}
Iterator & operator+=(difference_type value) noexcept {
pos -= value;
return *this;
}
Iterator operator+(difference_type value) noexcept {
return Iterator{instances, pos-value};
}
bool operator==(const Iterator &other) const noexcept {
return other.pos == pos;
}
bool operator!=(const Iterator &other) const noexcept {
return !(*this == other);
}
reference operator*() noexcept {
return instances[pos-1];
}
pointer operator->() noexcept {
return &instances.data()[pos-1];
}
private:
std::vector<value_type> &instances;
std::size_t pos;
};
public:
/*! @brief Type of the objects associated to the entities. */
using object_type = Type;
@@ -400,7 +496,7 @@ public:
/*! @brief Unsigned integer type. */
using size_type = typename underlying_type::size_type;
/*! @brief Input iterator type. */
using iterator_type = typename underlying_type::iterator_type;
using iterator_type = Iterator;
/*! @brief Default constructor. */
SparseSet() noexcept = default;
@@ -435,11 +531,11 @@ public:
* always a valid range, even if the container is empty.
*
* @note
* There are no guarantees on the order, even though `sort` has been
* previously invoked. Internal data structures arrange elements to maximize
* performance. Accessing them directly gives a performance boost but less
* guarantees. Use `begin` and `end` if you want to iterate the sparse set
* in the expected order.
* There are no guarantees on the order, even though either `sort` or
* `respect` has been previously invoked. Internal data structures arrange
* elements to maximize performance. Accessing them directly gives a
* performance boost but less guarantees. Use `begin` and `end` if you want
* to iterate the sparse set in the expected order.
*
* @return A pointer to the array of objects.
*/
@@ -454,11 +550,11 @@ public:
* always a valid range, even if the container is empty.
*
* @note
* There are no guarantees on the order, even though `sort` has been
* previously invoked. Internal data structures arrange elements to maximize
* performance. Accessing them directly gives a performance boost but less
* guarantees. Use `begin` and `end` if you want to iterate the sparse set
* in the expected order.
* There are no guarantees on the order, even though either `sort` or
* `respect` has been previously invoked. Internal data structures arrange
* elements to maximize performance. Accessing them directly gives a
* performance boost but less guarantees. Use `begin` and `end` if you want
* to iterate the sparse set in the expected order.
*
* @return A pointer to the array of objects.
*/
@@ -466,6 +562,40 @@ public:
return instances.data();
}
/**
* @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() noexcept {
return Iterator{instances, instances.size()};
}
/**
* @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() noexcept {
return Iterator{instances, 0};
}
/**
* @brief Returns the object associated to an entity.
*
@@ -501,6 +631,12 @@ public:
/**
* @brief Assigns an entity to a sparse set and constructs its object.
*
* @note
* _Sfinae'd_ function.<br/>
* This version is used for types that can be constructed in place directly.
* It doesn't work well with aggregates because of the placement new usually
* performed under the hood during an _emplace back_.
*
* @warning
* Attempting to use an entity that already belongs to the sparse set
* results in undefined behavior.<br/>
@@ -513,10 +649,38 @@ public:
* @return The object associated to the entity.
*/
template<typename... Args>
object_type & construct(entity_type entity, Args&&... args) {
std::enable_if_t<std::is_constructible<Type, Args...>::value, object_type &>
construct(entity_type entity, Args &&... args) {
underlying_type::construct(entity);
// emplace_back doesn't work well with PODs because of its placement new
instances.push_back(object_type{ std::forward<Args>(args)... });
instances.emplace_back(std::forward<Args>(args)...);
return instances.back();
}
/**
* @brief Assigns an entity to a sparse set and constructs its object.
*
* @note
* _Sfinae'd_ function.<br/>
* Fallback for aggregates and types in general that do not work well with a
* placement new as performed usually under the hood during an
* _emplace back_.
*
* @warning
* Attempting to use 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 already contains the given entity.
*
* @tparam Args Types of arguments to use to construct the object.
* @param entity A valid entity identifier.
* @param args Parameters to use to construct an object for the entity.
* @return The object associated to the entity.
*/
template<typename... Args>
std::enable_if_t<!std::is_constructible<Type, Args...>::value, object_type &>
construct(entity_type entity, Args &&... args) {
underlying_type::construct(entity);
instances.emplace_back(Type{std::forward<Args>(args)...});
return instances.back();
}
@@ -556,8 +720,9 @@ public:
* @endcode
*
* @note
* Attempting to iterate elements using the raw pointer returned by `data`
* gives no guarantees on the order, even though `sort` has been invoked.
* 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.
* @param compare A valid comparison function object.
@@ -576,8 +741,8 @@ public:
auto next = copy[curr];
while(curr != next) {
auto lhs = copy[curr];
auto rhs = copy[next];
const auto lhs = copy[curr];
const auto rhs = copy[next];
std::swap(instances[lhs], instances[rhs]);
underlying_type::swap(lhs, rhs);
copy[curr] = curr;
@@ -600,26 +765,29 @@ public:
* sets by using one of them as a master and the other one as a slave.
*
* Iterating the sparse set with a couple of iterators returns elements in
* the expected order after a call to `sort`. See `begin` and `end` for more
* details.
* the expected order after a call to `respect`. See `begin` and `end` for
* more details.
*
* @note
* Attempting to iterate elements using the raw pointer returned by `data`
* gives no guarantees on the order, even though `sort` has been invoked.
* 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
* `respect` has been invoked.
*
* @param other The sparse sets that imposes the order of the entities.
*/
void respect(const SparseSet<Entity> &other) noexcept override {
void respect(const SparseSet<Entity> &other) noexcept {
auto from = other.begin();
auto to = other.end();
pos_type pos = underlying_type::size() - 1;
const auto *direct = underlying_type::data();
const auto *local = underlying_type::data();
while(pos > 0 && from != to) {
if(underlying_type::has(*from)) {
if(*from != *(direct + pos)) {
auto candidate = underlying_type::get(*from);
while(pos && from != to) {
const auto curr = *from;
if(underlying_type::has(curr)) {
if(curr != *(local + pos)) {
auto candidate = underlying_type::get(curr);
std::swap(instances[pos], instances[candidate]);
underlying_type::swap(pos, candidate);
}

View File

@@ -2,22 +2,32 @@
#define ENTT_ENTITY_VIEW_HPP
#include <cassert>
#include <array>
#include <tuple>
#include <utility>
#include <algorithm>
#include <type_traits>
#include "entt_traits.hpp"
#include "sparse_set.hpp"
namespace entt {
/**
* @brief Forward declaration of the registry class.
*/
template<typename>
class Registry;
/**
* @brief Persistent view.
*
* A persistent view returns all the entities and only the entities that have
* at least the given components. Moreover, it's guaranteed that the entity list
* is thightly packed in memory for fast iterations.<br/>
* is tightly packed in memory for fast iterations.<br/>
* In general, persistent views don't stay true to the order of any set of
* components unless users explicitly sort them.
*
@@ -27,10 +37,11 @@ namespace entt {
*
* * New instances of the given components are created and assigned to entities.
* * The entity currently pointed is modified (as an example, if one of the
* given components is removed from the entity to which the iterator points).
* given components is removed from the entity to which the iterator points).
*
* In all the other cases, modify the pools of the given components somehow
* invalidates all the iterators and using them results in undefined behavior.
* In all the other cases, modifying the pools of the given components in any
* way invalidates all the iterators and using them results in undefined
* behavior.
*
* @note
* Views share references to the underlying data structures with the Registry
@@ -47,6 +58,7 @@ namespace entt {
*
* @sa View
* @sa View<Entity, Component>
* @sa RawView
*
* @tparam Entity A valid entity type (see entt_traits for more details).
* @tparam Component Types of components iterated by the view.
@@ -55,35 +67,27 @@ template<typename Entity, typename... Component>
class PersistentView final {
static_assert(sizeof...(Component) > 1, "!");
/*! @brief A registry is allowed to create views. */
friend class Registry<Entity>;
template<typename Comp>
using pool_type = SparseSet<Entity, Comp>;
using view_type = SparseSet<Entity>;
using pattern_type = std::tuple<pool_type<Component> &...>;
PersistentView(view_type &view, pool_type<Component> &... pools) noexcept
: view{view}, pools{pools...}
{}
public:
/*! Input iterator type. */
/*! @brief Input iterator type. */
using iterator_type = typename view_type::iterator_type;
/*! @brief Underlying entity identifier. */
using entity_type = typename view_type::entity_type;
/*! @brief Unsigned integer type. */
using size_type = typename view_type::size_type;
/**
* @brief Constructs a persistent view around a dedicated pool of entities.
*
* A persistent view is created out of:
*
* * A dedicated pool of entities that is shared between all the persistent
* views of the same type.
* * A bunch of pools of components to which to refer to get instances.
*
* @param view Shared reference to a dedicated pool of entities.
* @param pools References to pools of components.
*/
PersistentView(view_type &view, pool_type<Component>&... pools) noexcept
: view{view}, pools{pools...}
{}
/**
* @brief Returns the number of entities that have the given components.
* @return Number of entities that have the given components.
@@ -145,6 +149,15 @@ public:
return view.end();
}
/**
* @brief Checks if a view contains an entity.
* @param entity A valid entity identifier.
* @return True if the view contains the given entity, false otherwise.
*/
bool contains(entity_type entity) const noexcept {
return view.has(entity) && (view.data()[view.get(entity)] == entity);
}
/**
* @brief Returns the component assigned to the given entity.
*
@@ -155,32 +168,33 @@ public:
* Attempting to use an invalid component type results in a compilation
* error. Attempting to use an entity that doesn't belong to the view
* results in undefined behavior.<br/>
* An assertion will abort the execution at runtime in debug mode if
* the view doesn't contain the given entity.
* An assertion will abort the execution at runtime in debug mode if the
* view doesn't contain the given entity.
*
* @tparam Comp Type of the component to get.
* @tparam Comp Type of component to get.
* @param entity A valid entity identifier.
* @return The component assigned to the entity.
*/
template<typename Comp>
const Comp & get(entity_type entity) const noexcept {
assert(contains(entity));
return std::get<pool_type<Comp> &>(pools).get(entity);
}
/**
* @brief Returns the component assigned to the given entity.
*
* Prefer this function instead of `Registry::get` during iterations.
* It has far better performance than its companion function.
* Prefer this function instead of `Registry::get` during iterations. It has
* far better performance than its companion function.
*
* @warning
* Attempting to use an invalid component type results in a compilation
* error. Attempting to use an entity that doesn't belong to the view
* results in undefined behavior.<br/>
* An assertion will abort the execution at runtime in debug mode if
* the view doesn't contain the given entity.
* An assertion will abort the execution at runtime in debug mode if the
* view doesn't contain the given entity.
*
* @tparam Comp Type of the component to get.
* @tparam Comp Type of component to get.
* @param entity A valid entity identifier.
* @return The component assigned to the entity.
*/
@@ -199,8 +213,8 @@ public:
* Attempting to use invalid component types results in a compilation error.
* Attempting to use an entity that doesn't belong to the view results in
* undefined behavior.<br/>
* An assertion will abort the execution at runtime in debug mode if
* the view doesn't contain the given entity.
* An assertion will abort the execution at runtime in debug mode if the
* view doesn't contain the given entity.
*
* @tparam Comp Types of the components to get.
* @param entity A valid entity identifier.
@@ -209,7 +223,8 @@ public:
template<typename... Comp>
std::enable_if_t<(sizeof...(Comp) > 1), std::tuple<const Comp &...>>
get(entity_type entity) const noexcept {
return std::tuple<const Comp &...>{ get<Comp>(entity)... };
assert(contains(entity));
return std::tuple<const Comp &...>{get<Comp>(entity)...};
}
/**
@@ -222,8 +237,8 @@ public:
* Attempting to use invalid component types results in a compilation error.
* Attempting to use an entity that doesn't belong to the view results in
* undefined behavior.<br/>
* An assertion will abort the execution at runtime in debug mode if
* the view doesn't contain the given entity.
* An assertion will abort the execution at runtime in debug mode if the
* view doesn't contain the given entity.
*
* @tparam Comp Types of the components to get.
* @param entity A valid entity identifier.
@@ -232,11 +247,13 @@ public:
template<typename... Comp>
std::enable_if_t<(sizeof...(Comp) > 1), std::tuple<Comp &...>>
get(entity_type entity) noexcept {
return std::tuple<Comp &...>{ get<Comp>(entity)... };
assert(contains(entity));
return std::tuple<Comp &...>{get<Comp>(entity)...};
}
/**
* @brief Iterate the entities and applies them the given function object.
* @brief Iterates entities and components and applies the given function
* object to them.
*
* The function object is invoked for each entity. It is provided with the
* entity itself and a set of const references to all the components of the
@@ -252,13 +269,14 @@ public:
*/
template<typename Func>
void each(Func func) const {
for(auto entity: *this) {
for(auto entity: view) {
func(entity, get<Component>(entity)...);
}
}
/**
* @brief Iterate the entities and applies them the given function object.
* @brief Iterates entities and components and applies the given function
* object to them.
*
* The function object is invoked for each entity. It is provided with the
* entity itself and a set of references to all the components of the
@@ -293,7 +311,7 @@ public:
* can quickly ruin the order imposed to the pool of entities shared between
* the persistent views.
*
* @tparam Comp Type of the component to use to impose the order.
* @tparam Comp Type of component to use to impose the order.
*/
template<typename Comp>
void sort() {
@@ -302,7 +320,7 @@ public:
private:
view_type &view;
std::tuple<pool_type<Component> &...> pools;
const pattern_type pools;
};
@@ -324,10 +342,11 @@ private:
*
* * New instances of the given components are created and assigned to entities.
* * The entity currently pointed is modified (as an example, if one of the
* given components is removed from the entity to which the iterator points).
* given components is removed from the entity to which the iterator points).
*
* In all the other cases, modify the pools of the given components somehow
* invalidates all the iterators and using them results in undefined behavior.
* In all the other cases, modifying the pools of the given components in any
* way invalidates all the iterators and using them results in undefined
* behavior.
*
* @note
* Views share references to the underlying data structures with the Registry
@@ -340,6 +359,7 @@ private:
*
* @sa View<Entity, Component>
* @sa PersistentView
* @sa RawView
*
* @tparam Entity A valid entity type (see entt_traits for more details).
* @tparam Component Types of components iterated by the view.
@@ -348,28 +368,45 @@ template<typename Entity, typename... Component>
class View final {
static_assert(sizeof...(Component) > 1, "!");
/*! @brief A registry is allowed to create views. */
friend class Registry<Entity>;
template<typename Comp>
using pool_type = SparseSet<Entity, Comp>;
using base_pool_type = SparseSet<Entity>;
using underlying_iterator_type = typename base_pool_type::iterator_type;
using repo_type = std::tuple<pool_type<Component> &...>;
using view_type = SparseSet<Entity>;
using underlying_iterator_type = typename view_type::iterator_type;
using unchecked_type = std::array<const view_type *, (sizeof...(Component) - 1)>;
using pattern_type = std::tuple<pool_type<Component> &...>;
using traits_type = entt_traits<Entity>;
class Iterator {
using size_type = typename view_type::size_type;
inline bool valid() const noexcept {
using accumulator_type = bool[];
auto entity = *begin;
bool all = true;
accumulator_type accumulator = { all, (all = all && std::get<pool_type<Component> &>(pools).has(entity))... };
(void)accumulator;
return all;
const auto entity = *begin;
const auto sz = size_type(entity & traits_type::entity_mask);
auto pos = unchecked.size();
if(sz < extent) {
for(; pos && unchecked[pos-1]->fast(entity); --pos);
}
return !pos;
}
public:
using value_type = typename base_pool_type::entity_type;
using difference_type = typename underlying_iterator_type::difference_type;
using value_type = typename underlying_iterator_type::value_type;
using pointer = typename underlying_iterator_type::pointer;
using reference = typename underlying_iterator_type::reference;
using iterator_category = typename underlying_iterator_type::iterator_category;
Iterator(const repo_type &pools, underlying_iterator_type begin, underlying_iterator_type end) noexcept
: pools{pools}, begin{begin}, end{end}
Iterator(unchecked_type unchecked, size_type extent, underlying_iterator_type begin, underlying_iterator_type end) noexcept
: unchecked{unchecked},
extent{extent},
begin{begin},
end{end}
{
if(begin != end && !valid()) {
++(*this);
@@ -377,9 +414,7 @@ class View final {
}
Iterator & operator++() noexcept {
++begin;
while(begin != end && !valid()) { ++begin; }
return *this;
return (++begin != end && !valid()) ? ++(*this) : *this;
}
Iterator operator++(int) noexcept {
@@ -387,6 +422,14 @@ class View final {
return ++(*this), orig;
}
Iterator & operator+=(difference_type value) noexcept {
return ((begin += value) != end && !valid()) ? ++(*this) : *this;
}
Iterator operator+(difference_type value) noexcept {
return Iterator{unchecked, extent, begin+value, end};
}
bool operator==(const Iterator &other) const noexcept {
return other.begin == begin;
}
@@ -400,29 +443,34 @@ class View final {
}
private:
const repo_type &pools;
const unchecked_type unchecked;
const size_type extent;
underlying_iterator_type begin;
underlying_iterator_type end;
};
public:
/*! Input iterator type. */
using iterator_type = Iterator;
/*! @brief Underlying entity identifier. */
using entity_type = typename base_pool_type::entity_type;
/*! @brief Unsigned integer type. */
using size_type = typename base_pool_type::size_type;
/**
* @brief Constructs a view out of a bunch of pools of components.
* @param pools References to pools of components.
*/
View(pool_type<Component>&... pools) noexcept
: pools{pools...}, view{nullptr}
View(pool_type<Component> &... pools) noexcept
: pools{pools...}, view{nullptr}, unchecked{}
{
reset();
}
public:
/*! @brief Input iterator type. */
using iterator_type = Iterator;
/*! @brief Underlying entity identifier. */
using entity_type = typename view_type::entity_type;
/*! @brief Unsigned integer type. */
using size_type = typename view_type::size_type;
/**
* @brief Estimates the number of entities that have the given components.
* @return Estimated number of entities that have the given components.
*/
size_type size() const noexcept {
return view->size();
}
/**
* @brief Returns an iterator to the first entity that has the given
* components.
@@ -438,7 +486,8 @@ public:
* @return An iterator to the first entity that has the given components.
*/
iterator_type begin() const noexcept {
return Iterator{pools, view->begin(), view->end()};
const auto extent = std::min({ std::get<pool_type<Component> &>(pools).extent()... });
return Iterator{unchecked, extent, view->begin(), view->end()};
}
/**
@@ -457,28 +506,47 @@ public:
* given components.
*/
iterator_type end() const noexcept {
return Iterator{pools, view->end(), view->end()};
const auto extent = std::min({ std::get<pool_type<Component> &>(pools).extent()... });
return Iterator{unchecked, extent, view->end(), view->end()};
}
/**
* @brief Checks if a view contains an entity.
* @param entity A valid entity identifier.
* @return True if the view contains the given entity, false otherwise.
*/
bool contains(entity_type entity) const noexcept {
const auto extent = std::min({ std::get<pool_type<Component> &>(pools).extent()... });
const auto sz = size_type(entity & traits_type::entity_mask);
auto pos = unchecked.size();
if(sz < extent && view->has(entity) && (view->data()[view->get(entity)] == entity)) {
for(; pos && unchecked[pos-1]->fast(entity); --pos);
}
return !pos;
}
/**
* @brief Returns the component assigned to the given entity.
*
* Prefer this function instead of `Registry::get` during iterations.
* It has far better performance than its companion function.
* Prefer this function instead of `Registry::get` during iterations. It has
* far better performance than its companion function.
*
* @warning
* Attempting to use an invalid component type results in a compilation
* error. Attempting to use an entity that doesn't belong to the view
* results in undefined behavior.<br/>
* An assertion will abort the execution at runtime in debug mode if
* the view doesn't contain the given entity.
* An assertion will abort the execution at runtime in debug mode if the
* view doesn't contain the given entity.
*
* @tparam Comp Type of the component to get.
* @tparam Comp Type of component to get.
* @param entity A valid entity identifier.
* @return The component assigned to the entity.
*/
template<typename Comp>
const Comp & get(entity_type entity) const noexcept {
assert(contains(entity));
return std::get<pool_type<Comp> &>(pools).get(entity);
}
@@ -492,10 +560,10 @@ public:
* Attempting to use an invalid component type results in a compilation
* error. Attempting to use an entity that doesn't belong to the view
* results in undefined behavior.<br/>
* An assertion will abort the execution at runtime in debug mode if
* the view doesn't contain the given entity.
* An assertion will abort the execution at runtime in debug mode if the
* view doesn't contain the given entity.
*
* @tparam Comp Type of the component to get.
* @tparam Comp Type of component to get.
* @param entity A valid entity identifier.
* @return The component assigned to the entity.
*/
@@ -514,8 +582,8 @@ public:
* Attempting to use invalid component types results in a compilation error.
* Attempting to use an entity that doesn't belong to the view results in
* undefined behavior.<br/>
* An assertion will abort the execution at runtime in debug mode if
* the view doesn't contain the given entity.
* An assertion will abort the execution at runtime in debug mode if the
* view doesn't contain the given entity.
*
* @tparam Comp Types of the components to get.
* @param entity A valid entity identifier.
@@ -524,7 +592,8 @@ public:
template<typename... Comp>
std::enable_if_t<(sizeof...(Comp) > 1), std::tuple<const Comp &...>>
get(entity_type entity) const noexcept {
return std::tuple<const Comp &...>{ get<Comp>(entity)... };
assert(contains(entity));
return std::tuple<const Comp &...>{get<Comp>(entity)...};
}
/**
@@ -537,8 +606,8 @@ public:
* Attempting to use invalid component types results in a compilation error.
* Attempting to use an entity that doesn't belong to the view results in
* undefined behavior.<br/>
* An assertion will abort the execution at runtime in debug mode if
* the view doesn't contain the given entity.
* An assertion will abort the execution at runtime in debug mode if the
* view doesn't contain the given entity.
*
* @tparam Comp Types of the components to get.
* @param entity A valid entity identifier.
@@ -547,11 +616,13 @@ public:
template<typename... Comp>
std::enable_if_t<(sizeof...(Comp) > 1), std::tuple<Comp &...>>
get(entity_type entity) noexcept {
return std::tuple<Comp &...>{ get<Comp>(entity)... };
assert(contains(entity));
return std::tuple<Comp &...>{get<Comp>(entity)...};
}
/**
* @brief Iterate the entities and applies them the given function object.
* @brief Iterates entities and components and applies the given function
* object to them.
*
* The function object is invoked for each entity. It is provided with the
* entity itself and a set of const references to all the components of the
@@ -567,13 +638,26 @@ public:
*/
template<typename Func>
void each(Func func) const {
for(auto entity: *this) {
func(entity, get<Component>(entity)...);
const auto extent = std::min({ std::get<pool_type<Component> &>(pools).extent()... });
for(auto entity: *view) {
const auto sz = size_type(entity & traits_type::entity_mask);
if(sz < extent) {
auto pos = unchecked.size();
for(; pos && unchecked[pos-1]->fast(entity); --pos);
if(!pos) {
func(entity, get<Component>(entity)...);
}
}
}
}
/**
* @brief Iterate the entities and applies them the given function object.
* @brief Iterates entities and components and applies the given function
* object to them.
*
* The function object is invoked for each entity. It is provided with the
* entity itself and a set of references to all the components of the
@@ -606,15 +690,28 @@ public:
*/
void reset() {
using accumulator_type = size_type[];
auto probe = [this](auto sz, auto &pool) { return pool.size() < sz ? (view = &pool, pool.size()) : sz; };
size_type sz = std::max({ std::get<pool_type<Component> &>(pools).size()... }) + std::size_t{1};
accumulator_type accumulator = { sz, (sz = probe(sz, std::get<pool_type<Component> &>(pools)))... };
(void)accumulator;
size_type pos{};
auto probe = [this](auto sz, const auto &pool) {
return pool.size() < sz ? (view = &pool, pool.size()) : sz;
};
auto filter = [this](auto pos, const auto &pool) {
return (view != &pool) ? (unchecked[pos++] = &pool, pos) : pos;
};
accumulator_type probing = { (sz = probe(sz, std::get<pool_type<Component> &>(pools)))... };
accumulator_type filtering = { (pos = filter(pos, std::get<pool_type<Component> &>(pools)))... };
(void)filtering;
(void)probing;
}
private:
repo_type pools;
base_pool_type *view;
const pattern_type pools;
const view_type *view;
unchecked_type unchecked;
};
@@ -623,7 +720,7 @@ private:
*
* Single component views are specialized in order to get a boost in terms of
* performance. This kind of views can access the underlying data structure
* directly and avoid superflous checks.<br/>
* directly and avoid superfluous checks.<br/>
* Order of elements during iterations are highly dependent on the order of the
* underlying data structure. See SparseSet and its specializations for more
* details.
@@ -632,11 +729,11 @@ private:
*
* Iterators aren't invalidated if:
*
* * New instances of the given components are created and assigned to entities.
* * The entity currently pointed is modified (as an example, if one of the
* given components is removed from the entity to which the iterator points).
* * New instances of the given component are created and assigned to entities.
* * The entity currently pointed is modified (as an example, the given
* component is removed from the entity to which the iterator points).
*
* In all the other cases, modify the pools of the given components somehow
* In all the other cases, modifying the pool of the given component in any way
* invalidates all the iterators and using them results in undefined behavior.
*
* @note
@@ -650,32 +747,33 @@ private:
*
* @sa View
* @sa PersistentView
* @sa RawView
*
* @tparam Entity A valid entity type (see entt_traits for more details).
* @tparam Component Type of the component iterated by the view.
* @tparam Component Type of component iterated by the view.
*/
template<typename Entity, typename Component>
class View<Entity, Component> final {
/*! @brief A registry is allowed to create views. */
friend class Registry<Entity>;
using view_type = SparseSet<Entity>;
using pool_type = SparseSet<Entity, Component>;
View(pool_type &pool) noexcept
: pool{pool}
{}
public:
/*! Input iterator type. */
using iterator_type = typename pool_type::iterator_type;
/*! @brief Input iterator type. */
using iterator_type = typename view_type::iterator_type;
/*! @brief Underlying entity identifier. */
using entity_type = typename pool_type::entity_type;
/*! @brief Unsigned integer type. */
using size_type = typename pool_type::size_type;
/*! Type of the component iterated by the view. */
/*! @brief Type of component iterated by the view. */
using raw_type = typename pool_type::object_type;
/**
* @brief Constructs a view out of a pool of components.
* @param pool A reference to a pool of components.
*/
View(pool_type &pool) noexcept
: pool{pool}
{}
/**
* @brief Returns the number of entities that have the given component.
* @return Number of entities that have the given component.
@@ -747,7 +845,7 @@ public:
* @return An iterator to the first entity that has the given component.
*/
iterator_type begin() const noexcept {
return pool.begin();
return pool.view_type::begin();
}
/**
@@ -766,7 +864,16 @@ public:
* given component.
*/
iterator_type end() const noexcept {
return pool.end();
return pool.view_type::end();
}
/**
* @brief Checks if a view contains an entity.
* @param entity A valid entity identifier.
* @return True if the view contains the given entity, false otherwise.
*/
bool contains(entity_type entity) const noexcept {
return pool.has(entity) && (pool.data()[pool.view_type::get(entity)] == entity);
}
/**
@@ -785,6 +892,7 @@ public:
* @return The component assigned to the entity.
*/
const Component & get(entity_type entity) const noexcept {
assert(contains(entity));
return pool.get(entity);
}
@@ -808,7 +916,8 @@ public:
}
/**
* @brief Iterate the entities and applies them the given function object.
* @brief Iterates entities and components and applies the given function
* object to them.
*
* The function object is invoked for each entity. It is provided with the
* entity itself and a const reference to the component of the view.<br/>
@@ -823,13 +932,16 @@ public:
*/
template<typename Func>
void each(Func func) const {
for(auto entity: *this) {
const view_type &view = pool;
for(auto entity: view) {
func(entity, get(entity));
}
}
/**
* @brief Iterate the entities and applies them the given function object.
* @brief Iterates entities and components and applies the given function
* object to them.
*
* The function object is invoked for each entity. It is provided with the
* entity itself and a reference to the component of the view.<br/>
@@ -854,6 +966,162 @@ private:
};
/**
* @brief Raw view.
*
* Raw views are meant to easily iterate components without having to resort to
* using any other member function, so as to further increase the performance.
* Whenever knowing the entity to which a component belongs isn't required, this
* should be the preferred tool.<br/>
* Order of elements during iterations are highly dependent on the order of the
* underlying data structure. See SparseSet and its specializations for more
* details.
*
* @b Important
*
* Iterators aren't invalidated if:
*
* * New instances of the given component are created and assigned to entities.
* * The entity to which the component belongs is modified (as an example, the
* given component is destroyed).
*
* In all the other cases, modifying the pool of the given component in any way
* invalidates all the iterators and using them results in undefined behavior.
*
* @note
* Views share a reference to the underlying data structure with the Registry
* that generated them. Therefore any change to the entities and to the
* components made by means of the registry are immediately reflected by views.
*
* @warning
* Lifetime of a view must overcome the one of the registry that generated it.
* In any other case, attempting to use a view results in undefined behavior.
*
* @sa View
* @sa View<Entity, Component>
* @sa PersistentView
*
* @tparam Entity A valid entity type (see entt_traits for more details).
* @tparam Component Type of component iterated by the view.
*/
template<typename Entity, typename Component>
class RawView final {
/*! @brief A registry is allowed to create views. */
friend class Registry<Entity>;
using view_type = SparseSet<Entity>;
using pool_type = SparseSet<Entity, Component>;
RawView(pool_type &pool) noexcept
: pool{pool}
{}
public:
/*! @brief Input iterator type. */
using iterator_type = typename pool_type::iterator_type;
/*! @brief Underlying entity identifier. */
using entity_type = typename pool_type::entity_type;
/*! @brief Unsigned integer type. */
using size_type = typename pool_type::size_type;
/*! @brief Type of component iterated by the view. */
using raw_type = typename pool_type::object_type;
/**
* @brief Returns the number of instances of the given type.
* @return Number of instances of the given component.
*/
size_type size() const noexcept {
return pool.size();
}
/**
* @brief Direct access to the list of components.
*
* The returned pointer is such that range `[raw(), raw() + size()]` is
* always a valid range, even if the container is empty.
*
* @note
* There are no guarantees on the order of the components. Use `begin` and
* `end` if you want to iterate the view in the expected order.
*
* @return A pointer to the array of components.
*/
raw_type * raw() noexcept {
return pool.raw();
}
/**
* @brief Direct access to the list of components.
*
* The returned pointer is such that range `[raw(), raw() + size()]` is
* always a valid range, even if the container is empty.
*
* @note
* There are no guarantees on the order of the components. Use `begin` and
* `end` if you want to iterate the view in the expected order.
*
* @return A pointer to the array of components.
*/
const raw_type * raw() const noexcept {
return pool.raw();
}
/**
* @brief Direct access to the list of entities.
*
* The returned pointer is such that range `[data(), data() + size()]` is
* always a valid range, even if the container is empty.
*
* @note
* There are no guarantees on the order of the entities. Use `begin` and
* `end` if you want to iterate the view in the expected order.
*
* @return A pointer to the array of entities.
*/
const entity_type * data() const noexcept {
return pool.data();
}
/**
* @brief Returns an iterator to the first instance of the given type.
*
* The returned iterator points to the first instance of the given type. If
* the view is empty, the returned iterator will be equal to `end()`.
*
* @note
* Input iterators stay true to the order imposed to the underlying data
* structures.
*
* @return An iterator to the first instance of the given type.
*/
iterator_type begin() const noexcept {
return pool.begin();
}
/**
* @brief Returns an iterator that is past the last instance of the given
* type.
*
* 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 to the underlying data
* structures.
*
* @return An iterator to the element following the last instance of the
* given type.
*/
iterator_type end() const noexcept {
return pool.end();
}
private:
pool_type &pool;
};
}

View File

@@ -4,6 +4,7 @@
#include "entity/actor.hpp"
#include "entity/entt_traits.hpp"
#include "entity/registry.hpp"
#include "entity/snapshot.hpp"
#include "entity/sparse_set.hpp"
#include "entity/view.hpp"
#include "locator/locator.hpp"
@@ -14,6 +15,7 @@
#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

@@ -14,7 +14,7 @@ namespace entt {
* @brief Service locator, nothing more.
*
* A service locator can be used to do what it promises: locate services.<br/>
* Usually service locators are tighly bound to the services they expose and
* Usually service locators are tightly bound to the services they expose and
* thus it's hard to define a general purpose class to do that. This template
* based implementation tries to fill the gap and to get rid of the burden of
* defining a different specific locator for each application.
@@ -78,7 +78,7 @@ struct ServiceLocator final {
* @param args Parameters to use to construct the service.
*/
template<typename Impl = Service, typename... Args>
inline static void set(Args&&... args) {
inline static void set(Args &&... args) {
service = std::make_shared<Impl>(std::forward<Args>(args)...);
}

View File

@@ -19,33 +19,42 @@ namespace entt {
* required:
*
* * @code{.cpp}
* void update(Delta);
* void update(Delta, void *);
* @endcode
*
* It's invoked once per tick until a process is explicitly aborted or it
* terminates either with or without errors. Even though it's not mandatory to
* declare this member function, as a rule of thumb each process should at
* least define it to work properly.
* least define it to work properly. The `void *` parameter is an opaque
* pointer to user data (if any) forwarded directly to the process during an
* update.
*
* * @code{.cpp}
* void init();
* void init(void *);
* @endcode
* It's invoked at the first tick, immediately before an update.
*
* It's invoked at the first tick, immediately before an update. The `void *`
* parameter is an opaque pointer to user data (if any) forwarded directly to
* the process during an update.
*
* * @code{.cpp}
* void succeeded();
* @endcode
*
* It's invoked in case of success, immediately after an update and during the
* same tick.
*
* * @code{.cpp}
* void failed();
* @endcode
*
* It's invoked in case of errors, immediately after an update and during the
* same tick.
*
* * @code{.cpp}
* void aborted();
* @endcode
*
* It's invoked only if a process is explicitly aborted. There is no guarantee
* that it executes in the same tick, this depends solely on whether the
* process is aborted immediately or not.
@@ -105,7 +114,7 @@ class Process {
}
template<State S, typename... Args>
void tick(char, tag<S>, Args&&...) {}
void tick(char, tag<S>, Args &&...) {}
protected:
/**
@@ -161,7 +170,7 @@ public:
using delta_type = Delta;
/*! @brief Default destructor. */
~Process() noexcept {
virtual ~Process() noexcept {
static_assert(std::is_base_of<Process, Derived>::value, "!");
}
@@ -273,12 +282,13 @@ private:
* following:
*
* @code{.cpp}
* void(Delta delta, auto succeed, auto fail);
* void(Delta delta, void *data, auto succeed, auto fail);
* @endcode
*
* Where:
*
* * `delta` is the elapsed time.
* * `data` is an opaque pointer to user data if any, `nullptr` otherwise.
* * `succeed` is a function to call when a process terminates with success.
* * `fail` is a function to call when a process terminates with errors.
*
@@ -307,7 +317,7 @@ struct ProcessAdaptor: Process<ProcessAdaptor<Func, Delta>, Delta>, private Func
* @param args Parameters to use to initialize the actual process.
*/
template<typename... Args>
ProcessAdaptor(Args&&... args)
ProcessAdaptor(Args &&... args)
: Func{std::forward<Args>(args)...}
{}
@@ -317,7 +327,7 @@ struct ProcessAdaptor: Process<ProcessAdaptor<Func, Delta>, Delta>, private Func
* @param data Optional data.
*/
void update(Delta delta, void *data) {
Func::operator()(delta, data, [this](){ this->succeed(); }, [this](){ this->fail(); });
Func::operator()(delta, data, [this]() { this->succeed(); }, [this]() { this->fail(); });
}
};

View File

@@ -28,7 +28,7 @@ namespace entt {
* Example of use (pseudocode):
*
* @code{.cpp}
* scheduler.attach([](auto delta, auto succeed, auto fail) {
* scheduler.attach([](auto delta, void *, auto succeed, auto fail) {
* // code
* }).then<MyProcess>(arguments...);
* @endcode
@@ -64,7 +64,7 @@ class Scheduler final {
{}
template<typename Proc, typename... Args>
decltype(auto) then(Args&&... args) && {
decltype(auto) then(Args &&... args) && {
static_assert(std::is_base_of<Process<Proc, Delta>, Proc>::value, "!");
handler = Lambda::operator()(handler, tag<Proc>{}, std::forward<Args>(args)...);
return std::move(*this);
@@ -181,7 +181,7 @@ public:
* // schedules a task in the form of a process class
* scheduler.attach<MyProcess>(arguments...)
* // appends a child in the form of a lambda function
* .then([](auto delta, auto succeed, auto fail) {
* .then([](auto delta, void *, auto succeed, auto fail) {
* // code
* })
* // appends a child in the form of another process class
@@ -194,7 +194,7 @@ public:
* @return An opaque object to use to concatenate processes.
*/
template<typename Proc, typename... Args>
auto attach(Args&&... args) {
auto attach(Args &&... args) {
static_assert(std::is_base_of<Process<Proc, Delta>, Proc>::value, "!");
auto proc = typename ProcessHandler::instance_type{new Proc{std::forward<Args>(args)...}, &Scheduler::deleter<Proc>};
@@ -237,11 +237,11 @@ public:
*
* @code{.cpp}
* // schedules a task in the form of a lambda function
* scheduler.attach([](auto delta, auto succeed, auto fail) {
* scheduler.attach([](auto delta, void *, auto succeed, auto fail) {
* // code
* })
* // appends a child in the form of another lambda function
* .then([](auto delta, auto succeed, auto fail) {
* .then([](auto delta, void *, auto succeed, auto fail) {
* // code
* })
* // appends a child in the form of a process class
@@ -274,7 +274,7 @@ public:
void update(Delta delta, void *data = nullptr) {
bool clean = false;
for(auto pos = handlers.size(); pos > 0; --pos) {
for(auto pos = handlers.size(); pos; --pos) {
auto &handler = handlers[pos-1];
const bool dead = handler.update(handler, delta, data);
clean = clean || dead;

View File

@@ -92,7 +92,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(resource_type id, Args &&... args) {
static_assert(std::is_base_of<ResourceLoader<Loader, Resource>, Loader>::value, "!");
bool loaded = true;
@@ -125,8 +125,8 @@ public:
* @return True if the resource is ready to use, false otherwise.
*/
template<typename Loader, typename... Args>
void reload(resource_type id, Args&&... args) {
return (discard(id), load(id, std::forward<Args>(args)...));
bool reload(resource_type id, Args &&... args) {
return (discard(id), load<Loader>(id, std::forward<Args>(args)...));
}
/**
@@ -142,7 +142,7 @@ public:
* @return A handle for the given resource.
*/
template<typename Loader, typename... Args>
ResourceHandle<Resource> temp(Args&&... args) const {
ResourceHandle<Resource> temp(Args &&... args) const {
return { Loader{}.get(std::forward<Args>(args)...) };
}

View File

@@ -17,7 +17,7 @@ class ResourceCache;
*
* Resource loaders must inherit from this class and stay true to the CRTP
* idiom. Moreover, a resource loader must expose a public, const member
* function named `load` that accepts a variable number of arguments and return
* function named `load` that accepts a variable number of arguments and returns
* a shared pointer to the resource just created.<br/>
* As an example:
*
@@ -50,7 +50,7 @@ class ResourceLoader {
friend class ResourceCache<Resource>;
template<typename... Args>
std::shared_ptr<Resource> get(Args&&... args) const {
std::shared_ptr<Resource> get(Args &&... args) const {
return static_cast<const Loader *>(this)->load(std::forward<Args>(args)...);
}
};

View File

@@ -142,7 +142,7 @@ public:
* @param args Arguments to use to construct the event.
*/
template<typename Type, typename... Args>
void publish(Args&&... args) {
void publish(Args &&... args) {
Bus<Sig, Type>::publish(std::forward<Args>(args)...);
}
};
@@ -263,7 +263,7 @@ public:
* @param args Arguments to use to construct the event.
*/
template<typename... Args>
void publish(Args&&... args) {
void publish(Args &&... args) {
signal.publish({ std::forward<Args>(args)... });
}

View File

@@ -59,12 +59,12 @@ class Dispatcher final {
}
template<typename... Args>
inline void trigger(Args&&... args) {
inline void trigger(Args &&... args) {
signal.publish({ std::forward<Args>(args)... });
}
template<typename... Args>
inline void enqueue(std::size_t current, Args&&... args) {
inline void enqueue(std::size_t current, Args &&... args) {
events[current].push_back({ std::forward<Args>(args)... });
}
@@ -127,8 +127,9 @@ public:
* automatically detected and unregistered if available.
*
* @warning
* Disonnecting a listener during an update may lead to unexpected behavior.
* Unregister listeners before or after invoking the update if possible.
* 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.
@@ -151,7 +152,7 @@ public:
* @param args Arguments to use to construct the event.
*/
template<typename Event, typename... Args>
void trigger(Args&&... args) {
void trigger(Args &&... args) {
wrapper<Event>().trigger(std::forward<Args>(args)...);
}
@@ -166,7 +167,7 @@ public:
* @param args Arguments to use to construct the event.
*/
template<typename Event, typename... Args>
void enqueue(Args&&... args) {
void enqueue(Args &&... args) {
wrapper<Event>().enqueue(buffer(mode), std::forward<Args>(args)...);
}
@@ -181,7 +182,7 @@ public:
const auto buf = buffer(mode);
mode = !mode;
for(auto pos = wrappers.size(); pos > decltype(pos){0}; --pos) {
for(auto pos = wrappers.size(); pos; --pos) {
auto &wrapper = wrappers[pos-1];
if(wrapper) {

View File

@@ -52,7 +52,7 @@ class Emitter {
using connection_type = typename container_type::iterator;
bool empty() const noexcept override {
auto pred = [](auto &&element){ return element.first; };
auto pred = [](auto &&element) { return element.first; };
return std::all_of(onceL.cbegin(), onceL.cend(), pred) &&
std::all_of(onL.cbegin(), onL.cend(), pred);
@@ -60,7 +60,7 @@ class Emitter {
void clear() noexcept override {
if(publishing) {
auto func = [](auto &&element){ element.first = true; };
auto func = [](auto &&element) { element.first = true; };
std::for_each(onceL.begin(), onceL.end(), func);
std::for_each(onL.begin(), onL.end(), func);
} else {
@@ -81,7 +81,7 @@ class Emitter {
conn->first = true;
if(!publishing) {
auto pred = [](auto &&element){ return element.first; };
auto pred = [](auto &&element) { return element.first; };
onceL.remove_if(pred);
onL.remove_if(pred);
}
@@ -102,7 +102,7 @@ class Emitter {
publishing = false;
onL.remove_if([](auto &&element){ return element.first; });
onL.remove_if([](auto &&element) { return element.first; });
}
private:
@@ -215,7 +215,7 @@ public:
* @param args Parameters to use to initialize the event.
*/
template<typename Event, typename... Args>
void publish(Args&&... args) {
void publish(Args &&... args) {
handler<Event>().publish({ std::forward<Args>(args)... }, *static_cast<Derived *>(this));
}
@@ -287,7 +287,7 @@ public:
* @brief Disconnects all the listeners for the given event type.
*
* All the connections previously returned for the given event are
* invalidated. Using them results in undefined behaviour.
* invalidated. Using them results in undefined behavior.
*
* @tparam Event Type of event to reset.
*/
@@ -300,11 +300,11 @@ public:
* @brief Disconnects all the listeners.
*
* All the connections previously returned are invalidated. Using them
* results in undefined behaviour.
* results in undefined behavior.
*/
void clear() noexcept {
std::for_each(handlers.begin(), handlers.end(),
[](auto &&handler){ if(handler) { handler->clear(); } });
[](auto &&handler) { if(handler) { handler->clear(); } });
}
/**
@@ -327,7 +327,7 @@ public:
*/
bool empty() const noexcept {
return std::all_of(handlers.cbegin(), handlers.cend(),
[](auto &&handler){ return !handler || handler->empty(); });
[](auto &&handler) { return !handler || handler->empty(); });
}
private:

View File

@@ -24,21 +24,25 @@ struct Invoker<Ret(Args...), Collector> {
virtual ~Invoker() = default;
template<typename SFINAE = Ret>
typename std::enable_if_t<std::is_void<SFINAE>::value, bool>
invoke(Collector &, proto_type proto, void *instance, Args... args) {
proto(instance, args...);
return true;
}
template<typename SFINAE = Ret>
typename std::enable_if_t<!std::is_void<SFINAE>::value, bool>
invoke(Collector &collector, proto_type proto, void *instance, Args... args) {
bool invoke(Collector &collector, proto_type proto, void *instance, Args... args) {
return collector(proto(instance, args...));
}
};
template<typename... Args, typename Collector>
struct Invoker<void(Args...), Collector> {
using proto_type = void(*)(void *, Args...);
using call_type = std::pair<void *, proto_type>;
virtual ~Invoker() = default;
bool invoke(Collector &, proto_type proto, void *instance, Args... args) {
return (proto(instance, args...), true);
}
};
template<typename Ret>
struct NullCollector final {
using result_type = Ret;
@@ -100,7 +104,7 @@ class SigH;
*
* * `Param` is a type to which `Ret` can be converted.
* * The return type is true if the handler must stop collecting data, false
* otherwise.
* otherwise.
*
* @tparam Ret Return type of a function type.
* @tparam Args Types of arguments of a function type.
@@ -229,7 +233,7 @@ public:
* @param args Arguments to use to invoke listeners.
*/
void publish(Args... args) {
for(auto pos = calls.size(); pos > size_type{0}; --pos) {
for(auto pos = calls.size(); pos; --pos) {
auto &call = calls[pos-1];
call.second(call.first, args...);
}
@@ -243,7 +247,7 @@ public:
collector_type collect(Args... args) {
collector_type collector;
for(auto pos = calls.size(); pos > size_type{0}; --pos) {
for(auto pos = calls.size(); pos; --pos) {
auto &call = calls[pos-1];
if(!this->invoke(collector, call.second, call.first, args...)) {

View File

@@ -169,7 +169,7 @@ public:
void publish(Args... args) {
std::vector<call_type> next;
for(auto pos = calls.size(); pos > size_type{0}; --pos) {
for(auto pos = calls.size(); pos; --pos) {
auto &call = calls[pos-1];
if((call.second)(call.first, args...)) {

View File

@@ -36,6 +36,25 @@ if(BUILD_MOD)
add_test(NAME mod COMMAND mod)
endif()
# Test snapshot
if(BUILD_SNAPSHOT)
set(CEREAL_DEPS_DIR ${entt_SOURCE_DIR}/deps/cereal)
configure_file(${entt_SOURCE_DIR}/cmake/in/cereal.in ${CEREAL_DEPS_DIR}/CMakeLists.txt)
execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" . WORKING_DIRECTORY ${CEREAL_DEPS_DIR})
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)
endif()
# Test core
add_executable(
@@ -55,6 +74,7 @@ add_executable(
$<TARGET_OBJECTS:odr>
entt/entity/actor.cpp
entt/entity/registry.cpp
entt/entity/snapshot.cpp
entt/entity/sparse_set.cpp
entt/entity/view.cpp
)

View File

@@ -1,9 +1,8 @@
#include <gtest/gtest.h>
#include <iostream>
#include <cstddef>
#include <cstdint>
#include <chrono>
#include <vector>
#include <gtest/gtest.h>
#include <entt/entity/registry.hpp>
struct Position {
@@ -17,7 +16,7 @@ struct Velocity {
};
template<std::size_t>
struct Comp {};
struct Comp { int x; };
struct Timer final {
Timer(): start{std::chrono::system_clock::now()} {}
@@ -34,11 +33,11 @@ private:
TEST(Benchmark, Construct) {
entt::DefaultRegistry registry;
std::cout << "Constructing 10000000 entities" << std::endl;
std::cout << "Constructing 1000000 entities" << std::endl;
Timer timer;
for(std::uint64_t i = 0; i < 10000000L; i++) {
for(std::uint64_t i = 0; i < 1000000L; i++) {
registry.create();
}
@@ -47,19 +46,18 @@ TEST(Benchmark, Construct) {
TEST(Benchmark, Destroy) {
entt::DefaultRegistry registry;
std::vector<entt::DefaultRegistry::entity_type> entities{};
std::cout << "Destroying 10000000 entities" << std::endl;
std::cout << "Destroying 1000000 entities" << std::endl;
for(std::uint64_t i = 0; i < 10000000L; i++) {
entities.push_back(registry.create());
for(std::uint64_t i = 0; i < 1000000L; i++) {
registry.create();
}
Timer timer;
for(auto entity: entities) {
registry.each([&registry](auto entity) {
registry.destroy(entity);
}
});
timer.elapsed();
}
@@ -79,21 +77,24 @@ TEST(Benchmark, IterateCreateDeleteSingleComponent) {
}
for(auto entity: view) {
const auto &position = view.get(entity);
(void)position;
if(rand() % 2 == 0) {
registry.destroy(entity);
}
}
};
}
timer.elapsed();
}
TEST(Benchmark, IterateSingleComponent10M) {
TEST(Benchmark, IterateSingleComponent1M) {
entt::DefaultRegistry registry;
std::cout << "Iterating over 10000000 entities, one component" << std::endl;
std::cout << "Iterating over 1000000 entities, one component" << std::endl;
for(std::uint64_t i = 0; i < 10000000L; i++) {
for(std::uint64_t i = 0; i < 1000000L; i++) {
registry.create<Position>();
}
@@ -102,12 +103,28 @@ TEST(Benchmark, IterateSingleComponent10M) {
timer.elapsed();
}
TEST(Benchmark, IterateTwoComponents10M) {
TEST(Benchmark, IterateSingleComponentRaw1M) {
entt::DefaultRegistry registry;
std::cout << "Iterating over 10000000 entities, two components" << std::endl;
std::cout << "Iterating over 1000000 entities, one component, raw view" << std::endl;
for(std::uint64_t i = 0; i < 10000000L; i++) {
for(std::uint64_t i = 0; i < 1000000L; i++) {
registry.create<Position>();
}
Timer timer;
for(auto &&component: registry.raw<Position>()) {
(void)component;
}
timer.elapsed();
}
TEST(Benchmark, IterateTwoComponents1M) {
entt::DefaultRegistry registry;
std::cout << "Iterating over 1000000 entities, two components" << std::endl;
for(std::uint64_t i = 0; i < 1000000L; i++) {
registry.create<Position, Velocity>();
}
@@ -116,12 +133,12 @@ TEST(Benchmark, IterateTwoComponents10M) {
timer.elapsed();
}
TEST(Benchmark, IterateTwoComponents10MHalf) {
TEST(Benchmark, IterateTwoComponents1MHalf) {
entt::DefaultRegistry registry;
std::cout << "Iterating over 10000000 entities, two components, half of the entities have all the components" << std::endl;
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 < 10000000L; i++) {
for(std::uint64_t i = 0; i < 1000000L; i++) {
auto entity = registry.create<Velocity>();
if(i % 2) { registry.assign<Position>(entity); }
}
@@ -131,12 +148,12 @@ TEST(Benchmark, IterateTwoComponents10MHalf) {
timer.elapsed();
}
TEST(Benchmark, IterateTwoComponents10MOne) {
TEST(Benchmark, IterateTwoComponents1MOne) {
entt::DefaultRegistry registry;
std::cout << "Iterating over 10000000 entities, two components, only one entity has all the components" << std::endl;
std::cout << "Iterating over 1000000 entities, two components, only one entity has all the components" << std::endl;
for(std::uint64_t i = 0; i < 10000000L; i++) {
for(std::uint64_t i = 0; i < 1000000L; i++) {
auto entity = registry.create<Velocity>();
if(i == 5000000L) { registry.assign<Position>(entity); }
}
@@ -146,13 +163,13 @@ TEST(Benchmark, IterateTwoComponents10MOne) {
timer.elapsed();
}
TEST(Benchmark, IterateTwoComponentsPersistent10M) {
TEST(Benchmark, IterateTwoComponentsPersistent1M) {
entt::DefaultRegistry registry;
registry.prepare<Position, Velocity>();
std::cout << "Iterating over 10000000 entities, two components, persistent view" << std::endl;
std::cout << "Iterating over 1000000 entities, two components, persistent view" << std::endl;
for(std::uint64_t i = 0; i < 10000000L; i++) {
for(std::uint64_t i = 0; i < 1000000L; i++) {
registry.create<Position, Velocity>();
}
@@ -161,44 +178,12 @@ TEST(Benchmark, IterateTwoComponentsPersistent10M) {
timer.elapsed();
}
TEST(Benchmark, IterateTwoComponentsPersistent10MHalf) {
entt::DefaultRegistry registry;
registry.prepare<Position, Velocity>();
std::cout << "Iterating over 10000000 entities, two components, persistent view, half of the entities have all the components" << std::endl;
for(std::uint64_t i = 0; i < 10000000L; i++) {
auto entity = registry.create<Velocity>();
if(i % 2) { registry.assign<Position>(entity); }
}
Timer timer;
registry.persistent<Position, Velocity>().each([](auto, auto &...) {});
timer.elapsed();
}
TEST(Benchmark, IterateTwoComponentsPersistent10MOne) {
entt::DefaultRegistry registry;
registry.prepare<Position, Velocity>();
std::cout << "Iterating over 10000000 entities, two components, persistent view, only one entity has all the components" << std::endl;
for(std::uint64_t i = 0; i < 10000000L; i++) {
auto entity = registry.create<Velocity>();
if(i == 5000000L) { registry.assign<Position>(entity); }
}
Timer timer;
registry.persistent<Position, Velocity>().each([](auto, auto &...) {});
timer.elapsed();
}
TEST(Benchmark, IterateFiveComponents10M) {
TEST(Benchmark, IterateFiveComponents1M) {
entt::DefaultRegistry registry;
std::cout << "Iterating over 10000000 entities, five components" << std::endl;
std::cout << "Iterating over 1000000 entities, five components" << std::endl;
for(std::uint64_t i = 0; i < 10000000L; i++) {
for(std::uint64_t i = 0; i < 1000000L; i++) {
registry.create<Position, Velocity, Comp<1>, Comp<2>, Comp<3>>();
}
@@ -207,57 +192,43 @@ TEST(Benchmark, IterateFiveComponents10M) {
timer.elapsed();
}
TEST(Benchmark, IterateTenComponents10M) {
TEST(Benchmark, IterateFiveComponents1MHalf) {
entt::DefaultRegistry registry;
std::cout << "Iterating over 10000000 entities, ten components" << std::endl;
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 < 10000000L; i++) {
registry.create<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
}
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();
}
TEST(Benchmark, IterateTenComponents10MHalf) {
entt::DefaultRegistry registry;
std::cout << "Iterating over 10000000 entities, ten components, half of the entities have all the components" << std::endl;
for(std::uint64_t i = 0; i < 10000000L; i++) {
auto entity = registry.create<Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
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); }
}
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 &...) {});
registry.view<Position, Velocity, Comp<1>, Comp<2>, Comp<3>>().each([](auto, auto &...) {});
timer.elapsed();
}
TEST(Benchmark, IterateTenComponents10MOne) {
TEST(Benchmark, IterateFiveComponents1MOne) {
entt::DefaultRegistry registry;
std::cout << "Iterating over 10000000 entities, ten components, only one entity has all the components" << std::endl;
std::cout << "Iterating over 1000000 entities, five components, only one entity has all the components" << std::endl;
for(std::uint64_t i = 0; i < 10000000L; i++) {
auto entity = registry.create<Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
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); }
}
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 &...) {});
registry.view<Position, Velocity, Comp<1>, Comp<2>, Comp<3>>().each([](auto, auto &...) {});
timer.elapsed();
}
TEST(Benchmark, IterateFiveComponentsPersistent10M) {
TEST(Benchmark, IterateFiveComponentsPersistent1M) {
entt::DefaultRegistry registry;
registry.prepare<Position, Velocity, Comp<1>, Comp<2>, Comp<3>>();
std::cout << "Iterating over 10000000 entities, five components, persistent view" << std::endl;
std::cout << "Iterating over 1000000 entities, five components, persistent view" << std::endl;
for(std::uint64_t i = 0; i < 10000000L; i++) {
for(std::uint64_t i = 0; i < 1000000L; i++) {
registry.create<Position, Velocity, Comp<1>, Comp<2>, Comp<3>>();
}
@@ -266,46 +237,58 @@ TEST(Benchmark, IterateFiveComponentsPersistent10M) {
timer.elapsed();
}
TEST(Benchmark, IterateTenComponentsPersistent10M) {
TEST(Benchmark, IterateTenComponents1M) {
entt::DefaultRegistry registry;
registry.prepare<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
std::cout << "Iterating over 10000000 entities, ten components, persistent view" << std::endl;
std::cout << "Iterating over 1000000 entities, ten components" << std::endl;
for(std::uint64_t i = 0; i < 10000000L; i++) {
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>>();
}
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 &...) {});
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();
}
TEST(Benchmark, IterateTenComponentsPersistent10MHalf) {
TEST(Benchmark, IterateTenComponents1MHalf) {
entt::DefaultRegistry registry;
registry.prepare<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
std::cout << "Iterating over 10000000 entities, ten components, persistent view, half of the entities have all the components" << std::endl;
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 < 10000000L; i++) {
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); }
}
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 &...) {});
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();
}
TEST(Benchmark, IterateTenComponentsPersistent10MOne) {
TEST(Benchmark, IterateTenComponents1MOne) {
entt::DefaultRegistry registry;
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); }
}
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();
}
TEST(Benchmark, IterateTenComponentsPersistent1M) {
entt::DefaultRegistry registry;
registry.prepare<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
std::cout << "Iterating over 10000000 entities, ten components, persistent view, only one entity has all the components" << std::endl;
std::cout << "Iterating over 1000000 entities, ten components, persistent view" << std::endl;
for(std::uint64_t i = 0; i < 10000000L; 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); }
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>>();
}
Timer timer;
@@ -315,13 +298,11 @@ TEST(Benchmark, IterateTenComponentsPersistent10MOne) {
TEST(Benchmark, SortSingle) {
entt::DefaultRegistry registry;
std::vector<entt::DefaultRegistry::entity_type> entities{};
std::cout << "Sort 150000 entities, one component" << std::endl;
for(std::uint64_t i = 0; i < 150000L; i++) {
auto entity = registry.create<Position>({ i, i });
entities.push_back(entity);
registry.create<Position>({ i, i });
}
Timer timer;
@@ -335,13 +316,11 @@ TEST(Benchmark, SortSingle) {
TEST(Benchmark, SortMulti) {
entt::DefaultRegistry registry;
std::vector<entt::DefaultRegistry::entity_type> entities{};
std::cout << "Sort 150000 entities, two components" << std::endl;
for(std::uint64_t i = 0; i < 150000L; i++) {
auto entity = registry.create<Position, Velocity>({ i, i }, { i, i });
entities.push_back(entity);
registry.create<Position, Velocity>({ i, i }, { i, i });
}
registry.sort<Position>([](const auto &lhs, const auto &rhs) {

View File

@@ -1,7 +1,18 @@
#include <cstddef>
#include <gtest/gtest.h>
#include <entt/core/hashed_string.hpp>
constexpr bool check(const char *str) {
constexpr bool ptr(const char *str) {
using hash_type = entt::HashedString::hash_type;
return (static_cast<hash_type>(entt::HashedString{str}) == entt::HashedString{str}
&& static_cast<const char *>(entt::HashedString{str}) == str
&& entt::HashedString{str} == entt::HashedString{str}
&& !(entt::HashedString{str} != entt::HashedString{str}));
}
template<std::size_t N>
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}
@@ -11,8 +22,9 @@ constexpr bool check(const char *str) {
}
TEST(HashedString, Constexprness) {
// how would you test a constepxr otherwise?
static_assert(check("foobar"), "!");
// how would you test a constexpr otherwise?
static_assert(ptr("foo"), "!");
static_assert(ref("bar"), "!");
ASSERT_TRUE(true);
}
@@ -29,7 +41,7 @@ TEST(HashedString, Functionalities) {
ASSERT_EQ(static_cast<const char *>(barHs), bar);
ASSERT_TRUE(fooHs == fooHs);
ASSERT_FALSE(fooHs == barHs);
ASSERT_TRUE(fooHs != barHs);
entt::HashedString hs{"foobar"};

View File

@@ -1,6 +1,9 @@
#include <unordered_map>
#include <unordered_set>
#include <functional>
#include <type_traits>
#include <gtest/gtest.h>
#include <entt/entity/entt_traits.hpp>
#include <entt/entity/registry.hpp>
TEST(DefaultRegistry, Functionalities) {
@@ -8,6 +11,8 @@ TEST(DefaultRegistry, Functionalities) {
ASSERT_EQ(registry.size(), entt::DefaultRegistry::size_type{0});
ASSERT_NO_THROW(registry.reserve(42));
ASSERT_NO_THROW(registry.reserve<int>(8));
ASSERT_NO_THROW(registry.reserve<char>(8));
ASSERT_TRUE(registry.empty());
ASSERT_EQ(registry.capacity(), entt::DefaultRegistry::size_type{0});
@@ -16,11 +21,11 @@ TEST(DefaultRegistry, Functionalities) {
ASSERT_TRUE(registry.empty<int>());
ASSERT_TRUE(registry.empty<char>());
auto e1 = registry.create();
auto e2 = registry.create<int, char>();
auto e0 = registry.create();
auto e1 = registry.create<int, char>();
ASSERT_TRUE(registry.has<>(e0));
ASSERT_TRUE(registry.has<>(e1));
ASSERT_TRUE(registry.has<>(e2));
ASSERT_EQ(registry.capacity(), entt::DefaultRegistry::size_type{2});
ASSERT_EQ(registry.size<int>(), entt::DefaultRegistry::size_type{1});
@@ -28,67 +33,70 @@ TEST(DefaultRegistry, Functionalities) {
ASSERT_FALSE(registry.empty<int>());
ASSERT_FALSE(registry.empty<char>());
ASSERT_NE(e1, e2);
ASSERT_FALSE(registry.has<int>(e1));
ASSERT_TRUE(registry.has<int>(e2));
ASSERT_FALSE(registry.has<char>(e1));
ASSERT_TRUE(registry.has<char>(e2));
ASSERT_FALSE((registry.has<int, char>(e1)));
ASSERT_TRUE((registry.has<int, char>(e2)));
ASSERT_EQ(registry.assign<int>(e1, 42), 42);
ASSERT_EQ(registry.assign<char>(e1, 'c'), 'c');
ASSERT_NO_THROW(registry.remove<int>(e2));
ASSERT_NO_THROW(registry.remove<char>(e2));
ASSERT_NE(e0, e1);
ASSERT_FALSE(registry.has<int>(e0));
ASSERT_TRUE(registry.has<int>(e1));
ASSERT_FALSE(registry.has<int>(e2));
ASSERT_FALSE(registry.has<char>(e0));
ASSERT_TRUE(registry.has<char>(e1));
ASSERT_FALSE(registry.has<char>(e2));
ASSERT_FALSE((registry.has<int, char>(e0)));
ASSERT_TRUE((registry.has<int, char>(e1)));
ASSERT_FALSE((registry.has<int, char>(e2)));
auto e3 = registry.create();
ASSERT_EQ(registry.assign<int>(e0, 42), 42);
ASSERT_EQ(registry.assign<char>(e0, 'c'), 'c');
ASSERT_NO_THROW(registry.remove<int>(e1));
ASSERT_NO_THROW(registry.remove<char>(e1));
registry.accomodate<int>(e3, registry.get<int>(e1));
registry.accomodate<char>(e3, registry.get<char>(e1));
ASSERT_TRUE(registry.has<int>(e0));
ASSERT_FALSE(registry.has<int>(e1));
ASSERT_TRUE(registry.has<char>(e0));
ASSERT_FALSE(registry.has<char>(e1));
ASSERT_TRUE((registry.has<int, char>(e0)));
ASSERT_FALSE((registry.has<int, char>(e1)));
ASSERT_TRUE(registry.has<int>(e3));
ASSERT_TRUE(registry.has<char>(e3));
ASSERT_EQ(registry.get<int>(e1), 42);
ASSERT_EQ(registry.get<char>(e1), 'c');
auto e2 = registry.create();
ASSERT_EQ(std::get<0>(registry.get<int, char>(e1)), 42);
ASSERT_EQ(std::get<1>(static_cast<const entt::DefaultRegistry &>(registry).get<int, char>(e1)), 'c');
registry.accommodate<int>(e2, registry.get<int>(e0));
registry.accommodate<char>(e2, registry.get<char>(e0));
ASSERT_EQ(registry.get<int>(e1), registry.get<int>(e3));
ASSERT_EQ(registry.get<char>(e1), registry.get<char>(e3));
ASSERT_NE(&registry.get<int>(e1), &registry.get<int>(e3));
ASSERT_NE(&registry.get<char>(e1), &registry.get<char>(e3));
ASSERT_TRUE(registry.has<int>(e2));
ASSERT_TRUE(registry.has<char>(e2));
ASSERT_EQ(registry.get<int>(e0), 42);
ASSERT_EQ(registry.get<char>(e0), 'c');
ASSERT_NO_THROW(registry.replace<int>(e1, 0));
ASSERT_EQ(registry.get<int>(e1), 0);
ASSERT_EQ(std::get<0>(registry.get<int, char>(e0)), 42);
ASSERT_EQ(std::get<1>(static_cast<const entt::DefaultRegistry &>(registry).get<int, char>(e0)), 'c');
ASSERT_NO_THROW(registry.accomodate<int>(e1, 1));
ASSERT_NO_THROW(registry.accomodate<int>(e2, 1));
ASSERT_EQ(registry.get<int>(e0), registry.get<int>(e2));
ASSERT_EQ(registry.get<char>(e0), registry.get<char>(e2));
ASSERT_NE(&registry.get<int>(e0), &registry.get<int>(e2));
ASSERT_NE(&registry.get<char>(e0), &registry.get<char>(e2));
ASSERT_NO_THROW(registry.replace<int>(e0, 0));
ASSERT_EQ(registry.get<int>(e0), 0);
ASSERT_NO_THROW(registry.accommodate<int>(e0, 1));
ASSERT_NO_THROW(registry.accommodate<int>(e1, 1));
ASSERT_EQ(static_cast<const entt::DefaultRegistry &>(registry).get<int>(e0), 1);
ASSERT_EQ(static_cast<const entt::DefaultRegistry &>(registry).get<int>(e1), 1);
ASSERT_EQ(static_cast<const entt::DefaultRegistry &>(registry).get<int>(e2), 1);
ASSERT_EQ(registry.size(), entt::DefaultRegistry::size_type{3});
ASSERT_FALSE(registry.empty());
ASSERT_EQ(registry.version(e3), entt::DefaultRegistry::version_type{0});
ASSERT_EQ(registry.current(e3), entt::DefaultRegistry::version_type{0});
ASSERT_EQ(registry.version(e2), entt::DefaultRegistry::version_type{0});
ASSERT_EQ(registry.current(e2), entt::DefaultRegistry::version_type{0});
ASSERT_EQ(registry.capacity(), entt::DefaultRegistry::size_type{3});
ASSERT_NO_THROW(registry.destroy(e3));
ASSERT_NO_THROW(registry.destroy(e2));
ASSERT_EQ(registry.capacity(), entt::DefaultRegistry::size_type{3});
ASSERT_EQ(registry.version(e3), entt::DefaultRegistry::version_type{0});
ASSERT_EQ(registry.current(e3), entt::DefaultRegistry::version_type{1});
ASSERT_EQ(registry.version(e2), entt::DefaultRegistry::version_type{0});
ASSERT_EQ(registry.current(e2), entt::DefaultRegistry::version_type{1});
ASSERT_TRUE(registry.valid(e0));
ASSERT_TRUE(registry.fast(e0));
ASSERT_TRUE(registry.valid(e1));
ASSERT_TRUE(registry.valid(e2));
ASSERT_FALSE(registry.valid(e3));
ASSERT_TRUE(registry.fast(e1));
ASSERT_FALSE(registry.valid(e2));
ASSERT_FALSE(registry.fast(e2));
ASSERT_EQ(registry.size(), entt::DefaultRegistry::size_type{2});
ASSERT_FALSE(registry.empty());
@@ -119,24 +127,58 @@ TEST(DefaultRegistry, Functionalities) {
ASSERT_TRUE(registry.empty<int>());
ASSERT_TRUE(registry.empty<char>());
e1 = registry.create<int>();
e2 = registry.create();
e0 = registry.create<int>();
e1 = registry.create();
ASSERT_NO_THROW(registry.reset<int>(e0));
ASSERT_NO_THROW(registry.reset<int>(e1));
ASSERT_NO_THROW(registry.reset<int>(e2));
ASSERT_EQ(registry.size<int>(), entt::DefaultRegistry::size_type{0});
ASSERT_EQ(registry.size<char>(), entt::DefaultRegistry::size_type{0});
ASSERT_TRUE(registry.empty<int>());
}
TEST(DefaultRegistry, CreateDestroyCornerCase) {
entt::DefaultRegistry registry;
auto e0 = registry.create();
auto e1 = registry.create();
registry.destroy(e0);
registry.destroy(e1);
registry.each([](auto) { FAIL(); });
ASSERT_EQ(registry.current(e0), entt::DefaultRegistry::version_type{1});
ASSERT_EQ(registry.current(e1), entt::DefaultRegistry::version_type{1});
}
TEST(DefaultRegistry, VersionOverflow) {
entt::DefaultRegistry registry;
auto entity = registry.create();
registry.destroy(entity);
ASSERT_EQ(registry.version(entity), entt::DefaultRegistry::version_type{});
for(auto i = entt::entt_traits<entt::DefaultRegistry::entity_type>::version_mask; i; --i) {
ASSERT_NE(registry.current(entity), registry.version(entity));
registry.destroy(registry.create());
}
ASSERT_EQ(registry.current(entity), registry.version(entity));
}
TEST(DefaultRegistry, Each) {
entt::DefaultRegistry registry;
entt::DefaultRegistry::size_type tot;
entt::DefaultRegistry::size_type match;
registry.create();
registry.create<int>();
registry.create();
registry.create<int>();
registry.create();
tot = 0u;
match = 0u;
@@ -147,7 +189,22 @@ TEST(DefaultRegistry, Each) {
++tot;
});
ASSERT_EQ(tot, 2u);
ASSERT_EQ(tot, 5u);
ASSERT_EQ(match, 2u);
tot = 0u;
match = 0u;
registry.each([&](auto entity) {
if(registry.has<int>(entity)) {
registry.destroy(entity);
++match;
}
++tot;
});
ASSERT_EQ(tot, 10u);
ASSERT_EQ(match, 2u);
tot = 0u;
@@ -159,21 +216,35 @@ TEST(DefaultRegistry, Each) {
++tot;
});
ASSERT_EQ(tot, 4u);
ASSERT_EQ(match, 2u);
tot = 0u;
match = 0u;
registry.each([&](auto entity) {
if(registry.has<int>(entity)) { ++match; }
++tot;
});
ASSERT_EQ(tot, 4u);
ASSERT_EQ(tot, 8u);
ASSERT_EQ(match, 0u);
registry.each([&](auto) { FAIL(); });
}
TEST(DefaultRegistry, Orphans) {
entt::DefaultRegistry registry;
entt::DefaultRegistry::size_type tot{};
registry.create<int>();
registry.create();
registry.create<int>();
registry.create();
registry.attach<double>(registry.create());
registry.orphans([&](auto) { ++tot; });
ASSERT_EQ(tot, 2u);
tot = 0u;
registry.each([&](auto entity) { registry.reset<int>(entity); });
registry.orphans([&](auto) { ++tot; });
ASSERT_EQ(tot, 4u);
registry.reset();
tot = 0u;
registry.orphans([&](auto) { ++tot; });
ASSERT_EQ(tot, 0u);
}
TEST(DefaultRegistry, Types) {
entt::DefaultRegistry registry;
@@ -187,18 +258,34 @@ TEST(DefaultRegistry, Types) {
TEST(DefaultRegistry, CreateDestroyEntities) {
entt::DefaultRegistry registry;
entt::DefaultRegistry::entity_type pre{}, post{};
auto pre = registry.create<double>();
registry.destroy(pre);
auto post = registry.create<double>();
for(int i = 0; i < 10; ++i) {
registry.create<double>();
}
registry.reset();
for(int i = 0; i < 7; ++i) {
auto entity = registry.create<int>();
if(i == 3) { pre = entity; }
}
registry.reset();
for(int i = 0; i < 5; ++i) {
auto entity = registry.create();
if(i == 3) { post = entity; }
}
ASSERT_FALSE(registry.valid(pre));
ASSERT_TRUE(registry.valid(post));
ASSERT_NE(registry.version(pre), registry.version(post));
ASSERT_EQ(registry.version(pre) + 1, registry.version(post));
ASSERT_EQ(registry.current(pre), registry.current(post));
}
TEST(DefaultRegistry, AttachRemoveTags) {
TEST(DefaultRegistry, AttachSetRemoveTags) {
entt::DefaultRegistry registry;
const auto &cregistry = registry;
@@ -212,6 +299,21 @@ TEST(DefaultRegistry, AttachRemoveTags) {
ASSERT_EQ(cregistry.get<int>(), 42);
ASSERT_EQ(registry.attachee<int>(), entity);
registry.set<int>(3);
ASSERT_TRUE(registry.has<int>());
ASSERT_EQ(registry.get<int>(), 3);
ASSERT_EQ(cregistry.get<int>(), 3);
ASSERT_EQ(registry.attachee<int>(), entity);
auto other = registry.create();
registry.move<int>(other);
ASSERT_TRUE(registry.has<int>());
ASSERT_EQ(registry.get<int>(), 3);
ASSERT_EQ(cregistry.get<int>(), 3);
ASSERT_EQ(registry.attachee<int>(), other);
registry.remove<int>();
ASSERT_FALSE(registry.has<int>());
@@ -359,3 +461,50 @@ TEST(DefaultRegistry, ComponentsWithTypesFromStandardTemplateLibrary) {
registry.assign<std::unordered_set<int>>(entity).insert(42);
registry.destroy(entity);
}
TEST(DefaultRegistry, ConstructWithComponents) {
// it should compile, that's all
entt::DefaultRegistry registry;
const auto value = 0;
registry.create(value);
}
TEST(DefaultRegistry, MergeTwoRegistries) {
using entity_type = entt::DefaultRegistry::entity_type;
entt::DefaultRegistry src;
entt::DefaultRegistry dst;
std::unordered_map<entity_type, entity_type> ref;
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));
} else {
using component_type = std::decay_t<decltype(component)>;
dst.template assign<component_type>(ref[entity], component);
}
});
};
src.create<int, float, double>();
src.create<char, float, int>();
dst.create<int, char, double>();
dst.create<float, int>();
auto eq = [](auto begin, auto end) { ASSERT_EQ(begin, end); };
auto ne = [](auto begin, auto end) { ASSERT_NE(begin, end); };
eq(dst.view<int, float, double>().begin(), dst.view<int, float, double>().end());
eq(dst.view<char, float, int>().begin(), dst.view<char, float, int>().end());
merge(src.view<int>(), dst);
merge(src.view<char>(), dst);
merge(src.view<double>(), dst);
merge(src.view<float>(), dst);
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());
}

View File

@@ -0,0 +1,489 @@
#include <tuple>
#include <queue>
#include <vector>
#include <gtest/gtest.h>
#include <entt/entity/registry.hpp>
template<typename Storage>
struct OutputArchive {
OutputArchive(Storage &storage)
: storage{storage}
{}
template<typename Value>
void operator()(const Value &value) {
std::get<std::queue<Value>>(storage).push(value);
}
private:
Storage &storage;
};
template<typename Storage>
struct InputArchive {
InputArchive(Storage &storage)
: storage{storage}
{}
template<typename Value>
void operator()(Value &value) {
auto &queue = std::get<std::queue<Value>>(storage);
value = queue.front();
queue.pop();
}
private:
Storage &storage;
};
struct AComponent {};
struct AnotherComponent {
int key;
int value;
};
struct Foo {
entt::DefaultRegistry::entity_type bar;
std::vector<entt::DefaultRegistry::entity_type> quux;
};
TEST(Snapshot, Dump) {
entt::DefaultRegistry registry;
auto e0 = registry.create();
registry.assign<int>(e0, 42);
registry.assign<char>(e0, 'c');
registry.assign<double>(e0, .1);
auto e1 = registry.create();
auto e2 = registry.create();
registry.assign<int>(e2, 3);
auto e3 = registry.create();
registry.assign<char>(e3, '0');
registry.attach<float>(e3, .3f);
auto e4 = registry.create();
registry.attach<AComponent>(e4);
registry.destroy(e1);
auto v1 = registry.current(e1);
using storage_type = std::tuple<
std::queue<entt::DefaultRegistry::entity_type>,
std::queue<int>,
std::queue<char>,
std::queue<double>,
std::queue<float>,
std::queue<bool>,
std::queue<AComponent>,
std::queue<AnotherComponent>,
std::queue<Foo>
>;
storage_type storage;
OutputArchive<storage_type> output{storage};
InputArchive<storage_type> input{storage};
registry.snapshot()
.entities(output)
.destroyed(output)
.component<int, char, AnotherComponent, double>(output)
.tag<float, bool, AComponent>(output);
registry.reset();
ASSERT_FALSE(registry.valid(e0));
ASSERT_FALSE(registry.valid(e1));
ASSERT_FALSE(registry.valid(e2));
ASSERT_FALSE(registry.valid(e3));
ASSERT_FALSE(registry.valid(e4));
registry.restore()
.entities(input)
.destroyed(input)
.component<int, char, AnotherComponent, double>(input)
.tag<float, bool, AComponent>(input)
.orphans();
ASSERT_TRUE(registry.valid(e0));
ASSERT_FALSE(registry.valid(e1));
ASSERT_TRUE(registry.valid(e2));
ASSERT_TRUE(registry.valid(e3));
ASSERT_TRUE(registry.valid(e4));
ASSERT_FALSE(registry.orphan(e0));
ASSERT_FALSE(registry.orphan(e2));
ASSERT_FALSE(registry.orphan(e3));
ASSERT_FALSE(registry.orphan(e4));
ASSERT_EQ(registry.get<int>(e0), 42);
ASSERT_EQ(registry.get<char>(e0), 'c');
ASSERT_EQ(registry.get<double>(e0), .1);
ASSERT_EQ(registry.current(e1), v1);
ASSERT_EQ(registry.get<int>(e2), 3);
ASSERT_EQ(registry.get<char>(e3), '0');
ASSERT_TRUE(registry.has<float>());
ASSERT_EQ(registry.attachee<float>(), e3);
ASSERT_EQ(registry.get<float>(), .3f);
ASSERT_TRUE(registry.has<AComponent>());
ASSERT_EQ(registry.attachee<AComponent>(), e4);
ASSERT_TRUE(registry.empty<AnotherComponent>());
ASSERT_FALSE(registry.has<long int>());
}
TEST(Snapshot, Partial) {
entt::DefaultRegistry registry;
auto e0 = registry.create();
registry.assign<int>(e0, 42);
registry.assign<char>(e0, 'c');
registry.assign<double>(e0, .1);
auto e1 = registry.create();
auto e2 = registry.create();
registry.assign<int>(e2, 3);
auto e3 = registry.create();
registry.assign<char>(e3, '0');
registry.attach<float>(e3, .3f);
auto e4 = registry.create();
registry.attach<AComponent>(e4);
registry.destroy(e1);
auto v1 = registry.current(e1);
using storage_type = std::tuple<
std::queue<entt::DefaultRegistry::entity_type>,
std::queue<int>,
std::queue<char>,
std::queue<double>,
std::queue<float>,
std::queue<bool>,
std::queue<AComponent>,
std::queue<Foo>
>;
storage_type storage;
OutputArchive<storage_type> output{storage};
InputArchive<storage_type> input{storage};
registry.snapshot()
.entities(output)
.destroyed(output)
.component<char, int>(output)
.tag<bool, float>(output);
registry.reset();
ASSERT_FALSE(registry.valid(e0));
ASSERT_FALSE(registry.valid(e1));
ASSERT_FALSE(registry.valid(e2));
ASSERT_FALSE(registry.valid(e3));
ASSERT_FALSE(registry.valid(e4));
registry.restore()
.entities(input)
.destroyed(input)
.component<char, int>(input)
.tag<bool, float>(input);
ASSERT_TRUE(registry.valid(e0));
ASSERT_FALSE(registry.valid(e1));
ASSERT_TRUE(registry.valid(e2));
ASSERT_TRUE(registry.valid(e3));
ASSERT_TRUE(registry.valid(e4));
ASSERT_EQ(registry.get<int>(e0), 42);
ASSERT_EQ(registry.get<char>(e0), 'c');
ASSERT_FALSE(registry.has<double>(e0));
ASSERT_EQ(registry.current(e1), v1);
ASSERT_EQ(registry.get<int>(e2), 3);
ASSERT_EQ(registry.get<char>(e3), '0');
ASSERT_TRUE(registry.orphan(e4));
ASSERT_TRUE(registry.has<float>());
ASSERT_EQ(registry.attachee<float>(), e3);
ASSERT_EQ(registry.get<float>(), .3f);
ASSERT_FALSE(registry.has<long int>());
registry.snapshot()
.tag<float>(output)
.destroyed(output)
.entities(output);
registry.reset();
ASSERT_FALSE(registry.valid(e0));
ASSERT_FALSE(registry.valid(e1));
ASSERT_FALSE(registry.valid(e2));
ASSERT_FALSE(registry.valid(e3));
ASSERT_FALSE(registry.valid(e4));
registry.restore()
.tag<float>(input)
.destroyed(input)
.entities(input)
.orphans();
ASSERT_FALSE(registry.valid(e0));
ASSERT_FALSE(registry.valid(e1));
ASSERT_FALSE(registry.valid(e2));
ASSERT_TRUE(registry.valid(e3));
ASSERT_FALSE(registry.valid(e4));
}
TEST(Snapshot, Continuous) {
using entity_type = entt::DefaultRegistry::entity_type;
entt::DefaultRegistry src;
entt::DefaultRegistry dst;
entt::ContinuousLoader<entity_type> loader{dst};
std::vector<entity_type> entities;
entity_type entity;
using storage_type = std::tuple<
std::queue<entity_type>,
std::queue<AComponent>,
std::queue<AnotherComponent>,
std::queue<Foo>,
std::queue<double>
>;
storage_type storage;
OutputArchive<storage_type> output{storage};
InputArchive<storage_type> input{storage};
for(int i = 0; i < 10; ++i) {
src.create();
}
src.each([&src](auto entity) {
src.destroy(entity);
});
for(int i = 0; i < 5; ++i) {
entity = src.create();
entities.push_back(entity);
src.assign<AComponent>(entity);
src.assign<AnotherComponent>(entity, i, i);
if(i % 2) {
src.assign<Foo>(entity, entity);
} else if(i == 2) {
src.attach<double>(entity, .3);
}
}
src.view<Foo>().each([&entities](auto, auto &foo) {
foo.quux.insert(foo.quux.begin(), entities.begin(), entities.end());
});
entity = dst.create();
dst.assign<AComponent>(entity);
dst.assign<AnotherComponent>(entity, -1, -1);
src.snapshot()
.entities(output)
.destroyed(output)
.component<AComponent, AnotherComponent, Foo>(output)
.tag<double>(output);
loader.entities(input)
.destroyed(input)
.component<AComponent, AnotherComponent>(input)
.component<Foo>(input, &Foo::bar, &Foo::quux)
.tag<double>(input)
.orphans();
decltype(dst.size()) aComponentCnt{};
decltype(dst.size()) anotherComponentCnt{};
decltype(dst.size()) fooCnt{};
dst.each([&dst, &aComponentCnt](auto entity) {
ASSERT_TRUE(dst.has<AComponent>(entity));
++aComponentCnt;
});
dst.view<AnotherComponent>().each([&anotherComponentCnt](auto, const auto &component) {
ASSERT_EQ(component.value, component.key < 0 ? -1 : component.key);
++anotherComponentCnt;
});
dst.view<Foo>().each([&dst, &fooCnt](auto entity, const auto &component) {
ASSERT_EQ(entity, component.bar);
for(auto entity: component.quux) {
ASSERT_TRUE(dst.valid(entity));
}
++fooCnt;
});
ASSERT_TRUE(dst.has<double>());
ASSERT_EQ(dst.get<double>(), .3);
src.view<AnotherComponent>().each([](auto, auto &component) {
component.value = 2 * component.key;
});
auto size = dst.size();
src.snapshot()
.entities(output)
.destroyed(output)
.component<AComponent, AnotherComponent, Foo>(output)
.tag<double>(output);
loader.entities(input)
.destroyed(input)
.component<AComponent, AnotherComponent>(input)
.component<Foo>(input, &Foo::bar, &Foo::quux)
.tag<double>(input)
.orphans();
ASSERT_EQ(size, dst.size());
ASSERT_EQ(dst.size<AComponent>(), aComponentCnt);
ASSERT_EQ(dst.size<AnotherComponent>(), anotherComponentCnt);
ASSERT_EQ(dst.size<Foo>(), fooCnt);
ASSERT_TRUE(dst.has<double>());
dst.view<AnotherComponent>().each([](auto, auto &component) {
ASSERT_EQ(component.value, component.key < 0 ? -1 : (2 * component.key));
});
entity = src.create();
src.view<Foo>().each([entity](auto, auto &component) {
component.bar = entity;
});
src.snapshot()
.entities(output)
.destroyed(output)
.component<AComponent, AnotherComponent, Foo>(output)
.tag<double>(output);
loader.entities(input)
.destroyed(input)
.component<AComponent, AnotherComponent>(input)
.component<Foo>(input, &Foo::bar, &Foo::quux)
.tag<double>(input)
.orphans();
dst.view<Foo>().each([&loader, entity](auto, auto &component) {
ASSERT_EQ(component.bar, loader.map(entity));
});
entities.clear();
for(auto entity: src.view<AComponent>()) {
entities.push_back(entity);
}
src.destroy(entity);
loader.shrink();
src.snapshot()
.entities(output)
.destroyed(output)
.component<AComponent, AnotherComponent, Foo>(output)
.tag<double>(output);
loader.entities(input)
.destroyed(input)
.component<AComponent, AnotherComponent>(input)
.component<Foo>(input, &Foo::bar, &Foo::quux)
.tag<double>(input)
.orphans()
.shrink();
dst.view<Foo>().each([&dst, &loader, entity](auto, auto &component) {
ASSERT_FALSE(dst.valid(component.bar));
});
ASSERT_FALSE(loader.has(entity));
entity = src.create();
src.view<Foo>().each([entity](auto, auto &component) {
component.bar = entity;
});
dst.reset<AComponent>();
aComponentCnt = src.size<AComponent>();
src.snapshot()
.entities(output)
.destroyed(output)
.component<AComponent, AnotherComponent, Foo>(output)
.tag<double>(output);
loader.entities(input)
.destroyed(input)
.component<AComponent, AnotherComponent>(input)
.component<Foo>(input, &Foo::bar, &Foo::quux)
.tag<double>(input)
.orphans();
ASSERT_EQ(dst.size<AComponent>(), aComponentCnt);
ASSERT_TRUE(dst.has<double>());
src.reset<AComponent>();
src.remove<double>();
aComponentCnt = {};
src.snapshot()
.entities(output)
.destroyed(output)
.component<AComponent, AnotherComponent, Foo>(output)
.tag<double>(output);
loader.entities(input)
.destroyed(input)
.component<AComponent, AnotherComponent>(input)
.component<Foo>(input, &Foo::bar, &Foo::quux)
.tag<double>(input)
.orphans();
ASSERT_EQ(dst.size<AComponent>(), aComponentCnt);
ASSERT_FALSE(dst.has<double>());
}
TEST(Snapshot, ContinuousMoreOnShrink) {
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<AComponent>
>;
storage_type storage;
OutputArchive<storage_type> output{storage};
InputArchive<storage_type> input{storage};
auto entity = src.create();
src.snapshot().entities(output);
loader.entities(input).shrink();
ASSERT_TRUE(dst.valid(entity));
loader.shrink();
ASSERT_FALSE(dst.valid(entity));
}

View File

@@ -1,3 +1,4 @@
#include <unordered_set>
#include <gtest/gtest.h>
#include <entt/entity/sparse_set.hpp>
@@ -20,6 +21,7 @@ TEST(SparseSetNoType, Functionalities) {
ASSERT_NE(set.begin(), set.end());
ASSERT_FALSE(set.has(0));
ASSERT_TRUE(set.has(42));
ASSERT_TRUE(set.fast(42));
ASSERT_EQ(set.get(42), 0u);
set.destroy(42);
@@ -62,19 +64,183 @@ TEST(SparseSetNoType, DataBeginEnd) {
ASSERT_EQ(*(set.data() + 1u), 12u);
ASSERT_EQ(*(set.data() + 2u), 42u);
auto it = set.begin();
ASSERT_EQ(*it, 42u);
ASSERT_EQ(*(it+1), 12u);
ASSERT_EQ(*(it+2), 3u);
ASSERT_EQ(it += 3, set.end());
auto begin = set.begin();
auto end = set.end();
ASSERT_EQ(*(begin++), 42u);
ASSERT_EQ(*(begin++), 12u);
ASSERT_EQ(*(begin++), 3u);
ASSERT_EQ(begin, end);
}
TEST(SparseSetWithType, AggregatesMustWork) {
struct AggregateType { int value; };
// the goal of this test is to enforce the requirements for aggregate types
entt::SparseSet<unsigned int, AggregateType>{}.construct(0, 42);
TEST(SparseSetNoType, RespectDisjoint) {
entt::SparseSet<unsigned int> lhs;
entt::SparseSet<unsigned int> rhs;
const auto &clhs = lhs;
lhs.construct(3);
lhs.construct(12);
lhs.construct(42);
ASSERT_EQ(lhs.get(3), 0u);
ASSERT_EQ(lhs.get(12), 1u);
ASSERT_EQ(lhs.get(42), 2u);
lhs.respect(rhs);
ASSERT_EQ(clhs.get(3), 0u);
ASSERT_EQ(clhs.get(12), 1u);
ASSERT_EQ(clhs.get(42), 2u);
}
TEST(SparseSetNoType, RespectOverlap) {
entt::SparseSet<unsigned int> lhs;
entt::SparseSet<unsigned int> rhs;
const auto &clhs = lhs;
lhs.construct(3);
lhs.construct(12);
lhs.construct(42);
rhs.construct(12);
ASSERT_EQ(lhs.get(3), 0u);
ASSERT_EQ(lhs.get(12), 1u);
ASSERT_EQ(lhs.get(42), 2u);
lhs.respect(rhs);
ASSERT_EQ(clhs.get(3), 0u);
ASSERT_EQ(clhs.get(12), 2u);
ASSERT_EQ(clhs.get(42), 1u);
}
TEST(SparseSetNoType, RespectOrdered) {
entt::SparseSet<unsigned int> lhs;
entt::SparseSet<unsigned int> rhs;
lhs.construct(1);
lhs.construct(2);
lhs.construct(3);
lhs.construct(4);
lhs.construct(5);
ASSERT_EQ(lhs.get(1), 0u);
ASSERT_EQ(lhs.get(2), 1u);
ASSERT_EQ(lhs.get(3), 2u);
ASSERT_EQ(lhs.get(4), 3u);
ASSERT_EQ(lhs.get(5), 4u);
rhs.construct(6);
rhs.construct(1);
rhs.construct(2);
rhs.construct(3);
rhs.construct(4);
rhs.construct(5);
ASSERT_EQ(rhs.get(6), 0u);
ASSERT_EQ(rhs.get(1), 1u);
ASSERT_EQ(rhs.get(2), 2u);
ASSERT_EQ(rhs.get(3), 3u);
ASSERT_EQ(rhs.get(4), 4u);
ASSERT_EQ(rhs.get(5), 5u);
rhs.respect(lhs);
ASSERT_EQ(rhs.get(6), 0u);
ASSERT_EQ(rhs.get(1), 1u);
ASSERT_EQ(rhs.get(2), 2u);
ASSERT_EQ(rhs.get(3), 3u);
ASSERT_EQ(rhs.get(4), 4u);
ASSERT_EQ(rhs.get(5), 5u);
}
TEST(SparseSetNoType, RespectReverse) {
entt::SparseSet<unsigned int> lhs;
entt::SparseSet<unsigned int> rhs;
lhs.construct(1);
lhs.construct(2);
lhs.construct(3);
lhs.construct(4);
lhs.construct(5);
ASSERT_EQ(lhs.get(1), 0u);
ASSERT_EQ(lhs.get(2), 1u);
ASSERT_EQ(lhs.get(3), 2u);
ASSERT_EQ(lhs.get(4), 3u);
ASSERT_EQ(lhs.get(5), 4u);
rhs.construct(5);
rhs.construct(4);
rhs.construct(3);
rhs.construct(2);
rhs.construct(1);
rhs.construct(6);
ASSERT_EQ(rhs.get(5), 0u);
ASSERT_EQ(rhs.get(4), 1u);
ASSERT_EQ(rhs.get(3), 2u);
ASSERT_EQ(rhs.get(2), 3u);
ASSERT_EQ(rhs.get(1), 4u);
ASSERT_EQ(rhs.get(6), 5u);
rhs.respect(lhs);
ASSERT_EQ(rhs.get(6), 0u);
ASSERT_EQ(rhs.get(1), 1u);
ASSERT_EQ(rhs.get(2), 2u);
ASSERT_EQ(rhs.get(3), 3u);
ASSERT_EQ(rhs.get(4), 4u);
ASSERT_EQ(rhs.get(5), 5u);
}
TEST(SparseSetNoType, RespectUnordered) {
entt::SparseSet<unsigned int> lhs;
entt::SparseSet<unsigned int> rhs;
lhs.construct(1);
lhs.construct(2);
lhs.construct(3);
lhs.construct(4);
lhs.construct(5);
ASSERT_EQ(lhs.get(1), 0u);
ASSERT_EQ(lhs.get(2), 1u);
ASSERT_EQ(lhs.get(3), 2u);
ASSERT_EQ(lhs.get(4), 3u);
ASSERT_EQ(lhs.get(5), 4u);
rhs.construct(3);
rhs.construct(2);
rhs.construct(6);
rhs.construct(1);
rhs.construct(4);
rhs.construct(5);
ASSERT_EQ(rhs.get(3), 0u);
ASSERT_EQ(rhs.get(2), 1u);
ASSERT_EQ(rhs.get(6), 2u);
ASSERT_EQ(rhs.get(1), 3u);
ASSERT_EQ(rhs.get(4), 4u);
ASSERT_EQ(rhs.get(5), 5u);
rhs.respect(lhs);
ASSERT_EQ(rhs.get(6), 0u);
ASSERT_EQ(rhs.get(1), 1u);
ASSERT_EQ(rhs.get(2), 2u);
ASSERT_EQ(rhs.get(3), 3u);
ASSERT_EQ(rhs.get(4), 4u);
ASSERT_EQ(rhs.get(5), 5u);
}
TEST(SparseSetWithType, Functionalities) {
@@ -95,6 +261,7 @@ TEST(SparseSetWithType, Functionalities) {
ASSERT_NE(set.begin(), set.end());
ASSERT_FALSE(set.has(0));
ASSERT_TRUE(set.has(42));
ASSERT_TRUE(set.fast(42));
ASSERT_EQ(set.get(42), 3);
set.destroy(42);
@@ -122,6 +289,19 @@ TEST(SparseSetWithType, Functionalities) {
other = std::move(set);
}
TEST(SparseSetWithType, AggregatesMustWork) {
struct AggregateType { int value; };
// the goal of this test is to enforce the requirements for aggregate types
entt::SparseSet<unsigned int, AggregateType>{}.construct(0, 42);
}
TEST(SparseSetWithType, TypesFromStandardTemplateLibraryMustWork) {
// see #37 - this test shouldn't crash, that's all
entt::SparseSet<unsigned int, std::unordered_set<int>> set;
set.construct(0).insert(42);
set.destroy(0);
}
TEST(SparseSetWithType, RawBeginEnd) {
entt::SparseSet<unsigned int, int> set;
@@ -140,9 +320,9 @@ TEST(SparseSetWithType, RawBeginEnd) {
auto begin = set.begin();
auto end = set.end();
ASSERT_EQ(set.get(*(begin++)), 9);
ASSERT_EQ(set.get(*(begin++)), 6);
ASSERT_EQ(set.get(*(begin++)), 3);
ASSERT_EQ(*(begin++), 9);
ASSERT_EQ(*(begin++), 6);
ASSERT_EQ(*(begin++), 3);
ASSERT_EQ(begin, end);
}
@@ -174,11 +354,11 @@ TEST(SparseSetWithType, SortOrdered) {
auto begin = set.begin();
auto end = set.end();
ASSERT_EQ(set.get(*(begin++)), 1);
ASSERT_EQ(set.get(*(begin++)), 3);
ASSERT_EQ(set.get(*(begin++)), 6);
ASSERT_EQ(set.get(*(begin++)), 9);
ASSERT_EQ(set.get(*(begin++)), 12);
ASSERT_EQ(*(begin++), 1);
ASSERT_EQ(*(begin++), 3);
ASSERT_EQ(*(begin++), 6);
ASSERT_EQ(*(begin++), 9);
ASSERT_EQ(*(begin++), 12);
ASSERT_EQ(begin, end);
}
@@ -210,11 +390,11 @@ TEST(SparseSetWithType, SortReverse) {
auto begin = set.begin();
auto end = set.end();
ASSERT_EQ(set.get(*(begin++)), 1);
ASSERT_EQ(set.get(*(begin++)), 3);
ASSERT_EQ(set.get(*(begin++)), 6);
ASSERT_EQ(set.get(*(begin++)), 9);
ASSERT_EQ(set.get(*(begin++)), 12);
ASSERT_EQ(*(begin++), 1);
ASSERT_EQ(*(begin++), 3);
ASSERT_EQ(*(begin++), 6);
ASSERT_EQ(*(begin++), 9);
ASSERT_EQ(*(begin++), 12);
ASSERT_EQ(begin, end);
}
@@ -246,11 +426,11 @@ TEST(SparseSetWithType, SortUnordered) {
auto begin = set.begin();
auto end = set.end();
ASSERT_EQ(set.get(*(begin++)), 1);
ASSERT_EQ(set.get(*(begin++)), 3);
ASSERT_EQ(set.get(*(begin++)), 6);
ASSERT_EQ(set.get(*(begin++)), 9);
ASSERT_EQ(set.get(*(begin++)), 12);
ASSERT_EQ(*(begin++), 1);
ASSERT_EQ(*(begin++), 3);
ASSERT_EQ(*(begin++), 6);
ASSERT_EQ(*(begin++), 9);
ASSERT_EQ(*(begin++), 12);
ASSERT_EQ(begin, end);
}
@@ -263,9 +443,9 @@ TEST(SparseSetWithType, RespectDisjoint) {
lhs.construct(12, 6);
lhs.construct(42, 9);
ASSERT_EQ(lhs.get(3), 3);
ASSERT_EQ(lhs.get(12), 6);
ASSERT_EQ(lhs.get(42), 9);
ASSERT_EQ(clhs.get(3), 3);
ASSERT_EQ(clhs.get(12), 6);
ASSERT_EQ(clhs.get(42), 9);
lhs.respect(rhs);
@@ -273,12 +453,12 @@ TEST(SparseSetWithType, RespectDisjoint) {
ASSERT_EQ(*(clhs.raw() + 1u), 6);
ASSERT_EQ(*(clhs.raw() + 2u), 9);
auto begin = clhs.begin();
auto end = clhs.end();
auto begin = lhs.begin();
auto end = lhs.end();
ASSERT_EQ(clhs.get(*(begin++)), 9);
ASSERT_EQ(clhs.get(*(begin++)), 6);
ASSERT_EQ(clhs.get(*(begin++)), 3);
ASSERT_EQ(*(begin++), 9);
ASSERT_EQ(*(begin++), 6);
ASSERT_EQ(*(begin++), 3);
ASSERT_EQ(begin, end);
}
@@ -292,9 +472,9 @@ TEST(SparseSetWithType, RespectOverlap) {
lhs.construct(42, 9);
rhs.construct(12, 6);
ASSERT_EQ(lhs.get(3), 3);
ASSERT_EQ(lhs.get(12), 6);
ASSERT_EQ(lhs.get(42), 9);
ASSERT_EQ(clhs.get(3), 3);
ASSERT_EQ(clhs.get(12), 6);
ASSERT_EQ(clhs.get(42), 9);
ASSERT_EQ(rhs.get(12), 6);
lhs.respect(rhs);
@@ -303,12 +483,12 @@ TEST(SparseSetWithType, RespectOverlap) {
ASSERT_EQ(*(clhs.raw() + 1u), 9);
ASSERT_EQ(*(clhs.raw() + 2u), 6);
auto begin = clhs.begin();
auto end = clhs.end();
auto begin = lhs.begin();
auto end = lhs.end();
ASSERT_EQ(clhs.get(*(begin++)), 6);
ASSERT_EQ(clhs.get(*(begin++)), 9);
ASSERT_EQ(clhs.get(*(begin++)), 3);
ASSERT_EQ(*(begin++), 6);
ASSERT_EQ(*(begin++), 9);
ASSERT_EQ(*(begin++), 3);
ASSERT_EQ(begin, end);
}
@@ -449,3 +629,33 @@ TEST(SparseSetWithType, RespectUnordered) {
ASSERT_EQ(*(rhs.data() + 4u), 4u);
ASSERT_EQ(*(rhs.data() + 5u), 5u);
}
TEST(SparseSetWithType, ReferencesGuaranteed) {
struct Type { int value; };
entt::SparseSet<unsigned int, Type> set;
set.construct(0, 0);
set.construct(1, 1);
ASSERT_EQ(set.get(0).value, 0);
ASSERT_EQ(set.get(1).value, 1);
for(auto &&type: set) {
if(type.value) {
type.value = 42;
}
}
ASSERT_EQ(set.get(0).value, 0);
ASSERT_EQ(set.get(1).value, 42);
auto begin = set.begin();
while(begin != set.end()) {
(begin++)->value = 3;
}
ASSERT_EQ(set.get(0).value, 3);
ASSERT_EQ(set.get(1).value, 3);
}

View File

@@ -5,8 +5,8 @@
TEST(View, SingleComponent) {
entt::DefaultRegistry registry;
auto e1 = registry.create();
auto e2 = registry.create<int, char>();
auto e0 = registry.create();
auto e1 = registry.create<int, char>();
ASSERT_NO_THROW(registry.view<char>().begin()++);
ASSERT_NO_THROW(++registry.view<char>().begin());
@@ -16,30 +16,44 @@ TEST(View, SingleComponent) {
ASSERT_NE(view.begin(), view.end());
ASSERT_EQ(view.size(), typename decltype(view)::size_type{1});
registry.assign<char>(e1);
registry.assign<char>(e0);
ASSERT_EQ(view.size(), typename decltype(view)::size_type{2});
view.get(e1) = '1';
view.get(e2) = '2';
view.get(e0) = '1';
view.get(e1) = '2';
for(auto entity: view) {
const auto &cview = static_cast<const decltype(view) &>(view);
ASSERT_TRUE(cview.get(entity) == '1' || cview.get(entity) == '2');
}
ASSERT_EQ(*(view.data() + 0), e2);
ASSERT_EQ(*(view.data() + 1), e1);
ASSERT_EQ(*(view.data() + 0), e1);
ASSERT_EQ(*(view.data() + 1), e0);
ASSERT_EQ(*(view.raw() + 0), '2');
ASSERT_EQ(*(static_cast<const decltype(view) &>(view).raw() + 1), '1');
registry.remove<char>(e0);
registry.remove<char>(e1);
registry.remove<char>(e2);
ASSERT_EQ(view.begin(), view.end());
}
TEST(View, SingleComponentContains) {
entt::DefaultRegistry registry;
auto e0 = registry.create<int>();
auto e1 = registry.create<int>();
registry.destroy(e0);
auto view = registry.view<int>();
ASSERT_FALSE(view.contains(e0));
ASSERT_TRUE(view.contains(e1));
}
TEST(View, SingleComponentEmpty) {
entt::DefaultRegistry registry;
@@ -78,8 +92,14 @@ TEST(View, SingleComponentEach) {
TEST(View, MultipleComponent) {
entt::DefaultRegistry registry;
auto e1 = registry.create<char>();
auto e2 = registry.create<int, char>();
auto e0 = registry.create<char>();
auto e1 = registry.create<int, char>();
auto it = registry.view<char>().begin();
ASSERT_EQ(*it, e1);
ASSERT_EQ(*(it+1), e0);
ASSERT_EQ(it += 2, registry.view<char>().end());
ASSERT_NO_THROW((registry.view<int, char>().begin()++));
ASSERT_NO_THROW((++registry.view<int, char>().begin()));
@@ -87,10 +107,12 @@ TEST(View, MultipleComponent) {
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});
view.get<char>(e1) = '1';
view.get<char>(e2) = '2';
view.get<int>(e2) = 42;
registry.get<char>(e0) = '1';
registry.get<char>(e1) = '2';
registry.get<int>(e1) = 42;
for(auto entity: view) {
const auto &cview = static_cast<const decltype(view) &>(view);
@@ -99,13 +121,27 @@ TEST(View, MultipleComponent) {
ASSERT_EQ(cview.get<char>(entity), '2');
}
registry.remove<char>(e0);
registry.remove<char>(e1);
registry.remove<char>(e2);
view.reset();
ASSERT_EQ(view.begin(), view.end());
}
TEST(View, MultipleComponentContains) {
entt::DefaultRegistry registry;
auto e0 = registry.create<int, char>();
auto e1 = registry.create<int, char>();
registry.destroy(e0);
auto view = registry.view<int, char>();
ASSERT_FALSE(view.contains(e0));
ASSERT_TRUE(view.contains(e1));
}
TEST(View, MultipleComponentEmpty) {
entt::DefaultRegistry registry;
@@ -143,8 +179,8 @@ TEST(PersistentView, Prepare) {
entt::DefaultRegistry registry;
registry.prepare<int, char>();
auto e1 = registry.create<char>();
auto e2 = registry.create<int, char>();
auto e0 = registry.create<char>();
auto e1 = registry.create<int, char>();
ASSERT_NO_THROW((registry.persistent<int, char>().begin()++));
ASSERT_NO_THROW((++registry.persistent<int, char>().begin()));
@@ -154,17 +190,17 @@ TEST(PersistentView, Prepare) {
ASSERT_NE(view.begin(), view.end());
ASSERT_EQ(view.size(), typename decltype(view)::size_type{1});
registry.assign<int>(e1);
registry.assign<int>(e0);
ASSERT_EQ(view.size(), typename decltype(view)::size_type{2});
registry.remove<int>(e1);
registry.remove<int>(e0);
ASSERT_EQ(view.size(), typename decltype(view)::size_type{1});
view.get<char>(e1) = '1';
view.get<char>(e2) = '2';
view.get<int>(e2) = 42;
registry.get<char>(e0) = '1';
registry.get<char>(e1) = '2';
registry.get<int>(e1) = 42;
for(auto entity: view) {
const auto &cview = static_cast<const decltype(view) &>(view);
@@ -173,10 +209,10 @@ TEST(PersistentView, Prepare) {
ASSERT_EQ(cview.get<char>(entity), '2');
}
ASSERT_EQ(*(view.data() + 0), e2);
ASSERT_EQ(*(view.data() + 0), e1);
registry.remove<char>(e0);
registry.remove<char>(e1);
registry.remove<char>(e2);
ASSERT_EQ(view.begin(), view.end());
}
@@ -184,8 +220,8 @@ TEST(PersistentView, Prepare) {
TEST(PersistentView, NoPrepare) {
entt::DefaultRegistry registry;
auto e1 = registry.create<char>();
auto e2 = registry.create<int, char>();
auto e0 = registry.create<char>();
auto e1 = registry.create<int, char>();
ASSERT_NO_THROW((registry.persistent<int, char>().begin()++));
ASSERT_NO_THROW((++registry.persistent<int, char>().begin()));
@@ -195,17 +231,17 @@ TEST(PersistentView, NoPrepare) {
ASSERT_NE(view.begin(), view.end());
ASSERT_EQ(view.size(), typename decltype(view)::size_type{1});
registry.assign<int>(e1);
registry.assign<int>(e0);
ASSERT_EQ(view.size(), typename decltype(view)::size_type{2});
registry.remove<int>(e1);
registry.remove<int>(e0);
ASSERT_EQ(view.size(), typename decltype(view)::size_type{1});
view.get<char>(e1) = '1';
view.get<char>(e2) = '2';
view.get<int>(e2) = 42;
registry.get<char>(e0) = '1';
registry.get<char>(e1) = '2';
registry.get<int>(e1) = 42;
for(auto entity: view) {
const auto &cview = static_cast<const decltype(view) &>(view);
@@ -214,14 +250,28 @@ TEST(PersistentView, NoPrepare) {
ASSERT_EQ(cview.get<char>(entity), '2');
}
ASSERT_EQ(*(view.data() + 0), e2);
ASSERT_EQ(*(view.data() + 0), e1);
registry.remove<char>(e0);
registry.remove<char>(e1);
registry.remove<char>(e2);
ASSERT_EQ(view.begin(), view.end());
}
TEST(PersistentView, Contains) {
entt::DefaultRegistry registry;
auto e0 = registry.create<int, char>();
auto e1 = registry.create<int, char>();
registry.destroy(e0);
auto view = registry.persistent<int, char>();
ASSERT_FALSE(view.contains(e0));
ASSERT_TRUE(view.contains(e1));
}
TEST(PersistentView, Empty) {
entt::DefaultRegistry registry;
@@ -263,20 +313,20 @@ TEST(PersistentView, Sort) {
entt::DefaultRegistry registry;
registry.prepare<int, unsigned int>();
auto e0 = registry.create();
auto e1 = registry.create();
auto e2 = registry.create();
auto e3 = registry.create();
auto uval = 0u;
auto ival = 0;
registry.assign<unsigned int>(e0, uval++);
registry.assign<unsigned int>(e1, uval++);
registry.assign<unsigned int>(e2, uval++);
registry.assign<unsigned int>(e3, uval++);
registry.assign<int>(e0, ival++);
registry.assign<int>(e1, ival++);
registry.assign<int>(e2, ival++);
registry.assign<int>(e3, ival++);
auto view = registry.persistent<int, unsigned int>();
@@ -293,3 +343,65 @@ TEST(PersistentView, Sort) {
ASSERT_EQ(view.get<int>(entity), ival++);
}
}
TEST(RawView, Functionalities) {
entt::DefaultRegistry registry;
auto e0 = registry.create();
auto e1 = registry.create<int, char>();
ASSERT_NO_THROW(registry.raw<char>().begin()++);
ASSERT_NO_THROW(++registry.raw<char>().begin());
auto view = registry.raw<char>();
ASSERT_NE(view.begin(), view.end());
ASSERT_EQ(view.size(), typename decltype(view)::size_type{1});
registry.assign<char>(e0);
ASSERT_EQ(view.size(), typename decltype(view)::size_type{2});
registry.get<char>(e0) = '1';
registry.get<char>(e1) = '2';
for(auto &&component: view) {
ASSERT_TRUE(component == '1' || component == '2');
}
ASSERT_EQ(*(view.data() + 0), e1);
ASSERT_EQ(*(view.data() + 1), e0);
ASSERT_EQ(*(view.raw() + 0), '2');
ASSERT_EQ(*(static_cast<const decltype(view) &>(view).raw() + 1), '1');
for(auto &&component: view) {
// verifies that iterators return references to components
component = '0';
}
for(auto &&component: view) {
ASSERT_TRUE(component == '0');
}
registry.remove<char>(e0);
registry.remove<char>(e1);
ASSERT_EQ(view.begin(), view.end());
}
TEST(RawView, Empty) {
entt::DefaultRegistry registry;
registry.create<char, double>();
registry.create<char>();
auto view = registry.raw<int>();
ASSERT_EQ(view.size(), entt::DefaultRegistry::size_type{0});
for(auto &&component: view) {
(void)component;
FAIL();
}
}

View File

@@ -27,6 +27,7 @@ TEST(ResourceCache, Functionalities) {
ASSERT_FALSE(cache.contains(hs2));
ASSERT_FALSE(cache.load<BrokenLoader>(hs1, 42));
ASSERT_FALSE(cache.reload<BrokenLoader>(hs1, 42));
ASSERT_EQ(cache.size(), entt::ResourceCache<Resource>::size_type{});
ASSERT_TRUE(cache.empty());
@@ -34,6 +35,7 @@ TEST(ResourceCache, Functionalities) {
ASSERT_FALSE(cache.contains(hs2));
ASSERT_TRUE(cache.load<Loader>(hs1, 42));
ASSERT_TRUE(cache.reload<Loader>(hs1, 42));
ASSERT_NE(cache.size(), entt::ResourceCache<Resource>::size_type{});
ASSERT_FALSE(cache.empty());

View File

@@ -86,6 +86,21 @@ TEST(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;

View File

@@ -91,6 +91,21 @@ TEST(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;

View File

@@ -2,8 +2,8 @@
#include <cassert>
#include <map>
#include <string>
#include <duktape.h>
#include <entt/entity/registry.hpp>
#include "duktape.h"
template<typename Type>
struct tag { using type = Type; };
@@ -22,7 +22,7 @@ struct DuktapeRuntime {
template<typename Comp>
duk_ret_t set(duk_context *ctx, entt::DefaultRegistry &registry) {
const auto entity = duk_require_uint(ctx, 0);
registry.accomodate<Comp>(entity);
registry.accommodate<Comp>(entity);
return 0;
}
@@ -31,7 +31,7 @@ duk_ret_t set<Position>(duk_context *ctx, entt::DefaultRegistry &registry) {
const auto entity = duk_require_uint(ctx, 0);
const auto x = duk_require_number(ctx, 2);
const auto y = duk_require_number(ctx, 3);
registry.accomodate<Position>(entity, x, y);
registry.accommodate<Position>(entity, x, y);
return 0;
}

182
test/snapshot/snapshot.cpp Normal file
View File

@@ -0,0 +1,182 @@
#include <gtest/gtest.h>
#include <sstream>
#include <vector>
#include <cereal/archives/json.hpp>
#include <entt/entity/registry.hpp>
struct Position {
float x;
float y;
};
struct Timer {
int duration;
int elapsed{0};
};
struct Relationship {
entt::DefaultRegistry::entity_type parent;
};
template<class Archive>
void serialize(Archive &archive, Position &position) {
archive(position.x, position.y);
}
template<class Archive>
void serialize(Archive &archive, Timer &timer) {
archive(timer.duration);
}
template<class Archive>
void serialize(Archive &archive, Relationship &relationship) {
archive(relationship.parent);
}
TEST(Snapshot, Full) {
std::stringstream storage;
entt::DefaultRegistry source;
entt::DefaultRegistry destination;
auto e0 = source.create();
source.assign<Position>(e0, 16.f, 16.f);
source.destroy(source.create());
auto e1 = source.create();
source.assign<Position>(e1, .8f, .0f);
source.assign<Relationship>(e1, e0);
auto e2 = source.create();
auto e3 = source.create();
source.assign<Timer>(e3, 1000, 100);
source.destroy(e2);
auto v2 = source.current(e2);
{
// output finishes flushing its contents when it goes out of scope
cereal::JSONOutputArchive output{storage};
source.snapshot().entities(output).destroyed(output)
.component<Position, Timer, Relationship>(output);
}
cereal::JSONInputArchive input{storage};
destination.restore().entities(input).destroyed(input)
.component<Position, Timer, Relationship>(input);
ASSERT_TRUE(destination.valid(e0));
ASSERT_TRUE(destination.has<Position>(e0));
ASSERT_EQ(destination.get<Position>(e0).x, 16.f);
ASSERT_EQ(destination.get<Position>(e0).y, 16.f);
ASSERT_TRUE(destination.valid(e1));
ASSERT_TRUE(destination.has<Position>(e1));
ASSERT_EQ(destination.get<Position>(e1).x, .8f);
ASSERT_EQ(destination.get<Position>(e1).y, .0f);
ASSERT_TRUE(destination.has<Relationship>(e1));
ASSERT_EQ(destination.get<Relationship>(e1).parent, e0);
ASSERT_FALSE(destination.valid(e2));
ASSERT_EQ(destination.current(e2), v2);
ASSERT_TRUE(destination.valid(e3));
ASSERT_TRUE(destination.has<Timer>(e3));
ASSERT_EQ(destination.get<Timer>(e3).duration, 1000);
ASSERT_EQ(destination.get<Timer>(e3).elapsed, 0);
}
TEST(Snapshot, Continuous) {
std::stringstream storage;
entt::DefaultRegistry source;
entt::DefaultRegistry destination;
std::vector<entt::DefaultRegistry::entity_type> entities;
for(auto i = 0; i < 10; ++i) {
entities.push_back(source.create());
}
for(auto entity: entities) {
source.destroy(entity);
}
auto e0 = source.create();
source.assign<Position>(e0, 0.f, 0.f);
source.assign<Relationship>(e0, e0);
auto e1 = source.create();
source.assign<Position>(e1, 1.f, 1.f);
source.assign<Relationship>(e1, e0);
auto e2 = source.create();
source.assign<Position>(e2, .2f, .2f);
source.assign<Relationship>(e2, e0);
auto e3 = source.create();
source.assign<Timer>(e3, 1000, 1000);
source.assign<Relationship>(e3, e2);
{
// output finishes flushing its contents when it goes out of scope
cereal::JSONOutputArchive output{storage};
source.snapshot().entities(output).component<Position, Relationship, Timer>(output);
}
cereal::JSONInputArchive input{storage};
entt::ContinuousLoader<entt::DefaultRegistry::entity_type> loader{destination};
loader.entities(input)
.component<Position>(input)
.component<Relationship>(input, &Relationship::parent)
.component<Timer>(input);
ASSERT_FALSE(destination.valid(e0));
ASSERT_TRUE(loader.has(e0));
auto l0 = loader.map(e0);
ASSERT_TRUE(destination.valid(l0));
ASSERT_TRUE(destination.has<Position>(l0));
ASSERT_EQ(destination.get<Position>(l0).x, 0.f);
ASSERT_EQ(destination.get<Position>(l0).y, 0.f);
ASSERT_TRUE(destination.has<Relationship>(l0));
ASSERT_EQ(destination.get<Relationship>(l0).parent, l0);
ASSERT_FALSE(destination.valid(e1));
ASSERT_TRUE(loader.has(e1));
auto l1 = loader.map(e1);
ASSERT_TRUE(destination.valid(l1));
ASSERT_TRUE(destination.has<Position>(l1));
ASSERT_EQ(destination.get<Position>(l1).x, 1.f);
ASSERT_EQ(destination.get<Position>(l1).y, 1.f);
ASSERT_TRUE(destination.has<Relationship>(l1));
ASSERT_EQ(destination.get<Relationship>(l1).parent, l0);
ASSERT_FALSE(destination.valid(e2));
ASSERT_TRUE(loader.has(e2));
auto l2 = loader.map(e2);
ASSERT_TRUE(destination.valid(l2));
ASSERT_TRUE(destination.has<Position>(l2));
ASSERT_EQ(destination.get<Position>(l2).x, .2f);
ASSERT_EQ(destination.get<Position>(l2).y, .2f);
ASSERT_TRUE(destination.has<Relationship>(l2));
ASSERT_EQ(destination.get<Relationship>(l2).parent, l0);
ASSERT_FALSE(destination.valid(e3));
ASSERT_TRUE(loader.has(e3));
auto l3 = loader.map(e3);
ASSERT_TRUE(destination.valid(l3));
ASSERT_TRUE(destination.has<Timer>(l3));
ASSERT_EQ(destination.get<Timer>(l3).duration, 1000);
ASSERT_EQ(destination.get<Timer>(l3).elapsed, 0);
ASSERT_TRUE(destination.has<Relationship>(l3));
ASSERT_EQ(destination.get<Relationship>(l3).parent, l2);
}