Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3b4c025743 | ||
|
|
df065c5647 | ||
|
|
21380aacb8 | ||
|
|
5884b1cec5 | ||
|
|
05860fcf9b | ||
|
|
2916aaeda8 | ||
|
|
ec9a221a8f | ||
|
|
97d8ad7fc6 | ||
|
|
adfb4cd694 | ||
|
|
34374afd36 | ||
|
|
b29949c0fa | ||
|
|
d26e163f66 | ||
|
|
e47fb67d74 | ||
|
|
b328c49899 | ||
|
|
9f3b602c0a | ||
|
|
994a68eb81 | ||
|
|
bc256a989e | ||
|
|
2aeec2d50f | ||
|
|
03afa7652b | ||
|
|
6791cf1e2e | ||
|
|
e69b90ef1a | ||
|
|
6c925a32b4 | ||
|
|
8d3f381a5a | ||
|
|
02c424bd21 | ||
|
|
c324ee310a | ||
|
|
fe7250bfc1 | ||
|
|
63c1b046e0 | ||
|
|
ed98ae3e70 | ||
|
|
2011defb1e | ||
|
|
b35737b5d7 | ||
|
|
aeaf1632c8 | ||
|
|
af70573634 |
@@ -16,7 +16,7 @@ endif()
|
||||
# Project configuration
|
||||
#
|
||||
|
||||
project(entt VERSION 1.0.0)
|
||||
project(entt VERSION 1.1.0)
|
||||
|
||||
if(NOT CMAKE_BUILD_TYPE)
|
||||
set(CMAKE_BUILD_TYPE Debug)
|
||||
@@ -38,10 +38,21 @@ message("*")
|
||||
|
||||
set(CMAKE_CXX_STANDARD 14)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pedantic -Wall -Wconversion")
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0 -g -DDEBUG")
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3 -DRELEASE")
|
||||
|
||||
if(NOT MSVC)
|
||||
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pedantic -Wall -Wconversion")
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -DRELEASE")
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0 -g -DDEBUG")
|
||||
|
||||
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()
|
||||
endif()
|
||||
|
||||
#
|
||||
# CMake configuration
|
||||
|
||||
558
README.md
558
README.md
@@ -3,11 +3,12 @@
|
||||
[](https://travis-ci.org/skypjack/uvw)
|
||||
[](https://ci.appveyor.com/project/skypjack/entt)
|
||||
[](https://coveralls.io/github/skypjack/entt?branch=master)
|
||||
[](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=W2HF9FESD5LJY&lc=IT&item_name=Michele%20Caini¤cy_code=EUR&bn=PP%2dDonationsBF%3abtn_donateCC_LG%2egif%3aNonHosted)
|
||||
|
||||
# Introduction
|
||||
|
||||
`EnTT` is a header-only, tiny and easy to use Entity-Component System in modern C++.<br/>
|
||||
ECS is an architectural pattern used mostly in game development. For further details:
|
||||
_ECS_ is an architectural pattern used mostly in game development. For further details:
|
||||
|
||||
* [Entity Systems Wiki](http://entity-systems.wikidot.com/)
|
||||
* [Evolve Your Hierarchy](http://cowboyprogramming.com/2007/01/05/evolve-your-heirachy/)
|
||||
@@ -16,7 +17,6 @@ ECS is an architectural pattern used mostly in game development. For further det
|
||||
## Code Example
|
||||
|
||||
```cpp
|
||||
#include <iostream>
|
||||
#include <registry.hpp>
|
||||
|
||||
struct Position {
|
||||
@@ -40,38 +40,19 @@ int main() {
|
||||
if(i % 2 == 0) { ecs.assign<Velocity>(entity, i * .1f, i * .1f); }
|
||||
}
|
||||
|
||||
std::cout << "single component view" << std::endl;
|
||||
// single component view
|
||||
|
||||
for(auto entity: ecs.view<Position>()) {
|
||||
auto &position = ecs.get<Position>(entity);
|
||||
std::cout << position.x << "," << position.y << std::endl;
|
||||
// ...
|
||||
}
|
||||
|
||||
std::cout << "multi component view" << std::endl;
|
||||
// multi component view
|
||||
|
||||
for(auto entity: ecs.view<Position, Velocity>()) {
|
||||
auto &position = ecs.get<Position>(entity);
|
||||
auto &velocity = ecs.get<Velocity>(entity);
|
||||
std::cout << position.x << "," << position.y << " - " << velocity.dx << "," << velocity.dy << std::endl;
|
||||
if(entity % 4) { ecs.remove<Velocity>(entity); }
|
||||
else { ecs.destroy(entity); }
|
||||
}
|
||||
|
||||
std::cout << "single component view" << std::endl;
|
||||
|
||||
for(auto entity: ecs.view<Position>()) {
|
||||
auto &position = ecs.get<Position>(entity);
|
||||
std::cout << position.x << "," << position.y << std::endl;
|
||||
}
|
||||
|
||||
std::cout << "multi component view" << std::endl;
|
||||
|
||||
for(auto entity: ecs.view<Position, Velocity>()) {
|
||||
auto &position = ecs.get<Position>(entity);
|
||||
auto &velocity = ecs.get<Velocity>(entity);
|
||||
std::cout << position.x << "," << position.y << " - " << velocity.dx << "," << velocity.dy << std::endl;
|
||||
if(entity % 4) { ecs.remove<Velocity>(entity); }
|
||||
else { ecs.destroy(entity); }
|
||||
// ...
|
||||
}
|
||||
|
||||
ecs.reset();
|
||||
@@ -80,59 +61,44 @@ int main() {
|
||||
|
||||
## Motivation
|
||||
|
||||
I started using another well known Entity-Component System named [`EntityX`](https://github.com/alecthomas/entityx).<br/>
|
||||
While I was playing with it, I found that I didn't like that much the way it manages its memory.
|
||||
Moreover, I was pretty sure that one could achieve better performance with a slightly modified pool under the hood.<br/>
|
||||
That's also the reason for which the interface is quite similar to the one of `EntityX`, so that `EnTT` can be used as a
|
||||
drop-in replacement for it with a minimal effort.
|
||||
I started working on `EnTT` because of the wrong reason: my goal was to beat another well known open source _ECS_ in terms of performance.
|
||||
I did it, of course, but it wasn't much satisfying. Actually it wasn't satisfying at all. The fastest and nothing more, fairly little indeed.
|
||||
When I realized it, I tried hard to keep intact the great performance and to add all the features I want to see in my _ECS_ at the same time.
|
||||
|
||||
Today `EnTT` is finally what I was looking for: still faster than its _rivals_, a really good API and an amazing set of features.
|
||||
|
||||
### Performance
|
||||
|
||||
As it stands right now, `EnTT` is just fast enough for my requirements if compared to my first choice (that was already
|
||||
amazingly fast).
|
||||
These are the results of the twos when compiled with GCC 6.3:
|
||||
As it stands right now, `EnTT` is just fast enough for my requirements if compared to my first choice (that was already amazingly fast indeed).<br/>
|
||||
Here is a comparision between the two (both of them compiled with GCC 7.2.0 on a Dell XPS 13 out of the mid 2014):
|
||||
|
||||
| Benchmark | EntityX (experimental/compile_time) | EnTT |
|
||||
|-----------|-------------|-------------|
|
||||
| Creating 10M entities | 0.187042s | **0.0928331s** |
|
||||
| Destroying 10M entities | 0.0735151s | **0.060166s** |
|
||||
| Iterating over 10M entities, unpacking one component | 0.00784801s | **1.02e-07s** |
|
||||
| Iterating over 10M entities, unpacking two components | 0.00865273s | **0.00326714s** |
|
||||
| Iterating over 10M entities, unpacking five components | 0.0122006s | **0.00323354s** |
|
||||
| Iterating over 10M entities, unpacking ten components | 0.0100089s | **0.00323615s** |
|
||||
| Iterating over 50M entities, unpacking one component | 0.0394404s | **1.14e-07s** |
|
||||
| Iterating over 50M entities, unpacking two components | 0.0400407s | **0.0179783s** |
|
||||
| Creating 10M entities | 0.177225s | **0.0881921s** |
|
||||
| Destroying 10M entities | 0.066419s | **0.0552661s** |
|
||||
| Iterating over 10M entities, unpacking one component | 0.0104935s | **8.8e-08s** |
|
||||
| Iterating over 10M entities, unpacking two components | 0.00835546s | **0.00323798s** |
|
||||
| Iterating over 10M entities, unpacking two components, half of the entities have all the components | 0.00772169s | **0.00162265s** |
|
||||
| Iterating over 10M entities, unpacking two components, one of the entities has all the components | 0.00751099s | **5.17e-07s** |
|
||||
| Iterating over 10M entities, unpacking five components | 0.00863762s | **0.00323384s** |
|
||||
| Iterating over 10M entities, unpacking ten components | 0.0105657s | **0.00323742s** |
|
||||
| Iterating over 10M entities, unpacking ten components, half of the entities have all the components | 0.00880251s | **0.00164593s** |
|
||||
| Iterating over 10M entities, unpacking ten components, one of the entities has all the components | 0.0067667s | **5.38e-07s** |
|
||||
| Iterating over 50M entities, unpacking one component | 0.0530271s | **7.7e-08s** |
|
||||
| Iterating over 50M entities, unpacking two components | 0.056233s | **0.0161715s** |
|
||||
|
||||
These are the results of the twos when compiled with Clang 3.8.1:
|
||||
`EnTT` includes its own tests and benchmarks. See [benchmark.cpp](https://github.com/skypjack/entt/blob/master/test/benchmark.cpp) for further details.<br/>
|
||||
On Github users can find also a [benchmark suite](https://github.com/abeimler/ecs_benchmark) that compares a bunch of different projects, one of which is `EnTT`.
|
||||
|
||||
| Benchmark | EntityX (experimental/compile_time) | EnTT |
|
||||
|-----------|-------------|-------------|
|
||||
| Creating 10M entities | 0.268049s | **0.0899998s** |
|
||||
| Destroying 10M entities | **0.0713912s** | 0.078663s |
|
||||
| Iterating over 10M entities, unpacking one component | 0.00863192s | **3.05e-07s** |
|
||||
| Iterating over 10M entities, unpacking two components | 0.00780158s | **2.5434e-05s** |
|
||||
| Iterating over 10M entities, unpacking five components | 0.00829669s | **2.5497e-05s** |
|
||||
| Iterating over 10M entities, unpacking ten components | 0.00789789s | **2.5563e-05s** |
|
||||
| Iterating over 50M entities, unpacking one component | 0.0423244s | **1.94e-07s** |
|
||||
| Iterating over 50M entities, unpacking two components | 0.0435464s | **0.00012661s** |
|
||||
|
||||
I don't know what Clang does to squeeze out of `EnTT` the performance above, but I'd say that it does it incredibly well.
|
||||
|
||||
See [benchmark.cpp](https://github.com/skypjack/entt/blob/master/test/benchmark.cpp) for further details.<br/>
|
||||
Of course, I'll try to get out of it more features and better performance anyway in the future, mainly for fun.
|
||||
If you want to contribute and have any suggestion, feel free to make a PR or open an issue to discuss them.
|
||||
|
||||
### Benchmarks / Comparisons
|
||||
|
||||
`EnTT` includes its own benchmarks, mostly similar to the ones of `EntityX` so as to compare them.<br/>
|
||||
On Github you can find also a [benchmark suite](https://github.com/abeimler/ecs_benchmark) testing `EntityX` (both the official version and the compile-time one), `Anax` and `Artemis C++` with up to 10M entities.
|
||||
Of course, probably I'll try to get out of `EnTT` more features and better performance in the future, mainly for fun.<br/>
|
||||
If you want to contribute and/or have any suggestion, feel free to make a PR or open an issue to discuss your idea.
|
||||
|
||||
# Build Instructions
|
||||
|
||||
## Requirements
|
||||
|
||||
To be able to use `EnTT`, users must provide a full-featured compiler that supports at least C++14.<br/>
|
||||
CMake version 3.4 or later is mandatory to compile the tests, you don't have to install it otherwise.
|
||||
CMake version 3.2 or later is mandatory to compile the tests, users don't have to install it otherwise.
|
||||
|
||||
## Library
|
||||
|
||||
@@ -143,193 +109,364 @@ It's a matter of adding the following line at the top of a file:
|
||||
#include <registry.hpp>
|
||||
```
|
||||
|
||||
Then pass the proper `-I` argument to the compiler to add the `src` directory to the include paths.<br/>
|
||||
Then pass the proper `-I` argument to the compiler to add the `src` directory to the include paths.
|
||||
|
||||
## Documentation
|
||||
|
||||
### API Reference
|
||||
|
||||
`EnTT` contains three main actors: the *registry*, the *view* and the *pool*.<br/>
|
||||
Unless you have specific requirements of memory management, the default registry (that used the pool provided with
|
||||
`EnTT`) should be good enough for any use. Customization is an option anyway, so that you can use your own pool as
|
||||
long as it offers the expected interface.
|
||||
Unfortunately `EnTT` isn't documented yet and thus users cannot rely on in-code documentation.<br/>
|
||||
Source code and names are self-documenting and I'm pretty sure that a glimpse to the API is enough for most of the users.<br/>
|
||||
For all the others, below is a crash course that guides them through the project and tries to fill the gap.
|
||||
|
||||
### Crash Course
|
||||
|
||||
`EnTT` has two main actors: the **Registry** and the **View**.<br/>
|
||||
The former can be used to manage components, entities and collections of components and entities. The latter allows users to iterate the underlying collections.
|
||||
|
||||
#### The Registry
|
||||
|
||||
There are three options to instantiate your own registry:
|
||||
There are two options to instantiate a registry:
|
||||
|
||||
* By using the default one:
|
||||
* Use the `DefaultRegistry` alias:
|
||||
|
||||
```cpp
|
||||
auto registry = entt::DefaultRegistry<Components...>{args...};
|
||||
```
|
||||
|
||||
That is, you must provide the whole list of components to be registered with the default registry.
|
||||
Users must provide the whole list of components to be registered with the default registry and that's all.
|
||||
|
||||
* By using the standard one:
|
||||
* Use directly the `Registry` class template:
|
||||
|
||||
```cpp
|
||||
auto registry = entt::StandardRegistry<std::uint16_t, Components...>{args...};
|
||||
auto registry = entt::Registry<std::uint16_t, Components...>{args...};
|
||||
```
|
||||
|
||||
That is, you must provide the whole list of components to be registered with the default registry **and** the desired type for the entities. Note that the default type is `std::uint32_t`, that is larger enough for almost all the games but also too big for the most of the games.
|
||||
Users must provide the whole list of components to be registered with the registry **and** the desired type for the entities.
|
||||
Note that the default type (the one used by the default registry) is `std::uint32_t`, that is larger enough for almost all the games but also too big for the most of the games.
|
||||
|
||||
* By using your own pool:
|
||||
In both cases there are no requirements for the components but to be moveable, therefore POD types are just fine.
|
||||
|
||||
```cpp
|
||||
auto registry = entt::Registry<DesiredEntityType, YourOwnPool<Components...>>{args...};
|
||||
```
|
||||
|
||||
Note that the registry expects a class template where the template parameters are the components to be managed.
|
||||
|
||||
In both cases, `args...` parameters are forwarded to the underlying pool during the construction.<br/>
|
||||
There are no requirements for the components but to be moveable, therefore POD types are just fine.
|
||||
|
||||
Once you have created a registry, the followings are the exposed member functions:
|
||||
|
||||
* `size`: returns the number of entities still alive.
|
||||
* `capacity`: returns the maximum number of entities created till now.
|
||||
* `valid`: returns true if the entity is still in use, false otherwise.
|
||||
* `empty<Component>`: returns `true` if at least an instance of `Component` exists, `false` otherwise.
|
||||
* `empty`: returns `true` if all the entities have been destroyed, `false` otherwise.
|
||||
* `create<Components...>`: creates a new entity and assigns it the given components, then returns the entity.
|
||||
* `create`: creates a new entity and returns it, no components assigned.
|
||||
* `destroy`: destroys the entity and all its components.
|
||||
* `assign<Component>(entity, args...)`: assigns the given component to the entity and uses `args...` to initialize it.
|
||||
* `remove<Component>(entity)`: removes the given component from the entity.
|
||||
* `has<Components...>(entity)`: returns `true` if the entity has the given components, `false` otherwise.
|
||||
* `get<Component>(entity)`: returns a reference to the given component for the entity (undefined behaviour if the entity has not the component).
|
||||
* `replace<Component>(entity, args...)`: replaces the given component for the entity, using `args...` to create the new component.
|
||||
* `accomodate<Component>(entity, args...)`: replaces the given component for the entity if it exists, otherwise assigns it to the entity and uses `args...` to initialize it.
|
||||
* `clone(entity)`: clones an entity and all its components, then returns the new entity identifier.
|
||||
* `copy<Component>(to, from)`: copies a component from an entity to another one (both the entities must already have been assigned the component, undefined behaviour otherwise).
|
||||
* `copy(to, from)`: copies all the components and their contents from an entity to another one (comoonents are created or destroyed if needed).
|
||||
* `reset<Component>(entity)`: removes the given component from the entity if assigned.
|
||||
* `reset<Component>()`: destroys all the instances of `Component`.
|
||||
* `reset()`: resets the pool and destroys all the entities and their components.
|
||||
* `view<Components...>()`: gets a view of the entities that have the given components (see below for further details).
|
||||
|
||||
Note that entities are numbers and nothing more. They are not classes and they have no member functions at all.
|
||||
|
||||
#### The View
|
||||
|
||||
There are three different kinds of view, each one with a slighlty different interface:
|
||||
|
||||
* The _single component view_.
|
||||
* The _multi component view_.
|
||||
|
||||
All of them are iterable. In other terms they have `begin` and `end` member functions that are suitable for a range-based for loop:
|
||||
The `Registry` class offers a bunch of basic functionalities to query the internal data structures.
|
||||
In almost all the cases those member functions can be used to query either the entity list or the components lists.<br/>
|
||||
As an example, the member functions `empty` can be used to know if at least an entity exists and/or if at least one component of the given type has been assigned to an entity.<br/>
|
||||
|
||||
```cpp
|
||||
auto view = registry.view<Position, Velocity>();
|
||||
bool b = registry.empty();
|
||||
// ...
|
||||
bool b = registry.empty<MyComponent>();
|
||||
```
|
||||
|
||||
for(auto entity: view) {
|
||||
// do whatever you want with your entities
|
||||
Similarly, `size` can be used to know the number of entities alive and/or the number of components of a given type still assigned to entities. `capacity` follows the same pattern and returns the storage capacity for the given element.
|
||||
|
||||
The `valid` member function returns true if `entity` is still in use, false otherwise:
|
||||
|
||||
```cpp
|
||||
bool b = registry.valid(entity);
|
||||
```
|
||||
|
||||
Boring, I agree. Let's go to something more tasty.
|
||||
The following functionalities are meant to give users the chance to play with entities and components within a registry.
|
||||
|
||||
The `create` member function can be used to construct a new entity and it comes in two flavors:
|
||||
|
||||
* The plain version just creates a _naked_ entity with no components assigned to it:
|
||||
|
||||
```cpp
|
||||
auto entity = registry.create();
|
||||
```
|
||||
|
||||
* The member function template creates an entity and assigns to it the given _default-initialized_ components:
|
||||
|
||||
```cpp
|
||||
auto entity = registry.create<Position, Velocity>();
|
||||
```
|
||||
|
||||
It's a helper function, mostly syncactic sugar and it's equivalent to the following snippet:
|
||||
|
||||
```cpp
|
||||
auto entity = registry.create();
|
||||
registry.assign<Position>();
|
||||
registry.assign<Velocity>();
|
||||
```
|
||||
|
||||
See below to find more about the `assign` member function.
|
||||
|
||||
On the other side, the `destroy` member function can be used to delete an entity and all its components (if any):
|
||||
|
||||
```cpp
|
||||
registry.destroy(entity);
|
||||
```
|
||||
|
||||
It requires that `entity` is valid. In case it is not, an assertion will fail in debug mode and the behaviour is undefined in release mode.
|
||||
|
||||
If the purpose is to remove a single component instead, the `remove` member function template is the way to go:
|
||||
|
||||
```cpp
|
||||
registry.remove<Position>(entity);
|
||||
```
|
||||
|
||||
Again, it requires that `entity` is valid. Moreover, an instance of the component must have been previously assigned to the entity.
|
||||
If one of the requirements isn't satisfied, an assertion will fail in debug mode and the behaviour is undefined in release mode.
|
||||
|
||||
The `reset` member function behaves similarly but with a strictly defined behaviour (and a performance penalty is the price to pay for that). In particular it removes the component if and only if it exists, otherwise it returns safely to the caller:
|
||||
|
||||
```cpp
|
||||
registry.reset<Position>(entity);
|
||||
```
|
||||
|
||||
It requires only that `entity` is valid. In case it is not, an assertion will fail in debug mode and the behaviour is undefined in release mode.
|
||||
|
||||
There exist also two more _versions_ of the `reset` member function:
|
||||
|
||||
* If no entity is passed to it, `reset` will remove the given component from each entity that has it:
|
||||
|
||||
```cpp
|
||||
registry.reset<Position>();
|
||||
```
|
||||
|
||||
* If neither the entity nor the component are specified, all the entities and their components are destroyed:
|
||||
|
||||
```cpp
|
||||
registry.reset();
|
||||
```
|
||||
|
||||
**Note**: the registry has an assert in debug mode that verifies that entities are no longer valid when it's destructed. This function can be used to reset the registry to its initial state and thus to satisfy the requirement.
|
||||
|
||||
To assign a component to an entity, users can rely on the `assign` member function template. It accepts a variable number of arguments that are used to construct the component itself if present:
|
||||
|
||||
```cpp
|
||||
registry.assign<Position>(entity, 0., 0.);
|
||||
// ...
|
||||
auto &velocity = registr.assign<Velocity>(entity);
|
||||
velocity.dx = 0.;
|
||||
velocity.dy = 0.;
|
||||
```
|
||||
|
||||
It requires that `entity` is valid. Moreover, the entity shouldn't have another instance of the component assigned to it.
|
||||
If one of the requirements isn't satisfied, an assertion will fail in debug mode and the behaviour is undefined in release mode.
|
||||
|
||||
If the entity already has the given component and the user wants to replace it, the `replace` member function template is the way to go:
|
||||
|
||||
```cpp
|
||||
registry.replace<Position>(entity, 0., 0.);
|
||||
// ...
|
||||
auto &velocity = registr.replace<Velocity>(entity);
|
||||
velocity.dx = 0.;
|
||||
velocity.dy = 0.;
|
||||
```
|
||||
|
||||
It requires that `entity` is valid. Moreover, an instance of the component must have been previously assigned to the entity.
|
||||
If one of the requirements isn't satisfied, an assertion will fail in debug mode and the behaviour is undefined in release mode.
|
||||
|
||||
In case users want to assign a component to an entity, but it's unknown whether the entity already has it or not, `accomodate` does the work in a single call
|
||||
(of course, there is a performance penalty to pay for that mainly due to the fact that it must check if `entity` already has the given component or not):
|
||||
|
||||
```cpp
|
||||
registry.accomodate<Position>(entity, 0., 0.);
|
||||
// ...
|
||||
auto &velocity = registr.accomodate<Velocity>(entity);
|
||||
velocity.dx = 0.;
|
||||
velocity.dy = 0.;
|
||||
```
|
||||
|
||||
It requires only that `entity` is valid. In case it is not, an assertion will fail in debug mode and the behaviour is undefined in release mode.<br/>
|
||||
Note that `accomodate` is a sliglhty faster alternative for the following if/else statement and nothing more:
|
||||
|
||||
```cpp
|
||||
if(registry.has<Comp>(entity)) {
|
||||
registry.replace<Comp>(entity, arg1, argN);
|
||||
} else {
|
||||
registry.assign<Comp>(entity, arg1, argN);
|
||||
}
|
||||
```
|
||||
|
||||
Iterators are extremely poor, they are meant exclusively to be used to iterate over a set of entities.<br/>
|
||||
Guaranteed exposed member functions are:
|
||||
As already shown, if in doubt about whether or not an entity has one or more components, the `has` member function template may be useful:
|
||||
|
||||
* `operator++()`
|
||||
* `operator++(int)`
|
||||
* `operator==()`
|
||||
* `operator!=()`
|
||||
* `operator*()`
|
||||
```cpp
|
||||
bool b = registry.has<Position, Velocity>(entity);
|
||||
```
|
||||
|
||||
The single component view has an additional member function:
|
||||
It requires only that `entity` is valid. In case it is not, an assertion will fail in debug mode and the behaviour is undefined in release mode.
|
||||
|
||||
* `size()`: returns the exact number of expected entities.
|
||||
Entities can also be cloned and either partially or fully copied:
|
||||
|
||||
The multi component view has an additional member function:
|
||||
```cpp
|
||||
auto entity = registry.clone(other);
|
||||
// ...
|
||||
auto &velocity = registry.copy<Velocity>(to, from);
|
||||
// ...
|
||||
registry.copy(dst, src);
|
||||
```
|
||||
|
||||
* `reset()`: reorganizes internal data so as to further create optimized iterators (use it whenever the data within the registry are known to be changed).
|
||||
|
||||
All the views can be used more than once. They return newly created and correctly initialized iterators whenever
|
||||
`begin` or `end` is invoked. Anyway views and iterators are tiny objects and the time to construct them can be safely ignored.
|
||||
I'd suggest not to store them anywhere and to invoke the `Registry::view` member function at each iteration to get a properly
|
||||
initialized view over which to iterate.
|
||||
|
||||
**Note**: If underlying sets are modified, iterators are invalidated and using them is undefined behaviour.<br/>
|
||||
Do not try to assign or remove components on which you are iterating to entities. There are no guarantees.
|
||||
|
||||
**Note**: Iterators aren't thread safe. Do no try to iterate over a set of components and modify them concurrently.
|
||||
That being said, as long as a thread iterates over the entities that have the component `X` or assign and removes
|
||||
that component from a set of entities and another thread does something similar with components `Y` and `Z`, it shouldn't be a
|
||||
problem at all.<br/>
|
||||
As an example, that means that users can freely run the rendering system over the renderable entities and update the physics
|
||||
concurrently on a separate thread if needed.
|
||||
|
||||
#### The Pool
|
||||
|
||||
Custom pools for a given component can be defined as a specialization of the class template `ComponentPool`.<br/>
|
||||
In particular:
|
||||
|
||||
```cpp
|
||||
template<>
|
||||
struct ComponentPool<Entity, MyComponent> final {
|
||||
// ...
|
||||
};
|
||||
```
|
||||
* The `clone` member function creates a new entity and copy all the components from the given one.
|
||||
* The `copy` member function template copies one component from an entity to another one.
|
||||
* The `copy` member function copies all the components from an entity to another one.
|
||||
|
||||
Where `Entity` is the desired type for the entities, `MyComponent` the type of the component to be stored.
|
||||
All the functions above mentioned require that entities provided as arguments are valid and components exist wherever they have to be accessed.
|
||||
In case they are not, an assertion will fail in debug mode and the behaviour is undefined in release mode.
|
||||
|
||||
A custom pool should expose at least the following member functions:
|
||||
|
||||
* `bool empty() const noexcept;`
|
||||
* `size_type capacity() const noexcept;`
|
||||
* `size_type size() const noexcept;`
|
||||
* `iterator_type begin() noexcept;`
|
||||
* `const_iterator_type begin() const noexcept;`
|
||||
* `iterator_type end() noexcept;`
|
||||
* `const_iterator_type end() const noexcept;`
|
||||
* `bool has(entity_type entity) const noexcept;`
|
||||
* `const component_type & get(entity_type entity) const noexcept;`
|
||||
* `component_type & get(entity_type entity) noexcept;`
|
||||
* `template<typename... Args> component_type & construct(entity_type entity, Args... args);`
|
||||
* `void destroy(entity_type entity);`
|
||||
* `void reset();`
|
||||
|
||||
This is a fast and easy way to define a custom pool specialization for a given component (as an example, if the
|
||||
component `X` requires to be ordered internally somehow during construction or destruction operations) and to use the
|
||||
default pool for all the other components.<br/>
|
||||
It's a mattrer of including the given specialization along with the registry, so that it can find it during the instantiation.<br/>
|
||||
In this case, users are not required to use the more explicit `Registry` class. Instead, they can still use `entt::DefaultRegistry`.
|
||||
|
||||
In cases when the per-component pools are not good enough, the registry can be initialized with a custom pool.<br/>
|
||||
In other terms, `entt::Registry` has a template template parameter that can be used to provide both the pool and the list of
|
||||
components:
|
||||
There exists also an utility member function that can be used to `swap` components between entities:
|
||||
|
||||
```cpp
|
||||
auto registry = entt::Registry<Entity, MyCustomPool<Component1, Component2>>{};
|
||||
registry.swap<Position>(e1, e2);
|
||||
```
|
||||
|
||||
Even though the underlying pool doesn't store the components separately, the registry must know them to be able to do
|
||||
specific actions (like `destroy` or `copy`). That's why they must be explicitly specified.<br/>
|
||||
A generic pool should expose at least the following memeber functions:
|
||||
As usual, it requires that the two entities are valid and that two instances of the component have been previously assigned to them.
|
||||
In case they are not, an assertion will fail in debug mode and the behaviour is undefined in release mode.
|
||||
|
||||
* `template<typename Component> bool empty() const noexcept;`
|
||||
* `template<typename Component> size_type capacity() const noexcept;`
|
||||
* `template<typename Component> size_type size() const noexcept;`
|
||||
* `template<typename Component> iterator_type begin() noexcept;`
|
||||
* `template<typename Component> const_iterator_type begin() const noexcept;`
|
||||
* `template<typename Component> iterator_type end() noexcept;`
|
||||
* `template<typename Component> const_iterator_type end() const noexcept;`
|
||||
* `template<typename Component> bool has(entity_type entity) const noexcept;`
|
||||
* `template<typename Component> const Comp & get(entity_type entity) const noexcept;`
|
||||
* `template<typename Component> Comp & get(entity_type entity) noexcept;`
|
||||
* `template<typename Component, typename... Args> Comp & construct(entity_type entity, Args... args);`
|
||||
* `template<typename Component> void destroy(entity_type entity);`
|
||||
* `template<typename Component> void reset();`
|
||||
* `void reset();`
|
||||
The `get` member function template (either the non-const or the const version) gives direct access to the component of an entity instead:
|
||||
|
||||
Good luck. If you come out with a more performant components pool, do not forget to make a PR so that I can add it to
|
||||
the list of available ones. I would be glad to have such a contribution to the project!!
|
||||
```cpp
|
||||
auto &position = registry.get<Position>(entity);
|
||||
```
|
||||
|
||||
It requires that `entity` is valid. Moreover, an instance of the component must have been previously assigned to the entity.
|
||||
If one of the requirements isn't satisfied, an assertion will fail in debug mode and the behaviour is undefined in release mode.
|
||||
|
||||
Components can also be sorted in memory by means of the `sort` member function templates. In particular:
|
||||
|
||||
* Components can be sorted according to a component:
|
||||
|
||||
```cpp
|
||||
registry.sort<Renderable>([](const auto &lhs, const auto &rhs) { return lhs.z < rhs.z; });
|
||||
```
|
||||
|
||||
* Components can be sorted according to the order imposed by another component:
|
||||
|
||||
```cpp
|
||||
registry.sort<Movement, Physics>();
|
||||
```
|
||||
|
||||
In this case, instances of `Movement` are arranged in memory so that cache misses are minimized when the two components are iterated together.
|
||||
|
||||
Finally, the `view` member function template returns an iterable portion of entities and components:
|
||||
|
||||
```cpp
|
||||
auto view = registry.view<Position, Velocity>();
|
||||
```
|
||||
|
||||
Views are the other core component of `EnTT` and are usually extensively used by softwares that include it. See below for more details about the types of views.
|
||||
|
||||
#### The View
|
||||
|
||||
There are two types of views:
|
||||
|
||||
* **Single component view**.
|
||||
|
||||
A single component view gives direct access to both the components and the entities to which the components are assigned.<br/>
|
||||
This kind of views are created from the `Registry` class by means of the `view` member function template as it follows:
|
||||
|
||||
```cpp
|
||||
// Actual type is Registry<Components...>::view_type<Comp>, where Comp is the component for which the view should be created ...
|
||||
// ... well, auto is far easier to use in this case, isn't it?
|
||||
auto view = registry.view<Sprite>();
|
||||
```
|
||||
|
||||
Components and entities are stored in tightly packed arrays and single component views are the fastest solution to iterate them.<br/>
|
||||
They have the _C++11-ish_ `begin` and `end` member function that allow users to use them in a typical range-for loop:
|
||||
|
||||
```cpp
|
||||
auto view = registry.view<Sprite>();
|
||||
|
||||
for(auto entity: view) {
|
||||
auto &sprite = registry.get<Sprite>(entity);
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Iterating a view this way returns entities that can be further used to get components or perform other activities.<br/>
|
||||
There is also another method one can use to iterate the array of entities, that is by using the `size` and `data` member functions:
|
||||
|
||||
```cpp
|
||||
auto view = registry.view<Sprite>();
|
||||
const auto *data = view.data();
|
||||
|
||||
for(auto i = 0, end = view.size(); i < end; ++i) {
|
||||
auto entity = *(data + i);
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Entites are good when the sole component isn't enough to perform a task.
|
||||
Anyway they come with a cost: accessing components by entities has an extra level of indirection. It's pretty fast, but not that fast in some cases.<br/>
|
||||
Direct access to the packed array of components is the other option around of a single component view. Member functions `size` and `raw` are there for that:
|
||||
|
||||
```cpp
|
||||
auto view = registry.view<Sprite>();
|
||||
const auto *raw = view.raw();
|
||||
|
||||
for(auto i = 0, end = view.size(); i < end; ++i) {
|
||||
auto &sprite = *(raw + i);
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
This is the fastest solution to iterate over the components: they are packed together by construction and visit them in order will reduce to a minimum the number of cache misses.
|
||||
|
||||
* **Multi component view**.
|
||||
|
||||
A multi component view gives access only to the entities to which the components are assigned.<br/>
|
||||
This kind of views are created from the `Registry` class by means of the `view` member function template as it follows:
|
||||
|
||||
```cpp
|
||||
// Actual type is Registry<Components...>::view_type<Comp...>, where Comp... are the components for which the view should be created ...
|
||||
// ... well, auto is far easier to use in this case, isn't it?
|
||||
auto view = registry.view<Position, Velocity>();
|
||||
```
|
||||
|
||||
Multi component views can be iterated by means of the `begin` and `end` member functions in a typical range-for loop:
|
||||
|
||||
```cpp
|
||||
auto view = registry.view<Position, Velocity>();
|
||||
|
||||
for(auto entity: view) {
|
||||
auto &position = registry.get<Position>(entity);
|
||||
auto &velocity = registry.get<Velocity>(entity);
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Note that there exists a packed array of entities to which the component is assigned for each component.
|
||||
Iterators of a multi component view pick the shortest array up and use it to visit the smallest set of potential entities.<br/>
|
||||
The choice is performed when the view is constructed. It's good enough as long as views are discarded once they have been used.
|
||||
For all the other cases, the `reset` member function can be used whenever the data within the registry are known to be changed and forcing the choice again could speed up the execution.
|
||||
|
||||
**Note**: one could argue that an iterator should return the set of references to components for each entity instead of the entity itself.
|
||||
Well, who wants to spend CPU cycles to get a reference to an useless tag component? This drove the design choice indeed.
|
||||
|
||||
All the views can be used more than once. They return newly created and correctly initialized iterators whenever `begin` or `end` is invoked.
|
||||
The same is valid for `data` and `raw` too. Anyway views and iterators are tiny objects and the time spent to construct them can be safely ignored.<br/>
|
||||
I'd suggest not to store them anywhere and to invoke the `Registry::view` member function template at each iteration to get a properly initialized view through which to iterate.
|
||||
|
||||
#### Side notes
|
||||
|
||||
* Entities are numbers and nothing more. They are not classes and they have no member functions at all.
|
||||
|
||||
* Most of the _ECS_ available out there have an annoying limitation (at least from my point of view): entities and components cannot be created, assigned or deleted while users are iterating on them.<br/>
|
||||
`EnTT` partially solves the problem with a few limitations:
|
||||
|
||||
* Entities can be created at any time while iterating one or more components.
|
||||
* Components can be assigned to any entity at any time while iterating one or more components.
|
||||
* During an iteration, the current entity (that is the one returned by the iterator) can be deleted and all its components can be removed safely.
|
||||
|
||||
Entities that are not the current one (that is the one returned by the iterator) cannot be deleted from within a loop.<br/>
|
||||
Components assigned to entities that are not the current one (that is the one returned by the iterator) cannot be removed from within a loop.<br/>
|
||||
In this case, iterators are invalidated and the behaviour is undefined if one continues to use those iterators. Possible approaches are:
|
||||
|
||||
* Store aside the entities and 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 that they must be purged, then perform a second iteration to clean them up one by one.
|
||||
|
||||
* Iterators aren't thread safe. Do no try to iterate over a set of components and modify them concurrently.<br/>
|
||||
That being said, as long as a thread iterates over the entities that have the component `X` or assign and removes that component from a set of entities and another thread does something similar with components `Y` and `Z`, it shouldn't be a problem at all.<br/>
|
||||
As an example, that means that users can freely run the rendering system over the renderable entities and update the physics concurrently on a separate thread if needed.
|
||||
|
||||
## Tests
|
||||
|
||||
To compile and run the tests, `EnTT` requires *googletest*.<br/>
|
||||
Run the script `deps.sh` to download it. It is good practice to do it every time one pull the project.
|
||||
`cmake` will download and compile the library before to compile anything else.
|
||||
|
||||
Then, to build the tests:
|
||||
|
||||
@@ -338,6 +475,12 @@ Then, to build the tests:
|
||||
* `$ make`
|
||||
* `$ make test`
|
||||
|
||||
To build the benchmarks, use the following line instead:
|
||||
|
||||
* `$ cmake -DCMAKE_BUILD_TYPE=Release ..`
|
||||
|
||||
Benchmarks are compiled only in release mode currently.
|
||||
|
||||
# Contributors
|
||||
|
||||
If you want to contribute, please send patches as pull requests against the branch master.<br/>
|
||||
@@ -347,3 +490,8 @@ Check the [contributors list](https://github.com/skypjack/entt/blob/master/AUTHO
|
||||
|
||||
Code and documentation Copyright (c) 2017 Michele Caini.<br/>
|
||||
Code released under [the MIT license](https://github.com/skypjack/entt/blob/master/LICENSE).
|
||||
|
||||
# Donation
|
||||
|
||||
Developing and maintaining `EnTT` takes some time and lots of coffee. If you want to support this project, you can offer me an espresso. I'm from Italy, we're used to turning the best coffee ever in code.<br/>
|
||||
Take a look at the donation button at the top of the page for more details or just click [here](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=W2HF9FESD5LJY&lc=IT&item_name=Michele%20Caini¤cy_code=EUR&bn=PP%2dDonationsBF%3abtn_donateCC_LG%2egif%3aNonHosted).
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# can use variables like {build} and {branch}
|
||||
version: 1.0.{build}
|
||||
|
||||
image: Visual Studio 2015
|
||||
image: Visual Studio 2017
|
||||
|
||||
environment:
|
||||
BUILD_DIR: "%APPVEYOR_BUILD_FOLDER%\\build"
|
||||
@@ -13,9 +13,8 @@ configuration:
|
||||
- Release
|
||||
|
||||
before_build:
|
||||
- deps.bat
|
||||
- cd %BUILD_DIR%
|
||||
- cmake .. -G"%CMAKE_GENERATOR_NAME%"
|
||||
- cmake .. -G"Visual Studio 15 2017"
|
||||
|
||||
build:
|
||||
parallel: true
|
||||
|
||||
@@ -6,7 +6,7 @@ include(ExternalProject)
|
||||
ExternalProject_Add(
|
||||
googletest
|
||||
GIT_REPOSITORY https://github.com/google/googletest.git
|
||||
GIT_TAG master
|
||||
GIT_TAG release-1.8.0
|
||||
DOWNLOAD_DIR ${GOOGLETEST_DEPS_DIR}
|
||||
TMP_DIR ${GOOGLETEST_DEPS_DIR}/tmp
|
||||
STAMP_DIR ${GOOGLETEST_DEPS_DIR}/stamp
|
||||
|
||||
@@ -1,235 +0,0 @@
|
||||
#ifndef ENTT_COMPONENT_POOL_HPP
|
||||
#define ENTT_COMPONENT_POOL_HPP
|
||||
|
||||
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <cassert>
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
template<typename, typename, typename...>
|
||||
class ComponentPool;
|
||||
|
||||
|
||||
template<typename Entity, typename Component>
|
||||
class ComponentPool<Entity, Component> {
|
||||
public:
|
||||
using component_type = Component;
|
||||
using entity_type = Entity;
|
||||
using pos_type = entity_type;
|
||||
using size_type = typename std::vector<component_type>::size_type;
|
||||
using iterator_type = typename std::vector<entity_type>::iterator;
|
||||
using const_iterator_type = typename std::vector<entity_type>::const_iterator;
|
||||
|
||||
private:
|
||||
inline bool valid(entity_type entity) const noexcept {
|
||||
return entity < reverse.size() && reverse[entity] < direct.size() && direct[reverse[entity]] == entity;
|
||||
}
|
||||
|
||||
public:
|
||||
explicit ComponentPool(size_type dim = 4098) noexcept {
|
||||
assert(!(dim < 0));
|
||||
data.reserve(dim);
|
||||
}
|
||||
|
||||
ComponentPool(ComponentPool &&) = default;
|
||||
|
||||
~ComponentPool() noexcept {
|
||||
assert(empty());
|
||||
}
|
||||
|
||||
ComponentPool & operator=(ComponentPool &&) = default;
|
||||
|
||||
bool empty() const noexcept {
|
||||
return data.empty();
|
||||
}
|
||||
|
||||
size_type capacity() const noexcept {
|
||||
return data.capacity();
|
||||
}
|
||||
|
||||
size_type size() const noexcept {
|
||||
return data.size();
|
||||
}
|
||||
|
||||
iterator_type begin() noexcept {
|
||||
return direct.begin();
|
||||
}
|
||||
|
||||
const_iterator_type cbegin() const noexcept {
|
||||
return direct.cbegin();
|
||||
}
|
||||
|
||||
iterator_type end() noexcept {
|
||||
return direct.end();
|
||||
}
|
||||
|
||||
const_iterator_type cend() const noexcept {
|
||||
return direct.cend();
|
||||
}
|
||||
|
||||
bool has(entity_type entity) const noexcept {
|
||||
return valid(entity);
|
||||
}
|
||||
|
||||
const component_type & get(entity_type entity) const noexcept {
|
||||
assert(valid(entity));
|
||||
return data[reverse[entity]];
|
||||
}
|
||||
|
||||
component_type & get(entity_type entity) noexcept {
|
||||
return const_cast<component_type &>(const_cast<const ComponentPool *>(this)->get(entity));
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
component_type & construct(entity_type entity, Args... args) {
|
||||
assert(!valid(entity));
|
||||
|
||||
if(!(entity < reverse.size())) {
|
||||
reverse.resize(entity+1);
|
||||
}
|
||||
|
||||
reverse[entity] = pos_type(direct.size());
|
||||
direct.emplace_back(entity);
|
||||
data.push_back({ args... });
|
||||
|
||||
return data.back();
|
||||
}
|
||||
|
||||
void destroy(entity_type entity) {
|
||||
assert(valid(entity));
|
||||
|
||||
auto last = direct.size() - 1;
|
||||
|
||||
reverse[direct[last]] = reverse[entity];
|
||||
direct[reverse[entity]] = direct[last];
|
||||
data[reverse[entity]] = std::move(data[last]);
|
||||
|
||||
direct.pop_back();
|
||||
data.pop_back();
|
||||
}
|
||||
|
||||
void reset() {
|
||||
data.clear();
|
||||
reverse.resize(0);
|
||||
direct.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<component_type> data;
|
||||
std::vector<pos_type> reverse;
|
||||
std::vector<entity_type> direct;
|
||||
};
|
||||
|
||||
|
||||
template<typename Entity, typename Component, typename... Components>
|
||||
class ComponentPool
|
||||
: ComponentPool<Entity, Component>, ComponentPool<Entity, Components>...
|
||||
{
|
||||
template<typename Comp>
|
||||
using Pool = ComponentPool<Entity, Comp>;
|
||||
|
||||
public:
|
||||
using entity_type = typename Pool<Component>::entity_type;
|
||||
using pos_type = typename Pool<Component>::pos_type;
|
||||
using size_type = typename Pool<Component>::size_type;
|
||||
using iterator_type = typename Pool<Component>::iterator_type;
|
||||
using const_iterator_type = typename Pool<Component>::const_iterator_type;
|
||||
|
||||
explicit ComponentPool(size_type dim = 4098) noexcept
|
||||
#ifdef _MSC_VER
|
||||
: ComponentPool<Entity, Component>{dim}, ComponentPool<Entity, Components>{dim}...
|
||||
#else
|
||||
: Pool<Component>{dim}, Pool<Components>{dim}...
|
||||
#endif
|
||||
{
|
||||
assert(!(dim < 0));
|
||||
}
|
||||
|
||||
ComponentPool(const ComponentPool &) = delete;
|
||||
ComponentPool(ComponentPool &&) = delete;
|
||||
|
||||
ComponentPool & operator=(const ComponentPool &) = delete;
|
||||
ComponentPool & operator=(ComponentPool &&) = delete;
|
||||
|
||||
template<typename Comp>
|
||||
bool empty() const noexcept {
|
||||
return Pool<Comp>::empty();
|
||||
}
|
||||
|
||||
template<typename Comp>
|
||||
size_type capacity() const noexcept {
|
||||
return Pool<Comp>::capacity();
|
||||
}
|
||||
|
||||
template<typename Comp>
|
||||
size_type size() const noexcept {
|
||||
return Pool<Comp>::size();
|
||||
}
|
||||
|
||||
template<typename Comp>
|
||||
iterator_type begin() noexcept {
|
||||
return Pool<Comp>::begin();
|
||||
}
|
||||
|
||||
template<typename Comp>
|
||||
const_iterator_type cbegin() const noexcept {
|
||||
return Pool<Comp>::cbegin();
|
||||
}
|
||||
|
||||
template<typename Comp>
|
||||
iterator_type end() noexcept {
|
||||
return Pool<Comp>::end();
|
||||
}
|
||||
|
||||
template<typename Comp>
|
||||
const_iterator_type cend() const noexcept {
|
||||
return Pool<Comp>::cend();
|
||||
}
|
||||
|
||||
template<typename Comp>
|
||||
bool has(entity_type entity) const noexcept {
|
||||
return Pool<Comp>::has(entity);
|
||||
}
|
||||
|
||||
template<typename Comp>
|
||||
const Comp & get(entity_type entity) const noexcept {
|
||||
return Pool<Comp>::get(entity);
|
||||
}
|
||||
|
||||
template<typename Comp>
|
||||
Comp & get(entity_type entity) noexcept {
|
||||
return const_cast<Comp &>(const_cast<const ComponentPool *>(this)->get<Comp>(entity));
|
||||
}
|
||||
|
||||
template<typename Comp, typename... Args>
|
||||
Comp & construct(entity_type entity, Args... args) {
|
||||
return Pool<Comp>::construct(entity, args...);
|
||||
}
|
||||
|
||||
template<typename Comp>
|
||||
void destroy(entity_type entity) {
|
||||
Pool<Comp>::destroy(entity);
|
||||
}
|
||||
|
||||
template<typename Comp>
|
||||
void reset() {
|
||||
Pool<Comp>::reset();
|
||||
}
|
||||
|
||||
void reset() {
|
||||
using accumulator_type = int[];
|
||||
Pool<Component>::reset();
|
||||
accumulator_type accumulator = { (Pool<Components>::reset(), 0)... };
|
||||
(void)accumulator;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_COMPONENT_POOL_HPP
|
||||
247
src/registry.hpp
247
src/registry.hpp
@@ -2,37 +2,36 @@
|
||||
#define ENTT_REGISTRY_HPP
|
||||
|
||||
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
#include <bitset>
|
||||
#include <utility>
|
||||
#include <cstddef>
|
||||
#include <iterator>
|
||||
#include <cassert>
|
||||
#include <type_traits>
|
||||
#include "component_pool.hpp"
|
||||
#include "sparse_set.hpp"
|
||||
#include "ident.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
template<typename...>
|
||||
template<typename, std::size_t...>
|
||||
class View;
|
||||
|
||||
|
||||
template<template<typename...> class Pool, typename Entity, typename... Components, typename Type, typename... Types>
|
||||
class View<Pool<Entity, Components...>, Type, Types...> final {
|
||||
using pool_type = Pool<Entity, Components...>;
|
||||
using entity_type = typename pool_type::entity_type;
|
||||
using mask_type = std::bitset<sizeof...(Components)+1>;
|
||||
using underlying_iterator_type = typename pool_type::const_iterator_type;
|
||||
template<typename Pool, std::size_t Ident, std::size_t... Other>
|
||||
class View<Pool, Ident, Other...> final {
|
||||
using pool_type = Pool;
|
||||
using mask_type = std::bitset<std::tuple_size<Pool>::value + 1>;
|
||||
using underlying_iterator_type = typename std::tuple_element_t<Ident, Pool>::iterator_type;
|
||||
|
||||
class ViewIterator;
|
||||
|
||||
public:
|
||||
using iterator_type = ViewIterator;
|
||||
using const_iterator_type = iterator_type;
|
||||
using size_type = typename pool_type::size_type;
|
||||
using entity_type = typename std::tuple_element_t<Ident, Pool>::index_type;
|
||||
using size_type = typename std::tuple_element_t<Ident, Pool>::size_type;
|
||||
|
||||
private:
|
||||
class ViewIterator {
|
||||
@@ -42,10 +41,6 @@ private:
|
||||
|
||||
public:
|
||||
using value_type = entity_type;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using reference = entity_type &;
|
||||
using pointer = entity_type *;
|
||||
using iterator_category = std::input_iterator_tag;
|
||||
|
||||
ViewIterator(underlying_iterator_type begin, underlying_iterator_type end, const mask_type &bitmask, const mask_type *mask) noexcept
|
||||
: begin{begin}, end{end}, bitmask{bitmask}, mask{mask}
|
||||
@@ -85,133 +80,135 @@ private:
|
||||
const mask_type *mask;
|
||||
};
|
||||
|
||||
template<typename Comp>
|
||||
template<std::size_t Idx>
|
||||
void prefer(size_type &size) noexcept {
|
||||
auto sz = pool.template size<Comp>();
|
||||
auto &&cpool = std::get<Idx>(*pool);
|
||||
auto sz = cpool.size();
|
||||
|
||||
if(sz < size) {
|
||||
from = pool.template begin<Type>();
|
||||
to = pool.template end<Type>();
|
||||
from = cpool.begin();
|
||||
to = cpool.end();
|
||||
size = sz;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
explicit View(pool_type &pool, const mask_type *mask) noexcept
|
||||
: from{pool.template begin<Type>()},
|
||||
to{pool.template end<Type>()},
|
||||
explicit View(const pool_type *pool, const mask_type *mask) noexcept
|
||||
: from{std::get<Ident>(*pool).begin()},
|
||||
to{std::get<Ident>(*pool).end()},
|
||||
pool{pool},
|
||||
mask{mask}
|
||||
{
|
||||
using accumulator_type = int[];
|
||||
size_type size = pool.template size<Type>();
|
||||
bitmask.set(ident<Components...>.template get<Type>());
|
||||
accumulator_type types = { 0, (bitmask.set(ident<Components...>.template get<Types>()), 0)... };
|
||||
accumulator_type pref = { 0, (prefer<Types>(size), 0)... };
|
||||
size_type size = std::get<Ident>(*pool).size();
|
||||
bitmask.set(Ident);
|
||||
accumulator_type types = { 0, (bitmask.set(Other), 0)... };
|
||||
accumulator_type pref = { 0, (prefer<Other>(size), 0)... };
|
||||
(void)types, (void)pref;
|
||||
}
|
||||
|
||||
const_iterator_type begin() const noexcept {
|
||||
iterator_type begin() const noexcept {
|
||||
return ViewIterator{from, to, bitmask, mask};
|
||||
}
|
||||
|
||||
iterator_type begin() noexcept {
|
||||
return const_cast<const View *>(this)->begin();
|
||||
}
|
||||
|
||||
const_iterator_type end() const noexcept {
|
||||
iterator_type end() const noexcept {
|
||||
return ViewIterator{to, to, bitmask, mask};
|
||||
}
|
||||
|
||||
iterator_type end() noexcept {
|
||||
return const_cast<const View *>(this)->end();
|
||||
}
|
||||
|
||||
void reset() noexcept {
|
||||
using accumulator_type = int[];
|
||||
from = pool.template begin<Type>();
|
||||
to = pool.template end<Type>();
|
||||
size_type size = pool.template size<Type>();
|
||||
accumulator_type accumulator = { 0, (prefer<Types>(size), 0)... };
|
||||
auto &&cpool = std::get<Ident>(*pool);
|
||||
from = cpool.begin();
|
||||
to = cpool.end();
|
||||
size_type size = cpool.size();
|
||||
accumulator_type accumulator = { 0, (prefer<Other>(size), 0)... };
|
||||
(void)accumulator;
|
||||
}
|
||||
|
||||
private:
|
||||
underlying_iterator_type from;
|
||||
underlying_iterator_type to;
|
||||
pool_type &pool;
|
||||
const pool_type *pool;
|
||||
const mask_type *mask;
|
||||
mask_type bitmask;
|
||||
};
|
||||
|
||||
|
||||
template<template<typename...> class Pool, typename Entity, typename... Components, typename Type>
|
||||
class View<Pool<Entity, Components...>, Type> final {
|
||||
using pool_type = Pool<Entity, Components...>;
|
||||
template<typename Pool, std::size_t Ident>
|
||||
class View<Pool, Ident> final {
|
||||
using pool_type = std::tuple_element_t<Ident, Pool>;
|
||||
|
||||
public:
|
||||
using iterator_type = typename pool_type::iterator_type;
|
||||
using entity_type = typename pool_type::index_type;
|
||||
using size_type = typename pool_type::size_type;
|
||||
using iterator_type = typename pool_type::const_iterator_type;
|
||||
using const_iterator_type = iterator_type;
|
||||
using raw_type = typename pool_type::type;
|
||||
|
||||
explicit View(pool_type &pool) noexcept
|
||||
: pool{pool}
|
||||
explicit View(const Pool *pool) noexcept
|
||||
: pool{&std::get<Ident>(*pool)}
|
||||
{}
|
||||
|
||||
const_iterator_type cbegin() const noexcept {
|
||||
return pool.template cbegin<Type>();
|
||||
raw_type * raw() noexcept {
|
||||
return pool->raw();
|
||||
}
|
||||
|
||||
iterator_type begin() noexcept {
|
||||
return pool.template begin<Type>();
|
||||
const raw_type * raw() const noexcept {
|
||||
return pool->raw();
|
||||
}
|
||||
|
||||
const_iterator_type cend() const noexcept {
|
||||
return pool.template cend<Type>();
|
||||
}
|
||||
|
||||
iterator_type end() noexcept {
|
||||
return pool.template end<Type>();
|
||||
const entity_type * data() const noexcept {
|
||||
return pool->data();
|
||||
}
|
||||
|
||||
size_type size() const noexcept {
|
||||
return pool.template size<Type>();
|
||||
return pool->size();
|
||||
}
|
||||
|
||||
iterator_type begin() const noexcept {
|
||||
return pool->begin();
|
||||
}
|
||||
|
||||
iterator_type end() const noexcept {
|
||||
return pool->end();
|
||||
}
|
||||
|
||||
private:
|
||||
pool_type &pool;
|
||||
const pool_type *pool;
|
||||
};
|
||||
|
||||
|
||||
template<typename>
|
||||
class Registry;
|
||||
template<typename Entity, typename... Component>
|
||||
class Registry {
|
||||
using pool_type = std::tuple<SparseSet<Entity, Component>...>;
|
||||
using mask_type = std::bitset<sizeof...(Component)+1>;
|
||||
|
||||
static constexpr auto validity_bit = sizeof...(Component);
|
||||
|
||||
template<template<typename...> class Pool, typename Entity, typename... Components>
|
||||
class Registry<Pool<Entity, Components...>> {
|
||||
static_assert(sizeof...(Components) > 1, "!");
|
||||
|
||||
using pool_type = Pool<Entity, Components...>;
|
||||
using mask_type = std::bitset<sizeof...(Components)+1>;
|
||||
|
||||
static constexpr auto validity_bit = sizeof...(Components);
|
||||
// variable templates are fine as well, but for the fact that MSVC goes crazy
|
||||
template<typename Comp>
|
||||
struct identifier {
|
||||
static constexpr auto value = ident<Component...>.template get<Comp>();
|
||||
};
|
||||
|
||||
public:
|
||||
using entity_type = typename pool_type::entity_type;
|
||||
using entity_type = Entity;
|
||||
using size_type = typename std::vector<mask_type>::size_type;
|
||||
|
||||
template<typename... Comp>
|
||||
using view_type = View<pool_type, identifier<Comp>::value...>;
|
||||
|
||||
private:
|
||||
template<typename Comp>
|
||||
void clone(entity_type to, entity_type from) {
|
||||
if(entities[from].test(ident<Components...>.template get<Comp>())) {
|
||||
assign<Comp>(to, pool.template get<Comp>(from));
|
||||
if(entities[from].test(identifier<Comp>::value)) {
|
||||
assign<Comp>(to, std::get<identifier<Comp>::value>(pool).get(from));
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Comp>
|
||||
void sync(entity_type to, entity_type from) {
|
||||
bool src = entities[from].test(ident<Components...>.template get<Comp>());
|
||||
bool dst = entities[to].test(ident<Components...>.template get<Comp>());
|
||||
bool src = entities[from].test(identifier<Comp>::value);
|
||||
bool dst = entities[to].test(identifier<Comp>::value);
|
||||
|
||||
if(src && dst) {
|
||||
copy<Comp>(to, from);
|
||||
@@ -223,13 +220,8 @@ private:
|
||||
}
|
||||
|
||||
public:
|
||||
template<typename... Comp>
|
||||
using view_type = View<pool_type, Comp...>;
|
||||
|
||||
template<typename... Args>
|
||||
Registry(Args&&... args)
|
||||
: pool{std::forward<Args>(args)...}
|
||||
{}
|
||||
explicit Registry() = default;
|
||||
~Registry() = default;
|
||||
|
||||
Registry(const Registry &) = delete;
|
||||
Registry(Registry &&) = delete;
|
||||
@@ -237,17 +229,27 @@ public:
|
||||
Registry & operator=(const Registry &) = delete;
|
||||
Registry & operator=(Registry &&) = delete;
|
||||
|
||||
template<typename Comp>
|
||||
size_type size() const noexcept {
|
||||
return std::get<identifier<Comp>::value>(pool).size();
|
||||
}
|
||||
|
||||
size_type size() const noexcept {
|
||||
return entities.size() - available.size();
|
||||
}
|
||||
|
||||
template<typename Comp>
|
||||
size_type capacity() const noexcept {
|
||||
return std::get<identifier<Comp>::value>(pool).capacity();
|
||||
}
|
||||
|
||||
size_type capacity() const noexcept {
|
||||
return entities.size();
|
||||
}
|
||||
|
||||
template<typename Comp>
|
||||
bool empty() const noexcept {
|
||||
return pool.template empty<Comp>();
|
||||
return std::get<identifier<Comp>::value>(pool).empty();
|
||||
}
|
||||
|
||||
bool empty() const noexcept {
|
||||
@@ -286,7 +288,7 @@ public:
|
||||
void destroy(entity_type entity) {
|
||||
assert(valid(entity));
|
||||
using accumulator_type = int[];
|
||||
accumulator_type accumulator = { 0, (reset<Components>(entity), 0)... };
|
||||
accumulator_type accumulator = { 0, (reset<Component>(entity), 0)... };
|
||||
available.push_back(entity);
|
||||
entities[entity].reset();
|
||||
(void)accumulator;
|
||||
@@ -295,15 +297,15 @@ public:
|
||||
template<typename Comp, typename... Args>
|
||||
Comp & assign(entity_type entity, Args... args) {
|
||||
assert(valid(entity));
|
||||
entities[entity].set(ident<Components...>.template get<Comp>());
|
||||
return pool.template construct<Comp>(entity, args...);
|
||||
entities[entity].set(identifier<Comp>::value);
|
||||
return std::get<identifier<Comp>::value>(pool).construct(entity, args...);
|
||||
}
|
||||
|
||||
template<typename Comp>
|
||||
void remove(entity_type entity) {
|
||||
assert(valid(entity));
|
||||
entities[entity].reset(ident<Components...>.template get<Comp>());
|
||||
pool.template destroy<Comp>(entity);
|
||||
entities[entity].reset(identifier<Comp>::value);
|
||||
std::get<identifier<Comp>::value>(pool).destroy(entity);
|
||||
}
|
||||
|
||||
template<typename... Comp>
|
||||
@@ -312,31 +314,34 @@ public:
|
||||
using accumulator_type = bool[];
|
||||
bool all = true;
|
||||
auto &mask = entities[entity];
|
||||
accumulator_type accumulator = { true, (all = all && mask.test(ident<Components...>.template get<Comp>()))... };
|
||||
accumulator_type accumulator = { true, (all = all && mask.test(identifier<Comp>::value))... };
|
||||
(void)accumulator;
|
||||
return all;
|
||||
}
|
||||
|
||||
template<typename Comp>
|
||||
const Comp & get(entity_type entity) const noexcept {
|
||||
return pool.template get<Comp>(entity);
|
||||
assert(valid(entity));
|
||||
return std::get<identifier<Comp>::value>(pool).get(entity);
|
||||
}
|
||||
|
||||
template<typename Comp>
|
||||
Comp & get(entity_type entity) noexcept {
|
||||
return pool.template get<Comp>(entity);
|
||||
assert(valid(entity));
|
||||
return std::get<identifier<Comp>::value>(pool).get(entity);
|
||||
}
|
||||
|
||||
template<typename Comp, typename... Args>
|
||||
Comp & replace(entity_type entity, Args... args) {
|
||||
return (pool.template get<Comp>(entity) = Comp{args...});
|
||||
assert(valid(entity));
|
||||
return (std::get<identifier<Comp>::value>(pool).get(entity) = Comp{args...});
|
||||
}
|
||||
|
||||
template<typename Comp, typename... Args>
|
||||
Comp & accomodate(entity_type entity, Args... args) {
|
||||
assert(valid(entity));
|
||||
|
||||
return (entities[entity].test(ident<Components...>.template get<Comp>())
|
||||
return (entities[entity].test(identifier<Comp>::value)
|
||||
? this->template replace<Comp>(entity, std::forward<Args>(args)...)
|
||||
: this->template assign<Comp>(entity, std::forward<Args>(args)...));
|
||||
}
|
||||
@@ -345,27 +350,51 @@ public:
|
||||
assert(valid(from));
|
||||
using accumulator_type = int[];
|
||||
auto to = create();
|
||||
accumulator_type accumulator = { 0, (clone<Components>(to, from), 0)... };
|
||||
accumulator_type accumulator = { 0, (clone<Component>(to, from), 0)... };
|
||||
(void)accumulator;
|
||||
return to;
|
||||
}
|
||||
|
||||
template<typename Comp>
|
||||
Comp & copy(entity_type to, entity_type from) {
|
||||
return (pool.template get<Comp>(to) = pool.template get<Comp>(from));
|
||||
assert(valid(to));
|
||||
assert(valid(from));
|
||||
auto &&cpool = std::get<identifier<Comp>::value>(pool);
|
||||
return (cpool.get(to) = cpool.get(from));
|
||||
}
|
||||
|
||||
void copy(entity_type to, entity_type from) {
|
||||
assert(valid(to));
|
||||
assert(valid(from));
|
||||
using accumulator_type = int[];
|
||||
accumulator_type accumulator = { 0, (sync<Components>(to, from), 0)... };
|
||||
accumulator_type accumulator = { 0, (sync<Component>(to, from), 0)... };
|
||||
(void)accumulator;
|
||||
}
|
||||
|
||||
template<typename Comp>
|
||||
void swap(entity_type lhs, entity_type rhs) {
|
||||
assert(valid(lhs));
|
||||
assert(valid(rhs));
|
||||
std::get<identifier<Comp>::value>(pool).swap(lhs, rhs);
|
||||
}
|
||||
|
||||
template<typename Comp, typename Compare>
|
||||
void sort(Compare compare) {
|
||||
std::get<identifier<Comp>::value>(pool).sort(std::move(compare));
|
||||
}
|
||||
|
||||
template<typename To, typename From>
|
||||
void sort() {
|
||||
auto &&to = std::get<identifier<To>::value>(pool);
|
||||
auto &&from = std::get<identifier<From>::value>(pool);
|
||||
to.respect(from);
|
||||
}
|
||||
|
||||
template<typename Comp>
|
||||
void reset(entity_type entity) {
|
||||
assert(valid(entity));
|
||||
|
||||
if(entities[entity].test(ident<Components...>.template get<Comp>())) {
|
||||
if(entities[entity].test(identifier<Comp>::value)) {
|
||||
remove<Comp>(entity);
|
||||
}
|
||||
}
|
||||
@@ -373,25 +402,29 @@ public:
|
||||
template<typename Comp>
|
||||
void reset() {
|
||||
for(entity_type entity = 0, last = entity_type(entities.size()); entity < last; ++entity) {
|
||||
if(entities[entity].test(ident<Components...>.template get<Comp>())) {
|
||||
if(entities[entity].test(identifier<Comp>::value)) {
|
||||
remove<Comp>(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void reset() {
|
||||
using accumulator_type = int[];
|
||||
accumulator_type acc = { 0, (std::get<identifier<Component>::value>(pool).reset(), 0)... };
|
||||
entities.clear();
|
||||
available.clear();
|
||||
pool.reset();
|
||||
(void)acc;
|
||||
}
|
||||
|
||||
template<typename... Comp>
|
||||
std::enable_if_t<(sizeof...(Comp) == 1), view_type<Comp...>>
|
||||
view() noexcept { return view_type<Comp...>{pool}; }
|
||||
// view_type<Comp...> is fine as well, but for the fact that MSVC dislikes it
|
||||
std::enable_if_t<(sizeof...(Comp) == 1), View<pool_type, identifier<Comp>::value...>>
|
||||
view() noexcept { return view_type<Comp...>{&pool}; }
|
||||
|
||||
template<typename... Comp>
|
||||
std::enable_if_t<(sizeof...(Comp) > 1), view_type<Comp...>>
|
||||
view() noexcept { return view_type<Comp...>{pool, entities.data()}; }
|
||||
// view_type<Comp...> is fine as well, but for the fact that MSVC dislikes it
|
||||
std::enable_if_t<(sizeof...(Comp) > 1), View<pool_type, identifier<Comp>::value...>>
|
||||
view() noexcept { return view_type<Comp...>{&pool, entities.data()}; }
|
||||
|
||||
private:
|
||||
std::vector<mask_type> entities;
|
||||
@@ -400,12 +433,8 @@ private:
|
||||
};
|
||||
|
||||
|
||||
template<typename Entity, typename... Components>
|
||||
using StandardRegistry = Registry<ComponentPool<Entity, Components...>>;
|
||||
|
||||
|
||||
template<typename... Components>
|
||||
using DefaultRegistry = Registry<ComponentPool<std::uint32_t, Components...>>;
|
||||
template<typename... Component>
|
||||
using DefaultRegistry = Registry<std::uint32_t, Component...>;
|
||||
|
||||
|
||||
}
|
||||
|
||||
277
src/sparse_set.hpp
Normal file
277
src/sparse_set.hpp
Normal file
@@ -0,0 +1,277 @@
|
||||
#ifndef ENTT_COMPONENT_POOL_HPP
|
||||
#define ENTT_COMPONENT_POOL_HPP
|
||||
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
#include <numeric>
|
||||
#include <vector>
|
||||
#include <cstddef>
|
||||
#include <cassert>
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
template<typename...>
|
||||
class SparseSet;
|
||||
|
||||
|
||||
template<typename Index>
|
||||
class SparseSet<Index> {
|
||||
struct SparseSetIterator;
|
||||
|
||||
public:
|
||||
using index_type = Index;
|
||||
using pos_type = index_type;
|
||||
using size_type = std::size_t;
|
||||
using iterator_type = SparseSetIterator;
|
||||
|
||||
private:
|
||||
struct SparseSetIterator {
|
||||
using value_type = index_type;
|
||||
|
||||
SparseSetIterator(const std::vector<index_type> *direct, size_type pos)
|
||||
: direct{direct}, pos{pos}
|
||||
{}
|
||||
|
||||
SparseSetIterator & operator++() noexcept {
|
||||
return --pos, *this;
|
||||
}
|
||||
|
||||
SparseSetIterator operator++(int) noexcept {
|
||||
SparseSetIterator orig = *this;
|
||||
return ++(*this), orig;
|
||||
}
|
||||
|
||||
bool operator==(const SparseSetIterator &other) const noexcept {
|
||||
return other.pos == pos && other.direct == direct;
|
||||
}
|
||||
|
||||
bool operator!=(const SparseSetIterator &other) const noexcept {
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
value_type operator*() const noexcept {
|
||||
return (*direct)[pos-1];
|
||||
}
|
||||
|
||||
private:
|
||||
const std::vector<index_type> *direct;
|
||||
size_type pos;
|
||||
};
|
||||
|
||||
inline bool valid(Index idx) const noexcept {
|
||||
return idx < reverse.size() && reverse[idx] < direct.size() && direct[reverse[idx]] == idx;
|
||||
}
|
||||
|
||||
public:
|
||||
explicit SparseSet() = default;
|
||||
|
||||
SparseSet(const SparseSet &) = delete;
|
||||
SparseSet(SparseSet &&) = default;
|
||||
|
||||
~SparseSet() noexcept {
|
||||
assert(empty());
|
||||
}
|
||||
|
||||
SparseSet & operator=(const SparseSet &) = delete;
|
||||
SparseSet & operator=(SparseSet &&) = default;
|
||||
|
||||
size_type size() const noexcept {
|
||||
return direct.size();
|
||||
}
|
||||
|
||||
size_t capacity() const noexcept {
|
||||
return direct.capacity();
|
||||
}
|
||||
|
||||
bool empty() const noexcept {
|
||||
return direct.empty();
|
||||
}
|
||||
|
||||
const index_type * data() const noexcept {
|
||||
return direct.data();
|
||||
}
|
||||
|
||||
iterator_type begin() const noexcept {
|
||||
return SparseSetIterator{&direct, direct.size()};
|
||||
}
|
||||
|
||||
iterator_type end() const noexcept {
|
||||
return SparseSetIterator{&direct, 0};
|
||||
}
|
||||
|
||||
bool has(index_type idx) const noexcept {
|
||||
return valid(idx);
|
||||
}
|
||||
|
||||
pos_type get(index_type idx) const noexcept {
|
||||
assert(valid(idx));
|
||||
return reverse[idx];
|
||||
}
|
||||
|
||||
pos_type construct(index_type idx) {
|
||||
assert(!valid(idx));
|
||||
|
||||
if(!(idx < reverse.size())) {
|
||||
reverse.resize(idx+1);
|
||||
}
|
||||
|
||||
auto pos = pos_type(direct.size());
|
||||
reverse[idx] = pos;
|
||||
direct.emplace_back(idx);
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
pos_type destroy(index_type idx) {
|
||||
assert(valid(idx));
|
||||
|
||||
auto last = direct.size() - 1;
|
||||
auto pos = reverse[idx];
|
||||
|
||||
reverse[direct[last]] = pos;
|
||||
direct[pos] = direct[last];
|
||||
direct.pop_back();
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
void swap(index_type lhs, index_type rhs) {
|
||||
assert(valid(lhs));
|
||||
assert(valid(rhs));
|
||||
|
||||
std::swap(direct[reverse[lhs]], direct[reverse[rhs]]);
|
||||
std::swap(reverse[lhs], reverse[rhs]);
|
||||
}
|
||||
|
||||
void reset() {
|
||||
reverse.clear();
|
||||
direct.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<pos_type> reverse;
|
||||
std::vector<index_type> direct;
|
||||
};
|
||||
|
||||
|
||||
template<typename Index, typename Type>
|
||||
class SparseSet<Index, Type> final: public SparseSet<Index> {
|
||||
template<typename Compare>
|
||||
void arrange(Compare compare) {
|
||||
const auto *data = SparseSet<Index>::data();
|
||||
const auto size = SparseSet<Index>::size();
|
||||
std::vector<pos_type> copy(size);
|
||||
|
||||
std::iota(copy.begin(), copy.end(), pos_type{});
|
||||
std::sort(copy.begin(), copy.end(), compare);
|
||||
|
||||
for(pos_type i = 0; i < copy.size(); ++i) {
|
||||
const auto target = i;
|
||||
auto curr = i;
|
||||
|
||||
while(copy[curr] != target) {
|
||||
SparseSet<Index>::swap(*(data + copy[curr]), *(data + curr));
|
||||
std::swap(instances[copy[curr]], instances[curr]);
|
||||
std::swap(copy[curr], curr);
|
||||
}
|
||||
|
||||
copy[curr] = curr;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
using type = Type;
|
||||
using index_type = typename SparseSet<Index>::index_type;
|
||||
using pos_type = typename SparseSet<Index>::pos_type;
|
||||
using size_type = typename SparseSet<Index>::size_type;
|
||||
using iterator_type = typename SparseSet<Index>::iterator_type;
|
||||
|
||||
explicit SparseSet() = default;
|
||||
|
||||
SparseSet(const SparseSet &) = delete;
|
||||
SparseSet(SparseSet &&) = default;
|
||||
|
||||
SparseSet & operator=(const SparseSet &) = delete;
|
||||
SparseSet & operator=(SparseSet &&) = default;
|
||||
|
||||
type * raw() noexcept {
|
||||
return instances.data();
|
||||
}
|
||||
|
||||
const type * raw() const noexcept {
|
||||
return instances.data();
|
||||
}
|
||||
|
||||
const type & get(index_type idx) const noexcept {
|
||||
return instances[SparseSet<Index>::get(idx)];
|
||||
}
|
||||
|
||||
type & get(index_type idx) noexcept {
|
||||
return const_cast<type &>(const_cast<const SparseSet *>(this)->get(idx));
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
type & construct(index_type idx, Args&&... args) {
|
||||
SparseSet<Index>::construct(idx);
|
||||
instances.push_back({ std::forward<Args>(args)... });
|
||||
return instances.back();
|
||||
}
|
||||
|
||||
void destroy(index_type idx) {
|
||||
auto pos = SparseSet<Index>::destroy(idx);
|
||||
instances[pos] = std::move(instances[SparseSet<Index>::size()]);
|
||||
instances.pop_back();
|
||||
}
|
||||
|
||||
void swap(index_type lhs, index_type rhs) {
|
||||
std::swap(instances[SparseSet<Index>::get(lhs)], instances[SparseSet<Index>::get(rhs)]);
|
||||
}
|
||||
|
||||
template<typename Compare>
|
||||
void sort(Compare compare) {
|
||||
arrange([this, compare = std::move(compare)](auto lhs, auto rhs) {
|
||||
return !compare(instances[lhs], instances[rhs]);
|
||||
});
|
||||
}
|
||||
|
||||
template<typename Idx>
|
||||
void respect(const SparseSet<Idx> &other) {
|
||||
const auto *data = SparseSet<Index>::data();
|
||||
|
||||
arrange([data, &other](auto lhs, auto rhs) {
|
||||
auto eLhs = *(data + lhs);
|
||||
auto eRhs = *(data + rhs);
|
||||
|
||||
bool bLhs = other.has(eLhs);
|
||||
bool bRhs = other.has(eRhs);
|
||||
bool compare = false;
|
||||
|
||||
if(bLhs && bRhs) {
|
||||
compare = other.get(eLhs) < other.get(eRhs);
|
||||
} else if(!bLhs && !bRhs) {
|
||||
compare = eLhs < eRhs;
|
||||
} else {
|
||||
compare = bRhs;
|
||||
}
|
||||
|
||||
return compare;
|
||||
});
|
||||
}
|
||||
|
||||
void reset() {
|
||||
SparseSet<Index>::reset();
|
||||
instances.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<type> instances;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_COMPONENT_POOL_HPP
|
||||
@@ -11,7 +11,7 @@ set(TARGET_BENCHMARK benchmark)
|
||||
|
||||
# Test TARGET_ENTT
|
||||
|
||||
add_executable(${TARGET_ENTT} component_pool.cpp registry.cpp)
|
||||
add_executable(${TARGET_ENTT} ident.cpp registry.cpp sparse_set.cpp)
|
||||
target_include_directories(${TARGET_ENTT} PRIVATE ${PROJECT_SRC_DIR})
|
||||
target_link_libraries(${TARGET_ENTT} PRIVATE ${COMMON_LINK_LIBS})
|
||||
add_test(NAME ${TARGET_ENTT} COMMAND ${TARGET_ENTT})
|
||||
|
||||
@@ -38,6 +38,7 @@ TEST(DefaultRegistry, Construct) {
|
||||
std::cout << "Constructing 10000000 entities" << std::endl;
|
||||
|
||||
Timer timer;
|
||||
|
||||
for (uint64_t i = 0; i < 10000000L; i++) {
|
||||
registry.create();
|
||||
}
|
||||
|
||||
@@ -1,166 +0,0 @@
|
||||
#include <cstddef>
|
||||
#include <gtest/gtest.h>
|
||||
#include <component_pool.hpp>
|
||||
|
||||
TEST(ComponentPool, Functionalities) {
|
||||
using pool_type = entt::ComponentPool<std::uint8_t, int, double>;
|
||||
|
||||
pool_type pool{0};
|
||||
|
||||
ASSERT_TRUE(pool.empty<int>());
|
||||
ASSERT_TRUE(pool.empty<double>());
|
||||
ASSERT_EQ(pool.capacity<int>(), pool_type::size_type{0});
|
||||
ASSERT_EQ(pool.capacity<double>(), pool_type::size_type{0});
|
||||
ASSERT_EQ(pool.size<int>(), pool_type::size_type{0});
|
||||
ASSERT_EQ(pool.size<double>(), pool_type::size_type{0});
|
||||
ASSERT_EQ(pool.begin<int>(), pool.end<int>());
|
||||
ASSERT_EQ(pool.begin<double>(), pool.end<double>());
|
||||
ASSERT_FALSE(pool.has<int>(0));
|
||||
ASSERT_FALSE(pool.has<double>(0));
|
||||
}
|
||||
|
||||
TEST(ComponentPool, ConstructDestroy) {
|
||||
using pool_type = entt::ComponentPool<std::uint8_t, double, int>;
|
||||
|
||||
pool_type pool{4};
|
||||
|
||||
ASSERT_EQ(pool.construct<int>(0, 42), 42);
|
||||
ASSERT_FALSE(pool.empty<int>());
|
||||
ASSERT_TRUE(pool.empty<double>());
|
||||
ASSERT_EQ(pool.capacity<int>(), pool_type::size_type{4});
|
||||
ASSERT_EQ(pool.capacity<double>(), pool_type::size_type{4});
|
||||
ASSERT_EQ(pool.size<int>(), pool_type::size_type{1});
|
||||
ASSERT_EQ(pool.size<double>(), pool_type::size_type{0});
|
||||
ASSERT_TRUE(pool.has<int>(0));
|
||||
ASSERT_FALSE(pool.has<double>(0));
|
||||
ASSERT_FALSE(pool.has<int>(1));
|
||||
ASSERT_FALSE(pool.has<double>(1));
|
||||
|
||||
ASSERT_EQ(pool.construct<int>(1), 0);
|
||||
ASSERT_FALSE(pool.empty<int>());
|
||||
ASSERT_TRUE(pool.empty<double>());
|
||||
ASSERT_EQ(pool.capacity<int>(), pool_type::size_type{4});
|
||||
ASSERT_EQ(pool.capacity<double>(), pool_type::size_type{4});
|
||||
ASSERT_EQ(pool.size<int>(), pool_type::size_type{2});
|
||||
ASSERT_EQ(pool.size<double>(), pool_type::size_type{0});
|
||||
ASSERT_TRUE(pool.has<int>(0));
|
||||
ASSERT_FALSE(pool.has<double>(0));
|
||||
ASSERT_TRUE(pool.has<int>(1));
|
||||
ASSERT_FALSE(pool.has<double>(1));
|
||||
ASSERT_NE(pool.get<int>(0), pool.get<int>(1));
|
||||
ASSERT_NE(&pool.get<int>(0), &pool.get<int>(1));
|
||||
|
||||
ASSERT_NO_THROW(pool.destroy<int>(0));
|
||||
ASSERT_FALSE(pool.empty<int>());
|
||||
ASSERT_TRUE(pool.empty<double>());
|
||||
ASSERT_EQ(pool.capacity<int>(), pool_type::size_type{4});
|
||||
ASSERT_EQ(pool.capacity<double>(), pool_type::size_type{4});
|
||||
ASSERT_EQ(pool.size<int>(), pool_type::size_type{1});
|
||||
ASSERT_EQ(pool.size<double>(), pool_type::size_type{0});
|
||||
ASSERT_FALSE(pool.has<int>(0));
|
||||
ASSERT_FALSE(pool.has<double>(0));
|
||||
ASSERT_TRUE(pool.has<int>(1));
|
||||
ASSERT_FALSE(pool.has<double>(1));
|
||||
|
||||
ASSERT_NO_THROW(pool.destroy<int>(1));
|
||||
ASSERT_TRUE(pool.empty<int>());
|
||||
ASSERT_TRUE(pool.empty<double>());
|
||||
ASSERT_EQ(pool.capacity<int>(), pool_type::size_type{4});
|
||||
ASSERT_EQ(pool.capacity<double>(), pool_type::size_type{4});
|
||||
ASSERT_EQ(pool.size<int>(), pool_type::size_type{0});
|
||||
ASSERT_EQ(pool.size<int>(), pool_type::size_type{0});
|
||||
ASSERT_FALSE(pool.has<int>(0));
|
||||
ASSERT_FALSE(pool.has<double>(0));
|
||||
ASSERT_FALSE(pool.has<int>(1));
|
||||
ASSERT_FALSE(pool.has<double>(1));
|
||||
|
||||
int *comp[] = {
|
||||
&pool.construct<int>(0, 0),
|
||||
&pool.construct<int>(1, 1),
|
||||
nullptr,
|
||||
&pool.construct<int>(3, 3)
|
||||
};
|
||||
|
||||
ASSERT_FALSE(pool.empty<int>());
|
||||
ASSERT_TRUE(pool.empty<double>());
|
||||
ASSERT_EQ(pool.capacity<int>(), pool_type::size_type{4});
|
||||
ASSERT_EQ(pool.capacity<double>(), pool_type::size_type{4});
|
||||
ASSERT_EQ(pool.size<int>(), pool_type::size_type{3});
|
||||
ASSERT_EQ(pool.size<double>(), pool_type::size_type{0});
|
||||
ASSERT_TRUE(pool.has<int>(0));
|
||||
ASSERT_FALSE(pool.has<double>(0));
|
||||
ASSERT_TRUE(pool.has<int>(1));
|
||||
ASSERT_FALSE(pool.has<double>(1));
|
||||
ASSERT_FALSE(pool.has<int>(2));
|
||||
ASSERT_FALSE(pool.has<double>(2));
|
||||
ASSERT_TRUE(pool.has<int>(3));
|
||||
ASSERT_FALSE(pool.has<double>(3));
|
||||
ASSERT_EQ(&pool.get<int>(0), comp[0]);
|
||||
ASSERT_EQ(&pool.get<int>(1), comp[1]);
|
||||
ASSERT_EQ(&pool.get<int>(3), comp[3]);
|
||||
ASSERT_EQ(pool.get<int>(0), 0);
|
||||
ASSERT_EQ(pool.get<int>(1), 1);
|
||||
ASSERT_EQ(pool.get<int>(3), 3);
|
||||
|
||||
ASSERT_NO_THROW(pool.destroy<int>(0));
|
||||
ASSERT_NO_THROW(pool.destroy<int>(1));
|
||||
ASSERT_NO_THROW(pool.destroy<int>(3));
|
||||
}
|
||||
|
||||
TEST(ComponentPool, HasGet) {
|
||||
using pool_type = entt::ComponentPool<std::uint8_t, int, char>;
|
||||
|
||||
pool_type pool;
|
||||
const pool_type &cpool = pool;
|
||||
|
||||
int &comp = pool.construct<int>(0, 42);
|
||||
|
||||
ASSERT_EQ(pool.get<int>(0), comp);
|
||||
ASSERT_EQ(pool.get<int>(0), 42);
|
||||
ASSERT_TRUE(pool.has<int>(0));
|
||||
|
||||
ASSERT_EQ(cpool.get<int>(0), comp);
|
||||
ASSERT_EQ(cpool.get<int>(0), 42);
|
||||
ASSERT_TRUE(cpool.has<int>(0));
|
||||
|
||||
ASSERT_NO_THROW(pool.destroy<int>(0));
|
||||
}
|
||||
|
||||
TEST(ComponentPool, BeginEndReset) {
|
||||
using pool_type = entt::ComponentPool<std::uint8_t, int, char>;
|
||||
|
||||
pool_type pool{2};
|
||||
|
||||
ASSERT_EQ(pool.construct<int>(0, 0), 0);
|
||||
ASSERT_EQ(pool.construct<int>(2, 2), 2);
|
||||
ASSERT_EQ(pool.construct<int>(3, 3), 3);
|
||||
ASSERT_EQ(pool.construct<int>(1, 1), 1);
|
||||
|
||||
ASSERT_EQ(pool.size<int>(), decltype(pool.size<int>()){4});
|
||||
ASSERT_EQ(*(pool.begin<int>()+0), typename pool_type::entity_type{0});
|
||||
ASSERT_EQ(*(pool.begin<int>()+1), typename pool_type::entity_type{2});
|
||||
ASSERT_EQ(*(pool.begin<int>()+2), typename pool_type::entity_type{3});
|
||||
ASSERT_EQ(*(pool.begin<int>()+3), typename pool_type::entity_type{1});
|
||||
|
||||
pool.destroy<int>(2);
|
||||
|
||||
ASSERT_EQ(pool.size<int>(), decltype(pool.size<int>()){3});
|
||||
ASSERT_EQ(*(pool.begin<int>()+0), typename pool_type::entity_type{0});
|
||||
ASSERT_EQ(*(pool.begin<int>()+1), typename pool_type::entity_type{1});
|
||||
ASSERT_EQ(*(pool.begin<int>()+2), typename pool_type::entity_type{3});
|
||||
|
||||
ASSERT_EQ(pool.construct<char>(0, 'c'), 'c');
|
||||
|
||||
ASSERT_FALSE(pool.empty<int>());
|
||||
ASSERT_FALSE(pool.empty<char>());
|
||||
|
||||
ASSERT_NO_THROW(pool.reset<char>());
|
||||
|
||||
ASSERT_FALSE(pool.empty<int>());
|
||||
ASSERT_TRUE(pool.empty<char>());
|
||||
|
||||
ASSERT_NO_THROW(pool.reset());
|
||||
|
||||
ASSERT_TRUE(pool.empty<int>());
|
||||
ASSERT_TRUE(pool.empty<char>());
|
||||
}
|
||||
26
test/ident.cpp
Normal file
26
test/ident.cpp
Normal file
@@ -0,0 +1,26 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <ident.hpp>
|
||||
|
||||
struct A {};
|
||||
struct B {};
|
||||
|
||||
TEST(Identifier, Uniqueness) {
|
||||
constexpr auto ID = entt::ident<A, B>;
|
||||
constexpr A a;
|
||||
constexpr B b;
|
||||
|
||||
ASSERT_NE(ID.get<A>(), ID.get<B>());
|
||||
ASSERT_EQ(ID.get<A>(), ID.get<decltype(a)>());
|
||||
ASSERT_NE(ID.get<A>(), ID.get<decltype(b)>());
|
||||
ASSERT_EQ(ID.get<A>(), ID.get<A>());
|
||||
ASSERT_EQ(ID.get<B>(), ID.get<B>());
|
||||
|
||||
// test uses in constant expressions
|
||||
switch(ID.get<B>()) {
|
||||
case ID.get<A>():
|
||||
FAIL();
|
||||
break;
|
||||
case ID.get<B>():
|
||||
SUCCEED();
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <registry.hpp>
|
||||
#include <functional>
|
||||
|
||||
TEST(DefaultRegistry, Functionalities) {
|
||||
using registry_type = entt::DefaultRegistry<int, char>;
|
||||
@@ -10,12 +11,20 @@ TEST(DefaultRegistry, Functionalities) {
|
||||
ASSERT_EQ(registry.capacity(), registry_type::size_type{0});
|
||||
ASSERT_TRUE(registry.empty());
|
||||
|
||||
ASSERT_EQ(registry.size<int>(), registry_type::size_type{0});
|
||||
ASSERT_EQ(registry.size<char>(), registry_type::size_type{0});
|
||||
ASSERT_EQ(registry.capacity<int>(), registry_type::size_type{0});
|
||||
ASSERT_EQ(registry.capacity<char>(), registry_type::size_type{0});
|
||||
ASSERT_TRUE(registry.empty<int>());
|
||||
ASSERT_TRUE(registry.empty<char>());
|
||||
|
||||
registry_type::entity_type e1 = registry.create();
|
||||
registry_type::entity_type e2 = registry.create<int, char>();
|
||||
|
||||
ASSERT_EQ(registry.size<int>(), registry_type::size_type{1});
|
||||
ASSERT_EQ(registry.size<char>(), registry_type::size_type{1});
|
||||
ASSERT_GE(registry.capacity<int>(), registry_type::size_type{1});
|
||||
ASSERT_GE(registry.capacity<char>(), registry_type::size_type{1});
|
||||
ASSERT_FALSE(registry.empty<int>());
|
||||
ASSERT_FALSE(registry.empty<char>());
|
||||
|
||||
@@ -95,16 +104,28 @@ TEST(DefaultRegistry, Functionalities) {
|
||||
|
||||
registry.create<int, char>();
|
||||
|
||||
ASSERT_EQ(registry.size<int>(), registry_type::size_type{1});
|
||||
ASSERT_EQ(registry.size<char>(), registry_type::size_type{1});
|
||||
ASSERT_GE(registry.capacity<int>(), registry_type::size_type{1});
|
||||
ASSERT_GE(registry.capacity<char>(), registry_type::size_type{1});
|
||||
ASSERT_FALSE(registry.empty<int>());
|
||||
ASSERT_FALSE(registry.empty<char>());
|
||||
|
||||
ASSERT_NO_THROW(registry.reset<int>());
|
||||
|
||||
ASSERT_EQ(registry.size<int>(), registry_type::size_type{0});
|
||||
ASSERT_EQ(registry.size<char>(), registry_type::size_type{1});
|
||||
ASSERT_GE(registry.capacity<int>(), registry_type::size_type{0});
|
||||
ASSERT_GE(registry.capacity<char>(), registry_type::size_type{1});
|
||||
ASSERT_TRUE(registry.empty<int>());
|
||||
ASSERT_FALSE(registry.empty<char>());
|
||||
|
||||
ASSERT_NO_THROW(registry.reset());
|
||||
|
||||
ASSERT_EQ(registry.size<int>(), registry_type::size_type{0});
|
||||
ASSERT_EQ(registry.size<char>(), registry_type::size_type{0});
|
||||
ASSERT_GE(registry.capacity<int>(), registry_type::size_type{0});
|
||||
ASSERT_GE(registry.capacity<char>(), registry_type::size_type{1});
|
||||
ASSERT_TRUE(registry.empty<int>());
|
||||
ASSERT_TRUE(registry.empty<char>());
|
||||
|
||||
@@ -113,6 +134,11 @@ TEST(DefaultRegistry, Functionalities) {
|
||||
|
||||
ASSERT_NO_THROW(registry.reset<int>(e1));
|
||||
ASSERT_NO_THROW(registry.reset<int>(e2));
|
||||
|
||||
ASSERT_EQ(registry.size<int>(), registry_type::size_type{0});
|
||||
ASSERT_EQ(registry.size<char>(), registry_type::size_type{0});
|
||||
ASSERT_GE(registry.capacity<int>(), registry_type::size_type{0});
|
||||
ASSERT_GE(registry.capacity<char>(), registry_type::size_type{0});
|
||||
ASSERT_TRUE(registry.empty<int>());
|
||||
}
|
||||
|
||||
@@ -149,6 +175,99 @@ TEST(DefaultRegistry, Copy) {
|
||||
registry.reset();
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, Swap) {
|
||||
using registry_type = entt::DefaultRegistry<int, char>;
|
||||
|
||||
registry_type registry;
|
||||
|
||||
registry_type::entity_type e1 = registry.create<int, char>();
|
||||
registry_type::entity_type e2 = registry.create<int, char>();
|
||||
|
||||
registry.get<int>(e1) = 0;
|
||||
registry.get<char>(e1) = 'a';
|
||||
registry.get<int>(e2) = 1;
|
||||
registry.get<char>(e2) = 'b';
|
||||
|
||||
registry.swap<int>(e1, e2);
|
||||
|
||||
ASSERT_EQ(registry.get<int>(e1), 1);
|
||||
ASSERT_EQ(registry.get<char>(e1), 'a');
|
||||
ASSERT_EQ(registry.get<int>(e2), 0);
|
||||
ASSERT_EQ(registry.get<char>(e2), 'b');
|
||||
|
||||
registry.reset();
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, SortSingle) {
|
||||
using registry_type = entt::DefaultRegistry<int>;
|
||||
|
||||
registry_type registry;
|
||||
|
||||
registry_type::entity_type e1 = registry.create();
|
||||
registry_type::entity_type e2 = registry.create();
|
||||
registry_type::entity_type e3 = registry.create();
|
||||
|
||||
auto val = 0;
|
||||
|
||||
registry.assign<int>(e1, val++);
|
||||
registry.assign<int>(e2, val++);
|
||||
registry.assign<int>(e3, val++);
|
||||
|
||||
for(auto entity: registry.view<int>()) {
|
||||
ASSERT_EQ(registry.get<int>(entity), --val);
|
||||
}
|
||||
|
||||
registry.sort<int>(std::less<int>{});
|
||||
|
||||
for(auto entity: registry.view<int>()) {
|
||||
ASSERT_EQ(registry.get<int>(entity), val++);
|
||||
}
|
||||
|
||||
registry.reset();
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, SortMulti) {
|
||||
using registry_type = entt::DefaultRegistry<int, unsigned int>;
|
||||
|
||||
registry_type registry;
|
||||
|
||||
registry_type::entity_type e1 = registry.create();
|
||||
registry_type::entity_type e2 = registry.create();
|
||||
registry_type::entity_type e3 = registry.create();
|
||||
|
||||
auto uval = 0u;
|
||||
auto ival = 0;
|
||||
|
||||
registry.assign<unsigned int>(e1, uval++);
|
||||
registry.assign<unsigned int>(e2, uval++);
|
||||
registry.assign<unsigned int>(e3, uval++);
|
||||
|
||||
registry.assign<int>(e1, ival++);
|
||||
registry.assign<int>(e2, ival++);
|
||||
registry.assign<int>(e3, ival++);
|
||||
|
||||
for(auto entity: registry.view<unsigned int>()) {
|
||||
ASSERT_EQ(registry.get<unsigned int>(entity), --uval);
|
||||
}
|
||||
|
||||
for(auto entity: registry.view<int>()) {
|
||||
ASSERT_EQ(registry.get<int>(entity), --ival);
|
||||
}
|
||||
|
||||
registry.sort<unsigned int>(std::less<unsigned int>{});
|
||||
registry.sort<int, unsigned int>();
|
||||
|
||||
for(auto entity: registry.view<unsigned int>()) {
|
||||
ASSERT_EQ(registry.get<unsigned int>(entity), uval++);
|
||||
}
|
||||
|
||||
for(auto entity: registry.view<int>()) {
|
||||
ASSERT_EQ(registry.get<int>(entity), ival++);
|
||||
}
|
||||
|
||||
registry.reset();
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, ViewSingleComponent) {
|
||||
using registry_type = entt::DefaultRegistry<int, char>;
|
||||
|
||||
@@ -163,11 +282,11 @@ TEST(DefaultRegistry, ViewSingleComponent) {
|
||||
auto view = registry.view<char>();
|
||||
|
||||
ASSERT_NE(view.begin(), view.end());
|
||||
ASSERT_EQ(view.size(), typename registry_type::view_type<char>::size_type{1});
|
||||
ASSERT_EQ(view.size(), typename decltype(view)::size_type{1});
|
||||
|
||||
registry.assign<char>(e1);
|
||||
|
||||
ASSERT_EQ(view.size(), typename registry_type::view_type<char>::size_type{2});
|
||||
ASSERT_EQ(view.size(), typename decltype(view)::size_type{2});
|
||||
|
||||
registry.remove<char>(e1);
|
||||
registry.remove<char>(e2);
|
||||
@@ -199,7 +318,7 @@ TEST(DefaultRegistry, ViewMultipleComponent) {
|
||||
ASSERT_NO_THROW(registry.reset());
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, EmptyViewSingleComponent) {
|
||||
TEST(DefaultRegistry, ViewSingleComponentEmpty) {
|
||||
using registry_type = entt::DefaultRegistry<char, int, double>;
|
||||
|
||||
registry_type registry;
|
||||
@@ -211,10 +330,15 @@ TEST(DefaultRegistry, EmptyViewSingleComponent) {
|
||||
|
||||
ASSERT_EQ(view.size(), registry_type::size_type{0});
|
||||
|
||||
for(auto entity: view) {
|
||||
(void)entity;
|
||||
FAIL();
|
||||
}
|
||||
|
||||
registry.reset();
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, EmptyViewMultipleComponent) {
|
||||
TEST(DefaultRegistry, ViewMultipleComponentEmpty) {
|
||||
using registry_type = entt::DefaultRegistry<char, int, float, double>;
|
||||
|
||||
registry_type registry;
|
||||
|
||||
390
test/sparse_set.cpp
Normal file
390
test/sparse_set.cpp
Normal file
@@ -0,0 +1,390 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <sparse_set.hpp>
|
||||
|
||||
TEST(SparseSetNoType, Functionalities) {
|
||||
using SparseSet = entt::SparseSet<unsigned int>;
|
||||
|
||||
SparseSet set;
|
||||
|
||||
ASSERT_TRUE(set.empty());
|
||||
ASSERT_EQ(set.size(), 0u);
|
||||
ASSERT_EQ(set.capacity(), 0u);
|
||||
ASSERT_EQ(set.begin(), set.end());
|
||||
ASSERT_FALSE(set.has(0));
|
||||
ASSERT_FALSE(set.has(42));
|
||||
|
||||
ASSERT_EQ(set.construct(42), 0u);
|
||||
|
||||
ASSERT_FALSE(set.empty());
|
||||
ASSERT_EQ(set.size(), 1u);
|
||||
ASSERT_GE(set.capacity(), 1u);
|
||||
ASSERT_NE(set.begin(), set.end());
|
||||
ASSERT_FALSE(set.has(0));
|
||||
ASSERT_TRUE(set.has(42));
|
||||
ASSERT_EQ(set.get(42), 0u);
|
||||
|
||||
ASSERT_EQ(set.destroy(42), 0u);
|
||||
|
||||
ASSERT_TRUE(set.empty());
|
||||
ASSERT_EQ(set.size(), 0u);
|
||||
ASSERT_GE(set.capacity(), 1u);
|
||||
ASSERT_EQ(set.begin(), set.end());
|
||||
ASSERT_FALSE(set.has(0));
|
||||
ASSERT_FALSE(set.has(42));
|
||||
|
||||
ASSERT_EQ(set.construct(42), 0u);
|
||||
|
||||
set.reset();
|
||||
|
||||
ASSERT_TRUE(set.empty());
|
||||
ASSERT_EQ(set.size(), 0u);
|
||||
ASSERT_GE(set.capacity(), 0u);
|
||||
ASSERT_EQ(set.begin(), set.end());
|
||||
ASSERT_FALSE(set.has(0));
|
||||
ASSERT_FALSE(set.has(42));
|
||||
}
|
||||
|
||||
TEST(SparseSetNoType, DataBeginEnd) {
|
||||
using SparseSet = entt::SparseSet<unsigned int>;
|
||||
|
||||
SparseSet set;
|
||||
|
||||
ASSERT_EQ(set.construct(3), 0u);
|
||||
ASSERT_EQ(set.construct(12), 1u);
|
||||
ASSERT_EQ(set.construct(42), 2u);
|
||||
|
||||
ASSERT_EQ(*(set.data() + 0u), 3u);
|
||||
ASSERT_EQ(*(set.data() + 1u), 12u);
|
||||
ASSERT_EQ(*(set.data() + 2u), 42u);
|
||||
|
||||
auto begin = set.begin();
|
||||
auto end = set.end();
|
||||
|
||||
ASSERT_EQ(*(begin++), 42u);
|
||||
ASSERT_EQ(*(begin++), 12u);
|
||||
ASSERT_EQ(*(begin++), 3u);
|
||||
ASSERT_EQ(begin, end);
|
||||
|
||||
set.reset();
|
||||
}
|
||||
|
||||
TEST(SparseSetNoType, Swap) {
|
||||
using SparseSet = entt::SparseSet<unsigned int>;
|
||||
|
||||
SparseSet set;
|
||||
|
||||
ASSERT_EQ(set.construct(3), 0u);
|
||||
ASSERT_EQ(set.construct(12), 1u);
|
||||
|
||||
ASSERT_EQ(*(set.data() + 0u), 3u);
|
||||
ASSERT_EQ(*(set.data() + 1u), 12u);
|
||||
|
||||
set.swap(3, 12);
|
||||
|
||||
ASSERT_EQ(*(set.data() + 0u), 12u);
|
||||
ASSERT_EQ(*(set.data() + 1u), 3u);
|
||||
|
||||
set.reset();
|
||||
}
|
||||
|
||||
TEST(SparseSetWithType, Functionalities) {
|
||||
using SparseSet = entt::SparseSet<unsigned int, int>;
|
||||
|
||||
SparseSet set;
|
||||
|
||||
ASSERT_TRUE(set.empty());
|
||||
ASSERT_EQ(set.size(), 0u);
|
||||
ASSERT_EQ(set.capacity(), 0u);
|
||||
ASSERT_EQ(set.begin(), set.end());
|
||||
ASSERT_FALSE(set.has(0));
|
||||
ASSERT_FALSE(set.has(42));
|
||||
|
||||
ASSERT_EQ(set.construct(42, 3), 3);
|
||||
|
||||
ASSERT_FALSE(set.empty());
|
||||
ASSERT_EQ(set.size(), 1u);
|
||||
ASSERT_GE(set.capacity(), 1u);
|
||||
ASSERT_NE(set.begin(), set.end());
|
||||
ASSERT_FALSE(set.has(0));
|
||||
ASSERT_TRUE(set.has(42));
|
||||
ASSERT_EQ(set.get(42), 3);
|
||||
|
||||
set.destroy(42);
|
||||
|
||||
ASSERT_TRUE(set.empty());
|
||||
ASSERT_EQ(set.size(), 0u);
|
||||
ASSERT_GE(set.capacity(), 1u);
|
||||
ASSERT_EQ(set.begin(), set.end());
|
||||
ASSERT_FALSE(set.has(0));
|
||||
ASSERT_FALSE(set.has(42));
|
||||
|
||||
ASSERT_EQ(set.construct(42, 12), 12);
|
||||
|
||||
set.reset();
|
||||
|
||||
ASSERT_TRUE(set.empty());
|
||||
ASSERT_EQ(set.size(), 0u);
|
||||
ASSERT_GE(set.capacity(), 0u);
|
||||
ASSERT_EQ(set.begin(), set.end());
|
||||
ASSERT_FALSE(set.has(0));
|
||||
ASSERT_FALSE(set.has(42));
|
||||
}
|
||||
|
||||
TEST(SparseSetWithType, RawBeginEnd) {
|
||||
using SparseSet = entt::SparseSet<unsigned int, int>;
|
||||
|
||||
SparseSet set;
|
||||
|
||||
ASSERT_EQ(set.construct(3, 3), 3);
|
||||
ASSERT_EQ(set.construct(12, 6), 6);
|
||||
ASSERT_EQ(set.construct(42, 9), 9);
|
||||
|
||||
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(set.get(*(begin++)), 9);
|
||||
ASSERT_EQ(set.get(*(begin++)), 6);
|
||||
ASSERT_EQ(set.get(*(begin++)), 3);
|
||||
ASSERT_EQ(begin, end);
|
||||
|
||||
set.reset();
|
||||
}
|
||||
|
||||
TEST(SparseSetWithType, Swap) {
|
||||
using SparseSet = entt::SparseSet<unsigned int, int>;
|
||||
|
||||
SparseSet set;
|
||||
|
||||
ASSERT_EQ(set.construct(3, 3), 3);
|
||||
ASSERT_EQ(set.construct(12, 6), 6);
|
||||
|
||||
ASSERT_EQ(*(set.raw() + 0u), 3);
|
||||
ASSERT_EQ(*(set.raw() + 1u), 6);
|
||||
|
||||
set.swap(3, 12);
|
||||
|
||||
ASSERT_EQ(*(set.raw() + 0u), 6);
|
||||
ASSERT_EQ(*(set.raw() + 1u), 3);
|
||||
|
||||
set.reset();
|
||||
}
|
||||
|
||||
TEST(SparseSetWithType, SortOrdered) {
|
||||
using SparseSet = entt::SparseSet<unsigned int, int>;
|
||||
|
||||
SparseSet set;
|
||||
|
||||
ASSERT_EQ(set.construct(12, 12), 12);
|
||||
ASSERT_EQ(set.construct(42, 9), 9);
|
||||
ASSERT_EQ(set.construct(7, 6), 6);
|
||||
ASSERT_EQ(set.construct(3, 3), 3);
|
||||
ASSERT_EQ(set.construct(9, 1), 1);
|
||||
|
||||
set.sort([](auto lhs, auto rhs) {
|
||||
return lhs < rhs;
|
||||
});
|
||||
|
||||
ASSERT_EQ(*(set.raw() + 0u), 12);
|
||||
ASSERT_EQ(*(set.raw() + 1u), 9);
|
||||
ASSERT_EQ(*(set.raw() + 2u), 6);
|
||||
ASSERT_EQ(*(set.raw() + 3u), 3);
|
||||
ASSERT_EQ(*(set.raw() + 4u), 1);
|
||||
|
||||
auto begin = set.begin();
|
||||
auto end = set.end();
|
||||
|
||||
ASSERT_EQ(set.get(*(begin++)), 1);
|
||||
ASSERT_EQ(set.get(*(begin++)), 3);
|
||||
ASSERT_EQ(set.get(*(begin++)), 6);
|
||||
ASSERT_EQ(set.get(*(begin++)), 9);
|
||||
ASSERT_EQ(set.get(*(begin++)), 12);
|
||||
ASSERT_EQ(begin, end);
|
||||
|
||||
set.reset();
|
||||
}
|
||||
|
||||
TEST(SparseSetWithType, SortReverse) {
|
||||
using SparseSet = entt::SparseSet<unsigned int, int>;
|
||||
|
||||
SparseSet set;
|
||||
|
||||
ASSERT_EQ(set.construct(12, 1), 1);
|
||||
ASSERT_EQ(set.construct(42, 3), 3);
|
||||
ASSERT_EQ(set.construct(7, 6), 6);
|
||||
ASSERT_EQ(set.construct(3, 9), 9);
|
||||
ASSERT_EQ(set.construct(9, 12), 12);
|
||||
|
||||
set.sort([](auto lhs, auto rhs) {
|
||||
return lhs < rhs;
|
||||
});
|
||||
|
||||
ASSERT_EQ(*(set.raw() + 0u), 12);
|
||||
ASSERT_EQ(*(set.raw() + 1u), 9);
|
||||
ASSERT_EQ(*(set.raw() + 2u), 6);
|
||||
ASSERT_EQ(*(set.raw() + 3u), 3);
|
||||
ASSERT_EQ(*(set.raw() + 4u), 1);
|
||||
|
||||
auto begin = set.begin();
|
||||
auto end = set.end();
|
||||
|
||||
ASSERT_EQ(set.get(*(begin++)), 1);
|
||||
ASSERT_EQ(set.get(*(begin++)), 3);
|
||||
ASSERT_EQ(set.get(*(begin++)), 6);
|
||||
ASSERT_EQ(set.get(*(begin++)), 9);
|
||||
ASSERT_EQ(set.get(*(begin++)), 12);
|
||||
ASSERT_EQ(begin, end);
|
||||
|
||||
set.reset();
|
||||
}
|
||||
|
||||
TEST(SparseSetWithType, SortUnordered) {
|
||||
using SparseSet = entt::SparseSet<unsigned int, int>;
|
||||
|
||||
SparseSet set;
|
||||
|
||||
ASSERT_EQ(set.construct(12, 6), 6);
|
||||
ASSERT_EQ(set.construct(42, 3), 3);
|
||||
ASSERT_EQ(set.construct(7, 1), 1);
|
||||
ASSERT_EQ(set.construct(3, 9), 9);
|
||||
ASSERT_EQ(set.construct(9, 12), 12);
|
||||
|
||||
set.sort([](auto lhs, auto rhs) {
|
||||
return lhs < rhs;
|
||||
});
|
||||
|
||||
ASSERT_EQ(*(set.raw() + 0u), 12);
|
||||
ASSERT_EQ(*(set.raw() + 1u), 9);
|
||||
ASSERT_EQ(*(set.raw() + 2u), 6);
|
||||
ASSERT_EQ(*(set.raw() + 3u), 3);
|
||||
ASSERT_EQ(*(set.raw() + 4u), 1);
|
||||
|
||||
auto begin = set.begin();
|
||||
auto end = set.end();
|
||||
|
||||
ASSERT_EQ(set.get(*(begin++)), 1);
|
||||
ASSERT_EQ(set.get(*(begin++)), 3);
|
||||
ASSERT_EQ(set.get(*(begin++)), 6);
|
||||
ASSERT_EQ(set.get(*(begin++)), 9);
|
||||
ASSERT_EQ(set.get(*(begin++)), 12);
|
||||
ASSERT_EQ(begin, end);
|
||||
|
||||
set.reset();
|
||||
}
|
||||
|
||||
TEST(SparseSetWithType, RespectOrdered) {
|
||||
using SparseSet = entt::SparseSet<unsigned int, int>;
|
||||
|
||||
SparseSet lhs;
|
||||
SparseSet rhs;
|
||||
|
||||
ASSERT_EQ(lhs.construct(1, 0), 0);
|
||||
ASSERT_EQ(lhs.construct(2, 0), 0);
|
||||
ASSERT_EQ(lhs.construct(3, 0), 0);
|
||||
ASSERT_EQ(lhs.construct(4, 0), 0);
|
||||
ASSERT_EQ(lhs.construct(5, 0), 0);
|
||||
|
||||
ASSERT_EQ(rhs.construct(6, 0), 0);
|
||||
ASSERT_EQ(rhs.construct(1, 0), 0);
|
||||
ASSERT_EQ(rhs.construct(2, 0), 0);
|
||||
ASSERT_EQ(rhs.construct(3, 0), 0);
|
||||
ASSERT_EQ(rhs.construct(4, 0), 0);
|
||||
ASSERT_EQ(rhs.construct(5, 0), 0);
|
||||
|
||||
rhs.respect(lhs);
|
||||
|
||||
ASSERT_EQ(*(lhs.data() + 0u), 1u);
|
||||
ASSERT_EQ(*(lhs.data() + 1u), 2u);
|
||||
ASSERT_EQ(*(lhs.data() + 2u), 3u);
|
||||
ASSERT_EQ(*(lhs.data() + 3u), 4u);
|
||||
ASSERT_EQ(*(lhs.data() + 4u), 5u);
|
||||
|
||||
ASSERT_EQ(*(rhs.data() + 0u), 6u);
|
||||
ASSERT_EQ(*(rhs.data() + 1u), 1u);
|
||||
ASSERT_EQ(*(rhs.data() + 2u), 2u);
|
||||
ASSERT_EQ(*(rhs.data() + 3u), 3u);
|
||||
ASSERT_EQ(*(rhs.data() + 4u), 4u);
|
||||
ASSERT_EQ(*(rhs.data() + 5u), 5u);
|
||||
|
||||
lhs.reset();
|
||||
rhs.reset();
|
||||
}
|
||||
|
||||
TEST(SparseSetWithType, RespectReverse) {
|
||||
using SparseSet = entt::SparseSet<unsigned int, int>;
|
||||
|
||||
SparseSet lhs;
|
||||
SparseSet rhs;
|
||||
|
||||
ASSERT_EQ(lhs.construct(1, 0), 0);
|
||||
ASSERT_EQ(lhs.construct(2, 0), 0);
|
||||
ASSERT_EQ(lhs.construct(3, 0), 0);
|
||||
ASSERT_EQ(lhs.construct(4, 0), 0);
|
||||
ASSERT_EQ(lhs.construct(5, 0), 0);
|
||||
|
||||
ASSERT_EQ(rhs.construct(5, 0), 0);
|
||||
ASSERT_EQ(rhs.construct(4, 0), 0);
|
||||
ASSERT_EQ(rhs.construct(3, 0), 0);
|
||||
ASSERT_EQ(rhs.construct(2, 0), 0);
|
||||
ASSERT_EQ(rhs.construct(1, 0), 0);
|
||||
ASSERT_EQ(rhs.construct(6, 0), 0);
|
||||
|
||||
rhs.respect(lhs);
|
||||
|
||||
ASSERT_EQ(*(lhs.data() + 0u), 1u);
|
||||
ASSERT_EQ(*(lhs.data() + 1u), 2u);
|
||||
ASSERT_EQ(*(lhs.data() + 2u), 3u);
|
||||
ASSERT_EQ(*(lhs.data() + 3u), 4u);
|
||||
ASSERT_EQ(*(lhs.data() + 4u), 5u);
|
||||
|
||||
ASSERT_EQ(*(rhs.data() + 0u), 6u);
|
||||
ASSERT_EQ(*(rhs.data() + 1u), 1u);
|
||||
ASSERT_EQ(*(rhs.data() + 2u), 2u);
|
||||
ASSERT_EQ(*(rhs.data() + 3u), 3u);
|
||||
ASSERT_EQ(*(rhs.data() + 4u), 4u);
|
||||
ASSERT_EQ(*(rhs.data() + 5u), 5u);
|
||||
|
||||
lhs.reset();
|
||||
rhs.reset();
|
||||
}
|
||||
|
||||
TEST(SparseSetWithType, RespectUnordered) {
|
||||
using SparseSet = entt::SparseSet<unsigned int, int>;
|
||||
|
||||
SparseSet lhs;
|
||||
SparseSet rhs;
|
||||
|
||||
ASSERT_EQ(lhs.construct(1, 0), 0);
|
||||
ASSERT_EQ(lhs.construct(2, 0), 0);
|
||||
ASSERT_EQ(lhs.construct(3, 0), 0);
|
||||
ASSERT_EQ(lhs.construct(4, 0), 0);
|
||||
ASSERT_EQ(lhs.construct(5, 0), 0);
|
||||
|
||||
ASSERT_EQ(rhs.construct(3, 0), 0);
|
||||
ASSERT_EQ(rhs.construct(2, 0), 0);
|
||||
ASSERT_EQ(rhs.construct(6, 0), 0);
|
||||
ASSERT_EQ(rhs.construct(1, 0), 0);
|
||||
ASSERT_EQ(rhs.construct(4, 0), 0);
|
||||
ASSERT_EQ(rhs.construct(5, 0), 0);
|
||||
|
||||
rhs.respect(lhs);
|
||||
|
||||
ASSERT_EQ(*(lhs.data() + 0u), 1u);
|
||||
ASSERT_EQ(*(lhs.data() + 1u), 2u);
|
||||
ASSERT_EQ(*(lhs.data() + 2u), 3u);
|
||||
ASSERT_EQ(*(lhs.data() + 3u), 4u);
|
||||
ASSERT_EQ(*(lhs.data() + 4u), 5u);
|
||||
|
||||
ASSERT_EQ(*(rhs.data() + 0u), 6u);
|
||||
ASSERT_EQ(*(rhs.data() + 1u), 1u);
|
||||
ASSERT_EQ(*(rhs.data() + 2u), 2u);
|
||||
ASSERT_EQ(*(rhs.data() + 3u), 3u);
|
||||
ASSERT_EQ(*(rhs.data() + 4u), 4u);
|
||||
ASSERT_EQ(*(rhs.data() + 5u), 5u);
|
||||
|
||||
lhs.reset();
|
||||
rhs.reset();
|
||||
}
|
||||
Reference in New Issue
Block a user