Compare commits

...

73 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
Paolo Monteverde
59cec88a28 fixing clang build (#38) 2018-02-08 14:56:07 +01:00
Michele Caini
3ebc75af80 updated version 2018-02-08 12:42:38 +01:00
Michele Caini
4dce474e03 revert: too risky a function 2018-02-08 12:27:48 +01:00
Michele Caini
31a18da578 fix #37 2018-02-08 12:23:48 +01:00
Michele Caini
8c499850fc fixed doc 2018-02-04 12:36:50 +01:00
Michele Caini
6b6998a247 duktape is now an external 2018-02-04 12:31:24 +01:00
Michele Caini
a6cb0fc856 added Registry::alive and Registry::orphans 2018-02-02 17:35:15 +01:00
Michele Caini
e36b93e87b fixed 2018-02-02 12:58:10 +01:00
Michele Caini
1e3723b8bb minor changes 2018-02-02 12:38:17 +01:00
Michele Caini
412372289e updated copyright 2018-01-26 17:28:40 +01:00
Michele Caini
96f7e66073 fixed 2018-01-14 00:53:55 +01:00
Michele Caini
6040f8f263 issue #31: multi component get 2018-01-14 00:32:23 +01:00
Michele Caini
9761b6e14a updated version 2017-12-29 18:29:38 +01:00
Michele Caini
cb49910ed2 allow attaching listeners at any time, allow removing current listener 2017-12-29 18:25:49 +01:00
41 changed files with 5164 additions and 100922 deletions

1
.gitignore vendored
View File

@@ -1,2 +1 @@
# QtCreator
*.user

View File

@@ -16,7 +16,7 @@ endif()
# Project configuration
#
project(entt VERSION 2.4.0)
project(entt VERSION 2.5.0)
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Debug)
@@ -29,7 +29,7 @@ set(PROJECT_AUTHOR_EMAIL "michele.caini@gmail.com")
message("*")
message("* ${PROJECT_NAME} v${PROJECT_VERSION} (${CMAKE_BUILD_TYPE})")
message("* Copyright (c) 2017 ${PROJECT_AUTHOR} <${PROJECT_AUTHOR_EMAIL}>")
message("* Copyright (c) 2018 ${PROJECT_AUTHOR} <${PROJECT_AUTHOR_EMAIL}>")
message("*")
#
@@ -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
)

View File

@@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2017 Michele Caini
Copyright (c) 2018 Michele Caini
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

1922
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 ""
)

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

