Compare commits
137 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d14b0b6843 | ||
|
|
f763c8a777 | ||
|
|
4231b040d8 | ||
|
|
67c587e673 | ||
|
|
3978e24fd8 | ||
|
|
c45de0c032 | ||
|
|
ff935efea1 | ||
|
|
808464f47d | ||
|
|
c986a6c4dd | ||
|
|
17c0479343 | ||
|
|
d93238912a | ||
|
|
d6ef0956e6 | ||
|
|
05db0aad29 | ||
|
|
89bbdfa1fe | ||
|
|
871bda6198 | ||
|
|
20732c9206 | ||
|
|
2a34a3ebb6 | ||
|
|
3f04247a53 | ||
|
|
ca0a1f8f8b | ||
|
|
d8a9f0ca12 | ||
|
|
c2116b841e | ||
|
|
700cf69f18 | ||
|
|
ed5dee5218 | ||
|
|
b224dfdfac | ||
|
|
e7da68547f | ||
|
|
9a785ceb2e | ||
|
|
fca1cd5a1c | ||
|
|
24b862e32e | ||
|
|
2ec9043cf2 | ||
|
|
1102d63469 | ||
|
|
b89f39d78c | ||
|
|
7ba479c9c9 | ||
|
|
8ad6a2980c | ||
|
|
d3b6ed78d9 | ||
|
|
8bd5605c2a | ||
|
|
08dc2fcf33 | ||
|
|
caa8d16371 | ||
|
|
bce92b3d85 | ||
|
|
67858bf300 | ||
|
|
7157e7e77d | ||
|
|
cf5074bdc5 | ||
|
|
fda44063ce | ||
|
|
f9becda02c | ||
|
|
fb9fc952c6 | ||
|
|
18451edfe9 | ||
|
|
8c73cac72f | ||
|
|
c54cedf14b | ||
|
|
8ef4cdc9c3 | ||
|
|
c0213e84f6 | ||
|
|
29de6d89d4 | ||
|
|
c9bf38ce36 | ||
|
|
338eb75bab | ||
|
|
31b1b453b0 | ||
|
|
aaf0e145eb | ||
|
|
089b3e13fd | ||
|
|
e9da2ce12a | ||
|
|
92048ac17b | ||
|
|
5e8561a578 | ||
|
|
d2f5e13074 | ||
|
|
820178f006 | ||
|
|
0a36a91e6d | ||
|
|
5013a92795 | ||
|
|
d81ecfec32 | ||
|
|
e99d7e2c3c | ||
|
|
d417984ff3 | ||
|
|
d38b3e641b | ||
|
|
28ce491dd5 | ||
|
|
c260d72125 | ||
|
|
d1d1b3156d | ||
|
|
472064b751 | ||
|
|
95ab9a0b70 | ||
|
|
4b03f6a039 | ||
|
|
c3460727fa | ||
|
|
2cc1850212 | ||
|
|
2d7443acaf | ||
|
|
13d0b0940c | ||
|
|
c101797924 | ||
|
|
83b55f8e3f | ||
|
|
b3b6362cd9 | ||
|
|
fc9af32d5f | ||
|
|
4cd1025011 | ||
|
|
5233fe8abc | ||
|
|
041e31ea78 | ||
|
|
7a3e881099 | ||
|
|
631bf42f84 | ||
|
|
1f704a7019 | ||
|
|
d295c88474 | ||
|
|
1dd9da4dff | ||
|
|
f2eb0c8427 | ||
|
|
c8ba11faf8 | ||
|
|
a2e243d992 | ||
|
|
c588fff5ca | ||
|
|
87f9599fea | ||
|
|
0459599b1d | ||
|
|
9447b1a696 | ||
|
|
0ccb7443c2 | ||
|
|
02cf27091f | ||
|
|
fdfbd04503 | ||
|
|
866c18200a | ||
|
|
c1cada49d4 | ||
|
|
7bf550a75f | ||
|
|
9c540c03aa | ||
|
|
b3df46db19 | ||
|
|
7ca615a1c1 | ||
|
|
c83db557a6 | ||
|
|
d54594f11d | ||
|
|
434e38608f | ||
|
|
871f090ca0 | ||
|
|
d1d235e025 | ||
|
|
e822a5fd53 | ||
|
|
7b82a4ae50 | ||
|
|
c532e9f2eb | ||
|
|
3fd034816e | ||
|
|
bb4b868c79 | ||
|
|
3b3da11a36 | ||
|
|
f2cbb5306b | ||
|
|
94ede1b324 | ||
|
|
0367248338 | ||
|
|
936db30e58 | ||
|
|
4822f0dd11 | ||
|
|
456d220829 | ||
|
|
b459ba6ea7 | ||
|
|
a19ef9bd16 | ||
|
|
59cec88a28 | ||
|
|
3ebc75af80 | ||
|
|
4dce474e03 | ||
|
|
31a18da578 | ||
|
|
8c499850fc | ||
|
|
6b6998a247 | ||
|
|
a6cb0fc856 | ||
|
|
e36b93e87b | ||
|
|
1e3723b8bb | ||
|
|
412372289e | ||
|
|
96f7e66073 | ||
|
|
6040f8f263 | ||
|
|
9761b6e14a | ||
|
|
cb49910ed2 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1 @@
|
||||
# QtCreator
|
||||
*.user
|
||||
|
||||
2
AUTHORS
2
AUTHORS
@@ -5,3 +5,5 @@ Michele Caini aka skypjack
|
||||
# Contributors
|
||||
|
||||
Paolo Monteverde aka morbo84
|
||||
David Nerjes aka DavidHamburg
|
||||
Indi Kernick aka Kerndog73
|
||||
|
||||
@@ -16,7 +16,7 @@ endif()
|
||||
# Project configuration
|
||||
#
|
||||
|
||||
project(entt VERSION 2.4.0)
|
||||
project(entt VERSION 2.6.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)
|
||||
@@ -104,7 +120,9 @@ add_custom_target(
|
||||
SOURCES
|
||||
appveyor.yml
|
||||
AUTHORS
|
||||
CONTRIBUTING
|
||||
LICENSE
|
||||
README.md
|
||||
TODO
|
||||
.travis.yml
|
||||
)
|
||||
|
||||
43
CONTRIBUTING
Normal file
43
CONTRIBUTING
Normal file
@@ -0,0 +1,43 @@
|
||||
# Contributing
|
||||
|
||||
First of all, thank you very much for taking the time to contribute to the
|
||||
`EnTT` framework.<br/>
|
||||
How to do it mostly depends on the type of contribution:
|
||||
|
||||
* If you have a question, **please** ensure there isn't already an answer for
|
||||
you by searching on GitHub under
|
||||
[issues](https://github.com/skypjack/entt/issues). Do not forget to search
|
||||
also through the closed ones. If you are unable to find a proper answer, feel
|
||||
free to [open a new issue](https://github.com/skypjack/entt/issues/new).
|
||||
Usually, questions are marked as such and closed in a few days.
|
||||
|
||||
* If you want to fix a typo in the inline documentation or in the README file,
|
||||
if you want to add some new sections or if you want to help me with the
|
||||
language by reviewing what I wrote so far (I'm not a native speaker after
|
||||
all), **please** open a new
|
||||
[pull request](https://github.com/skypjack/entt/pulls) with your changes.
|
||||
|
||||
* If you found a bug, **please** ensure there isn't already an answer for you by
|
||||
searching on GitHub under [issues](https://github.com/skypjack/entt/issues).
|
||||
If you are unable to find an open issue addressing the problem, feel free to
|
||||
[open a new one](https://github.com/skypjack/entt/issues/new). **Please**, do
|
||||
not forget to carefully describe how to reproduce the problem, then add all
|
||||
the informaion about the system on which you are experiencing it and point out
|
||||
the version of `EnTT` you are using (tag or commit).
|
||||
|
||||
* If you found a bug and you wrote a patch to fix it, open a new
|
||||
[pull request](https://github.com/skypjack/entt/pulls) with your code.
|
||||
**Please**, add some tests to avoid regressions in future if possible, it
|
||||
would be really appreciated. Note that the `EnTT` framework has a
|
||||
[coverage at 100%](https://coveralls.io/github/skypjack/entt?branch=master)
|
||||
(at least it was at 100% at the time I wrote this file) and this is the reason
|
||||
for which you can be confident with using it in a production environment.
|
||||
|
||||
* If you want to propose a new feature and you know how to code it, **please**
|
||||
do not issue directly a pull request. Before to do it,
|
||||
[create a new issue](https://github.com/skypjack/entt/issues/new) to discuss
|
||||
your proposal. Other users could be interested in your idea and the discussion
|
||||
that will follow can refine it and therefore give us a better solution.
|
||||
|
||||
* If you want to request a new feature, I'm available for hiring. Take a look at
|
||||
[my profile](https://github.com/skypjack) and feel free to write me.
|
||||
2
LICENSE
2
LICENSE
@@ -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
|
||||
|
||||
13
TODO
Normal file
13
TODO
Normal file
@@ -0,0 +1,13 @@
|
||||
* custom allocators and EnTT allocator-aware in general (long term feature, I don't actually need it at the moment) - see #22
|
||||
* scene management (I prefer the concept of spaces, that is a kind of scene anyway)
|
||||
* 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
|
||||
* dynamic view, useful for runtime ecs, can be filled with the desired pool at runtime and are not constrained to a compile-time list of components
|
||||
* define basic reactive systems (track entities to which component is attached, track entities from which component is removed, and so on)
|
||||
* define systems as composable mixins (initializazion, reactive, update, whatever) with flexible auto-detected arguments (registry, views, etc)
|
||||
* create dedicated flat map based on types implementation (sort of "type map") for types to use within the registry and so on...
|
||||
* ease the assignment of tags as string (use a template class with a non-type template parameter behind the scene)
|
||||
* improve CMake interface, see mail from Malte
|
||||
* is registry/utility.hpp really required?
|
||||
* "singleton mode" for tags (see #66)
|
||||
* AOB
|
||||
19
cmake/in/cereal.in
Normal file
19
cmake/in/cereal.in
Normal 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
19
cmake/in/duktape.in
Normal 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 ""
|
||||
)
|
||||
10
src/entt/config/config.h
Normal file
10
src/entt/config/config.h
Normal file
@@ -0,0 +1,10 @@
|
||||
#ifndef ENTT_CONFIG_CONFIG_H
|
||||
#define ENTT_CONFIG_CONFIG_H
|
||||
|
||||
|
||||
#ifndef ENTT_NOEXCEPT
|
||||
#define ENTT_NOEXCEPT noexcept
|
||||
#endif
|
||||
|
||||
|
||||
#endif // ENTT_CONFIG_CONFIG_H
|
||||
76
src/entt/core/algorithm.hpp
Normal file
76
src/entt/core/algorithm.hpp
Normal file
@@ -0,0 +1,76 @@
|
||||
#ifndef ENTT_CORE_ALGORITHM_HPP
|
||||
#define ENTT_CORE_ALGORITHM_HPP
|
||||
|
||||
|
||||
#include <functional>
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/**
|
||||
* @brief Function object to wrap `std::sort` in a class type.
|
||||
*
|
||||
* Unfortunately, `std::sort` cannot be passed as template argument to a class
|
||||
* template or a function template.<br/>
|
||||
* This class fills the gap by wrapping some flavors of `std::sort` in a
|
||||
* function object.
|
||||
*/
|
||||
struct StdSort {
|
||||
/**
|
||||
* @brief Sorts the element in a range.
|
||||
*
|
||||
* Sorts the element in a range using the given binary comparison function.
|
||||
*
|
||||
* @tparam It Type of random access iterator.
|
||||
* @tparam Compare Type of comparison function object.
|
||||
* @param first An iterator to the first element of the range to sort.
|
||||
* @param last An iterator past the last element of the range to sort.
|
||||
* @param compare A valid comparison function object.
|
||||
*/
|
||||
template<typename It, typename Compare = std::less<>>
|
||||
void operator()(It first, It last, Compare compare = Compare{}) {
|
||||
std::sort(std::move(first), std::move(last), std::move(compare));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/*! @brief Function object for performing insertion sort. */
|
||||
struct InsertionSort {
|
||||
/**
|
||||
* @brief Sorts the element in a range.
|
||||
*
|
||||
* Sorts the element in a range using the given binary comparison function.
|
||||
*
|
||||
* @tparam It Type of random access iterator.
|
||||
* @tparam Compare Type of comparison function object.
|
||||
* @param first An iterator to the first element of the range to sort.
|
||||
* @param last An iterator past the last element of the range to sort.
|
||||
* @param compare A valid comparison function object.
|
||||
*/
|
||||
template<typename It, typename Compare = std::less<>>
|
||||
void operator()(It first, It last, Compare compare = Compare{}) {
|
||||
auto it = first + 1;
|
||||
|
||||
while(it != last) {
|
||||
auto value = *it;
|
||||
auto pre = it;
|
||||
|
||||
while(pre != first && compare(value, *(pre-1))) {
|
||||
*pre = *(pre-1);
|
||||
--pre;
|
||||
}
|
||||
|
||||
*pre = value;
|
||||
++it;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_CORE_ALGORITHM_HPP
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
#include<type_traits>
|
||||
#include<cstddef>
|
||||
#include<atomic>
|
||||
#include "../config/config.h"
|
||||
|
||||
|
||||
namespace entt {
|
||||
@@ -18,14 +20,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 std::size_t family() ENTT_NOEXCEPT {
|
||||
static const std::size_t value = identifier.fetch_add(1);
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -38,12 +37,16 @@ public:
|
||||
* @return Statically generated unique identifier for the given type.
|
||||
*/
|
||||
template<typename... Type>
|
||||
static family_type type() noexcept {
|
||||
inline static family_type type() ENTT_NOEXCEPT {
|
||||
return family<std::decay_t<Type>...>();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template<typename... Types>
|
||||
std::atomic<std::size_t> Family<Types...>::identifier{};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
#define ENTT_CORE_HASHED_STRING_HPP
|
||||
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include "../config/config.h"
|
||||
|
||||
|
||||
namespace entt {
|
||||
@@ -20,7 +22,7 @@ namespace entt {
|
||||
class HashedString final {
|
||||
struct ConstCharWrapper final {
|
||||
// non-explicit constructor on purpose
|
||||
constexpr ConstCharWrapper(const char *str) noexcept: str{str} {}
|
||||
constexpr ConstCharWrapper(const char *str) ENTT_NOEXCEPT: str{str} {}
|
||||
const char *str;
|
||||
};
|
||||
|
||||
@@ -28,7 +30,7 @@ class HashedString final {
|
||||
static constexpr std::uint64_t prime = 1099511628211ull;
|
||||
|
||||
// Fowler–Noll–Vo hash function v. 1a - the good
|
||||
static constexpr std::uint64_t helper(std::uint64_t partial, const char *str) noexcept {
|
||||
static constexpr std::uint64_t helper(std::uint64_t partial, const char *str) ENTT_NOEXCEPT {
|
||||
return str[0] == 0 ? partial : helper((partial^str[0])*prime, str+1);
|
||||
}
|
||||
|
||||
@@ -51,7 +53,7 @@ public:
|
||||
* @param str Human-readable identifer.
|
||||
*/
|
||||
template <std::size_t N>
|
||||
constexpr HashedString(const char (&str)[N]) noexcept
|
||||
constexpr HashedString(const char (&str)[N]) ENTT_NOEXCEPT
|
||||
: hash{helper(offset, str)}, str{str}
|
||||
{}
|
||||
|
||||
@@ -61,7 +63,7 @@ public:
|
||||
*
|
||||
* @param wrapper Helps achieving the purpose by relying on overloading.
|
||||
*/
|
||||
explicit constexpr HashedString(ConstCharWrapper wrapper) noexcept
|
||||
explicit constexpr HashedString(ConstCharWrapper wrapper) ENTT_NOEXCEPT
|
||||
: hash{helper(offset, wrapper.str)}, str{wrapper.str}
|
||||
{}
|
||||
|
||||
@@ -69,20 +71,20 @@ public:
|
||||
* @brief Returns the human-readable representation of a hashed string.
|
||||
* @return The string used to initialize the instance.
|
||||
*/
|
||||
constexpr operator const char *() const noexcept { return str; }
|
||||
constexpr operator const char *() const ENTT_NOEXCEPT { return str; }
|
||||
|
||||
/**
|
||||
* @brief Returns the numeric representation of a hashed string.
|
||||
* @return The numeric representation of the instance.
|
||||
*/
|
||||
constexpr operator hash_type() const noexcept { return hash; }
|
||||
constexpr operator hash_type() const ENTT_NOEXCEPT { return hash; }
|
||||
|
||||
/**
|
||||
* @brief Compares two hashed strings.
|
||||
* @param other Hashed string with which to compare.
|
||||
* @return True if the two hashed strings are identical, false otherwise.
|
||||
*/
|
||||
constexpr bool operator==(const HashedString &other) const noexcept {
|
||||
constexpr bool operator==(const HashedString &other) const ENTT_NOEXCEPT {
|
||||
return hash == other.hash;
|
||||
}
|
||||
|
||||
@@ -98,7 +100,7 @@ private:
|
||||
* @param rhs A valid hashed string.
|
||||
* @return True if the two hashed strings are identical, false otherwise.
|
||||
*/
|
||||
constexpr bool operator!=(const HashedString &lhs, const HashedString &rhs) noexcept {
|
||||
constexpr bool operator!=(const HashedString &lhs, const HashedString &rhs) ENTT_NOEXCEPT {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,13 @@
|
||||
namespace entt {
|
||||
|
||||
|
||||
namespace {
|
||||
namespace internal {
|
||||
|
||||
|
||||
/**
|
||||
* @cond TURN_OFF_DOXYGEN
|
||||
* Internal details not to be documented.
|
||||
*/
|
||||
|
||||
|
||||
template<typename... Types>
|
||||
@@ -47,6 +53,12 @@ private:
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Internal details not to be documented.
|
||||
* @endcond TURN_OFF_DOXYGEN
|
||||
*/
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -87,7 +99,7 @@ private:
|
||||
* @tparam Types List of types for which to generate identifiers.
|
||||
*/
|
||||
template<typename... Types>
|
||||
constexpr auto ident = Identifier<std::decay_t<Types>...>{std::make_index_sequence<sizeof...(Types)>{}};
|
||||
constexpr auto ident = internal::Identifier<std::decay_t<Types>...>{std::make_index_sequence<sizeof...(Types)>{}};
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
#define ENTT_ENTITY_ACTOR_HPP
|
||||
|
||||
|
||||
#include <cassert>
|
||||
#include <utility>
|
||||
#include "../config/config.h"
|
||||
#include "registry.hpp"
|
||||
|
||||
|
||||
@@ -16,26 +18,25 @@ namespace entt {
|
||||
* with entity-component systems and prefer to iterate objects directly.
|
||||
*
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
* @tparam Delta Type to use to provide elapsed time.
|
||||
*/
|
||||
template<typename Entity, typename Delta>
|
||||
template<typename Entity>
|
||||
struct Actor {
|
||||
/*! @brief Type of registry used internally. */
|
||||
using registry_type = Registry<Entity>;
|
||||
/*! @brief Type used to provide elapsed time. */
|
||||
using delta_type = Delta;
|
||||
/*! @brief Underlying entity identifier. */
|
||||
using entity_type = Entity;
|
||||
|
||||
/**
|
||||
* @brief Constructs an actor by using the given registry.
|
||||
* @param reg An entity-component system properly initialized.
|
||||
*/
|
||||
Actor(Registry<Entity> ®)
|
||||
: reg{reg}, entity{reg.create()}
|
||||
: reg{reg}, entt{reg.create()}
|
||||
{}
|
||||
|
||||
/*! @brief Default destructor. */
|
||||
virtual ~Actor() {
|
||||
reg.destroy(entity);
|
||||
reg.destroy(entt);
|
||||
}
|
||||
|
||||
/*! @brief Default copy constructor. */
|
||||
@@ -48,6 +49,24 @@ struct Actor {
|
||||
/*! @brief Default move assignment operator. @return This actor. */
|
||||
Actor & operator=(Actor &&) = default;
|
||||
|
||||
/**
|
||||
* @brief Assigns the given tag to an actor.
|
||||
*
|
||||
* A new instance of the given tag is created and initialized with the
|
||||
* arguments provided (the tag must have a proper constructor or be of
|
||||
* aggregate type). Then the tag is removed from its previous owner (if any)
|
||||
* and assigned to the actor.
|
||||
*
|
||||
* @tparam Tag Type of the tag to create.
|
||||
* @tparam Args Types of arguments to use to construct the tag.
|
||||
* @param args Parameters to use to initialize the tag.
|
||||
* @return A reference to the newly created tag.
|
||||
*/
|
||||
template<typename Tag, typename... Args>
|
||||
Tag & assign(tag_t, Args &&... args) {
|
||||
return (reg.template remove<Tag>(), reg.template assign<Tag>(tag_t{}, entt, std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns the given component to an actor.
|
||||
*
|
||||
@@ -63,8 +82,18 @@ struct Actor {
|
||||
* @return A reference to the newly created component.
|
||||
*/
|
||||
template<typename Component, typename... Args>
|
||||
Component & set(Args&&... args) {
|
||||
return reg.template accomodate<Component>(entity, std::forward<Args>(args)...);
|
||||
Component & assign(Args &&... args) {
|
||||
return reg.template accommodate<Component>(entt, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Removes the given tag from an actor.
|
||||
* @tparam Tag Type of the tag to remove.
|
||||
*/
|
||||
template<typename Tag>
|
||||
void remove(tag_t) {
|
||||
assert(has<Tag>(tag_t{}));
|
||||
reg.template remove<Tag>();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -72,8 +101,18 @@ struct Actor {
|
||||
* @tparam Component Type of the component to remove.
|
||||
*/
|
||||
template<typename Component>
|
||||
void unset() {
|
||||
reg.template remove<Component>(entity);
|
||||
void remove() {
|
||||
reg.template remove<Component>(entt);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if an actor owns the given tag.
|
||||
* @tparam Tag Type of the tag for which to perform the check.
|
||||
* @return True if the actor owns the tag, false otherwise.
|
||||
*/
|
||||
template<typename Tag>
|
||||
bool has(tag_t) const ENTT_NOEXCEPT {
|
||||
return (reg.template has<Tag>() && (reg.template attachee<Tag>() == entt));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -82,55 +121,78 @@ struct Actor {
|
||||
* @return True if the actor has the component, false otherwise.
|
||||
*/
|
||||
template<typename Component>
|
||||
bool has() const noexcept {
|
||||
return reg.template has<Component>(entity);
|
||||
bool has() const ENTT_NOEXCEPT {
|
||||
return reg.template has<Component>(entt);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a reference to the given tag for an actor.
|
||||
* @tparam Tag Type of the tag to get.
|
||||
* @return A reference to the instance of the tag owned by the actor.
|
||||
*/
|
||||
template<typename Tag>
|
||||
const Tag & get(tag_t) const ENTT_NOEXCEPT {
|
||||
assert(has<Tag>(tag_t{}));
|
||||
return reg.template get<Tag>();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a reference to the given tag for an actor.
|
||||
* @tparam Tag Type of the tag to get.
|
||||
* @return A reference to the instance of the tag owned by the actor.
|
||||
*/
|
||||
template<typename Tag>
|
||||
inline Tag & get(tag_t) ENTT_NOEXCEPT {
|
||||
return const_cast<Tag &>(const_cast<const Actor *>(this)->get<Tag>(tag_t{}));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a reference to the given component for an actor.
|
||||
* @tparam Component Type of the component to get.
|
||||
* @return A reference to the instance of the component owned by the entity.
|
||||
* @return A reference to the instance of the component owned by the actor.
|
||||
*/
|
||||
template<typename Component>
|
||||
const Component & get() const noexcept {
|
||||
return reg.template get<Component>(entity);
|
||||
const Component & get() const ENTT_NOEXCEPT {
|
||||
return reg.template get<Component>(entt);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a reference to the given component for an actor.
|
||||
* @tparam Component Type of the component to get.
|
||||
* @return A reference to the instance of the component owned by the entity.
|
||||
* @return A reference to the instance of the component owned by the actor.
|
||||
*/
|
||||
template<typename Component>
|
||||
Component & get() noexcept {
|
||||
inline Component & get() ENTT_NOEXCEPT {
|
||||
return const_cast<Component &>(const_cast<const Actor *>(this)->get<Component>());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a reference to the underlying registry.
|
||||
* @return A reference to the underlying registry
|
||||
* @return A reference to the underlying registry.
|
||||
*/
|
||||
const registry_type & registry() const noexcept {
|
||||
const registry_type & registry() const ENTT_NOEXCEPT {
|
||||
return reg;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a reference to the underlying registry.
|
||||
* @return A reference to the underlying registry
|
||||
* @return A reference to the underlying registry.
|
||||
*/
|
||||
registry_type & registry() noexcept {
|
||||
inline registry_type & registry() ENTT_NOEXCEPT {
|
||||
return const_cast<registry_type &>(const_cast<const Actor *>(this)->registry());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Updates an actor, whatever it means to update it.
|
||||
* @param delta Elapsed time.
|
||||
* @brief Returns the entity associated with an actor.
|
||||
* @return The entity associated with the actor.
|
||||
*/
|
||||
virtual void update(delta_type delta) = 0;
|
||||
entity_type entity() const ENTT_NOEXCEPT {
|
||||
return entt;
|
||||
}
|
||||
|
||||
private:
|
||||
registry_type ®
|
||||
Entity entity;
|
||||
Entity entt;
|
||||
};
|
||||
|
||||
|
||||
@@ -139,11 +201,8 @@ private:
|
||||
*
|
||||
* The default actor is the best choice for almost all the applications.<br/>
|
||||
* Users should have a really good reason to choose something different.
|
||||
*
|
||||
* @tparam Delta Type to use to provide elapsed time.
|
||||
*/
|
||||
template<typename Delta>
|
||||
using DefaultActor = Actor<std::uint32_t, Delta>;
|
||||
using DefaultActor = Actor<DefaultRegistry::entity_type>;
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
84
src/entt/entity/helper.hpp
Normal file
84
src/entt/entity/helper.hpp
Normal file
@@ -0,0 +1,84 @@
|
||||
#ifndef ENTT_ENTITY_HELPER_HPP
|
||||
#define ENTT_ENTITY_HELPER_HPP
|
||||
|
||||
|
||||
#include "../signal/sigh.hpp"
|
||||
#include "registry.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/**
|
||||
* @brief Dependency function prototype.
|
||||
*
|
||||
* A _dependency function_ is a built-in listener to use to automatically assign
|
||||
* components to an entity when a type has a dependency on some other types.
|
||||
*
|
||||
* This is a prototype function to use to create dependencies.<br/>
|
||||
* It isn't intended for direct use, although nothing forbids using it freely.
|
||||
*
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
* @tparam Component Types of components to assign to an entity if triggered.
|
||||
* @param registry A valid reference to a registry.
|
||||
* @param entity A valid entity identifier.
|
||||
*/
|
||||
template<typename Entity, typename... Component>
|
||||
void dependency(Registry<Entity> ®istry, const Entity entity) {
|
||||
using accumulator_type = int[];
|
||||
accumulator_type accumulator = { ((registry.template has<Component>(entity) ? void() : (registry.template assign<Component>(entity), void())), 0)... };
|
||||
(void)accumulator;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Connects a dependency function to the given sink.
|
||||
*
|
||||
* A _dependency function_ is a built-in listener to use to automatically assign
|
||||
* components to an entity when a type has a dependency on some other types.
|
||||
*
|
||||
* The following adds components `AType` and `AnotherType` whenever `MyType` is
|
||||
* assigned to an entity:
|
||||
* @code{.cpp}
|
||||
* entt::DefaultRegistry registry;
|
||||
* entt::dependency<AType, AnotherType>(registry.construction<MyType>());
|
||||
* @endcode
|
||||
*
|
||||
* @tparam Dependency Types of components to assign to an entity if triggered.
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
* @param sink A sink object properly initialized.
|
||||
*/
|
||||
template<typename... Dependency, typename Entity>
|
||||
void dependency(Sink<void(Registry<Entity> &, const Entity)> sink) {
|
||||
sink.template connect<dependency<Entity, Dependency...>>();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Disconnects a dependency function from the given sink.
|
||||
*
|
||||
* A _dependency function_ is a built-in listener to use to automatically assign
|
||||
* components to an entity when a type has a dependency on some other types.
|
||||
*
|
||||
* The following breaks the dependency between the component `MyType` and the
|
||||
* components `AType` and `AnotherType`:
|
||||
* @code{.cpp}
|
||||
* entt::DefaultRegistry registry;
|
||||
* entt::dependency<AType, AnotherType>(entt::break_t{}, registry.construction<MyType>());
|
||||
* @endcode
|
||||
*
|
||||
* @tparam Dependency Types of components used to create the dependency.
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
* @param sink A sink object properly initialized.
|
||||
*/
|
||||
template<typename... Dependency, typename Entity>
|
||||
void dependency(break_t, Sink<void(Registry<Entity> &, const Entity)> sink) {
|
||||
sink.template disconnect<dependency<Entity, Dependency...>>();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_ENTITY_HELPER_HPP
|
||||
330
src/entt/entity/prototype.hpp
Normal file
330
src/entt/entity/prototype.hpp
Normal file
@@ -0,0 +1,330 @@
|
||||
#ifndef ENTT_ENTITY_PROTOTYPE_HPP
|
||||
#define ENTT_ENTITY_PROTOTYPE_HPP
|
||||
|
||||
|
||||
#include <tuple>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
#include <cstddef>
|
||||
#include <algorithm>
|
||||
#include "registry.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/**
|
||||
* @brief Prototype container for _concepts_.
|
||||
*
|
||||
* A prototype is used to define a _concept_ in terms of components.<br/>
|
||||
* Prototypes act as templates for those specific types of an application which
|
||||
* users would otherwise define through a series of component assignments to
|
||||
* entities. In other words, prototypes can be used to assign components to
|
||||
* entities of a registry at once.
|
||||
*
|
||||
* @note
|
||||
* Components used along with prototypes must be copy constructible.
|
||||
*
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
*/
|
||||
template<typename Entity>
|
||||
class Prototype {
|
||||
using component_type = typename Registry<Entity>::component_type;
|
||||
using fn_type = void(*)(Registry<Entity> &, const Entity, const void *);
|
||||
using deleter_type = void(*)(void *);
|
||||
using ptr_type = std::unique_ptr<void, deleter_type>;
|
||||
|
||||
template<typename Component>
|
||||
static void accommodate(Registry<Entity> ®istry, const Entity entity, const void *component) {
|
||||
const auto &ref = *static_cast<const Component *>(component);
|
||||
registry.template accommodate<Component>(entity, ref);
|
||||
}
|
||||
|
||||
template<typename Component>
|
||||
static void assign(Registry<Entity> ®istry, const Entity entity, const void *component) {
|
||||
if(!registry.template has<Component>(entity)) {
|
||||
const auto &ref = *static_cast<const Component *>(component);
|
||||
registry.template assign<Component>(entity, ref);
|
||||
}
|
||||
}
|
||||
|
||||
struct Handler final {
|
||||
Handler(ptr_type component, const fn_type accommodate, const fn_type assign, const component_type type)
|
||||
: component{std::move(component)},
|
||||
accommodate{accommodate},
|
||||
assign{assign},
|
||||
type{type}
|
||||
{}
|
||||
|
||||
ptr_type component{nullptr, +[](void *) {}};
|
||||
fn_type accommodate{nullptr};
|
||||
fn_type assign{nullptr};
|
||||
component_type type;
|
||||
};
|
||||
|
||||
public:
|
||||
/*! @brief Registry type. */
|
||||
using registry_type = Registry<Entity>;
|
||||
/*! @brief Underlying entity identifier. */
|
||||
using entity_type = Entity;
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = std::size_t;
|
||||
|
||||
/**
|
||||
* @brief Assigns to or replaces the given component of a prototype.
|
||||
* @tparam Component Type of component to assign or replace.
|
||||
* @tparam Args Types of arguments to use to construct the component.
|
||||
* @param args Parameters to use to initialize the component.
|
||||
* @return A reference to the newly created component.
|
||||
*/
|
||||
template<typename Component, typename... Args>
|
||||
Component & set(Args &&... args) {
|
||||
const auto ctype = registry_type::template type<Component>();
|
||||
|
||||
auto it = std::find_if(handlers.begin(), handlers.end(), [ctype](const auto &handler) {
|
||||
return handler.type == ctype;
|
||||
});
|
||||
|
||||
const auto deleter = +[](void *component) { delete static_cast<Component *>(component); };
|
||||
ptr_type component{new Component{std::forward<Args>(args)...}, deleter};
|
||||
|
||||
if(it == handlers.cend()) {
|
||||
handlers.emplace_back(std::move(component), &Prototype::accommodate<Component>, &Prototype::assign<Component>, ctype);
|
||||
} else {
|
||||
it->component = std::move(component);
|
||||
}
|
||||
|
||||
return get<Component>();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Removes the given component from a prototype.
|
||||
* @tparam Component Type of component to remove.
|
||||
*/
|
||||
template<typename Component>
|
||||
void unset() ENTT_NOEXCEPT {
|
||||
handlers.erase(std::remove_if(handlers.begin(), handlers.end(), [](const auto &handler) {
|
||||
return handler.type == registry_type::template type<Component>();
|
||||
}), handlers.end());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if a prototype owns all the given components.
|
||||
* @tparam Component Components for which to perform the check.
|
||||
* @return True if the prototype owns all the components, false otherwise.
|
||||
*/
|
||||
template<typename... Component>
|
||||
bool has() const ENTT_NOEXCEPT {
|
||||
auto found = [this](const auto ctype) {
|
||||
return std::find_if(handlers.cbegin(), handlers.cend(), [ctype](const auto &handler) {
|
||||
return handler.type == ctype;
|
||||
}) != handlers.cend();
|
||||
};
|
||||
|
||||
bool all = true;
|
||||
using accumulator_type = bool[];
|
||||
accumulator_type accumulator = { all, (all = all && found(registry_type::template type<Component>()))... };
|
||||
(void)accumulator;
|
||||
return all;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a reference to the given component.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to get a component from a prototype that doesn't own it
|
||||
* results in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* prototype doesn't own an instance of the given component.
|
||||
*
|
||||
* @tparam Component Type of component to get.
|
||||
* @return A reference to the component owned by the prototype.
|
||||
*/
|
||||
template<typename Component>
|
||||
const Component & get() const ENTT_NOEXCEPT {
|
||||
assert(has<Component>());
|
||||
|
||||
auto it = std::find_if(handlers.cbegin(), handlers.cend(), [](const auto &handler) {
|
||||
return handler.type == registry_type::template type<Component>();
|
||||
});
|
||||
|
||||
return *static_cast<Component *>(it->component.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a reference to the given component.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to get a component from a prototype that doesn't own it
|
||||
* results in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* prototype doesn't own an instance of the given component.
|
||||
*
|
||||
* @tparam Component Type of component to get.
|
||||
* @return A reference to the component owned by the prototype.
|
||||
*/
|
||||
template<typename Component>
|
||||
inline Component & get() ENTT_NOEXCEPT {
|
||||
return const_cast<Component &>(const_cast<const Prototype *>(this)->get<Component>());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a reference to the given components.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to get components from a prototype that doesn't own them
|
||||
* results in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* prototype doesn't own instances of the given components.
|
||||
*
|
||||
* @tparam Component Type of components to get.
|
||||
* @return References to the components owned by the prototype.
|
||||
*/
|
||||
template<typename... Component>
|
||||
std::enable_if_t<(sizeof...(Component) > 1), std::tuple<const Component &...>>
|
||||
get() const ENTT_NOEXCEPT {
|
||||
return std::tuple<const Component &...>{get<Component>()...};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a reference to the given components.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to get components from a prototype that doesn't own them
|
||||
* results in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* prototype doesn't own instances of the given components.
|
||||
*
|
||||
* @tparam Component Type of components to get.
|
||||
* @return References to the components owned by the prototype.
|
||||
*/
|
||||
template<typename... Component>
|
||||
std::enable_if_t<(sizeof...(Component) > 1), std::tuple<Component &...>>
|
||||
get() ENTT_NOEXCEPT {
|
||||
return std::tuple<Component &...>{get<Component>()...};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Creates a new entity using a given prototype.
|
||||
*
|
||||
* Utility shortcut, equivalent to the following snippet:
|
||||
*
|
||||
* @code{.cpp}
|
||||
* const auto entity = registry.create();
|
||||
* prototype(registry, entity);
|
||||
* @endcode
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an invalid entity results in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode in case of
|
||||
* invalid entity.
|
||||
*
|
||||
* @param registry A valid reference to a registry.
|
||||
* @return A valid entity identifier.
|
||||
*/
|
||||
entity_type create(registry_type ®istry) {
|
||||
const auto entity = registry.create();
|
||||
assign(registry, entity);
|
||||
return entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns the components of a prototype to a given entity.
|
||||
*
|
||||
* Assigning a prototype to an entity won't overwrite existing components
|
||||
* under any circumstances.<br/>
|
||||
* In other words, only those components that the entity doesn't own yet are
|
||||
* copied over. All the other components remain unchanged.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an invalid entity results in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode in case of
|
||||
* invalid entity.
|
||||
*
|
||||
* @param registry A valid reference to a registry.
|
||||
* @param entity A valid entity identifier.
|
||||
*/
|
||||
void assign(registry_type ®istry, const entity_type entity) {
|
||||
std::for_each(handlers.begin(), handlers.end(), [®istry, entity](auto &&handler) {
|
||||
handler.assign(registry, entity, handler.component.get());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns or replaces the components of a prototype for an entity.
|
||||
*
|
||||
* Existing components are overwritten, if any. All the other components
|
||||
* will be copied over to the target entity.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an invalid entity results in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode in case of
|
||||
* invalid entity.
|
||||
*
|
||||
* @param registry A valid reference to a registry.
|
||||
* @param entity A valid entity identifier.
|
||||
*/
|
||||
void accommodate(registry_type ®istry, const entity_type entity) {
|
||||
std::for_each(handlers.begin(), handlers.end(), [®istry, entity](auto &&handler) {
|
||||
handler.accommodate(registry, entity, handler.component.get());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns the components of a prototype to an entity.
|
||||
*
|
||||
* Assigning a prototype to an entity won't overwrite existing components
|
||||
* under any circumstances.<br/>
|
||||
* In other words, only the components that the entity doesn't own yet are
|
||||
* copied over. All the other components remain unchanged.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an invalid entity results in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode in case of
|
||||
* invalid entity.
|
||||
*
|
||||
* @param registry A valid reference to a registry.
|
||||
* @param entity A valid entity identifier.
|
||||
*/
|
||||
inline void operator()(registry_type ®istry, const entity_type entity) ENTT_NOEXCEPT {
|
||||
assign(registry, entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Creates a new entity using a given prototype.
|
||||
*
|
||||
* Utility shortcut, equivalent to the following snippet:
|
||||
*
|
||||
* @code{.cpp}
|
||||
* const auto entity = registry.create();
|
||||
* prototype(registry, entity);
|
||||
* @endcode
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an invalid entity results in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode in case of
|
||||
* invalid entity.
|
||||
*
|
||||
* @param registry A valid reference to a registry.
|
||||
* @return A valid entity identifier.
|
||||
*/
|
||||
inline entity_type operator()(registry_type ®istry) ENTT_NOEXCEPT {
|
||||
return create(registry);
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<Handler> handlers;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Default prototype
|
||||
*/
|
||||
using DefaultPrototype = Prototype<uint32_t>;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_ENTITY_PROTOTYPE_HPP
|
||||
File diff suppressed because it is too large
Load Diff
720
src/entt/entity/snapshot.hpp
Normal file
720
src/entt/entity/snapshot.hpp
Normal file
@@ -0,0 +1,720 @@
|
||||
#ifndef ENTT_ENTITY_SNAPSHOT_HPP
|
||||
#define ENTT_ENTITY_SNAPSHOT_HPP
|
||||
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <utility>
|
||||
#include <cassert>
|
||||
#include <iterator>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include "../config/config.h"
|
||||
#include "entt_traits.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/**
|
||||
* @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> &, const Entity);
|
||||
|
||||
Snapshot(const Registry<Entity> ®istry, Entity seed, follow_fn_type follow) ENTT_NOEXCEPT
|
||||
: registry{registry},
|
||||
seed{seed},
|
||||
follow{follow}
|
||||
{}
|
||||
|
||||
template<typename Component, typename Archive, typename It>
|
||||
void get(Archive &archive, std::size_t sz, It first, It last) {
|
||||
archive(static_cast<Entity>(sz));
|
||||
|
||||
while(first != last) {
|
||||
const auto entity = *(first++);
|
||||
|
||||
if(registry.template has<Component>(entity)) {
|
||||
archive(entity, registry.template get<Component>(entity));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename... Component, typename Archive, typename It, std::size_t... Indexes>
|
||||
void component(Archive &archive, It first, It last, std::index_sequence<Indexes...>) {
|
||||
std::array<std::size_t, sizeof...(Indexes)> size{};
|
||||
auto begin = first;
|
||||
|
||||
while(begin != last) {
|
||||
const auto entity = *(begin++);
|
||||
using accumulator_type = std::size_t[];
|
||||
accumulator_type accumulator = { (registry.template has<Component>(entity) ? ++size[Indexes] : size[Indexes])... };
|
||||
(void)accumulator;
|
||||
}
|
||||
|
||||
using accumulator_type = int[];
|
||||
accumulator_type accumulator = { (get<Component>(archive, size[Indexes], first, last), 0)... };
|
||||
(void)accumulator;
|
||||
}
|
||||
|
||||
public:
|
||||
/*! @brief Copying a snapshot isn't allowed. */
|
||||
Snapshot(const Snapshot &) = delete;
|
||||
/*! @brief Default move constructor. */
|
||||
Snapshot(Snapshot &&) = default;
|
||||
|
||||
/*! @brief Copying a snapshot isn't allowed. @return This snapshot. */
|
||||
Snapshot & operator=(const Snapshot &) = delete;
|
||||
/*! @brief Default move assignment operator. @return This snapshot. */
|
||||
Snapshot & operator=(Snapshot &&) = default;
|
||||
|
||||
/**
|
||||
* @brief Puts aside all the entities that are still in use.
|
||||
*
|
||||
* 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](const 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) {
|
||||
auto size = registry.capacity() - registry.size();
|
||||
archive(static_cast<Entity>(size));
|
||||
auto curr = seed;
|
||||
|
||||
for(; size; --size) {
|
||||
archive(curr);
|
||||
curr = follow(registry, curr);
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Puts aside the given component.
|
||||
*
|
||||
* Each instance is serialized together with the entity to which it belongs.
|
||||
* Entities are serialized along with their versions.
|
||||
*
|
||||
* @tparam Component Type of component to serialize.
|
||||
* @tparam Archive Type of output archive.
|
||||
* @param archive A valid reference to an output archive.
|
||||
* @return An object of this type to continue creating the snapshot.
|
||||
*/
|
||||
template<typename Component, typename Archive>
|
||||
Snapshot & component(Archive &archive) {
|
||||
const auto sz = registry.template size<Component>();
|
||||
const auto *entities = registry.template data<Component>();
|
||||
|
||||
archive(static_cast<Entity>(sz));
|
||||
|
||||
for(std::remove_const_t<decltype(sz)> i{}; i < sz; ++i) {
|
||||
const auto entity = entities[i];
|
||||
archive(entity, registry.template get<Component>(entity));
|
||||
};
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Puts aside the given components.
|
||||
*
|
||||
* Each instance is serialized together with the entity to which it belongs.
|
||||
* Entities are serialized along with their versions.
|
||||
*
|
||||
* @tparam Component Types of components to serialize.
|
||||
* @tparam Archive Type of output archive.
|
||||
* @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>
|
||||
std::enable_if_t<(sizeof...(Component) > 1), Snapshot &>
|
||||
component(Archive &archive) {
|
||||
using accumulator_type = int[];
|
||||
accumulator_type accumulator = { 0, (component<Component>(archive), 0)... };
|
||||
(void)accumulator;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Puts aside the given components for the entities in a range.
|
||||
*
|
||||
* Each instance is serialized together with the entity to which it belongs.
|
||||
* Entities are serialized along with their versions.
|
||||
*
|
||||
* @tparam Component Types of components to serialize.
|
||||
* @tparam Archive Type of output archive.
|
||||
* @tparam It Type of input iterator.
|
||||
* @param archive A valid reference to an output archive.
|
||||
* @param first An iterator to the first element of the range to serialize.
|
||||
* @param last An iterator past the last element of the range to serialize.
|
||||
* @return An object of this type to continue creating the snapshot.
|
||||
*/
|
||||
template<typename... Component, typename Archive, typename It>
|
||||
Snapshot & component(Archive &archive, It first, It last) {
|
||||
component<Component...>(archive, first, last, std::make_index_sequence<sizeof...(Component)>{});
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Puts aside the given tag.
|
||||
*
|
||||
* Each instance is serialized together with the entity to which it belongs.
|
||||
* Entities are serialized along with their versions.
|
||||
*
|
||||
* @tparam Tag Type of tag to serialize.
|
||||
* @tparam Archive Type of output archive.
|
||||
* @param archive A valid reference to an output archive.
|
||||
* @return An object of this type to continue creating the snapshot.
|
||||
*/
|
||||
template<typename Tag, typename Archive>
|
||||
Snapshot & tag(Archive &archive) {
|
||||
const bool has = registry.template has<Tag>();
|
||||
|
||||
// numerical length is forced for tags to facilitate loading
|
||||
archive(has ? Entity(1): Entity{});
|
||||
|
||||
if(has) {
|
||||
archive(registry.template attachee<Tag>(), registry.template get<Tag>());
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Puts aside the given tags.
|
||||
*
|
||||
* Each instance is serialized together with the entity to which it belongs.
|
||||
* Entities are serialized along with their versions.
|
||||
*
|
||||
* @tparam Tag Types of tags to serialize.
|
||||
* @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>
|
||||
std::enable_if_t<(sizeof...(Tag) > 1), Snapshot &>
|
||||
tag(Archive &archive) {
|
||||
using accumulator_type = int[];
|
||||
accumulator_type accumulator = { 0, (tag<Tag>(archive), 0)... };
|
||||
(void)accumulator;
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
const Registry<Entity> ®istry;
|
||||
const Entity seed;
|
||||
follow_fn_type follow;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @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> &, const Entity, const bool);
|
||||
|
||||
SnapshotLoader(Registry<Entity> ®istry, assure_fn_type assure_fn) ENTT_NOEXCEPT
|
||||
: registry{registry},
|
||||
assure_fn{assure_fn}
|
||||
{
|
||||
// restore a snapshot as a whole requires a clean registry
|
||||
assert(!registry.capacity());
|
||||
}
|
||||
|
||||
template<typename Archive>
|
||||
void assure(Archive &archive, bool destroyed) {
|
||||
Entity length{};
|
||||
archive(length);
|
||||
|
||||
while(length--) {
|
||||
Entity entity{};
|
||||
archive(entity);
|
||||
assure_fn(registry, entity, destroyed);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Type, typename Archive, typename... Args>
|
||||
void assign(Archive &archive, Args... args) {
|
||||
Entity length{};
|
||||
archive(length);
|
||||
|
||||
while(length--) {
|
||||
Entity entity{};
|
||||
Type instance{};
|
||||
archive(entity, instance);
|
||||
static constexpr auto destroyed = false;
|
||||
assure_fn(registry, entity, destroyed);
|
||||
registry.template assign<Type>(args..., entity, static_cast<const Type &>(instance));
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
/*! @brief Copying a snapshot loader isn't allowed. */
|
||||
SnapshotLoader(const SnapshotLoader &) = delete;
|
||||
/*! @brief Default move constructor. */
|
||||
SnapshotLoader(SnapshotLoader &&) = default;
|
||||
|
||||
/*! @brief Copying a snapshot loader isn't allowed. @return This loader. */
|
||||
SnapshotLoader & operator=(const SnapshotLoader &) = delete;
|
||||
/*! @brief Default move assignment operator. @return This loader. */
|
||||
SnapshotLoader & operator=(SnapshotLoader &&) = default;
|
||||
|
||||
/**
|
||||
* @brief Restores entities that were in use during serialization.
|
||||
*
|
||||
* 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) {
|
||||
static constexpr auto destroyed = false;
|
||||
assure(archive, 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) {
|
||||
static constexpr auto destroyed = true;
|
||||
assure(archive, 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, (assign<Tag>(archive, tag_t{}), 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](const auto entity) {
|
||||
registry.destroy(entity);
|
||||
});
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
Registry<Entity> ®istry;
|
||||
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>;
|
||||
|
||||
void 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);
|
||||
}
|
||||
}
|
||||
|
||||
void 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;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Type, typename Member>
|
||||
std::enable_if_t<std::is_same<Member, Entity>::value>
|
||||
update(Type &instance, Member Type:: *member) {
|
||||
instance.*member = map(instance.*member);
|
||||
}
|
||||
|
||||
template<typename Type, typename Member>
|
||||
std::enable_if_t<std::is_same<typename std::iterator_traits<typename Member::iterator>::value_type, Entity>::value>
|
||||
update(Type &instance, Member Type:: *member) {
|
||||
for(auto &entity: instance.*member) {
|
||||
entity = map(entity);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Other, typename Type, typename Member>
|
||||
std::enable_if_t<!std::is_same<Other, Type>::value>
|
||||
update(Other &, Member Type:: *) {}
|
||||
|
||||
template<typename Archive>
|
||||
void assure(Archive &archive, void(ContinuousLoader:: *member)(Entity)) {
|
||||
Entity length{};
|
||||
archive(length);
|
||||
|
||||
while(length--) {
|
||||
Entity entity{};
|
||||
archive(entity);
|
||||
(this->*member)(entity);
|
||||
}
|
||||
}
|
||||
|
||||
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 Other, typename Archive, typename Func, typename... Type, typename... Member>
|
||||
void assign(Archive &archive, Func func, Member Type:: *... member) {
|
||||
Entity length{};
|
||||
archive(length);
|
||||
|
||||
while(length--) {
|
||||
Entity entity{};
|
||||
Other instance{};
|
||||
|
||||
archive(entity, instance);
|
||||
restore(entity);
|
||||
|
||||
using accumulator_type = int[];
|
||||
accumulator_type accumulator = { 0, (update(instance, member), 0)... };
|
||||
(void)accumulator;
|
||||
|
||||
func(map(entity), instance);
|
||||
}
|
||||
}
|
||||
|
||||
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> ®istry) ENTT_NOEXCEPT
|
||||
: registry{registry}
|
||||
{}
|
||||
|
||||
/*! @brief Copying a snapshot loader isn't allowed. */
|
||||
ContinuousLoader(const ContinuousLoader &) = delete;
|
||||
/*! @brief Default move constructor. */
|
||||
ContinuousLoader(ContinuousLoader &&) = default;
|
||||
|
||||
/*! @brief Copying a snapshot loader isn't allowed. @return This loader. */
|
||||
ContinuousLoader & operator=(const ContinuousLoader &) = delete;
|
||||
/*! @brief Default move assignment operator. @return This loader. */
|
||||
ContinuousLoader & operator=(ContinuousLoader &&) = default;
|
||||
|
||||
/**
|
||||
* @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) {
|
||||
assure(archive, &ContinuousLoader::restore);
|
||||
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) {
|
||||
assure(archive, &ContinuousLoader::destroy);
|
||||
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 components to update with local counterparts.
|
||||
* @tparam Member Types of members to update with their local counterparts.
|
||||
* @param archive A valid reference to an input archive.
|
||||
* @param member Members to update with their local counterparts.
|
||||
* @return A non-const reference to this loader.
|
||||
*/
|
||||
template<typename... Component, typename Archive, typename... Type, typename... Member>
|
||||
ContinuousLoader & component(Archive &archive, Member Type:: *... member) {
|
||||
auto apply = [this](const auto entity, const auto &component) {
|
||||
registry.template accommodate<std::decay_t<decltype(component)>>(entity, component);
|
||||
};
|
||||
|
||||
using accumulator_type = int[];
|
||||
accumulator_type accumulator = { 0, (reset<Component>(), assign<Component>(archive, apply, member...), 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 components to update with local counterparts.
|
||||
* @tparam Member Types of members to update with their local counterparts.
|
||||
* @param archive A valid reference to an input archive.
|
||||
* @param member Members to update with their local counterparts.
|
||||
* @return A non-const reference to this loader.
|
||||
*/
|
||||
template<typename... Tag, typename Archive, typename... Type, typename... Member>
|
||||
ContinuousLoader & tag(Archive &archive, Member Type:: *... member) {
|
||||
auto apply = [this](const auto entity, const auto &tag) {
|
||||
registry.template assign<std::decay_t<decltype(tag)>>(tag_t{}, entity, tag);
|
||||
};
|
||||
|
||||
using accumulator_type = int[];
|
||||
accumulator_type accumulator = { 0, (registry.template remove<Tag>(), assign<Tag>(archive, apply, member...), 0)... };
|
||||
(void)accumulator;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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](const 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> ®istry;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_ENTITY_SNAPSHOT_HPP
|
||||
@@ -3,13 +3,16 @@
|
||||
|
||||
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include <numeric>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <cstddef>
|
||||
#include <cassert>
|
||||
#include <type_traits>
|
||||
#include "traits.hpp"
|
||||
#include "../config/config.h"
|
||||
#include "../core/algorithm.hpp"
|
||||
#include "entt_traits.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
@@ -56,40 +59,53 @@ 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 = const value_type *;
|
||||
using reference = value_type;
|
||||
using iterator_category = std::input_iterator_tag;
|
||||
|
||||
Iterator(const std::vector<Entity> *direct, std::size_t pos)
|
||||
Iterator(pointer direct, std::size_t pos)
|
||||
: direct{direct}, pos{pos}
|
||||
{}
|
||||
|
||||
Iterator & operator++() noexcept {
|
||||
Iterator & operator++() ENTT_NOEXCEPT {
|
||||
return --pos, *this;
|
||||
}
|
||||
|
||||
Iterator operator++(int) noexcept {
|
||||
Iterator operator++(int) ENTT_NOEXCEPT {
|
||||
Iterator orig = *this;
|
||||
return ++(*this), orig;
|
||||
}
|
||||
|
||||
bool operator==(const Iterator &other) const noexcept {
|
||||
return other.pos == pos && other.direct == direct;
|
||||
Iterator & operator+=(const difference_type value) ENTT_NOEXCEPT {
|
||||
pos -= value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool operator!=(const Iterator &other) const noexcept {
|
||||
Iterator operator+(const difference_type value) const ENTT_NOEXCEPT {
|
||||
return Iterator{direct, pos-value};
|
||||
}
|
||||
|
||||
bool operator==(const Iterator &other) const ENTT_NOEXCEPT {
|
||||
return other.pos == pos;
|
||||
}
|
||||
|
||||
inline bool operator!=(const Iterator &other) const ENTT_NOEXCEPT {
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
value_type operator*() const noexcept {
|
||||
return (*direct)[pos-1];
|
||||
reference operator*() const ENTT_NOEXCEPT {
|
||||
return direct[pos-1];
|
||||
}
|
||||
|
||||
private:
|
||||
const std::vector<Entity> *direct;
|
||||
pointer direct;
|
||||
std::size_t pos;
|
||||
};
|
||||
|
||||
static constexpr Entity in_use = (Entity{1} << traits_type::entity_shift);
|
||||
static constexpr auto pending = ~typename traits_type::entity_type{};
|
||||
|
||||
public:
|
||||
/*! @brief Underlying entity identifier. */
|
||||
@@ -100,12 +116,14 @@ public:
|
||||
using size_type = std::size_t;
|
||||
/*! @brief Input iterator type. */
|
||||
using iterator_type = Iterator;
|
||||
/*! @brief Constant input iterator type. */
|
||||
using const_iterator_type = Iterator;
|
||||
|
||||
/*! @brief Default constructor. */
|
||||
SparseSet() noexcept = default;
|
||||
SparseSet() ENTT_NOEXCEPT = default;
|
||||
|
||||
/*! @brief Default destructor. */
|
||||
virtual ~SparseSet() noexcept = default;
|
||||
virtual ~SparseSet() ENTT_NOEXCEPT = default;
|
||||
|
||||
/*! @brief Copying a sparse set isn't allowed. */
|
||||
SparseSet(const SparseSet &) = delete;
|
||||
@@ -125,11 +143,24 @@ public:
|
||||
*
|
||||
* @param cap Desired capacity.
|
||||
*/
|
||||
void reserve(size_type cap) {
|
||||
reverse.reserve(cap);
|
||||
void reserve(const size_type 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 ENTT_NOEXCEPT {
|
||||
return reverse.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the number of elements in a sparse set.
|
||||
*
|
||||
@@ -140,7 +171,7 @@ public:
|
||||
*
|
||||
* @return Number of elements.
|
||||
*/
|
||||
size_type size() const noexcept {
|
||||
size_type size() const ENTT_NOEXCEPT {
|
||||
return direct.size();
|
||||
}
|
||||
|
||||
@@ -148,7 +179,7 @@ public:
|
||||
* @brief Checks whether a sparse set is empty.
|
||||
* @return True if the sparse set is empty, false otherwise.
|
||||
*/
|
||||
bool empty() const noexcept {
|
||||
bool empty() const ENTT_NOEXCEPT {
|
||||
return direct.empty();
|
||||
}
|
||||
|
||||
@@ -159,7 +190,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
|
||||
@@ -167,41 +198,107 @@ public:
|
||||
*
|
||||
* @return A pointer to the internal packed array.
|
||||
*/
|
||||
const entity_type * data() const noexcept {
|
||||
const entity_type * data() const ENTT_NOEXCEPT {
|
||||
return direct.data();
|
||||
}
|
||||
|
||||
/**
|
||||
* @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()};
|
||||
const_iterator_type cbegin() const ENTT_NOEXCEPT {
|
||||
return const_iterator_type{direct.data(), direct.size()};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns an iterator to the beginning.
|
||||
*
|
||||
* The returned iterator points to the first entity of the internal packed
|
||||
* array. If the sparse set is empty, the returned iterator will be equal to
|
||||
* `end()`.
|
||||
*
|
||||
* @note
|
||||
* Input iterators stay true to the order imposed by a call to `respect`.
|
||||
*
|
||||
* @return An iterator to the first entity of the internal packed array.
|
||||
*/
|
||||
inline const_iterator_type begin() const ENTT_NOEXCEPT {
|
||||
return cbegin();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns an iterator to the beginning.
|
||||
*
|
||||
* The returned iterator points to the first entity of the internal packed
|
||||
* array. If the sparse set is empty, the returned iterator will be equal to
|
||||
* `end()`.
|
||||
*
|
||||
* @note
|
||||
* Input iterators stay true to the order imposed by a call to `respect`.
|
||||
*
|
||||
* @return An iterator to the first entity of the internal packed array.
|
||||
*/
|
||||
inline iterator_type begin() ENTT_NOEXCEPT {
|
||||
return cbegin();
|
||||
}
|
||||
|
||||
/**
|
||||
* @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};
|
||||
const_iterator_type cend() const ENTT_NOEXCEPT {
|
||||
return const_iterator_type{direct.data(), {}};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns an iterator to the end.
|
||||
*
|
||||
* The returned iterator points to the element following the last entity in
|
||||
* the internal packed array. Attempting to dereference the returned
|
||||
* iterator results in undefined behavior.
|
||||
*
|
||||
* @note
|
||||
* Input iterators stay true to the order imposed by a call to `respect`.
|
||||
*
|
||||
* @return An iterator to the element following the last entity of the
|
||||
* internal packed array.
|
||||
*/
|
||||
inline const_iterator_type end() const ENTT_NOEXCEPT {
|
||||
return cend();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns an iterator to the end.
|
||||
*
|
||||
* The returned iterator points to the element following the last entity in
|
||||
* the internal packed array. Attempting to dereference the returned
|
||||
* iterator results in undefined behavior.
|
||||
*
|
||||
* @note
|
||||
* Input iterators stay true to the order imposed by a call to `respect`.
|
||||
*
|
||||
* @return An iterator to the element following the last entity of the
|
||||
* internal packed array.
|
||||
*/
|
||||
inline iterator_type end() ENTT_NOEXCEPT {
|
||||
return cend();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -209,12 +306,34 @@ public:
|
||||
* @param entity A valid entity identifier.
|
||||
* @return True if the sparse set contains the entity, false otherwise.
|
||||
*/
|
||||
bool has(entity_type entity) const noexcept {
|
||||
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;
|
||||
// the in-use control bit permits to avoid accessing the direct vector
|
||||
return (entt < reverse.size()) && (reverse[entt] & in_use);
|
||||
bool has(const entity_type entity) const ENTT_NOEXCEPT {
|
||||
const auto pos = size_type(entity & traits_type::entity_mask);
|
||||
// testing against pending permits to avoid accessing the direct vector
|
||||
return (pos < reverse.size()) && (reverse[pos] != pending);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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(const entity_type entity) const ENTT_NOEXCEPT {
|
||||
const auto pos = size_type(entity & traits_type::entity_mask);
|
||||
assert(pos < reverse.size());
|
||||
// testing against pending permits to avoid accessing the direct vector
|
||||
return (reverse[pos] != pending);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -229,11 +348,9 @@ public:
|
||||
* @param entity A valid entity identifier.
|
||||
* @return The position of the entity in the sparse set.
|
||||
*/
|
||||
pos_type get(entity_type entity) const noexcept {
|
||||
pos_type get(const entity_type entity) const ENTT_NOEXCEPT {
|
||||
assert(has(entity));
|
||||
const auto entt = entity & traits_type::entity_mask;
|
||||
// we must get rid of the in-use bit for it's not part of the position
|
||||
return reverse[entt] & ~in_use;
|
||||
return reverse[entity & traits_type::entity_mask];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -247,20 +364,39 @@ public:
|
||||
*
|
||||
* @param entity A valid entity identifier.
|
||||
*/
|
||||
void construct(entity_type entity) {
|
||||
void construct(const 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())) {
|
||||
const auto value = pending;
|
||||
reverse.resize(pos+1, value);
|
||||
}
|
||||
|
||||
// we exploit the fact that pos_type is equal to entity_type and pos has
|
||||
// traits_type::version_mask bits unused we can use to mark it as in-use
|
||||
reverse[entt] = pos_type(direct.size()) | in_use;
|
||||
direct.emplace_back(entity);
|
||||
reverse[pos] = pos_type(direct.size());
|
||||
direct.push_back(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns an entity to a sparse set by cloning another entity.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to clone an entity that doesn't belong to the sparse set or to
|
||||
* assign an entity that already belongs to the sparse set results in
|
||||
* undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* sparse set doesn't contain the entity to clone or if it already contains
|
||||
* the given entity.
|
||||
*
|
||||
* @param entity A valid entity identifier.
|
||||
* @param source A valid entity identifier from which to clone.
|
||||
*/
|
||||
inline virtual void clone(const entity_type entity, const entity_type source) {
|
||||
assert(has(source));
|
||||
assert(!has(entity));
|
||||
construct(entity);
|
||||
// useful to suppress warnings when asserts are disabled
|
||||
(void)source;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -274,17 +410,14 @@ public:
|
||||
*
|
||||
* @param entity A valid entity identifier.
|
||||
*/
|
||||
virtual void destroy(entity_type entity) {
|
||||
virtual void destroy(const entity_type entity) {
|
||||
assert(has(entity));
|
||||
const auto entt = entity & traits_type::entity_mask;
|
||||
const auto back = direct.back() & traits_type::entity_mask;
|
||||
const auto pos = reverse[entt] & ~in_use;
|
||||
// the order matters: if back and entt are the same (for the sparse set
|
||||
// has size 1), switching the two lines below doesn't work as expected
|
||||
reverse[back] = pos | in_use;
|
||||
reverse[entt] = pos;
|
||||
const auto back = direct.back();
|
||||
auto &candidate = reverse[entity & traits_type::entity_mask];
|
||||
// swapping isn't required here, we are getting rid of the last element
|
||||
direct[pos] = direct.back();
|
||||
reverse[back & traits_type::entity_mask] = candidate;
|
||||
direct[candidate] = back;
|
||||
candidate = pending;
|
||||
direct.pop_back();
|
||||
}
|
||||
|
||||
@@ -303,13 +436,13 @@ public:
|
||||
* @param lhs A valid position within the sparse set.
|
||||
* @param rhs A valid position within the sparse set.
|
||||
*/
|
||||
void swap(pos_type lhs, pos_type rhs) noexcept {
|
||||
void swap(const pos_type lhs, const pos_type rhs) ENTT_NOEXCEPT {
|
||||
assert(lhs < direct.size());
|
||||
assert(rhs < direct.size());
|
||||
const auto src = direct[lhs] & traits_type::entity_mask;
|
||||
const auto dst = direct[rhs] & traits_type::entity_mask;
|
||||
std::swap(reverse[src], reverse[dst]);
|
||||
std::swap(direct[lhs], direct[rhs]);
|
||||
auto &src = direct[lhs];
|
||||
auto &dst = direct[rhs];
|
||||
std::swap(reverse[src & traits_type::entity_mask], reverse[dst & traits_type::entity_mask]);
|
||||
std::swap(src, dst);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -322,22 +455,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 {
|
||||
auto from = other.begin();
|
||||
auto to = other.end();
|
||||
void respect(const SparseSet<Entity> &other) ENTT_NOEXCEPT {
|
||||
auto from = other.cbegin();
|
||||
auto to = other.cend();
|
||||
|
||||
pos_type pos = direct.size() - 1;
|
||||
|
||||
while(pos > 0 && from != to) {
|
||||
while(pos && from != to) {
|
||||
if(has(*from)) {
|
||||
if(*from != direct[pos]) {
|
||||
swap(pos, get(*from));
|
||||
@@ -390,6 +523,57 @@ template<typename Entity, typename Type>
|
||||
class SparseSet<Entity, Type>: public SparseSet<Entity> {
|
||||
using underlying_type = SparseSet<Entity>;
|
||||
|
||||
template<bool Const>
|
||||
struct Iterator final {
|
||||
using difference_type = std::size_t;
|
||||
using value_type = std::conditional_t<Const, const Type, Type>;
|
||||
using pointer = value_type *;
|
||||
using reference = value_type &;
|
||||
using iterator_category = std::input_iterator_tag;
|
||||
|
||||
Iterator(pointer instances, std::size_t pos)
|
||||
: instances{instances}, pos{pos}
|
||||
{}
|
||||
|
||||
Iterator & operator++() ENTT_NOEXCEPT {
|
||||
return --pos, *this;
|
||||
}
|
||||
|
||||
Iterator operator++(int) ENTT_NOEXCEPT {
|
||||
Iterator orig = *this;
|
||||
return ++(*this), orig;
|
||||
}
|
||||
|
||||
Iterator & operator+=(const difference_type value) ENTT_NOEXCEPT {
|
||||
pos -= value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Iterator operator+(const difference_type value) const ENTT_NOEXCEPT {
|
||||
return Iterator{instances, pos-value};
|
||||
}
|
||||
|
||||
bool operator==(const Iterator &other) const ENTT_NOEXCEPT {
|
||||
return other.pos == pos;
|
||||
}
|
||||
|
||||
inline bool operator!=(const Iterator &other) const ENTT_NOEXCEPT {
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
reference operator*() const ENTT_NOEXCEPT {
|
||||
return instances[pos-1];
|
||||
}
|
||||
|
||||
pointer operator->() const ENTT_NOEXCEPT {
|
||||
return (instances+pos-1);
|
||||
}
|
||||
|
||||
private:
|
||||
pointer instances;
|
||||
std::size_t pos;
|
||||
};
|
||||
|
||||
public:
|
||||
/*! @brief Type of the objects associated to the entities. */
|
||||
using object_type = Type;
|
||||
@@ -400,10 +584,12 @@ 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<false>;
|
||||
/*! @brief Constant input iterator type. */
|
||||
using const_iterator_type = Iterator<true>;
|
||||
|
||||
/*! @brief Default constructor. */
|
||||
SparseSet() noexcept = default;
|
||||
SparseSet() ENTT_NOEXCEPT = default;
|
||||
|
||||
/*! @brief Copying a sparse set isn't allowed. */
|
||||
SparseSet(const SparseSet &) = delete;
|
||||
@@ -423,7 +609,7 @@ public:
|
||||
*
|
||||
* @param cap Desired capacity.
|
||||
*/
|
||||
void reserve(size_type cap) {
|
||||
void reserve(const size_type cap) {
|
||||
underlying_type::reserve(cap);
|
||||
instances.reserve(cap);
|
||||
}
|
||||
@@ -435,15 +621,15 @@ 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.
|
||||
*/
|
||||
const object_type * raw() const noexcept {
|
||||
const object_type * raw() const ENTT_NOEXCEPT {
|
||||
return instances.data();
|
||||
}
|
||||
|
||||
@@ -454,18 +640,120 @@ 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.
|
||||
*/
|
||||
object_type * raw() noexcept {
|
||||
object_type * raw() ENTT_NOEXCEPT {
|
||||
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.
|
||||
*/
|
||||
const_iterator_type cbegin() const ENTT_NOEXCEPT {
|
||||
return const_iterator_type{instances.data(), instances.size()};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns an iterator to the beginning.
|
||||
*
|
||||
* The returned iterator points to the first instance of the given type. If
|
||||
* the sparse set is empty, the returned iterator will be equal to `end()`.
|
||||
*
|
||||
* @note
|
||||
* Input iterators stay true to the order imposed by a call to either `sort`
|
||||
* or `respect`.
|
||||
*
|
||||
* @return An iterator to the first instance of the given type.
|
||||
*/
|
||||
inline const_iterator_type begin() const ENTT_NOEXCEPT {
|
||||
return cbegin();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns an iterator to the beginning.
|
||||
*
|
||||
* The returned iterator points to the first instance of the given type. If
|
||||
* the sparse set is empty, the returned iterator will be equal to `end()`.
|
||||
*
|
||||
* @note
|
||||
* Input iterators stay true to the order imposed by a call to either `sort`
|
||||
* or `respect`.
|
||||
*
|
||||
* @return An iterator to the first instance of the given type.
|
||||
*/
|
||||
iterator_type begin() ENTT_NOEXCEPT {
|
||||
return iterator_type{instances.data(), instances.size()};
|
||||
}
|
||||
|
||||
/**
|
||||
* @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.
|
||||
*/
|
||||
const_iterator_type cend() const ENTT_NOEXCEPT {
|
||||
return const_iterator_type{instances.data(), {}};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns an iterator to the end.
|
||||
*
|
||||
* The returned iterator points to the element following the last instance
|
||||
* of the given type. Attempting to dereference the returned iterator
|
||||
* results in undefined behavior.
|
||||
*
|
||||
* @note
|
||||
* Input iterators stay true to the order imposed by a call to either `sort`
|
||||
* or `respect`.
|
||||
*
|
||||
* @return An iterator to the element following the last instance of the
|
||||
* given type.
|
||||
*/
|
||||
inline const_iterator_type end() const ENTT_NOEXCEPT {
|
||||
return cend();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns an iterator to the end.
|
||||
*
|
||||
* The returned iterator points to the element following the last instance
|
||||
* of the given type. Attempting to dereference the returned iterator
|
||||
* results in undefined behavior.
|
||||
*
|
||||
* @note
|
||||
* Input iterators stay true to the order imposed by a call to either `sort`
|
||||
* or `respect`.
|
||||
*
|
||||
* @return An iterator to the element following the last instance of the
|
||||
* given type.
|
||||
*/
|
||||
iterator_type end() ENTT_NOEXCEPT {
|
||||
return iterator_type{instances.data(), {}};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the object associated to an entity.
|
||||
*
|
||||
@@ -478,7 +766,7 @@ public:
|
||||
* @param entity A valid entity identifier.
|
||||
* @return The object associated to the entity.
|
||||
*/
|
||||
const object_type & get(entity_type entity) const noexcept {
|
||||
const object_type & get(const entity_type entity) const ENTT_NOEXCEPT {
|
||||
return instances[underlying_type::get(entity)];
|
||||
}
|
||||
|
||||
@@ -494,13 +782,19 @@ public:
|
||||
* @param entity A valid entity identifier.
|
||||
* @return The object associated to the entity.
|
||||
*/
|
||||
object_type & get(entity_type entity) noexcept {
|
||||
inline object_type & get(const entity_type entity) ENTT_NOEXCEPT {
|
||||
return const_cast<object_type &>(const_cast<const SparseSet *>(this)->get(entity));
|
||||
}
|
||||
|
||||
/**
|
||||
* @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,13 +807,59 @@ 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(const 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(const entity_type entity, Args &&... args) {
|
||||
underlying_type::construct(entity);
|
||||
instances.emplace_back(Type{std::forward<Args>(args)...});
|
||||
return instances.back();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns an entity to a sparse set by cloning another entity.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to clone an entity that doesn't belong to the sparse set or to
|
||||
* assign an entity that already belongs to the sparse set results in
|
||||
* undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* sparse set doesn't contain the entity to clone or if it already contains
|
||||
* the given entity.
|
||||
*
|
||||
* @param entity A valid entity identifier.
|
||||
* @param source A valid entity identifier from which to clone.
|
||||
*/
|
||||
inline void clone(const entity_type entity, const entity_type source) override {
|
||||
construct(entity, get(source));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Removes an entity from a sparse set and destroies its object.
|
||||
*
|
||||
@@ -531,9 +871,11 @@ public:
|
||||
*
|
||||
* @param entity A valid entity identifier.
|
||||
*/
|
||||
void destroy(entity_type entity) override {
|
||||
void destroy(const entity_type entity) override {
|
||||
// swapping isn't required here, we are getting rid of the last element
|
||||
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);
|
||||
}
|
||||
@@ -553,19 +895,36 @@ public:
|
||||
* bool(const Type &, const Type &)
|
||||
* @endcode
|
||||
*
|
||||
* Moreover, the comparison function object shall induce a
|
||||
* _strict weak ordering_ on the values.
|
||||
*
|
||||
* The sort function oject must offer a member function template
|
||||
* `operator()` that accepts three arguments:
|
||||
*
|
||||
* * An iterator to the first element of the range to sort.
|
||||
* * An iterator past the last element of the range to sort.
|
||||
* * A comparison function to use to compare the elements.
|
||||
*
|
||||
* The comparison funtion object received by the sort function object hasn't
|
||||
* necessarily the type of the one passed along with the other parameters to
|
||||
* this member function.
|
||||
*
|
||||
* @note
|
||||
* Attempting to iterate elements using 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.
|
||||
* @tparam Sort Type of sort function object.
|
||||
* @param compare A valid comparison function object.
|
||||
* @param sort A valid sort function object.
|
||||
*/
|
||||
template<typename Compare>
|
||||
void sort(Compare compare) {
|
||||
template<typename Compare, typename Sort = StdSort>
|
||||
void sort(Compare compare, Sort sort = Sort{}) {
|
||||
std::vector<pos_type> copy(instances.size());
|
||||
std::iota(copy.begin(), copy.end(), 0);
|
||||
|
||||
std::sort(copy.begin(), copy.end(), [this, compare = std::move(compare)](auto lhs, auto rhs) {
|
||||
sort(copy.begin(), copy.end(), [this, compare = std::move(compare)](const auto lhs, const auto rhs) {
|
||||
return compare(const_cast<const object_type &>(instances[rhs]), const_cast<const object_type &>(instances[lhs]));
|
||||
});
|
||||
|
||||
@@ -574,8 +933,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 +957,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 {
|
||||
auto from = other.begin();
|
||||
auto to = other.end();
|
||||
void respect(const SparseSet<Entity> &other) ENTT_NOEXCEPT {
|
||||
auto from = other.cbegin();
|
||||
auto to = other.cend();
|
||||
|
||||
pos_type pos = underlying_type::size() - 1;
|
||||
const auto *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);
|
||||
}
|
||||
|
||||
27
src/entt/entity/utility.hpp
Normal file
27
src/entt/entity/utility.hpp
Normal file
@@ -0,0 +1,27 @@
|
||||
#ifndef ENTT_ENTITY_UTILITY_HPP
|
||||
#define ENTT_ENTITY_UTILITY_HPP
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/*! @brief Tag class type used to disambiguate overloads. */
|
||||
struct tag_t final {};
|
||||
|
||||
|
||||
/*! @brief Persistent view type used to disambiguate overloads. */
|
||||
struct persistent_t final {};
|
||||
|
||||
|
||||
/*! @brief Raw view type used to disambiguate overloads. */
|
||||
struct raw_t final {};
|
||||
|
||||
|
||||
/*! @brief Break type used to disambiguate overloads. */
|
||||
struct break_t final {};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_ENTITY_UTILITY_HPP
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,10 +1,15 @@
|
||||
#include "core/algorithm.hpp"
|
||||
#include "core/family.hpp"
|
||||
#include "core/hashed_string.hpp"
|
||||
#include "core/ident.hpp"
|
||||
#include "entity/actor.hpp"
|
||||
#include "entity/entt_traits.hpp"
|
||||
#include "entity/helper.hpp"
|
||||
#include "entity/prototype.hpp"
|
||||
#include "entity/registry.hpp"
|
||||
#include "entity/snapshot.hpp"
|
||||
#include "entity/sparse_set.hpp"
|
||||
#include "entity/traits.hpp"
|
||||
#include "entity/utility.hpp"
|
||||
#include "entity/view.hpp"
|
||||
#include "locator/locator.hpp"
|
||||
#include "process/process.hpp"
|
||||
@@ -12,8 +17,7 @@
|
||||
#include "resource/cache.hpp"
|
||||
#include "resource/handle.hpp"
|
||||
#include "resource/loader.hpp"
|
||||
#include "signal/bus.hpp"
|
||||
#include "signal/delegate.hpp"
|
||||
#include "signal/dispatcher.hpp"
|
||||
#include "signal/emitter.hpp"
|
||||
#include "signal/sigh.hpp"
|
||||
#include "signal/signal.hpp"
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <cassert>
|
||||
#include "../config/config.h"
|
||||
|
||||
|
||||
namespace entt {
|
||||
@@ -14,7 +15,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.
|
||||
@@ -35,7 +36,7 @@ struct ServiceLocator final {
|
||||
* @brief Tests if a valid service implementation is set.
|
||||
* @return True if the service is set, false otherwise.
|
||||
*/
|
||||
inline static bool empty() noexcept {
|
||||
inline static bool empty() ENTT_NOEXCEPT {
|
||||
return !static_cast<bool>(service);
|
||||
}
|
||||
|
||||
@@ -49,7 +50,7 @@ struct ServiceLocator final {
|
||||
*
|
||||
* @return A reference to the service implementation currently set, if any.
|
||||
*/
|
||||
inline static std::weak_ptr<Service> get() noexcept {
|
||||
inline static std::weak_ptr<Service> get() ENTT_NOEXCEPT {
|
||||
return service;
|
||||
}
|
||||
|
||||
@@ -67,7 +68,7 @@ struct ServiceLocator final {
|
||||
*
|
||||
* @return A reference to the service implementation currently set, if any.
|
||||
*/
|
||||
inline static Service & ref() noexcept {
|
||||
inline static Service & ref() ENTT_NOEXCEPT {
|
||||
return *service;
|
||||
}
|
||||
|
||||
@@ -78,7 +79,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)...);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <type_traits>
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
#include "../config/config.h"
|
||||
|
||||
|
||||
namespace entt {
|
||||
@@ -19,33 +20,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 +115,7 @@ class Process {
|
||||
}
|
||||
|
||||
template<State S, typename... Args>
|
||||
void tick(char, tag<S>, Args&&...) {}
|
||||
void tick(char, tag<S>, Args &&...) {}
|
||||
|
||||
protected:
|
||||
/**
|
||||
@@ -114,7 +124,7 @@ protected:
|
||||
* The function is idempotent and it does nothing if the process isn't
|
||||
* alive.
|
||||
*/
|
||||
void succeed() noexcept {
|
||||
void succeed() ENTT_NOEXCEPT {
|
||||
if(alive()) {
|
||||
current = State::SUCCEEDED;
|
||||
}
|
||||
@@ -126,7 +136,7 @@ protected:
|
||||
* The function is idempotent and it does nothing if the process isn't
|
||||
* alive.
|
||||
*/
|
||||
void fail() noexcept {
|
||||
void fail() ENTT_NOEXCEPT {
|
||||
if(alive()) {
|
||||
current = State::FAILED;
|
||||
}
|
||||
@@ -138,7 +148,7 @@ protected:
|
||||
* The function is idempotent and it does nothing if the process isn't
|
||||
* running.
|
||||
*/
|
||||
void pause() noexcept {
|
||||
void pause() ENTT_NOEXCEPT {
|
||||
if(current == State::RUNNING) {
|
||||
current = State::PAUSED;
|
||||
}
|
||||
@@ -150,7 +160,7 @@ protected:
|
||||
* The function is idempotent and it does nothing if the process isn't
|
||||
* paused.
|
||||
*/
|
||||
void unpause() noexcept {
|
||||
void unpause() ENTT_NOEXCEPT {
|
||||
if(current == State::PAUSED) {
|
||||
current = State::RUNNING;
|
||||
}
|
||||
@@ -161,7 +171,7 @@ public:
|
||||
using delta_type = Delta;
|
||||
|
||||
/*! @brief Default destructor. */
|
||||
~Process() noexcept {
|
||||
virtual ~Process() ENTT_NOEXCEPT {
|
||||
static_assert(std::is_base_of<Process, Derived>::value, "!");
|
||||
}
|
||||
|
||||
@@ -173,7 +183,7 @@ public:
|
||||
*
|
||||
* @param immediately Requests an immediate operation.
|
||||
*/
|
||||
void abort(bool immediately = false) noexcept {
|
||||
void abort(const bool immediately = false) ENTT_NOEXCEPT {
|
||||
if(alive()) {
|
||||
current = State::ABORTED;
|
||||
|
||||
@@ -187,7 +197,7 @@ public:
|
||||
* @brief Returns true if a process is either running or paused.
|
||||
* @return True if the process is still alive, false otherwise.
|
||||
*/
|
||||
bool alive() const noexcept {
|
||||
bool alive() const ENTT_NOEXCEPT {
|
||||
return current == State::RUNNING || current == State::PAUSED;
|
||||
}
|
||||
|
||||
@@ -195,7 +205,7 @@ public:
|
||||
* @brief Returns true if a process is already terminated.
|
||||
* @return True if the process is terminated, false otherwise.
|
||||
*/
|
||||
bool dead() const noexcept {
|
||||
bool dead() const ENTT_NOEXCEPT {
|
||||
return current == State::FINISHED;
|
||||
}
|
||||
|
||||
@@ -203,7 +213,7 @@ public:
|
||||
* @brief Returns true if a process is currently paused.
|
||||
* @return True if the process is paused, false otherwise.
|
||||
*/
|
||||
bool paused() const noexcept {
|
||||
bool paused() const ENTT_NOEXCEPT {
|
||||
return current == State::PAUSED;
|
||||
}
|
||||
|
||||
@@ -211,7 +221,7 @@ public:
|
||||
* @brief Returns true if a process terminated with errors.
|
||||
* @return True if the process terminated with errors, false otherwise.
|
||||
*/
|
||||
bool rejected() const noexcept {
|
||||
bool rejected() const ENTT_NOEXCEPT {
|
||||
return stopped;
|
||||
}
|
||||
|
||||
@@ -220,7 +230,7 @@ public:
|
||||
* @param delta Elapsed time.
|
||||
* @param data Optional data.
|
||||
*/
|
||||
void tick(Delta delta, void *data = nullptr) {
|
||||
void tick(const Delta delta, void *data = nullptr) {
|
||||
switch (current) {
|
||||
case State::UNINITIALIZED:
|
||||
tick(0, tag<State::UNINITIALIZED>{}, data);
|
||||
@@ -273,12 +283,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 +318,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)...}
|
||||
{}
|
||||
|
||||
@@ -316,8 +327,8 @@ struct ProcessAdaptor: Process<ProcessAdaptor<Func, Delta>, Delta>, private Func
|
||||
* @param delta Elapsed time.
|
||||
* @param data Optional data.
|
||||
*/
|
||||
void update(Delta delta, void *data) {
|
||||
Func::operator()(delta, data, [this](){ this->succeed(); }, [this](){ this->fail(); });
|
||||
void update(const Delta delta, void *data) {
|
||||
Func::operator()(delta, data, [this]() { this->succeed(); }, [this]() { this->fail(); });
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <iterator>
|
||||
#include <algorithm>
|
||||
#include <type_traits>
|
||||
#include "../config/config.h"
|
||||
#include "process.hpp"
|
||||
|
||||
|
||||
@@ -28,7 +29,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
|
||||
@@ -43,7 +44,7 @@ namespace entt {
|
||||
template<typename Delta>
|
||||
class Scheduler final {
|
||||
template<typename T>
|
||||
struct tag { using type = T; };
|
||||
struct type_t { using type = T; };
|
||||
|
||||
struct ProcessHandler final {
|
||||
using instance_type = std::unique_ptr<void, void(*)(void *)>;
|
||||
@@ -64,9 +65,9 @@ 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)...);
|
||||
handler = Lambda::operator()(handler, type_t<Proc>{}, std::forward<Args>(args)...);
|
||||
return std::move(*this);
|
||||
}
|
||||
|
||||
@@ -81,7 +82,7 @@ class Scheduler final {
|
||||
};
|
||||
|
||||
template<typename Proc>
|
||||
static bool update(ProcessHandler &handler, Delta delta, void *data) {
|
||||
static bool update(ProcessHandler &handler, const Delta delta, void *data) {
|
||||
auto *process = static_cast<Proc *>(handler.instance.get());
|
||||
process->tick(delta, data);
|
||||
|
||||
@@ -100,7 +101,7 @@ class Scheduler final {
|
||||
}
|
||||
|
||||
template<typename Proc>
|
||||
static void abort(ProcessHandler &handler, bool immediately) {
|
||||
static void abort(ProcessHandler &handler, const bool immediately) {
|
||||
static_cast<Proc *>(handler.instance.get())->abort(immediately);
|
||||
}
|
||||
|
||||
@@ -130,7 +131,7 @@ public:
|
||||
using size_type = typename std::vector<ProcessHandler>::size_type;
|
||||
|
||||
/*! @brief Default constructor. */
|
||||
Scheduler() noexcept= default;
|
||||
Scheduler() ENTT_NOEXCEPT = default;
|
||||
|
||||
/*! @brief Copying a scheduler isn't allowed. */
|
||||
Scheduler(const Scheduler &) = delete;
|
||||
@@ -139,14 +140,14 @@ public:
|
||||
|
||||
/*! @brief Copying a scheduler isn't allowed. @return This scheduler. */
|
||||
Scheduler & operator=(const Scheduler &) = delete;
|
||||
/*! @brief Default move assignament operator. @return This scheduler. */
|
||||
/*! @brief Default move assignment operator. @return This scheduler. */
|
||||
Scheduler & operator=(Scheduler &&) = default;
|
||||
|
||||
/**
|
||||
* @brief Number of processes currently scheduled.
|
||||
* @return Number of processes currently scheduled.
|
||||
*/
|
||||
size_type size() const noexcept {
|
||||
size_type size() const ENTT_NOEXCEPT {
|
||||
return handlers.size();
|
||||
}
|
||||
|
||||
@@ -154,7 +155,7 @@ public:
|
||||
* @brief Returns true if at least a process is currently scheduled.
|
||||
* @return True if there are scheduled processes, false otherwise.
|
||||
*/
|
||||
bool empty() const noexcept {
|
||||
bool empty() const ENTT_NOEXCEPT {
|
||||
return handlers.empty();
|
||||
}
|
||||
|
||||
@@ -181,7 +182,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 +195,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 +238,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
|
||||
@@ -271,10 +272,10 @@ public:
|
||||
* @param delta Elapsed time.
|
||||
* @param data Optional data.
|
||||
*/
|
||||
void update(Delta delta, void *data = nullptr) {
|
||||
void update(const Delta delta, void *data = nullptr) {
|
||||
bool clean = false;
|
||||
|
||||
for(auto pos = handlers.size(); pos > 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;
|
||||
@@ -297,7 +298,7 @@ public:
|
||||
*
|
||||
* @param immediately Requests an immediate operation.
|
||||
*/
|
||||
void abort(bool immediately = false) {
|
||||
void abort(const bool immediately = false) {
|
||||
decltype(handlers) exec;
|
||||
exec.swap(handlers);
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <utility>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include "../config/config.h"
|
||||
#include "../core/hashed_string.hpp"
|
||||
#include "handle.hpp"
|
||||
#include "loader.hpp"
|
||||
@@ -38,20 +39,20 @@ public:
|
||||
ResourceCache() = default;
|
||||
|
||||
/*! @brief Copying a cache isn't allowed. */
|
||||
ResourceCache(const ResourceCache &) noexcept = delete;
|
||||
ResourceCache(const ResourceCache &) ENTT_NOEXCEPT = delete;
|
||||
/*! @brief Default move constructor. */
|
||||
ResourceCache(ResourceCache &&) noexcept = default;
|
||||
ResourceCache(ResourceCache &&) ENTT_NOEXCEPT = default;
|
||||
|
||||
/*! @brief Copying a cache isn't allowed. @return This cache. */
|
||||
ResourceCache & operator=(const ResourceCache &) noexcept = delete;
|
||||
ResourceCache & operator=(const ResourceCache &) ENTT_NOEXCEPT = delete;
|
||||
/*! @brief Default move assignment operator. @return This cache. */
|
||||
ResourceCache & operator=(ResourceCache &&) noexcept = default;
|
||||
ResourceCache & operator=(ResourceCache &&) ENTT_NOEXCEPT = default;
|
||||
|
||||
/**
|
||||
* @brief Number of resources managed by a cache.
|
||||
* @return Number of resources currently stored.
|
||||
*/
|
||||
size_type size() const noexcept {
|
||||
size_type size() const ENTT_NOEXCEPT {
|
||||
return resources.size();
|
||||
}
|
||||
|
||||
@@ -59,7 +60,7 @@ public:
|
||||
* @brief Returns true if a cache contains no resources, false otherwise.
|
||||
* @return True if the cache contains no resources, false otherwise.
|
||||
*/
|
||||
bool empty() const noexcept {
|
||||
bool empty() const ENTT_NOEXCEPT {
|
||||
return resources.empty();
|
||||
}
|
||||
|
||||
@@ -69,7 +70,7 @@ public:
|
||||
* Handles are not invalidated and the memory used by a resource isn't
|
||||
* freed as long as at least a handle keeps the resource itself alive.
|
||||
*/
|
||||
void clear() noexcept {
|
||||
void clear() ENTT_NOEXCEPT {
|
||||
resources.clear();
|
||||
}
|
||||
|
||||
@@ -92,7 +93,7 @@ public:
|
||||
* @return True if the resource is ready to use, false otherwise.
|
||||
*/
|
||||
template<typename Loader, typename... Args>
|
||||
bool load(resource_type id, Args&&... args) {
|
||||
bool load(const resource_type id, Args &&... args) {
|
||||
static_assert(std::is_base_of<ResourceLoader<Loader, Resource>, Loader>::value, "!");
|
||||
|
||||
bool loaded = true;
|
||||
@@ -125,8 +126,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(const resource_type id, Args &&... args) {
|
||||
return (discard(id), load<Loader>(id, std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -142,7 +143,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)...) };
|
||||
}
|
||||
|
||||
@@ -159,7 +160,7 @@ public:
|
||||
* @param id Unique resource identifier.
|
||||
* @return A handle for the given resource.
|
||||
*/
|
||||
ResourceHandle<Resource> handle(resource_type id) const {
|
||||
ResourceHandle<Resource> handle(const resource_type id) const {
|
||||
auto it = resources.find(id);
|
||||
return { it == resources.end() ? nullptr : it->second };
|
||||
}
|
||||
@@ -169,8 +170,8 @@ public:
|
||||
* @param id Unique resource identifier.
|
||||
* @return True if the cache contains the resource, false otherwise.
|
||||
*/
|
||||
bool contains(resource_type id) const noexcept {
|
||||
return !(resources.find(id) == resources.cend());
|
||||
bool contains(const resource_type id) const ENTT_NOEXCEPT {
|
||||
return (resources.find(id) != resources.cend());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -181,7 +182,7 @@ public:
|
||||
*
|
||||
* @param id Unique resource identifier.
|
||||
*/
|
||||
void discard(resource_type id) noexcept {
|
||||
void discard(const resource_type id) ENTT_NOEXCEPT {
|
||||
auto it = resources.find(id);
|
||||
|
||||
if(it != resources.end()) {
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <cassert>
|
||||
#include "../config/config.h"
|
||||
|
||||
|
||||
namespace entt {
|
||||
@@ -31,20 +32,20 @@ class ResourceHandle final {
|
||||
/*! @brief Resource handles are friends of their caches. */
|
||||
friend class ResourceCache<Resource>;
|
||||
|
||||
ResourceHandle(std::shared_ptr<Resource> res) noexcept
|
||||
ResourceHandle(std::shared_ptr<Resource> res) ENTT_NOEXCEPT
|
||||
: resource{std::move(res)}
|
||||
{}
|
||||
|
||||
public:
|
||||
/*! @brief Default copy constructor. */
|
||||
ResourceHandle(const ResourceHandle &) noexcept = default;
|
||||
ResourceHandle(const ResourceHandle &) ENTT_NOEXCEPT = default;
|
||||
/*! @brief Default move constructor. */
|
||||
ResourceHandle(ResourceHandle &&) noexcept = default;
|
||||
ResourceHandle(ResourceHandle &&) ENTT_NOEXCEPT = default;
|
||||
|
||||
/*! @brief Default copy assignment operator. @return This handle. */
|
||||
ResourceHandle & operator=(const ResourceHandle &) noexcept = default;
|
||||
ResourceHandle & operator=(const ResourceHandle &) ENTT_NOEXCEPT = default;
|
||||
/*! @brief Default move assignment operator. @return This handle. */
|
||||
ResourceHandle & operator=(ResourceHandle &&) noexcept = default;
|
||||
ResourceHandle & operator=(ResourceHandle &&) ENTT_NOEXCEPT = default;
|
||||
|
||||
/**
|
||||
* @brief Gets a reference to the managed resource.
|
||||
@@ -56,7 +57,7 @@ public:
|
||||
*
|
||||
* @return A reference to the managed resource.
|
||||
*/
|
||||
const Resource & get() const noexcept {
|
||||
const Resource & get() const ENTT_NOEXCEPT {
|
||||
assert(static_cast<bool>(resource));
|
||||
return *resource;
|
||||
}
|
||||
@@ -69,7 +70,7 @@ public:
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* handle is empty.
|
||||
*/
|
||||
inline operator const Resource & () const noexcept { return get(); }
|
||||
inline operator const Resource &() const ENTT_NOEXCEPT { return get(); }
|
||||
|
||||
/**
|
||||
* @brief Dereferences a handle to obtain the managed resource.
|
||||
@@ -81,7 +82,7 @@ public:
|
||||
*
|
||||
* @return A reference to the managed resource.
|
||||
*/
|
||||
inline const Resource & operator *() const noexcept { return get(); }
|
||||
inline const Resource & operator *() const ENTT_NOEXCEPT { return get(); }
|
||||
|
||||
/**
|
||||
* @brief Gets a pointer to the managed resource from a handle .
|
||||
@@ -94,7 +95,7 @@ public:
|
||||
* @return A pointer to the managed resource or `nullptr` if the handle
|
||||
* contains no resource at all.
|
||||
*/
|
||||
inline const Resource * operator ->() const noexcept {
|
||||
inline const Resource * operator ->() const ENTT_NOEXCEPT {
|
||||
assert(static_cast<bool>(resource));
|
||||
return resource.get();
|
||||
}
|
||||
|
||||
@@ -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)...);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,305 +0,0 @@
|
||||
#ifndef ENTT_SIGNAL_BUS_HPP
|
||||
#define ENTT_SIGNAL_BUS_HPP
|
||||
|
||||
|
||||
#include <cstddef>
|
||||
#include <utility>
|
||||
#include "signal.hpp"
|
||||
#include "sigh.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/**
|
||||
* @brief Minimal event bus.
|
||||
*
|
||||
* Primary template isn't defined on purpose. The main reason for which it
|
||||
* exists is to work around the doxygen's parsing capabilities. In fact, there
|
||||
* is no need to declare it actually.
|
||||
*/
|
||||
template<template<typename...> class, typename...>
|
||||
class Bus;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Event bus specialization for multiple types.
|
||||
*
|
||||
* The event bus is designed to allow an easy registration of specific member
|
||||
* functions to a bunch of signal handlers (either manager or unmanaged).
|
||||
* Classes must publicly expose the required member functions to allow the bus
|
||||
* to detect them for the purpose of registering and unregistering
|
||||
* instances.<br/>
|
||||
* In particular, for each event type `E`, a matching member function has the
|
||||
* following signature: `void receive(const E &)`. Events will be properly
|
||||
* redirected to all the listeners by calling the right member functions, if
|
||||
* any.
|
||||
*
|
||||
* @tparam Sig Type of signal handler to use.
|
||||
* @tparam Event The list of events managed by the bus.
|
||||
*/
|
||||
template<template<typename...> class Sig, typename Event, typename... Other>
|
||||
class Bus<Sig, Event, Other...>
|
||||
: private Bus<Sig, Event>, private Bus<Sig, Other>...
|
||||
{
|
||||
public:
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = std::size_t;
|
||||
|
||||
/**
|
||||
* @brief Unregisters all the member functions of an instance.
|
||||
*
|
||||
* A bus is used to convey a certain set of events. This method detects
|
||||
* and unregisters from the bus all the matching member functions of an
|
||||
* instance.<br/>
|
||||
* For each event type `E`, a matching member function has the following
|
||||
* signature: `void receive(const E &)`.
|
||||
*
|
||||
* @tparam Instance Type of instance to unregister.
|
||||
* @param instance A valid instance of the right type.
|
||||
*/
|
||||
template<typename Instance>
|
||||
void unreg(Instance instance) {
|
||||
using accumulator_type = int[];
|
||||
accumulator_type accumulator = {
|
||||
(Bus<Sig, Event>::unreg(instance), 0),
|
||||
(Bus<Sig, Other>::unreg(instance), 0)...
|
||||
};
|
||||
return void(accumulator);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Registers all the member functions of an instance.
|
||||
*
|
||||
* A bus is used to convey a certain set of events. This method detects
|
||||
* and registers to the bus all the matching member functions of an
|
||||
* instance.<br/>
|
||||
* For each event type `E`, a matching member function has the following
|
||||
* signature: `void receive(const E &)`.
|
||||
*
|
||||
* @tparam Instance Type of instance to register.
|
||||
* @param instance A valid instance of the right type.
|
||||
*/
|
||||
template<typename Instance>
|
||||
void reg(Instance instance) {
|
||||
using accumulator_type = int[];
|
||||
accumulator_type accumulator = {
|
||||
(Bus<Sig, Event>::reg(instance), 0),
|
||||
(Bus<Sig, Other>::reg(instance), 0)...
|
||||
};
|
||||
return void(accumulator);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Number of listeners connected to the bus.
|
||||
* @return Number of listeners currently connected.
|
||||
*/
|
||||
size_type size() const noexcept {
|
||||
using accumulator_type = std::size_t[];
|
||||
std::size_t sz = Bus<Sig, Event>::size();
|
||||
accumulator_type accumulator = { sz, (sz += Bus<Sig, Other>::size())... };
|
||||
return void(accumulator), sz;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns false if at least a listener is connected to the bus.
|
||||
* @return True if the bus has no listeners connected, false otherwise.
|
||||
*/
|
||||
bool empty() const noexcept {
|
||||
using accumulator_type = bool[];
|
||||
bool ret = Bus<Sig, Event>::empty();
|
||||
accumulator_type accumulator = { ret, (ret = ret && Bus<Sig, Other>::empty())... };
|
||||
return void(accumulator), ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Connects a free function to the bus.
|
||||
* @tparam Type Type of event to which to connect the function.
|
||||
* @tparam Function A valid free function pointer.
|
||||
*/
|
||||
template<typename Type, void(*Function)(const Type &)>
|
||||
void connect() {
|
||||
Bus<Sig, Type>::template connect<Function>();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects a free function from the bus.
|
||||
* @tparam Type Type of event from which to disconnect the function.
|
||||
* @tparam Function A valid free function pointer.
|
||||
*/
|
||||
template<typename Type, void(*Function)(const Type &)>
|
||||
void disconnect() {
|
||||
Bus<Sig, Type>::template disconnect<Function>();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Publishes an event.
|
||||
*
|
||||
* All the listeners are notified. Order isn't guaranteed.
|
||||
*
|
||||
* @tparam Type Type of event to publish.
|
||||
* @tparam Args Types of arguments to use to construct the event.
|
||||
* @param args Arguments to use to construct the event.
|
||||
*/
|
||||
template<typename Type, typename... Args>
|
||||
void publish(Args&&... args) {
|
||||
Bus<Sig, Type>::publish(std::forward<Args>(args)...);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Event bus specialization for a single type.
|
||||
*
|
||||
* The event bus is designed to allow an easy registration of a specific member
|
||||
* function to a signal handler (either manager or unmanaged).
|
||||
* Classes must publicly expose the required member function to allow the bus to
|
||||
* detect it for the purpose of registering and unregistering instances.<br/>
|
||||
* In particular, a matching member function has the following signature:
|
||||
* `void receive(const Event &)`. Events of the given type will be properly
|
||||
* redirected to all the listeners by calling the right member function, if any.
|
||||
*
|
||||
* @tparam Sig Type of signal handler to use.
|
||||
* @tparam Event Type of event managed by the bus.
|
||||
*/
|
||||
template<template<typename...> class Sig, typename Event>
|
||||
class Bus<Sig, Event> {
|
||||
using signal_type = Sig<void(const Event &)>;
|
||||
|
||||
template<typename Class>
|
||||
using instance_type = typename signal_type::template instance_type<Class>;
|
||||
|
||||
template<typename Class>
|
||||
auto disconnect(int, instance_type<Class> instance)
|
||||
-> decltype(std::declval<Class>().receive(std::declval<Event>()), void()) {
|
||||
signal.template disconnect<Class, &Class::receive>(std::move(instance));
|
||||
}
|
||||
|
||||
template<typename Class>
|
||||
auto connect(int, instance_type<Class> instance)
|
||||
-> decltype(std::declval<Class>().receive(std::declval<Event>()), void()) {
|
||||
signal.template connect<Class, &Class::receive>(std::move(instance));
|
||||
}
|
||||
|
||||
template<typename Class> void disconnect(char, instance_type<Class>) {}
|
||||
template<typename Class> void connect(char, instance_type<Class>) {}
|
||||
|
||||
public:
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = typename signal_type::size_type;
|
||||
|
||||
/**
|
||||
* @brief Unregisters member functions of instances.
|
||||
*
|
||||
* This method tries to detect and unregister from the bus matching member
|
||||
* functions of instances.<br/>
|
||||
* A matching member function has the following signature:
|
||||
* `void receive(const Event &)`.
|
||||
*
|
||||
* @tparam Class Type of instance to unregister.
|
||||
* @param instance A valid instance of the right type.
|
||||
*/
|
||||
template<typename Class>
|
||||
void unreg(instance_type<Class> instance) {
|
||||
disconnect(0, std::move(instance));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Tries to register an instance.
|
||||
*
|
||||
* This method tries to detect and register to the bus matching member
|
||||
* functions of instances.<br/>
|
||||
* A matching member function has the following signature:
|
||||
* `void receive(const Event &)`.
|
||||
*
|
||||
* @tparam Class Type of instance to register.
|
||||
* @param instance A valid instance of the right type.
|
||||
*/
|
||||
template<typename Class>
|
||||
void reg(instance_type<Class> instance) {
|
||||
connect(0, std::move(instance));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Number of listeners connected to the bus.
|
||||
* @return Number of listeners currently connected.
|
||||
*/
|
||||
size_type size() const noexcept {
|
||||
return signal.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns false if at least a listener is connected to the bus.
|
||||
* @return True if the bus has no listeners connected, false otherwise.
|
||||
*/
|
||||
bool empty() const noexcept {
|
||||
return signal.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Connects a free function to the bus.
|
||||
* @tparam Function A valid free function pointer.
|
||||
*/
|
||||
template<void(*Function)(const Event &)>
|
||||
void connect() {
|
||||
signal.template connect<Function>();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects a free function from the bus.
|
||||
* @tparam Function A valid free function pointer.
|
||||
*/
|
||||
template<void(*Function)(const Event &)>
|
||||
void disconnect() {
|
||||
signal.template disconnect<Function>();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Publishes an event.
|
||||
*
|
||||
* All the listeners are notified. Order isn't guaranteed.
|
||||
*
|
||||
* @tparam Args Types of arguments to use to construct the event.
|
||||
* @param args Arguments to use to construct the event.
|
||||
*/
|
||||
template<typename... Args>
|
||||
void publish(Args&&... args) {
|
||||
signal.publish({ std::forward<Args>(args)... });
|
||||
}
|
||||
|
||||
private:
|
||||
signal_type signal;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Managed event bus.
|
||||
*
|
||||
* A managed event bus uses the Signal class template as an underlying type. The
|
||||
* type of the instances is the one required by the signal handler:
|
||||
* `std::shared_ptr<Class>` (a shared pointer).
|
||||
*
|
||||
* @tparam Event The list of events managed by the bus.
|
||||
*/
|
||||
template<typename... Event>
|
||||
using ManagedBus = Bus<Signal, Event...>;
|
||||
|
||||
/**
|
||||
* @brief Unmanaged event bus.
|
||||
*
|
||||
* An unmanaged event bus uses the SigH class template as an underlying type.
|
||||
* The type of the instances is the one required by the signal handler:
|
||||
* `Class *` (a naked pointer).<br/>
|
||||
* When it comes to work with this kind of bus, users must guarantee that the
|
||||
* lifetimes of the instances overcome the one of the bus itself.
|
||||
*
|
||||
* @tparam Event The list of events managed by the bus.
|
||||
*/
|
||||
template<typename... Event>
|
||||
using UnmanagedBus = Bus<SigH, Event...>;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_SIGNAL_BUS_HPP
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
|
||||
#include <utility>
|
||||
#include "../config/config.h"
|
||||
|
||||
|
||||
namespace entt {
|
||||
@@ -36,21 +37,21 @@ class Delegate<Ret(Args...)> final {
|
||||
using proto_type = Ret(*)(void *, Args...);
|
||||
using stub_type = std::pair<void *, proto_type>;
|
||||
|
||||
static Ret fallback(void *, Args...) noexcept { return {}; }
|
||||
static Ret fallback(void *, Args...) ENTT_NOEXCEPT { return {}; }
|
||||
|
||||
template<Ret(*Function)(Args...)>
|
||||
static Ret proto(void *, Args... args) {
|
||||
return (Function)(args...);
|
||||
}
|
||||
|
||||
template<typename Class, Ret(Class::*Member)(Args...)>
|
||||
template<typename Class, Ret(Class:: *Member)(Args...)>
|
||||
static Ret proto(void *instance, Args... args) {
|
||||
return (static_cast<Class *>(instance)->*Member)(args...);
|
||||
}
|
||||
|
||||
public:
|
||||
/*! @brief Default constructor. */
|
||||
Delegate() noexcept
|
||||
Delegate() ENTT_NOEXCEPT
|
||||
: stub{std::make_pair(nullptr, &fallback)}
|
||||
{}
|
||||
|
||||
@@ -59,7 +60,7 @@ public:
|
||||
* @tparam Function A valid free function pointer.
|
||||
*/
|
||||
template<Ret(*Function)(Args...)>
|
||||
void connect() noexcept {
|
||||
void connect() ENTT_NOEXCEPT {
|
||||
stub = std::make_pair(nullptr, &proto<Function>);
|
||||
}
|
||||
|
||||
@@ -74,8 +75,8 @@ public:
|
||||
* @tparam Member Member function to connect to the delegate.
|
||||
* @param instance A valid instance of type pointer to `Class`.
|
||||
*/
|
||||
template<typename Class, Ret(Class::*Member)(Args...)>
|
||||
void connect(Class *instance) noexcept {
|
||||
template<typename Class, Ret(Class:: *Member)(Args...)>
|
||||
void connect(Class *instance) ENTT_NOEXCEPT {
|
||||
stub = std::make_pair(instance, &proto<Class, Member>);
|
||||
}
|
||||
|
||||
@@ -84,7 +85,7 @@ public:
|
||||
*
|
||||
* After a reset, a delegate can be safely invoked with no effect.
|
||||
*/
|
||||
void reset() noexcept {
|
||||
void reset() ENTT_NOEXCEPT {
|
||||
stub = std::make_pair(nullptr, &fallback);
|
||||
}
|
||||
|
||||
@@ -105,7 +106,7 @@ public:
|
||||
* @param other Delegate with which to compare.
|
||||
* @return True if the two delegates are identical, false otherwise.
|
||||
*/
|
||||
bool operator==(const Delegate<Ret(Args...)> &other) const noexcept {
|
||||
bool operator==(const Delegate<Ret(Args...)> &other) const ENTT_NOEXCEPT {
|
||||
return stub.first == other.stub.first && stub.second == other.stub.second;
|
||||
}
|
||||
|
||||
@@ -126,7 +127,7 @@ private:
|
||||
* @return True if the two delegates are different, false otherwise.
|
||||
*/
|
||||
template<typename Ret, typename... Args>
|
||||
bool operator!=(const Delegate<Ret(Args...)> &lhs, const Delegate<Ret(Args...)> &rhs) noexcept {
|
||||
bool operator!=(const Delegate<Ret(Args...)> &lhs, const Delegate<Ret(Args...)> &rhs) ENTT_NOEXCEPT {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,8 +6,10 @@
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <cstdint>
|
||||
#include <algorithm>
|
||||
#include <type_traits>
|
||||
#include "../config/config.h"
|
||||
#include "../core/family.hpp"
|
||||
#include "signal.hpp"
|
||||
#include "sigh.hpp"
|
||||
|
||||
|
||||
@@ -20,63 +22,58 @@ namespace entt {
|
||||
* A dispatcher can be used either to trigger an immediate event or to enqueue
|
||||
* events to be published all together once per tick.<br/>
|
||||
* Listeners are provided in the form of member functions. For each event of
|
||||
* type `Event`, listeners must have the following signature:
|
||||
* `void(const Event &)`. Member functions named `receive` are automatically
|
||||
* detected and registered or unregistered by the dispatcher.
|
||||
* type `Event`, listeners must have the following function type:
|
||||
* @code{.cpp}
|
||||
* void(const Event &)
|
||||
* @endcode
|
||||
*
|
||||
* @tparam Sig Type of the signal handler to use.
|
||||
* Member functions named `receive` are automatically detected and registered or
|
||||
* unregistered by the dispatcher. The type of the instances is `Class *` (a
|
||||
* naked pointer). It means that users must guarantee that the lifetimes of the
|
||||
* instances overcome the one of the dispatcher itself to avoid crashes.
|
||||
*/
|
||||
template<template<typename...> class Sig>
|
||||
class Dispatcher final {
|
||||
using event_family = Family<struct InternalDispatcherEventFamily>;
|
||||
|
||||
template<typename Class, typename Event>
|
||||
using instance_type = typename Sig<void(const Event &)>::template instance_type<Class>;
|
||||
using instance_type = typename SigH<void(const Event &)>::template instance_type<Class>;
|
||||
|
||||
struct BaseSignalWrapper {
|
||||
virtual ~BaseSignalWrapper() = default;
|
||||
virtual void publish(std::size_t) = 0;
|
||||
virtual void publish() = 0;
|
||||
};
|
||||
|
||||
template<typename Event>
|
||||
struct SignalWrapper final: BaseSignalWrapper {
|
||||
void publish(std::size_t current) override {
|
||||
for(const auto &event: events[current]) {
|
||||
signal.publish(event);
|
||||
}
|
||||
using sink_type = typename SigH<void(const Event &)>::sink_type;
|
||||
|
||||
events[current].clear();
|
||||
void publish() override {
|
||||
const auto &curr = current++;
|
||||
current %= std::extent<decltype(events)>::value;
|
||||
std::for_each(events[curr].cbegin(), events[curr].cend(), [this](const auto &event) { signal.publish(event); });
|
||||
events[curr].clear();
|
||||
}
|
||||
|
||||
template<typename Class, void(Class::*Member)(const Event &)>
|
||||
inline void connect(instance_type<Class, Event> instance) noexcept {
|
||||
signal.template connect<Class, Member>(std::move(instance));
|
||||
}
|
||||
|
||||
template<typename Class, void(Class::*Member)(const Event &)>
|
||||
inline void disconnect(instance_type<Class, Event> instance) noexcept {
|
||||
signal.template disconnect<Class, Member>(std::move(instance));
|
||||
inline sink_type sink() ENTT_NOEXCEPT {
|
||||
return signal.sink();
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
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(Args &&... args) {
|
||||
events[current].push_back({ std::forward<Args>(args)... });
|
||||
}
|
||||
|
||||
private:
|
||||
Sig<void(const Event &)> signal{};
|
||||
SigH<void(const Event &)> signal{};
|
||||
std::vector<Event> events[2];
|
||||
int current{};
|
||||
};
|
||||
|
||||
inline static std::size_t buffer(bool mode) {
|
||||
return mode ? 0 : 1;
|
||||
}
|
||||
|
||||
template<typename Event>
|
||||
SignalWrapper<Event> & wrapper() {
|
||||
const auto type = event_family::type<Event>();
|
||||
@@ -93,51 +90,30 @@ class Dispatcher final {
|
||||
}
|
||||
|
||||
public:
|
||||
/*! @brief Default constructor. */
|
||||
Dispatcher() noexcept
|
||||
: wrappers{}, mode{false}
|
||||
{}
|
||||
/*! @brief Type of sink for the given event. */
|
||||
template<typename Event>
|
||||
using sink_type = typename SignalWrapper<Event>::sink_type;
|
||||
|
||||
/**
|
||||
* @brief Registers a listener given in the form of a member function.
|
||||
* @brief Returns a sink object for the given event.
|
||||
*
|
||||
* A matching member function has the following signature:
|
||||
* `void receive(const Event &)`. Member functions named `receive` are
|
||||
* automatically detected and registered if available.
|
||||
* A sink is an opaque object used to connect listeners to events.
|
||||
*
|
||||
* @warning
|
||||
* Connecting a listener during an update may lead to unexpected behavior.
|
||||
* Register listeners before or after invoking the update if possible.
|
||||
* The function type for a listener is:
|
||||
* @code{.cpp}
|
||||
* void(const Event &)
|
||||
* @endcode
|
||||
*
|
||||
* @tparam Event Type of event to which to connect the function.
|
||||
* @tparam Class Type of class to which the member function belongs.
|
||||
* @tparam Member Member function to connect to the signal.
|
||||
* @param instance A valid instance of the right type.
|
||||
* The order of invocation of the listeners isn't guaranteed.
|
||||
*
|
||||
* @sa SigH::Sink
|
||||
*
|
||||
* @tparam Event Type of event of which to get the sink.
|
||||
* @return A temporary sink object.
|
||||
*/
|
||||
template<typename Event, typename Class, void(Class::*Member)(const Event &) = &Class::receive>
|
||||
void connect(instance_type<Class, Event> instance) noexcept {
|
||||
wrapper<Event>().template connect<Class, Member>(std::move(instance));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Unregisters a listener given in the form of a member function.
|
||||
*
|
||||
* A matching member function has the following signature:
|
||||
* `void receive(const Event &)`. Member functions named `receive` are
|
||||
* automatically detected and unregistered if available.
|
||||
*
|
||||
* @warning
|
||||
* Disonnecting a listener during an update may lead to unexpected behavior.
|
||||
* Unregister listeners before or after invoking the update if possible.
|
||||
*
|
||||
* @tparam Event Type of event from which to disconnect the function.
|
||||
* @tparam Class Type of class to which the member function belongs.
|
||||
* @tparam Member Member function to connect to the signal.
|
||||
* @param instance A valid instance of the right type.
|
||||
*/
|
||||
template<typename Event, typename Class, void(Class::*Member)(const Event &) = &Class::receive>
|
||||
void disconnect(instance_type<Class, Event> instance) noexcept {
|
||||
wrapper<Event>().template disconnect<Class, Member>(std::move(instance));
|
||||
template<typename Event>
|
||||
inline sink_type<Event> sink() ENTT_NOEXCEPT {
|
||||
return wrapper<Event>().sink();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -151,7 +127,7 @@ public:
|
||||
* @param args Arguments to use to construct the event.
|
||||
*/
|
||||
template<typename Event, typename... Args>
|
||||
void trigger(Args&&... args) {
|
||||
inline void trigger(Args &&... args) {
|
||||
wrapper<Event>().trigger(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
@@ -166,56 +142,46 @@ public:
|
||||
* @param args Arguments to use to construct the event.
|
||||
*/
|
||||
template<typename Event, typename... Args>
|
||||
void enqueue(Args&&... args) {
|
||||
wrapper<Event>().enqueue(buffer(mode), std::forward<Args>(args)...);
|
||||
inline void enqueue(Args &&... args) {
|
||||
wrapper<Event>().enqueue(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Delivers all the pending events of the given type.
|
||||
*
|
||||
* This method is blocking and it doesn't return until all the events are
|
||||
* delivered to the registered listeners. It's responsibility of the users
|
||||
* to reduce at a minimum the time spent in the bodies of the listeners.
|
||||
*
|
||||
* @tparam Event Type of events to send.
|
||||
*/
|
||||
template<typename Event>
|
||||
inline void update() {
|
||||
wrapper<Event>().publish();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Delivers all the pending events.
|
||||
*
|
||||
* This method is blocking and it doesn't return until all the events are
|
||||
* delivered to the registered listeners. It's responsability of the users
|
||||
* delivered to the registered listeners. It's responsibility of the users
|
||||
* to reduce at a minimum the time spent in the bodies of the listeners.
|
||||
*/
|
||||
void update() {
|
||||
const auto buf = buffer(mode);
|
||||
mode = !mode;
|
||||
inline void update() {
|
||||
for(auto pos = wrappers.size(); pos; --pos) {
|
||||
auto &wrapper = wrappers[pos-1];
|
||||
|
||||
for(auto &&wrapper: wrappers) {
|
||||
if(wrapper) {
|
||||
wrapper->publish(buf);
|
||||
wrapper->publish();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<std::unique_ptr<BaseSignalWrapper>> wrappers;
|
||||
bool mode;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Managed dispatcher.
|
||||
*
|
||||
* A managed dispatcher uses the Signal class template as an underlying type.
|
||||
* The type of the instances is the one required by the signal handler:
|
||||
* `std::shared_ptr<Class>` (a shared pointer).
|
||||
*/
|
||||
using ManagedDispatcher = Dispatcher<Signal>;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Unmanaged dispatcher.
|
||||
*
|
||||
* An unmanaged dispatcher uses the SigH class template as an underlying type.
|
||||
* The type of the instances is the one required by the signal handler:
|
||||
* `Class *` (a naked pointer).<br/>
|
||||
* When it comes to work with this kind of dispatcher, users must guarantee that
|
||||
* the lifetimes of the instances overcome the one of the dispatcher itself.
|
||||
*/
|
||||
using UnmanagedDispatcher = Dispatcher<SigH>;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <list>
|
||||
#include "../config/config.h"
|
||||
|
||||
|
||||
namespace entt {
|
||||
@@ -40,8 +41,8 @@ template<typename Derived>
|
||||
class Emitter {
|
||||
struct BaseHandler {
|
||||
virtual ~BaseHandler() = default;
|
||||
virtual bool empty() const noexcept = 0;
|
||||
virtual void clear() noexcept = 0;
|
||||
virtual bool empty() const ENTT_NOEXCEPT = 0;
|
||||
virtual void clear() ENTT_NOEXCEPT = 0;
|
||||
};
|
||||
|
||||
template<typename Event>
|
||||
@@ -51,16 +52,16 @@ class Emitter {
|
||||
using container_type = std::list<element_type>;
|
||||
using connection_type = typename container_type::iterator;
|
||||
|
||||
bool empty() const noexcept override {
|
||||
auto pred = [](auto &&element){ return element.first; };
|
||||
bool empty() const ENTT_NOEXCEPT override {
|
||||
auto pred = [](auto &&element) { return element.first; };
|
||||
|
||||
return std::all_of(onceL.cbegin(), onceL.cend(), pred) &&
|
||||
std::all_of(onL.cbegin(), onL.cend(), pred);
|
||||
}
|
||||
|
||||
void clear() noexcept override {
|
||||
void clear() ENTT_NOEXCEPT override {
|
||||
if(publishing) {
|
||||
auto func = [](auto &&element){ element.first = true; };
|
||||
auto func = [](auto &&element) { element.first = true; };
|
||||
std::for_each(onceL.begin(), onceL.end(), func);
|
||||
std::for_each(onL.begin(), onL.end(), func);
|
||||
} else {
|
||||
@@ -77,11 +78,11 @@ class Emitter {
|
||||
return onL.emplace(onL.cend(), false, std::move(listener));
|
||||
}
|
||||
|
||||
void erase(connection_type conn) noexcept {
|
||||
void erase(connection_type conn) ENTT_NOEXCEPT {
|
||||
conn->first = true;
|
||||
|
||||
if(!publishing) {
|
||||
auto pred = [](auto &&element){ return element.first; };
|
||||
auto pred = [](auto &&element) { return element.first; };
|
||||
onceL.remove_if(pred);
|
||||
onL.remove_if(pred);
|
||||
}
|
||||
@@ -102,7 +103,7 @@ class Emitter {
|
||||
|
||||
publishing = false;
|
||||
|
||||
onL.remove_if([](auto &&element){ return element.first; });
|
||||
onL.remove_if([](auto &&element) { return element.first; });
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -111,19 +112,19 @@ class Emitter {
|
||||
container_type onL{};
|
||||
};
|
||||
|
||||
static std::size_t next() noexcept {
|
||||
static std::size_t next() ENTT_NOEXCEPT {
|
||||
static std::size_t counter = 0;
|
||||
return counter++;
|
||||
}
|
||||
|
||||
template<typename>
|
||||
static std::size_t type() noexcept {
|
||||
static std::size_t type() ENTT_NOEXCEPT {
|
||||
static std::size_t value = next();
|
||||
return value;
|
||||
}
|
||||
|
||||
template<typename Event>
|
||||
Handler<Event> & handler() noexcept {
|
||||
Handler<Event> & handler() ENTT_NOEXCEPT {
|
||||
const std::size_t family = type<Event>();
|
||||
|
||||
if(!(family < handlers.size())) {
|
||||
@@ -138,7 +139,7 @@ class Emitter {
|
||||
}
|
||||
|
||||
public:
|
||||
/** @brief Type of listeners accepted for the given type of event. */
|
||||
/** @brief Type of listeners accepted for the given event. */
|
||||
template<typename Event>
|
||||
using Listener = typename Handler<Event>::listener_type;
|
||||
|
||||
@@ -157,7 +158,7 @@ public:
|
||||
friend class Emitter;
|
||||
|
||||
/*! @brief Default constructor. */
|
||||
Connection() noexcept = default;
|
||||
Connection() ENTT_NOEXCEPT = default;
|
||||
|
||||
/**
|
||||
* @brief Creates a connection that wraps its underlying instance.
|
||||
@@ -173,7 +174,7 @@ public:
|
||||
Connection(Connection &&) = default;
|
||||
|
||||
/**
|
||||
* @brief Default copy assignament operator.
|
||||
* @brief Default copy assignment operator.
|
||||
* @return This connection.
|
||||
*/
|
||||
Connection & operator=(const Connection &) = default;
|
||||
@@ -186,10 +187,10 @@ public:
|
||||
};
|
||||
|
||||
/*! @brief Default constructor. */
|
||||
Emitter() noexcept = default;
|
||||
Emitter() ENTT_NOEXCEPT = default;
|
||||
|
||||
/*! @brief Default destructor. */
|
||||
virtual ~Emitter() noexcept {
|
||||
virtual ~Emitter() ENTT_NOEXCEPT {
|
||||
static_assert(std::is_base_of<Emitter<Derived>, Derived>::value, "!");
|
||||
}
|
||||
|
||||
@@ -200,7 +201,7 @@ public:
|
||||
|
||||
/*! @brief Copying an emitter isn't allowed. @return This emitter. */
|
||||
Emitter & operator=(const Emitter &) = delete;
|
||||
/*! @brief Default move assignament operator. @return This emitter. */
|
||||
/*! @brief Default move assignment operator. @return This emitter. */
|
||||
Emitter & operator=(Emitter &&) = default;
|
||||
|
||||
/**
|
||||
@@ -215,7 +216,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));
|
||||
}
|
||||
|
||||
@@ -279,7 +280,7 @@ public:
|
||||
* @param conn A valid connection.
|
||||
*/
|
||||
template<typename Event>
|
||||
void erase(Connection<Event> conn) noexcept {
|
||||
void erase(Connection<Event> conn) ENTT_NOEXCEPT {
|
||||
handler<Event>().erase(std::move(conn));
|
||||
}
|
||||
|
||||
@@ -287,12 +288,12 @@ 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.
|
||||
*/
|
||||
template<typename Event>
|
||||
void clear() noexcept {
|
||||
void clear() ENTT_NOEXCEPT {
|
||||
handler<Event>().clear();
|
||||
}
|
||||
|
||||
@@ -300,11 +301,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 {
|
||||
void clear() ENTT_NOEXCEPT {
|
||||
std::for_each(handlers.begin(), handlers.end(),
|
||||
[](auto &&handler){ if(handler) { handler->clear(); } });
|
||||
[](auto &&handler) { if(handler) { handler->clear(); } });
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -313,7 +314,7 @@ public:
|
||||
* @return True if there are no listeners registered, false otherwise.
|
||||
*/
|
||||
template<typename Event>
|
||||
bool empty() const noexcept {
|
||||
bool empty() const ENTT_NOEXCEPT {
|
||||
const std::size_t family = type<Event>();
|
||||
|
||||
return (!(family < handlers.size()) ||
|
||||
@@ -325,9 +326,9 @@ public:
|
||||
* @brief Checks if there are listeners registered with the event emitter.
|
||||
* @return True if there are no listeners registered, false otherwise.
|
||||
*/
|
||||
bool empty() const noexcept {
|
||||
bool empty() const ENTT_NOEXCEPT {
|
||||
return std::all_of(handlers.cbegin(), handlers.cend(),
|
||||
[](auto &&handler){ return !handler || handler->empty(); });
|
||||
[](auto &&handler) { return !handler || handler->empty(); });
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
@@ -5,12 +5,19 @@
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include "../config/config.h"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
namespace {
|
||||
namespace internal {
|
||||
|
||||
|
||||
/**
|
||||
* @cond TURN_OFF_DOXYGEN
|
||||
* Internal details not to be documented.
|
||||
*/
|
||||
|
||||
|
||||
template<typename, typename>
|
||||
@@ -24,32 +31,36 @@ 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;
|
||||
bool operator()(result_type) const noexcept { return true; }
|
||||
bool operator()(result_type) const ENTT_NOEXCEPT { return true; }
|
||||
};
|
||||
|
||||
|
||||
template<>
|
||||
struct NullCollector<void> final {
|
||||
using result_type = void;
|
||||
bool operator()() const noexcept { return true; }
|
||||
bool operator()() const ENTT_NOEXCEPT { return true; }
|
||||
};
|
||||
|
||||
|
||||
@@ -67,9 +78,27 @@ template<typename Function>
|
||||
using DefaultCollectorType = typename DefaultCollector<Function>::collector_type;
|
||||
|
||||
|
||||
/**
|
||||
* Internal details not to be documented.
|
||||
* @endcond TURN_OFF_DOXYGEN
|
||||
*/
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Sink implementation.
|
||||
*
|
||||
* Primary template isn't defined on purpose. All the specializations give a
|
||||
* compile-time error unless the template parameter is a function type.
|
||||
*
|
||||
* @tparam Function A valid function type.
|
||||
*/
|
||||
template<typename Function>
|
||||
class Sink;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Unmanaged signal handler declaration.
|
||||
*
|
||||
@@ -79,88 +108,53 @@ using DefaultCollectorType = typename DefaultCollector<Function>::collector_type
|
||||
* @tparam Function A valid function type.
|
||||
* @tparam Collector Type of collector to use, if any.
|
||||
*/
|
||||
template<typename Function, typename Collector = DefaultCollectorType<Function>>
|
||||
template<typename Function, typename Collector = internal::DefaultCollectorType<Function>>
|
||||
class SigH;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Unmanaged signal handler definition.
|
||||
* @brief Sink implementation.
|
||||
*
|
||||
* Unmanaged signal handler. It works directly with naked pointers to classes
|
||||
* and pointers to member functions as well as pointers to free functions. Users
|
||||
* of this class are in charge of disconnecting instances before deleting them.
|
||||
* A sink is an opaque object used to connect listeners to signals.<br/>
|
||||
* The function type for a listener is the one of the signal to which it
|
||||
* belongs.
|
||||
*
|
||||
* This class serves mainly two purposes:
|
||||
*
|
||||
* * Creating signals used later to notify a bunch of listeners.
|
||||
* * Collecting results from a set of functions like in a voting system.
|
||||
*
|
||||
* The default collector does nothing. To properly collect data, define and use
|
||||
* a class that has a call operator the signature of which is `bool(Param)` and:
|
||||
*
|
||||
* * `Param` is a type to which `Ret` can be converted.
|
||||
* * The return type is true if the handler must stop collecting data, false
|
||||
* otherwise.
|
||||
* The clear separation between a signal and a sink permits to store the
|
||||
* former as private data member without exposing the publish functionality
|
||||
* to the users of a class.
|
||||
*
|
||||
* @tparam Ret Return type of a function type.
|
||||
* @tparam Args Types of arguments of a function type.
|
||||
* @tparam Collector Type of collector to use, if any.
|
||||
*/
|
||||
template<typename Ret, typename... Args, typename Collector>
|
||||
class SigH<Ret(Args...), Collector> final: private Invoker<Ret(Args...), Collector> {
|
||||
using typename Invoker<Ret(Args...), Collector>::call_type;
|
||||
template<typename Ret, typename... Args>
|
||||
class Sink<Ret(Args...)> final {
|
||||
/*! @brief A signal is allowed to create sinks. */
|
||||
template<typename, typename>
|
||||
friend class SigH;
|
||||
|
||||
using proto_type = Ret(*)(void *, Args...);
|
||||
using call_type = std::pair<void *, proto_type>;
|
||||
|
||||
template<Ret(*Function)(Args...)>
|
||||
static Ret proto(void *, Args... args) {
|
||||
return (Function)(args...);
|
||||
}
|
||||
|
||||
template<typename Class, Ret(Class::*Member)(Args... args)>
|
||||
template<typename Class, Ret(Class:: *Member)(Args... args)>
|
||||
static Ret proto(void *instance, Args... args) {
|
||||
return (static_cast<Class *>(instance)->*Member)(args...);
|
||||
}
|
||||
|
||||
Sink(std::vector<call_type> &calls)
|
||||
: calls{calls}
|
||||
{}
|
||||
|
||||
public:
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = typename std::vector<call_type>::size_type;
|
||||
/*! @brief Collector type. */
|
||||
using collector_type = Collector;
|
||||
|
||||
/**
|
||||
* @brief Instance type when it comes to connecting member functions.
|
||||
* @tparam Class Type of class to which the member function belongs.
|
||||
*/
|
||||
template<typename Class>
|
||||
using instance_type = Class *;
|
||||
|
||||
/**
|
||||
* @brief Number of listeners connected to the signal.
|
||||
* @return Number of listeners currently connected.
|
||||
*/
|
||||
size_type size() const noexcept {
|
||||
return calls.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns false if at least a listener is connected to the signal.
|
||||
* @return True if the signal has no listeners connected, false otherwise.
|
||||
*/
|
||||
bool empty() const noexcept {
|
||||
return calls.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects all the listeners from a signal.
|
||||
*/
|
||||
void clear() noexcept {
|
||||
calls.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Connects a free function to a signal.
|
||||
*
|
||||
* The signal handler performs checks to avoid multiple connections for free
|
||||
* functions.
|
||||
* The signal handler performs checks to avoid multiple connections for
|
||||
* free functions.
|
||||
*
|
||||
* @tparam Function A valid free function pointer.
|
||||
*/
|
||||
@@ -175,15 +169,16 @@ public:
|
||||
*
|
||||
* The signal isn't responsible for the connected object. Users must
|
||||
* guarantee that the lifetime of the instance overcomes the one of the
|
||||
* signal. On the other side, the signal handler performs checks to avoid
|
||||
* multiple connections for the same member function of a given instance.
|
||||
* signal. On the other side, the signal handler performs checks to
|
||||
* avoid multiple connections for the same member function of a given
|
||||
* instance.
|
||||
*
|
||||
* @tparam Class Type of class to which the member function belongs.
|
||||
* @tparam Member Member function to connect to the signal.
|
||||
* @param instance A valid instance of type pointer to `Class`.
|
||||
*/
|
||||
template <typename Class, Ret(Class::*Member)(Args...)>
|
||||
void connect(instance_type<Class> instance) {
|
||||
template <typename Class, Ret(Class:: *Member)(Args...) = &Class::receive>
|
||||
void connect(Class *instance) {
|
||||
disconnect<Class, Member>(instance);
|
||||
calls.emplace_back(instance, &proto<Class, Member>);
|
||||
}
|
||||
@@ -204,8 +199,8 @@ public:
|
||||
* @tparam Member Member function to connect to the signal.
|
||||
* @param instance A valid instance of type pointer to `Class`.
|
||||
*/
|
||||
template<typename Class, Ret(Class::*Member)(Args...)>
|
||||
void disconnect(instance_type<Class> instance) {
|
||||
template<typename Class, Ret(Class:: *Member)(Args...)>
|
||||
void disconnect(Class *instance) {
|
||||
call_type target{instance, &proto<Class, Member>};
|
||||
calls.erase(std::remove(calls.begin(), calls.end(), std::move(target)), calls.end());
|
||||
}
|
||||
@@ -216,11 +211,94 @@ public:
|
||||
* @param instance A valid instance of type pointer to `Class`.
|
||||
*/
|
||||
template<typename Class>
|
||||
void disconnect(instance_type<Class> instance) {
|
||||
void disconnect(Class *instance) {
|
||||
auto func = [instance](const call_type &call) { return call.first == instance; };
|
||||
calls.erase(std::remove_if(calls.begin(), calls.end(), std::move(func)), calls.end());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects all the listeners from a signal.
|
||||
*/
|
||||
void disconnect() {
|
||||
calls.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<call_type> &calls;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Unmanaged signal handler definition.
|
||||
*
|
||||
* Unmanaged signal handler. It works directly with naked pointers to classes
|
||||
* and pointers to member functions as well as pointers to free functions. Users
|
||||
* of this class are in charge of disconnecting instances before deleting them.
|
||||
*
|
||||
* This class serves mainly two purposes:
|
||||
*
|
||||
* * Creating signals used later to notify a bunch of listeners.
|
||||
* * Collecting results from a set of functions like in a voting system.
|
||||
*
|
||||
* The default collector does nothing. To properly collect data, define and use
|
||||
* a class that has a call operator the signature of which is `bool(Param)` and:
|
||||
*
|
||||
* * `Param` is a type to which `Ret` can be converted.
|
||||
* * The return type is true if the handler must stop collecting data, false
|
||||
* otherwise.
|
||||
*
|
||||
* @tparam Ret Return type of a function type.
|
||||
* @tparam Args Types of arguments of a function type.
|
||||
* @tparam Collector Type of collector to use, if any.
|
||||
*/
|
||||
template<typename Ret, typename... Args, typename Collector>
|
||||
class SigH<Ret(Args...), Collector> final: private internal::Invoker<Ret(Args...), Collector> {
|
||||
using call_type = typename internal::Invoker<Ret(Args...), Collector>::call_type;
|
||||
|
||||
public:
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = typename std::vector<call_type>::size_type;
|
||||
/*! @brief Collector type. */
|
||||
using collector_type = Collector;
|
||||
/*! @brief Sink type. */
|
||||
using sink_type = Sink<Ret(Args...)>;
|
||||
|
||||
/**
|
||||
* @brief Instance type when it comes to connecting member functions.
|
||||
* @tparam Class Type of class to which the member function belongs.
|
||||
*/
|
||||
template<typename Class>
|
||||
using instance_type = Class *;
|
||||
|
||||
/**
|
||||
* @brief Number of listeners connected to the signal.
|
||||
* @return Number of listeners currently connected.
|
||||
*/
|
||||
size_type size() const ENTT_NOEXCEPT {
|
||||
return calls.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns false if at least a listener is connected to the signal.
|
||||
* @return True if the signal has no listeners connected, false otherwise.
|
||||
*/
|
||||
bool empty() const ENTT_NOEXCEPT {
|
||||
return calls.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a sink object for the given signal.
|
||||
*
|
||||
* A sink is an opaque object used to connect listeners to signals.<br/>
|
||||
* The function type for a listener is the one of the signal to which it
|
||||
* belongs. The order of invocation of the listeners isn't guaranteed.
|
||||
*
|
||||
* @return A temporary sink object.
|
||||
*/
|
||||
sink_type sink() {
|
||||
return { calls };
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Triggers a signal.
|
||||
*
|
||||
@@ -229,7 +307,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...);
|
||||
}
|
||||
}
|
||||
@@ -270,7 +349,7 @@ public:
|
||||
* @param other Signal with which to compare.
|
||||
* @return True if the two signals are identical, false otherwise.
|
||||
*/
|
||||
bool operator==(const SigH &other) const noexcept {
|
||||
bool operator==(const SigH &other) const ENTT_NOEXCEPT {
|
||||
return std::equal(calls.cbegin(), calls.cend(), other.calls.cbegin(), other.calls.cend());
|
||||
}
|
||||
|
||||
@@ -292,7 +371,7 @@ private:
|
||||
* @return True if the two signals are different, false otherwise.
|
||||
*/
|
||||
template<typename Ret, typename... Args>
|
||||
bool operator!=(const SigH<Ret(Args...)> &lhs, const SigH<Ret(Args...)> &rhs) noexcept {
|
||||
bool operator!=(const SigH<Ret(Args...)> &lhs, const SigH<Ret(Args...)> &rhs) ENTT_NOEXCEPT {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,227 +0,0 @@
|
||||
#ifndef ENTT_SIGNAL_SIGNAL_HPP
|
||||
#define ENTT_SIGNAL_SIGNAL_HPP
|
||||
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
#include <cstdint>
|
||||
#include <iterator>
|
||||
#include <algorithm>
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/**
|
||||
* @brief Managed signal handler declaration.
|
||||
*
|
||||
* Primary template isn't defined on purpose. All the specializations give a
|
||||
* compile-time error unless the template parameter is a function type.
|
||||
*/
|
||||
template<typename>
|
||||
class Signal;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Managed signal handler definition.
|
||||
*
|
||||
* Managed signal handler. It works with weak pointers to classes and pointers
|
||||
* to member functions as well as pointers to free functions. References are
|
||||
* automatically removed when the instances to which they point are freed.
|
||||
*
|
||||
* This class can be used to create signals used later to notify a bunch of
|
||||
* listeners.
|
||||
*
|
||||
* @tparam Args Types of arguments of a function type.
|
||||
*/
|
||||
template<typename... Args>
|
||||
class Signal<void(Args...)> final {
|
||||
using proto_type = bool(*)(std::weak_ptr<void> &, Args...);
|
||||
using call_type = std::pair<std::weak_ptr<void>, proto_type>;
|
||||
|
||||
template<void(*Function)(Args...)>
|
||||
static bool proto(std::weak_ptr<void> &, Args... args) {
|
||||
Function(args...);
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename Class, void(Class::*Member)(Args...)>
|
||||
static bool proto(std::weak_ptr<void> &wptr, Args... args) {
|
||||
bool ret = false;
|
||||
|
||||
if(!wptr.expired()) {
|
||||
auto ptr = std::static_pointer_cast<Class>(wptr.lock());
|
||||
(ptr.get()->*Member)(args...);
|
||||
ret = true;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public:
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = std::size_t;
|
||||
|
||||
/**
|
||||
* @brief Instance type when it comes to connecting member functions.
|
||||
* @tparam Class Type of class to which the member function belongs.
|
||||
*/
|
||||
template<typename Class>
|
||||
using instance_type = std::shared_ptr<Class>;
|
||||
|
||||
/**
|
||||
* @brief Number of listeners connected to the signal.
|
||||
* @return Number of listeners currently connected.
|
||||
*/
|
||||
size_type size() const noexcept {
|
||||
return calls.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns false if at least a listener is connected to the signal.
|
||||
* @return True if the signal has no listeners connected, false otherwise.
|
||||
*/
|
||||
bool empty() const noexcept {
|
||||
return calls.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects all the listeners from a signal.
|
||||
*/
|
||||
void clear() noexcept {
|
||||
calls.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Connects a free function to a signal.
|
||||
*
|
||||
* The signal handler performs checks to avoid multiple connections for free
|
||||
* functions.
|
||||
*
|
||||
* @tparam Function A valid free function pointer.
|
||||
*/
|
||||
template<void(*Function)(Args...)>
|
||||
void connect() {
|
||||
disconnect<Function>();
|
||||
calls.emplace_back(std::weak_ptr<void>{}, &proto<Function>);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Connects a member function for a given instance to a signal.
|
||||
*
|
||||
* The signal handler performs checks to avoid multiple connections for the
|
||||
* same member function of a given instance.
|
||||
*
|
||||
* @tparam Class Type of class to which the member function belongs.
|
||||
* @tparam Member Member function to connect to the signal.
|
||||
* @param instance A valid instance of type pointer to `Class`.
|
||||
*/
|
||||
template<typename Class, void(Class::*Member)(Args...)>
|
||||
void connect(instance_type<Class> instance) {
|
||||
disconnect<Class, Member>(instance);
|
||||
calls.emplace_back(std::move(instance), &proto<Class, Member>);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects a free function from a signal.
|
||||
* @tparam Function A valid free function pointer.
|
||||
*/
|
||||
template<void(*Function)(Args...)>
|
||||
void disconnect() {
|
||||
calls.erase(std::remove_if(calls.begin(), calls.end(),
|
||||
[](const call_type &call) { return call.second == &proto<Function> && !call.first.lock(); }
|
||||
), calls.end());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects the given member function from a signal.
|
||||
* @tparam Class Type of class to which the member function belongs.
|
||||
* @tparam Member Member function to connect to the signal.
|
||||
* @param instance A valid instance of type pointer to `Class`.
|
||||
*/
|
||||
template<typename Class, void(Class::*Member)(Args...)>
|
||||
void disconnect(instance_type<Class> instance) {
|
||||
calls.erase(std::remove_if(calls.begin(), calls.end(),
|
||||
[instance{std::move(instance)}](const call_type &call) { return call.second == &proto<Class, Member> && call.first.lock() == instance; }
|
||||
), calls.end());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Removes all existing connections for the given instance.
|
||||
* @tparam Class Type of class to which the member function belongs.
|
||||
* @param instance A valid instance of type pointer to `Class`.
|
||||
*/
|
||||
template<typename Class>
|
||||
void disconnect(instance_type<Class> instance) {
|
||||
calls.erase(std::remove_if(calls.begin(), calls.end(),
|
||||
[instance{std::move(instance)}](const call_type &call) { return call.first.lock() == instance; }
|
||||
), calls.end());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Triggers a signal.
|
||||
*
|
||||
* All the listeners are notified. Order isn't guaranteed.
|
||||
*
|
||||
* @param args Arguments to use to invoke listeners.
|
||||
*/
|
||||
void publish(Args... args) {
|
||||
for(auto it = calls.rbegin(), end = calls.rend(); it != end; it++) {
|
||||
if(!(it->second)(it->first, args...)) {
|
||||
calls.erase(std::next(it).base());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Swaps listeners between the two signals.
|
||||
* @param lhs A valid signal object.
|
||||
* @param rhs A valid signal object.
|
||||
*/
|
||||
friend void swap(Signal &lhs, Signal &rhs) {
|
||||
using std::swap;
|
||||
swap(lhs.calls, rhs.calls);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if the contents of the two signals are identical.
|
||||
*
|
||||
* Two signals are identical if they have the same size and the same
|
||||
* listeners registered exactly in the same order.
|
||||
*
|
||||
* @param other Signal with which to compare.
|
||||
* @return True if the two signals are identical, false otherwise.
|
||||
*/
|
||||
bool operator==(const Signal &other) const noexcept {
|
||||
return std::equal(calls.cbegin(), calls.cend(), other.calls.cbegin(), other.calls.cend(), [](const auto &lhs, const auto &rhs) {
|
||||
return (lhs.second == rhs.second) && (lhs.first.lock() == rhs.first.lock());
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<call_type> calls;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Checks if the contents of the two signals are different.
|
||||
*
|
||||
* Two signals are identical if they have the same size and the same
|
||||
* listeners registered exactly in the same order.
|
||||
*
|
||||
* @tparam Args Types of arguments of a function type.
|
||||
* @param lhs A valid signal object.
|
||||
* @param rhs A valid signal object.
|
||||
* @return True if the two signals are different, false otherwise.
|
||||
*/
|
||||
template<typename... Args>
|
||||
bool operator!=(const Signal<void(Args...)> &lhs, const Signal<void(Args...)> &rhs) noexcept {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_SIGNAL_SIGNAL_HPP
|
||||
@@ -4,98 +4,78 @@
|
||||
|
||||
add_library(odr OBJECT odr.cpp)
|
||||
|
||||
macro(ADD_ENTT_TEST TEST_NAME TEST_SOURCE)
|
||||
add_executable(${TEST_NAME} $<TARGET_OBJECTS:odr> ${TEST_SOURCE})
|
||||
target_link_libraries(${TEST_NAME} PRIVATE gtest_main Threads::Threads)
|
||||
add_test(NAME ${TEST_NAME} COMMAND ${TEST_NAME})
|
||||
endmacro()
|
||||
|
||||
# Test benchmark
|
||||
|
||||
if(BUILD_BENCHMARK)
|
||||
add_executable(
|
||||
benchmark
|
||||
$<TARGET_OBJECTS:odr>
|
||||
benchmark/benchmark.cpp
|
||||
)
|
||||
target_link_libraries(benchmark PRIVATE gtest_main Threads::Threads)
|
||||
add_test(NAME benchmark COMMAND benchmark)
|
||||
ADD_ENTT_TEST(benchmark benchmark/benchmark.cpp)
|
||||
endif()
|
||||
|
||||
# Test mod
|
||||
|
||||
if(BUILD_MOD)
|
||||
add_executable(
|
||||
mod
|
||||
$<TARGET_OBJECTS:odr>
|
||||
mod/duktape.c
|
||||
mod/mod.cpp
|
||||
)
|
||||
target_link_libraries(mod PRIVATE gtest_main Threads::Threads m)
|
||||
add_test(NAME mod COMMAND 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)
|
||||
|
||||
set(MOD_TEST_SOURCE ${DUKTAPE_SRC_DIR}/duktape.c mod/mod.cpp)
|
||||
ADD_ENTT_TEST(mod ${MOD_TEST_SOURCE})
|
||||
target_include_directories(mod PRIVATE ${DUKTAPE_SRC_DIR})
|
||||
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_ENTT_TEST(cereal snapshot/snapshot.cpp)
|
||||
target_include_directories(cereal PRIVATE ${CEREAL_SRC_DIR})
|
||||
endif()
|
||||
|
||||
# Test core
|
||||
|
||||
add_executable(
|
||||
core
|
||||
$<TARGET_OBJECTS:odr>
|
||||
entt/core/family.cpp
|
||||
entt/core/hashed_string.cpp
|
||||
entt/core/ident.cpp
|
||||
)
|
||||
target_link_libraries(core PRIVATE gtest_main Threads::Threads)
|
||||
add_test(NAME core COMMAND core)
|
||||
ADD_ENTT_TEST(algorithm entt/core/algorithm.cpp)
|
||||
ADD_ENTT_TEST(family entt/core/family.cpp)
|
||||
ADD_ENTT_TEST(hashed_string entt/core/hashed_string.cpp)
|
||||
ADD_ENTT_TEST(ident entt/core/ident.cpp)
|
||||
|
||||
# Test entity
|
||||
|
||||
add_executable(
|
||||
entity
|
||||
$<TARGET_OBJECTS:odr>
|
||||
entt/entity/actor.cpp
|
||||
entt/entity/registry.cpp
|
||||
entt/entity/sparse_set.cpp
|
||||
entt/entity/view.cpp
|
||||
)
|
||||
target_link_libraries(entity PRIVATE gtest_main Threads::Threads)
|
||||
add_test(NAME entity COMMAND entity)
|
||||
ADD_ENTT_TEST(actor entt/entity/actor.cpp)
|
||||
ADD_ENTT_TEST(helper entt/entity/helper.cpp)
|
||||
ADD_ENTT_TEST(prototype entt/entity/prototype.cpp)
|
||||
ADD_ENTT_TEST(registry entt/entity/registry.cpp)
|
||||
ADD_ENTT_TEST(snapshot entt/entity/snapshot.cpp)
|
||||
ADD_ENTT_TEST(sparse_set entt/entity/sparse_set.cpp)
|
||||
ADD_ENTT_TEST(view entt/entity/view.cpp)
|
||||
|
||||
# Test locator
|
||||
|
||||
add_executable(
|
||||
locator
|
||||
$<TARGET_OBJECTS:odr>
|
||||
entt/locator/locator.cpp
|
||||
)
|
||||
target_link_libraries(locator PRIVATE gtest_main Threads::Threads)
|
||||
add_test(NAME locator COMMAND locator)
|
||||
ADD_ENTT_TEST(locator entt/locator/locator.cpp)
|
||||
|
||||
# Test process
|
||||
|
||||
add_executable(
|
||||
process
|
||||
$<TARGET_OBJECTS:odr>
|
||||
entt/process/process.cpp
|
||||
entt/process/scheduler.cpp
|
||||
)
|
||||
target_link_libraries(process PRIVATE gtest_main Threads::Threads)
|
||||
add_test(NAME process COMMAND process)
|
||||
ADD_ENTT_TEST(process entt/process/process.cpp)
|
||||
ADD_ENTT_TEST(scheduler entt/process/scheduler.cpp)
|
||||
|
||||
# Test resource
|
||||
|
||||
add_executable(
|
||||
resource
|
||||
$<TARGET_OBJECTS:odr>
|
||||
entt/resource/resource.cpp
|
||||
)
|
||||
target_link_libraries(resource PRIVATE gtest_main Threads::Threads)
|
||||
add_test(NAME resource COMMAND resource)
|
||||
ADD_ENTT_TEST(resource entt/resource/resource.cpp)
|
||||
|
||||
# Test signal
|
||||
|
||||
add_executable(
|
||||
signal
|
||||
$<TARGET_OBJECTS:odr>
|
||||
entt/signal/bus.cpp
|
||||
entt/signal/delegate.cpp
|
||||
entt/signal/dispatcher.cpp
|
||||
entt/signal/emitter.cpp
|
||||
entt/signal/sigh.cpp
|
||||
entt/signal/signal.cpp
|
||||
)
|
||||
target_link_libraries(signal PRIVATE gtest_main Threads::Threads)
|
||||
add_test(NAME signal COMMAND signal)
|
||||
ADD_ENTT_TEST(delegate entt/signal/delegate.cpp)
|
||||
ADD_ENTT_TEST(dispatcher entt/signal/dispatcher.cpp)
|
||||
ADD_ENTT_TEST(emitter entt/signal/emitter.cpp)
|
||||
ADD_ENTT_TEST(sigh entt/signal/sigh.cpp)
|
||||
|
||||
@@ -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([®istry](auto entity) {
|
||||
registry.destroy(entity);
|
||||
}
|
||||
});
|
||||
|
||||
timer.elapsed();
|
||||
}
|
||||
@@ -75,7 +73,8 @@ TEST(Benchmark, IterateCreateDeleteSingleComponent) {
|
||||
|
||||
for(int i = 0; i < 10000; i++) {
|
||||
for(int j = 0; j < 10000; j++) {
|
||||
registry.create<Position>();
|
||||
const auto entity = registry.create();
|
||||
registry.assign<Position>(entity);
|
||||
}
|
||||
|
||||
for(auto entity: view) {
|
||||
@@ -88,240 +87,427 @@ TEST(Benchmark, IterateCreateDeleteSingleComponent) {
|
||||
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++) {
|
||||
registry.create<Position>();
|
||||
for(std::uint64_t i = 0; i < 1000000L; i++) {
|
||||
const auto entity = registry.create();
|
||||
registry.assign<Position>(entity);
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
registry.view<Position>().each([](auto, auto &) {});
|
||||
timer.elapsed();
|
||||
auto test = [®istry](auto func) {
|
||||
Timer timer;
|
||||
registry.view<Position>().each(func);
|
||||
timer.elapsed();
|
||||
};
|
||||
|
||||
test([](auto, const auto &) {});
|
||||
test([](auto, auto &... comp) {
|
||||
using accumulator_type = int[];
|
||||
accumulator_type accumulator = { (comp.x = {}, 0)... };
|
||||
(void)accumulator;
|
||||
});
|
||||
}
|
||||
|
||||
TEST(Benchmark, 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++) {
|
||||
registry.create<Position, Velocity>();
|
||||
for(std::uint64_t i = 0; i < 1000000L; i++) {
|
||||
const auto entity = registry.create();
|
||||
registry.assign<Position>(entity);
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
registry.view<Position, Velocity>().each([](auto, auto &...) {});
|
||||
timer.elapsed();
|
||||
auto test = [®istry](auto func) {
|
||||
Timer timer;
|
||||
registry.view<Position>(entt::raw_t{}).each(func);
|
||||
timer.elapsed();
|
||||
};
|
||||
|
||||
test([](const auto &) {});
|
||||
test([](auto &... comp) {
|
||||
using accumulator_type = int[];
|
||||
accumulator_type accumulator = { (comp.x = {}, 0)... };
|
||||
(void)accumulator;
|
||||
});
|
||||
}
|
||||
|
||||
TEST(Benchmark, IterateTwoComponents10MHalf) {
|
||||
TEST(Benchmark, IterateTwoComponents1M) {
|
||||
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" << std::endl;
|
||||
|
||||
for(std::uint64_t i = 0; i < 10000000L; i++) {
|
||||
auto entity = registry.create<Velocity>();
|
||||
if(i % 2) { registry.assign<Position>(entity); }
|
||||
for(std::uint64_t i = 0; i < 1000000L; i++) {
|
||||
const auto entity = registry.create();
|
||||
registry.assign<Position>(entity);
|
||||
registry.assign<Velocity>(entity);
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
registry.view<Position, Velocity>().each([](auto, auto &...) {});
|
||||
timer.elapsed();
|
||||
auto test = [®istry](auto func) {
|
||||
Timer timer;
|
||||
registry.view<Position, Velocity>().each(func);
|
||||
timer.elapsed();
|
||||
};
|
||||
|
||||
test([](auto, const auto &...) {});
|
||||
test([](auto, auto &... comp) {
|
||||
using accumulator_type = int[];
|
||||
accumulator_type accumulator = { (comp.x = {}, 0)... };
|
||||
(void)accumulator;
|
||||
});
|
||||
}
|
||||
|
||||
TEST(Benchmark, IterateTwoComponents10MOne) {
|
||||
TEST(Benchmark, IterateTwoComponents1MHalf) {
|
||||
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, 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 == 5000000L) { registry.assign<Position>(entity); }
|
||||
for(std::uint64_t i = 0; i < 1000000L; i++) {
|
||||
const auto entity = registry.create();
|
||||
registry.assign<Velocity>(entity);
|
||||
|
||||
if(i % 2) {
|
||||
registry.assign<Position>(entity);
|
||||
}
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
registry.view<Position, Velocity>().each([](auto, auto &...) {});
|
||||
timer.elapsed();
|
||||
auto test = [®istry](auto func) {
|
||||
Timer timer;
|
||||
registry.view<Position, Velocity>().each(func);
|
||||
timer.elapsed();
|
||||
};
|
||||
|
||||
test([](auto, const auto &...) {});
|
||||
test([](auto, auto &... comp) {
|
||||
using accumulator_type = int[];
|
||||
accumulator_type accumulator = { (comp.x = {}, 0)... };
|
||||
(void)accumulator;
|
||||
});
|
||||
}
|
||||
|
||||
TEST(Benchmark, IterateTwoComponentsPersistent10M) {
|
||||
TEST(Benchmark, IterateTwoComponents1MOne) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
std::cout << "Iterating over 1000000 entities, two components, only one entity has all the components" << std::endl;
|
||||
|
||||
for(std::uint64_t i = 0; i < 1000000L; i++) {
|
||||
const auto entity = registry.create();
|
||||
registry.assign<Velocity>(entity);
|
||||
|
||||
if(i == 5000000L) {
|
||||
registry.assign<Position>(entity);
|
||||
}
|
||||
}
|
||||
|
||||
auto test = [®istry](auto func) {
|
||||
Timer timer;
|
||||
registry.view<Position, Velocity>().each(func);
|
||||
timer.elapsed();
|
||||
};
|
||||
|
||||
test([](auto, const auto &...) {});
|
||||
test([](auto, auto &... comp) {
|
||||
using accumulator_type = int[];
|
||||
accumulator_type accumulator = { (comp.x = {}, 0)... };
|
||||
(void)accumulator;
|
||||
});
|
||||
}
|
||||
|
||||
TEST(Benchmark, IterateTwoComponentsPersistent1M) {
|
||||
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++) {
|
||||
registry.create<Position, Velocity>();
|
||||
for(std::uint64_t i = 0; i < 1000000L; i++) {
|
||||
const auto entity = registry.create();
|
||||
registry.assign<Position>(entity);
|
||||
registry.assign<Velocity>(entity);
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
registry.persistent<Position, Velocity>().each([](auto, auto &...) {});
|
||||
timer.elapsed();
|
||||
auto test = [®istry](auto func) {
|
||||
Timer timer;
|
||||
registry.view<Position, Velocity>(entt::persistent_t{}).each(func);
|
||||
timer.elapsed();
|
||||
};
|
||||
|
||||
test([](auto, const auto &...) {});
|
||||
test([](auto, auto &... comp) {
|
||||
using accumulator_type = int[];
|
||||
accumulator_type accumulator = { (comp.x = {}, 0)... };
|
||||
(void)accumulator;
|
||||
});
|
||||
}
|
||||
|
||||
TEST(Benchmark, 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++) {
|
||||
registry.create<Position, Velocity, Comp<1>, Comp<2>, Comp<3>>();
|
||||
for(std::uint64_t i = 0; i < 1000000L; i++) {
|
||||
const auto entity = registry.create();
|
||||
registry.assign<Position>(entity);
|
||||
registry.assign<Velocity>(entity);
|
||||
registry.assign<Comp<1>>(entity);
|
||||
registry.assign<Comp<2>>(entity);
|
||||
registry.assign<Comp<3>>(entity);
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
registry.view<Position, Velocity, Comp<1>, Comp<2>, Comp<3>>().each([](auto, auto &...) {});
|
||||
timer.elapsed();
|
||||
auto test = [®istry](auto func) {
|
||||
Timer timer;
|
||||
registry.view<Position, Velocity, Comp<1>, Comp<2>, Comp<3>>().each(func);
|
||||
timer.elapsed();
|
||||
};
|
||||
|
||||
test([](auto, const auto &...) {});
|
||||
test([](auto, auto &... comp) {
|
||||
using accumulator_type = int[];
|
||||
accumulator_type accumulator = { (comp.x = {}, 0)... };
|
||||
(void)accumulator;
|
||||
});
|
||||
}
|
||||
|
||||
TEST(Benchmark, 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>>();
|
||||
for(std::uint64_t i = 0; i < 1000000L; i++) {
|
||||
const auto entity = registry.create();
|
||||
registry.assign<Velocity>(entity);
|
||||
registry.assign<Comp<1>>(entity);
|
||||
registry.assign<Comp<2>>(entity);
|
||||
registry.assign<Comp<3>>(entity);
|
||||
|
||||
if(i % 2) {
|
||||
registry.assign<Position>(entity);
|
||||
}
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
registry.view<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>().each([](auto, auto &...) {});
|
||||
timer.elapsed();
|
||||
auto test = [®istry](auto func) {
|
||||
Timer timer;
|
||||
registry.view<Position, Velocity, Comp<1>, Comp<2>, Comp<3>>().each(func);
|
||||
timer.elapsed();
|
||||
};
|
||||
|
||||
test([](auto, const auto &...) {});
|
||||
test([](auto, auto &... comp) {
|
||||
using accumulator_type = int[];
|
||||
accumulator_type accumulator = { (comp.x = {}, 0)... };
|
||||
(void)accumulator;
|
||||
});
|
||||
}
|
||||
|
||||
TEST(Benchmark, IterateTenComponents10MHalf) {
|
||||
TEST(Benchmark, IterateFiveComponents1MOne) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, ten components, half of the entities have 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>>();
|
||||
if(i % 2) { registry.assign<Position>(entity); }
|
||||
for(std::uint64_t i = 0; i < 1000000L; i++) {
|
||||
const auto entity = registry.create();
|
||||
registry.assign<Velocity>(entity);
|
||||
registry.assign<Comp<1>>(entity);
|
||||
registry.assign<Comp<2>>(entity);
|
||||
registry.assign<Comp<3>>(entity);
|
||||
|
||||
if(i == 5000000L) {
|
||||
registry.assign<Position>(entity);
|
||||
}
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
registry.view<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>().each([](auto, auto &...) {});
|
||||
timer.elapsed();
|
||||
auto test = [®istry](auto func) {
|
||||
Timer timer;
|
||||
registry.view<Position, Velocity, Comp<1>, Comp<2>, Comp<3>>().each(func);
|
||||
timer.elapsed();
|
||||
};
|
||||
|
||||
test([](auto, const auto &...) {});
|
||||
test([](auto, auto &... comp) {
|
||||
using accumulator_type = int[];
|
||||
accumulator_type accumulator = { (comp.x = {}, 0)... };
|
||||
(void)accumulator;
|
||||
});
|
||||
}
|
||||
|
||||
TEST(Benchmark, IterateTenComponents10MOne) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, ten 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>>();
|
||||
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, 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++) {
|
||||
registry.create<Position, Velocity, Comp<1>, Comp<2>, Comp<3>>();
|
||||
for(std::uint64_t i = 0; i < 1000000L; i++) {
|
||||
const auto entity = registry.create();
|
||||
registry.assign<Position>(entity);
|
||||
registry.assign<Velocity>(entity);
|
||||
registry.assign<Comp<1>>(entity);
|
||||
registry.assign<Comp<2>>(entity);
|
||||
registry.assign<Comp<3>>(entity);
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
registry.persistent<Position, Velocity, Comp<1>, Comp<2>, Comp<3>>().each([](auto, auto &...) {});
|
||||
timer.elapsed();
|
||||
auto test = [®istry](auto func) {
|
||||
Timer timer;
|
||||
registry.view<Position, Velocity, Comp<1>, Comp<2>, Comp<3>>(entt::persistent_t{}).each(func);
|
||||
timer.elapsed();
|
||||
};
|
||||
|
||||
test([](auto, const auto &...) {});
|
||||
test([](auto, auto &... comp) {
|
||||
using accumulator_type = int[];
|
||||
accumulator_type accumulator = { (comp.x = {}, 0)... };
|
||||
(void)accumulator;
|
||||
});
|
||||
}
|
||||
|
||||
TEST(Benchmark, IterateTenComponentsPersistent10M) {
|
||||
TEST(Benchmark, IterateTenComponents1M) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
std::cout << "Iterating over 1000000 entities, ten components" << std::endl;
|
||||
|
||||
for(std::uint64_t i = 0; i < 1000000L; i++) {
|
||||
const auto entity = registry.create();
|
||||
registry.assign<Position>(entity);
|
||||
registry.assign<Velocity>(entity);
|
||||
registry.assign<Comp<1>>(entity);
|
||||
registry.assign<Comp<2>>(entity);
|
||||
registry.assign<Comp<3>>(entity);
|
||||
registry.assign<Comp<4>>(entity);
|
||||
registry.assign<Comp<5>>(entity);
|
||||
registry.assign<Comp<6>>(entity);
|
||||
registry.assign<Comp<7>>(entity);
|
||||
registry.assign<Comp<8>>(entity);
|
||||
}
|
||||
|
||||
auto test = [®istry](auto func) {
|
||||
Timer timer;
|
||||
registry.view<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>().each(func);
|
||||
timer.elapsed();
|
||||
};
|
||||
|
||||
test([](auto, const auto &...) {});
|
||||
test([](auto, auto &... comp) {
|
||||
using accumulator_type = int[];
|
||||
accumulator_type accumulator = { (comp.x = {}, 0)... };
|
||||
(void)accumulator;
|
||||
});
|
||||
}
|
||||
|
||||
TEST(Benchmark, IterateTenComponents1MHalf) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
std::cout << "Iterating over 1000000 entities, ten components, half of the entities have all the components" << std::endl;
|
||||
|
||||
for(std::uint64_t i = 0; i < 1000000L; i++) {
|
||||
const auto entity = registry.create();
|
||||
registry.assign<Velocity>(entity);
|
||||
registry.assign<Comp<1>>(entity);
|
||||
registry.assign<Comp<2>>(entity);
|
||||
registry.assign<Comp<3>>(entity);
|
||||
registry.assign<Comp<4>>(entity);
|
||||
registry.assign<Comp<5>>(entity);
|
||||
registry.assign<Comp<6>>(entity);
|
||||
registry.assign<Comp<7>>(entity);
|
||||
registry.assign<Comp<8>>(entity);
|
||||
|
||||
if(i % 2) {
|
||||
registry.assign<Position>(entity);
|
||||
}
|
||||
}
|
||||
|
||||
auto test = [®istry](auto func) {
|
||||
Timer timer;
|
||||
registry.view<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>().each(func);
|
||||
timer.elapsed();
|
||||
};
|
||||
|
||||
test([](auto, auto &...) {});
|
||||
test([](auto, auto &... comp) {
|
||||
using accumulator_type = int[];
|
||||
accumulator_type accumulator = { (comp.x = {}, 0)... };
|
||||
(void)accumulator;
|
||||
});
|
||||
}
|
||||
|
||||
TEST(Benchmark, IterateTenComponents1MOne) {
|
||||
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++) {
|
||||
const auto entity = registry.create();
|
||||
registry.assign<Velocity>(entity);
|
||||
registry.assign<Comp<1>>(entity);
|
||||
registry.assign<Comp<2>>(entity);
|
||||
registry.assign<Comp<3>>(entity);
|
||||
registry.assign<Comp<4>>(entity);
|
||||
registry.assign<Comp<5>>(entity);
|
||||
registry.assign<Comp<6>>(entity);
|
||||
registry.assign<Comp<7>>(entity);
|
||||
registry.assign<Comp<8>>(entity);
|
||||
|
||||
if(i == 5000000L) {
|
||||
registry.assign<Position>(entity);
|
||||
}
|
||||
}
|
||||
|
||||
auto test = [®istry](auto func) {
|
||||
Timer timer;
|
||||
registry.view<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>().each(func);
|
||||
timer.elapsed();
|
||||
};
|
||||
|
||||
test([](auto, const auto &...) {});
|
||||
test([](auto, auto &... comp) {
|
||||
using accumulator_type = int[];
|
||||
accumulator_type accumulator = { (comp.x = {}, 0)... };
|
||||
(void)accumulator;
|
||||
});
|
||||
}
|
||||
|
||||
TEST(Benchmark, IterateTenComponentsPersistent1M) {
|
||||
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, persistent view" << 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>>();
|
||||
for(std::uint64_t i = 0; i < 1000000L; i++) {
|
||||
const auto entity = registry.create();
|
||||
registry.assign<Position>(entity);
|
||||
registry.assign<Velocity>(entity);
|
||||
registry.assign<Comp<1>>(entity);
|
||||
registry.assign<Comp<2>>(entity);
|
||||
registry.assign<Comp<3>>(entity);
|
||||
registry.assign<Comp<4>>(entity);
|
||||
registry.assign<Comp<5>>(entity);
|
||||
registry.assign<Comp<6>>(entity);
|
||||
registry.assign<Comp<7>>(entity);
|
||||
registry.assign<Comp<8>>(entity);
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
registry.persistent<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>().each([](auto, auto &...) {});
|
||||
timer.elapsed();
|
||||
}
|
||||
auto test = [®istry](auto func) {
|
||||
Timer timer;
|
||||
registry.view<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>(entt::persistent_t{}).each(func);
|
||||
timer.elapsed();
|
||||
};
|
||||
|
||||
TEST(Benchmark, IterateTenComponentsPersistent10MHalf) {
|
||||
entt::DefaultRegistry registry;
|
||||
registry.prepare<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, ten 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, 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 &...) {});
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(Benchmark, IterateTenComponentsPersistent10MOne) {
|
||||
entt::DefaultRegistry registry;
|
||||
registry.prepare<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, ten 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, 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.persistent<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>().each([](auto, auto &...) {});
|
||||
timer.elapsed();
|
||||
test([](auto, const auto &...) {});
|
||||
test([](auto, auto &... comp) {
|
||||
using accumulator_type = int[];
|
||||
accumulator_type accumulator = { (comp.x = {}, 0)... };
|
||||
(void)accumulator;
|
||||
});
|
||||
}
|
||||
|
||||
TEST(Benchmark, SortSingle) {
|
||||
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);
|
||||
const auto entity = registry.create();
|
||||
registry.assign<Position>(entity, i, i);
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
@@ -335,13 +521,13 @@ 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);
|
||||
const auto entity = registry.create();
|
||||
registry.assign<Position>(entity, i, i);
|
||||
registry.assign<Velocity>(entity, i, i);
|
||||
}
|
||||
|
||||
registry.sort<Position>([](const auto &lhs, const auto &rhs) {
|
||||
@@ -354,3 +540,63 @@ TEST(Benchmark, SortMulti) {
|
||||
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(Benchmark, AlmostSortedStdSort) {
|
||||
entt::DefaultRegistry registry;
|
||||
entt::DefaultRegistry::entity_type entities[3];
|
||||
|
||||
std::cout << "Sort 150000 entities, almost sorted, std::sort" << std::endl;
|
||||
|
||||
for(std::uint64_t i = 0; i < 150000L; i++) {
|
||||
const auto entity = registry.create();
|
||||
registry.assign<Position>(entity, i, i);
|
||||
|
||||
if(!(i % 50000)) {
|
||||
entities[i / 50000] = entity;
|
||||
}
|
||||
}
|
||||
|
||||
for(std::uint64_t i = 0; i < 3; ++i) {
|
||||
registry.destroy(entities[i]);
|
||||
const auto entity = registry.create();
|
||||
registry.assign<Position>(entity, 50000 * i, 50000 * i);
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
|
||||
registry.sort<Position>([](const auto &lhs, const auto &rhs) {
|
||||
return lhs.x > rhs.x && lhs.y > rhs.y;
|
||||
});
|
||||
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(Benchmark, AlmostSortedInsertionSort) {
|
||||
entt::DefaultRegistry registry;
|
||||
entt::DefaultRegistry::entity_type entities[3];
|
||||
|
||||
std::cout << "Sort 150000 entities, almost sorted, insertion sort" << std::endl;
|
||||
|
||||
for(std::uint64_t i = 0; i < 150000L; i++) {
|
||||
const auto entity = registry.create();
|
||||
registry.assign<Position>(entity, i, i);
|
||||
|
||||
if(!(i % 50000)) {
|
||||
entities[i / 50000] = entity;
|
||||
}
|
||||
}
|
||||
|
||||
for(std::uint64_t i = 0; i < 3; ++i) {
|
||||
registry.destroy(entities[i]);
|
||||
const auto entity = registry.create();
|
||||
registry.assign<Position>(entity, 50000 * i, 50000 * i);
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
|
||||
registry.sort<Position>([](const auto &lhs, const auto &rhs) {
|
||||
return lhs.x > rhs.x && lhs.y > rhs.y;
|
||||
}, entt::InsertionSort{});
|
||||
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
26
test/entt/core/algorithm.cpp
Normal file
26
test/entt/core/algorithm.cpp
Normal file
@@ -0,0 +1,26 @@
|
||||
#include <array>
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/core/algorithm.hpp>
|
||||
|
||||
TEST(Algorithm, StdSort) {
|
||||
// well, I'm pretty sure it works, it's std::sort!!
|
||||
std::array<int, 5> arr{{4, 1, 3, 2, 0}};
|
||||
entt::StdSort sort;
|
||||
|
||||
sort(arr.begin(), arr.end());
|
||||
|
||||
for(auto i = 0; i < 4; ++i) {
|
||||
ASSERT_LT(arr[i], arr[i+1]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Algorithm, InsertionSort) {
|
||||
std::array<int, 5> arr{{4, 1, 3, 2, 0}};
|
||||
entt::InsertionSort sort;
|
||||
|
||||
sort(arr.begin(), arr.end());
|
||||
|
||||
for(auto i = 0; i < 4; ++i) {
|
||||
ASSERT_LT(arr[i], arr[i+1]);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,18 @@
|
||||
#include <cstddef>
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/core/hashed_string.hpp>
|
||||
|
||||
constexpr bool check(const char *str) {
|
||||
static constexpr bool ptr(const char *str) {
|
||||
using hash_type = entt::HashedString::hash_type;
|
||||
|
||||
return (static_cast<hash_type>(entt::HashedString{str}) == entt::HashedString{str}
|
||||
&& 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>
|
||||
static constexpr bool ref(const char (&str)[N]) {
|
||||
using hash_type = entt::HashedString::hash_type;
|
||||
|
||||
return (static_cast<hash_type>(entt::HashedString{str}) == entt::HashedString{str}
|
||||
@@ -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"};
|
||||
|
||||
|
||||
@@ -2,32 +2,32 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/core/ident.hpp>
|
||||
|
||||
struct A {};
|
||||
struct B {};
|
||||
struct AType {};
|
||||
struct AnotherType {};
|
||||
|
||||
TEST(Identifier, Uniqueness) {
|
||||
constexpr auto ID = entt::ident<A, B>;
|
||||
constexpr A a;
|
||||
constexpr B b;
|
||||
constexpr auto ID = entt::ident<AType, AnotherType>;
|
||||
constexpr AType anInstance;
|
||||
constexpr AnotherType anotherInstance;
|
||||
|
||||
ASSERT_NE(ID.get<A>(), ID.get<B>());
|
||||
ASSERT_EQ(ID.get<A>(), ID.get<decltype(a)>());
|
||||
ASSERT_NE(ID.get<A>(), ID.get<decltype(b)>());
|
||||
ASSERT_EQ(ID.get<A>(), ID.get<A>());
|
||||
ASSERT_EQ(ID.get<B>(), ID.get<B>());
|
||||
ASSERT_NE(ID.get<AType>(), ID.get<AnotherType>());
|
||||
ASSERT_EQ(ID.get<AType>(), ID.get<decltype(anInstance)>());
|
||||
ASSERT_NE(ID.get<AType>(), ID.get<decltype(anotherInstance)>());
|
||||
ASSERT_EQ(ID.get<AType>(), ID.get<AType>());
|
||||
ASSERT_EQ(ID.get<AnotherType>(), ID.get<AnotherType>());
|
||||
|
||||
// test uses in constant expressions
|
||||
switch(ID.get<B>()) {
|
||||
case ID.get<A>():
|
||||
switch(ID.get<AnotherType>()) {
|
||||
case ID.get<AType>():
|
||||
FAIL();
|
||||
break;
|
||||
case ID.get<B>():
|
||||
case ID.get<AnotherType>():
|
||||
SUCCEED();
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Identifier, SingleType) {
|
||||
constexpr auto ID = entt::ident<A>;
|
||||
constexpr auto ID = entt::ident<AType>;
|
||||
std::integral_constant<decltype(ID)::identifier_type, ID.get()> ic;
|
||||
(void)ic;
|
||||
}
|
||||
|
||||
@@ -3,55 +3,74 @@
|
||||
#include <entt/entity/actor.hpp>
|
||||
#include <entt/entity/registry.hpp>
|
||||
|
||||
struct TestActor: entt::DefaultActor<unsigned int> {
|
||||
using entt::DefaultActor<unsigned int>::DefaultActor;
|
||||
void update(unsigned int) {}
|
||||
};
|
||||
struct ActorComponent final {};
|
||||
struct ActorTag final {};
|
||||
|
||||
struct Position final {};
|
||||
struct Velocity final {};
|
||||
|
||||
TEST(Actor, Functionalities) {
|
||||
TEST(Actor, Component) {
|
||||
entt::DefaultRegistry registry;
|
||||
TestActor *actor = new TestActor{registry};
|
||||
const auto &cactor = *actor;
|
||||
entt::DefaultActor actor{registry};
|
||||
const auto &cactor = actor;
|
||||
|
||||
ASSERT_EQ(®istry, &actor->registry());
|
||||
ASSERT_EQ(®istry, &actor.registry());
|
||||
ASSERT_EQ(®istry, &cactor.registry());
|
||||
ASSERT_TRUE(registry.empty<Position>());
|
||||
ASSERT_TRUE(registry.empty<Velocity>());
|
||||
ASSERT_TRUE(registry.empty<ActorComponent>());
|
||||
ASSERT_FALSE(registry.empty());
|
||||
ASSERT_FALSE(actor->has<Position>());
|
||||
ASSERT_FALSE(actor->has<Velocity>());
|
||||
ASSERT_FALSE(actor.has<ActorComponent>());
|
||||
|
||||
const auto &position = actor->set<Position>();
|
||||
const auto &component = actor.assign<ActorComponent>();
|
||||
|
||||
ASSERT_EQ(&position, &actor->get<Position>());
|
||||
ASSERT_EQ(&position, &cactor.get<Position>());
|
||||
ASSERT_FALSE(registry.empty<Position>());
|
||||
ASSERT_TRUE(registry.empty<Velocity>());
|
||||
ASSERT_EQ(&component, &actor.get<ActorComponent>());
|
||||
ASSERT_EQ(&component, &cactor.get<ActorComponent>());
|
||||
ASSERT_FALSE(registry.empty<ActorComponent>());
|
||||
ASSERT_FALSE(registry.empty());
|
||||
ASSERT_TRUE(actor->has<Position>());
|
||||
ASSERT_FALSE(actor->has<Velocity>());
|
||||
ASSERT_TRUE(actor.has<ActorComponent>());
|
||||
|
||||
actor->unset<Position>();
|
||||
actor.remove<ActorComponent>();
|
||||
|
||||
ASSERT_TRUE(registry.empty<Position>());
|
||||
ASSERT_TRUE(registry.empty<Velocity>());
|
||||
ASSERT_TRUE(registry.empty<ActorComponent>());
|
||||
ASSERT_FALSE(registry.empty());
|
||||
ASSERT_FALSE(actor->has<Position>());
|
||||
ASSERT_FALSE(actor->has<Velocity>());
|
||||
ASSERT_FALSE(actor.has<ActorComponent>());
|
||||
}
|
||||
|
||||
actor->set<Position>();
|
||||
actor->set<Velocity>();
|
||||
TEST(Actor, Tag) {
|
||||
entt::DefaultRegistry registry;
|
||||
entt::DefaultActor actor{registry};
|
||||
const auto &cactor = actor;
|
||||
|
||||
ASSERT_EQ(®istry, &actor.registry());
|
||||
ASSERT_EQ(®istry, &cactor.registry());
|
||||
ASSERT_FALSE(registry.has<ActorTag>());
|
||||
ASSERT_FALSE(actor.has<ActorTag>(entt::tag_t{}));
|
||||
|
||||
const auto &tag = actor.assign<ActorTag>(entt::tag_t{});
|
||||
|
||||
ASSERT_EQ(&tag, &actor.get<ActorTag>(entt::tag_t{}));
|
||||
ASSERT_EQ(&tag, &cactor.get<ActorTag>(entt::tag_t{}));
|
||||
ASSERT_TRUE(registry.has<ActorTag>());
|
||||
ASSERT_FALSE(registry.empty());
|
||||
ASSERT_FALSE(registry.empty<Position>());
|
||||
ASSERT_FALSE(registry.empty<Velocity>());
|
||||
ASSERT_TRUE(actor.has<ActorTag>(entt::tag_t{}));
|
||||
|
||||
actor.remove<ActorTag>(entt::tag_t{});
|
||||
|
||||
ASSERT_FALSE(registry.has<ActorTag>());
|
||||
ASSERT_FALSE(registry.empty());
|
||||
ASSERT_FALSE(actor.has<ActorTag>(entt::tag_t{}));
|
||||
}
|
||||
|
||||
TEST(Actor, EntityLifetime) {
|
||||
entt::DefaultRegistry registry;
|
||||
auto *actor = new entt::DefaultActor{registry};
|
||||
actor->assign<ActorComponent>();
|
||||
|
||||
ASSERT_FALSE(registry.empty<ActorComponent>());
|
||||
ASSERT_FALSE(registry.empty());
|
||||
|
||||
registry.each([actor](const auto entity) {
|
||||
ASSERT_EQ(actor->entity(), entity);
|
||||
});
|
||||
|
||||
delete actor;
|
||||
|
||||
ASSERT_TRUE(registry.empty<ActorComponent>());
|
||||
ASSERT_TRUE(registry.empty());
|
||||
ASSERT_TRUE(registry.empty<Position>());
|
||||
ASSERT_TRUE(registry.empty<Velocity>());
|
||||
}
|
||||
|
||||
49
test/entt/entity/helper.cpp
Normal file
49
test/entt/entity/helper.cpp
Normal file
@@ -0,0 +1,49 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/entity/helper.hpp>
|
||||
#include <entt/entity/registry.hpp>
|
||||
|
||||
TEST(Dependency, Functionalities) {
|
||||
entt::DefaultRegistry registry;
|
||||
const auto entity = registry.create();
|
||||
entt::dependency<double, float>(registry.construction<int>());
|
||||
|
||||
ASSERT_FALSE(registry.has<double>(entity));
|
||||
ASSERT_FALSE(registry.has<float>(entity));
|
||||
|
||||
registry.assign<char>(entity);
|
||||
|
||||
ASSERT_FALSE(registry.has<double>(entity));
|
||||
ASSERT_FALSE(registry.has<float>(entity));
|
||||
|
||||
registry.assign<int>(entity);
|
||||
|
||||
ASSERT_TRUE(registry.has<double>(entity));
|
||||
ASSERT_TRUE(registry.has<float>(entity));
|
||||
ASSERT_EQ(registry.get<double>(entity), .0);
|
||||
ASSERT_EQ(registry.get<float>(entity), .0f);
|
||||
|
||||
registry.get<double>(entity) = .3;
|
||||
registry.get<float>(entity) = .1f;
|
||||
registry.remove<int>(entity);
|
||||
registry.assign<int>(entity);
|
||||
|
||||
ASSERT_EQ(registry.get<double>(entity), .3);
|
||||
ASSERT_EQ(registry.get<float>(entity), .1f);
|
||||
|
||||
registry.remove<int>(entity);
|
||||
registry.remove<float>(entity);
|
||||
registry.assign<int>(entity);
|
||||
|
||||
ASSERT_TRUE(registry.has<float>(entity));
|
||||
ASSERT_EQ(registry.get<double>(entity), .3);
|
||||
ASSERT_EQ(registry.get<float>(entity), .0f);
|
||||
|
||||
registry.remove<int>(entity);
|
||||
registry.remove<double>(entity);
|
||||
registry.remove<float>(entity);
|
||||
entt::dependency<double, float>(entt::break_t{}, registry.construction<int>());
|
||||
registry.assign<int>(entity);
|
||||
|
||||
ASSERT_FALSE(registry.has<double>(entity));
|
||||
ASSERT_FALSE(registry.has<float>(entity));
|
||||
}
|
||||
63
test/entt/entity/prototype.cpp
Normal file
63
test/entt/entity/prototype.cpp
Normal file
@@ -0,0 +1,63 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/entity/prototype.hpp>
|
||||
#include <entt/entity/registry.hpp>
|
||||
|
||||
TEST(Prototype, Functionalities) {
|
||||
entt::DefaultRegistry registry;
|
||||
entt::DefaultPrototype prototype;
|
||||
const auto &cprototype = prototype;
|
||||
|
||||
ASSERT_FALSE((prototype.has<int, char>()));
|
||||
ASSERT_TRUE(registry.empty());
|
||||
|
||||
ASSERT_EQ(prototype.set<int>(2), 2);
|
||||
ASSERT_EQ(prototype.set<int>(3), 3);
|
||||
ASSERT_EQ(prototype.set<char>('c'), 'c');
|
||||
|
||||
ASSERT_EQ(prototype.get<int>(), 3);
|
||||
ASSERT_EQ(cprototype.get<char>(), 'c');
|
||||
ASSERT_EQ(std::get<0>(prototype.get<int, char>()), 3);
|
||||
ASSERT_EQ(std::get<1>(cprototype.get<int, char>()), 'c');
|
||||
|
||||
const auto e0 = prototype(registry);
|
||||
|
||||
ASSERT_TRUE((prototype.has<int, char>()));
|
||||
ASSERT_FALSE(registry.orphan(e0));
|
||||
ASSERT_FALSE(registry.empty());
|
||||
|
||||
const auto e1 = prototype(registry);
|
||||
prototype(registry, e0);
|
||||
|
||||
ASSERT_FALSE(registry.orphan(e0));
|
||||
ASSERT_FALSE(registry.orphan(e1));
|
||||
|
||||
ASSERT_TRUE((registry.has<int, char>(e0)));
|
||||
ASSERT_TRUE((registry.has<int, char>(e1)));
|
||||
|
||||
registry.remove<int>(e0);
|
||||
registry.remove<int>(e1);
|
||||
prototype.unset<int>();
|
||||
|
||||
ASSERT_FALSE((prototype.has<int, char>()));
|
||||
ASSERT_FALSE((prototype.has<int>()));
|
||||
ASSERT_TRUE((prototype.has<char>()));
|
||||
|
||||
prototype(registry, e0);
|
||||
prototype(registry, e1);
|
||||
|
||||
ASSERT_FALSE(registry.has<int>(e0));
|
||||
ASSERT_FALSE(registry.has<int>(e1));
|
||||
|
||||
ASSERT_EQ(registry.get<char>(e0), 'c');
|
||||
ASSERT_EQ(registry.get<char>(e1), 'c');
|
||||
|
||||
registry.get<char>(e0) = '*';
|
||||
prototype.assign(registry, e0);
|
||||
|
||||
ASSERT_EQ(registry.get<char>(e0), '*');
|
||||
|
||||
registry.get<char>(e1) = '*';
|
||||
prototype.accommodate(registry, e1);
|
||||
|
||||
ASSERT_EQ(registry.get<char>(e1), 'c');
|
||||
}
|
||||
@@ -1,12 +1,67 @@
|
||||
#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>
|
||||
|
||||
struct Listener {
|
||||
template<typename Component>
|
||||
void incrComponent(entt::DefaultRegistry ®istry, entt::DefaultRegistry::entity_type entity) {
|
||||
ASSERT_TRUE(registry.valid(entity));
|
||||
ASSERT_TRUE(registry.has<Component>(entity));
|
||||
last = entity;
|
||||
++counter;
|
||||
}
|
||||
|
||||
template<typename Tag>
|
||||
void incrTag(entt::DefaultRegistry ®istry, entt::DefaultRegistry::entity_type entity) {
|
||||
ASSERT_TRUE(registry.valid(entity));
|
||||
ASSERT_TRUE(registry.has<Tag>());
|
||||
ASSERT_EQ(registry.attachee<Tag>(), entity);
|
||||
last = entity;
|
||||
++counter;
|
||||
}
|
||||
|
||||
template<typename Component>
|
||||
void decrComponent(entt::DefaultRegistry ®istry, entt::DefaultRegistry::entity_type entity) {
|
||||
ASSERT_TRUE(registry.valid(entity));
|
||||
ASSERT_TRUE(registry.has<Component>(entity));
|
||||
last = entity;
|
||||
--counter;
|
||||
}
|
||||
|
||||
template<typename Tag>
|
||||
void decrTag(entt::DefaultRegistry ®istry, entt::DefaultRegistry::entity_type entity) {
|
||||
ASSERT_TRUE(registry.valid(entity));
|
||||
ASSERT_TRUE(registry.has<Tag>());
|
||||
ASSERT_EQ(registry.attachee<Tag>(), entity);
|
||||
last = entity;
|
||||
--counter;
|
||||
}
|
||||
|
||||
entt::DefaultRegistry::entity_type last;
|
||||
int counter{0};
|
||||
};
|
||||
|
||||
TEST(DefaultRegistry, Types) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
ASSERT_EQ(registry.type<int>(entt::tag_t{}), registry.type<int>(entt::tag_t{}));
|
||||
ASSERT_EQ(registry.type<int>(), registry.type<int>());
|
||||
|
||||
ASSERT_NE(registry.type<int>(entt::tag_t{}), registry.type<double>(entt::tag_t{}));
|
||||
ASSERT_NE(registry.type<int>(), registry.type<double>(entt::tag_t{}));
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, Functionalities) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
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 +70,14 @@ TEST(DefaultRegistry, Functionalities) {
|
||||
ASSERT_TRUE(registry.empty<int>());
|
||||
ASSERT_TRUE(registry.empty<char>());
|
||||
|
||||
auto e1 = registry.create();
|
||||
auto e2 = registry.create<int, char>();
|
||||
const auto e0 = registry.create();
|
||||
const auto e1 = registry.create();
|
||||
|
||||
registry.assign<int>(e1);
|
||||
registry.assign<char>(e1);
|
||||
|
||||
ASSERT_TRUE(registry.has<>(e0));
|
||||
ASSERT_TRUE(registry.has<>(e1));
|
||||
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 +85,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(®istry.get<int>(e1), ®istry.get<int>(e3));
|
||||
ASSERT_NE(®istry.get<char>(e1), ®istry.get<char>(e3));
|
||||
const 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(®istry.get<int>(e0), ®istry.get<int>(e2));
|
||||
ASSERT_NE(®istry.get<char>(e0), ®istry.get<char>(e2));
|
||||
|
||||
ASSERT_NO_THROW(registry.replace<int>(e0, 0));
|
||||
ASSERT_EQ(registry.get<int>(e0), 0);
|
||||
|
||||
ASSERT_NO_THROW(registry.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());
|
||||
@@ -93,7 +158,10 @@ TEST(DefaultRegistry, Functionalities) {
|
||||
ASSERT_EQ(registry.size(), entt::DefaultRegistry::size_type{0});
|
||||
ASSERT_TRUE(registry.empty());
|
||||
|
||||
registry.create<int, char>();
|
||||
const auto e3 = registry.create();
|
||||
|
||||
registry.assign<int>(e3);
|
||||
registry.assign<char>(e3);
|
||||
|
||||
ASSERT_EQ(registry.size<int>(), entt::DefaultRegistry::size_type{1});
|
||||
ASSERT_EQ(registry.size<char>(), entt::DefaultRegistry::size_type{1});
|
||||
@@ -114,24 +182,80 @@ TEST(DefaultRegistry, Functionalities) {
|
||||
ASSERT_TRUE(registry.empty<int>());
|
||||
ASSERT_TRUE(registry.empty<char>());
|
||||
|
||||
e1 = registry.create<int>();
|
||||
e2 = registry.create();
|
||||
const auto e4 = registry.create();
|
||||
const auto e5 = registry.create();
|
||||
|
||||
ASSERT_NO_THROW(registry.reset<int>(e1));
|
||||
ASSERT_NO_THROW(registry.reset<int>(e2));
|
||||
registry.assign<int>(e4);
|
||||
|
||||
ASSERT_NO_THROW(registry.reset<int>(e4));
|
||||
ASSERT_NO_THROW(registry.reset<int>(e5));
|
||||
|
||||
ASSERT_EQ(registry.size<int>(), entt::DefaultRegistry::size_type{0});
|
||||
ASSERT_EQ(registry.size<char>(), entt::DefaultRegistry::size_type{0});
|
||||
ASSERT_TRUE(registry.empty<int>());
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, RawData) {
|
||||
entt::DefaultRegistry registry;
|
||||
const entt::DefaultRegistry &cregistry = registry;
|
||||
const auto entity = registry.create();
|
||||
|
||||
ASSERT_EQ(registry.raw<int>(), nullptr);
|
||||
ASSERT_EQ(cregistry.raw<int>(), nullptr);
|
||||
ASSERT_EQ(cregistry.data<int>(), nullptr);
|
||||
|
||||
registry.assign<int>(entity, 42);
|
||||
|
||||
ASSERT_NE(registry.raw<int>(), nullptr);
|
||||
ASSERT_NE(cregistry.raw<int>(), nullptr);
|
||||
ASSERT_NE(cregistry.data<int>(), nullptr);
|
||||
|
||||
ASSERT_EQ(*registry.raw<int>(), 42);
|
||||
ASSERT_EQ(*cregistry.raw<int>(), 42);
|
||||
ASSERT_EQ(*cregistry.data<int>(), entity);
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, CreateDestroyCornerCase) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
const auto e0 = registry.create();
|
||||
const 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;
|
||||
|
||||
const 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<int>();
|
||||
registry.create<int>();
|
||||
registry.create();
|
||||
registry.assign<int>(registry.create());
|
||||
registry.create();
|
||||
registry.assign<int>(registry.create());
|
||||
registry.create();
|
||||
|
||||
tot = 0u;
|
||||
match = 0u;
|
||||
@@ -142,7 +266,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,78 +293,147 @@ 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, Types) {
|
||||
TEST(DefaultRegistry, Orphans) {
|
||||
entt::DefaultRegistry registry;
|
||||
entt::DefaultRegistry::size_type tot{};
|
||||
|
||||
ASSERT_EQ(registry.tag<int>(), registry.tag<int>());
|
||||
ASSERT_EQ(registry.component<int>(), registry.component<int>());
|
||||
registry.assign<int>(registry.create());
|
||||
registry.create();
|
||||
registry.assign<int>(registry.create());
|
||||
registry.create();
|
||||
registry.assign<double>(entt::tag_t{}, registry.create());
|
||||
|
||||
ASSERT_NE(registry.tag<int>(), registry.tag<double>());
|
||||
ASSERT_NE(registry.component<int>(), registry.component<double>());
|
||||
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, 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) {
|
||||
const auto entity = registry.create();
|
||||
registry.assign<double>(entity);
|
||||
}
|
||||
|
||||
registry.reset();
|
||||
|
||||
for(int i = 0; i < 7; ++i) {
|
||||
const auto entity = registry.create();
|
||||
registry.assign<int>(entity);
|
||||
if(i == 3) { pre = entity; }
|
||||
}
|
||||
|
||||
registry.reset();
|
||||
|
||||
for(int i = 0; i < 5; ++i) {
|
||||
const 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, CloneEntities) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
const auto entity = registry.create();
|
||||
registry.assign<int>(entity, 42);
|
||||
registry.assign<char>(entity, 'c');
|
||||
|
||||
const auto other = registry.clone(entity);
|
||||
|
||||
ASSERT_TRUE(registry.has<int>(other));
|
||||
ASSERT_TRUE(registry.has<char>(other));
|
||||
ASSERT_EQ(registry.get<int>(entity), registry.get<int>(other));
|
||||
ASSERT_EQ(registry.get<char>(entity), registry.get<char>(other));
|
||||
ASSERT_EQ(registry.get<int>(other), 42);
|
||||
ASSERT_EQ(registry.get<char>(other), 'c');
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, AttachSetRemoveTags) {
|
||||
entt::DefaultRegistry registry;
|
||||
const auto &cregistry = registry;
|
||||
|
||||
ASSERT_FALSE(registry.has<int>());
|
||||
|
||||
auto entity = registry.create();
|
||||
registry.attach<int>(entity, 42);
|
||||
const auto entity = registry.create();
|
||||
registry.assign<int>(entt::tag_t{}, entity, 42);
|
||||
|
||||
ASSERT_TRUE(registry.has<int>());
|
||||
ASSERT_TRUE(registry.has<int>(entt::tag_t{}, entity));
|
||||
ASSERT_EQ(registry.get<int>(), 42);
|
||||
ASSERT_EQ(cregistry.get<int>(), 42);
|
||||
ASSERT_EQ(registry.attachee<int>(), entity);
|
||||
|
||||
registry.replace<int>(entt::tag_t{}, 3);
|
||||
|
||||
ASSERT_TRUE(registry.has<int>());
|
||||
ASSERT_TRUE(registry.has<int>(entt::tag_t{}, entity));
|
||||
ASSERT_EQ(registry.get<int>(), 3);
|
||||
ASSERT_EQ(cregistry.get<int>(), 3);
|
||||
ASSERT_EQ(registry.attachee<int>(), entity);
|
||||
|
||||
const auto other = registry.create();
|
||||
registry.move<int>(other);
|
||||
|
||||
ASSERT_TRUE(registry.has<int>());
|
||||
ASSERT_FALSE(registry.has<int>(entt::tag_t{}, entity));
|
||||
ASSERT_TRUE(registry.has<int>(entt::tag_t{}, other));
|
||||
ASSERT_EQ(registry.get<int>(), 3);
|
||||
ASSERT_EQ(cregistry.get<int>(), 3);
|
||||
ASSERT_EQ(registry.attachee<int>(), other);
|
||||
|
||||
registry.remove<int>();
|
||||
|
||||
ASSERT_FALSE(registry.has<int>());
|
||||
ASSERT_FALSE(registry.has<int>(entt::tag_t{}, entity));
|
||||
ASSERT_FALSE(registry.has<int>(entt::tag_t{}, other));
|
||||
|
||||
registry.attach<int>(entity, 42);
|
||||
registry.assign<int>(entt::tag_t{}, entity, 42);
|
||||
registry.destroy(entity);
|
||||
|
||||
ASSERT_FALSE(registry.has<int>());
|
||||
ASSERT_FALSE(registry.has<int>(entt::tag_t{}, entity));
|
||||
ASSERT_FALSE(registry.has<int>(entt::tag_t{}, other));
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, StandardViews) {
|
||||
TEST(DefaultRegistry, StandardView) {
|
||||
entt::DefaultRegistry registry;
|
||||
auto mview = registry.view<int, char>();
|
||||
auto iview = registry.view<int>();
|
||||
auto cview = registry.view<char>();
|
||||
|
||||
registry.create(0, 'c');
|
||||
registry.create(0);
|
||||
registry.create(0, 'c');
|
||||
const auto e0 = registry.create();
|
||||
registry.assign<int>(e0, 0);
|
||||
registry.assign<char>(e0, 'c');
|
||||
|
||||
const auto e1 = registry.create();
|
||||
registry.assign<int>(e1, 0);
|
||||
|
||||
const auto e2 = registry.create();
|
||||
registry.assign<int>(e2, 0);
|
||||
registry.assign<char>(e2, 'c');
|
||||
|
||||
ASSERT_EQ(iview.size(), decltype(iview)::size_type{3});
|
||||
ASSERT_EQ(cview.size(), decltype(cview)::size_type{2});
|
||||
@@ -236,9 +444,9 @@ TEST(DefaultRegistry, StandardViews) {
|
||||
ASSERT_EQ(cnt, decltype(mview)::size_type{2});
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, PersistentViews) {
|
||||
TEST(DefaultRegistry, PersistentView) {
|
||||
entt::DefaultRegistry registry;
|
||||
auto view = registry.persistent<int, char>();
|
||||
auto view = registry.view<int, char>(entt::persistent_t{});
|
||||
|
||||
ASSERT_TRUE((registry.contains<int, char>()));
|
||||
ASSERT_FALSE((registry.contains<int, double>()));
|
||||
@@ -251,9 +459,16 @@ TEST(DefaultRegistry, PersistentViews) {
|
||||
|
||||
ASSERT_FALSE((registry.contains<int, double>()));
|
||||
|
||||
registry.create(0, 'c');
|
||||
registry.create(0);
|
||||
registry.create(0, 'c');
|
||||
const auto e0 = registry.create();
|
||||
registry.assign<int>(e0, 0);
|
||||
registry.assign<char>(e0, 'c');
|
||||
|
||||
const auto e1 = registry.create();
|
||||
registry.assign<int>(e1, 0);
|
||||
|
||||
const auto e2 = registry.create();
|
||||
registry.assign<int>(e2, 0);
|
||||
registry.assign<char>(e2, 'c');
|
||||
|
||||
decltype(view)::size_type cnt{0};
|
||||
view.each([&cnt](auto...) { ++cnt; });
|
||||
@@ -261,10 +476,28 @@ TEST(DefaultRegistry, PersistentViews) {
|
||||
ASSERT_EQ(cnt, decltype(view)::size_type{2});
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, CleanStandardViewsAfterReset) {
|
||||
TEST(DefaultRegistry, RawView) {
|
||||
entt::DefaultRegistry registry;
|
||||
auto view = registry.view<int>(entt::raw_t{});
|
||||
|
||||
const auto e0 = registry.create();
|
||||
registry.assign<int>(e0, 0);
|
||||
registry.assign<char>(e0, 'c');
|
||||
|
||||
const auto e1 = registry.create();
|
||||
registry.assign<int>(e1, 0);
|
||||
registry.assign<char>(e1, 'c');
|
||||
|
||||
decltype(view)::size_type cnt{0};
|
||||
view.each([&cnt](auto &...) { ++cnt; });
|
||||
|
||||
ASSERT_EQ(cnt, decltype(view)::size_type{2});
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, CleanStandardViewAfterReset) {
|
||||
entt::DefaultRegistry registry;
|
||||
auto view = registry.view<int>();
|
||||
registry.create(0);
|
||||
registry.assign<int>(registry.create(), 0);
|
||||
|
||||
ASSERT_EQ(view.size(), entt::DefaultRegistry::size_type{1});
|
||||
|
||||
@@ -273,10 +506,25 @@ TEST(DefaultRegistry, CleanStandardViewsAfterReset) {
|
||||
ASSERT_EQ(view.size(), entt::DefaultRegistry::size_type{0});
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, CleanPersistentViewsAfterReset) {
|
||||
TEST(DefaultRegistry, CleanPersistentViewAfterReset) {
|
||||
entt::DefaultRegistry registry;
|
||||
auto view = registry.persistent<int, char>();
|
||||
registry.create(0, 'c');
|
||||
auto view = registry.view<int, char>(entt::persistent_t{});
|
||||
|
||||
const auto entity = registry.create();
|
||||
registry.assign<int>(entity, 0);
|
||||
registry.assign<char>(entity, 'c');
|
||||
|
||||
ASSERT_EQ(view.size(), entt::DefaultRegistry::size_type{1});
|
||||
|
||||
registry.reset();
|
||||
|
||||
ASSERT_EQ(view.size(), entt::DefaultRegistry::size_type{0});
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, CleanRawViewAfterReset) {
|
||||
entt::DefaultRegistry registry;
|
||||
auto view = registry.view<int>(entt::raw_t{});
|
||||
registry.assign<int>(registry.create(), 0);
|
||||
|
||||
ASSERT_EQ(view.size(), entt::DefaultRegistry::size_type{1});
|
||||
|
||||
@@ -287,8 +535,8 @@ TEST(DefaultRegistry, CleanPersistentViewsAfterReset) {
|
||||
|
||||
TEST(DefaultRegistry, CleanTagsAfterReset) {
|
||||
entt::DefaultRegistry registry;
|
||||
auto entity = registry.create();
|
||||
registry.attach<int>(entity);
|
||||
const auto entity = registry.create();
|
||||
registry.assign<int>(entt::tag_t{}, entity);
|
||||
|
||||
ASSERT_TRUE(registry.has<int>());
|
||||
|
||||
@@ -302,9 +550,9 @@ TEST(DefaultRegistry, SortSingle) {
|
||||
|
||||
int val = 0;
|
||||
|
||||
registry.create(val++);
|
||||
registry.create(val++);
|
||||
registry.create(val++);
|
||||
registry.assign<int>(registry.create(), val++);
|
||||
registry.assign<int>(registry.create(), val++);
|
||||
registry.assign<int>(registry.create(), val++);
|
||||
|
||||
for(auto entity: registry.view<int>()) {
|
||||
ASSERT_EQ(registry.get<int>(entity), --val);
|
||||
@@ -323,9 +571,11 @@ TEST(DefaultRegistry, SortMulti) {
|
||||
unsigned int uval = 0u;
|
||||
int ival = 0;
|
||||
|
||||
registry.create(uval++, ival++);
|
||||
registry.create(uval++, ival++);
|
||||
registry.create(uval++, ival++);
|
||||
for(auto i = 0; i < 3; ++i) {
|
||||
const auto entity = registry.create();
|
||||
registry.assign<unsigned int>(entity, uval++);
|
||||
registry.assign<int>(entity, ival++);
|
||||
}
|
||||
|
||||
for(auto entity: registry.view<unsigned int>()) {
|
||||
ASSERT_EQ(registry.get<unsigned int>(entity), --uval);
|
||||
@@ -346,3 +596,170 @@ 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;
|
||||
const 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.assign<int>(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()) {
|
||||
const auto other = dst.create();
|
||||
dst.template assign<std::decay_t<decltype(component)>>(other, component);
|
||||
ref.emplace(entity, other);
|
||||
} else {
|
||||
using component_type = std::decay_t<decltype(component)>;
|
||||
dst.template assign<component_type>(ref[entity], component);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
auto e0 = src.create();
|
||||
src.assign<int>(e0);
|
||||
src.assign<float>(e0);
|
||||
src.assign<double>(e0);
|
||||
|
||||
auto e1 = src.create();
|
||||
src.assign<char>(e1);
|
||||
src.assign<float>(e1);
|
||||
src.assign<int>(e1);
|
||||
|
||||
auto e2 = dst.create();
|
||||
dst.assign<int>(e2);
|
||||
dst.assign<char>(e2);
|
||||
dst.assign<double>(e2);
|
||||
|
||||
auto e3 = dst.create();
|
||||
dst.assign<float>(e3);
|
||||
dst.assign<int>(e3);
|
||||
|
||||
auto eq = [](auto begin, auto end) { ASSERT_EQ(begin, end); };
|
||||
auto ne = [](auto begin, auto end) { ASSERT_NE(begin, end); };
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, ComponentSignals) {
|
||||
entt::DefaultRegistry registry;
|
||||
Listener listener;
|
||||
|
||||
registry.construction<int>().connect<Listener, &Listener::incrComponent<int>>(&listener);
|
||||
registry.destruction<int>().connect<Listener, &Listener::decrComponent<int>>(&listener);
|
||||
|
||||
auto e0 = registry.create();
|
||||
auto e1 = registry.create();
|
||||
|
||||
registry.assign<int>(e0);
|
||||
registry.assign<int>(e1);
|
||||
|
||||
ASSERT_EQ(listener.counter, 2);
|
||||
ASSERT_EQ(listener.last, e1);
|
||||
|
||||
registry.remove<int>(e0);
|
||||
|
||||
ASSERT_EQ(listener.counter, 1);
|
||||
ASSERT_EQ(listener.last, e0);
|
||||
|
||||
registry.destruction<int>().disconnect<Listener, &Listener::decrComponent<int>>(&listener);
|
||||
registry.remove<int>(e1);
|
||||
|
||||
ASSERT_EQ(listener.counter, 1);
|
||||
ASSERT_EQ(listener.last, e0);
|
||||
|
||||
registry.construction<int>().disconnect<Listener, &Listener::incrComponent<int>>(&listener);
|
||||
registry.assign<int>(e1);
|
||||
|
||||
ASSERT_EQ(listener.counter, 1);
|
||||
ASSERT_EQ(listener.last, e0);
|
||||
|
||||
registry.construction<int>().connect<Listener, &Listener::incrComponent<int>>(&listener);
|
||||
registry.destruction<int>().connect<Listener, &Listener::decrComponent<int>>(&listener);
|
||||
registry.assign<int>(e0);
|
||||
registry.reset<int>(e1);
|
||||
|
||||
ASSERT_EQ(listener.counter, 1);
|
||||
ASSERT_EQ(listener.last, e1);
|
||||
|
||||
registry.reset<int>();
|
||||
|
||||
ASSERT_EQ(listener.counter, 0);
|
||||
ASSERT_EQ(listener.last, e0);
|
||||
|
||||
registry.assign<int>(e0);
|
||||
registry.assign<int>(e1);
|
||||
registry.destroy(e1);
|
||||
|
||||
ASSERT_EQ(listener.counter, 1);
|
||||
ASSERT_EQ(listener.last, e1);
|
||||
|
||||
e1 = registry.clone(e0);
|
||||
|
||||
ASSERT_EQ(listener.counter, 2);
|
||||
ASSERT_EQ(listener.last, e1);
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, TagSignals) {
|
||||
entt::DefaultRegistry registry;
|
||||
Listener listener;
|
||||
|
||||
registry.construction<int>(entt::tag_t{}).connect<Listener, &Listener::incrTag<int>>(&listener);
|
||||
registry.destruction<int>(entt::tag_t{}).connect<Listener, &Listener::decrTag<int>>(&listener);
|
||||
|
||||
auto e0 = registry.create();
|
||||
registry.assign<int>(entt::tag_t{}, e0);
|
||||
|
||||
ASSERT_EQ(listener.counter, 1);
|
||||
ASSERT_EQ(listener.last, e0);
|
||||
|
||||
auto e1 = registry.create();
|
||||
registry.move<int>(e1);
|
||||
registry.remove<int>();
|
||||
|
||||
ASSERT_EQ(listener.counter, 0);
|
||||
ASSERT_EQ(listener.last, e1);
|
||||
|
||||
registry.construction<int>(entt::tag_t{}).disconnect<Listener, &Listener::incrTag<int>>(&listener);
|
||||
registry.destruction<int>(entt::tag_t{}).disconnect<Listener, &Listener::decrTag<int>>(&listener);
|
||||
registry.assign<int>(entt::tag_t{}, e0);
|
||||
registry.remove<int>();
|
||||
|
||||
ASSERT_EQ(listener.counter, 0);
|
||||
ASSERT_EQ(listener.last, e1);
|
||||
|
||||
registry.construction<int>(entt::tag_t{}).connect<Listener, &Listener::incrTag<int>>(&listener);
|
||||
registry.destruction<int>(entt::tag_t{}).connect<Listener, &Listener::decrTag<int>>(&listener);
|
||||
|
||||
registry.assign<int>(entt::tag_t{}, e0);
|
||||
registry.destroy(e0);
|
||||
|
||||
ASSERT_EQ(listener.counter, 0);
|
||||
ASSERT_EQ(listener.last, e0);
|
||||
}
|
||||
|
||||
575
test/entt/entity/snapshot.cpp
Normal file
575
test/entt/entity/snapshot.cpp
Normal file
@@ -0,0 +1,575 @@
|
||||
#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) {
|
||||
using accumulator_type = int[];
|
||||
accumulator_type accumulator = { (std::get<std::queue<Value>>(storage).push(value), 0)... };
|
||||
(void)accumulator;
|
||||
}
|
||||
|
||||
private:
|
||||
Storage &storage;
|
||||
};
|
||||
|
||||
template<typename Storage>
|
||||
struct InputArchive {
|
||||
InputArchive(Storage &storage)
|
||||
: storage{storage}
|
||||
{}
|
||||
|
||||
template<typename... Value>
|
||||
void operator()(Value &... value) {
|
||||
auto assign = [this](auto &value) {
|
||||
auto &queue = std::get<std::queue<std::decay_t<decltype(value)>>>(storage);
|
||||
value = queue.front();
|
||||
queue.pop();
|
||||
};
|
||||
|
||||
using accumulator_type = int[];
|
||||
accumulator_type accumulator = { (assign(value), 0)... };
|
||||
(void)accumulator;
|
||||
}
|
||||
|
||||
private:
|
||||
Storage &storage;
|
||||
};
|
||||
|
||||
struct AComponent {};
|
||||
|
||||
struct AnotherComponent {
|
||||
int key;
|
||||
int value;
|
||||
};
|
||||
|
||||
struct WhatAComponent {
|
||||
entt::DefaultRegistry::entity_type bar;
|
||||
std::vector<entt::DefaultRegistry::entity_type> quux;
|
||||
};
|
||||
|
||||
TEST(Snapshot, Dump) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
const auto e0 = registry.create();
|
||||
registry.assign<int>(e0, 42);
|
||||
registry.assign<char>(e0, 'c');
|
||||
registry.assign<double>(e0, .1);
|
||||
|
||||
const auto e1 = registry.create();
|
||||
|
||||
const auto e2 = registry.create();
|
||||
registry.assign<int>(e2, 3);
|
||||
|
||||
const auto e3 = registry.create();
|
||||
registry.assign<char>(e3, '0');
|
||||
registry.assign<float>(entt::tag_t{}, e3, .3f);
|
||||
|
||||
const auto e4 = registry.create();
|
||||
registry.assign<AComponent>(entt::tag_t{}, 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<WhatAComponent>
|
||||
>;
|
||||
|
||||
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;
|
||||
|
||||
const auto e0 = registry.create();
|
||||
registry.assign<int>(e0, 42);
|
||||
registry.assign<char>(e0, 'c');
|
||||
registry.assign<double>(e0, .1);
|
||||
|
||||
const auto e1 = registry.create();
|
||||
|
||||
const auto e2 = registry.create();
|
||||
registry.assign<int>(e2, 3);
|
||||
|
||||
const auto e3 = registry.create();
|
||||
registry.assign<char>(e3, '0');
|
||||
registry.assign<float>(entt::tag_t{}, e3, .3f);
|
||||
|
||||
const auto e4 = registry.create();
|
||||
registry.assign<AComponent>(entt::tag_t{}, 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<WhatAComponent>
|
||||
>;
|
||||
|
||||
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, Iterator) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
for(auto i = 0; i < 50; ++i) {
|
||||
const auto entity = registry.create();
|
||||
registry.assign<AnotherComponent>(entity, i, i);
|
||||
|
||||
if(i % 2) {
|
||||
registry.assign<AComponent>(entity);
|
||||
}
|
||||
}
|
||||
|
||||
using storage_type = std::tuple<
|
||||
std::queue<entt::DefaultRegistry::entity_type>,
|
||||
std::queue<AnotherComponent>
|
||||
>;
|
||||
|
||||
storage_type storage;
|
||||
OutputArchive<storage_type> output{storage};
|
||||
InputArchive<storage_type> input{storage};
|
||||
|
||||
const auto view = registry.view<AComponent>();
|
||||
const auto size = view.size();
|
||||
|
||||
registry.snapshot().component<AnotherComponent>(output, view.cbegin(), view.cend());
|
||||
registry.reset();
|
||||
registry.restore().component<AnotherComponent>(input);
|
||||
|
||||
ASSERT_EQ(registry.view<AnotherComponent>().size(), size);
|
||||
|
||||
registry.view<AnotherComponent>().each([](const auto entity, auto &&...) {
|
||||
ASSERT_TRUE(entity % 2);
|
||||
});
|
||||
}
|
||||
|
||||
TEST(Snapshot, Continuous) {
|
||||
using entity_type = entt::DefaultRegistry::entity_type;
|
||||
|
||||
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<WhatAComponent>,
|
||||
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.reset();
|
||||
|
||||
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<WhatAComponent>(entity, entity);
|
||||
} else if(i == 2) {
|
||||
src.assign<double>(entt::tag_t{}, entity, .3);
|
||||
}
|
||||
}
|
||||
|
||||
src.view<WhatAComponent>().each([&entities](auto, auto &whatAComponent) {
|
||||
whatAComponent.quux.insert(whatAComponent.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, WhatAComponent>(output)
|
||||
.tag<double>(output);
|
||||
|
||||
loader.entities(input)
|
||||
.destroyed(input)
|
||||
.component<AComponent, AnotherComponent, WhatAComponent>(input, &WhatAComponent::bar, &WhatAComponent::quux)
|
||||
.tag<double>(input)
|
||||
.orphans();
|
||||
|
||||
decltype(dst.size()) aComponentCnt{};
|
||||
decltype(dst.size()) anotherComponentCnt{};
|
||||
decltype(dst.size()) whatAComponentCnt{};
|
||||
|
||||
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<WhatAComponent>().each([&dst, &whatAComponentCnt](auto entity, const auto &component) {
|
||||
ASSERT_EQ(entity, component.bar);
|
||||
|
||||
for(auto entity: component.quux) {
|
||||
ASSERT_TRUE(dst.valid(entity));
|
||||
}
|
||||
|
||||
++whatAComponentCnt;
|
||||
});
|
||||
|
||||
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, WhatAComponent, AnotherComponent>(output)
|
||||
.tag<double>(output);
|
||||
|
||||
loader.entities(input)
|
||||
.destroyed(input)
|
||||
.component<AComponent, WhatAComponent, AnotherComponent>(input, &WhatAComponent::bar, &WhatAComponent::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<WhatAComponent>(), whatAComponentCnt);
|
||||
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<WhatAComponent>().each([entity](auto, auto &component) {
|
||||
component.bar = entity;
|
||||
});
|
||||
|
||||
src.snapshot()
|
||||
.entities(output)
|
||||
.destroyed(output)
|
||||
.component<WhatAComponent, AComponent, AnotherComponent>(output)
|
||||
.tag<double>(output);
|
||||
|
||||
loader.entities(input)
|
||||
.destroyed(input)
|
||||
.component<WhatAComponent, AComponent, AnotherComponent>(input, &WhatAComponent::bar, &WhatAComponent::quux)
|
||||
.tag<double>(input)
|
||||
.orphans();
|
||||
|
||||
dst.view<WhatAComponent>().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, WhatAComponent>(output)
|
||||
.tag<double>(output);
|
||||
|
||||
loader.entities(input)
|
||||
.destroyed(input)
|
||||
.component<AComponent, AnotherComponent, WhatAComponent>(input, &WhatAComponent::bar, &WhatAComponent::quux)
|
||||
.tag<double>(input)
|
||||
.orphans()
|
||||
.shrink();
|
||||
|
||||
dst.view<WhatAComponent>().each([&dst](auto, auto &component) {
|
||||
ASSERT_FALSE(dst.valid(component.bar));
|
||||
});
|
||||
|
||||
ASSERT_FALSE(loader.has(entity));
|
||||
|
||||
entity = src.create();
|
||||
|
||||
src.view<WhatAComponent>().each([entity](auto, auto &component) {
|
||||
component.bar = entity;
|
||||
});
|
||||
|
||||
dst.reset<AComponent>();
|
||||
aComponentCnt = src.size<AComponent>();
|
||||
|
||||
src.snapshot()
|
||||
.entities(output)
|
||||
.destroyed(output)
|
||||
.component<AComponent, WhatAComponent, AnotherComponent>(output)
|
||||
.tag<double>(output);
|
||||
|
||||
loader.entities(input)
|
||||
.destroyed(input)
|
||||
.component<AComponent, WhatAComponent, AnotherComponent>(input, &WhatAComponent::bar, &WhatAComponent::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<WhatAComponent, AComponent, AnotherComponent>(output)
|
||||
.tag<double>(output);
|
||||
|
||||
loader.entities(input)
|
||||
.destroyed(input)
|
||||
.component<WhatAComponent, AComponent, AnotherComponent>(input, &WhatAComponent::bar, &WhatAComponent::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));
|
||||
}
|
||||
|
||||
TEST(Snapshot, SyncDataMembers) {
|
||||
using entity_type = entt::DefaultRegistry::entity_type;
|
||||
|
||||
entt::DefaultRegistry src;
|
||||
entt::DefaultRegistry dst;
|
||||
|
||||
entt::ContinuousLoader<entity_type> loader{dst};
|
||||
|
||||
using storage_type = std::tuple<
|
||||
std::queue<entity_type>,
|
||||
std::queue<WhatAComponent>
|
||||
>;
|
||||
|
||||
storage_type storage;
|
||||
OutputArchive<storage_type> output{storage};
|
||||
InputArchive<storage_type> input{storage};
|
||||
|
||||
src.create();
|
||||
src.create();
|
||||
|
||||
src.reset();
|
||||
|
||||
auto parent = src.create();
|
||||
auto child = src.create();
|
||||
|
||||
src.assign<WhatAComponent>(entt::tag_t{}, child, parent).quux.push_back(parent);
|
||||
src.assign<WhatAComponent>(child, child).quux.push_back(child);
|
||||
|
||||
src.snapshot().entities(output)
|
||||
.component<WhatAComponent>(output)
|
||||
.tag<WhatAComponent>(output);
|
||||
|
||||
loader.entities(input)
|
||||
.component<WhatAComponent>(input, &WhatAComponent::bar, &WhatAComponent::quux)
|
||||
.tag<WhatAComponent>(input, &WhatAComponent::bar, &WhatAComponent::quux);
|
||||
|
||||
ASSERT_FALSE(dst.valid(parent));
|
||||
ASSERT_FALSE(dst.valid(child));
|
||||
|
||||
ASSERT_TRUE(dst.has<WhatAComponent>());
|
||||
ASSERT_EQ(dst.attachee<WhatAComponent>(), loader.map(child));
|
||||
ASSERT_EQ(dst.get<WhatAComponent>().bar, loader.map(parent));
|
||||
ASSERT_EQ(dst.get<WhatAComponent>().quux[0], loader.map(parent));
|
||||
ASSERT_TRUE(dst.has<WhatAComponent>(loader.map(child)));
|
||||
|
||||
const auto &component = dst.get<WhatAComponent>(loader.map(child));
|
||||
|
||||
ASSERT_EQ(component.bar, loader.map(child));
|
||||
ASSERT_EQ(component.quux[0], loader.map(child));
|
||||
}
|
||||
@@ -1,12 +1,15 @@
|
||||
#include <unordered_set>
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/entity/sparse_set.hpp>
|
||||
|
||||
TEST(SparseSetNoType, Functionalities) {
|
||||
entt::SparseSet<unsigned int> set;
|
||||
const auto &cset = set;
|
||||
|
||||
ASSERT_NO_THROW(set.reserve(42));
|
||||
ASSERT_TRUE(set.empty());
|
||||
ASSERT_EQ(set.size(), 0u);
|
||||
ASSERT_EQ(cset.begin(), cset.end());
|
||||
ASSERT_EQ(set.begin(), set.end());
|
||||
ASSERT_FALSE(set.has(0));
|
||||
ASSERT_FALSE(set.has(42));
|
||||
@@ -17,15 +20,18 @@ TEST(SparseSetNoType, Functionalities) {
|
||||
|
||||
ASSERT_FALSE(set.empty());
|
||||
ASSERT_EQ(set.size(), 1u);
|
||||
ASSERT_NE(cset.begin(), cset.end());
|
||||
ASSERT_NE(set.begin(), set.end());
|
||||
ASSERT_FALSE(set.has(0));
|
||||
ASSERT_TRUE(set.has(42));
|
||||
ASSERT_TRUE(set.fast(42));
|
||||
ASSERT_EQ(set.get(42), 0u);
|
||||
|
||||
set.destroy(42);
|
||||
|
||||
ASSERT_TRUE(set.empty());
|
||||
ASSERT_EQ(set.size(), 0u);
|
||||
ASSERT_EQ(cset.begin(), cset.end());
|
||||
ASSERT_EQ(set.begin(), set.end());
|
||||
ASSERT_FALSE(set.has(0));
|
||||
ASSERT_FALSE(set.has(42));
|
||||
@@ -38,6 +44,7 @@ TEST(SparseSetNoType, Functionalities) {
|
||||
|
||||
ASSERT_TRUE(set.empty());
|
||||
ASSERT_EQ(set.size(), 0u);
|
||||
ASSERT_EQ(cset.begin(), cset.end());
|
||||
ASSERT_EQ(set.begin(), set.end());
|
||||
ASSERT_FALSE(set.has(0));
|
||||
ASSERT_FALSE(set.has(42));
|
||||
@@ -47,6 +54,23 @@ TEST(SparseSetNoType, Functionalities) {
|
||||
other = std::move(set);
|
||||
}
|
||||
|
||||
TEST(SparseSetNoType, Clone) {
|
||||
entt::SparseSet<unsigned int> set;
|
||||
|
||||
ASSERT_FALSE(set.has(0));
|
||||
ASSERT_FALSE(set.has(42));
|
||||
|
||||
set.construct(0);
|
||||
|
||||
ASSERT_TRUE(set.has(0));
|
||||
ASSERT_FALSE(set.has(42));
|
||||
|
||||
set.clone(42, 0);
|
||||
|
||||
ASSERT_TRUE(set.has(0));
|
||||
ASSERT_TRUE(set.has(42));
|
||||
}
|
||||
|
||||
TEST(SparseSetNoType, DataBeginEnd) {
|
||||
entt::SparseSet<unsigned int> set;
|
||||
|
||||
@@ -62,45 +86,222 @@ 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);
|
||||
|
||||
auto cbegin = set.cbegin();
|
||||
auto cend = set.cend();
|
||||
|
||||
ASSERT_NE(cbegin, cend);
|
||||
ASSERT_EQ(cbegin+3, cend);
|
||||
ASSERT_NE(cbegin, cend);
|
||||
ASSERT_EQ(cbegin += 3, cend);
|
||||
ASSERT_EQ(cbegin, cend);
|
||||
}
|
||||
|
||||
TEST(SparseSetWithType, 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) {
|
||||
entt::SparseSet<unsigned int, int> set;
|
||||
const auto &cset = set;
|
||||
|
||||
ASSERT_NO_THROW(set.reserve(42));
|
||||
ASSERT_TRUE(set.empty());
|
||||
ASSERT_EQ(set.size(), 0u);
|
||||
ASSERT_EQ(cset.begin(), cset.end());
|
||||
ASSERT_EQ(set.begin(), set.end());
|
||||
ASSERT_FALSE(set.has(0));
|
||||
ASSERT_FALSE(set.has(42));
|
||||
|
||||
set.construct(42, 3);
|
||||
|
||||
ASSERT_EQ(set.get(42), 3);
|
||||
ASSERT_FALSE(set.empty());
|
||||
ASSERT_EQ(set.size(), 1u);
|
||||
ASSERT_NE(cset.begin(), cset.end());
|
||||
ASSERT_NE(set.begin(), set.end());
|
||||
ASSERT_FALSE(set.has(0));
|
||||
ASSERT_TRUE(set.has(42));
|
||||
ASSERT_TRUE(set.fast(42));
|
||||
ASSERT_EQ(set.get(42), 3);
|
||||
|
||||
set.destroy(42);
|
||||
|
||||
ASSERT_TRUE(set.empty());
|
||||
ASSERT_EQ(set.size(), 0u);
|
||||
ASSERT_EQ(cset.begin(), cset.end());
|
||||
ASSERT_EQ(set.begin(), set.end());
|
||||
ASSERT_FALSE(set.has(0));
|
||||
ASSERT_FALSE(set.has(42));
|
||||
@@ -113,6 +314,7 @@ TEST(SparseSetWithType, Functionalities) {
|
||||
|
||||
ASSERT_TRUE(set.empty());
|
||||
ASSERT_EQ(set.size(), 0u);
|
||||
ASSERT_EQ(cset.begin(), cset.end());
|
||||
ASSERT_EQ(set.begin(), set.end());
|
||||
ASSERT_FALSE(set.has(0));
|
||||
ASSERT_FALSE(set.has(42));
|
||||
@@ -122,6 +324,39 @@ TEST(SparseSetWithType, Functionalities) {
|
||||
other = std::move(set);
|
||||
}
|
||||
|
||||
TEST(SparseSetWithType, Clone) {
|
||||
entt::SparseSet<unsigned int, int> set;
|
||||
|
||||
ASSERT_FALSE(set.has(0));
|
||||
ASSERT_FALSE(set.has(42));
|
||||
|
||||
set.construct(0, 3);
|
||||
|
||||
ASSERT_TRUE(set.has(0));
|
||||
ASSERT_FALSE(set.has(42));
|
||||
ASSERT_EQ(set.get(0), 3);
|
||||
|
||||
set.clone(42, 0);
|
||||
|
||||
ASSERT_TRUE(set.has(0));
|
||||
ASSERT_TRUE(set.has(42));
|
||||
ASSERT_EQ(set.get(0), set.get(42));
|
||||
ASSERT_EQ(set.get(42), 3);
|
||||
}
|
||||
|
||||
TEST(SparseSetWithType, AggregatesMustWork) {
|
||||
struct AggregateType { int value; };
|
||||
// the goal of this test is to enforce the requirements for aggregate types
|
||||
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,10 +375,19 @@ 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);
|
||||
|
||||
auto cbegin = set.cbegin();
|
||||
auto cend = set.cend();
|
||||
|
||||
ASSERT_NE(cbegin, cend);
|
||||
ASSERT_EQ(cbegin+3, cend);
|
||||
ASSERT_NE(cbegin, cend);
|
||||
ASSERT_EQ(cbegin += 3, cend);
|
||||
ASSERT_EQ(cbegin, cend);
|
||||
}
|
||||
|
||||
TEST(SparseSetWithType, SortOrdered) {
|
||||
@@ -174,11 +418,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 +454,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 +490,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 +507,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 +517,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 +536,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 +547,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 +693,33 @@ TEST(SparseSetWithType, RespectUnordered) {
|
||||
ASSERT_EQ(*(rhs.data() + 4u), 4u);
|
||||
ASSERT_EQ(*(rhs.data() + 5u), 5u);
|
||||
}
|
||||
|
||||
TEST(SparseSetWithType, ReferencesGuaranteed) {
|
||||
struct InternalType { int value; };
|
||||
|
||||
entt::SparseSet<unsigned int, InternalType> 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);
|
||||
}
|
||||
|
||||
@@ -4,47 +4,98 @@
|
||||
|
||||
TEST(View, SingleComponent) {
|
||||
entt::DefaultRegistry registry;
|
||||
auto view = registry.view<char>();
|
||||
|
||||
auto e1 = registry.create();
|
||||
auto e2 = registry.create<int, char>();
|
||||
const auto e0 = registry.create();
|
||||
const auto e1 = registry.create();
|
||||
|
||||
ASSERT_TRUE(view.empty());
|
||||
|
||||
registry.assign<int>(e1);
|
||||
registry.assign<char>(e1);
|
||||
|
||||
ASSERT_NO_THROW(registry.view<char>().begin()++);
|
||||
ASSERT_NO_THROW(++registry.view<char>().begin());
|
||||
|
||||
auto view = registry.view<char>();
|
||||
|
||||
ASSERT_NE(view.begin(), view.end());
|
||||
ASSERT_EQ(view.size(), typename decltype(view)::size_type{1});
|
||||
ASSERT_FALSE(view.empty());
|
||||
|
||||
registry.assign<char>(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());
|
||||
ASSERT_TRUE(view.empty());
|
||||
}
|
||||
|
||||
TEST(View, SingleComponentBeginEnd) {
|
||||
entt::DefaultRegistry registry;
|
||||
auto view = registry.view<int>();
|
||||
const auto &cview = view;
|
||||
|
||||
for(auto i = 0; i < 3; ++i) {
|
||||
registry.assign<int>(registry.create());
|
||||
}
|
||||
|
||||
auto test = [](auto begin, auto end) {
|
||||
ASSERT_NE(begin, end);
|
||||
ASSERT_NE(++begin, end);
|
||||
ASSERT_NE(begin++, end);
|
||||
ASSERT_EQ(begin+1, end);
|
||||
ASSERT_NE(begin, end);
|
||||
ASSERT_EQ((begin += 1), end);
|
||||
ASSERT_EQ(begin, end);
|
||||
};
|
||||
|
||||
test(view.begin(), view.end());
|
||||
test(cview.begin(), cview.end());
|
||||
test(view.cbegin(), view.cend());
|
||||
}
|
||||
|
||||
TEST(View, SingleComponentContains) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
const auto e0 = registry.create();
|
||||
registry.assign<int>(e0);
|
||||
|
||||
const auto e1 = registry.create();
|
||||
registry.assign<int>(e1);
|
||||
|
||||
registry.destroy(e0);
|
||||
|
||||
auto view = registry.view<int>();
|
||||
|
||||
ASSERT_FALSE(view.contains(e0));
|
||||
ASSERT_TRUE(view.contains(e1));
|
||||
}
|
||||
|
||||
TEST(View, SingleComponentEmpty) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
registry.create<char, double>();
|
||||
registry.create<char>();
|
||||
const auto e0 = registry.create();
|
||||
registry.assign<char>(e0);
|
||||
registry.assign<double>(e0);
|
||||
|
||||
const auto e1 = registry.create();
|
||||
registry.assign<char>(e1);
|
||||
|
||||
auto view = registry.view<int>();
|
||||
|
||||
@@ -59,8 +110,8 @@ TEST(View, SingleComponentEmpty) {
|
||||
TEST(View, SingleComponentEach) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
registry.create<int, char>();
|
||||
registry.create<int, char>();
|
||||
registry.assign<int>(registry.create());
|
||||
registry.assign<int>(registry.create());
|
||||
|
||||
auto view = registry.view<int>();
|
||||
const auto &cview = static_cast<const decltype(view) &>(view);
|
||||
@@ -77,37 +128,104 @@ TEST(View, SingleComponentEach) {
|
||||
|
||||
TEST(View, MultipleComponent) {
|
||||
entt::DefaultRegistry registry;
|
||||
auto view = registry.view<int, char>();
|
||||
|
||||
auto e1 = registry.create<char>();
|
||||
auto e2 = registry.create<int, char>();
|
||||
ASSERT_TRUE(view.empty());
|
||||
|
||||
const auto e0 = registry.create();
|
||||
registry.assign<char>(e0);
|
||||
|
||||
const auto e1 = registry.create();
|
||||
registry.assign<int>(e1);
|
||||
|
||||
ASSERT_FALSE(view.empty());
|
||||
|
||||
registry.assign<char>(e1);
|
||||
|
||||
auto it = registry.view<char>().begin();
|
||||
|
||||
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()));
|
||||
|
||||
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, MultipleComponentBeginEnd) {
|
||||
entt::DefaultRegistry registry;
|
||||
auto view = registry.view<int, char>();
|
||||
const auto &cview = view;
|
||||
|
||||
for(auto i = 0; i < 3; ++i) {
|
||||
const auto entity = registry.create();
|
||||
registry.assign<int>(entity);
|
||||
registry.assign<char>(entity);
|
||||
}
|
||||
|
||||
auto test = [](auto begin, auto end) {
|
||||
ASSERT_NE(begin, end);
|
||||
ASSERT_NE(++begin, end);
|
||||
ASSERT_NE(begin++, end);
|
||||
ASSERT_EQ(begin+1, end);
|
||||
ASSERT_NE(begin, end);
|
||||
ASSERT_EQ((begin += 1), end);
|
||||
ASSERT_EQ(begin, end);
|
||||
};
|
||||
|
||||
test(cview.begin(), cview.end());
|
||||
test(view.begin(), view.end());
|
||||
test(view.cbegin(), view.cend());
|
||||
}
|
||||
|
||||
TEST(View, MultipleComponentContains) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
const auto e0 = registry.create();
|
||||
registry.assign<int>(e0);
|
||||
registry.assign<char>(e0);
|
||||
|
||||
const auto e1 = registry.create();
|
||||
registry.assign<int>(e1);
|
||||
registry.assign<char>(e1);
|
||||
|
||||
registry.destroy(e0);
|
||||
|
||||
auto view = registry.view<int, char>();
|
||||
|
||||
ASSERT_FALSE(view.contains(e0));
|
||||
ASSERT_TRUE(view.contains(e1));
|
||||
}
|
||||
|
||||
TEST(View, MultipleComponentEmpty) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
registry.create<double, int, float>();
|
||||
registry.create<char, float>();
|
||||
const auto e0 = registry.create();
|
||||
registry.assign<double>(e0);
|
||||
registry.assign<int>(e0);
|
||||
registry.assign<float>(e0);
|
||||
|
||||
const auto e1 = registry.create();
|
||||
registry.assign<char>(e1);
|
||||
registry.assign<float>(e1);
|
||||
|
||||
auto view = registry.view<char, int, float>();
|
||||
|
||||
@@ -120,8 +238,13 @@ TEST(View, MultipleComponentEmpty) {
|
||||
TEST(View, MultipleComponentEach) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
registry.create<int, char>();
|
||||
registry.create<int, char>();
|
||||
const auto e0 = registry.create();
|
||||
registry.assign<int>(e0);
|
||||
registry.assign<char>(e0);
|
||||
|
||||
const auto e1 = registry.create();
|
||||
registry.assign<int>(e1);
|
||||
registry.assign<char>(e1);
|
||||
|
||||
auto view = registry.view<int, char>();
|
||||
const auto &cview = static_cast<const decltype(view) &>(view);
|
||||
@@ -136,95 +259,191 @@ TEST(View, MultipleComponentEach) {
|
||||
ASSERT_EQ(cnt, std::size_t{0});
|
||||
}
|
||||
|
||||
TEST(View, MultipleComponentEachWithHoles) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
const auto e0 = registry.create();
|
||||
const auto e1 = registry.create();
|
||||
const auto e2 = registry.create();
|
||||
|
||||
registry.assign<char>(e0, '0');
|
||||
registry.assign<char>(e1, '1');
|
||||
|
||||
registry.assign<int>(e0, 0);
|
||||
registry.assign<int>(e2, 2);
|
||||
|
||||
auto view = registry.view<char, int>();
|
||||
|
||||
view.each([e0](auto entity, const char &c, const int &i) {
|
||||
if(e0 == entity) {
|
||||
ASSERT_EQ(c, '0');
|
||||
ASSERT_EQ(i, 0);
|
||||
} else {
|
||||
FAIL();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
TEST(PersistentView, Prepare) {
|
||||
entt::DefaultRegistry registry;
|
||||
registry.prepare<int, char>();
|
||||
auto view = registry.view<int, char>(entt::persistent_t{});
|
||||
|
||||
auto e1 = registry.create<char>();
|
||||
auto e2 = registry.create<int, char>();
|
||||
ASSERT_TRUE(view.empty());
|
||||
|
||||
ASSERT_NO_THROW((registry.persistent<int, char>().begin()++));
|
||||
ASSERT_NO_THROW((++registry.persistent<int, char>().begin()));
|
||||
const auto e0 = registry.create();
|
||||
registry.assign<char>(e0);
|
||||
|
||||
auto view = registry.persistent<int, char>();
|
||||
const auto e1 = registry.create();
|
||||
registry.assign<int>(e1);
|
||||
registry.assign<char>(e1);
|
||||
|
||||
ASSERT_FALSE(view.empty());
|
||||
ASSERT_NO_THROW((registry.view<int, char>(entt::persistent_t{}).begin()++));
|
||||
ASSERT_NO_THROW((++registry.view<int, char>(entt::persistent_t{}).begin()));
|
||||
|
||||
ASSERT_NE(view.begin(), view.end());
|
||||
ASSERT_EQ(view.size(), typename decltype(view)::size_type{1});
|
||||
|
||||
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());
|
||||
ASSERT_TRUE(view.empty());
|
||||
}
|
||||
|
||||
TEST(PersistentView, NoPrepare) {
|
||||
entt::DefaultRegistry registry;
|
||||
auto view = registry.view<int, char>(entt::persistent_t{});
|
||||
|
||||
auto e1 = registry.create<char>();
|
||||
auto e2 = registry.create<int, char>();
|
||||
ASSERT_TRUE(view.empty());
|
||||
|
||||
ASSERT_NO_THROW((registry.persistent<int, char>().begin()++));
|
||||
ASSERT_NO_THROW((++registry.persistent<int, char>().begin()));
|
||||
const auto e0 = registry.create();
|
||||
registry.assign<char>(e0);
|
||||
|
||||
auto view = registry.persistent<int, char>();
|
||||
const auto e1 = registry.create();
|
||||
registry.assign<int>(e1);
|
||||
registry.assign<char>(e1);
|
||||
|
||||
ASSERT_FALSE(view.empty());
|
||||
ASSERT_NO_THROW((registry.view<int, char>(entt::persistent_t{}).begin()++));
|
||||
ASSERT_NO_THROW((++registry.view<int, char>(entt::persistent_t{}).begin()));
|
||||
|
||||
ASSERT_NE(view.begin(), view.end());
|
||||
ASSERT_EQ(view.size(), typename decltype(view)::size_type{1});
|
||||
|
||||
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());
|
||||
ASSERT_TRUE(view.empty());
|
||||
}
|
||||
|
||||
TEST(PersistentView, BeginEnd) {
|
||||
entt::DefaultRegistry registry;
|
||||
auto view = registry.view<int, char>(entt::persistent_t{});
|
||||
const auto &cview = view;
|
||||
|
||||
for(auto i = 0; i < 3; ++i) {
|
||||
const auto entity = registry.create();
|
||||
registry.assign<int>(entity);
|
||||
registry.assign<char>(entity);
|
||||
}
|
||||
|
||||
auto test = [](auto begin, auto end) {
|
||||
ASSERT_NE(begin, end);
|
||||
ASSERT_NE(++begin, end);
|
||||
ASSERT_NE(begin++, end);
|
||||
ASSERT_EQ(begin+1, end);
|
||||
ASSERT_NE(begin, end);
|
||||
ASSERT_EQ((begin += 1), end);
|
||||
ASSERT_EQ(begin, end);
|
||||
};
|
||||
|
||||
test(cview.begin(), cview.end());
|
||||
test(view.begin(), view.end());
|
||||
test(view.cbegin(), view.cend());
|
||||
}
|
||||
|
||||
TEST(PersistentView, Contains) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
const auto e0 = registry.create();
|
||||
registry.assign<int>(e0);
|
||||
registry.assign<char>(e0);
|
||||
|
||||
const auto e1 = registry.create();
|
||||
registry.assign<int>(e1);
|
||||
registry.assign<char>(e1);
|
||||
|
||||
registry.destroy(e0);
|
||||
|
||||
auto view = registry.view<int, char>(entt::persistent_t{});
|
||||
|
||||
ASSERT_FALSE(view.contains(e0));
|
||||
ASSERT_TRUE(view.contains(e1));
|
||||
}
|
||||
|
||||
TEST(PersistentView, Empty) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
registry.create<double, int, float>();
|
||||
registry.create<char, float>();
|
||||
const auto e0 = registry.create();
|
||||
registry.assign<double>(e0);
|
||||
registry.assign<int>(e0);
|
||||
registry.assign<float>(e0);
|
||||
|
||||
for(auto entity: registry.persistent<char, int, float>()) {
|
||||
const auto e1 = registry.create();
|
||||
registry.assign<char>(e1);
|
||||
registry.assign<float>(e1);
|
||||
|
||||
for(auto entity: registry.view<char, int, float>(entt::persistent_t{})) {
|
||||
(void)entity;
|
||||
FAIL();
|
||||
}
|
||||
|
||||
for(auto entity: registry.persistent<double, char, int, float>()) {
|
||||
for(auto entity: registry.view<double, char, int, float>(entt::persistent_t{})) {
|
||||
(void)entity;
|
||||
FAIL();
|
||||
}
|
||||
@@ -234,10 +453,15 @@ TEST(PersistentView, Each) {
|
||||
entt::DefaultRegistry registry;
|
||||
registry.prepare<int, char>();
|
||||
|
||||
registry.create<int, char>();
|
||||
registry.create<int, char>();
|
||||
const auto e0 = registry.create();
|
||||
registry.assign<int>(e0);
|
||||
registry.assign<char>(e0);
|
||||
|
||||
auto view = registry.persistent<int, char>();
|
||||
const auto e1 = registry.create();
|
||||
registry.assign<int>(e1);
|
||||
registry.assign<char>(e1);
|
||||
|
||||
auto view = registry.view<int, char>(entt::persistent_t{});
|
||||
const auto &cview = static_cast<const decltype(view) &>(view);
|
||||
std::size_t cnt = 0;
|
||||
|
||||
@@ -254,22 +478,22 @@ TEST(PersistentView, Sort) {
|
||||
entt::DefaultRegistry registry;
|
||||
registry.prepare<int, unsigned int>();
|
||||
|
||||
auto e1 = registry.create();
|
||||
auto e2 = registry.create();
|
||||
auto e3 = registry.create();
|
||||
const auto e0 = registry.create();
|
||||
const auto e1 = registry.create();
|
||||
const auto e2 = 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>();
|
||||
auto view = registry.view<int, unsigned int>(entt::persistent_t{});
|
||||
|
||||
for(auto entity: view) {
|
||||
ASSERT_EQ(view.get<unsigned int>(entity), --uval);
|
||||
@@ -284,3 +508,118 @@ TEST(PersistentView, Sort) {
|
||||
ASSERT_EQ(view.get<int>(entity), ival++);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(RawView, Functionalities) {
|
||||
entt::DefaultRegistry registry;
|
||||
auto view = registry.view<char>(entt::raw_t{});
|
||||
|
||||
ASSERT_TRUE(view.empty());
|
||||
|
||||
const auto e0 = registry.create();
|
||||
const auto e1 = registry.create();
|
||||
|
||||
registry.assign<int>(e1);
|
||||
registry.assign<char>(e1);
|
||||
|
||||
ASSERT_FALSE(view.empty());
|
||||
ASSERT_NO_THROW(registry.view<char>(entt::raw_t{}).begin()++);
|
||||
ASSERT_NO_THROW(++registry.view<char>(entt::raw_t{}).begin());
|
||||
|
||||
ASSERT_NE(view.begin(), view.end());
|
||||
ASSERT_EQ(view.size(), typename decltype(view)::size_type{1});
|
||||
|
||||
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());
|
||||
ASSERT_TRUE(view.empty());
|
||||
}
|
||||
|
||||
TEST(RawView, BeginEnd) {
|
||||
entt::DefaultRegistry registry;
|
||||
auto view = registry.view<int>(entt::raw_t{});
|
||||
const auto &cview = view;
|
||||
|
||||
for(auto i = 0; i < 3; ++i) {
|
||||
registry.assign<int>(registry.create());
|
||||
}
|
||||
|
||||
auto test = [](auto begin, auto end) {
|
||||
ASSERT_NE(begin, end);
|
||||
ASSERT_NE(++begin, end);
|
||||
ASSERT_NE(begin++, end);
|
||||
ASSERT_EQ(begin+1, end);
|
||||
ASSERT_NE(begin, end);
|
||||
ASSERT_EQ((begin += 1), end);
|
||||
ASSERT_EQ(begin, end);
|
||||
};
|
||||
|
||||
test(cview.begin(), cview.end());
|
||||
test(view.begin(), view.end());
|
||||
test(view.cbegin(), view.cend());
|
||||
}
|
||||
|
||||
TEST(RawView, Empty) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
const auto e0 = registry.create();
|
||||
registry.assign<char>(e0);
|
||||
registry.assign<double>(e0);
|
||||
|
||||
const auto e1 = registry.create();
|
||||
registry.assign<char>(e1);
|
||||
|
||||
auto view = registry.view<int>(entt::raw_t{});
|
||||
|
||||
ASSERT_EQ(view.size(), entt::DefaultRegistry::size_type{0});
|
||||
|
||||
for(auto &&component: view) {
|
||||
(void)component;
|
||||
FAIL();
|
||||
}
|
||||
}
|
||||
|
||||
TEST(RawView, Each) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
registry.assign<int>(registry.create(), 1);
|
||||
registry.assign<int>(registry.create(), 3);
|
||||
|
||||
auto view = registry.view<int>(entt::raw_t{});
|
||||
const auto &cview = static_cast<const decltype(view) &>(view);
|
||||
std::size_t cnt = 0;
|
||||
|
||||
view.each([&cnt](int &v) { cnt += (v % 2); });
|
||||
|
||||
ASSERT_EQ(cnt, std::size_t{2});
|
||||
|
||||
cview.each([&cnt](const int &v) { cnt -= (v % 2); });
|
||||
|
||||
ASSERT_EQ(cnt, std::size_t{0});
|
||||
}
|
||||
|
||||
@@ -1,49 +1,49 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/locator/locator.hpp>
|
||||
|
||||
struct A {};
|
||||
struct AService {};
|
||||
|
||||
struct B {
|
||||
struct AnotherService {
|
||||
virtual void f(bool) = 0;
|
||||
bool check{false};
|
||||
};
|
||||
|
||||
struct D: B {
|
||||
D(int): B{} {}
|
||||
struct DerivedService: AnotherService {
|
||||
DerivedService(int): AnotherService{} {}
|
||||
void f(bool b) override { check = b; }
|
||||
};
|
||||
|
||||
TEST(ServiceLocator, Functionalities) {
|
||||
using entt::ServiceLocator;
|
||||
|
||||
ASSERT_TRUE(ServiceLocator<A>::empty());
|
||||
ASSERT_TRUE(ServiceLocator<B>::empty());
|
||||
ASSERT_TRUE(ServiceLocator<AService>::empty());
|
||||
ASSERT_TRUE(ServiceLocator<AnotherService>::empty());
|
||||
|
||||
ServiceLocator<A>::set();
|
||||
ServiceLocator<AService>::set();
|
||||
|
||||
ASSERT_FALSE(ServiceLocator<A>::empty());
|
||||
ASSERT_TRUE(ServiceLocator<B>::empty());
|
||||
ASSERT_FALSE(ServiceLocator<AService>::empty());
|
||||
ASSERT_TRUE(ServiceLocator<AnotherService>::empty());
|
||||
|
||||
ServiceLocator<A>::reset();
|
||||
ServiceLocator<AService>::reset();
|
||||
|
||||
ASSERT_TRUE(ServiceLocator<A>::empty());
|
||||
ASSERT_TRUE(ServiceLocator<B>::empty());
|
||||
ASSERT_TRUE(ServiceLocator<AService>::empty());
|
||||
ASSERT_TRUE(ServiceLocator<AnotherService>::empty());
|
||||
|
||||
ServiceLocator<A>::set(std::make_shared<A>());
|
||||
ServiceLocator<AService>::set(std::make_shared<AService>());
|
||||
|
||||
ASSERT_FALSE(ServiceLocator<A>::empty());
|
||||
ASSERT_TRUE(ServiceLocator<B>::empty());
|
||||
ASSERT_FALSE(ServiceLocator<AService>::empty());
|
||||
ASSERT_TRUE(ServiceLocator<AnotherService>::empty());
|
||||
|
||||
ServiceLocator<B>::set<D>(42);
|
||||
ServiceLocator<AnotherService>::set<DerivedService>(42);
|
||||
|
||||
ASSERT_FALSE(ServiceLocator<A>::empty());
|
||||
ASSERT_FALSE(ServiceLocator<B>::empty());
|
||||
ASSERT_FALSE(ServiceLocator<AService>::empty());
|
||||
ASSERT_FALSE(ServiceLocator<AnotherService>::empty());
|
||||
|
||||
ServiceLocator<B>::get().lock()->f(!ServiceLocator<B>::get().lock()->check);
|
||||
ServiceLocator<AnotherService>::get().lock()->f(!ServiceLocator<AnotherService>::get().lock()->check);
|
||||
|
||||
ASSERT_TRUE(ServiceLocator<B>::get().lock()->check);
|
||||
ASSERT_TRUE(ServiceLocator<AnotherService>::get().lock()->check);
|
||||
|
||||
ServiceLocator<B>::ref().f(!ServiceLocator<B>::get().lock()->check);
|
||||
ServiceLocator<AnotherService>::ref().f(!ServiceLocator<AnotherService>::get().lock()->check);
|
||||
|
||||
ASSERT_FALSE(ServiceLocator<B>::get().lock()->check);
|
||||
ASSERT_FALSE(ServiceLocator<AnotherService>::get().lock()->check);
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -1,141 +0,0 @@
|
||||
#include <memory>
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/signal/bus.hpp>
|
||||
|
||||
struct EventA
|
||||
{
|
||||
EventA(int x, int y): value{x+y} {}
|
||||
int value;
|
||||
};
|
||||
|
||||
struct EventB {};
|
||||
struct EventC {};
|
||||
|
||||
struct MyListener
|
||||
{
|
||||
void receive(const EventA &) { A++; }
|
||||
static void listen(const EventB &) { B++; }
|
||||
void receive(const EventC &) { C++; }
|
||||
void reset() { A = 0; B = 0; C = 0; }
|
||||
int A{0};
|
||||
static int B;
|
||||
int C{0};
|
||||
};
|
||||
|
||||
int MyListener::B = 0;
|
||||
|
||||
template<typename Bus, typename Listener>
|
||||
void testRegUnregEmit(Listener listener) {
|
||||
Bus bus;
|
||||
|
||||
listener->reset();
|
||||
bus.template publish<EventA>(40, 2);
|
||||
bus.template publish<EventB>();
|
||||
bus.template publish<EventC>();
|
||||
|
||||
ASSERT_EQ(bus.size(), (decltype(bus.size()))0);
|
||||
ASSERT_TRUE(bus.empty());
|
||||
ASSERT_EQ(listener->A, 0);
|
||||
ASSERT_EQ(listener->B, 0);
|
||||
ASSERT_EQ(listener->C, 0);
|
||||
|
||||
bus.reg(listener);
|
||||
bus.template connect<EventB, &MyListener::listen>();
|
||||
|
||||
listener->reset();
|
||||
bus.template publish<EventA>(40, 2);
|
||||
bus.template publish<EventB>();
|
||||
bus.template publish<EventC>();
|
||||
|
||||
ASSERT_EQ(bus.size(), (decltype(bus.size()))3);
|
||||
ASSERT_FALSE(bus.empty());
|
||||
ASSERT_EQ(listener->A, 1);
|
||||
ASSERT_EQ(listener->B, 1);
|
||||
ASSERT_EQ(listener->C, 1);
|
||||
|
||||
bus.unreg(listener);
|
||||
|
||||
listener->reset();
|
||||
bus.template publish<EventA>(40, 2);
|
||||
bus.template publish<EventB>();
|
||||
bus.template publish<EventC>();
|
||||
|
||||
ASSERT_EQ(bus.size(), (decltype(bus.size()))1);
|
||||
ASSERT_FALSE(bus.empty());
|
||||
ASSERT_EQ(listener->A, 0);
|
||||
ASSERT_EQ(listener->B, 1);
|
||||
ASSERT_EQ(listener->C, 0);
|
||||
|
||||
bus.template disconnect<EventB, MyListener::listen>();
|
||||
|
||||
listener->reset();
|
||||
bus.template publish<EventA>(40, 2);
|
||||
bus.template publish<EventB>();
|
||||
bus.template publish<EventC>();
|
||||
|
||||
ASSERT_EQ(bus.size(), (decltype(bus.size()))0);
|
||||
ASSERT_TRUE(bus.empty());
|
||||
ASSERT_EQ(listener->A, 0);
|
||||
ASSERT_EQ(listener->B, 0);
|
||||
ASSERT_EQ(listener->C, 0);
|
||||
}
|
||||
|
||||
TEST(ManagedBus, RegUnregEmit) {
|
||||
using MyManagedBus = entt::ManagedBus<EventA, EventB, EventC>;
|
||||
testRegUnregEmit<MyManagedBus>(std::make_shared<MyListener>());
|
||||
}
|
||||
|
||||
TEST(ManagedBus, ExpiredListeners) {
|
||||
entt::ManagedBus<EventA, EventB, EventC> bus;
|
||||
auto listener = std::make_shared<MyListener>();
|
||||
|
||||
listener->reset();
|
||||
bus.reg(listener);
|
||||
bus.template publish<EventA>(40, 2);
|
||||
bus.template publish<EventB>();
|
||||
|
||||
ASSERT_EQ(bus.size(), (decltype(bus.size()))2);
|
||||
ASSERT_FALSE(bus.empty());
|
||||
ASSERT_EQ(listener->A, 1);
|
||||
ASSERT_EQ(listener->B, 0);
|
||||
|
||||
listener->reset();
|
||||
listener = nullptr;
|
||||
|
||||
ASSERT_EQ(bus.size(), (decltype(bus.size()))2);
|
||||
ASSERT_FALSE(bus.empty());
|
||||
|
||||
EXPECT_NO_THROW(bus.template publish<EventA>(40, 2));
|
||||
EXPECT_NO_THROW(bus.template publish<EventC>());
|
||||
|
||||
ASSERT_EQ(bus.size(), (decltype(bus.size()))0);
|
||||
ASSERT_TRUE(bus.empty());
|
||||
}
|
||||
|
||||
TEST(UnmanagedBus, RegUnregEmit) {
|
||||
using MyUnmanagedBus = entt::UnmanagedBus<EventA, EventB, EventC>;
|
||||
auto ptr = std::make_unique<MyListener>();
|
||||
testRegUnregEmit<MyUnmanagedBus>(ptr.get());
|
||||
}
|
||||
|
||||
TEST(UnmanagedBus, ExpiredListeners) {
|
||||
entt::UnmanagedBus<EventA, EventB, EventC> bus;
|
||||
auto listener = std::make_unique<MyListener>();
|
||||
|
||||
listener->reset();
|
||||
bus.reg(listener.get());
|
||||
bus.template publish<EventA>(40, 2);
|
||||
bus.template publish<EventB>();
|
||||
|
||||
ASSERT_EQ(bus.size(), (decltype(bus.size()))2);
|
||||
ASSERT_FALSE(bus.empty());
|
||||
ASSERT_EQ(listener->A, 1);
|
||||
ASSERT_EQ(listener->B, 0);
|
||||
|
||||
listener->reset();
|
||||
listener = nullptr;
|
||||
|
||||
// dangling pointer inside ... well, unmanaged means unmanaged!! :-)
|
||||
ASSERT_EQ(bus.size(), (decltype(bus.size()))2);
|
||||
ASSERT_FALSE(bus.empty());
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/signal/delegate.hpp>
|
||||
|
||||
int f(int i) {
|
||||
int delegateFunction(int i) {
|
||||
return i*i;
|
||||
}
|
||||
|
||||
struct S {
|
||||
int f(int i) {
|
||||
struct DelegateFunctor {
|
||||
int operator()(int i) {
|
||||
return i+i;
|
||||
}
|
||||
};
|
||||
@@ -14,13 +14,13 @@ struct S {
|
||||
TEST(Delegate, Functionalities) {
|
||||
entt::Delegate<int(int)> ffdel;
|
||||
entt::Delegate<int(int)> mfdel;
|
||||
S test;
|
||||
DelegateFunctor functor;
|
||||
|
||||
ASSERT_EQ(ffdel(42), int{});
|
||||
ASSERT_EQ(mfdel(42), int{});
|
||||
|
||||
ffdel.connect<&f>();
|
||||
mfdel.connect<S, &S::f>(&test);
|
||||
ffdel.connect<&delegateFunction>();
|
||||
mfdel.connect<DelegateFunctor, &DelegateFunctor::operator()>(&functor);
|
||||
|
||||
ASSERT_EQ(ffdel(3), 9);
|
||||
ASSERT_EQ(mfdel(3), 6);
|
||||
@@ -35,7 +35,7 @@ TEST(Delegate, Functionalities) {
|
||||
TEST(Delegate, Comparison) {
|
||||
entt::Delegate<int(int)> delegate;
|
||||
entt::Delegate<int(int)> def;
|
||||
delegate.connect<&f>();
|
||||
delegate.connect<&delegateFunction>();
|
||||
|
||||
ASSERT_EQ(def, entt::Delegate<int(int)>{});
|
||||
ASSERT_NE(def, delegate);
|
||||
|
||||
@@ -2,46 +2,39 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/signal/dispatcher.hpp>
|
||||
|
||||
struct Event {};
|
||||
struct AnEvent {};
|
||||
struct AnotherEvent {};
|
||||
|
||||
struct Receiver {
|
||||
void receive(const Event &) { ++cnt; }
|
||||
void receive(const AnEvent &) { ++cnt; }
|
||||
void reset() { cnt = 0; }
|
||||
std::size_t cnt{0};
|
||||
int cnt{0};
|
||||
};
|
||||
|
||||
template<typename Dispatcher, typename Rec>
|
||||
void testDispatcher(Rec receiver) {
|
||||
Dispatcher dispatcher;
|
||||
TEST(Dispatcher, Functionalities) {
|
||||
entt::Dispatcher dispatcher;
|
||||
Receiver receiver;
|
||||
|
||||
dispatcher.template connect<Event>(receiver);
|
||||
dispatcher.template trigger<Event>();
|
||||
dispatcher.template enqueue<Event>();
|
||||
dispatcher.template sink<AnEvent>().connect(&receiver);
|
||||
dispatcher.template trigger<AnEvent>();
|
||||
dispatcher.template enqueue<AnEvent>();
|
||||
dispatcher.template enqueue<AnotherEvent>();
|
||||
dispatcher.update<AnotherEvent>();
|
||||
|
||||
ASSERT_EQ(receiver->cnt, static_cast<decltype(receiver->cnt)>(1));
|
||||
ASSERT_EQ(receiver.cnt, 1);
|
||||
|
||||
dispatcher.update<AnEvent>();
|
||||
dispatcher.template trigger<AnEvent>();
|
||||
|
||||
ASSERT_EQ(receiver.cnt, 3);
|
||||
|
||||
receiver.reset();
|
||||
|
||||
dispatcher.template sink<AnEvent>().disconnect(&receiver);
|
||||
dispatcher.template trigger<AnEvent>();
|
||||
dispatcher.template enqueue<AnEvent>();
|
||||
dispatcher.update();
|
||||
dispatcher.update();
|
||||
dispatcher.template trigger<Event>();
|
||||
dispatcher.template trigger<AnEvent>();
|
||||
|
||||
ASSERT_EQ(receiver->cnt, static_cast<decltype(receiver->cnt)>(3));
|
||||
|
||||
receiver->reset();
|
||||
|
||||
dispatcher.template disconnect<Event>(receiver);
|
||||
dispatcher.template trigger<Event>();
|
||||
dispatcher.template enqueue<Event>();
|
||||
dispatcher.update();
|
||||
dispatcher.template trigger<Event>();
|
||||
|
||||
ASSERT_EQ(receiver->cnt, static_cast<decltype(receiver->cnt)>(0));
|
||||
}
|
||||
|
||||
TEST(ManagedDispatcher, Basics) {
|
||||
testDispatcher<entt::ManagedDispatcher>(std::make_shared<Receiver>());
|
||||
}
|
||||
|
||||
TEST(UnmanagedDispatcher, Basics) {
|
||||
auto ptr = std::make_unique<Receiver>();
|
||||
testDispatcher<entt::UnmanagedDispatcher>(ptr.get());
|
||||
ASSERT_EQ(receiver.cnt, 0);
|
||||
}
|
||||
|
||||
@@ -3,148 +3,18 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/signal/sigh.hpp>
|
||||
|
||||
TEST(SigH, Lifetime) {
|
||||
using signal = entt::SigH<void(void)>;
|
||||
|
||||
ASSERT_NO_THROW(signal{});
|
||||
|
||||
signal src{}, other{};
|
||||
|
||||
ASSERT_NO_THROW(signal{src});
|
||||
ASSERT_NO_THROW(signal{std::move(other)});
|
||||
ASSERT_NO_THROW(src = other);
|
||||
ASSERT_NO_THROW(src = std::move(other));
|
||||
|
||||
ASSERT_NO_THROW(delete new signal{});
|
||||
}
|
||||
|
||||
TEST(SigH, Comparison) {
|
||||
struct S {
|
||||
void f() {}
|
||||
void g() {}
|
||||
};
|
||||
|
||||
entt::SigH<void()> sig1;
|
||||
entt::SigH<void()> sig2;
|
||||
|
||||
S s1;
|
||||
S s2;
|
||||
|
||||
sig1.connect<S, &S::f>(&s1);
|
||||
sig2.connect<S, &S::f>(&s2);
|
||||
|
||||
ASSERT_FALSE(sig1 == sig2);
|
||||
ASSERT_TRUE(sig1 != sig2);
|
||||
|
||||
sig1.disconnect<S, &S::f>(&s1);
|
||||
sig2.disconnect<S, &S::f>(&s2);
|
||||
|
||||
sig1.connect<S, &S::f>(&s1);
|
||||
sig2.connect<S, &S::g>(&s1);
|
||||
|
||||
ASSERT_FALSE(sig1 == sig2);
|
||||
ASSERT_TRUE(sig1 != sig2);
|
||||
|
||||
sig1.disconnect<S, &S::f>(&s1);
|
||||
sig2.disconnect<S, &S::g>(&s1);
|
||||
|
||||
ASSERT_TRUE(sig1 == sig2);
|
||||
ASSERT_FALSE(sig1 != sig2);
|
||||
|
||||
sig1.connect<S, &S::f>(&s1);
|
||||
sig1.connect<S, &S::g>(&s1);
|
||||
sig2.connect<S, &S::f>(&s1);
|
||||
sig2.connect<S, &S::g>(&s1);
|
||||
|
||||
ASSERT_TRUE(sig1 == sig2);
|
||||
|
||||
sig1.disconnect<S, &S::f>(&s1);
|
||||
sig1.disconnect<S, &S::g>(&s1);
|
||||
sig2.disconnect<S, &S::f>(&s1);
|
||||
sig2.disconnect<S, &S::g>(&s1);
|
||||
|
||||
sig1.connect<S, &S::f>(&s1);
|
||||
sig1.connect<S, &S::g>(&s1);
|
||||
sig2.connect<S, &S::g>(&s1);
|
||||
sig2.connect<S, &S::f>(&s1);
|
||||
|
||||
ASSERT_FALSE(sig1 == sig2);
|
||||
}
|
||||
|
||||
struct S {
|
||||
struct SigHListener {
|
||||
static void f(int &v) { v = 42; }
|
||||
|
||||
bool g(int) { k = !k; return true; }
|
||||
bool h(int) { return k; }
|
||||
|
||||
void i() {}
|
||||
void l() {}
|
||||
|
||||
bool k{false};
|
||||
};
|
||||
|
||||
TEST(SigH, Clear) {
|
||||
entt::SigH<void(int &)> sigh;
|
||||
sigh.connect<&S::f>();
|
||||
|
||||
ASSERT_FALSE(sigh.empty());
|
||||
|
||||
sigh.clear();
|
||||
|
||||
ASSERT_TRUE(sigh.empty());
|
||||
}
|
||||
|
||||
TEST(SigH, Functions) {
|
||||
entt::SigH<void(int &)> sigh;
|
||||
int v = 0;
|
||||
|
||||
sigh.connect<&S::f>();
|
||||
sigh.publish(v);
|
||||
|
||||
ASSERT_FALSE(sigh.empty());
|
||||
ASSERT_EQ((entt::SigH<bool(int)>::size_type)1, sigh.size());
|
||||
ASSERT_EQ(42, v);
|
||||
|
||||
v = 0;
|
||||
sigh.disconnect<&S::f>();
|
||||
sigh.publish(v);
|
||||
|
||||
ASSERT_TRUE(sigh.empty());
|
||||
ASSERT_EQ((entt::SigH<bool(int)>::size_type)0, sigh.size());
|
||||
ASSERT_EQ(0, v);
|
||||
|
||||
sigh.connect<&S::f>();
|
||||
}
|
||||
|
||||
TEST(SigH, Members) {
|
||||
struct S {
|
||||
bool f(int) { b = !b; return true; }
|
||||
bool g(int) { return b; }
|
||||
bool b{false};
|
||||
};
|
||||
|
||||
S s;
|
||||
S *ptr = &s;
|
||||
entt::SigH<bool(int)> sigh;
|
||||
|
||||
sigh.connect<S, &S::f>(ptr);
|
||||
sigh.publish(42);
|
||||
|
||||
ASSERT_TRUE(s.b);
|
||||
ASSERT_FALSE(sigh.empty());
|
||||
ASSERT_EQ((entt::SigH<bool(int)>::size_type)1, sigh.size());
|
||||
|
||||
sigh.disconnect<S, &S::f>(ptr);
|
||||
sigh.publish(42);
|
||||
|
||||
ASSERT_TRUE(s.b);
|
||||
ASSERT_TRUE(sigh.empty());
|
||||
ASSERT_EQ((entt::SigH<bool(int)>::size_type)0, sigh.size());
|
||||
|
||||
sigh.connect<S, &S::f>(ptr);
|
||||
sigh.connect<S, &S::g>(ptr);
|
||||
|
||||
ASSERT_FALSE(sigh.empty());
|
||||
ASSERT_EQ((entt::SigH<bool(int)>::size_type)2, sigh.size());
|
||||
|
||||
sigh.disconnect(ptr);
|
||||
|
||||
ASSERT_TRUE(sigh.empty());
|
||||
ASSERT_EQ((entt::SigH<bool(int)>::size_type)0, sigh.size());
|
||||
}
|
||||
|
||||
template<typename Ret>
|
||||
struct TestCollectAll {
|
||||
std::vector<Ret> vec{};
|
||||
@@ -175,10 +45,152 @@ struct TestCollectFirst {
|
||||
}
|
||||
};
|
||||
|
||||
TEST(SigH, Lifetime) {
|
||||
using signal = entt::SigH<void(void)>;
|
||||
|
||||
ASSERT_NO_THROW(signal{});
|
||||
|
||||
signal src{}, other{};
|
||||
|
||||
ASSERT_NO_THROW(signal{src});
|
||||
ASSERT_NO_THROW(signal{std::move(other)});
|
||||
ASSERT_NO_THROW(src = other);
|
||||
ASSERT_NO_THROW(src = std::move(other));
|
||||
|
||||
ASSERT_NO_THROW(delete new signal{});
|
||||
}
|
||||
|
||||
TEST(SigH, Comparison) {
|
||||
entt::SigH<void()> sig1;
|
||||
entt::SigH<void()> sig2;
|
||||
|
||||
SigHListener s1;
|
||||
SigHListener s2;
|
||||
|
||||
sig1.sink().connect<SigHListener, &SigHListener::i>(&s1);
|
||||
sig2.sink().connect<SigHListener, &SigHListener::i>(&s2);
|
||||
|
||||
ASSERT_FALSE(sig1 == sig2);
|
||||
ASSERT_TRUE(sig1 != sig2);
|
||||
|
||||
sig1.sink().disconnect<SigHListener, &SigHListener::i>(&s1);
|
||||
sig2.sink().disconnect<SigHListener, &SigHListener::i>(&s2);
|
||||
|
||||
sig1.sink().connect<SigHListener, &SigHListener::i>(&s1);
|
||||
sig2.sink().connect<SigHListener, &SigHListener::l>(&s1);
|
||||
|
||||
ASSERT_FALSE(sig1 == sig2);
|
||||
ASSERT_TRUE(sig1 != sig2);
|
||||
|
||||
sig1.sink().disconnect<SigHListener, &SigHListener::i>(&s1);
|
||||
sig2.sink().disconnect<SigHListener, &SigHListener::l>(&s1);
|
||||
|
||||
ASSERT_TRUE(sig1 == sig2);
|
||||
ASSERT_FALSE(sig1 != sig2);
|
||||
|
||||
sig1.sink().connect<SigHListener, &SigHListener::i>(&s1);
|
||||
sig1.sink().connect<SigHListener, &SigHListener::l>(&s1);
|
||||
sig2.sink().connect<SigHListener, &SigHListener::i>(&s1);
|
||||
sig2.sink().connect<SigHListener, &SigHListener::l>(&s1);
|
||||
|
||||
ASSERT_TRUE(sig1 == sig2);
|
||||
|
||||
sig1.sink().disconnect<SigHListener, &SigHListener::i>(&s1);
|
||||
sig1.sink().disconnect<SigHListener, &SigHListener::l>(&s1);
|
||||
sig2.sink().disconnect<SigHListener, &SigHListener::i>(&s1);
|
||||
sig2.sink().disconnect<SigHListener, &SigHListener::l>(&s1);
|
||||
|
||||
sig1.sink().connect<SigHListener, &SigHListener::i>(&s1);
|
||||
sig1.sink().connect<SigHListener, &SigHListener::l>(&s1);
|
||||
sig2.sink().connect<SigHListener, &SigHListener::l>(&s1);
|
||||
sig2.sink().connect<SigHListener, &SigHListener::i>(&s1);
|
||||
|
||||
ASSERT_FALSE(sig1 == sig2);
|
||||
}
|
||||
|
||||
TEST(SigH, Clear) {
|
||||
entt::SigH<void(int &)> sigh;
|
||||
sigh.sink().connect<&SigHListener::f>();
|
||||
|
||||
ASSERT_FALSE(sigh.empty());
|
||||
|
||||
sigh.sink().disconnect();
|
||||
|
||||
ASSERT_TRUE(sigh.empty());
|
||||
}
|
||||
|
||||
TEST(SigH, Swap) {
|
||||
entt::SigH<void(int &)> sigh1;
|
||||
entt::SigH<void(int &)> sigh2;
|
||||
|
||||
sigh1.sink().connect<&SigHListener::f>();
|
||||
|
||||
ASSERT_FALSE(sigh1.empty());
|
||||
ASSERT_TRUE(sigh2.empty());
|
||||
|
||||
std::swap(sigh1, sigh2);
|
||||
|
||||
ASSERT_TRUE(sigh1.empty());
|
||||
ASSERT_FALSE(sigh2.empty());
|
||||
}
|
||||
|
||||
TEST(SigH, Functions) {
|
||||
entt::SigH<void(int &)> sigh;
|
||||
int v = 0;
|
||||
|
||||
sigh.sink().connect<&SigHListener::f>();
|
||||
sigh.publish(v);
|
||||
|
||||
ASSERT_FALSE(sigh.empty());
|
||||
ASSERT_EQ((entt::SigH<bool(int)>::size_type)1, sigh.size());
|
||||
ASSERT_EQ(42, v);
|
||||
|
||||
v = 0;
|
||||
sigh.sink().disconnect<&SigHListener::f>();
|
||||
sigh.publish(v);
|
||||
|
||||
ASSERT_TRUE(sigh.empty());
|
||||
ASSERT_EQ((entt::SigH<bool(int)>::size_type)0, sigh.size());
|
||||
ASSERT_EQ(0, v);
|
||||
|
||||
sigh.sink().connect<&SigHListener::f>();
|
||||
}
|
||||
|
||||
TEST(SigH, Members) {
|
||||
SigHListener s;
|
||||
SigHListener *ptr = &s;
|
||||
entt::SigH<bool(int)> sigh;
|
||||
|
||||
sigh.sink().connect<SigHListener, &SigHListener::g>(ptr);
|
||||
sigh.publish(42);
|
||||
|
||||
ASSERT_TRUE(s.k);
|
||||
ASSERT_FALSE(sigh.empty());
|
||||
ASSERT_EQ((entt::SigH<bool(int)>::size_type)1, sigh.size());
|
||||
|
||||
sigh.sink().disconnect<SigHListener, &SigHListener::g>(ptr);
|
||||
sigh.publish(42);
|
||||
|
||||
ASSERT_TRUE(s.k);
|
||||
ASSERT_TRUE(sigh.empty());
|
||||
ASSERT_EQ((entt::SigH<bool(int)>::size_type)0, sigh.size());
|
||||
|
||||
sigh.sink().connect<SigHListener, &SigHListener::g>(ptr);
|
||||
sigh.sink().connect<SigHListener, &SigHListener::h>(ptr);
|
||||
|
||||
ASSERT_FALSE(sigh.empty());
|
||||
ASSERT_EQ((entt::SigH<bool(int)>::size_type)2, sigh.size());
|
||||
|
||||
sigh.sink().disconnect(ptr);
|
||||
|
||||
ASSERT_TRUE(sigh.empty());
|
||||
ASSERT_EQ((entt::SigH<bool(int)>::size_type)0, sigh.size());
|
||||
}
|
||||
|
||||
TEST(SigH, Collector) {
|
||||
entt::SigH<void(), TestCollectAll<void>> sigh_void;
|
||||
|
||||
sigh_void.connect<&TestCollectAll<void>::h>();
|
||||
sigh_void.sink().connect<&TestCollectAll<void>::h>();
|
||||
auto collector_void = sigh_void.collect();
|
||||
|
||||
ASSERT_FALSE(sigh_void.empty());
|
||||
@@ -186,9 +198,9 @@ TEST(SigH, Collector) {
|
||||
|
||||
entt::SigH<int(), TestCollectAll<int>> sigh_all;
|
||||
|
||||
sigh_all.connect<&TestCollectAll<int>::f>();
|
||||
sigh_all.connect<&TestCollectAll<int>::f>();
|
||||
sigh_all.connect<&TestCollectAll<int>::g>();
|
||||
sigh_all.sink().connect<&TestCollectAll<int>::f>();
|
||||
sigh_all.sink().connect<&TestCollectAll<int>::f>();
|
||||
sigh_all.sink().connect<&TestCollectAll<int>::g>();
|
||||
auto collector_all = sigh_all.collect();
|
||||
|
||||
ASSERT_FALSE(sigh_all.empty());
|
||||
@@ -199,8 +211,8 @@ TEST(SigH, Collector) {
|
||||
|
||||
entt::SigH<int(), TestCollectFirst<int>> sigh_first;
|
||||
|
||||
sigh_first.connect<&TestCollectFirst<int>::f>();
|
||||
sigh_first.connect<&TestCollectFirst<int>::f>();
|
||||
sigh_first.sink().connect<&TestCollectFirst<int>::f>();
|
||||
sigh_first.sink().connect<&TestCollectFirst<int>::f>();
|
||||
auto collector_first = sigh_first.collect();
|
||||
|
||||
ASSERT_FALSE(sigh_first.empty());
|
||||
|
||||
@@ -1,164 +0,0 @@
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/signal/signal.hpp>
|
||||
|
||||
struct S {
|
||||
static void f(const int &j) { i = j; }
|
||||
void g(const int &j) { i = j; }
|
||||
void h(const int &) {}
|
||||
static int i;
|
||||
};
|
||||
|
||||
int S::i = 0;
|
||||
|
||||
TEST(Signal, Lifetime) {
|
||||
using signal = entt::Signal<void(void)>;
|
||||
|
||||
ASSERT_NO_THROW(signal{});
|
||||
|
||||
signal src{}, other{};
|
||||
|
||||
ASSERT_NO_THROW(signal{src});
|
||||
ASSERT_NO_THROW(signal{std::move(other)});
|
||||
ASSERT_NO_THROW(src = other);
|
||||
ASSERT_NO_THROW(src = std::move(other));
|
||||
|
||||
ASSERT_NO_THROW(delete new signal{});
|
||||
}
|
||||
|
||||
TEST(Signal, Comparison) {
|
||||
struct S {
|
||||
void f() {}
|
||||
void g() {}
|
||||
};
|
||||
|
||||
entt::Signal<void()> sig1;
|
||||
entt::Signal<void()> sig2;
|
||||
|
||||
auto s1 = std::make_shared<S>();
|
||||
auto s2 = std::make_shared<S>();
|
||||
|
||||
sig1.connect<S, &S::f>(s1);
|
||||
sig2.connect<S, &S::f>(s2);
|
||||
|
||||
ASSERT_FALSE(sig1 == sig2);
|
||||
ASSERT_TRUE(sig1 != sig2);
|
||||
|
||||
sig1.disconnect<S, &S::f>(s1);
|
||||
sig2.disconnect<S, &S::f>(s2);
|
||||
|
||||
sig1.connect<S, &S::f>(s1);
|
||||
sig2.connect<S, &S::g>(s1);
|
||||
|
||||
ASSERT_FALSE(sig1 == sig2);
|
||||
ASSERT_TRUE(sig1 != sig2);
|
||||
|
||||
sig1.disconnect<S, &S::f>(s1);
|
||||
sig2.disconnect<S, &S::g>(s1);
|
||||
|
||||
ASSERT_TRUE(sig1 == sig2);
|
||||
ASSERT_FALSE(sig1 != sig2);
|
||||
|
||||
sig1.connect<S, &S::f>(s1);
|
||||
sig1.connect<S, &S::g>(s1);
|
||||
sig2.connect<S, &S::f>(s1);
|
||||
sig2.connect<S, &S::g>(s1);
|
||||
|
||||
ASSERT_TRUE(sig1 == sig2);
|
||||
|
||||
sig1.disconnect<S, &S::f>(s1);
|
||||
sig1.disconnect<S, &S::g>(s1);
|
||||
sig2.disconnect<S, &S::f>(s1);
|
||||
sig2.disconnect<S, &S::g>(s1);
|
||||
|
||||
sig1.connect<S, &S::f>(s1);
|
||||
sig1.connect<S, &S::g>(s1);
|
||||
sig2.connect<S, &S::g>(s1);
|
||||
sig2.connect<S, &S::f>(s1);
|
||||
|
||||
ASSERT_FALSE(sig1 == sig2);
|
||||
}
|
||||
|
||||
TEST(Signal, Clear) {
|
||||
entt::Signal<void(const int &)> signal;
|
||||
signal.connect<&S::f>();
|
||||
|
||||
ASSERT_FALSE(signal.empty());
|
||||
|
||||
signal.clear();
|
||||
|
||||
ASSERT_TRUE(signal.empty());
|
||||
}
|
||||
|
||||
TEST(Signal, Functions) {
|
||||
entt::Signal<void(const int &)> signal;
|
||||
auto val = S::i + 1;
|
||||
|
||||
signal.connect<&S::f>();
|
||||
signal.publish(val);
|
||||
|
||||
ASSERT_FALSE(signal.empty());
|
||||
ASSERT_EQ(entt::Signal<void(const int &)>::size_type{1}, signal.size());
|
||||
ASSERT_EQ(S::i, val);
|
||||
|
||||
signal.disconnect<&S::f>();
|
||||
signal.publish(val+1);
|
||||
|
||||
ASSERT_TRUE(signal.empty());
|
||||
ASSERT_EQ(entt::Signal<void(const int &)>::size_type{0}, signal.size());
|
||||
ASSERT_EQ(S::i, val);
|
||||
}
|
||||
|
||||
TEST(Signal, Members) {
|
||||
entt::Signal<void(const int &)> signal;
|
||||
auto ptr = std::make_shared<S>();
|
||||
auto val = S::i + 1;
|
||||
|
||||
signal.connect<S, &S::g>(ptr);
|
||||
signal.publish(val);
|
||||
|
||||
ASSERT_FALSE(signal.empty());
|
||||
ASSERT_EQ(entt::Signal<void(const int &)>::size_type{1}, signal.size());
|
||||
ASSERT_EQ(S::i, val);
|
||||
|
||||
signal.disconnect<S, &S::g>(ptr);
|
||||
signal.publish(val+1);
|
||||
|
||||
ASSERT_TRUE(signal.empty());
|
||||
ASSERT_EQ(entt::Signal<void(const int &)>::size_type{0}, signal.size());
|
||||
ASSERT_EQ(S::i, val);
|
||||
|
||||
++val;
|
||||
|
||||
signal.connect<S, &S::g>(ptr);
|
||||
signal.connect<S, &S::h>(ptr);
|
||||
signal.publish(val);
|
||||
|
||||
ASSERT_FALSE(signal.empty());
|
||||
ASSERT_EQ(entt::Signal<void(const int &)>::size_type{2}, signal.size());
|
||||
ASSERT_EQ(S::i, val);
|
||||
|
||||
signal.disconnect(ptr);
|
||||
signal.publish(val+1);
|
||||
|
||||
ASSERT_TRUE(signal.empty());
|
||||
ASSERT_EQ(entt::Signal<void(const int &)>::size_type{0}, signal.size());
|
||||
ASSERT_EQ(S::i, val);
|
||||
}
|
||||
|
||||
TEST(Signal, Cleanup) {
|
||||
entt::Signal<void(const int &)> signal;
|
||||
auto ptr = std::make_shared<S>();
|
||||
signal.connect<S, &S::g>(ptr);
|
||||
auto val = S::i;
|
||||
ptr = nullptr;
|
||||
|
||||
ASSERT_FALSE(signal.empty());
|
||||
ASSERT_EQ(S::i, val);
|
||||
|
||||
signal.publish(val);
|
||||
|
||||
ASSERT_TRUE(signal.empty());
|
||||
ASSERT_EQ(S::i, val);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
95118
test/mod/duktape.c
95118
test/mod/duktape.c
File diff suppressed because it is too large
Load Diff
1349
test/mod/duktape.h
1349
test/mod/duktape.h
File diff suppressed because it is too large
Load Diff
@@ -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 ®istry) {
|
||||
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 ®istry) {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -135,7 +135,6 @@ duk_ret_t get<DuktapeRuntime>(duk_context *ctx, entt::DefaultRegistry ®istry)
|
||||
duk_push_string(ctx, runtime.components[type].c_str());
|
||||
duk_json_decode(ctx, -1);
|
||||
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -157,7 +156,7 @@ class DuktapeRegistry {
|
||||
template<typename... Comp>
|
||||
void reg() {
|
||||
using accumulator_type = int[];
|
||||
accumulator_type acc = { (func[registry.component<Comp>()] = {
|
||||
accumulator_type acc = { (func[registry.type<Comp>()] = {
|
||||
&::set<Comp>,
|
||||
&::unset<Comp>,
|
||||
&::has<Comp>,
|
||||
@@ -186,7 +185,7 @@ class DuktapeRegistry {
|
||||
auto type = duk_require_uint(ctx, 1);
|
||||
|
||||
if(type >= udef) {
|
||||
type = registry.component<DuktapeRuntime>();
|
||||
type = registry.type<DuktapeRuntime>();
|
||||
}
|
||||
|
||||
assert(func.find(type) != func.cend());
|
||||
@@ -248,7 +247,7 @@ public:
|
||||
assert(func.find(type) != func.cend());
|
||||
match = (registry.*func[type].test)(entity);
|
||||
} else {
|
||||
const auto ctype = registry.component<DuktapeRuntime>();
|
||||
const auto ctype = registry.type<DuktapeRuntime>();
|
||||
assert(func.find(ctype) != func.cend());
|
||||
match = (registry.*func[ctype].test)(entity);
|
||||
|
||||
@@ -287,7 +286,7 @@ const duk_function_list_entry js_DuktapeRegistry_methods[] = {
|
||||
void exportTypes(duk_context *ctx, entt::DefaultRegistry ®istry) {
|
||||
auto exportType = [](auto *ctx, auto ®istry, auto idx, auto type, const auto *name) {
|
||||
duk_push_string(ctx, name);
|
||||
duk_push_uint(ctx, registry.template component<typename decltype(type)::type>());
|
||||
duk_push_uint(ctx, registry.template type<typename decltype(type)::type>());
|
||||
duk_def_prop(ctx, idx, DUK_DEFPROP_HAVE_VALUE | DUK_DEFPROP_CLEAR_WRITABLE);
|
||||
};
|
||||
|
||||
@@ -331,8 +330,12 @@ TEST(Mod, Duktape) {
|
||||
FAIL();
|
||||
}
|
||||
|
||||
registry.create(Position{ 0., 0. }, Renderable{});
|
||||
registry.create(Position{ 0., 0. });
|
||||
const auto e0 = registry.create();
|
||||
registry.assign<Position>(e0, 0., 0.);
|
||||
registry.assign<Renderable>(e0);
|
||||
|
||||
const auto e1 = registry.create();
|
||||
registry.assign<Position>(e1, 0., 0.);
|
||||
|
||||
const char *s1 = ""
|
||||
"Registry.entities(Types.POSITION, Types.RENDERABLE).forEach(function(entity) {"
|
||||
|
||||
181
test/snapshot/snapshot.cpp
Normal file
181
test/snapshot/snapshot.cpp
Normal file
@@ -0,0 +1,181 @@
|
||||
#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<typename Archive>
|
||||
void serialize(Archive &archive, Position &position) {
|
||||
archive(position.x, position.y);
|
||||
}
|
||||
|
||||
template<typename Archive>
|
||||
void serialize(Archive &archive, Timer &timer) {
|
||||
archive(timer.duration);
|
||||
}
|
||||
|
||||
template<typename 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, 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);
|
||||
}
|
||||
Reference in New Issue
Block a user