Compare commits

...

48 Commits

Author SHA1 Message Date
Michele Caini
ddc0a32bbc fix: runtime view (#108) 2018-07-02 08:40:30 +02:00
Michele Caini
10a7c54364 runtime view 2018-06-28 14:31:04 +02:00
Michele Caini
a66fa9d844 review: capacity 2018-06-28 08:36:24 +02:00
Michele Caini
a16f2ac15c fixed build system 2018-06-27 22:53:21 +02:00
Michele Caini
b699797a40 naming convention 2018-06-25 14:02:31 +02:00
Michele Caini
a6e9520d06 updated TODO 2018-06-25 13:16:11 +02:00
Michele Caini
f8310b1296 added monostate (built-in config system) 2018-06-23 19:43:22 +02:00
Michele Caini
353bf99cd5 now working with vs2017 (again) 2018-06-22 14:46:43 +02:00
Michele Caini
a478e4acc9 fixed compilation errors and suppressed warnings with clang 2018-06-22 14:16:02 +02:00
Michele Caini
d810e0ba7d review ident 2018-06-22 09:59:03 +02:00
Michele Caini
1e51ffdb72 user defined string literal for hashed strings 2018-06-20 17:08:14 +02:00
Michele Caini
7da1d1fc64 minor changes 2018-06-19 15:36:21 +02:00
Michele Caini
957697c383 review: delegate (see #101 and #102) 2018-06-18 17:10:31 +02:00
Michele Caini
107eb72225 updated TODO 2018-06-18 13:25:34 +02:00
Michele Caini
810b77f9da use null entity everywhere 2018-06-18 08:34:27 +02:00
Michele Caini
5e3bc2049b clean up 2018-06-14 14:48:19 +02:00
Michele Caini
229500347d review: process scheduler 2018-06-14 14:46:39 +02:00
Michele Caini
1f461db0a1 minor changes 2018-06-13 23:33:47 +02:00
Malte
6c55aafee3 Export CMake Config file and "modernize" CMake scripts (#87)
* CMake modifications and Config file generation

* CMake minor style changes

* Get rid of commented lines, fix indentation

* Respect Windows conventions for CMake config directory, Fix error with Config Version file, Export CMake package

* Add CMake option for use of ENTT_COMPILE_OPTIONS, Add CMake option for using libc++, Remove  from EnTT target and move it to test targets

* Fix indentation

* Fix indentation (again)

* Fix Windows problems with compile option -Wall in not using it on Windows

* Improved generator expression
The problem with -Wall is not due to the platform but due to the compiler MSVC

* Set compatibility for ConfigVersion file to AnyNewerVersion, Add PATH_VARS CMAKE_INSTALL_INCLUDE_DIR of configure_package_config_file, Remove redundant options, correct target_include_directory for INSTALL_INTERFACE, set the Version in EnTTConfig file and check CMake version

* Add missing closing brace, Add a special config file for the build tree
2018-06-13 14:04:05 +02:00
Michele Caini
d57e55b719 improved sort 2018-06-12 22:32:11 +02:00
Michele Caini
bf772e5fe5 docs 2018-06-12 13:00:33 +02:00
Michele Caini
413f3356ce updated README 2018-06-12 08:46:05 +02:00
Michele Caini
f147326fe0 typo 2018-06-11 14:50:43 +02:00
Michele Caini
30c59644b6 minor changes 2018-06-11 13:57:37 +02:00
Michele Caini
3fa5acf2e6 updated TOC 2018-06-11 08:26:53 +02:00
Michele Caini
3e6ded8823 more on destroy 2018-06-11 08:24:48 +02:00
Michele Caini
612017aaa2 null entity 2018-06-11 08:23:07 +02:00
Michele Caini
ef57d7e7b6 removed wrong noexcept 2018-06-10 00:10:12 +02:00
Michele Caini
a8d0db5036 updated README 2018-06-09 23:51:57 +02:00
Michele Caini
ad6b5f8fc1 fixed tests 2018-06-09 23:51:48 +02:00
Michele Caini
e3cb6a0aec minor changes 2018-06-09 21:58:49 +02:00
Sztergbaum Roman
cbf18a7dc4 fix warning on windows C4267 (#97) 2018-06-09 21:49:33 +02:00
Michele Caini
01559410a9 updated TODO 2018-06-09 12:26:59 +02:00
Michele Caini
f2ab94fa7f added operator[] to sparse set and views 2018-06-09 00:36:22 +02:00
Michele Caini
d7394a8369 more on prototype 2018-06-08 23:03:05 +02:00
Michele Caini
9feef11d6f cleanup + tests 2018-06-08 22:58:12 +02:00
Michele Caini
dc4e5ddc3c review: iterators (sparse set/view) 2018-06-08 22:30:50 +02:00
Michele Caini
8600781bb6 fixed doc + minor changes 2018-06-08 19:04:19 +02:00
Michele Caini
bdc7bbdc9d DefaultPrototype depends on DefaultRegistry now 2018-06-06 14:39:56 +02:00
Michele Caini
73badef594 updated gtest (#92) 2018-06-06 13:42:09 +02:00
Michele Caini
9474e6c08c updated prototype (#91) 2018-06-05 08:38:27 +02:00
Michele Caini
653ded0e6f updated version 2018-06-04 08:52:10 +02:00
Michele Caini
e34bec7dee cloning an entity is no longer allowed 2018-06-04 08:49:13 +02:00
Michele Caini
610b560fb5 typo 2018-06-03 22:24:45 +02:00
Michele Caini
0a03ddb8a7 typo 2018-06-03 22:17:06 +02:00
Michele Caini
f31790631a fixed include 2018-06-03 22:10:33 +02:00
Michele Caini
e07128760e review: prototype (#89) 2018-06-03 19:06:12 +02:00
Michele Caini
dd02ae313d minor changes 2018-06-02 17:06:44 +02:00
43 changed files with 3253 additions and 1183 deletions

View File

@@ -7,3 +7,4 @@ Michele Caini aka skypjack
Paolo Monteverde aka morbo84
David Nerjes aka DavidHamburg
Indi Kernick aka Kerndog73
Malte Müller-Rowold aka m-waka

View File

@@ -16,7 +16,9 @@ endif()
# Project configuration
#
project(entt VERSION 2.6.0)
project(entt VERSION 2.7.1)
include(GNUInstallDirs)
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Debug)
@@ -32,19 +34,20 @@ message("* ${PROJECT_NAME} v${PROJECT_VERSION} (${CMAKE_BUILD_TYPE})")
message("* Copyright (c) 2018 ${PROJECT_AUTHOR} <${PROJECT_AUTHOR_EMAIL}>")
message("*")
option(ENTT_COMPILE_OPTIONS "Use compile options from EnTT." ON)
option(USE_LIBCPP "Use libc++ by adding -stdlib=libc++ flag if availbale." ON)
#
# Compiler stuff
#
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
if(NOT MSVC)
if(NOT MSVC AND USE_LIBCPP)
include(CheckCXXSourceCompiles)
include(CMakePushCheckState)
set(OLD_CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS})
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")
cmake_push_check_state()
set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -stdlib=libc++")
check_cxx_source_compiles("
#include<type_traits>
@@ -52,28 +55,102 @@ if(NOT MSVC)
" HAS_LIBCPP)
if(NOT HAS_LIBCPP)
set(CMAKE_CXX_FLAGS "${OLD_CMAKE_CXX_FLAGS}")
message(WARNING "The option USE_LIBCPP is set (by default) but libc++ is not available. The flag will not be added to the target.")
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 ...
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2")
else()
# ... on the other side, GCC is incredibly comfortable with it.
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3")
endif()
cmake_pop_check_state()
endif()
#
# Include EnTT
# Add EnTT target
#
include_directories(${entt_SOURCE_DIR}/src)
add_library(EnTT INTERFACE)
target_include_directories(
EnTT INTERFACE
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/src>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
)
target_compile_definitions(
EnTT
INTERFACE $<$<AND:$<CONFIG:Debug>,$<NOT:$<CXX_COMPILER_ID:MSVC>>>:DEBUG>
INTERFACE $<$<AND:$<CONFIG:Release>,$<NOT:$<CXX_COMPILER_ID:MSVC>>>:RELEASE>
)
if(ENTT_COMPILE_OPTIONS)
target_compile_options(
EnTT
INTERFACE $<$<AND:$<CONFIG:Debug>,$<NOT:$<CXX_COMPILER_ID:MSVC>>>:-O0 -g>
# it seems that -O3 ruins the performance when using clang ...
INTERFACE $<$<AND:$<CONFIG:Release>,$<CXX_COMPILER_ID:Clang>>:-O2>
# ... on the other side, GCC is incredibly comfortable with it.
INTERFACE $<$<AND:$<CONFIG:Release>,$<CXX_COMPILER_ID:GNU>>:-O3>
)
endif()
if(USE_LIBCPP AND HAS_LIBCPP)
target_compile_options(EnTT BEFORE INTERFACE -stdlib=libc++)
endif()
target_compile_features(EnTT INTERFACE cxx_std_14)
#
# Install EnTT
#
if(${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
set(ENTT_INSTALL_CONFIGDIR cmake)
else()
set(ENTT_INSTALL_CONFIGDIR ${CMAKE_INSTALL_LIBDIR}/cmake/entt)
endif()
install(DIRECTORY src/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
install(TARGETS EnTT EXPORT EnTTTargets)
export(EXPORT EnTTTargets FILE ${entt_BINARY_DIR}/EnTTTargets.cmake)
install(
EXPORT EnTTTargets
FILE EnTTTargets.cmake
DESTINATION ${ENTT_INSTALL_CONFIGDIR}
)
#
# Build tree package config file
#
configure_file(cmake/in/EnTTBuildConfig.cmake.in EnTTConfig.cmake @ONLY)
include(CMakePackageConfigHelpers)
#
# Install tree package config file
#
configure_package_config_file(
cmake/in/EnTTConfig.cmake.in
${ENTT_INSTALL_CONFIGDIR}/EnTTConfig.cmake
INSTALL_DESTINATION ${ENTT_INSTALL_CONFIGDIR}
PATH_VARS CMAKE_INSTALL_INCLUDEDIR
NO_CHECK_REQUIRED_COMPONENTS_MACRO
)
write_basic_package_version_file(
${entt_BINARY_DIR}/EnTTConfigVersion.cmake
VERSION ${PROJECT_VERSION}
COMPATIBILITY AnyNewerVersion
)
install(
FILES
${entt_BINARY_DIR}/${ENTT_INSTALL_CONFIGDIR}/EnTTConfig.cmake
${entt_BINARY_DIR}/EnTTConfigVersion.cmake
DESTINATION ${ENTT_INSTALL_CONFIGDIR}
)
export(PACKAGE EnTT)
#
# Tests
@@ -105,10 +182,14 @@ endif()
# Documentation
#
find_package(Doxygen 1.8)
option(BUILD_DOCS "Enable building with documentation." OFF)
if(DOXYGEN_FOUND)
add_subdirectory(docs)
if(BUILD_DOCS)
find_package(Doxygen 1.8)
if(DOXYGEN_FOUND)
add_subdirectory(docs)
endif()
endif()
#

388
README.md
View File

@@ -29,18 +29,22 @@
* [Prototype](#prototype)
* [Helpers](#helpers)
* [Dependency function](#dependency-function)
* [Null entity](#null-entity)
* [View: to persist or not to persist?](#view-to-persist-or-not-to-persist)
* [Standard View](#standard-view)
* [Single component standard view](#single-component-standard-view)
* [Multi component standard view](#multi-component-standard-view)
* [Persistent View](#persistent-view)
* [Raw View](#raw-view)
* [Runtime View](#runtime-view)
* [Give me everything](#give-me-everything)
* [Side notes](#side-notes)
* [Iterations: what is allowed and what is not](#iterations-what-is-allowed-and-what-is-not)
* [Multithreading](#multithreading)
* [Crash Course: core functionalities](#crash-course-core-functionalities)
* [Compile-time identifiers](#compile-time-identifiers)
* [Runtime identifiers](#runtime-identifiers)
* [Hashed strings](#hashed-strings)
* [Monostate](#monostate)
* [Crash Course: service locator](#crash-course-service-locator)
* [Crash Course: cooperative scheduler](#crash-course-cooperative-scheduler)
* [The process](#the-process)
@@ -208,18 +212,14 @@ Dell XPS 13 out of the mid 2014):
|-----------|-------------|-------------|
| Create 1M entities | 0.0147s | **0.0046s** |
| Destroy 1M entities | 0.0053s | **0.0045s** |
| Standard view, 1M entities, one component | 0.0012s | **1.9e-07s** |
| Standard view, 1M entities, two components | 0.0012s | **3.8e-07s** |
| Standard view, 1M entities, two components<br/>Half of the entities have all the components | 0.0009s | **3.8e-07s** |
| Standard view, 1M entities, two components<br/>One of the entities has all the components | 0.0008s | **1.0e-06s** |
| Persistent view, 1M entities, two components | 0.0012s | **2.8e-07s** |
| Standard view, 1M entities, five components | 0.0010s | **7.0e-07s** |
| Persistent view, 1M entities, five components | 0.0010s | **2.8e-07s** |
| Standard view, 1M entities, ten components | 0.0011s | **1.2e-06s** |
| Standard view, 1M entities, ten components<br/>Half of the entities have all the components | 0.0010s | **1.2e-06s** |
| Standard view, 1M entities, ten components<br/>One of the entities has all the components | 0.0008s | **1.2e-06s** |
| Persistent view, 1M entities, ten components | 0.0011s | **3.0e-07s** |
| Raw view, 1M entities | - | **2.2e-07s** |
| 1M entities, one component | 0.0012s | **1.9e-07s** |
| 1M entities, two components | 0.0012s | **3.8e-07s** |
| 1M entities, two components<br/>Half of the entities have all the components | 0.0009s | **3.8e-07s** |
| 1M entities, two components<br/>One of the entities has all the components | 0.0008s | **1.0e-06s** |
| 1M entities, five components | 0.0010s | **7.0e-07s** |
| 1M entities, ten components | 0.0011s | **1.2e-06s** |
| 1M entities, ten components<br/>Half of the entities have all the components | 0.0010s | **1.2e-06s** |
| 1M entities, ten components<br/>One of the entities has all the components | 0.0008s | **1.2e-06s** |
| Sort 150k entities, one component<br/>Arrays are in reverse order | - | **0.0036s** |
| Sort 150k entities, enforce permutation<br/>Arrays are in reverse order | - | **0.0005s** |
| Sort 150k entities, one component<br/>Arrays are almost sorted, std::sort | - | **0.0035s** |
@@ -290,8 +290,8 @@ The documentation is based on [doxygen](http://www.stack.nl/~dimitri/doxygen/).
To build it:
$ cd build
$ cmake ..
$ make docs
$ cmake .. -DBUILD_DOCS=ON
$ make
The API reference will be created in HTML format within the directory
`build/docs/html`. To navigate it with your favorite browser:
@@ -305,7 +305,8 @@ for the latest version.
## Tests
To compile and run the tests, `EnTT` requires *googletest*.<br/>
`cmake` will download and compile the library before to compile anything else.
`cmake` will download and compile the library before compiling anything else.
In order to build without tests set CMake option `BUILD_TESTING=OFF`.
To build the most basic set of tests:
@@ -396,25 +397,30 @@ Entities are represented by _entity identifiers_. An entity identifier is an
opaque type that users should not inspect or modify in any way. It carries
information about the entity itself and its version.
A registry can be used both to construct and destroy entities, as well as to
clone already existing entities:
A registry can be used both to construct and destroy entities:
```cpp
// constructs a naked entity with no components and returns its identifier
auto entity = registry.create();
// clones an entity and assigns all its components by copy to the newly created one
auto other = registry.clone(entity);
// destroys an entity and all its components
registry.destroy(entity);
```
Be aware that cloning an entity can lead to unexpected results under certain
conditions. Please refer to the inline documentation for more details.<br/>
Once an entity is deleted, the registry can freely reuse it internally with a
Entities can also be destroyed _by type_, that is by specifying the types of the
tags or components that identify them:
```cpp
// destroys the entity that owns the given tag, if any
registry.destroy<MyTag>(entt::tag_t{});
// destroys the entities that own the given components, if any
registry.destroy<AComponent, AnotherComponent>();
```
When an entity is destroyed, the registry can freely reuse it internally with a
slightly different identifier. In particular, the version of an entity is
increased each and every time it's destroyed.<br/>
increased each and every time it's discarded.<br/>
In case entity identifiers are stored around, the registry offers all the
functionalities required to test them and get out of the them all the
information they carry:
@@ -430,19 +436,6 @@ auto version = registry.version(entity);
auto curr = registry.current(entity);
```
Finally, there is also a sort of _null identifier_ made available to users. It's
treated as if it were a _null pointer_ that doesn't identify any entity. A
registry will reject this identifier in all cases because it isn't considered
valid.<br/>
The rules that define a _null identifier_ are a bit tricky to explain. However,
being `Entity` the type of the entities (for example, `std::uint32_t`), users
can easily construct a _null identifier_ by flipping all the bits of the _zero_:
```cpp
using Entity = std::uint32_t;
const auto null = ~Entity{};
```
Components can be assigned to or removed from entities at any time with a few
calls to member functions of the registry. As for the entities, the registry
offers also a set of functionalities users can use to work with the components.
@@ -719,9 +712,9 @@ created or destroyed.
### Runtime components
Defining components at runtime is useful to support plugins and mods in general.
However, it seems impossible with a tool designed around a bunch of templates.
Indeed it's not that difficult.<br/>
Defining components at runtime is useful to support plugin systems and mods in
general. However, it seems impossible with a tool designed around a bunch of
templates. Indeed it's not that difficult.<br/>
Of course, some features cannot be easily exported into a runtime
environment. As an example, sorting a group of components defined at runtime
isn't for free if compared to most of the other operations. However, the basic
@@ -1057,16 +1050,15 @@ how many prototypes they want, each one initialized differently from the others.
The following is an example of use of a prototype:
```cpp
entt::DefaultPrototype prototype;
entt::DefaultRegistry registry;
entt::DefaultPrototype prototype{registry};
prototype.set<Position>(100.f, 100.f);
prototype.set<Velocity>(0.f, 0.f);
// ...
entt::DefaultRegistry registry;
const auto entity = prototype(registry);
const auto entity = prototype();
```
To assign and remove components from a prototype, it offers two dedicated member
@@ -1079,21 +1071,21 @@ Creating an entity from a prototype is straightforward:
* To create a new entity from scratch and assign it a prototype, this is the way
to go:
```cpp
const auto entity = prototype(registry);
const auto entity = prototype();
```
It is equivalent to the following invokation:
```cpp
const auto entity = prototype.create(registry);
const auto entity = prototype.create();
```
* In case we want to initialize an already existing entity, we can provide the
`operator()` directly with the entity identifier:
```cpp
prototype(registry, entity);
prototype(entity);
```
It is equivalent to the following invokation:
```cpp
prototype.assign(registry, entity);
prototype.assign(entity);
```
Note that existing components aren't overwritten in this case. Only those
components that the entity doesn't own yet are copied over. All the other
@@ -1102,9 +1094,14 @@ Creating an entity from a prototype is straightforward:
* Finally, to assign or replace all the components for an entity, thus
overwriting existing ones:
```cpp
prototype.accommodate(registry, entity);
prototype.accommodate(entity);
```
In the examples above, the prototype uses its underlying registry to create
entities and components both for its purposes and when it's cloned. To use a
different repository to clone a prototype, all the member functions accept also
a reference to a valid registry as a first argument.
Prototypes are a very useful tool that can save a lot of typing sometimes.
Furthermore, the codebase may be easier to maintain, since updating a prototype
is much less error prone than jumping around in the codebase to update all the
@@ -1137,6 +1134,34 @@ A dependency can easily be broken by means of the same function template:
entt::dependency<AType, AnotherType>(entt::break_t{}, registry.construction<MyType>());
```
### Null entity
In `EnTT`, there exists a sort of _null entity_ made available to users that is
accessible via the `entt::null` variable.<br/>
The framework guarantees that the following expression always returns false:
```cpp
registry.valid(entt::null);
```
In other terms, a registry will reject the null entity in all cases because it
isn't considered valid. It means that the null entity cannot own components or
tags for obvious reasons.<br/>
The type of the null entity is internal and should not be used for any purpose
other than defining the null entity itself. However, there exist implicit
conversions from the null entity to identifiers of any allowed type:
```cpp
typename entt::DefaultRegistry::entity_type null = entt::null;
```
Similarly, the null entity can be compared to any other identifier:
```cpp
const auto entity = registry.create();
const bool null = (entity == entt::null);
```
## View: to persist or not to persist?
First of all, it is worth answering an obvious question: why views?<br/>
@@ -1147,9 +1172,9 @@ view can only iterate entities and their components, then read or update the
data members of the latter.<br/>
It is a subtle difference that can help designing a better software sometimes.
There are mainly three kinds of views: standard (also known as `View`),
persistent (also known as `PersistentView`) and raw (also known as
`RawView`).<br/>
There are mainly four kinds of views: standard (also known as `View`),
persistent (also known as `PersistentView`), raw (also known as `RawView`) and
runtime (also known as `RuntimeView`).<br/>
All of them have pros and cons to take in consideration. In particular:
* Standard views:
@@ -1204,6 +1229,21 @@ All of them have pros and cons to take in consideration. In particular:
* They can be used to iterate only one type of component at a time.
* They don't return the entity to which a component belongs to the caller.
* Runtime views:
Pros:
* Their lists of components are defined at runtime and not at compile-time.
* Creating and destroying them isn't expensive at all because they don't have
any type of initialization.
* They are the best tool for things like plugin systems and mods in general.
* They don't affect any other operations of the registry.
Cons:
* Their performances are definitely lower than those of all the other views,
although they are still usable and sufficient for most of the purposes.
To sum up and as a rule of thumb:
* Use a raw view to iterate components only (no entities) for a given type.
@@ -1222,6 +1262,8 @@ To sum up and as a rule of thumb:
entities but the intersection between the sets of entities is small.
* Prepare and use a persistent view in all the cases where a standard view
wouldn't fit well otherwise.
* Finally, in case you don't know at compile-time what are the components to
use, choose a runtime view and set them during execution.
To easily iterate entities and components, all the views offer the common
`begin` and `end` member functions that allow users to use a view in a typical
@@ -1260,7 +1302,7 @@ There is no need to store views around for they are extremely cheap to
construct, even though they can be copied without problems and reused freely. In
fact, they return newly created and correctly initialized iterators whenever
`begin` or `end` are invoked.<br/>
To iterate a single component standard view, either use it in range-for loop:
To iterate a single component standard view, either use it in a range-for loop:
```cpp
auto view = registry.view<Renderable>();
@@ -1281,8 +1323,8 @@ registry.view<Renderable>().each([](auto entity, auto &renderable) {
});
```
Performance are more or less the same. The best approach depends mainly on
whether all the components have to be accessed or not.
The `each` member function is highly optimized. Unless users want to iterate
only entities, using `each` should be the preferred approach.
**Note**: prefer the `get` member function of a view instead of the `get` member
function template of a registry during iterations, if possible. However, keep in
@@ -1305,7 +1347,7 @@ There is no need to store views around for they are extremely cheap to
construct, even though they can be copied without problems and reused freely. In
fact, they return newly created and correctly initialized iterators whenever
`begin` or `end` are invoked.<br/>
To iterate a multi component standard view, either use it in range-for loop:
To iterate a multi component standard view, either use it in a range-for loop:
```cpp
auto view = registry.view<Position, Velocity>();
@@ -1331,8 +1373,9 @@ registry.view<Position, Velocity>().each([](auto entity, auto &position, auto &v
});
```
Performance are more or less the same. The best approach depends mainly on
whether all the components have to be accessed or not.
The `each` member function is highly optimized. Unless users want to iterate
only entities or get only some of the components, using `each` should be the
preferred approach.
**Note**: prefer the `get` member function of a view instead of the `get` member
function template of a registry during iterations, if possible. However, keep in
@@ -1378,7 +1421,7 @@ of the components for which it has been constructed. It's also possible to ask a
view if it contains a given entity.<br/>
Refer to the inline documentation for all the details.
To iterate a persistent view, either use it in range-for loop:
To iterate a persistent view, either use it in a range-for loop:
```cpp
auto view = registry.view<Position, Velocity>(entt::persistent_t{});
@@ -1433,7 +1476,7 @@ There is no need to store views around for they are extremely cheap to
construct, even though they can be copied without problems and reused freely. In
fact, they return newly created and correctly initialized iterators whenever
`begin` or `end` are invoked.<br/>
To iterate a raw view, use it in range-for loop:
To iterate a raw view, use it in a range-for loop:
```cpp
auto view = registry.view<Renderable>(entt::raw_t{});
@@ -1443,9 +1486,74 @@ for(auto &&component: raw) {
}
```
**Note**: raw views don't have the `each` nor the `get` member function for
obvious reasons. The former would only return the components and therefore it
would be redundant, the latter isn't required at all.
Or rely on the `each` member function:
```cpp
registry.view<Renderable>(entt::raw_t{}).each([](auto &renderable) {
// ...
});
```
Performance are exactly the same in both cases.
**Note**: raw views don't have a `get` member function for obvious reasons.
### Runtime View
Runtime views iterate entities that have at least all the given components in
their bags. During construction, these views look at the number of entities
available for each component and pick up a reference to the smallest
set of candidates in order to speed up iterations.<br/>
They offer more or less the same functionalities of a multi component standard
view. However, they don't expose a `get` member function and users should refer
to the registry that generated the view to access components. In particular, a
runtime view exposes utility functions to get the estimated number of entities
it is going to return and to know whether it's empty or not. It's also possible
to ask a view if it contains a given entity.<br/>
Refer to the inline documentation for all the details.
Runtime view are extremely cheap to construct and should not be stored around in
any case. They should be used immediately after creation and then they should be
thrown away. The reasons for this go far beyond the scope of this document.<br/>
To iterate a runtime view, either use it in a range-for loop:
```cpp
using component_type = typename decltype(registry)::component_type;
component_type types[] = { registry.type<Position>(), registry.type<Velocity>() };
auto view = registry.view(std::cbegin(types), std::cend(types));
for(auto entity: view) {
// a component at a time ...
Position &position = registry.get<Position>(entity);
Velocity &velocity = registry.get<Velocity>(entity);
// ... or multiple components at once
std::tuple<Position &, Velocity &> tup = view.get<Position, Velocity>(entity);
// ...
}
```
Or rely on the `each` member function to iterate entities:
```cpp
using component_type = typename decltype(registry)::component_type;
component_type types[] = { registry.type<Position>(), registry.type<Velocity>() };
auto view = registry.view(std::cbegin(types), std::cend(types)).each([](auto entity) {
// ...
});
```
Performance are exactly the same in both cases.
**Note**: runtime views are meant for all those cases where users don't know at
compile-time what components to use to iterate entities. This is particularly
well suited to plugin systems and mods in general. Where possible, don't use
runtime views, as their performance are slightly inferior to those of the other
views.
### Give me everything
@@ -1488,62 +1596,65 @@ In general, all these functions can result in poor performance.<br/>
entity. For similar reasons, `orphans` can be even slower. Both functions should
not be used frequently to avoid the risk of a performance hit.
## Side notes
## Iterations: what is allowed and what is not
* Entity identifiers are numbers and nothing more. They are not classes and they
have no member functions at all. As already mentioned, do no try to inspect or
modify an entity descriptor in any way.
Most of the _ECS_ available out there have some annoying limitations (at least
from my point of view): entities and components cannot be created nor destroyed
during iterations.<br/>
`EnTT` partially solves the problem with a few limitations:
* As shown in the examples above, the preferred way to get references to the
components while iterating a view is by using the view itself. It's a faster
alternative to the `get` member function template that is part of the API of
the `Registry`. This is because the registry must ensure that a pool for the
given component exists before to use it; on the other side, views force the
construction of the pools for all their components and access them directly,
thus avoiding all the checks.
* Creating entities and components is allowed during iterations.
* Deleting an entity or removing its components is allowed during iterations if
it's the one currently returned by the view. For all the other entities,
destroying them or removing their components isn't allowed and it can result
in undefined behavior.
* Most of the _ECS_ available out there have some annoying limitations (at least
from my point of view): entities and components cannot be created and/or
destroyed during iterations.<br/>
`EnTT` partially solves the problem with a few limitations:
Iterators are invalidated and the behavior is undefined if an entity is modified
or destroyed and it's not the one currently returned by the view nor a newly
created one.<br/>
To work around it, possible approaches are:
* Creating entities and components is allowed during iterations.
* Deleting an entity or removing its components is allowed during iterations
if it's the one currently returned by a view. For all the other entities,
destroying them or removing their components isn't allowed and it can result
in undefined behavior.
* Store aside the entities and the components to be removed and perform the
operations at the end of the iteration.
* Mark entities and components with a proper tag component that indicates they
must be purged, then perform a second iteration to clean them up one by one.
Iterators are invalidated and the behavior is undefined if an entity is
modified or destroyed and it's not the one currently returned by the
view.<br/>
To work around it, possible approaches are:
A notable side effect of this feature is that the number of required allocations
is further reduced in most of the cases.
* Store aside the entities and the components to be removed and perform the
operations at the end of the iteration.
* Mark entities and components with a proper tag component that indicates
they must be purged, then perform a second iteration to clean them up one
by one.
## Multithreading
* Views and thus their iterators aren't thread safe. Do no try to iterate a set
of components and modify the same set concurrently.<br/>
That being said, as long as a thread iterates the entities that have the
component `X` or assign and removes that component from a set of entities,
another thread can safely do the same with components `Y` and `Z` and
everything will work like a charm.<br/>
As a trivial example, users can freely execute the rendering system and
In general, the entire registry isn't thread safe as it is. Thread safety isn't
something that users should want out of the box for several reasons. Just to
mention one of them: performance.<br/>
Views and consequently the approach adopted by `EnTT` are the great exception to
the rule. It's true that views and thus their iterators aren't thread safe by
themselves. Because of this users shouldn't try to iterate a set of components
and modify the same set concurrently. However:
* As long as a thread iterates the entities that have the component `X` or
assign and removes that component from a set of entities, another thread can
safely do the same with components `Y` and `Z` and everything will work like a
charm. As a trivial example, users can freely execute the rendering system and
iterate the renderable entities while updating a physic component concurrently
on a separate thread.
* In general, the entire registry isn't thread safe as it is. Thread safety
isn't something that users should want out of the box for several reasons.
Just to mention one of them: performance.<br/>
This kind of entity-component systems can be used in single threaded
applications as well as along with async stuff. Moreover, typical thread based
models for ECS don't require a fully thread safe registry to work. Actually
one could reach the goal with the registry as it is while working with most of
the common models, after all.<br/>
Because of the few reasons mentioned above and many others not mentioned,
users are completely responsible for synchronization whether required.
* Similarly, a single set of components can be iterated by multiple threads as
long as the components are neither assigned nor removed in the meantime. In
other words, a hypothetical movement system can start multiple threads, each
of which will access the components that carry information about velocity and
position for its entities.
This kind of entity-component systems can be used in single threaded
applications as well as along with async stuff or multiple threads. Moreover,
typical thread based models for _ECS_ don't require a fully thread safe registry
to work. Actually, users can reach the goal with the registry as it is while
working with most of the common models.
Because of the few reasons mentioned above and many others not mentioned, users
are completely responsible for synchronization whether required. On the other
hand, they could get away with it without having to resort to particular
expedients.
# Crash Course: core functionalities
@@ -1561,21 +1672,21 @@ There are plenty of different solutions out there and I could have used one of
them. However, I decided to spend my time to define a compact and versatile tool
that fully embraces what the modern C++ has to offer.
The _result of my efforts_ is the `ident` `constexpr` variable:
The _result of my efforts_ is the `Identifier` class template:
```cpp
#include <ident.hpp>
// defines the identifiers for the given types
constexpr auto identifiers = entt::ident<AType, AnotherType>;
using ID = entt::Identifier<AType, AnotherType>;
// ...
switch(aTypeIdentifier) {
case identifers.get<AType>():
case ID::get<AType>():
// ...
break;
case identifers.get<AnotherType>():
case ID::get<AnotherType>():
// ...
break;
default:
@@ -1583,9 +1694,9 @@ default:
}
```
This is all what the variable has to offer: a `get` member function that returns
a numerical identifier for the given type. It can be used in any context where
constant expressions are required.
This is all what the class template has to offer: a static `get` member function
that returns a numerical identifier for the given type. It can be used in any
context where constant expressions are required.
As long as the list remains unchanged, identifiers are also guaranteed to be the
same for every run. In case they have been used in a production environment and
@@ -1595,7 +1706,7 @@ identifiers unchanged:
```cpp
template<typename> struct IgnoreType {};
constexpr auto identifiers = entt::ident<
using ID = entt::Identifier<
ATypeStillValid,
IgnoreType<ATypeNoLongerValid>,
AnotherTypeStillValid
@@ -1667,6 +1778,29 @@ and over which users have not the control. Choosing a slightly different
identifier is probably the best solution to make the conflict disappear in this
case.
## Monostate
The monostate pattern is often presented as an alternative to a singleton based
configuration system. This is exactly its purpose in `EnTT`. Moreover, this
implementation is thread safe by design (hopefully).<br/>
Keys are represented by hashed strings, values are basic types like `int`s or
`bool`s. Values of different types can be associated to each key, even more than
one at a time. Because of this, users must pay attention to use the same type
both during an assignment and when they try to read back their data. Otherwise,
they will probably incur in unexpected results.
Example of use:
```cpp
entt::Monostate<entt::HashedString{"mykey"}>{} = true;
entt::Monostate<"mykey"_hs>{} = 42;
// ...
const bool b = entt::Monostate<"mykey"_hs>{};
const int i = entt::Monostate<entt::HashedString{"mykey"}>{};
```
# Crash Course: service locator
Usually service locators are tightly bound to the services they expose and it's
@@ -1860,7 +1994,7 @@ It has member functions to query its internal data structures, like `empty` or
```cpp
// checks if there are processes still running
bool empty = scheduler.empty();
const auto empty = scheduler.empty();
// gets the number of processes still running
Scheduler<std::uint32_t>::size_type size = scheduler.size();
@@ -2008,10 +2142,10 @@ _organize_ it:
```cpp
// gets the number of resources managed by a cache
auto size = cache.size();
const auto size = cache.size();
// checks if a cache contains at least a valid resource
auto empty = cache.empty();
const auto empty = cache.empty();
// clears a cache and discards its content
cache.clear();
@@ -2207,7 +2341,7 @@ listeners they contain (`size`) or if they contain at least a listener (`empty`)
and even to swap two signal handlers (`swap`).
Besides them, there are member functions to use both to connect and disconnect
listeners in all their forms by means of a sink::
listeners in all their forms by means of a sink:
```cpp
void foo(int, char) { /* ... */ }
@@ -2323,6 +2457,12 @@ delegate.connect<MyStruct, &MyStruct::f>(&instance);
It hasn't a `disconnect` counterpart. Instead, there exists a `reset` member
function to clear it.<br/>
The `empty` member function can be used to know if a delegate is empty:
```cpp
const auto empty = delegate.empty();
```
Finally, to invoke a delegate, the function call operator is the way to go as
usual:
@@ -2571,7 +2711,9 @@ Below an incomplete list of projects and articles:
* [EnttPong](https://github.com/reworks/EnttPong): example game for `EnTT`
framework.
* [ECS_SpaceBattle](https://github.com/vblanco20-1/ECS_SpaceBattle): huge space
* [Space Battle: Huge edition](http://victor.madtriangles.com/code%20experiment/2018/06/11/post-ecs-battle-huge.html):
huge space battle built entirely from scratch.
* [Space Battle](https://github.com/vblanco20-1/ECS_SpaceBattle): huge space
battle built on `UE4`.
* [Experimenting with ECS in UE4](http://victor.madtriangles.com/code%20experiment/2018/03/25/post-ue4-ecs-battle.html):
interesting article about `UE4` and `EnTT`.

9
TODO
View File

@@ -2,12 +2,11 @@
* 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)
* ease the assignment of tags as string (type-less assign member function + user defined literal for hashed strings)
* family -> std::uint32_t (or some other fixed and known size type)
* work stealing job system (see #100)
* C++17. That's all.
* AOB

View File

@@ -0,0 +1,6 @@
set(ENTT_VERSION "@PROJECT_VERSION@")
set(ENTT_INCLUDE_DIRS "@CMAKE_CURRENT_SOURCE_DIR@/src")
if(NOT CMAKE_VERSION VERSION_LESS "3.0")
include("${CMAKE_CURRENT_LIST_DIR}/EnTTTargets.cmake")
endif()

View File

@@ -0,0 +1,11 @@
set(ENTT_VERSION "@PROJECT_VERSION@")
@PACKAGE_INIT@
set_and_check(ENTT_INCLUDE_DIRS "@PACKAGE_CMAKE_INSTALL_INCLUDEDIR@")
if(NOT CMAKE_VERSION VERSION_LESS "3.0")
include("${CMAKE_CURRENT_LIST_DIR}/EnTTTargets.cmake")
endif()
check_required_components("@PROJECT_NAME@")

View File

@@ -6,7 +6,7 @@ include(ExternalProject)
ExternalProject_Add(
googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG release-1.8.0
GIT_TAG master
DOWNLOAD_DIR ${GOOGLETEST_DEPS_DIR}
TMP_DIR ${GOOGLETEST_DEPS_DIR}/tmp
STAMP_DIR ${GOOGLETEST_DEPS_DIR}/stamp

View File

@@ -2,8 +2,6 @@
# Doxygen configuration (documentation)
#
set(TARGET_DOCS docs)
set(DOXY_SOURCE_DIRECTORY ${entt_SOURCE_DIR}/src)
set(DOXY_DOCS_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
set(DOXY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
@@ -11,7 +9,7 @@ set(DOXY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
configure_file(doxy.in doxy.cfg @ONLY)
add_custom_target(
${TARGET_DOCS}
docs ALL
COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/doxy.cfg
WORKING_DIRECTORY ${entt_SOURCE_DIR}
VERBATIM

View File

@@ -4,7 +4,13 @@
#ifndef ENTT_NOEXCEPT
#define ENTT_NOEXCEPT noexcept
#endif
#endif // ENTT_NOEXCEPT
#ifndef ENTT_HS_SUFFIX
#define ENTT_HS_SUFFIX _hs
#endif // ENTT_HS_SUFFIX
#endif // ENTT_CONFIG_CONFIG_H

View File

@@ -26,13 +26,15 @@ struct StdSort {
*
* @tparam It Type of random access iterator.
* @tparam Compare Type of comparison function object.
* @tparam Args Types of arguments to forward to the sort function.
* @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.
* @param args Arguments to forward to the sort function, if any.
*/
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));
template<typename It, typename Compare = std::less<>, typename... Args>
void operator()(It first, It last, Compare compare = Compare{}, Args &&... args) const {
std::sort(std::forward<Args>(args)..., std::move(first), std::move(last), std::move(compare));
}
};
@@ -51,7 +53,7 @@ struct InsertionSort {
* @param compare A valid comparison function object.
*/
template<typename It, typename Compare = std::less<>>
void operator()(It first, It last, Compare compare = Compare{}) {
void operator()(It first, It last, Compare compare = Compare{}) const {
auto it = first + 1;
while(it != last) {

View File

@@ -2,9 +2,9 @@
#define ENTT_CORE_FAMILY_HPP
#include<type_traits>
#include<cstddef>
#include<atomic>
#include <type_traits>
#include <cstddef>
#include <atomic>
#include "../config/config.h"

View File

@@ -108,4 +108,9 @@ constexpr bool operator!=(const HashedString &lhs, const HashedString &rhs) ENTT
}
constexpr entt::HashedString operator"" ENTT_HS_SUFFIX(const char *str, std::size_t) ENTT_NOEXCEPT {
return entt::HashedString{str};
}
#endif // ENTT_CORE_HASHED_STRING_HPP

View File

@@ -2,9 +2,11 @@
#define ENTT_CORE_IDENT_HPP
#include<type_traits>
#include<cstddef>
#include<utility>
#include <type_traits>
#include <cstddef>
#include <utility>
#include <tuple>
#include "../config/config.h"
namespace entt {
@@ -19,38 +21,14 @@ namespace internal {
*/
template<typename... Types>
struct Identifier final: Identifier<Types>... {
using identifier_type = std::size_t;
template<std::size_t... Indexes>
constexpr Identifier(std::index_sequence<Indexes...>)
: Identifier<Types>{std::index_sequence<Indexes>{}}...
{}
template<typename Type>
constexpr std::size_t get() const {
return Identifier<std::decay_t<Type>>::get();
}
};
template<typename...>
struct IsPartOf;
template<typename Type, typename Current, typename... Other>
struct IsPartOf<Type, Current, Other...>: std::conditional_t<std::is_same<Type, Current>::value, std::true_type, IsPartOf<Type, Other...>> {};
template<typename Type>
struct Identifier<Type> {
using identifier_type = std::size_t;
template<std::size_t Index>
constexpr Identifier(std::index_sequence<Index>)
: index{Index}
{}
constexpr std::size_t get() const {
return index;
}
private:
const std::size_t index;
};
struct IsPartOf<Type>: std::false_type {};
/**
@@ -63,23 +41,23 @@ private:
/**
* @brief Types identifers.
* @brief Types identifiers.
*
* Variable template used to generate identifiers at compile-time for the given
* types. Use the `constexpr` `get` member function to know what's the
* identifier associated to the specific type.
* types. Use the `get` member function to know what's the identifier associated
* to the specific type.
*
* @note
* Identifiers are constant expression and can be used in any context where such
* an expression is required. As an example:
* @code{.cpp}
* constexpr auto identifiers = entt::ident<AType, AnotherType>;
* using ID = entt::Identifier<AType, AnotherType>;
*
* switch(aTypeIdentifier) {
* case identifers.get<AType>():
* case ID::get<AType>():
* // ...
* break;
* case identifers.get<AnotherType>():
* case ID::get<AnotherType>():
* // ...
* break;
* default:
@@ -87,19 +65,37 @@ private:
* }
* @endcode
*
* @note
* In case of single type list, `get` isn't a member function template:
* @code{.cpp}
* func(std::integral_constant<
* entt::ident<AType>::identifier_type,
* entt::ident<AType>::get()
* >{});
* @endcode
*
* @tparam Types List of types for which to generate identifiers.
*/
template<typename... Types>
constexpr auto ident = internal::Identifier<std::decay_t<Types>...>{std::make_index_sequence<sizeof...(Types)>{}};
class Identifier final {
using tuple_type = std::tuple<std::decay_t<Types>...>;
template<typename Type, std::size_t... Indexes>
static constexpr std::size_t get(std::index_sequence<Indexes...>) ENTT_NOEXCEPT {
static_assert(internal::IsPartOf<Type, Types...>::value, "!");
std::size_t max{};
using accumulator_type = std::size_t[];
accumulator_type accumulator = { (max = std::is_same<Type, std::tuple_element_t<Indexes, tuple_type>>::value ? Indexes : max)... };
(void)accumulator;
return max;
}
public:
/*! @brief Unsigned integer type. */
using identifier_type = std::size_t;
/**
* @brief Returns the identifier associated with a given type.
* @tparam Type of which to return the identifier.
* @return The identifier associated with the given type.
*/
template<typename Type>
static constexpr identifier_type get() ENTT_NOEXCEPT {
return get<std::decay_t<Type>>(std::make_index_sequence<sizeof...(Types)>{});
}
};
}

View File

@@ -0,0 +1,61 @@
#ifndef ENTT_CORE_MONOSTATE_HPP
#define ENTT_CORE_MONOSTATE_HPP
#include <atomic>
#include <cassert>
#include "family.hpp"
#include "hashed_string.hpp"
namespace entt {
/**
* @brief Minimal implementation of the monostate pattern.
*
* A minimal, yet complete configuration system built on top of the monostate
* pattern. Thread safe by design, it works only with basic types like `int`s or
* `bool`s.<br/>
* Multiple types and therefore more than one value can be associated with a
* single key. Because of this, users must pay attention to use the same type
* both during an assignment and when they try to read back their data.
* Otherwise, they can incur in unexpected results.
*/
template<HashedString::hash_type>
struct Monostate {
/**
* @brief Assigns a value of a specific type to a given key.
* @tparam Type Type of the value to assign.
* @param val User data to assign to the given key.
*/
template<typename Type>
void operator=(Type val) const ENTT_NOEXCEPT {
Monostate::value<Type> = val;
}
/**
* @brief Gets a value of a specific type for a given key.
* @tparam Type Type of the value to get.
* @return Stored value, if any.
*/
template<typename Type>
operator Type() const ENTT_NOEXCEPT {
return Monostate::value<Type>;
}
private:
template<typename Type>
static std::atomic<Type> value;
};
template<HashedString::hash_type ID>
template<typename Type>
std::atomic<Type> Monostate<ID>::value{};
}
#endif // ENTT_CORE_MONOSTATE_HPP

View File

@@ -170,7 +170,7 @@ struct Actor {
* @brief Returns a reference to the underlying registry.
* @return A reference to the underlying registry.
*/
const registry_type & registry() const ENTT_NOEXCEPT {
inline const registry_type & registry() const ENTT_NOEXCEPT {
return reg;
}
@@ -186,7 +186,7 @@ struct Actor {
* @brief Returns the entity associated with an actor.
* @return The entity associated with the actor.
*/
entity_type entity() const ENTT_NOEXCEPT {
inline entity_type entity() const ENTT_NOEXCEPT {
return entt;
}

View File

@@ -0,0 +1,87 @@
#ifndef ENTT_ENTITY_ENTITY_HPP
#define ENTT_ENTITY_ENTITY_HPP
#include "../config/config.h"
#include "entt_traits.hpp"
namespace entt {
namespace internal {
/**
* @cond TURN_OFF_DOXYGEN
* Internal details not to be documented.
*/
template<typename Entity>
static constexpr auto null = ~typename entt_traits<Entity>::entity_type{};
struct Null {
explicit constexpr Null() = default;
template<typename Entity>
constexpr operator Entity() const ENTT_NOEXCEPT {
return null<Entity>;
}
constexpr bool operator==(Null) const ENTT_NOEXCEPT {
return true;
}
constexpr bool operator!=(Null) const ENTT_NOEXCEPT {
return false;
}
template<typename Entity>
constexpr bool operator==(const Entity entity) const ENTT_NOEXCEPT {
return entity == null<Entity>;
}
template<typename Entity>
constexpr bool operator!=(const Entity entity) const ENTT_NOEXCEPT {
return entity != null<Entity>;
}
};
template<typename Entity>
constexpr bool operator==(const Entity entity, Null null) ENTT_NOEXCEPT {
return null == entity;
}
template<typename Entity>
constexpr bool operator!=(const Entity entity, Null null) ENTT_NOEXCEPT {
return null != entity;
}
/**
* Internal details not to be documented.
* @endcond TURN_OFF_DOXYGEN
*/
}
/**
* @brief Null entity.
*
* There exist implicit conversions from this variable to entity identifiers of
* any allowed type. Similarly, there exist comparision operators between the
* null entity and any other entity identifier.
*/
constexpr auto null = internal::Null{};
}
#endif // ENTT_ENTITY_ENTITY_HPP

View File

@@ -32,6 +32,8 @@ struct entt_traits<std::uint16_t> {
using entity_type = std::uint16_t;
/*! @brief Underlying version type. */
using version_type = std::uint8_t;
/*! @brief Difference type. */
using difference_type = std::int32_t;
/*! @brief Mask to use to get the entity number out of an identifier. */
static constexpr auto entity_mask = 0xFFF;
@@ -47,8 +49,8 @@ struct entt_traits<std::uint16_t> {
*
* A 32 bits entity identifier guarantees:
*
* * 24 bits for the entity number (suitable for almost all the games).
* * 8 bit for the version (resets in [0-255]).
* * 20 bits for the entity number (suitable for almost all the games).
* * 12 bit for the version (resets in [0-4095]).
*/
template<>
struct entt_traits<std::uint32_t> {
@@ -56,6 +58,8 @@ struct entt_traits<std::uint32_t> {
using entity_type = std::uint32_t;
/*! @brief Underlying version type. */
using version_type = std::uint16_t;
/*! @brief Difference type. */
using difference_type = std::int64_t;
/*! @brief Mask to use to get the entity number out of an identifier. */
static constexpr auto entity_mask = 0xFFFFF;
@@ -71,8 +75,8 @@ struct entt_traits<std::uint32_t> {
*
* A 64 bits entity identifier guarantees:
*
* * 40 bits for the entity number (an indecently large number).
* * 24 bit for the version (an indecently large number).
* * 32 bits for the entity number (an indecently large number).
* * 32 bit for the version (an indecently large number).
*/
template<>
struct entt_traits<std::uint64_t> {
@@ -80,13 +84,15 @@ struct entt_traits<std::uint64_t> {
using entity_type = std::uint64_t;
/*! @brief Underlying version type. */
using version_type = std::uint32_t;
/*! @brief Difference type. */
using difference_type = std::int64_t;
/*! @brief Mask to use to get the entity number out of an identifier. */
static constexpr auto entity_mask = 0xFFFFFFFFFF;
static constexpr auto entity_mask = 0xFFFFFFFF;
/*! @brief Mask to use to get the version out of an identifier. */
static constexpr auto version_mask = 0xFFFFFF;
static constexpr auto version_mask = 0xFFFFFFFF;
/*! @brief Extent of the entity number within an identifier. */
static constexpr auto entity_shift = 40;
static constexpr auto entity_shift = 32;
};

View File

@@ -3,11 +3,11 @@
#include <tuple>
#include <memory>
#include <vector>
#include <utility>
#include <cstddef>
#include <algorithm>
#include <type_traits>
#include <unordered_map>
#include "../config/config.h"
#include "registry.hpp"
@@ -24,45 +24,36 @@ namespace entt {
* entities of a registry at once.
*
* @note
* Components used along with prototypes must be copy constructible.
* Components used along with prototypes must be copy constructible. Prototypes
* wrap component types with custom types, so they do not interfere with other
* users of the registry they were built with.
*
* @warning
* Prototypes directly use their underlying registries to store entities and
* components for their purposes. Users must ensure that the lifetime of a
* registry and its contents exceed that of the prototypes that use it.
*
* @tparam Entity A valid entity type (see entt_traits for more details).
*/
template<typename Entity>
class Prototype {
class Prototype final {
using basic_fn_type = void(const Prototype &, Registry<Entity> &, const Entity);
using component_type = typename Registry<Entity>::component_type;
using fn_type = void(*)(Registry<Entity> &, const Entity, const void *);
using deleter_type = void(*)(void *);
using ptr_type = std::unique_ptr<void, deleter_type>;
template<typename Component>
static void accommodate(Registry<Entity> &registry, const Entity entity, const void *component) {
const auto &ref = *static_cast<const Component *>(component);
registry.template accommodate<Component>(entity, ref);
}
struct Wrapper { Component component; };
template<typename Component>
static void assign(Registry<Entity> &registry, const Entity entity, const void *component) {
if(!registry.template has<Component>(entity)) {
const auto &ref = *static_cast<const Component *>(component);
registry.template assign<Component>(entity, ref);
struct Handler {
basic_fn_type *accommodate;
basic_fn_type *assign;
};
void release() {
if(registry->valid(entity)) {
registry->destroy(entity);
}
}
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>;
@@ -71,6 +62,66 @@ public:
/*! @brief Unsigned integer type. */
using size_type = std::size_t;
/**
* @brief Constructs a prototype that is bound to a given registry.
* @param registry A valid reference to a registry.
*/
Prototype(Registry<Entity> &registry)
: registry{&registry},
entity{registry.create()}
{}
/**
* @brief Releases all its resources.
*/
~Prototype() {
release();
}
/*! @brief Copying a prototype isn't allowed. */
Prototype(const Prototype &) = delete;
/**
* @brief Move constructor.
*
* After prototype move construction, instances that have been moved from
* are placed in a valid but unspecified state. It's highly discouraged to
* continue using them.
*
* @param other The instance to move from.
*/
Prototype(Prototype &&other)
: handlers{std::move(other.handlers)},
registry{other.registry},
entity{other.entity}
{
other.entity = ~entity_type{};
}
/*! @brief Copying a prototype isn't allowed. @return This Prototype. */
Prototype & operator=(const Prototype &) = delete;
/**
* @brief Move assignment operator.
*
* After prototype move assignment, instances that have been moved from are
* placed in a valid but unspecified state. It's highly discouraged to
* continue using them.
*
* @param other The instance to move from.
* @return This Prototype.
*/
Prototype & operator=(Prototype &&other) {
if(this != &other) {
auto tmp{std::move(other)};
handlers.swap(tmp.handlers);
std::swap(registry, tmp.registry);
std::swap(entity, tmp.entity);
}
return *this;
}
/**
* @brief Assigns to or replaces the given component of a prototype.
* @tparam Component Type of component to assign or replace.
@@ -80,22 +131,21 @@ public:
*/
template<typename Component, typename... Args>
Component & set(Args &&... args) {
const auto ctype = registry_type::template type<Component>();
basic_fn_type *accommodate = [](const Prototype &prototype, Registry<Entity> &other, const Entity dst) {
const auto &wrapper = prototype.registry->template get<Wrapper<Component>>(prototype.entity);
other.template accommodate<Component>(dst, wrapper.component);
};
auto it = std::find_if(handlers.begin(), handlers.end(), [ctype](const auto &handler) {
return handler.type == ctype;
});
basic_fn_type *assign = [](const Prototype &prototype, Registry<Entity> &other, const Entity dst) {
if(!other.template has<Component>(dst)) {
const auto &wrapper = prototype.registry->template get<Wrapper<Component>>(prototype.entity);
other.template accommodate<Component>(dst, wrapper.component);
}
};
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>();
handlers[registry->template type<Component>()] = Handler{accommodate, assign};
auto &wrapper = registry->template accommodate<Wrapper<Component>>(entity, Component{std::forward<Args>(args)...});
return wrapper.component;
}
/**
@@ -104,9 +154,8 @@ public:
*/
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());
registry->template reset<Wrapper<Component>>(entity);
handlers.erase(registry->template type<Component>());
}
/**
@@ -116,17 +165,7 @@ public:
*/
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;
return registry->template has<Wrapper<Component>...>(entity);
}
/**
@@ -143,13 +182,7 @@ public:
*/
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());
return registry->template get<Wrapper<Component>>(entity).component;
}
/**
@@ -182,7 +215,7 @@ public:
* @return References to the components owned by the prototype.
*/
template<typename... Component>
std::enable_if_t<(sizeof...(Component) > 1), std::tuple<const Component &...>>
inline std::enable_if_t<(sizeof...(Component) > 1), std::tuple<const Component &...>>
get() const ENTT_NOEXCEPT {
return std::tuple<const Component &...>{get<Component>()...};
}
@@ -200,7 +233,7 @@ public:
* @return References to the components owned by the prototype.
*/
template<typename... Component>
std::enable_if_t<(sizeof...(Component) > 1), std::tuple<Component &...>>
inline std::enable_if_t<(sizeof...(Component) > 1), std::tuple<Component &...>>
get() ENTT_NOEXCEPT {
return std::tuple<Component &...>{get<Component>()...};
}
@@ -215,20 +248,41 @@ public:
* 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.
* @note
* The registry may or may not be different from the one already used by
* the prototype. There is also an overload that directly uses the
* underlying registry.
*
* @param registry A valid reference to a registry.
* @param other A valid reference to a registry.
* @return A valid entity identifier.
*/
entity_type create(registry_type &registry) {
const auto entity = registry.create();
assign(registry, entity);
entity_type create(registry_type &other) const {
const auto entity = other.create();
assign(other, entity);
return 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(entity);
* @endcode
*
* @note
* This overload directly uses the underlying registry as a working space.
* Therefore, the components of the prototype and of the entity will share
* the same registry.
*
* @return A valid entity identifier.
*/
inline entity_type create() const {
return create(*registry);
}
/**
* @brief Assigns the components of a prototype to a given entity.
*
@@ -237,18 +291,47 @@ public:
* In other words, only those components that the entity doesn't own yet are
* copied over. All the other components remain unchanged.
*
* @note
* The registry may or may not be different from the one already used by
* the prototype. There is also an overload that directly uses the
* underlying registry.
*
* @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.
* @param other A valid reference to a registry.
* @param dst A valid entity identifier.
*/
void assign(registry_type &registry, const entity_type entity) {
std::for_each(handlers.begin(), handlers.end(), [&registry, entity](auto &&handler) {
handler.assign(registry, entity, handler.component.get());
});
void assign(registry_type &other, const entity_type dst) const {
for(auto &handler: handlers) {
handler.second.assign(*this, other, dst);
}
}
/**
* @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.
*
* @note
* This overload directly uses the underlying registry as a working space.
* Therefore, the components of the prototype and of the entity will share
* the same registry.
*
* @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 dst A valid entity identifier.
*/
inline void assign(const entity_type dst) const {
assign(*registry, dst);
}
/**
@@ -257,18 +340,45 @@ public:
* Existing components are overwritten, if any. All the other components
* will be copied over to the target entity.
*
* @note
* The registry may or may not be different from the one already used by
* the prototype. There is also an overload that directly uses the
* underlying registry.
*
* @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.
* @param other A valid reference to a registry.
* @param dst A valid entity identifier.
*/
void accommodate(registry_type &registry, const entity_type entity) {
std::for_each(handlers.begin(), handlers.end(), [&registry, entity](auto &&handler) {
handler.accommodate(registry, entity, handler.component.get());
});
void accommodate(registry_type &other, const entity_type dst) const {
for(auto &handler: handlers) {
handler.second.accommodate(*this, other, dst);
}
}
/**
* @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.
*
* @note
* This overload directly uses the underlying registry as a working space.
* Therefore, the components of the prototype and of the entity will share
* the same registry.
*
* @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 dst A valid entity identifier.
*/
inline void accommodate(const entity_type dst) const {
accommodate(*registry, dst);
}
/**
@@ -279,16 +389,45 @@ public:
* In other words, only the components that the entity doesn't own yet are
* copied over. All the other components remain unchanged.
*
* @note
* The registry may or may not be different from the one already used by
* the prototype. There is also an overload that directly uses the
* underlying registry.
*
* @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.
* @param other A valid reference to a registry.
* @param dst A valid entity identifier.
*/
inline void operator()(registry_type &registry, const entity_type entity) ENTT_NOEXCEPT {
assign(registry, entity);
inline void operator()(registry_type &other, const entity_type dst) const ENTT_NOEXCEPT {
assign(other, dst);
}
/**
* @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.
*
* @note
* This overload directly uses the underlying registry as a working space.
* Therefore, the components of the prototype and of the entity will share
* the same registry.
*
* @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 dst A valid entity identifier.
*/
inline void operator()(const entity_type dst) const ENTT_NOEXCEPT {
assign(*registry, dst);
}
/**
@@ -301,27 +440,54 @@ public:
* 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.
* @note
* The registry may or may not be different from the one already used by
* the prototype. There is also an overload that directly uses the
* underlying registry.
*
* @param registry A valid reference to a registry.
* @param other A valid reference to a registry.
* @return A valid entity identifier.
*/
inline entity_type operator()(registry_type &registry) ENTT_NOEXCEPT {
return create(registry);
inline entity_type operator()(registry_type &other) const ENTT_NOEXCEPT {
return create(other);
}
/**
* @brief Creates a new entity using a given prototype.
*
* Utility shortcut, equivalent to the following snippet:
*
* @code{.cpp}
* const auto entity = registry.create();
* prototype(entity);
* @endcode
*
* @note
* This overload directly uses the underlying registry as a working space.
* Therefore, the components of the prototype and of the entity will share
* the same registry.
*
* @return A valid entity identifier.
*/
inline entity_type operator()() const ENTT_NOEXCEPT {
return create(*registry);
}
private:
std::vector<Handler> handlers;
std::unordered_map<component_type, Handler> handlers;
Registry<Entity> *registry;
entity_type entity;
};
/**
* @brief Default prototype
*
* The default prototype is the best choice for almost all the
* applications.<br/>
* Users should have a really good reason to choose something different.
*/
using DefaultPrototype = Prototype<uint32_t>;
using DefaultPrototype = Prototype<DefaultRegistry::entity_type>;
}

View File

@@ -9,6 +9,7 @@
#include <cstddef>
#include <cstdint>
#include <cassert>
#include <iterator>
#include <algorithm>
#include <type_traits>
#include "../config/config.h"
@@ -57,7 +58,7 @@ class Registry {
}
struct Attachee {
Attachee(const Entity entity): entity{entity} {}
Attachee(const Entity entity) ENTT_NOEXCEPT: entity{entity} {}
virtual ~Attachee() = default;
Entity entity;
};
@@ -74,13 +75,13 @@ class Registry {
};
template<typename Component>
bool managed() const ENTT_NOEXCEPT {
inline bool managed() const ENTT_NOEXCEPT {
const auto ctype = component_family::type<Component>();
return ctype < pools.size() && std::get<0>(pools[ctype]);
}
template<typename Component>
const SparseSet<Entity, Component> & pool() const ENTT_NOEXCEPT {
inline const SparseSet<Entity, Component> & pool() const ENTT_NOEXCEPT {
assert(managed<Component>());
const auto ctype = component_family::type<Component>();
return static_cast<SparseSet<Entity, Component> &>(*std::get<0>(pools[ctype]));
@@ -195,7 +196,7 @@ public:
}
/**
* @brief Increases the capacity of the pool for a given component.
* @brief Increases the capacity of the pool for the given component.
*
* If the new capacity is greater than the current capacity, new storage is
* allocated, otherwise the method does nothing.
@@ -222,17 +223,28 @@ public:
}
/**
* @brief Returns the number of entities ever created.
* @return Number of entities ever created.
* @brief Returns the capacity of the pool for the given component.
* @tparam Component Type of component in which one is interested.
* @return Capacity of the pool of the given component.
*/
template<typename Component>
size_type capacity() const ENTT_NOEXCEPT {
return entities.size();
return managed<Component>() ? pool<Component>().capacity() : size_type{};
}
/**
* @brief Checks whether the pool for the given component is empty.
* @brief Returns the number of entities that a registry has currently
* allocated space for.
* @return Capacity of the registry.
*/
size_type capacity() const ENTT_NOEXCEPT {
return entities.capacity();
}
/**
* @brief Checks whether the pool of the given component is empty.
* @tparam Component Type of component in which one is interested.
* @return True if the pool for the given component is empty, false
* @return True if the pool of the given component is empty, false
* otherwise.
*/
template<typename Component>
@@ -383,11 +395,11 @@ public:
* function can be used to know if they are still valid or the entity has
* been destroyed and potentially recycled.
*
* The returned entity has no components assigned.
* The returned entity has no components nor tags assigned.
*
* @return A valid entity identifier.
*/
entity_type create() ENTT_NOEXCEPT {
entity_type create() {
entity_type entity;
if(available) {
@@ -409,48 +421,22 @@ public:
}
/**
* @brief Clones an entity and returns the newly created one.
* @brief Destroys the entity that owns the given tag, if any.
*
* There are two kinds of entity identifiers:
* Convenient shortcut to destroy an entity by means of a tag type.<br/>
* Syntactic sugar for the following snippet:
*
* * Newly created ones in case no entities have been previously destroyed.
* * Recycled ones with updated versions.
* @code{.cpp}
* if(registry.has<Tag>()) {
* registry.destroy(registry.attachee<Tag>());
* }
* @endcode
*
* Users should not care about the type of the returned entity identifier.
* In case entity identifers are stored around, the `valid` member
* function can be used to know if they are still valid or the entity has
* been destroyed and potentially recycled.
*
* @warning
* In case there are listeners that observe the construction of components
* and assign other components to the entity in their bodies, the result of
* invoking this function may not be as expected. In the worst case, it
* could lead to undefined behavior. An assertion will abort the execution
* at runtime in debug mode if a violation is detected.
*
* @warning
* Attempting to clone 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 entity A valid entity identifier
* @return A valid entity identifier.
* @tparam Tag Type of tag to use to search for the entity.
*/
entity_type clone(const entity_type entity) ENTT_NOEXCEPT {
assert(valid(entity));
const auto other = create();
for(auto pos = pools.size(); pos; --pos) {
auto &tup = pools[pos-1];
auto &cpool = std::get<0>(tup);
if(cpool && cpool->has(entity)) {
cpool->clone(other, entity);
std::get<1>(tup).publish(*this, other);
}
}
return other;
template<typename Tag>
void destroy(tag_t) {
return has<Tag>() ? destroy(attachee<Tag>()) : void();
}
/**
@@ -473,7 +459,7 @@ public:
* An assertion will abort the execution at runtime in debug mode in case of
* invalid entity.
*
* @param entity A valid entity identifier
* @param entity A valid entity identifier.
*/
void destroy(const entity_type entity) {
assert(valid(entity));
@@ -510,6 +496,28 @@ public:
++available;
}
/**
* @brief Destroys the entities that own he given components, if any.
*
* Convenient shortcut to destroy a set of entities at once.<br/>
* Syntactic sugar for the following snippet:
*
* @code{.cpp}
* for(const auto entity: registry.view<Component...>(Type{}...)) {
* registry.destroy(entity);
* }
* @endcode
*
* @tparam Component Types of components to use to search for the entities.
* @tparam Type Type of view to use or empty to use a standard view.
*/
template<typename... Component, typename... Type>
void destroy(Type...) {
for(const auto entity: view<Component...>(Type{}...)) {
destroy(entity);
}
}
/**
* @brief Attaches the given tag to an entity.
*
@@ -527,7 +535,7 @@ public:
*
* @tparam Tag Type of tag to create.
* @tparam Args Types of arguments to use to construct the tag.
* @param entity A valid entity identifier
* @param entity A valid entity identifier.
* @param args Parameters to use to initialize the tag.
* @return A reference to the newly created tag.
*/
@@ -756,7 +764,7 @@ public:
* @return References to the components owned by the entity.
*/
template<typename... Component>
std::enable_if_t<(sizeof...(Component) > 1), std::tuple<const Component &...>>
inline std::enable_if_t<(sizeof...(Component) > 1), std::tuple<const Component &...>>
get(const entity_type entity) const ENTT_NOEXCEPT {
return std::tuple<const Component &...>{get<Component>(entity)...};
}
@@ -776,7 +784,7 @@ public:
* @return References to the components owned by the entity.
*/
template<typename... Component>
std::enable_if_t<(sizeof...(Component) > 1), std::tuple<Component &...>>
inline std::enable_if_t<(sizeof...(Component) > 1), std::tuple<Component &...>>
get(const entity_type entity) ENTT_NOEXCEPT {
return std::tuple<Component &...>{get<Component>(entity)...};
}
@@ -846,7 +854,7 @@ public:
* @return A valid entity identifier.
*/
template<typename Tag>
entity_type move(const entity_type entity) {
entity_type move(const entity_type entity) ENTT_NOEXCEPT {
assert(valid(entity));
assert(has<Tag>());
auto &tag = std::get<0>(tags[tag_family::type<Tag>()]);
@@ -1033,7 +1041,7 @@ public:
* maximize the performance during iterations and users should not make any
* assumption on the order.<br/>
* This function can be used to impose an order to the elements in the pool
* for the given component. The order is kept valid until a component of the
* of the given component. The order is kept valid until a component of the
* given type is assigned or removed from an entity.
*
* The comparison function object must return `true` if the first element
@@ -1061,13 +1069,15 @@ public:
* @tparam Component Type of components to sort.
* @tparam Compare Type of comparison function object.
* @tparam Sort Type of sort function object.
* @tparam Args Types of arguments to forward to the sort function object.
* @param compare A valid comparison function object.
* @param sort A valid sort function object.
* @param args Arguments to forward to the sort function object, if any.
*/
template<typename Component, typename Compare, typename Sort = StdSort>
void sort(Compare compare, Sort sort = Sort{}) {
template<typename Component, typename Compare, typename Sort = StdSort, typename... Args>
void sort(Compare compare, Sort sort = Sort{}, Args &&... args) {
assure<Component>();
pool<Component>().sort(std::move(compare), std::move(sort));
pool<Component>().sort(std::move(compare), std::move(sort), std::forward<Args>(args)...);
}
/**
@@ -1165,7 +1175,8 @@ public:
*/
void reset() {
each([this](const auto entity) {
destroy(entity);
// useless this-> used to suppress a warning with clang
this->destroy(entity);
});
}
@@ -1193,7 +1204,7 @@ public:
void each(Func func) const {
if(available) {
for(auto pos = entities.size(); pos; --pos) {
const entity_type curr = pos - 1;
const auto curr = entity_type(pos - 1);
const auto entity = entities[curr];
const auto entt = entity & traits_type::entity_mask;
@@ -1288,6 +1299,7 @@ public:
* @see View<Entity, Component>
* @see PersistentView
* @see RawView
* @see RuntimeView
*
* @tparam Component Type of components used to construct the view.
* @return A newly created standard view.
@@ -1422,6 +1434,7 @@ public:
* @see View<Entity, Component>
* @see PersistentView
* @see RawView
* @see RuntimeView
*
* @tparam Component Types of components used to construct the view.
* @return A newly created persistent view.
@@ -1451,6 +1464,7 @@ public:
* @see View<Entity, Component>
* @see PersistentView
* @see RawView
* @see RuntimeView
*
* @tparam Component Type of component used to construct the view.
* @return A newly created raw view.
@@ -1461,6 +1475,44 @@ public:
return RawView<Entity, Component>{pool<Component>()};
}
/**
* @brief Returns a runtime view for the given components.
*
* This kind of views are created on the fly and share with the registry its
* internal data structures.<br/>
* Users should throw away the view after use. Fortunately, creating and
* destroying a view is an incredibly cheap operation because they do not
* require any type of initialization.<br/>
* As a rule of thumb, storing a view should never be an option.
*
* Runtime views are well suited when users want to construct a view from
* some external inputs and don't know at compile-time what are the required
* components.<br/>
* This is particularly well suited to plugin systems and mods in general.
*
* @see View
* @see View<Entity, Component>
* @see PersistentView
* @see RawView
* @see RuntimeView
*
* @tparam It Type of forward iterator.
* @param first An iterator to the first element of the range of components.
* @param last An iterator past the last element of the range of components.
* @return A newly created runtime view.
*/
template<typename It>
RuntimeView<Entity> view(It first, It last) {
static_assert(std::is_convertible<typename std::iterator_traits<It>::value_type, component_type>::value, "!");
std::vector<const SparseSet<Entity> *> set(last - first);
std::transform(first, last, set.begin(), [this](const component_type ctype) {
return ctype < pools.size() ? std::get<0>(pools[ctype]).get() : nullptr;
});
return RuntimeView<Entity>{std::move(set)};
}
/**
* @brief Returns a temporary object to use to create snapshots.
*
@@ -1471,11 +1523,11 @@ public:
*
* @return A temporary object to use to take snasphosts.
*/
Snapshot<Entity> snapshot() const {
using follow_fn_type = entity_type(*)(const Registry &, const entity_type);
Snapshot<Entity> snapshot() const ENTT_NOEXCEPT {
using follow_fn_type = entity_type(const Registry &, const entity_type);
const entity_type seed = available ? (next | (entities[next] & ~traits_type::entity_mask)) : next;
follow_fn_type follow = [](const Registry &registry, const entity_type entity) -> entity_type {
follow_fn_type *follow = [](const Registry &registry, const entity_type entity) -> entity_type {
const auto &entities = registry.entities;
const auto entt = entity & traits_type::entity_mask;
const auto next = entities[entt] & traits_type::entity_mask;
@@ -1500,10 +1552,10 @@ public:
*
* @return A temporary object to use to load snasphosts.
*/
SnapshotLoader<Entity> restore() {
using assure_fn_type = void(*)(Registry &, const entity_type, const bool);
SnapshotLoader<Entity> restore() ENTT_NOEXCEPT {
using assure_fn_type = void(Registry &, const entity_type, const bool);
assure_fn_type assure = [](Registry &registry, const entity_type entity, const bool destroyed) {
assure_fn_type *assure = [](Registry &registry, const entity_type entity, const bool destroyed) {
using promotion_type = std::conditional_t<sizeof(size_type) >= sizeof(entity_type), size_type, entity_type>;
// explicit promotion to avoid warnings with std::uint16_t
const auto entt = promotion_type{entity} & traits_type::entity_mask;

View File

@@ -39,16 +39,16 @@ 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);
using follow_fn_type = Entity(const Registry<Entity> &, const Entity);
Snapshot(const Registry<Entity> &registry, Entity seed, follow_fn_type follow) ENTT_NOEXCEPT
Snapshot(const Registry<Entity> &registry, 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) {
void get(Archive &archive, std::size_t sz, It first, It last) const {
archive(static_cast<Entity>(sz));
while(first != last) {
@@ -61,7 +61,7 @@ class Snapshot final {
}
template<typename... Component, typename Archive, typename It, std::size_t... Indexes>
void component(Archive &archive, It first, It last, std::index_sequence<Indexes...>) {
void component(Archive &archive, It first, It last, std::index_sequence<Indexes...>) const {
std::array<std::size_t, sizeof...(Indexes)> size{};
auto begin = first;
@@ -99,7 +99,7 @@ public:
* @return An object of this type to continue creating the snapshot.
*/
template<typename Archive>
Snapshot & entities(Archive &archive) {
const Snapshot & entities(Archive &archive) const {
archive(static_cast<Entity>(registry.size()));
registry.each([&archive](const auto entity) { archive(entity); });
return *this;
@@ -116,7 +116,7 @@ public:
* @return An object of this type to continue creating the snapshot.
*/
template<typename Archive>
Snapshot & destroyed(Archive &archive) {
const Snapshot & destroyed(Archive &archive) const {
auto size = registry.capacity() - registry.size();
archive(static_cast<Entity>(size));
auto curr = seed;
@@ -141,7 +141,7 @@ public:
* @return An object of this type to continue creating the snapshot.
*/
template<typename Component, typename Archive>
Snapshot & component(Archive &archive) {
const Snapshot & component(Archive &archive) const {
const auto sz = registry.template size<Component>();
const auto *entities = registry.template data<Component>();
@@ -167,8 +167,8 @@ public:
* @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) {
std::enable_if_t<(sizeof...(Component) > 1), const Snapshot &>
component(Archive &archive) const {
using accumulator_type = int[];
accumulator_type accumulator = { 0, (component<Component>(archive), 0)... };
(void)accumulator;
@@ -190,7 +190,7 @@ public:
* @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) {
const Snapshot & component(Archive &archive, It first, It last) const {
component<Component...>(archive, first, last, std::make_index_sequence<sizeof...(Component)>{});
return *this;
}
@@ -207,7 +207,7 @@ public:
* @return An object of this type to continue creating the snapshot.
*/
template<typename Tag, typename Archive>
Snapshot & tag(Archive &archive) {
const Snapshot & tag(Archive &archive) const {
const bool has = registry.template has<Tag>();
// numerical length is forced for tags to facilitate loading
@@ -232,8 +232,8 @@ public:
* @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) {
std::enable_if_t<(sizeof...(Tag) > 1), const Snapshot &>
tag(Archive &archive) const {
using accumulator_type = int[];
accumulator_type accumulator = { 0, (tag<Tag>(archive), 0)... };
(void)accumulator;
@@ -243,7 +243,7 @@ public:
private:
const Registry<Entity> &registry;
const Entity seed;
follow_fn_type follow;
follow_fn_type *follow;
};
@@ -262,9 +262,9 @@ class SnapshotLoader final {
/*! @brief A registry is allowed to create snapshot loaders. */
friend class Registry<Entity>;
using assure_fn_type = void(*)(Registry<Entity> &, const Entity, const bool);
using assure_fn_type = void(Registry<Entity> &, const Entity, const bool);
SnapshotLoader(Registry<Entity> &registry, assure_fn_type assure_fn) ENTT_NOEXCEPT
SnapshotLoader(Registry<Entity> &registry, assure_fn_type *assure_fn) ENTT_NOEXCEPT
: registry{registry},
assure_fn{assure_fn}
{
@@ -273,7 +273,7 @@ class SnapshotLoader final {
}
template<typename Archive>
void assure(Archive &archive, bool destroyed) {
void assure(Archive &archive, bool destroyed) const {
Entity length{};
archive(length);
@@ -285,7 +285,7 @@ class SnapshotLoader final {
}
template<typename Type, typename Archive, typename... Args>
void assign(Archive &archive, Args... args) {
void assign(Archive &archive, Args... args) const {
Entity length{};
archive(length);
@@ -321,7 +321,7 @@ public:
* @return A valid loader to continue restoring data.
*/
template<typename Archive>
SnapshotLoader & entities(Archive &archive) {
const SnapshotLoader & entities(Archive &archive) const {
static constexpr auto destroyed = false;
assure(archive, destroyed);
return *this;
@@ -338,7 +338,7 @@ public:
* @return A valid loader to continue restoring data.
*/
template<typename Archive>
SnapshotLoader & destroyed(Archive &archive) {
const SnapshotLoader & destroyed(Archive &archive) const {
static constexpr auto destroyed = true;
assure(archive, destroyed);
return *this;
@@ -358,7 +358,7 @@ public:
* @return A valid loader to continue restoring data.
*/
template<typename... Component, typename Archive>
SnapshotLoader & component(Archive &archive) {
const SnapshotLoader & component(Archive &archive) const {
using accumulator_type = int[];
accumulator_type accumulator = { 0, (assign<Component>(archive), 0)... };
(void)accumulator;
@@ -379,7 +379,7 @@ public:
* @return A valid loader to continue restoring data.
*/
template<typename... Tag, typename Archive>
SnapshotLoader & tag(Archive &archive) {
const SnapshotLoader & tag(Archive &archive) const {
using accumulator_type = int[];
accumulator_type accumulator = { 0, (assign<Tag>(archive, tag_t{}), 0)... };
(void)accumulator;
@@ -396,7 +396,7 @@ public:
*
* @return A valid loader to continue restoring data.
*/
SnapshotLoader & orphans() {
const SnapshotLoader & orphans() const {
registry.orphans([this](const auto entity) {
registry.destroy(entity);
});
@@ -406,7 +406,7 @@ public:
private:
Registry<Entity> &registry;
assure_fn_type assure_fn;
assure_fn_type *assure_fn;
};
@@ -687,7 +687,7 @@ public:
* @param entity An entity identifier.
* @return True if `entity` is managed by the loader, false otherwise.
*/
bool has(entity_type entity) {
bool has(entity_type entity) const ENTT_NOEXCEPT {
return (remloc.find(entity) != remloc.cend());
}
@@ -703,9 +703,9 @@ public:
* @param entity An entity identifier.
* @return The identifier to which `entity` refers in the target registry.
*/
entity_type map(entity_type entity) {
entity_type map(entity_type entity) const ENTT_NOEXCEPT {
assert(has(entity));
return remloc[entity].first;
return remloc.find(entity)->second.first;
}
private:

View File

@@ -13,6 +13,7 @@
#include "../config/config.h"
#include "../core/algorithm.hpp"
#include "entt_traits.hpp"
#include "entity.hpp"
namespace entt {
@@ -59,19 +60,30 @@ template<typename Entity>
class SparseSet<Entity> {
using traits_type = entt_traits<Entity>;
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;
class Iterator final {
friend class SparseSet<Entity>;
Iterator(pointer direct, std::size_t pos)
: direct{direct}, pos{pos}
using entity_type = Entity;
using index_type = typename traits_type::difference_type;
Iterator(const entity_type *direct, index_type index) ENTT_NOEXCEPT
: direct{direct}, index{index}
{}
public:
using difference_type = index_type;
using value_type = const entity_type;
using pointer = value_type *;
using reference = value_type &;
using iterator_category = std::random_access_iterator_tag;
Iterator() ENTT_NOEXCEPT = default;
Iterator(const Iterator &) ENTT_NOEXCEPT = default;
Iterator & operator=(const Iterator &) ENTT_NOEXCEPT = default;
Iterator & operator++() ENTT_NOEXCEPT {
return --pos, *this;
return --index, *this;
}
Iterator operator++(int) ENTT_NOEXCEPT {
@@ -79,39 +91,80 @@ class SparseSet<Entity> {
return ++(*this), orig;
}
Iterator & operator--() ENTT_NOEXCEPT {
return ++index, *this;
}
Iterator operator--(int) ENTT_NOEXCEPT {
Iterator orig = *this;
return --(*this), orig;
}
Iterator & operator+=(const difference_type value) ENTT_NOEXCEPT {
pos -= value;
index -= value;
return *this;
}
Iterator operator+(const difference_type value) const ENTT_NOEXCEPT {
return Iterator{direct, pos-value};
return Iterator{direct, index-value};
}
inline Iterator & operator-=(const difference_type value) ENTT_NOEXCEPT {
return (*this += -value);
}
inline Iterator operator-(const difference_type value) const ENTT_NOEXCEPT {
return (*this + -value);
}
difference_type operator-(const Iterator &other) const ENTT_NOEXCEPT {
return other.index - index;
}
reference operator[](const difference_type value) const ENTT_NOEXCEPT {
return direct[index-value-1];
}
bool operator==(const Iterator &other) const ENTT_NOEXCEPT {
return other.pos == pos;
return other.index == index;
}
inline bool operator!=(const Iterator &other) const ENTT_NOEXCEPT {
return !(*this == other);
}
reference operator*() const ENTT_NOEXCEPT {
return direct[pos-1];
bool operator<(const Iterator &other) const ENTT_NOEXCEPT {
return index > other.index;
}
bool operator>(const Iterator &other) const ENTT_NOEXCEPT {
return index < other.index;
}
inline bool operator<=(const Iterator &other) const ENTT_NOEXCEPT {
return !(*this > other);
}
inline bool operator>=(const Iterator &other) const ENTT_NOEXCEPT {
return !(*this < other);
}
pointer operator->() const ENTT_NOEXCEPT {
return (direct+index-1);
}
inline reference operator*() const ENTT_NOEXCEPT {
return *operator->();
}
private:
pointer direct;
std::size_t pos;
index_type index;
};
static constexpr auto pending = ~typename traits_type::entity_type{};
public:
/*! @brief Underlying entity identifier. */
using entity_type = Entity;
/*! @brief Entity dependent position type. */
using pos_type = entity_type;
/*! @brief Unsigned integer type. */
using size_type = std::size_t;
/*! @brief Input iterator type. */
@@ -147,6 +200,15 @@ public:
direct.reserve(cap);
}
/**
* @brief Returns the number of elements that a sparse set has currently
* allocated space for.
* @return Capacity of the sparse set.
*/
size_type capacity() const ENTT_NOEXCEPT {
return direct.capacity();
}
/**
* @brief Returns the extent of a sparse set.
*
@@ -215,7 +277,8 @@ public:
* @return An iterator to the first entity of the internal packed array.
*/
const_iterator_type cbegin() const ENTT_NOEXCEPT {
return const_iterator_type{direct.data(), direct.size()};
const typename traits_type::difference_type pos = direct.size();
return const_iterator_type{direct.data(), pos};
}
/**
@@ -301,6 +364,15 @@ public:
return cend();
}
/**
* @brief Returns a reference to the element at the given position.
* @param pos Position of the element to return.
* @return A reference to the requested element.
*/
inline const entity_type & operator[](const size_type pos) const ENTT_NOEXCEPT {
return cbegin()[pos];
}
/**
* @brief Checks if a sparse set contains an entity.
* @param entity A valid entity identifier.
@@ -308,8 +380,8 @@ public:
*/
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);
// testing against null permits to avoid accessing the direct vector
return (pos < reverse.size()) && (reverse[pos] != null);
}
/**
@@ -332,8 +404,8 @@ public:
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);
// testing against null permits to avoid accessing the direct vector
return (reverse[pos] != null);
}
/**
@@ -348,7 +420,7 @@ public:
* @param entity A valid entity identifier.
* @return The position of the entity in the sparse set.
*/
pos_type get(const entity_type entity) const ENTT_NOEXCEPT {
size_type get(const entity_type entity) const ENTT_NOEXCEPT {
assert(has(entity));
return reverse[entity & traits_type::entity_mask];
}
@@ -369,36 +441,14 @@ public:
const auto pos = size_type(entity & traits_type::entity_mask);
if(!(pos < reverse.size())) {
const auto value = pending;
reverse.resize(pos+1, value);
// null is safe in all cases for our purposes
reverse.resize(pos+1, null);
}
reverse[pos] = pos_type(direct.size());
reverse[pos] = entity_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;
}
/**
* @brief Removes an entity from a sparse set.
*
@@ -417,7 +467,7 @@ public:
// swapping isn't required here, we are getting rid of the last element
reverse[back & traits_type::entity_mask] = candidate;
direct[candidate] = back;
candidate = pending;
candidate = null;
direct.pop_back();
}
@@ -436,7 +486,7 @@ public:
* @param lhs A valid position within the sparse set.
* @param rhs A valid position within the sparse set.
*/
void swap(const pos_type lhs, const pos_type rhs) ENTT_NOEXCEPT {
void swap(const size_type lhs, const size_type rhs) ENTT_NOEXCEPT {
assert(lhs < direct.size());
assert(rhs < direct.size());
auto &src = direct[lhs];
@@ -468,7 +518,7 @@ public:
auto from = other.cbegin();
auto to = other.cend();
pos_type pos = direct.size() - 1;
size_type pos = direct.size() - 1;
while(pos && from != to) {
if(has(*from)) {
@@ -492,7 +542,7 @@ public:
}
private:
std::vector<pos_type> reverse;
std::vector<entity_type> reverse;
std::vector<entity_type> direct;
};
@@ -522,21 +572,33 @@ private:
template<typename Entity, typename Type>
class SparseSet<Entity, Type>: public SparseSet<Entity> {
using underlying_type = SparseSet<Entity>;
using traits_type = entt_traits<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;
class Iterator final {
friend class SparseSet<Entity, Type>;
Iterator(pointer instances, std::size_t pos)
: instances{instances}, pos{pos}
using instance_type = std::conditional_t<Const, const Type, Type>;
using index_type = typename traits_type::difference_type;
Iterator(instance_type *instances, index_type index) ENTT_NOEXCEPT
: instances{instances}, index{index}
{}
public:
using difference_type = index_type;
using value_type = instance_type;
using pointer = value_type *;
using reference = value_type &;
using iterator_category = std::random_access_iterator_tag;
Iterator() ENTT_NOEXCEPT = default;
Iterator(const Iterator &) ENTT_NOEXCEPT = default;
Iterator & operator=(const Iterator &) ENTT_NOEXCEPT = default;
Iterator & operator++() ENTT_NOEXCEPT {
return --pos, *this;
return --index, *this;
}
Iterator operator++(int) ENTT_NOEXCEPT {
@@ -544,34 +606,75 @@ class SparseSet<Entity, Type>: public SparseSet<Entity> {
return ++(*this), orig;
}
Iterator & operator--() ENTT_NOEXCEPT {
return ++index, *this;
}
Iterator operator--(int) ENTT_NOEXCEPT {
Iterator orig = *this;
return --(*this), orig;
}
Iterator & operator+=(const difference_type value) ENTT_NOEXCEPT {
pos -= value;
index -= value;
return *this;
}
Iterator operator+(const difference_type value) const ENTT_NOEXCEPT {
return Iterator{instances, pos-value};
return Iterator{instances, index-value};
}
inline Iterator & operator-=(const difference_type value) ENTT_NOEXCEPT {
return (*this += -value);
}
inline Iterator operator-(const difference_type value) const ENTT_NOEXCEPT {
return (*this + -value);
}
difference_type operator-(const Iterator &other) const ENTT_NOEXCEPT {
return other.index - index;
}
reference operator[](const difference_type value) const ENTT_NOEXCEPT {
return instances[index-value-1];
}
bool operator==(const Iterator &other) const ENTT_NOEXCEPT {
return other.pos == pos;
return other.index == index;
}
inline bool operator!=(const Iterator &other) const ENTT_NOEXCEPT {
return !(*this == other);
}
reference operator*() const ENTT_NOEXCEPT {
return instances[pos-1];
bool operator<(const Iterator &other) const ENTT_NOEXCEPT {
return index > other.index;
}
bool operator>(const Iterator &other) const ENTT_NOEXCEPT {
return index < other.index;
}
inline bool operator<=(const Iterator &other) const ENTT_NOEXCEPT {
return !(*this > other);
}
inline bool operator>=(const Iterator &other) const ENTT_NOEXCEPT {
return !(*this < other);
}
pointer operator->() const ENTT_NOEXCEPT {
return (instances+pos-1);
return (instances+index-1);
}
inline reference operator*() const ENTT_NOEXCEPT {
return *operator->();
}
private:
pointer instances;
std::size_t pos;
index_type index;
};
public:
@@ -579,8 +682,6 @@ public:
using object_type = Type;
/*! @brief Underlying entity identifier. */
using entity_type = typename underlying_type::entity_type;
/*! @brief Entity dependent position type. */
using pos_type = typename underlying_type::pos_type;
/*! @brief Unsigned integer type. */
using size_type = typename underlying_type::size_type;
/*! @brief Input iterator type. */
@@ -665,7 +766,8 @@ public:
* @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()};
const typename traits_type::difference_type pos = instances.size();
return const_iterator_type{instances.data(), pos};
}
/**
@@ -697,7 +799,8 @@ public:
* @return An iterator to the first instance of the given type.
*/
iterator_type begin() ENTT_NOEXCEPT {
return iterator_type{instances.data(), instances.size()};
const typename traits_type::difference_type pos = instances.size();
return iterator_type{instances.data(), pos};
}
/**
@@ -754,6 +857,24 @@ public:
return iterator_type{instances.data(), {}};
}
/**
* @brief Returns a reference to the element at the given position.
* @param pos Position of the element to return.
* @return A reference to the requested element.
*/
inline const object_type & operator[](const size_type pos) const ENTT_NOEXCEPT {
return cbegin()[pos];
}
/**
* @brief Returns a reference to the element at the given position.
* @param pos Position of the element to return.
* @return A reference to the requested element.
*/
inline object_type & operator[](const size_type pos) ENTT_NOEXCEPT {
return const_cast<object_type &>(const_cast<const SparseSet *>(this)->operator[](pos));
}
/**
* @brief Returns the object associated to an entity.
*
@@ -842,24 +963,6 @@ public:
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.
*
@@ -916,19 +1019,21 @@ public:
*
* @tparam Compare Type of comparison function object.
* @tparam Sort Type of sort function object.
* @tparam Args Types of arguments to forward to the sort function object.
* @param compare A valid comparison function object.
* @param sort A valid sort function object.
* @param args Arguments to forward to the sort function object, if any.
*/
template<typename Compare, typename Sort = StdSort>
void sort(Compare compare, Sort sort = Sort{}) {
std::vector<pos_type> copy(instances.size());
template<typename Compare, typename Sort = StdSort, typename... Args>
void sort(Compare compare, Sort sort = Sort{}, Args &&... args) {
std::vector<size_type> copy(instances.size());
std::iota(copy.begin(), copy.end(), 0);
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]));
});
}, std::forward<Args>(args)...);
for(pos_type pos = 0, last = copy.size(); pos < last; ++pos) {
for(size_type pos = 0, last = copy.size(); pos < last; ++pos) {
auto curr = pos;
auto next = copy[curr];
@@ -971,7 +1076,7 @@ public:
auto from = other.cbegin();
auto to = other.cend();
pos_type pos = underlying_type::size() - 1;
size_type pos = underlying_type::size() - 1;
const auto *local = underlying_type::data();
while(pos && from != to) {

View File

@@ -2,9 +2,11 @@
#define ENTT_ENTITY_VIEW_HPP
#include <iterator>
#include <cassert>
#include <array>
#include <tuple>
#include <vector>
#include <utility>
#include <algorithm>
#include <type_traits>
@@ -60,6 +62,7 @@ class Registry;
* @sa View
* @sa View<Entity, Component>
* @sa RawView
* @sa RuntimeView
*
* @tparam Entity A valid entity type (see entt_traits for more details).
* @tparam Component Types of components iterated by the view.
@@ -82,14 +85,14 @@ class PersistentView final {
{}
public:
/*! @brief Input iterator type. */
using iterator_type = typename view_type::iterator_type;
/*! @brief Constant input iterator type. */
using const_iterator_type = typename view_type::const_iterator_type;
/*! @brief Underlying entity identifier. */
using entity_type = typename view_type::entity_type;
/*! @brief Unsigned integer type. */
using size_type = typename view_type::size_type;
/*! @brief Input iterator type. */
using iterator_type = typename view_type::iterator_type;
/*! @brief Constant input iterator type. */
using const_iterator_type = typename view_type::const_iterator_type;
/**
* @brief Returns the number of entities that have the given components.
@@ -234,6 +237,15 @@ public:
return view.end();
}
/**
* @brief Returns a reference to the element at the given position.
* @param pos Position of the element to return.
* @return A reference to the requested element.
*/
const entity_type & operator[](const size_type pos) const ENTT_NOEXCEPT {
return view[pos];
}
/**
* @brief Checks if a view contains an entity.
* @param entity A valid entity identifier.
@@ -306,7 +318,7 @@ public:
* @return The components assigned to the entity.
*/
template<typename... Comp>
std::enable_if_t<(sizeof...(Comp) > 1), std::tuple<const Comp &...>>
inline std::enable_if_t<(sizeof...(Comp) > 1), std::tuple<const Comp &...>>
get(const entity_type entity) const ENTT_NOEXCEPT {
assert(contains(entity));
return std::tuple<const Comp &...>{get<Comp>(entity)...};
@@ -330,7 +342,7 @@ public:
* @return The components assigned to the entity.
*/
template<typename... Comp>
std::enable_if_t<(sizeof...(Comp) > 1), std::tuple<Comp &...>>
inline std::enable_if_t<(sizeof...(Comp) > 1), std::tuple<Comp &...>>
get(const entity_type entity) ENTT_NOEXCEPT {
assert(contains(entity));
return std::tuple<Comp &...>{get<Comp>(entity)...};
@@ -445,6 +457,7 @@ private:
* @sa View<Entity, Component>
* @sa PersistentView
* @sa RawView
* @sa RuntimeView
*
* @tparam Entity A valid entity type (see entt_traits for more details).
* @tparam Component Types of components iterated by the view.
@@ -469,7 +482,25 @@ class View final {
using traits_type = entt_traits<Entity>;
class Iterator {
using size_type = typename view_type::size_type;
friend class View<Entity, Component...>;
using extent_type = typename view_type::size_type;
Iterator(unchecked_type unchecked, underlying_iterator_type begin, underlying_iterator_type end) ENTT_NOEXCEPT
: unchecked{unchecked},
begin{begin},
end{end},
extent{min(std::make_index_sequence<unchecked.size()>{})}
{
if(begin != end && !valid()) {
++(*this);
}
}
template<std::size_t... Indexes>
extent_type min(std::index_sequence<Indexes...>) const ENTT_NOEXCEPT {
return std::min({ std::get<Indexes>(unchecked)->extent()... });
}
bool valid() const ENTT_NOEXCEPT {
const auto entity = *begin;
@@ -485,18 +516,12 @@ class View final {
using value_type = typename underlying_iterator_type::value_type;
using pointer = typename underlying_iterator_type::pointer;
using reference = typename underlying_iterator_type::reference;
using iterator_category = typename underlying_iterator_type::iterator_category;
using iterator_category = std::forward_iterator_tag;
Iterator(unchecked_type unchecked, size_type extent, underlying_iterator_type begin, underlying_iterator_type end) ENTT_NOEXCEPT
: unchecked{unchecked},
extent{extent},
begin{begin},
end{end}
{
if(begin != end && !valid()) {
++(*this);
}
}
Iterator() ENTT_NOEXCEPT = default;
Iterator(const Iterator &) ENTT_NOEXCEPT = default;
Iterator & operator=(const Iterator &) ENTT_NOEXCEPT = default;
Iterator & operator++() ENTT_NOEXCEPT {
return (++begin != end && !valid()) ? ++(*this) : *this;
@@ -507,14 +532,6 @@ class View final {
return ++(*this), orig;
}
Iterator & operator+=(const difference_type value) ENTT_NOEXCEPT {
return ((begin += value) != end && !valid()) ? ++(*this) : *this;
}
Iterator operator+(const difference_type value) const ENTT_NOEXCEPT {
return Iterator{unchecked, extent, begin+value, end};
}
bool operator==(const Iterator &other) const ENTT_NOEXCEPT {
return other.begin == begin;
}
@@ -523,15 +540,19 @@ class View final {
return !(*this == other);
}
value_type operator*() const ENTT_NOEXCEPT {
return *begin;
pointer operator->() const ENTT_NOEXCEPT {
return begin.operator->();
}
inline reference operator*() const ENTT_NOEXCEPT {
return *operator->();
}
private:
const unchecked_type unchecked;
const size_type extent;
unchecked_type unchecked;
underlying_iterator_type begin;
underlying_iterator_type end;
extent_type extent;
};
View(pool_type<Component> &... pools) ENTT_NOEXCEPT
@@ -543,11 +564,6 @@ class View final {
return std::get<pool_type<Comp> &>(pools);
}
template<typename Comp>
inline pool_type<Comp> & pool() ENTT_NOEXCEPT {
return const_cast<pool_type<Comp> &>(const_cast<const View *>(this)->pool<Comp>());
}
const view_type * candidate() const ENTT_NOEXCEPT {
return std::min({ static_cast<const view_type *>(&pool<Component>())... }, [](const auto *lhs, const auto *rhs) {
return lhs->size() < rhs->size();
@@ -563,10 +579,6 @@ class View final {
return other;
}
typename view_type::size_type extent() const ENTT_NOEXCEPT {
return std::min({ pool<Component>().extent()... });
}
template<typename Comp, typename Other>
inline std::enable_if_t<std::is_same<Comp, Other>::value, const Other &>
get(const component_iterator_type<Comp> &it, const Entity) const ENTT_NOEXCEPT { return *it; }
@@ -592,15 +604,15 @@ class View final {
}
}
const auto extent = std::min({ pool<Component>().extent()... });
auto it = std::get<component_iterator_type<Comp>>(raw);
const auto ext = extent();
// fallback to visit what remains using indirections
for(; data[0] != end; ++data[0], ++it) {
const auto entity = *data[0];
const auto sz = size_type(entity & traits_type::entity_mask);
if(sz < ext && std::all_of(other.cbegin(), other.cend(), [entity](const view_type *view) { return view->fast(entity); })) {
if(sz < extent && std::all_of(other.cbegin(), other.cend(), [entity](const view_type *view) { return view->fast(entity); })) {
// avoided at least the indirection due to the sparse set for the pivot type (see get for more details)
func(entity, get<Comp, Component>(it, entity)...);
}
@@ -608,14 +620,14 @@ class View final {
}
public:
/*! @brief Input iterator type. */
using iterator_type = Iterator;
/*! @brief Constant input iterator type. */
using const_iterator_type = Iterator;
/*! @brief Underlying entity identifier. */
using entity_type = typename view_type::entity_type;
/*! @brief Unsigned integer type. */
using size_type = typename view_type::size_type;
/*! @brief Input iterator type. */
using iterator_type = Iterator;
/*! @brief Constant input iterator type. */
using const_iterator_type = Iterator;
/**
* @brief Estimates the number of entities that have the given components.
@@ -649,7 +661,7 @@ public:
*/
const_iterator_type cbegin() const ENTT_NOEXCEPT {
const auto *view = candidate();
return iterator_type{ unchecked(view), extent(), view->cbegin(), view->cend() };
return const_iterator_type{unchecked(view), view->cbegin(), view->cend()};
}
/**
@@ -705,7 +717,7 @@ public:
*/
const_iterator_type cend() const ENTT_NOEXCEPT {
const auto *view = candidate();
return iterator_type{ unchecked(view), extent(), view->cend(), view->cend() };
return const_iterator_type{unchecked(view), view->cend(), view->cend()};
}
/**
@@ -753,7 +765,8 @@ public:
*/
bool contains(const entity_type entity) const ENTT_NOEXCEPT {
const auto sz = size_type(entity & traits_type::entity_mask);
return sz < extent() && std::min({ (pool<Component>().has(entity) && (pool<Component>().data()[pool<Component>().view_type::get(entity)] == entity))... });
const auto extent = std::min({ pool<Component>().extent()... });
return sz < extent && std::min({ (pool<Component>().has(entity) && (pool<Component>().data()[pool<Component>().view_type::get(entity)] == entity))... });
}
/**
@@ -819,7 +832,7 @@ public:
* @return The components assigned to the entity.
*/
template<typename... Comp>
std::enable_if_t<(sizeof...(Comp) > 1), std::tuple<const Comp &...>>
inline std::enable_if_t<(sizeof...(Comp) > 1), std::tuple<const Comp &...>>
get(const entity_type entity) const ENTT_NOEXCEPT {
assert(contains(entity));
return std::tuple<const Comp &...>{get<Comp>(entity)...};
@@ -843,7 +856,7 @@ public:
* @return The components assigned to the entity.
*/
template<typename... Comp>
std::enable_if_t<(sizeof...(Comp) > 1), std::tuple<Comp &...>>
inline std::enable_if_t<(sizeof...(Comp) > 1), std::tuple<Comp &...>>
get(const entity_type entity) ENTT_NOEXCEPT {
assert(contains(entity));
return std::tuple<Comp &...>{get<Comp>(entity)...};
@@ -934,6 +947,7 @@ private:
* @sa View
* @sa PersistentView
* @sa RawView
* @sa RuntimeView
*
* @tparam Entity A valid entity type (see entt_traits for more details).
* @tparam Component Type of component iterated by the view.
@@ -951,16 +965,16 @@ class View<Entity, Component> final {
{}
public:
/*! @brief Input iterator type. */
using iterator_type = typename view_type::iterator_type;
/*! @brief Constant input iterator type. */
using const_iterator_type = typename view_type::const_iterator_type;
/*! @brief Type of component iterated by the view. */
using raw_type = typename pool_type::object_type;
/*! @brief Underlying entity identifier. */
using entity_type = typename pool_type::entity_type;
/*! @brief Unsigned integer type. */
using size_type = typename pool_type::size_type;
/*! @brief Type of component iterated by the view. */
using raw_type = typename pool_type::object_type;
/*! @brief Input iterator type. */
using iterator_type = typename view_type::iterator_type;
/*! @brief Constant input iterator type. */
using const_iterator_type = typename view_type::const_iterator_type;
/**
* @brief Returns the number of entities that have the given component.
@@ -1137,6 +1151,15 @@ public:
return pool.view_type::end();
}
/**
* @brief Returns a reference to the element at the given position.
* @param pos Position of the element to return.
* @return A reference to the requested element.
*/
const entity_type & operator[](const size_type pos) const ENTT_NOEXCEPT {
return pool.view_type::operator[](pos);
}
/**
* @brief Checks if a view contains an entity.
* @param entity A valid entity identifier.
@@ -1268,6 +1291,7 @@ private:
* @sa View
* @sa View<Entity, Component>
* @sa PersistentView
* @sa RuntimeView
*
* @tparam Entity A valid entity type (see entt_traits for more details).
* @tparam Component Type of component iterated by the view.
@@ -1284,16 +1308,16 @@ class RawView final {
{}
public:
/*! @brief Input iterator type. */
using iterator_type = typename pool_type::iterator_type;
/*! @brief Constant input iterator type. */
using const_iterator_type = typename pool_type::const_iterator_type;
/*! @brief Type of component iterated by the view. */
using raw_type = typename pool_type::object_type;
/*! @brief Underlying entity identifier. */
using entity_type = typename pool_type::entity_type;
/*! @brief Unsigned integer type. */
using size_type = typename pool_type::size_type;
/*! @brief Type of component iterated by the view. */
using raw_type = typename pool_type::object_type;
/*! @brief Input iterator type. */
using iterator_type = typename pool_type::iterator_type;
/*! @brief Constant input iterator type. */
using const_iterator_type = typename pool_type::const_iterator_type;
/**
* @brief Returns the number of instances of the given type.
@@ -1464,6 +1488,24 @@ public:
return pool.end();
}
/**
* @brief Returns a reference to the element at the given position.
* @param pos Position of the element to return.
* @return A reference to the requested element.
*/
const raw_type & operator[](const size_type pos) const ENTT_NOEXCEPT {
return pool[pos];
}
/**
* @brief Returns a reference to the element at the given position.
* @param pos Position of the element to return.
* @return A reference to the requested element.
*/
inline raw_type & operator[](const size_type pos) ENTT_NOEXCEPT {
return const_cast<raw_type &>(const_cast<const RawView *>(this)->operator[](pos));
}
/**
* @brief Iterates components and applies the given function object to them.
*
@@ -1486,12 +1528,12 @@ public:
/**
* @brief Iterates components and applies the given function object to them.
*
* The function object is provided with a const reference to each component
* of the view.<br/>
* The function object is provided with a reference to each component of the
* view.<br/>
* The signature of the function should be equivalent to the following:
*
* @code{.cpp}
* void(const Component &);
* void(Component &);
* @endcode
*
* @tparam Func Type of the function object to invoke.
@@ -1507,6 +1549,345 @@ private:
};
/**
* @brief Runtime view.
*
* Runtime views iterate over those entities that have at least all the given
* components in their bags. During initialization, a runtime view looks at the
* number of entities available for each component and picks up a reference to
* the smallest set of candidate entities in order to get a performance boost
* when iterate.<br/>
* Order of elements during iterations are highly dependent on the order of the
* underlying data structures. See SparseSet and its specializations for more
* details.
*
* @b Important
*
* Iterators aren't invalidated if:
*
* * New instances of the given components are created and assigned to entities.
* * The entity currently pointed is modified (as an example, if one of the
* given components is removed from the entity to which the iterator points).
*
* In all the other cases, modifying the pools of the given components in any
* way invalidates all the iterators and using them results in undefined
* behavior.
*
* @note
* Views share references to the underlying data structures with the Registry
* that generated them. Therefore any change to the entities and to the
* components made by means of the registry are immediately reflected by views,
* unless a pool wasn't missing when the view was built (in this case, the view
* won't have a valid reference and won't be updated accordingly).
*
* @warning
* Lifetime of a view must overcome the one of the registry that generated it.
* In any other case, attempting to use a view results in undefined behavior.
*
* @sa View
* @sa View<Entity, Component>
* @sa PersistentView
* @sa RawView
*
* @tparam Entity A valid entity type (see entt_traits for more details).
*/
template<typename Entity>
class RuntimeView {
/*! @brief A registry is allowed to create views. */
friend class Registry<Entity>;
using view_type = SparseSet<Entity>;
using underlying_iterator_type = typename view_type::const_iterator_type;
using pattern_type = std::vector<const view_type *>;
using extent_type = typename view_type::size_type;
using traits_type = entt_traits<Entity>;
class Iterator {
friend class RuntimeView<Entity>;
Iterator(underlying_iterator_type begin, underlying_iterator_type end, const view_type * const *first, const view_type * const *last, extent_type extent) ENTT_NOEXCEPT
: begin{begin},
end{end},
first{first},
last{last},
extent{extent}
{
if(begin != end && !valid()) {
++(*this);
}
}
bool valid() const ENTT_NOEXCEPT {
const auto entity = *begin;
const auto sz = size_type(entity & traits_type::entity_mask);
return sz < extent && std::all_of(first, last, [entity](const auto *view) {
return view->fast(entity);
});
}
public:
using difference_type = typename underlying_iterator_type::difference_type;
using value_type = typename underlying_iterator_type::value_type;
using pointer = typename underlying_iterator_type::pointer;
using reference = typename underlying_iterator_type::reference;
using iterator_category = std::forward_iterator_tag;
Iterator() ENTT_NOEXCEPT = default;
Iterator(const Iterator &) ENTT_NOEXCEPT = default;
Iterator & operator=(const Iterator &) ENTT_NOEXCEPT = default;
Iterator & operator++() ENTT_NOEXCEPT {
return (++begin != end && !valid()) ? ++(*this) : *this;
}
Iterator operator++(int) ENTT_NOEXCEPT {
Iterator orig = *this;
return ++(*this), orig;
}
bool operator==(const Iterator &other) const ENTT_NOEXCEPT {
return other.begin == begin;
}
inline bool operator!=(const Iterator &other) const ENTT_NOEXCEPT {
return !(*this == other);
}
pointer operator->() const ENTT_NOEXCEPT {
return begin.operator->();
}
inline reference operator*() const ENTT_NOEXCEPT {
return *operator->();
}
private:
underlying_iterator_type begin;
underlying_iterator_type end;
const view_type * const *first;
const view_type * const *last;
extent_type extent;
};
RuntimeView(pattern_type others) ENTT_NOEXCEPT
: pools{std::move(others)}
{
const auto it = std::min_element(pools.begin(), pools.end(), [](const auto *lhs, const auto *rhs) {
return (!lhs && rhs) || (lhs && rhs && lhs->size() < rhs->size());
});
// brings the best candidate (if any) on front of the vector
std::rotate(pools.begin(), it, pools.end());
}
extent_type min() const ENTT_NOEXCEPT {
extent_type extent{};
if(valid()) {
const auto it = std::min_element(pools.cbegin(), pools.cend(), [](const auto *lhs, const auto *rhs) {
return lhs->extent() < rhs->extent();
});
extent = (*it)->extent();
}
return extent;
}
inline bool valid() const ENTT_NOEXCEPT {
return !pools.empty() && pools.front();
}
public:
/*! @brief Underlying entity identifier. */
using entity_type = typename view_type::entity_type;
/*! @brief Unsigned integer type. */
using size_type = typename view_type::size_type;
/*! @brief Input iterator type. */
using iterator_type = Iterator;
/*! @brief Constant input iterator type. */
using const_iterator_type = Iterator;
/**
* @brief Estimates the number of entities that have the given components.
* @return Estimated number of entities that have the given components.
*/
size_type size() const ENTT_NOEXCEPT {
return valid() ? pools.front()->size() : size_type{};
}
/**
* @brief Checks if the view is definitely empty.
* @return True if the view is definitely empty, false otherwise.
*/
bool empty() const ENTT_NOEXCEPT {
return !valid() || pools.front()->empty();
}
/**
* @brief Returns an iterator to the first entity that has the given
* components.
*
* The returned iterator points to the first entity that has the given
* components. If the view is empty, the returned iterator will be equal to
* `end()`.
*
* @note
* Input iterators stay true to the order imposed to the underlying data
* structures.
*
* @return An iterator to the first entity that has the given components.
*/
const_iterator_type cbegin() const ENTT_NOEXCEPT {
const_iterator_type it{};
if(valid()) {
const auto &pool = *pools.front();
const auto * const *data = pools.data();
it = { pool.cbegin(), pool.cend(), data + 1, data + pools.size(), min() };
}
return it;
}
/**
* @brief Returns an iterator to the first entity that has the given
* components.
*
* The returned iterator points to the first entity that has the given
* components. If the view is empty, the returned iterator will be equal to
* `end()`.
*
* @note
* Input iterators stay true to the order imposed to the underlying data
* structures.
*
* @return An iterator to the first entity that has the given components.
*/
inline const_iterator_type begin() const ENTT_NOEXCEPT {
return cbegin();
}
/**
* @brief Returns an iterator to the first entity that has the given
* components.
*
* The returned iterator points to the first entity that has the given
* components. If the view is empty, the returned iterator will be equal to
* `end()`.
*
* @note
* Input iterators stay true to the order imposed to the underlying data
* structures.
*
* @return An iterator to the first entity that has the given components.
*/
inline iterator_type begin() ENTT_NOEXCEPT {
return cbegin();
}
/**
* @brief Returns an iterator that is past the last entity that has the
* given components.
*
* The returned iterator points to the entity following the last entity that
* has the given components. Attempting to dereference the returned iterator
* results in undefined behavior.
*
* @note
* Input iterators stay true to the order imposed to the underlying data
* structures.
*
* @return An iterator to the entity following the last entity that has the
* given components.
*/
const_iterator_type cend() const ENTT_NOEXCEPT {
const_iterator_type it{};
if(valid()) {
const auto &pool = *pools.front();
it = { pool.cend(), pool.cend(), nullptr, nullptr, min() };
}
return it;
}
/**
* @brief Returns an iterator that is past the last entity that has the
* given components.
*
* The returned iterator points to the entity following the last entity that
* has the given components. Attempting to dereference the returned iterator
* results in undefined behavior.
*
* @note
* Input iterators stay true to the order imposed to the underlying data
* structures.
*
* @return An iterator to the entity following the last entity that has the
* given components.
*/
inline const_iterator_type end() const ENTT_NOEXCEPT {
return cend();
}
/**
* @brief Returns an iterator that is past the last entity that has the
* given components.
*
* The returned iterator points to the entity following the last entity that
* has the given components. Attempting to dereference the returned iterator
* results in undefined behavior.
*
* @note
* Input iterators stay true to the order imposed to the underlying data
* structures.
*
* @return An iterator to the entity following the last entity that has the
* given components.
*/
inline iterator_type end() ENTT_NOEXCEPT {
return cend();
}
/**
* @brief Checks if a view contains an entity.
* @param entity A valid entity identifier.
* @return True if the view contains the given entity, false otherwise.
*/
bool contains(const entity_type entity) const ENTT_NOEXCEPT {
return valid() && std::all_of(pools.cbegin(), pools.cend(), [entity](const auto *view) {
return view->has(entity) && view->data()[view->get(entity)] == entity;
});
}
/**
* @brief Iterates entities and applies the given function object to them.
*
* The function object is invoked for each entity. It is provided only with
* the entity itself. To get the components, users can use the registry with
* which the view was built.<br/>
* The signature of the function should be equivalent to the following:
*
* @code{.cpp}
* void(const entity_type);
* @endcode
*
* @tparam Func Type of the function object to invoke.
* @param func A valid function object.
*/
template<typename Func>
void each(Func func) const {
std::for_each(cbegin(), cend(), func);
}
private:
pattern_type pools;
};
}

View File

@@ -2,7 +2,9 @@
#include "core/family.hpp"
#include "core/hashed_string.hpp"
#include "core/ident.hpp"
#include "core/monostate.hpp"
#include "entity/actor.hpp"
#include "entity/entity.hpp"
#include "entity/entt_traits.hpp"
#include "entity/helper.hpp"
#include "entity/prototype.hpp"

View File

@@ -115,7 +115,7 @@ class Process {
}
template<State S, typename... Args>
void tick(char, tag<S>, Args &&...) {}
void tick(char, tag<S>, Args &&...) const ENTT_NOEXCEPT {}
protected:
/**

View File

@@ -5,7 +5,6 @@
#include <vector>
#include <memory>
#include <utility>
#include <iterator>
#include <algorithm>
#include <type_traits>
#include "../config/config.h"
@@ -43,31 +42,27 @@ namespace entt {
*/
template<typename Delta>
class Scheduler final {
template<typename T>
struct type_t { using type = T; };
struct ProcessHandler final {
using instance_type = std::unique_ptr<void, void(*)(void *)>;
using update_type = bool(*)(ProcessHandler &, Delta, void *);
using abort_type = void(*)(ProcessHandler &, bool);
using update_fn_type = bool(ProcessHandler &, Delta, void *);
using abort_fn_type = void(ProcessHandler &, bool);
using next_type = std::unique_ptr<ProcessHandler>;
instance_type instance;
update_type update;
abort_type abort;
update_fn_type *update;
abort_fn_type *abort;
next_type next;
};
template<typename Lambda>
struct Then final: Lambda {
Then(Lambda &&lambda, ProcessHandler *handler)
: Lambda{std::forward<Lambda>(lambda)}, handler{handler}
struct Then final {
Then(ProcessHandler *handler)
: handler{handler}
{}
template<typename Proc, typename... Args>
decltype(auto) then(Args &&... args) && {
static_assert(std::is_base_of<Process<Proc, Delta>, Proc>::value, "!");
handler = Lambda::operator()(handler, type_t<Proc>{}, std::forward<Args>(args)...);
handler = Scheduler::then<Proc>(handler, std::forward<Args>(args)...);
return std::move(*this);
}
@@ -110,20 +105,15 @@ class Scheduler final {
delete static_cast<Proc *>(proc);
}
auto then(ProcessHandler *handler) {
auto lambda = [](ProcessHandler *handler, auto next, auto... args) {
using Proc = typename decltype(next)::type;
template<typename Proc, typename... Args>
static auto then(ProcessHandler *handler, Args &&... args) {
if(handler) {
auto proc = typename ProcessHandler::instance_type{new Proc{std::forward<Args>(args)...}, &Scheduler::deleter<Proc>};
handler->next.reset(new ProcessHandler{std::move(proc), &Scheduler::update<Proc>, &Scheduler::abort<Proc>, nullptr});
handler = handler->next.get();
}
if(handler) {
auto proc = typename ProcessHandler::instance_type{new Proc{std::forward<decltype(args)>(args)...}, &Scheduler::deleter<Proc>};
handler->next.reset(new ProcessHandler{std::move(proc), &Scheduler::update<Proc>, &Scheduler::abort<Proc>, nullptr});
handler = handler->next.get();
}
return handler;
};
return Then<decltype(lambda)>{std::move(lambda), handler};
return handler;
}
public:
@@ -202,7 +192,7 @@ public:
ProcessHandler handler{std::move(proc), &Scheduler::update<Proc>, &Scheduler::abort<Proc>, nullptr};
handlers.push_back(std::move(handler));
return then(&handlers.back());
return Then{&handlers.back()};
}
/**

View File

@@ -85,7 +85,7 @@ public:
inline const Resource & operator *() const ENTT_NOEXCEPT { return get(); }
/**
* @brief Gets a pointer to the managed resource from a handle .
* @brief Gets a pointer to the managed resource from a handle.
*
* @warning
* The behavior is undefined if the handle doesn't contain a resource.<br/>

View File

@@ -34,10 +34,8 @@ class Delegate;
*/
template<typename Ret, typename... Args>
class Delegate<Ret(Args...)> final {
using proto_type = Ret(*)(void *, Args...);
using stub_type = std::pair<void *, proto_type>;
static Ret fallback(void *, Args...) ENTT_NOEXCEPT { return {}; }
using proto_fn_type = Ret(void *, Args...);
using stub_type = std::pair<void *, proto_fn_type *>;
template<Ret(*Function)(Args...)>
static Ret proto(void *, Args... args) {
@@ -52,9 +50,18 @@ class Delegate<Ret(Args...)> final {
public:
/*! @brief Default constructor. */
Delegate() ENTT_NOEXCEPT
: stub{std::make_pair(nullptr, &fallback)}
: stub{}
{}
/**
* @brief Checks whether a delegate actually stores a listener.
* @return True if the delegate is empty, false otherwise.
*/
bool empty() const ENTT_NOEXCEPT {
// no need to test also stub.first
return !stub.second;
}
/**
* @brief Binds a free function to a delegate.
* @tparam Function A valid free function pointer.
@@ -86,7 +93,7 @@ public:
* After a reset, a delegate can be safely invoked with no effect.
*/
void reset() ENTT_NOEXCEPT {
stub = std::make_pair(nullptr, &fallback);
stub.second = nullptr;
}
/**
@@ -94,7 +101,7 @@ public:
* @param args Arguments to use to invoke the underlying function.
* @return The value returned by the underlying function.
*/
Ret operator()(Args... args) {
Ret operator()(Args... args) const {
return stub.second(stub.first, args...);
}

View File

@@ -167,7 +167,7 @@ public:
* 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.
*/
inline void update() {
inline void update() const {
for(auto pos = wrappers.size(); pos; --pos) {
auto &wrapper = wrappers[pos-1];

View File

@@ -11,6 +11,7 @@
#include <vector>
#include <list>
#include "../config/config.h"
#include "../core/family.hpp"
namespace entt {
@@ -39,6 +40,8 @@ namespace entt {
*/
template<typename Derived>
class Emitter {
using handler_family = Family<struct InternalEmitterHandlerFamily>;
struct BaseHandler {
virtual ~BaseHandler() = default;
virtual bool empty() const ENTT_NOEXCEPT = 0;
@@ -112,20 +115,9 @@ class Emitter {
container_type onL{};
};
static std::size_t next() ENTT_NOEXCEPT {
static std::size_t counter = 0;
return counter++;
}
template<typename>
static std::size_t type() ENTT_NOEXCEPT {
static std::size_t value = next();
return value;
}
template<typename Event>
Handler<Event> & handler() ENTT_NOEXCEPT {
const std::size_t family = type<Event>();
const std::size_t family = handler_family::type<Event>();
if(!(family < handlers.size())) {
handlers.resize(family+1);
@@ -304,8 +296,9 @@ public:
* results in undefined behavior.
*/
void clear() ENTT_NOEXCEPT {
std::for_each(handlers.begin(), handlers.end(),
[](auto &&handler) { if(handler) { handler->clear(); } });
std::for_each(handlers.begin(), handlers.end(), [](auto &&handler) {
return handler ? handler->clear() : void();
});
}
/**
@@ -315,7 +308,7 @@ public:
*/
template<typename Event>
bool empty() const ENTT_NOEXCEPT {
const std::size_t family = type<Event>();
const std::size_t family = handler_family::type<Event>();
return (!(family < handlers.size()) ||
!handlers[family] ||
@@ -327,8 +320,9 @@ public:
* @return True if there are no listeners registered, false otherwise.
*/
bool empty() const ENTT_NOEXCEPT {
return std::all_of(handlers.cbegin(), handlers.cend(),
[](auto &&handler) { return !handler || handler->empty(); });
return std::all_of(handlers.cbegin(), handlers.cend(), [](auto &&handler) {
return !handler || handler->empty();
});
}
private:

View File

@@ -26,12 +26,12 @@ struct Invoker;
template<typename Ret, typename... Args, typename Collector>
struct Invoker<Ret(Args...), Collector> {
using proto_type = Ret(*)(void *, Args...);
using call_type = std::pair<void *, proto_type>;
using proto_fn_type = Ret(void *, Args...);
using call_type = std::pair<void *, proto_fn_type *>;
virtual ~Invoker() = default;
bool invoke(Collector &collector, proto_type proto, void *instance, Args... args) {
bool invoke(Collector &collector, proto_fn_type *proto, void *instance, Args... args) const {
return collector(proto(instance, args...));
}
};
@@ -39,12 +39,12 @@ struct Invoker<Ret(Args...), Collector> {
template<typename... Args, typename Collector>
struct Invoker<void(Args...), Collector> {
using proto_type = void(*)(void *, Args...);
using call_type = std::pair<void *, proto_type>;
using proto_fn_type = void(void *, Args...);
using call_type = std::pair<void *, proto_fn_type *>;
virtual ~Invoker() = default;
bool invoke(Collector &, proto_type proto, void *instance, Args... args) {
bool invoke(Collector &, proto_fn_type *proto, void *instance, Args... args) const {
return (proto(instance, args...), true);
}
};
@@ -132,8 +132,8 @@ class Sink<Ret(Args...)> final {
template<typename, typename>
friend class SigH;
using proto_type = Ret(*)(void *, Args...);
using call_type = std::pair<void *, proto_type>;
using proto_fn_type = Ret(void *, Args...);
using call_type = std::pair<void *, proto_fn_type *>;
template<Ret(*Function)(Args...)>
static Ret proto(void *, Args... args) {
@@ -145,7 +145,7 @@ class Sink<Ret(Args...)> final {
return (static_cast<Class *>(instance)->*Member)(args...);
}
Sink(std::vector<call_type> &calls)
Sink(std::vector<call_type> &calls) ENTT_NOEXCEPT
: calls{calls}
{}
@@ -295,7 +295,7 @@ public:
*
* @return A temporary sink object.
*/
sink_type sink() {
sink_type sink() ENTT_NOEXCEPT {
return { calls };
}
@@ -306,7 +306,7 @@ public:
*
* @param args Arguments to use to invoke listeners.
*/
void publish(Args... args) {
void publish(Args... args) const {
for(auto pos = calls.size(); pos; --pos) {
auto &call = calls[pos-1];
call.second(call.first, args...);
@@ -318,7 +318,7 @@ public:
* @param args Arguments to use to invoke listeners.
* @return An instance of the collector filled with collected data.
*/
collector_type collect(Args... args) {
collector_type collect(Args... args) const {
collector_type collector;
for(auto &&call: calls) {

View File

@@ -4,9 +4,30 @@
add_library(odr OBJECT odr.cpp)
set_target_properties(odr PROPERTIES CXX_EXTENSIONS OFF)
target_include_directories(odr
PRIVATE $<TARGET_PROPERTY:EnTT,INTERFACE_INCLUDE_DIRECTORIES>
)
target_compile_definitions(odr
PRIVATE $<TARGET_PROPERTY:EnTT,INTERFACE_COMPILE_DEFINITIONS>
)
target_compile_options(odr
PRIVATE $<TARGET_PROPERTY:EnTT,INTERFACE_COMPILE_OPTIONS>
PRIVATE $<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-pedantic -Wall>
)
target_compile_features(odr
PRIVATE $<TARGET_PROPERTY:EnTT,INTERFACE_COMPILE_FEATURES>
)
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)
target_link_libraries(${TEST_NAME} PRIVATE EnTT gtest_main Threads::Threads)
set_target_properties(${TEST_NAME} PROPERTIES CXX_EXTENSIONS OFF)
target_compile_options(${TEST_NAME} PRIVATE $<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-pedantic -Wall>)
add_test(NAME ${TEST_NAME} COMMAND ${TEST_NAME})
endmacro()
@@ -26,7 +47,7 @@ if(BUILD_MOD)
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})
ADD_ENTT_TEST(mod "${MOD_TEST_SOURCE}")
target_include_directories(mod PRIVATE ${DUKTAPE_SRC_DIR})
endif()
@@ -49,10 +70,12 @@ 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)
ADD_ENTT_TEST(monostate entt/core/monostate.cpp)
# Test entity
ADD_ENTT_TEST(actor entt/entity/actor.cpp)
ADD_ENTT_TEST(entity entt/entity/entity.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)

View File

@@ -2,6 +2,7 @@
#include <cstddef>
#include <cstdint>
#include <chrono>
#include <iterator>
#include <gtest/gtest.h>
#include <entt/entity/registry.hpp>
@@ -135,6 +136,31 @@ TEST(Benchmark, IterateSingleComponentRaw1M) {
});
}
TEST(Benchmark, IterateSingleComponentRuntime1M) {
entt::DefaultRegistry registry;
std::cout << "Iterating over 1000000 entities, one component, runtime view" << std::endl;
for(std::uint64_t i = 0; i < 1000000L; i++) {
const auto entity = registry.create();
registry.assign<Position>(entity);
}
auto test = [&registry](auto func) {
using component_type = typename entt::DefaultRegistry::component_type;
component_type types[] = { registry.type<Position>() };
Timer timer;
registry.view(std::begin(types), std::end(types)).each(func);
timer.elapsed();
};
test([](auto) {});
test([&registry](auto entity) {
registry.get<Position>(entity).x = {};
});
}
TEST(Benchmark, IterateTwoComponents1M) {
entt::DefaultRegistry registry;
@@ -242,6 +268,94 @@ TEST(Benchmark, IterateTwoComponentsPersistent1M) {
});
}
TEST(Benchmark, IterateTwoComponentsRuntime1M) {
entt::DefaultRegistry registry;
registry.prepare<Position, Velocity>();
std::cout << "Iterating over 1000000 entities, two components, runtime view" << std::endl;
for(std::uint64_t i = 0; i < 1000000L; i++) {
const auto entity = registry.create();
registry.assign<Position>(entity);
registry.assign<Velocity>(entity);
}
auto test = [&registry](auto func) {
using component_type = typename entt::DefaultRegistry::component_type;
component_type types[] = { registry.type<Position>(), registry.type<Velocity>() };
Timer timer;
registry.view(std::begin(types), std::end(types)).each(func);
timer.elapsed();
};
test([](auto) {});
test([&registry](auto entity) {
registry.get<Position>(entity).x = {};
registry.get<Velocity>(entity).x = {};
});
}
TEST(Benchmark, IterateTwoComponentsRuntime1MHalf) {
entt::DefaultRegistry registry;
std::cout << "Iterating over 1000000 entities, two components, half of the entities have all the components, runtime view" << std::endl;
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);
}
}
auto test = [&registry](auto func) {
using component_type = typename entt::DefaultRegistry::component_type;
component_type types[] = { registry.type<Position>(), registry.type<Velocity>() };
Timer timer;
registry.view(std::begin(types), std::end(types)).each(func);
timer.elapsed();
};
test([](auto) {});
test([&registry](auto entity) {
registry.get<Position>(entity).x = {};
registry.get<Velocity>(entity).x = {};
});
}
TEST(Benchmark, IterateTwoComponentsRuntime1MOne) {
entt::DefaultRegistry registry;
std::cout << "Iterating over 1000000 entities, two components, only one entity has all the components, runtime view" << 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 = [&registry](auto func) {
using component_type = typename entt::DefaultRegistry::component_type;
component_type types[] = { registry.type<Position>(), registry.type<Velocity>() };
Timer timer;
registry.view(std::begin(types), std::end(types)).each(func);
timer.elapsed();
};
test([](auto) {});
test([&registry](auto entity) {
registry.get<Position>(entity).x = {};
registry.get<Velocity>(entity).x = {};
});
}
TEST(Benchmark, IterateFiveComponents1M) {
entt::DefaultRegistry registry;
@@ -361,6 +475,130 @@ TEST(Benchmark, IterateFiveComponentsPersistent1M) {
});
}
TEST(Benchmark, IterateFiveComponentsRuntime1M) {
entt::DefaultRegistry registry;
registry.prepare<Position, Velocity, Comp<1>, Comp<2>, Comp<3>>();
std::cout << "Iterating over 1000000 entities, five components, runtime view" << 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);
}
auto test = [&registry](auto func) {
using component_type = typename entt::DefaultRegistry::component_type;
component_type types[] = {
registry.type<Position>(),
registry.type<Velocity>(),
registry.type<Comp<1>>(),
registry.type<Comp<2>>(),
registry.type<Comp<3>>()
};
Timer timer;
registry.view(std::begin(types), std::end(types)).each(func);
timer.elapsed();
};
test([](auto) {});
test([&registry](auto entity) {
registry.get<Position>(entity).x = {};
registry.get<Velocity>(entity).x = {};
registry.get<Comp<1>>(entity).x = {};
registry.get<Comp<2>>(entity).x = {};
registry.get<Comp<3>>(entity).x = {};
});
}
TEST(Benchmark, IterateFiveComponentsRuntime1MHalf) {
entt::DefaultRegistry registry;
std::cout << "Iterating over 1000000 entities, five components, half of the entities have all the components, runtime view" << 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);
if(i % 2) {
registry.assign<Position>(entity);
}
}
auto test = [&registry](auto func) {
using component_type = typename entt::DefaultRegistry::component_type;
component_type types[] = {
registry.type<Position>(),
registry.type<Velocity>(),
registry.type<Comp<1>>(),
registry.type<Comp<2>>(),
registry.type<Comp<3>>()
};
Timer timer;
registry.view(std::begin(types), std::end(types)).each(func);
timer.elapsed();
};
test([](auto) {});
test([&registry](auto entity) {
registry.get<Position>(entity).x = {};
registry.get<Velocity>(entity).x = {};
registry.get<Comp<1>>(entity).x = {};
registry.get<Comp<2>>(entity).x = {};
registry.get<Comp<3>>(entity).x = {};
});
}
TEST(Benchmark, IterateFiveComponentsRuntime1MOne) {
entt::DefaultRegistry registry;
std::cout << "Iterating over 1000000 entities, five components, only one entity has all the components, runtime view" << 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);
if(i == 5000000L) {
registry.assign<Position>(entity);
}
}
auto test = [&registry](auto func) {
using component_type = typename entt::DefaultRegistry::component_type;
component_type types[] = {
registry.type<Position>(),
registry.type<Velocity>(),
registry.type<Comp<1>>(),
registry.type<Comp<2>>(),
registry.type<Comp<3>>()
};
Timer timer;
registry.view(std::begin(types), std::end(types)).each(func);
timer.elapsed();
};
test([](auto) {});
test([&registry](auto entity) {
registry.get<Position>(entity).x = {};
registry.get<Velocity>(entity).x = {};
registry.get<Comp<1>>(entity).x = {};
registry.get<Comp<2>>(entity).x = {};
registry.get<Comp<3>>(entity).x = {};
});
}
TEST(Benchmark, IterateTenComponents1M) {
entt::DefaultRegistry registry;
@@ -500,6 +738,175 @@ TEST(Benchmark, IterateTenComponentsPersistent1M) {
});
}
TEST(Benchmark, IterateTenComponentsRuntime1M) {
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 1000000 entities, ten components, runtime view" << 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 = [&registry](auto func) {
using component_type = typename entt::DefaultRegistry::component_type;
component_type types[] = {
registry.type<Position>(),
registry.type<Velocity>(),
registry.type<Comp<1>>(),
registry.type<Comp<2>>(),
registry.type<Comp<3>>(),
registry.type<Comp<4>>(),
registry.type<Comp<5>>(),
registry.type<Comp<6>>(),
registry.type<Comp<7>>(),
registry.type<Comp<8>>()
};
Timer timer;
registry.view(std::begin(types), std::end(types)).each(func);
timer.elapsed();
};
test([](auto) {});
test([&registry](auto entity) {
registry.get<Position>(entity).x = {};
registry.get<Velocity>(entity).x = {};
registry.get<Comp<1>>(entity).x = {};
registry.get<Comp<2>>(entity).x = {};
registry.get<Comp<3>>(entity).x = {};
registry.get<Comp<4>>(entity).x = {};
registry.get<Comp<5>>(entity).x = {};
registry.get<Comp<6>>(entity).x = {};
registry.get<Comp<7>>(entity).x = {};
registry.get<Comp<8>>(entity).x = {};
});
}
TEST(Benchmark, IterateTenComponentsRuntime1MHalf) {
entt::DefaultRegistry registry;
std::cout << "Iterating over 1000000 entities, ten components, half of the entities have all the components, runtime view" << 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 = [&registry](auto func) {
using component_type = typename entt::DefaultRegistry::component_type;
component_type types[] = {
registry.type<Position>(),
registry.type<Velocity>(),
registry.type<Comp<1>>(),
registry.type<Comp<2>>(),
registry.type<Comp<3>>(),
registry.type<Comp<4>>(),
registry.type<Comp<5>>(),
registry.type<Comp<6>>(),
registry.type<Comp<7>>(),
registry.type<Comp<8>>()
};
Timer timer;
registry.view(std::begin(types), std::end(types)).each(func);
timer.elapsed();
};
test([](auto) {});
test([&registry](auto entity) {
registry.get<Position>(entity).x = {};
registry.get<Velocity>(entity).x = {};
registry.get<Comp<1>>(entity).x = {};
registry.get<Comp<2>>(entity).x = {};
registry.get<Comp<3>>(entity).x = {};
registry.get<Comp<4>>(entity).x = {};
registry.get<Comp<5>>(entity).x = {};
registry.get<Comp<6>>(entity).x = {};
registry.get<Comp<7>>(entity).x = {};
registry.get<Comp<8>>(entity).x = {};
});
}
TEST(Benchmark, IterateTenComponentsRuntime1MOne) {
entt::DefaultRegistry registry;
std::cout << "Iterating over 1000000 entities, ten components, only one entity has all the components, runtime view" << 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 = [&registry](auto func) {
using component_type = typename entt::DefaultRegistry::component_type;
component_type types[] = {
registry.type<Position>(),
registry.type<Velocity>(),
registry.type<Comp<1>>(),
registry.type<Comp<2>>(),
registry.type<Comp<3>>(),
registry.type<Comp<4>>(),
registry.type<Comp<5>>(),
registry.type<Comp<6>>(),
registry.type<Comp<7>>(),
registry.type<Comp<8>>()
};
Timer timer;
registry.view(std::begin(types), std::end(types)).each(func);
timer.elapsed();
};
test([](auto) {});
test([&registry](auto entity) {
registry.get<Position>(entity).x = {};
registry.get<Velocity>(entity).x = {};
registry.get<Comp<1>>(entity).x = {};
registry.get<Comp<2>>(entity).x = {};
registry.get<Comp<3>>(entity).x = {};
registry.get<Comp<4>>(entity).x = {};
registry.get<Comp<5>>(entity).x = {};
registry.get<Comp<6>>(entity).x = {};
registry.get<Comp<7>>(entity).x = {};
registry.get<Comp<8>>(entity).x = {};
});
}
TEST(Benchmark, SortSingle) {
entt::DefaultRegistry registry;

View File

@@ -1,33 +1,7 @@
#include <cstddef>
#include <type_traits>
#include <gtest/gtest.h>
#include <entt/core/hashed_string.hpp>
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}
&& static_cast<const char *>(entt::HashedString{str}) == str
&& entt::HashedString{str} == entt::HashedString{str}
&& !(entt::HashedString{str} != entt::HashedString{str}));
}
TEST(HashedString, Constexprness) {
// how would you test a constexpr otherwise?
static_assert(ptr("foo"), "!");
static_assert(ref("bar"), "!");
ASSERT_TRUE(true);
}
TEST(HashedString, Functionalities) {
using hash_type = entt::HashedString::hash_type;
@@ -40,10 +14,21 @@ TEST(HashedString, Functionalities) {
ASSERT_EQ(static_cast<const char *>(fooHs), "foo");
ASSERT_EQ(static_cast<const char *>(barHs), bar);
ASSERT_TRUE(fooHs == fooHs);
ASSERT_TRUE(fooHs != barHs);
ASSERT_EQ(fooHs, fooHs);
ASSERT_NE(fooHs, barHs);
entt::HashedString hs{"foobar"};
ASSERT_EQ(static_cast<hash_type>(hs), 0x85944171f73967e8);
ASSERT_EQ(fooHs, "foo"_hs);
ASSERT_NE(barHs, "foo"_hs);
}
TEST(HashedString, Constexprness) {
using hash_type = entt::HashedString::hash_type;
// how would you test a constexpr otherwise?
(void)std::integral_constant<hash_type, entt::HashedString{"quux"}>{};
(void)std::integral_constant<hash_type, "quux"_hs>{};
ASSERT_TRUE(true);
}

View File

@@ -6,28 +6,28 @@ struct AType {};
struct AnotherType {};
TEST(Identifier, Uniqueness) {
constexpr auto ID = entt::ident<AType, AnotherType>;
using ID = entt::Identifier<AType, AnotherType>;
constexpr AType anInstance;
constexpr AnotherType anotherInstance;
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>());
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<AnotherType>()) {
case ID.get<AType>():
switch(ID::get<AnotherType>()) {
case ID::get<AType>():
FAIL();
break;
case ID.get<AnotherType>():
case ID::get<AnotherType>():
SUCCEED();
}
}
TEST(Identifier, SingleType) {
constexpr auto ID = entt::ident<AType>;
std::integral_constant<decltype(ID)::identifier_type, ID.get()> ic;
using ID = entt::Identifier<AType>;
std::integral_constant<ID::identifier_type, ID::get<AType>()> ic;
(void)ic;
}

View File

@@ -0,0 +1,20 @@
#include <gtest/gtest.h>
#include <entt/core/hashed_string.hpp>
#include <entt/core/monostate.hpp>
TEST(Monostate, Functionalities) {
const bool bPre = entt::Monostate<entt::HashedString{"foobar"}>{};
const int iPre = entt::Monostate<"foobar"_hs>{};
ASSERT_FALSE(bPre);
ASSERT_EQ(iPre, int{});
entt::Monostate<"foobar"_hs>{} = true;
entt::Monostate<"foobar"_hs>{} = 42;
const bool &bPost = entt::Monostate<"foobar"_hs>{};
const int &iPost = entt::Monostate<entt::HashedString{"foobar"}>{};
ASSERT_TRUE(bPost);
ASSERT_EQ(iPost, 42);
}

View File

@@ -0,0 +1,27 @@
#include <functional>
#include <gtest/gtest.h>
#include <entt/entity/entity.hpp>
#include <entt/entity/registry.hpp>
template<bool>
struct S {};
TEST(Traits, Null) {
entt::DefaultRegistry registry{};
const auto entity = registry.create();
registry.assign<int>(entity, 42);
ASSERT_TRUE(~typename entt::DefaultRegistry::entity_type{} == entt::null);
ASSERT_TRUE(entt::null == entt::null);
ASSERT_FALSE(entt::null != entt::null);
ASSERT_FALSE(entity == entt::null);
ASSERT_FALSE(entt::null == entity);
ASSERT_TRUE(entity != entt::null);
ASSERT_TRUE(entt::null != entity);
ASSERT_FALSE(registry.valid(entt::null));
}

View File

@@ -2,13 +2,13 @@
#include <entt/entity/prototype.hpp>
#include <entt/entity/registry.hpp>
TEST(Prototype, Functionalities) {
TEST(Prototype, SameRegistry) {
entt::DefaultRegistry registry;
entt::DefaultPrototype prototype;
entt::DefaultPrototype prototype{registry};
const auto &cprototype = prototype;
ASSERT_FALSE(registry.empty());
ASSERT_FALSE((prototype.has<int, char>()));
ASSERT_TRUE(registry.empty());
ASSERT_EQ(prototype.set<int>(2), 2);
ASSERT_EQ(prototype.set<int>(3), 3);
@@ -19,11 +19,70 @@ TEST(Prototype, Functionalities) {
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);
const auto e0 = prototype.create();
ASSERT_TRUE((prototype.has<int, char>()));
ASSERT_FALSE(registry.orphan(e0));
const auto e1 = prototype();
prototype(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(e0);
prototype(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(e0);
ASSERT_EQ(registry.get<char>(e0), '*');
registry.get<char>(e1) = '*';
prototype.accommodate(e1);
ASSERT_EQ(registry.get<char>(e1), 'c');
}
TEST(Prototype, OtherRegistry) {
entt::DefaultRegistry registry;
entt::DefaultRegistry repository;
entt::DefaultPrototype prototype{repository};
const auto &cprototype = prototype;
ASSERT_TRUE(registry.empty());
ASSERT_FALSE((prototype.has<int, char>()));
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.create(registry);
ASSERT_TRUE((prototype.has<int, char>()));
ASSERT_FALSE(registry.orphan(e0));
ASSERT_FALSE(registry.empty());
const auto e1 = prototype(registry);
prototype(registry, e0);
@@ -61,3 +120,34 @@ TEST(Prototype, Functionalities) {
ASSERT_EQ(registry.get<char>(e1), 'c');
}
TEST(Prototype, RAII) {
entt::DefaultRegistry registry;
{
entt::DefaultPrototype prototype{registry};
prototype.set<int>(0);
ASSERT_FALSE(registry.empty());
}
ASSERT_TRUE(registry.empty());
}
TEST(Prototype, MoveConstructionAssignment) {
entt::DefaultRegistry registry;
entt::DefaultPrototype prototype{registry};
prototype.set<int>(0);
auto other{std::move(prototype)};
const auto e0 = other();
ASSERT_EQ(registry.size(), entt::DefaultRegistry::size_type{2});
ASSERT_TRUE(registry.has<int>(e0));
prototype = std::move(other);
const auto e1 = prototype();
ASSERT_EQ(registry.size(), entt::DefaultRegistry::size_type{3});
ASSERT_TRUE(registry.has<int>(e1));
}

View File

@@ -64,7 +64,9 @@ TEST(DefaultRegistry, Functionalities) {
ASSERT_NO_THROW(registry.reserve<char>(8));
ASSERT_TRUE(registry.empty());
ASSERT_EQ(registry.capacity(), entt::DefaultRegistry::size_type{0});
ASSERT_EQ(registry.capacity(), entt::DefaultRegistry::size_type{42});
ASSERT_EQ(registry.capacity<int>(), entt::DefaultRegistry::size_type{8});
ASSERT_EQ(registry.capacity<char>(), entt::DefaultRegistry::size_type{8});
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>());
@@ -79,7 +81,6 @@ TEST(DefaultRegistry, Functionalities) {
ASSERT_TRUE(registry.has<>(e0));
ASSERT_TRUE(registry.has<>(e1));
ASSERT_EQ(registry.capacity(), entt::DefaultRegistry::size_type{2});
ASSERT_EQ(registry.size<int>(), entt::DefaultRegistry::size_type{1});
ASSERT_EQ(registry.size<char>(), entt::DefaultRegistry::size_type{1});
ASSERT_FALSE(registry.empty<int>());
@@ -137,9 +138,7 @@ TEST(DefaultRegistry, Functionalities) {
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(e2));
ASSERT_EQ(registry.capacity(), entt::DefaultRegistry::size_type{3});
ASSERT_EQ(registry.version(e2), entt::DefaultRegistry::version_type{0});
ASSERT_EQ(registry.current(e2), entt::DefaultRegistry::version_type{1});
@@ -354,23 +353,6 @@ TEST(DefaultRegistry, CreateDestroyEntities) {
ASSERT_EQ(registry.current(pre), registry.current(post));
}
TEST(DefaultRegistry, CloneEntities) {
entt::DefaultRegistry registry;
const auto entity = registry.create();
registry.assign<int>(entity, 42);
registry.assign<char>(entity, 'c');
const auto other = registry.clone(entity);
ASSERT_TRUE(registry.has<int>(other));
ASSERT_TRUE(registry.has<char>(other));
ASSERT_EQ(registry.get<int>(entity), registry.get<int>(other));
ASSERT_EQ(registry.get<char>(entity), registry.get<char>(other));
ASSERT_EQ(registry.get<int>(other), 42);
ASSERT_EQ(registry.get<char>(other), 'c');
}
TEST(DefaultRegistry, AttachSetRemoveTags) {
entt::DefaultRegistry registry;
const auto &cregistry = registry;
@@ -719,11 +701,6 @@ TEST(DefaultRegistry, ComponentSignals) {
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) {
@@ -763,3 +740,54 @@ TEST(DefaultRegistry, TagSignals) {
ASSERT_EQ(listener.counter, 0);
ASSERT_EQ(listener.last, e0);
}
TEST(DefaultRegistry, DestroyByTagAndComponents) {
entt::DefaultRegistry registry;
const auto e0 = registry.create();
const auto e1 = registry.create();
const auto e2 = registry.create();
const auto e3 = registry.create();
registry.assign<int>(e0);
registry.assign<char>(e0);
registry.assign<double>(e0);
registry.assign<int>(e1);
registry.assign<char>(e1);
registry.assign<int>(e2);
registry.assign<float>(entt::tag_t{}, e3);
ASSERT_TRUE(registry.valid(e0));
ASSERT_TRUE(registry.valid(e1));
ASSERT_TRUE(registry.valid(e2));
ASSERT_TRUE(registry.valid(e3));
registry.destroy<int, char, double>(entt::persistent_t{});
ASSERT_FALSE(registry.valid(e0));
ASSERT_TRUE(registry.valid(e1));
ASSERT_TRUE(registry.valid(e2));
ASSERT_TRUE(registry.valid(e3));
registry.destroy<int, char>();
ASSERT_FALSE(registry.valid(e0));
ASSERT_FALSE(registry.valid(e1));
ASSERT_TRUE(registry.valid(e2));
ASSERT_TRUE(registry.valid(e3));
registry.destroy<int>();
ASSERT_FALSE(registry.valid(e0));
ASSERT_FALSE(registry.valid(e1));
ASSERT_FALSE(registry.valid(e2));
ASSERT_TRUE(registry.valid(e3));
registry.destroy<int>(entt::tag_t{});
registry.destroy<char>(entt::tag_t{});
registry.destroy<double>(entt::tag_t{});
registry.destroy<float>(entt::tag_t{});
}

View File

@@ -3,10 +3,12 @@
#include <entt/entity/sparse_set.hpp>
TEST(SparseSetNoType, Functionalities) {
entt::SparseSet<unsigned int> set;
entt::SparseSet<std::uint64_t> set;
const auto &cset = set;
ASSERT_NO_THROW(set.reserve(42));
set.reserve(42);
ASSERT_EQ(set.capacity(), 42);
ASSERT_TRUE(set.empty());
ASSERT_EQ(set.size(), 0u);
ASSERT_EQ(cset.begin(), cset.end());
@@ -49,30 +51,116 @@ TEST(SparseSetNoType, Functionalities) {
ASSERT_FALSE(set.has(0));
ASSERT_FALSE(set.has(42));
(void)entt::SparseSet<unsigned int>{std::move(set)};
entt::SparseSet<unsigned int> other;
(void)entt::SparseSet<std::uint64_t>{std::move(set)};
entt::SparseSet<std::uint64_t> other;
other = std::move(set);
}
TEST(SparseSetNoType, Clone) {
entt::SparseSet<unsigned int> set;
TEST(SparseSetNoType, ElementAccess) {
entt::SparseSet<std::uint64_t> set;
const auto &cset = set;
ASSERT_FALSE(set.has(0));
ASSERT_FALSE(set.has(42));
set.construct(42);
set.construct(3);
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));
for(typename entt::SparseSet<std::uint64_t>::size_type i{}; i < set.size(); ++i) {
ASSERT_EQ(set[i], i ? 42 : 3);
ASSERT_EQ(cset[i], i ? 42 : 3);
}
}
TEST(SparseSetNoType, DataBeginEnd) {
entt::SparseSet<unsigned int> set;
TEST(SparseSetNoType, Iterator) {
using iterator_type = typename entt::SparseSet<std::uint64_t>::iterator_type;
entt::SparseSet<std::uint64_t> set;
set.construct(3);
iterator_type end{set.begin()};
iterator_type begin{};
begin = set.end();
std::swap(begin, end);
ASSERT_EQ(begin, set.begin());
ASSERT_EQ(end, set.end());
ASSERT_NE(begin, end);
ASSERT_EQ(begin++, set.begin());
ASSERT_EQ(begin--, set.end());
ASSERT_EQ(begin+1, set.end());
ASSERT_EQ(end-1, set.begin());
ASSERT_EQ(++begin, set.end());
ASSERT_EQ(--begin, set.begin());
ASSERT_EQ(begin += 1, set.end());
ASSERT_EQ(begin -= 1, set.begin());
ASSERT_EQ(begin + (end - begin), set.end());
ASSERT_EQ(begin - (begin - end), set.end());
ASSERT_EQ(end - (end - begin), set.begin());
ASSERT_EQ(end + (begin - end), set.begin());
ASSERT_EQ(begin[0], *set.begin());
ASSERT_LT(begin, end);
ASSERT_LE(begin, set.begin());
ASSERT_GT(end, begin);
ASSERT_GE(end, set.end());
ASSERT_EQ(*begin, 3);
ASSERT_EQ(*begin.operator->(), 3);
}
TEST(SparseSetNoType, ConstIterator) {
using iterator_type = typename entt::SparseSet<std::uint64_t>::const_iterator_type;
entt::SparseSet<std::uint64_t> set;
set.construct(3);
iterator_type cend{set.cbegin()};
iterator_type cbegin{};
cbegin = set.cend();
std::swap(cbegin, cend);
ASSERT_EQ(cbegin, set.cbegin());
ASSERT_EQ(cend, set.cend());
ASSERT_NE(cbegin, cend);
ASSERT_EQ(cbegin++, set.cbegin());
ASSERT_EQ(cbegin--, set.cend());
ASSERT_EQ(cbegin+1, set.cend());
ASSERT_EQ(cend-1, set.cbegin());
ASSERT_EQ(++cbegin, set.cend());
ASSERT_EQ(--cbegin, set.cbegin());
ASSERT_EQ(cbegin += 1, set.cend());
ASSERT_EQ(cbegin -= 1, set.cbegin());
ASSERT_EQ(cbegin + (cend - cbegin), set.cend());
ASSERT_EQ(cbegin - (cbegin - cend), set.cend());
ASSERT_EQ(cend - (cend - cbegin), set.cbegin());
ASSERT_EQ(cend + (cbegin - cend), set.cbegin());
ASSERT_EQ(cbegin[0], *set.cbegin());
ASSERT_LT(cbegin, cend);
ASSERT_LE(cbegin, set.cbegin());
ASSERT_GT(cend, cbegin);
ASSERT_GE(cend, set.cend());
ASSERT_EQ(*cbegin, 3);
ASSERT_EQ(*cbegin.operator->(), 3);
}
TEST(SparseSetNoType, Data) {
entt::SparseSet<std::uint64_t> set;
set.construct(3);
set.construct(12);
@@ -85,36 +173,11 @@ TEST(SparseSetNoType, DataBeginEnd) {
ASSERT_EQ(*(set.data() + 0u), 3u);
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(SparseSetNoType, RespectDisjoint) {
entt::SparseSet<unsigned int> lhs;
entt::SparseSet<unsigned int> rhs;
entt::SparseSet<std::uint64_t> lhs;
entt::SparseSet<std::uint64_t> rhs;
const auto &clhs = lhs;
lhs.construct(3);
@@ -133,8 +196,8 @@ TEST(SparseSetNoType, RespectDisjoint) {
}
TEST(SparseSetNoType, RespectOverlap) {
entt::SparseSet<unsigned int> lhs;
entt::SparseSet<unsigned int> rhs;
entt::SparseSet<std::uint64_t> lhs;
entt::SparseSet<std::uint64_t> rhs;
const auto &clhs = lhs;
lhs.construct(3);
@@ -155,8 +218,8 @@ TEST(SparseSetNoType, RespectOverlap) {
}
TEST(SparseSetNoType, RespectOrdered) {
entt::SparseSet<unsigned int> lhs;
entt::SparseSet<unsigned int> rhs;
entt::SparseSet<std::uint64_t> lhs;
entt::SparseSet<std::uint64_t> rhs;
lhs.construct(1);
lhs.construct(2);
@@ -195,8 +258,8 @@ TEST(SparseSetNoType, RespectOrdered) {
}
TEST(SparseSetNoType, RespectReverse) {
entt::SparseSet<unsigned int> lhs;
entt::SparseSet<unsigned int> rhs;
entt::SparseSet<std::uint64_t> lhs;
entt::SparseSet<std::uint64_t> rhs;
lhs.construct(1);
lhs.construct(2);
@@ -235,8 +298,8 @@ TEST(SparseSetNoType, RespectReverse) {
}
TEST(SparseSetNoType, RespectUnordered) {
entt::SparseSet<unsigned int> lhs;
entt::SparseSet<unsigned int> rhs;
entt::SparseSet<std::uint64_t> lhs;
entt::SparseSet<std::uint64_t> rhs;
lhs.construct(1);
lhs.construct(2);
@@ -275,10 +338,12 @@ TEST(SparseSetNoType, RespectUnordered) {
}
TEST(SparseSetWithType, Functionalities) {
entt::SparseSet<unsigned int, int> set;
entt::SparseSet<std::uint64_t, int> set;
const auto &cset = set;
ASSERT_NO_THROW(set.reserve(42));
set.reserve(42);
ASSERT_EQ(set.capacity(), 42);
ASSERT_TRUE(set.empty());
ASSERT_EQ(set.size(), 0u);
ASSERT_EQ(cset.begin(), cset.end());
@@ -319,46 +384,127 @@ TEST(SparseSetWithType, Functionalities) {
ASSERT_FALSE(set.has(0));
ASSERT_FALSE(set.has(42));
(void)entt::SparseSet<unsigned int>{std::move(set)};
entt::SparseSet<unsigned int> other;
(void)entt::SparseSet<std::uint64_t>{std::move(set)};
entt::SparseSet<std::uint64_t> other;
other = std::move(set);
}
TEST(SparseSetWithType, Clone) {
entt::SparseSet<unsigned int, int> set;
TEST(SparseSetWithType, ElementAccess) {
entt::SparseSet<std::uint64_t, int> set;
const auto &cset = set;
ASSERT_FALSE(set.has(0));
ASSERT_FALSE(set.has(42));
set.construct(42, 1);
set.construct(3, 0);
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);
for(typename entt::SparseSet<std::uint64_t, int>::size_type i{}; i < set.size(); ++i) {
ASSERT_EQ(set[i], i);
ASSERT_EQ(cset[i], i);
}
}
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);
entt::SparseSet<std::uint64_t, 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;
entt::SparseSet<std::uint64_t, std::unordered_set<int>> set;
set.construct(0).insert(42);
set.destroy(0);
}
TEST(SparseSetWithType, RawBeginEnd) {
entt::SparseSet<unsigned int, int> set;
TEST(SparseSetWithType, Iterator) {
struct InternalType { int value; };
using iterator_type = typename entt::SparseSet<std::uint64_t, InternalType>::iterator_type;
entt::SparseSet<std::uint64_t, InternalType> set;
set.construct(3, 42);
iterator_type end{set.begin()};
iterator_type begin{};
begin = set.end();
std::swap(begin, end);
ASSERT_EQ(begin, set.begin());
ASSERT_EQ(end, set.end());
ASSERT_NE(begin, end);
ASSERT_EQ(begin++, set.begin());
ASSERT_EQ(begin--, set.end());
ASSERT_EQ(begin+1, set.end());
ASSERT_EQ(end-1, set.begin());
ASSERT_EQ(++begin, set.end());
ASSERT_EQ(--begin, set.begin());
ASSERT_EQ(begin += 1, set.end());
ASSERT_EQ(begin -= 1, set.begin());
ASSERT_EQ(begin + (end - begin), set.end());
ASSERT_EQ(begin - (begin - end), set.end());
ASSERT_EQ(end - (end - begin), set.begin());
ASSERT_EQ(end + (begin - end), set.begin());
ASSERT_EQ(begin[0].value, set.begin()->value);
ASSERT_LT(begin, end);
ASSERT_LE(begin, set.begin());
ASSERT_GT(end, begin);
ASSERT_GE(end, set.end());
}
TEST(SparseSetWithType, ConstIterator) {
struct InternalType { int value; };
using iterator_type = typename entt::SparseSet<std::uint64_t, InternalType>::const_iterator_type;
entt::SparseSet<std::uint64_t, InternalType> set;
set.construct(3, 42);
iterator_type cend{set.cbegin()};
iterator_type cbegin{};
cbegin = set.cend();
std::swap(cbegin, cend);
ASSERT_EQ(cbegin, set.cbegin());
ASSERT_EQ(cend, set.cend());
ASSERT_NE(cbegin, cend);
ASSERT_EQ(cbegin++, set.cbegin());
ASSERT_EQ(cbegin--, set.cend());
ASSERT_EQ(cbegin+1, set.cend());
ASSERT_EQ(cend-1, set.cbegin());
ASSERT_EQ(++cbegin, set.cend());
ASSERT_EQ(--cbegin, set.cbegin());
ASSERT_EQ(cbegin += 1, set.cend());
ASSERT_EQ(cbegin -= 1, set.cbegin());
ASSERT_EQ(cbegin + (cend - cbegin), set.cend());
ASSERT_EQ(cbegin - (cbegin - cend), set.cend());
ASSERT_EQ(cend - (cend - cbegin), set.cbegin());
ASSERT_EQ(cend + (cbegin - cend), set.cbegin());
ASSERT_EQ(cbegin[0].value, set.cbegin()->value);
ASSERT_LT(cbegin, cend);
ASSERT_LE(cbegin, set.cbegin());
ASSERT_GT(cend, cbegin);
ASSERT_GE(cend, set.cend());
}
TEST(SparseSetWithType, Raw) {
entt::SparseSet<std::uint64_t, int> set;
set.construct(3, 3);
set.construct(12, 6);
@@ -371,27 +517,10 @@ TEST(SparseSetWithType, RawBeginEnd) {
ASSERT_EQ(*(set.raw() + 0u), 3);
ASSERT_EQ(*(set.raw() + 1u), 6);
ASSERT_EQ(*(set.raw() + 2u), 9);
auto begin = set.begin();
auto end = set.end();
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) {
entt::SparseSet<unsigned int, int> set;
entt::SparseSet<std::uint64_t, int> set;
set.construct(12, 12);
set.construct(42, 9);
@@ -427,7 +556,7 @@ TEST(SparseSetWithType, SortOrdered) {
}
TEST(SparseSetWithType, SortReverse) {
entt::SparseSet<unsigned int, int> set;
entt::SparseSet<std::uint64_t, int> set;
set.construct(12, 1);
set.construct(42, 3);
@@ -463,7 +592,7 @@ TEST(SparseSetWithType, SortReverse) {
}
TEST(SparseSetWithType, SortUnordered) {
entt::SparseSet<unsigned int, int> set;
entt::SparseSet<std::uint64_t, int> set;
set.construct(12, 6);
set.construct(42, 3);
@@ -499,8 +628,8 @@ TEST(SparseSetWithType, SortUnordered) {
}
TEST(SparseSetWithType, RespectDisjoint) {
entt::SparseSet<unsigned int, int> lhs;
entt::SparseSet<unsigned int, int> rhs;
entt::SparseSet<std::uint64_t, int> lhs;
entt::SparseSet<std::uint64_t, int> rhs;
const auto &clhs = lhs;
lhs.construct(3, 3);
@@ -527,8 +656,8 @@ TEST(SparseSetWithType, RespectDisjoint) {
}
TEST(SparseSetWithType, RespectOverlap) {
entt::SparseSet<unsigned int, int> lhs;
entt::SparseSet<unsigned int, int> rhs;
entt::SparseSet<std::uint64_t, int> lhs;
entt::SparseSet<std::uint64_t, int> rhs;
const auto &clhs = lhs;
lhs.construct(3, 3);
@@ -557,8 +686,8 @@ TEST(SparseSetWithType, RespectOverlap) {
}
TEST(SparseSetWithType, RespectOrdered) {
entt::SparseSet<unsigned int, int> lhs;
entt::SparseSet<unsigned int, int> rhs;
entt::SparseSet<std::uint64_t, int> lhs;
entt::SparseSet<std::uint64_t, int> rhs;
lhs.construct(1, 0);
lhs.construct(2, 0);
@@ -603,8 +732,8 @@ TEST(SparseSetWithType, RespectOrdered) {
}
TEST(SparseSetWithType, RespectReverse) {
entt::SparseSet<unsigned int, int> lhs;
entt::SparseSet<unsigned int, int> rhs;
entt::SparseSet<std::uint64_t, int> lhs;
entt::SparseSet<std::uint64_t, int> rhs;
lhs.construct(1, 0);
lhs.construct(2, 0);
@@ -649,8 +778,8 @@ TEST(SparseSetWithType, RespectReverse) {
}
TEST(SparseSetWithType, RespectUnordered) {
entt::SparseSet<unsigned int, int> lhs;
entt::SparseSet<unsigned int, int> rhs;
entt::SparseSet<std::uint64_t, int> lhs;
entt::SparseSet<std::uint64_t, int> rhs;
lhs.construct(1, 0);
lhs.construct(2, 0);
@@ -697,7 +826,7 @@ TEST(SparseSetWithType, RespectUnordered) {
TEST(SparseSetWithType, ReferencesGuaranteed) {
struct InternalType { int value; };
entt::SparseSet<unsigned int, InternalType> set;
entt::SparseSet<std::uint64_t, InternalType> set;
set.construct(0, 0);
set.construct(1, 1);
@@ -723,3 +852,18 @@ TEST(SparseSetWithType, ReferencesGuaranteed) {
ASSERT_EQ(set.get(0).value, 3);
ASSERT_EQ(set.get(1).value, 3);
}
TEST(SparseSetWithType, MoveOnlyComponent) {
struct MoveOnlyComponent {
MoveOnlyComponent() = default;
~MoveOnlyComponent() = default;
MoveOnlyComponent(const MoveOnlyComponent &) = delete;
MoveOnlyComponent(MoveOnlyComponent &&) = default;
MoveOnlyComponent & operator=(const MoveOnlyComponent &) = delete;
MoveOnlyComponent & operator=(MoveOnlyComponent &&) = default;
};
// it's purpose is to ensure that move only components are always accepted
entt::SparseSet<std::uint64_t, MoveOnlyComponent> set;
(void)set;
}

File diff suppressed because it is too large Load Diff

View File

@@ -4,6 +4,7 @@
struct AService {};
struct AnotherService {
virtual ~AnotherService() = default;
virtual void f(bool) = 0;
bool check{false};
};

View File

@@ -16,20 +16,23 @@ TEST(Delegate, Functionalities) {
entt::Delegate<int(int)> mfdel;
DelegateFunctor functor;
ASSERT_EQ(ffdel(42), int{});
ASSERT_EQ(mfdel(42), int{});
ASSERT_TRUE(ffdel.empty());
ASSERT_TRUE(mfdel.empty());
ffdel.connect<&delegateFunction>();
mfdel.connect<DelegateFunctor, &DelegateFunctor::operator()>(&functor);
ASSERT_FALSE(ffdel.empty());
ASSERT_FALSE(mfdel.empty());
ASSERT_EQ(ffdel(3), 9);
ASSERT_EQ(mfdel(3), 6);
ffdel.reset();
mfdel.reset();
ASSERT_EQ(ffdel(42), int{});
ASSERT_EQ(mfdel(42), int{});
ASSERT_TRUE(ffdel.empty());
ASSERT_TRUE(mfdel.empty());
}
TEST(Delegate, Comparison) {

View File

@@ -235,34 +235,41 @@ public:
duk_push_array(ctx);
dreg.registry.each([ctx, nargs, &pos, &dreg](auto entity) {
auto &registry = dreg.registry;
auto &func = dreg.func;
bool match = true;
std::vector<typename entt::DefaultRegistry::component_type> components;
std::vector<typename entt::DefaultRegistry::component_type> runtime;
for (duk_idx_t arg = 0; match && arg < nargs; arg++) {
auto type = duk_require_uint(ctx, arg);
for(duk_idx_t arg = 0; arg < nargs; arg++) {
auto type = duk_require_uint(ctx, arg);
if(type < udef) {
assert(func.find(type) != func.cend());
match = (registry.*func[type].test)(entity);
} else {
const auto ctype = registry.type<DuktapeRuntime>();
assert(func.find(ctype) != func.cend());
match = (registry.*func[ctype].test)(entity);
if(match) {
auto &components = registry.get<DuktapeRuntime>(entity).components;
match = (components.find(type) != components.cend());
}
if(type < udef) {
components.push_back(type);
} else {
if(runtime.empty()) {
components.push_back(dreg.registry.type<DuktapeRuntime>());
}
}
if(match) {
runtime.push_back(type);
}
}
auto view = dreg.registry.view(components.cbegin(), components.cend());
for(const auto entity: view) {
if(runtime.empty()) {
duk_push_uint(ctx, entity);
duk_put_prop_index(ctx, -2, pos++);
} else {
const auto &components = dreg.registry.get<DuktapeRuntime>(entity).components;
const auto match = std::all_of(runtime.cbegin(), runtime.cend(), [&components](const auto type) {
return components.find(type) != components.cend();
});
if(match) {
duk_push_uint(ctx, entity);
duk_put_prop_index(ctx, -2, pos++);
}
}
});
}
return 1;
}