@@ -0,0 +1,19 @@
project(duktape-download NONE)
cmake_minimum_required(VERSION 3.2)
include(ExternalProject)
ExternalProject_Add(
duktape
GIT_REPOSITORY https://github.com/svaarala/duktape-releases.git
GIT_TAG v2.2.0
DOWNLOAD_DIR ${DUKTAPE_DEPS_DIR}
TMP_DIR ${DUKTAPE_DEPS_DIR}/tmp
STAMP_DIR ${DUKTAPE_DEPS_DIR}/stamp
SOURCE_DIR ${DUKTAPE_DEPS_DIR}/src
BINARY_DIR ${DUKTAPE_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

@@ -2,6 +2,7 @@
#define ENTT_CORE_HASHED_STRING_HPP
#include <cstddef>
#include <cstdint>

View File

@@ -22,6 +22,8 @@ template<typename Entity, typename Delta>
struct Actor {
/*! @brief Type of registry used internally. */
using registry_type = Registry<Entity>;
/*! @brief Underlying entity identifier. */
using entity_type = Entity;
/*! @brief Type used to provide elapsed time. */
using delta_type = Delta;
@@ -63,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)...);
}
/**
@@ -143,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

@@ -1,5 +1,5 @@
#ifndef ENTT_ENTITY_ENTT_HPP
#define ENTT_ENTITY_ENTT_HPP
#ifndef ENTT_ENTITY_ENTT_TRAITS_HPP
#define ENTT_ENTITY_ENTT_TRAITS_HPP
#include <cstdint>
@@ -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;
};
@@ -93,4 +93,4 @@ struct entt_traits<std::uint64_t> {
}
#endif // ENTT_ENTITY_ENTT_HPP
#endif // ENTT_ENTITY_ENTT_TRAITS_HPP

View File

@@ -2,6 +2,7 @@
#define ENTT_ENTITY_REGISTRY_HPP
#include <tuple>
#include <vector>
#include <memory>
#include <utility>
@@ -9,9 +10,11 @@
#include <cstdint>
#include <cassert>
#include <algorithm>
#include <type_traits>
#include "../core/family.hpp"
#include "entt_traits.hpp"
#include "snapshot.hpp"
#include "sparse_set.hpp"
#include "traits.hpp"
#include "view.hpp"
@@ -55,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) {
@@ -111,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())) {
@@ -136,7 +139,6 @@ class Registry {
if(!handlers[vtype]) {
using accumulator_type = int[];
auto set = std::make_unique<SparseSet<Entity>>();
for(auto entity: view<Component...>()) {
@@ -144,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);
@@ -228,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;
}
/**
@@ -242,7 +244,7 @@ public:
*/
template<typename Component>
void reserve(size_type cap) {
ensure<Component>().reserve(cap);
assure<Component>().reserve(cap);
}
/**
@@ -255,7 +257,6 @@ public:
*/
void reserve(size_type cap) {
entities.reserve(cap);
available.reserve(cap);
}
/**
@@ -282,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);
}
/**
@@ -324,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);
}
/**
@@ -351,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;
}
@@ -381,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;
}
@@ -406,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;
@@ -437,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)) {
@@ -451,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
@@ -472,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>();
@@ -481,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>
@@ -498,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.
*/
@@ -513,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
@@ -531,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
@@ -548,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
@@ -586,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)...);
}
/**
@@ -644,7 +719,7 @@ public:
*
* @tparam Component Type of component to get.
* @param entity A valid entity identifier.
* @return A reference to the instance of the component owned by the entity.
* @return A reference to the component owned by the entity.
*/
template<typename Component>
const Component & get(entity_type entity) const noexcept {
@@ -664,13 +739,53 @@ public:
*
* @tparam Component Type of component to get.
* @param entity A valid entity identifier.
* @return A reference to the instance of the component owned by the entity.
* @return A reference to the component owned by the entity.
*/
template<typename Component>
Component & get(entity_type entity) noexcept {
return const_cast<Component &>(const_cast<const Registry *>(this)->get<Component>(entity));
}
/**
* @brief Returns a reference to the given components for an entity.
*
* @warning
* Attempting to use an invalid entity or to get components from an entity
* that doesn't own them results in undefined behavior.<br/>
* An assertion will abort the execution at runtime in debug mode in case of
* invalid entity or if the entity doesn't own instances of the given
* components.
*
* @tparam Component Type of components to get.
* @param entity A valid entity identifier.
* @return References to the components owned by the entity.
*/
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)...};
}
/**
* @brief Returns a reference to the given components for an entity.
*
* @warning
* Attempting to use an invalid entity or to get components from an entity
* that doesn't own them results in undefined behavior.<br/>
* An assertion will abort the execution at runtime in debug mode in case of
* invalid entity or if the entity doesn't own instances of the given
* components.
*
* @tparam Component Type of components to get.
* @param entity A valid entity identifier.
* @return References to the components owned by the entity.
*/
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)...};
}
/**
* @brief Replaces the given component for an entity.
*
@@ -692,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)...});
}
/**
@@ -710,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
@@ -725,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)...})
@@ -737,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/>
@@ -759,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.
@@ -783,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`.
*
@@ -794,7 +908,7 @@ public:
*/
template<typename To, typename From>
void sort() {
ensure<To>().respect(ensure<From>());
assure<To>().respect(assure<From>());
}
/**
@@ -837,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);
}
}
});
}
}
@@ -849,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.
*
@@ -915,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
@@ -932,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>()...};
}
/**
@@ -953,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.
*/
@@ -1012,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.
@@ -1035,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.
@@ -1045,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,13 +3,14 @@
#include <algorithm>
#include <iterator>
#include <numeric>
#include <utility>
#include <vector>
#include <cstddef>
#include <cassert>
#include <type_traits>
#include "traits.hpp"
#include "entt_traits.hpp"
namespace entt {
@@ -56,10 +57,14 @@ template<typename Entity>
class SparseSet<Entity> {
using traits_type = entt_traits<Entity>;
struct Iterator {
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<Entity> *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<Entity> *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({ 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();
}
@@ -533,7 +697,9 @@ public:
*/
void destroy(entity_type entity) override {
// swapping isn't required here, we are getting rid of the last element
instances[underlying_type::get(entity)] = std::move(instances.back());
// however, we must protect ourselves from self assignments (see #37)
auto tmp = std::move(instances.back());
instances[underlying_type::get(entity)] = std::move(tmp);
instances.pop_back();
underlying_type::destroy(entity);
}
@@ -554,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.
@@ -574,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;
@@ -598,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,21 +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.
*
@@ -26,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
@@ -46,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.
@@ -54,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.
@@ -144,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.
*
@@ -154,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.
*/
@@ -189,7 +204,56 @@ public:
}
/**
* @brief Iterate the entities and applies them the given function object.
* @brief Returns the components assigned to the given entity.
*
* Prefer this function instead of `Registry::get` during iterations. It has
* far better performance than its companion function.
*
* @warning
* 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.
*
* @tparam Comp Types of the components to get.
* @param entity A valid entity identifier.
* @return The components assigned to the entity.
*/
template<typename... Comp>
std::enable_if_t<(sizeof...(Comp) > 1), std::tuple<const Comp &...>>
get(entity_type entity) const noexcept {
assert(contains(entity));
return std::tuple<const Comp &...>{get<Comp>(entity)...};
}
/**
* @brief Returns the components assigned to the given entity.
*
* Prefer this function instead of `Registry::get` during iterations. It has
* far better performance than its companion function.
*
* @warning
* 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.
*
* @tparam Comp Types of the components to get.
* @param entity A valid entity identifier.
* @return The components assigned to the entity.
*/
template<typename... Comp>
std::enable_if_t<(sizeof...(Comp) > 1), std::tuple<Comp &...>>
get(entity_type entity) noexcept {
assert(contains(entity));
return std::tuple<Comp &...>{get<Comp>(entity)...};
}
/**
* @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
@@ -205,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
@@ -246,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() {
@@ -255,7 +320,7 @@ public:
private:
view_type &view;
std::tuple<pool_type<Component> &...> pools;
const pattern_type pools;
};
@@ -277,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
@@ -293,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.
@@ -301,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);
@@ -330,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 {
@@ -340,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;
}
@@ -353,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.
@@ -391,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()};
}
/**
@@ -410,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);
}
@@ -445,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.
*/
@@ -458,7 +573,56 @@ public:
}
/**
* @brief Iterate the entities and applies them the given function object.
* @brief Returns the components assigned to the given entity.
*
* Prefer this function instead of `Registry::get` during iterations. It has
* far better performance than its companion function.
*
* @warning
* 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.
*
* @tparam Comp Types of the components to get.
* @param entity A valid entity identifier.
* @return The components assigned to the entity.
*/
template<typename... Comp>
std::enable_if_t<(sizeof...(Comp) > 1), std::tuple<const Comp &...>>
get(entity_type entity) const noexcept {
assert(contains(entity));
return std::tuple<const Comp &...>{get<Comp>(entity)...};
}
/**
* @brief Returns the components assigned to the given entity.
*
* Prefer this function instead of `Registry::get` during iterations. It has
* far better performance than its companion function.
*
* @warning
* 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.
*
* @tparam Comp Types of the components to get.
* @param entity A valid entity identifier.
* @return The components assigned to the entity.
*/
template<typename... Comp>
std::enable_if_t<(sizeof...(Comp) > 1), std::tuple<Comp &...>>
get(entity_type entity) noexcept {
assert(contains(entity));
return std::tuple<Comp &...>{get<Comp>(entity)...};
}
/**
* @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
@@ -474,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
@@ -513,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;
};
@@ -530,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.
@@ -539,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
@@ -557,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.
@@ -654,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();
}
/**
@@ -673,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);
}
/**
@@ -692,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);
}
@@ -715,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/>
@@ -730,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/>
@@ -761,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

@@ -2,9 +2,10 @@
#include "core/hashed_string.hpp"
#include "core/ident.hpp"
#include "entity/actor.hpp"
#include "entity/entt_traits.hpp"
#include "entity/registry.hpp"
#include "entity/snapshot.hpp"
#include "entity/sparse_set.hpp"
#include "entity/traits.hpp"
#include "entity/view.hpp"
#include "locator/locator.hpp"
#include "process/process.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,9 @@ public:
const auto buf = buffer(mode);
mode = !mode;
for(auto &&wrapper: wrappers) {
for(auto pos = wrappers.size(); pos; --pos) {
auto &wrapper = wrappers[pos-1];
if(wrapper) {
wrapper->publish(buf);
}

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,8 @@ public:
* @param args Arguments to use to invoke listeners.
*/
void publish(Args... args) {
for(auto &&call: calls) {
for(auto pos = calls.size(); pos; --pos) {
auto &call = calls[pos-1];
call.second(call.first, args...);
}
}
@@ -242,7 +247,9 @@ public:
collector_type collect(Args... args) {
collector_type collector;
for(auto &&call: calls) {
for(auto pos = calls.size(); pos; --pos) {
auto &call = calls[pos-1];
if(!this->invoke(collector, call.second, call.first, args...)) {
break;
}

View File

@@ -167,11 +167,17 @@ public:
* @param args Arguments to use to invoke listeners.
*/
void publish(Args... args) {
for(auto it = calls.rbegin(), end = calls.rend(); it != end; it++) {
if(!(it->second)(it->first, args...)) {
calls.erase(std::next(it).base());
std::vector<call_type> next;
for(auto pos = calls.size(); pos; --pos) {
auto &call = calls[pos-1];
if((call.second)(call.first, args...)) {
next.push_back(call);
}
}
calls.swap(next);
}
/**

View File

@@ -19,16 +19,42 @@ endif()
# Test mod
if(BUILD_MOD)
set(DUKTAPE_DEPS_DIR ${entt_SOURCE_DIR}/deps/duktape)
configure_file(${entt_SOURCE_DIR}/cmake/in/duktape.in ${DUKTAPE_DEPS_DIR}/CMakeLists.txt)
execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" . WORKING_DIRECTORY ${DUKTAPE_DEPS_DIR})
execute_process(COMMAND ${CMAKE_COMMAND} --build . WORKING_DIRECTORY ${DUKTAPE_DEPS_DIR})
set(DUKTAPE_SRC_DIR ${DUKTAPE_DEPS_DIR}/src/src)
add_executable(
mod
$<TARGET_OBJECTS:odr>
mod/duktape.c
${DUKTAPE_SRC_DIR}/duktape.c
mod/mod.cpp
)
target_include_directories(mod PRIVATE ${DUKTAPE_SRC_DIR})
target_link_libraries(mod PRIVATE gtest_main Threads::Threads m)
add_test(NAME mod COMMAND mod)
endif()
# Test snapshot
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(
@@ -48,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,5 +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) {
@@ -7,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});
@@ -15,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});
@@ -27,63 +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');
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));
auto e2 = registry.create();
ASSERT_NO_THROW(registry.replace<int>(e1, 0));
ASSERT_EQ(registry.get<int>(e1), 0);
registry.accommodate<int>(e2, registry.get<int>(e0));
registry.accommodate<char>(e2, registry.get<char>(e0));
ASSERT_NO_THROW(registry.accomodate<int>(e1, 1));
ASSERT_NO_THROW(registry.accomodate<int>(e2, 1));
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_EQ(std::get<0>(registry.get<int, char>(e0)), 42);
ASSERT_EQ(std::get<1>(static_cast<const entt::DefaultRegistry &>(registry).get<int, char>(e0)), 'c');
ASSERT_EQ(registry.get<int>(e0), registry.get<int>(e2));
ASSERT_EQ(registry.get<char>(e0), registry.get<char>(e2));
ASSERT_NE(&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());
@@ -114,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;
@@ -142,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;
@@ -154,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;
@@ -182,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;
@@ -207,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>());
@@ -346,3 +453,58 @@ TEST(DefaultRegistry, SortMulti) {
ASSERT_EQ(registry.get<int>(entity), ival++);
}
}
TEST(DefaultRegistry, ComponentsWithTypesFromStandardTemplateLibrary) {
// see #37 - the test shouldn't crash, that's all
entt::DefaultRegistry registry;
auto entity = registry.create();
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,22 +107,41 @@ 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';
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);
ASSERT_TRUE(cview.get<char>(entity) == '2');
ASSERT_EQ(std::get<0>(cview.get<int, char>(entity)), 42);
ASSERT_EQ(std::get<1>(view.get<int, char>(entity)), '2');
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;
@@ -140,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()));
@@ -151,26 +190,29 @@ 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';
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);
ASSERT_TRUE(cview.get<char>(entity) == '2');
ASSERT_EQ(std::get<0>(cview.get<int, char>(entity)), 42);
ASSERT_EQ(std::get<1>(view.get<int, char>(entity)), '2');
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());
}
@@ -178,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()));
@@ -189,30 +231,47 @@ 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';
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);
ASSERT_TRUE(cview.get<char>(entity) == '2');
ASSERT_EQ(std::get<0>(cview.get<int, char>(entity)), 42);
ASSERT_EQ(std::get<1>(view.get<int, char>(entity)), '2');
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;
@@ -254,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>();
@@ -284,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;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

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);
}