Compare commits
42 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
50069d3743 | ||
|
|
1e03f27f23 | ||
|
|
36bb55a9ce | ||
|
|
451e4050db | ||
|
|
367fd3e87f | ||
|
|
a67a2e12fd | ||
|
|
292978daf0 | ||
|
|
85a4a76a14 | ||
|
|
9d0ab7ed70 | ||
|
|
3d5b6a5e0b | ||
|
|
ab20372093 | ||
|
|
ab887f30e4 | ||
|
|
6cb6a8c25f | ||
|
|
9d1d2aca0a | ||
|
|
75cb2cd1f7 | ||
|
|
ed6adbbfd7 | ||
|
|
b6c950ffc5 | ||
|
|
8b89c69d5f | ||
|
|
290dda50fe | ||
|
|
a7278573a8 | ||
|
|
68ce4dc689 | ||
|
|
a9f5118013 | ||
|
|
d1f2e8ecf9 | ||
|
|
fe6873b61a | ||
|
|
7c7bcf80cf | ||
|
|
cf6022866d | ||
|
|
c630cb1de2 | ||
|
|
2e6c8d542c | ||
|
|
2f781906b5 | ||
|
|
b4f3b6f7bd | ||
|
|
71b464f44a | ||
|
|
438070ed58 | ||
|
|
a06c891969 | ||
|
|
a935bd09aa | ||
|
|
fb8745ccf0 | ||
|
|
53a4c4be7f | ||
|
|
c0a110ea8a | ||
|
|
c426a8e331 | ||
|
|
526e4f69a4 | ||
|
|
f901fa50ff | ||
|
|
bea9eeac16 | ||
|
|
3055da5316 |
26
.travis.yml
26
.travis.yml
@@ -11,6 +11,13 @@ matrix:
|
||||
sources: ['ubuntu-toolchain-r-test']
|
||||
packages: ['g++-6']
|
||||
env: COMPILER=g++-6
|
||||
- os: linux
|
||||
compiler: gcc
|
||||
addons:
|
||||
apt:
|
||||
sources: ['ubuntu-toolchain-r-test']
|
||||
packages: ['g++-7']
|
||||
env: COMPILER=g++-7
|
||||
- os: linux
|
||||
compiler: clang
|
||||
addons:
|
||||
@@ -18,23 +25,34 @@ matrix:
|
||||
sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-trusty-4.0']
|
||||
packages: ['clang-4.0', 'libstdc++-4.9-dev']
|
||||
env: COMPILER=clang++-4.0
|
||||
- os: linux
|
||||
compiler: clang
|
||||
addons:
|
||||
apt:
|
||||
sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-trusty-5.0']
|
||||
packages: ['clang-5.0', 'libstdc++-4.9-dev']
|
||||
env: COMPILER=clang++-5.0
|
||||
- os: osx
|
||||
osx_image: xcode8.3
|
||||
compiler: clang
|
||||
env: COMPILER=clang++
|
||||
- os: osx
|
||||
osx_image: xcode9.1
|
||||
compiler: clang
|
||||
env: COMPILER=clang++
|
||||
- os: linux
|
||||
compiler: gcc
|
||||
addons:
|
||||
apt:
|
||||
sources: ['ubuntu-toolchain-r-test']
|
||||
packages: ['g++-6']
|
||||
packages: ['g++-7']
|
||||
env:
|
||||
- COMPILER=g++-6
|
||||
- COMPILER=g++-7
|
||||
- CXXFLAGS="-O0 --coverage -fno-inline -fno-inline-small-functions -fno-default-inline"
|
||||
before_script:
|
||||
- pip install --user cpp-coveralls
|
||||
after_success:
|
||||
- coveralls --gcov gcov-6 --gcov-options '\-lp' --root ${TRAVIS_BUILD_DIR} --build-root ${TRAVIS_BUILD_DIR}/build --extension cpp --extension hpp --exclude deps --include src
|
||||
- coveralls --gcov gcov-7 --gcov-options '\-lp' --root ${TRAVIS_BUILD_DIR} --build-root ${TRAVIS_BUILD_DIR}/build --extension cpp --extension hpp --exclude deps --include src
|
||||
|
||||
notifications:
|
||||
email:
|
||||
@@ -51,5 +69,5 @@ install:
|
||||
|
||||
script:
|
||||
- mkdir -p build && cd build
|
||||
- cmake -DCMAKE_BUILD_TYPE=Release .. && make -j4
|
||||
- cmake .. && make -j4
|
||||
- CTEST_OUTPUT_ON_FAILURE=1 make test
|
||||
|
||||
@@ -16,7 +16,7 @@ endif()
|
||||
# Project configuration
|
||||
#
|
||||
|
||||
project(entt VERSION 2.0.0)
|
||||
project(entt VERSION 2.3.0)
|
||||
|
||||
if(NOT CMAKE_BUILD_TYPE)
|
||||
set(CMAKE_BUILD_TYPE Debug)
|
||||
@@ -55,14 +55,10 @@ if(NOT MSVC)
|
||||
endif()
|
||||
|
||||
#
|
||||
# CMake configuration
|
||||
# Include EnTT
|
||||
#
|
||||
|
||||
set(PROJECT_CMAKE_IN ${entt_SOURCE_DIR}/cmake/in)
|
||||
set(PROJECT_DEPS_DIR ${entt_SOURCE_DIR}/deps)
|
||||
set(PROJECT_SRC_DIR ${entt_SOURCE_DIR}/src)
|
||||
|
||||
set(PROJECT_RUNTIME_OUTPUT_DIRECTORY bin)
|
||||
include_directories(${entt_SOURCE_DIR}/src)
|
||||
|
||||
#
|
||||
# Tests
|
||||
@@ -74,9 +70,12 @@ if(BUILD_TESTING)
|
||||
set(THREADS_PREFER_PTHREAD_FLAG ON)
|
||||
find_package(Threads REQUIRED)
|
||||
|
||||
option(BUILD_BENCHMARK "Build benchmark." OFF)
|
||||
option(BUILD_MOD "Build mod example." OFF)
|
||||
|
||||
# gtest, gtest_main, gmock and gmock_main targets are available from now on
|
||||
set(GOOGLETEST_DEPS_DIR ${PROJECT_DEPS_DIR}/googletest)
|
||||
configure_file(${PROJECT_CMAKE_IN}/googletest.in ${GOOGLETEST_DEPS_DIR}/CMakeLists.txt)
|
||||
set(GOOGLETEST_DEPS_DIR ${entt_SOURCE_DIR}/deps/googletest)
|
||||
configure_file(${entt_SOURCE_DIR}/cmake/in/googletest.in ${GOOGLETEST_DEPS_DIR}/CMakeLists.txt)
|
||||
execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" . WORKING_DIRECTORY ${GOOGLETEST_DEPS_DIR})
|
||||
execute_process(COMMAND ${CMAKE_COMMAND} --build . WORKING_DIRECTORY ${GOOGLETEST_DEPS_DIR})
|
||||
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
|
||||
@@ -95,3 +94,17 @@ find_package(Doxygen 1.8)
|
||||
if(DOXYGEN_FOUND)
|
||||
add_subdirectory(docs)
|
||||
endif()
|
||||
|
||||
#
|
||||
# AOB
|
||||
#
|
||||
|
||||
add_custom_target(
|
||||
entt_aob
|
||||
SOURCES
|
||||
appveyor.yml
|
||||
AUTHORS
|
||||
LICENSE
|
||||
README.md
|
||||
.travis.yml
|
||||
)
|
||||
|
||||
396
README.md
396
README.md
@@ -1,4 +1,4 @@
|
||||
# The EnTT Framework
|
||||
# EnTT Framework
|
||||
|
||||
[](https://travis-ci.org/skypjack/entt)
|
||||
[](https://ci.appveyor.com/project/skypjack/entt)
|
||||
@@ -9,22 +9,75 @@
|
||||
|
||||
`EnTT` is a header-only, tiny and easy to use framework written in modern
|
||||
C++.<br/>
|
||||
It's entirely designed around an architectural pattern pattern called _ECS_ that
|
||||
is used mostly in game development. For further details:
|
||||
It was originally designed entirely around an architectural pattern called _ECS_
|
||||
that is 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/)
|
||||
* [ECS on Wikipedia](https://en.wikipedia.org/wiki/Entity%E2%80%93component%E2%80%93system)
|
||||
|
||||
Originally, `EnTT` was written as a faster alternative to other well known and
|
||||
open source entity-component systems.<br/>
|
||||
After a while the codebase has grown and more features have become part of the
|
||||
framework.
|
||||
A long time ago, the sole entity-component system was part of the project. After
|
||||
a while the codebase has grown and more and more classes have become part
|
||||
of the repository.<br/>
|
||||
That's why today it's called _the EnTT Framework_.
|
||||
|
||||
Currently, `EnTT` is tested on Linux, Microsoft Windows and OS X. It has proven
|
||||
to work also on both Android and iOS.<br/>
|
||||
Most likely it will not be problematic on other systems as well, but has not
|
||||
been sufficiently tested so far.
|
||||
|
||||
## The framework
|
||||
|
||||
`EnTT` was written initially as a faster alternative to other well known and
|
||||
open source entity-component systems. Nowadays the `EnTT` framework is moving
|
||||
its first steps. Much more will come in the future and hopefully I'm going to
|
||||
work on it for a long time.<br/>
|
||||
Requests for feature, PR, suggestions ad feedback are highly appreciated.
|
||||
|
||||
If you find you can help me and want to contribute to the `EnTT` framework with
|
||||
your experience or you do want to get part of the project for some other
|
||||
reason, feel free to contact me directly (you can find the mail in the
|
||||
[profile](https://github.com/skypjack)).<br/>
|
||||
I can't promise that each and every contribution will be accepted, but I can
|
||||
assure that I'll do my best to take them all seriously.
|
||||
|
||||
### State of the art
|
||||
|
||||
Here is a brief list of what it offers today:
|
||||
|
||||
* Statically generated integer identifiers for types (assigned either at
|
||||
compile-time or at runtime).
|
||||
* A constexpr utility for human readable resource identifiers.
|
||||
* An incredibly fast entity-component system based on sparse sets, with its own
|
||||
views and a _pay for what you use_ policy to adjust performance and memory
|
||||
pressure according to the users' requirements.
|
||||
* Actor class for those who aren't confident with entity-component systems.
|
||||
* The smallest and most basic implementation of a service locator ever seen.
|
||||
* A cooperative scheduler for processes of any type.
|
||||
* All what is needed for resource management (cache, loaders, handles).
|
||||
* Signal handlers of any type, delegates and an event bus.
|
||||
* A general purpose event emitter, that is a CRTP idiom based class template.
|
||||
* An event dispatcher for immediate and delayed events to integrate in loops.
|
||||
* ...
|
||||
* Any other business.
|
||||
|
||||
Consider it a work in progress. For more details and an updated list, please
|
||||
refer to the [online documentation](https://skypjack.github.io/entt/).
|
||||
|
||||
### A note about the README
|
||||
|
||||
The README file stays true to the original project and it describes only the
|
||||
entity-component system. However, the whole API is fully documented in-code and
|
||||
the [online documentation](https://skypjack.github.io/entt/) contains much
|
||||
more.<br/>
|
||||
Continue reading to know how the core part of the project works or follow the
|
||||
link above to take a look at the API reference for all other available classes.
|
||||
|
||||
## Code Example
|
||||
|
||||
```cpp
|
||||
#include <registry.hpp>
|
||||
#include <entt/entt.hpp>
|
||||
#include <cstdint>
|
||||
|
||||
struct Position {
|
||||
float x;
|
||||
@@ -37,25 +90,43 @@ struct Velocity {
|
||||
};
|
||||
|
||||
void update(entt::DefaultRegistry ®istry) {
|
||||
auto view = ecs.view<Position, Velocity>();
|
||||
auto view = registry.view<Position, Velocity>();
|
||||
|
||||
for(auto entity: view) {
|
||||
auto &position = view.get<Position>(entity);
|
||||
// gets only the components that are going to be used ...
|
||||
|
||||
auto &velocity = view.get<Velocity>(entity);
|
||||
|
||||
velocity.dx = 0.;
|
||||
velocity.dy = 0.;
|
||||
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
void update(std::uint64_t dt, entt::DefaultRegistry ®istry) {
|
||||
registry.view<Position, Velocity>().each([dt](auto entity, auto &position, auto &velocity) {
|
||||
// gets all the components of the view at once ...
|
||||
|
||||
position.x += velocity.dx * dt;
|
||||
position.y += velocity.dy * dt;
|
||||
|
||||
// ...
|
||||
});
|
||||
}
|
||||
|
||||
int main() {
|
||||
entt::DefaultRegistry registry;
|
||||
std::uint64_t dt = 16;
|
||||
|
||||
for(auto i = 0; i < 10; ++i) {
|
||||
auto entity = registry.create();
|
||||
registry.assign<Position>(entity, i * 1.f, i * 1.f);
|
||||
auto entity = registry.create(Position{i * 1.f, i * 1.f});
|
||||
if(i % 2 == 0) { registry.assign<Velocity>(entity, i * .1f, i * .1f); }
|
||||
}
|
||||
|
||||
update(dt, registry);
|
||||
update(registry);
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
@@ -65,42 +136,40 @@ int main() {
|
||||
I started working on `EnTT` because of the wrong reason: my goal was to design
|
||||
an entity-component system that beated another well known open source solution
|
||||
in terms of performance.<br/>
|
||||
I did it, of course, but it wasn't much satisfying. Actually it wasn't
|
||||
In the end, I did it, 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 of `EnTT` and to
|
||||
add all the features I wanted to see in *my* entity-component system 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. And even more, of course.
|
||||
Today `EnTT` is finally what I was looking for: still faster than its
|
||||
_competitors_, a really good API and an amazing set of features. And even more,
|
||||
of course.
|
||||
|
||||
## 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 indeed).<br/>
|
||||
compared to my first choice (it was already amazingly fast actually).<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 |
|
||||
| Benchmark | EntityX (compile-time) | EnTT |
|
||||
|-----------|-------------|-------------|
|
||||
| Creating 10M entities | 0.128881s | **0.0408754s** |
|
||||
| Destroying 10M entities | **0.0531374s** | 0.0545839s |
|
||||
| Iterating over 10M entities, unpacking one component, standard view | 0.010661s | **1.58e-07s** |
|
||||
| Iterating over 10M entities, unpacking two components, standard view | **0.0112664s** | 0.0840068s |
|
||||
| Iterating over 10M entities, unpacking two components, standard view, half of the entities have all the components | **0.0077951s** | 0.042168s |
|
||||
| Iterating over 10M entities, unpacking two components, standard view, one of the entities has all the components | 0.00713398s | **8.93e-07s** |
|
||||
| Iterating over 10M entities, unpacking two components, persistent view | 0.0112664s | **5.68e-07s** |
|
||||
| Iterating over 10M entities, unpacking five components, standard view | **0.00905084s** | 0.137757s |
|
||||
| Iterating over 10M entities, unpacking five components, persistent view | 0.00905084s | **2.9e-07s** |
|
||||
| Iterating over 10M entities, unpacking ten components, standard view | **0.0104708s** | 0.388602s |
|
||||
| Iterating over 10M entities, unpacking ten components, standard view, half of the entities have all the components | **0.00899859s** | 0.200752s |
|
||||
| Iterating over 10M entities, unpacking ten components, standard view, one of the entities has all the components | 0.00700349s | **2.565e-06s** |
|
||||
| Iterating over 10M entities, unpacking ten components, persistent view | 0.0104708s | **6.23e-07s** |
|
||||
| Iterating over 50M entities, unpacking one component, standard view | 0.055194s | **2.87e-07s** |
|
||||
| Iterating over 50M entities, unpacking two components, standard view | **0.0533921s** | 0.243197s |
|
||||
| Iterating over 50M entities, unpacking two components, persistent view | 0.055194s | **4.47e-07s** |
|
||||
| Sort 150k entities, one component | - | **0.0080046s** |
|
||||
| Sort 150k entities, match two components | - | **0.00608322s** |
|
||||
| Create 10M entities | 0.1289s | **0.0409s** |
|
||||
| Destroy 10M entities | **0.0531s** | 0.0546s |
|
||||
| Standard view, 10M entities, one component | 0.0107s | **1.6e-07s** |
|
||||
| Standard view, 10M entities, two components | **0.0113s** | 0.0295s |
|
||||
| Standard view, 10M entities, two components<br/>Half of the entities have all the components | **0.0078s** | 0.0150s |
|
||||
| Standard view, 10M entities, two components<br/>One of the entities has all the components | 0.0071s | **8.8e-07s** |
|
||||
| Persistent view, 10M entities, two components | 0.0113s | **5.7e-07s** |
|
||||
| Standard view, 10M entities, five components | **0.0091s** | 0.0688s |
|
||||
| Persistent view, 10M entities, five components | 0.0091s | **2.9e-07s** |
|
||||
| Standard view, 10M entities, ten components | **0.0105s** | 0.1403s |
|
||||
| Standard view, 10M entities, ten components<br/>Half of the entities have all the components | **0.0090s** | 0.0620s |
|
||||
| Standard view, 10M entities, ten components<br/>One of the entities has all the components | 0.0070s | **1.3e-06s** |
|
||||
| Persistent view, 10M entities, ten components | 0.0105s | **6.2e-07s** |
|
||||
| Sort 150k entities, one component<br/>Arrays are in reverse order | - | **0.0043s** |
|
||||
| Sort 150k entities, enforce permutation<br/>Arrays are in reverse order | - | **0.0006s** |
|
||||
|
||||
`EnTT` includes its own tests and benchmarks. See
|
||||
[benchmark.cpp](https://github.com/skypjack/entt/blob/master/test/benchmark.cpp)
|
||||
@@ -109,8 +178,8 @@ 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`.
|
||||
|
||||
Of course, probably I'll try to get out of `EnTT` more features and better
|
||||
performance in the future, mainly for fun.<br/>
|
||||
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.
|
||||
|
||||
@@ -185,6 +254,44 @@ Benchmarks are compiled only in release mode currently.
|
||||
|
||||
# Crash Course
|
||||
|
||||
## Design choices
|
||||
|
||||
### A bitset-free entity-component system
|
||||
|
||||
`EnTT` is a _bitset-free_ entity-component system that doesn't require users to
|
||||
specify the component set at compile-time.<br/>
|
||||
This is why users can instantiate the core class simply like:
|
||||
|
||||
```cpp
|
||||
entt::DefaultRegistry registry;
|
||||
```
|
||||
|
||||
In place of its more annoying and error-prone counterpart:
|
||||
|
||||
```cpp
|
||||
entt::DefaultRegistry<Comp0, Comp1, ..., CompN> registry;
|
||||
```
|
||||
|
||||
### Pay per use
|
||||
|
||||
`EnTT` is entirely designed around the principle that users have to pay only for
|
||||
what they want.
|
||||
|
||||
When it comes to using an entity-componet system, the tradeoff is usually
|
||||
between performance and memory usage. The faster it is, the more memory it uses.
|
||||
However, slightly worse performance along non-critical paths are the right price
|
||||
to pay to reduce memory usage and I've always wondered why this kind of tools do
|
||||
not leave me the choice.<br/>
|
||||
`EnTT` follows a completely different approach. It squezees the best from the
|
||||
basic data structures and gives users the possibility to pay more for higher
|
||||
performance where needed.<br/>
|
||||
The disadvantage of this approach is that users need to know the systems they
|
||||
are working on and the tools they are using. Otherwise, the risk to ruin the
|
||||
performance along critical paths is high.
|
||||
|
||||
So far, this choice has proven to be a good one and I really hope it can be for
|
||||
many others besides me.
|
||||
|
||||
## Vademecum
|
||||
|
||||
The `Registry` to store, the `View`s to iterate. That's all.
|
||||
@@ -197,17 +304,18 @@ functionalities to query them out-of-the-box. The underlying type of an entity
|
||||
when defining a registry (actually the DefaultRegistry is nothing more than a
|
||||
Registry where the type of the entities is `std::uint32_t`).<br/>
|
||||
Components (the _C_ of an _ECS_) should be plain old data structures or more
|
||||
complex and moveable data structures with a proper constructor. They are list
|
||||
initialized by using the parameters provided to construct the component. No need
|
||||
to register components or their types neither with the registry nor with the
|
||||
entity-component system at all.<br/>
|
||||
complex and moveable data structures with a proper constructor. Actually, the
|
||||
sole requirement of a component type is that it must be both move constructible
|
||||
and move assignable. They are list initialized by using the parameters provided
|
||||
to construct the component itself. No need to register components or their types
|
||||
neither with the registry nor with the entity-component system at all.<br/>
|
||||
Systems (the _S_ of an _ECS_) are just plain functions, functors, lambdas or
|
||||
whatever the users want. They can accept a Registry, a View or a PersistentView
|
||||
and use them the way they prefer. No need to register systems or their types
|
||||
neither with the registry nor with the entity-component system at all.
|
||||
|
||||
The following sections will explain in short how to use the entity-component
|
||||
system, the core part of the `EnTT` framework.<br/>
|
||||
system, the core part of the whole framework.<br/>
|
||||
In fact, the framework is composed of many other classes in addition to those
|
||||
describe below. For more details, please refer to the
|
||||
[online documentation](https://skypjack.github.io/entt/).
|
||||
@@ -289,8 +397,8 @@ velocity.dy = 0.;
|
||||
|
||||
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):
|
||||
(there is a performance penalty to pay for this mainly due to the fact that it
|
||||
has to check if `entity` already has the given component or not):
|
||||
|
||||
```cpp
|
||||
registry.accomodate<Position>(entity, 0., 0.);
|
||||
@@ -330,7 +438,7 @@ registry.remove<Position>(entity);
|
||||
|
||||
Otherwise consider to use the `reset` member function. It behaves similarly to
|
||||
`remove` 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
|
||||
price to pay for this). In particular it removes the component if and only if it
|
||||
exists, otherwise it returns safely to the caller:
|
||||
|
||||
```cpp
|
||||
@@ -353,11 +461,11 @@ their components are destroyed:
|
||||
registry.reset();
|
||||
```
|
||||
|
||||
Finally, references to components can be retrieved by just doing this:
|
||||
Finally, references to components can be retrieved simply by doing this:
|
||||
|
||||
```cpp
|
||||
// either a non-const reference ...
|
||||
DefaultRegistry registry;
|
||||
entt::DefaultRegistry registry;
|
||||
auto &position = registry.get<Position>(entity);
|
||||
|
||||
// ... or a const one
|
||||
@@ -368,9 +476,122 @@ const auto &position = cregistry.get<Position>(entity);
|
||||
The `get` member function template gives direct access to the component of an
|
||||
entity stored in the underlying data structures of the registry.
|
||||
|
||||
### Single instance components
|
||||
|
||||
In those cases where all what is needed is a single instance component, tags are
|
||||
the right tool to achieve the purpose.<br/>
|
||||
Tags undergo the same requirements of components. They can be either plain old
|
||||
data structures or more complex and moveable data structures with a proper
|
||||
constructor.<br/>
|
||||
Actually, the same type can be used both as a tag and as a component and the
|
||||
registry will not complain about it. It is up to the users to properly manage
|
||||
their own types.
|
||||
|
||||
Attaching tags to entities and removing them is trivial:
|
||||
|
||||
```cpp
|
||||
auto player = registry.create();
|
||||
auto camera = registry.create();
|
||||
|
||||
// attaches a default-initialized tag to an entity
|
||||
registry.attach<PlayingCharacter>(player);
|
||||
|
||||
// attaches a tag to an entity and initializes it
|
||||
registry.attach<Camera>(camera, player);
|
||||
|
||||
// removes tags from their owners
|
||||
registry.remove<PlayingCharacter>();
|
||||
registry.remove<Camera>();
|
||||
```
|
||||
|
||||
If in doubt about whether or not a tag has already an owner, the `has` member
|
||||
function template may be useful:
|
||||
|
||||
```cpp
|
||||
bool b = registry.has<PlayingCharacter>();
|
||||
```
|
||||
|
||||
References to tags can be retrieved simply by doing this:
|
||||
|
||||
```cpp
|
||||
// either a non-const reference ...
|
||||
entt::DefaultRegistry registry;
|
||||
auto &player = registry.get<PlayingCharacter>();
|
||||
|
||||
// ... or a const one
|
||||
const auto &cregistry = registry;
|
||||
const auto &camera = cregistry.get<Camera>();
|
||||
```
|
||||
|
||||
The `get` member function template gives direct access to the tag as stored in
|
||||
the underlying data structures of the registry.
|
||||
|
||||
As shown above, in almost all the cases the entity identifier isn't required,
|
||||
since a single instance component can have only one associated entity and
|
||||
therefore it doesn't make much sense to mention it explicitly.<br/>
|
||||
To find out who the owner is, just do the following:
|
||||
|
||||
```cpp
|
||||
auto player = registry.attachee<PlayingCharacter>();
|
||||
```
|
||||
|
||||
Note that iterating tags isn't possible for obvious reasons. Tags give direct
|
||||
access to single entities and nothing more.
|
||||
|
||||
### Runtime components
|
||||
|
||||
Defining components at runtime is useful to support plugins and mods in general.
|
||||
However, it seems impossible with a tool designed around a bunch of templates.
|
||||
Indeed it's not that difficult.<br/>
|
||||
Of course, some features cannot be easily exported into a runtime
|
||||
environment. As an example, sorting a group of components defined at runtime
|
||||
isn't for free if compared to most of the other operations. However, the basic
|
||||
functionalities of an entity-component system such as `EnTT` fit the problem
|
||||
perfectly and can also be used to manage runtime components if required.<br/>
|
||||
All that is necessary to do it is to know the identifiers of the components. An
|
||||
identifier is nothing more than a number or similar that can be used at runtime
|
||||
to work with the type system.
|
||||
|
||||
In `EnTT`, identifiers are easily accessible:
|
||||
|
||||
```cpp
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
// standard component identifier
|
||||
auto ctype = registry.component<Position>();
|
||||
|
||||
// single instance component identifier
|
||||
auto ttype = registry.tag<PlayingCharacter>();
|
||||
```
|
||||
|
||||
Once the identifiers are made available, almost everything becomes pretty
|
||||
simple.
|
||||
|
||||
#### A journey through a plugin
|
||||
|
||||
`EnTT` comes with an example (actually a test) that shows how to integrate
|
||||
compile-time and runtime components in a stack based JavaScript environment. It
|
||||
uses [`duktape`](https://github.com/svaarala/duktape) under the hood, mainly
|
||||
because I wanted to learn how it works at the time I was writing the code.
|
||||
|
||||
It's not production-ready and overall performance can be highly improved.
|
||||
However, I sacrificed optimizations in favor of a more readable piece of
|
||||
code. I hope I succeeded.<br/>
|
||||
Note also that this isn't neither the only nor (probably) the best way to do it.
|
||||
In fact, the right way depends on the scripting language and the problem one is
|
||||
facing in general.
|
||||
|
||||
The basic idea is that of creating a compile-time component aimed to map all the
|
||||
runtime components assigned to an entity.<br/>
|
||||
Identifiers come in use to address the right function from a map when invoked
|
||||
from the runtime environment and to filter entities when iterating.<br/>
|
||||
With a bit of gymnastic, one can narrow views and improve the performance to
|
||||
some extent but it was not the goal of the example.
|
||||
|
||||
### Sorting: is it possible?
|
||||
|
||||
Of course, sorting entities and components is possible with `EnTT`.<br/>
|
||||
It goes without saying that sorting entities and components is possible with
|
||||
`EnTT`.<br/>
|
||||
In fact, there are two functions that respond to slightly different needs:
|
||||
|
||||
* Components can be sorted directly:
|
||||
@@ -440,8 +661,9 @@ To sum up and as a rule of thumb, use a standard view:
|
||||
|
||||
Use a persistent view in all the other cases.
|
||||
|
||||
To easily iterate entities, all the views offer _C++-ish_ `begin` and `end`
|
||||
member functions that allow users to use them in a typical range-for loop.<br/>
|
||||
To easily iterate entities, all the views offer the common `begin` and `end`
|
||||
member functions that allow users to use a view in a typical range-for
|
||||
loop.<br/>
|
||||
Continue reading for more details or refer to the
|
||||
[official documentation](https://skypjack.github.io/entt/).
|
||||
|
||||
@@ -477,7 +699,7 @@ There is no need to store views around for they are extremely cheap to
|
||||
construct, even though they can be copied without problems and reused
|
||||
freely. In fact, they return newly created and correctly initialized iterators
|
||||
whenever `begin` or `end` are invoked.<br/>
|
||||
To iterate a single component standard view, just use it in range-for:
|
||||
To iterate a single component standard view, either use it in range-for loop:
|
||||
|
||||
```cpp
|
||||
auto view = registry.view<Renderable>();
|
||||
@@ -489,8 +711,21 @@ for(auto entity: view) {
|
||||
}
|
||||
```
|
||||
|
||||
**Note**: prefer the `get` member function of the view instead of the `get`
|
||||
member function template of the registry during iterations.
|
||||
Or rely on the `each` member function to iterate entities and get all their
|
||||
components at once:
|
||||
|
||||
```cpp
|
||||
registry.view<Renderable>().each([](auto entity, auto &renderable) {
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
Performance are more or less the same. The best approach depends mainly on
|
||||
whether all the components have to be accessed or not.
|
||||
|
||||
**Note**: prefer the `get` member function of a view instead of the `get` member
|
||||
function template of a registry during iterations, if possible. However, keep in
|
||||
mind that it works only with the components of the view itself.
|
||||
|
||||
#### Multi component standard view
|
||||
|
||||
@@ -508,7 +743,7 @@ There is no need to store views around for they are extremely cheap to
|
||||
construct, even though they can be copied without problems and reused
|
||||
freely. In fact, they return newly created and correctly initialized iterators
|
||||
whenever `begin` or `end` are invoked.<br/>
|
||||
To iterate a multi component standard view, just use it in range-for:
|
||||
To iterate a multi component standard view, either use it in range-for loop:
|
||||
|
||||
```cpp
|
||||
auto view = registry.view<Position, Velocity>();
|
||||
@@ -521,8 +756,21 @@ for(auto entity: view) {
|
||||
}
|
||||
```
|
||||
|
||||
**Note**: prefer the `get` member function template of the view instead of the
|
||||
`get` member function template of the registry during iterations.
|
||||
Or rely on the `each` member function to iterate entities and get all their
|
||||
components at once:
|
||||
|
||||
```cpp
|
||||
registry.view<Position, Velocity>().each([](auto entity, auto &position, auto &velocity) {
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
Performance are more or less the same. The best approach depends mainly on
|
||||
whether all the components have to be accessed or not.
|
||||
|
||||
**Note**: prefer the `get` member function of a view instead of the `get` member
|
||||
function template of a registry during iterations, if possible. However, keep in
|
||||
mind that it works only with the components of the view itself.
|
||||
|
||||
### Persistent View
|
||||
|
||||
@@ -563,7 +811,7 @@ of the components for which it has been constructed.<br/>
|
||||
Refer to the [official documentation](https://skypjack.github.io/entt/) for all
|
||||
the details.
|
||||
|
||||
To iterate a persistent view, just use it in range-for:
|
||||
To iterate a persistent view, either use it in range-for loop:
|
||||
|
||||
```cpp
|
||||
auto view = registry.persistent<Position, Velocity>();
|
||||
@@ -576,8 +824,21 @@ for(auto entity: view) {
|
||||
}
|
||||
```
|
||||
|
||||
**Note**: prefer the `get` member function template of the view instead of the
|
||||
`get` member function template of the registry during iterations.
|
||||
Or rely on the `each` member function to iterate entities and get all their
|
||||
components at once:
|
||||
|
||||
```cpp
|
||||
registry.persistent<Position, Velocity>().each([](auto entity, auto &position, auto &velocity) {
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
Performance are more or less the same. The best approach depends mainly on
|
||||
whether all the components have to be accessed or not.
|
||||
|
||||
**Note**: prefer the `get` member function of a view instead of the `get` member
|
||||
function template of a registry during iterations, if possible. However, keep in
|
||||
mind that it works only with the components of the view itself.
|
||||
|
||||
## Side notes
|
||||
|
||||
@@ -588,7 +849,7 @@ for(auto entity: view) {
|
||||
* As shown in the examples above, the preferred way to get references to the
|
||||
components while iterating a view is by using the view itself. It's a faster
|
||||
alternative to the `get` member function template that is part of the API of
|
||||
the Registry. That's because the registry must ensure that a pool for the
|
||||
the Registry. This is because the registry must ensure that a pool for the
|
||||
given component exists before to use it; on the other side, views force the
|
||||
construction of the pools for all their components and access them directly,
|
||||
thus avoiding all the checks.
|
||||
@@ -625,21 +886,6 @@ for(auto entity: view) {
|
||||
renderable entities while updating a physic component concurrently on a
|
||||
separate thread if needed.
|
||||
|
||||
## What else?
|
||||
|
||||
The `EnTT` framework is moving its first steps. More and more will come in the
|
||||
future and hopefully I'm going to work on it for a long time.<br/>
|
||||
Here is a brief list of what it offers today:
|
||||
|
||||
* Statically generated integer identifiers for types.
|
||||
* An entity-component system based on sparse sets.
|
||||
* Signal handlers and event emitters of any type.
|
||||
* ...
|
||||
* Any other business.
|
||||
|
||||
Consider it a work in progress. For more details and an updated list, please
|
||||
refer to the [online documentation](https://skypjack.github.io/entt/).
|
||||
|
||||
# Contributors
|
||||
|
||||
If you want to contribute, please send patches as pull requests against the
|
||||
|
||||
@@ -4,21 +4,18 @@
|
||||
|
||||
set(TARGET_DOCS docs)
|
||||
|
||||
set(DOXY_IN_FILE doxy.in)
|
||||
|
||||
set(DOXY_SOURCE_DIRECTORY ${PROJECT_SRC_DIR})
|
||||
set(DOXY_SOURCE_DIRECTORY ${entt_SOURCE_DIR}/src)
|
||||
set(DOXY_DOCS_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
set(DOXY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
|
||||
set(DOXY_CFG_FILE doxy.cfg)
|
||||
|
||||
configure_file(${DOXY_IN_FILE} ${DOXY_CFG_FILE} @ONLY)
|
||||
configure_file(doxy.in doxy.cfg @ONLY)
|
||||
|
||||
add_custom_target(
|
||||
${TARGET_DOCS}
|
||||
COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/${DOXY_CFG_FILE}
|
||||
COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/doxy.cfg
|
||||
WORKING_DIRECTORY ${entt_SOURCE_DIR}
|
||||
VERBATIM
|
||||
SOURCES ${DOXY_IN_FILE}
|
||||
SOURCES doxy.in
|
||||
)
|
||||
|
||||
install(
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
#include<type_traits>
|
||||
#include<cstddef>
|
||||
#include<utility>
|
||||
|
||||
|
||||
namespace entt {
|
||||
@@ -24,15 +23,23 @@ class Family {
|
||||
return value++;
|
||||
}
|
||||
|
||||
template<typename...>
|
||||
static std::size_t family() noexcept {
|
||||
static const std::size_t value = identifier();
|
||||
return value;
|
||||
}
|
||||
|
||||
public:
|
||||
/*! @brief Unsigned integer type. */
|
||||
using family_type = std::size_t;
|
||||
|
||||
/**
|
||||
* @brief Returns an unique identifier for the given type.
|
||||
* @return Statically generated unique identifier for the given type.
|
||||
*/
|
||||
template<typename...>
|
||||
static std::size_t type() noexcept {
|
||||
static const std::size_t value = identifier();
|
||||
return value;
|
||||
template<typename... Type>
|
||||
static family_type type() noexcept {
|
||||
return family<std::decay_t<Type>...>();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
109
src/entt/core/hashed_string.hpp
Normal file
109
src/entt/core/hashed_string.hpp
Normal file
@@ -0,0 +1,109 @@
|
||||
#ifndef ENTT_CORE_HASHED_STRING_HPP
|
||||
#define ENTT_CORE_HASHED_STRING_HPP
|
||||
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/**
|
||||
* @brief Zero overhead resource identifier.
|
||||
*
|
||||
* A hashed string is a compile-time tool that allows users to use
|
||||
* human-readable identifers in the codebase while using their numeric
|
||||
* counterparts at runtime.<br/>
|
||||
* Because of that, a hashed string can also be used in constant expressions if
|
||||
* required.
|
||||
*/
|
||||
class HashedString final {
|
||||
struct ConstCharWrapper final {
|
||||
// non-explicit constructor on purpose
|
||||
constexpr ConstCharWrapper(const char *str) noexcept: str{str} {}
|
||||
const char *str;
|
||||
};
|
||||
|
||||
static constexpr std::uint64_t offset = 14695981039346656037ull;
|
||||
static constexpr std::uint64_t prime = 1099511628211ull;
|
||||
|
||||
// Fowler–Noll–Vo hash function v. 1a - the good
|
||||
static constexpr std::uint64_t helper(std::uint64_t partial, const char *str) noexcept {
|
||||
return str[0] == 0 ? partial : helper((partial^str[0])*prime, str+1);
|
||||
}
|
||||
|
||||
public:
|
||||
/*! @brief Unsigned integer type. */
|
||||
using hash_type = std::uint64_t;
|
||||
|
||||
/**
|
||||
* @brief Constructs a hashed string from an array of const chars.
|
||||
*
|
||||
* Forcing template resolution avoids implicit conversions. An
|
||||
* human-readable identifier can be anything but a plain, old bunch of
|
||||
* characters.<br/>
|
||||
* Example of use:
|
||||
* @code{.cpp}
|
||||
* HashedString sh{"my.png"};
|
||||
* @endcode
|
||||
*
|
||||
* @tparam N Number of characters of the identifier.
|
||||
* @param str Human-readable identifer.
|
||||
*/
|
||||
template <std::size_t N>
|
||||
constexpr HashedString(const char (&str)[N]) noexcept
|
||||
: hash{helper(offset, str)}, str{str}
|
||||
{}
|
||||
|
||||
/**
|
||||
* @brief Explicit constructor on purpose to avoid constructing a hashed
|
||||
* string directly from a `const char *`.
|
||||
*
|
||||
* @param wrapper Helps achieving the purpose by relying on overloading.
|
||||
*/
|
||||
explicit constexpr HashedString(ConstCharWrapper wrapper) noexcept
|
||||
: hash{helper(offset, wrapper.str)}, str{wrapper.str}
|
||||
{}
|
||||
|
||||
/**
|
||||
* @brief Returns the human-readable representation of a hashed string.
|
||||
* @return The string used to initialize the instance.
|
||||
*/
|
||||
constexpr operator const char *() const noexcept { return str; }
|
||||
|
||||
/**
|
||||
* @brief Returns the numeric representation of a hashed string.
|
||||
* @return The numeric representation of the instance.
|
||||
*/
|
||||
constexpr operator hash_type() const noexcept { return hash; }
|
||||
|
||||
/**
|
||||
* @brief Compares two hashed strings.
|
||||
* @param other Hashed string with which to compare.
|
||||
* @return True if the two hashed strings are identical, false otherwise.
|
||||
*/
|
||||
constexpr bool operator==(const HashedString &other) const noexcept {
|
||||
return hash == other.hash;
|
||||
}
|
||||
|
||||
private:
|
||||
const hash_type hash;
|
||||
const char *str;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Compares two hashed strings.
|
||||
* @param lhs A valid hashed string.
|
||||
* @param rhs A valid hashed string.
|
||||
* @return True if the two hashed strings are identical, false otherwise.
|
||||
*/
|
||||
constexpr bool operator!=(const HashedString &lhs, const HashedString &rhs) noexcept {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_CORE_HASHED_STRING_HPP
|
||||
@@ -13,21 +13,37 @@ namespace entt {
|
||||
namespace {
|
||||
|
||||
|
||||
template<typename Type>
|
||||
struct Wrapper {
|
||||
using type = Type;
|
||||
constexpr Wrapper(std::size_t index): index{index} {}
|
||||
const std::size_t index;
|
||||
template<typename... Types>
|
||||
struct Identifier final: Identifier<Types>... {
|
||||
using identifier_type = std::size_t;
|
||||
|
||||
template<std::size_t... Indexes>
|
||||
constexpr Identifier(std::index_sequence<Indexes...>)
|
||||
: Identifier<Types>{std::index_sequence<Indexes>{}}...
|
||||
{}
|
||||
|
||||
template<typename Type>
|
||||
constexpr std::size_t get() const {
|
||||
return Identifier<std::decay_t<Type>>::get();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template<typename... Types>
|
||||
struct Identifier final: Wrapper<Types>... {
|
||||
template<std::size_t... Indexes>
|
||||
constexpr Identifier(std::index_sequence<Indexes...>): Wrapper<Types>{Indexes}... {}
|
||||
template<typename Type>
|
||||
struct Identifier<Type> {
|
||||
using identifier_type = std::size_t;
|
||||
|
||||
template<typename Type>
|
||||
constexpr std::size_t get() const { return Wrapper<std::decay_t<Type>>::index; }
|
||||
template<std::size_t Index>
|
||||
constexpr Identifier(std::index_sequence<Index>)
|
||||
: index{Index}
|
||||
{}
|
||||
|
||||
constexpr std::size_t get() const {
|
||||
return index;
|
||||
}
|
||||
|
||||
private:
|
||||
const std::size_t index;
|
||||
};
|
||||
|
||||
|
||||
@@ -59,7 +75,16 @@ struct Identifier final: Wrapper<Types>... {
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* @tparam Types The list of types for which to generate identifiers.
|
||||
* @note
|
||||
* In case of single type list, `get` isn't a member function template:
|
||||
* @code{.cpp}
|
||||
* func(std::integral_constant<
|
||||
* entt::ident<AType>::identifier_type,
|
||||
* entt::ident<AType>::get()
|
||||
* >{});
|
||||
* @endcode
|
||||
*
|
||||
* @tparam Types List of types for which to generate identifiers.
|
||||
*/
|
||||
template<typename... Types>
|
||||
constexpr auto ident = Identifier<std::decay_t<Types>...>{std::make_index_sequence<sizeof...(Types)>{}};
|
||||
|
||||
152
src/entt/entity/actor.hpp
Normal file
152
src/entt/entity/actor.hpp
Normal file
@@ -0,0 +1,152 @@
|
||||
#ifndef ENTT_ENTITY_ACTOR_HPP
|
||||
#define ENTT_ENTITY_ACTOR_HPP
|
||||
|
||||
|
||||
#include <utility>
|
||||
#include "registry.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/**
|
||||
* @brief Dedicated to those who aren't confident with entity-component systems.
|
||||
*
|
||||
* Tiny wrapper around a registry, for all those users that aren't confident
|
||||
* with entity-component systems and prefer to iterate objects directly.
|
||||
*
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
* @tparam Delta Type to use to provide elapsed time.
|
||||
*/
|
||||
template<typename Entity, typename Delta>
|
||||
struct Actor {
|
||||
/*! @brief Type of registry used internally. */
|
||||
using registry_type = Registry<Entity>;
|
||||
/*! @brief Type used to provide elapsed time. */
|
||||
using delta_type = Delta;
|
||||
|
||||
/**
|
||||
* @brief Constructs an actor by using the given registry.
|
||||
* @param reg An entity-component system properly initialized.
|
||||
*/
|
||||
Actor(Registry<Entity> ®)
|
||||
: reg{reg}, entity{reg.create()}
|
||||
{}
|
||||
|
||||
/*! @brief Default destructor. */
|
||||
virtual ~Actor() {
|
||||
reg.destroy(entity);
|
||||
}
|
||||
|
||||
/*! @brief Default copy constructor. */
|
||||
Actor(const Actor &) = default;
|
||||
/*! @brief Default move constructor. */
|
||||
Actor(Actor &&) = default;
|
||||
|
||||
/*! @brief Default copy assignment operator. @return This actor. */
|
||||
Actor & operator=(const Actor &) = default;
|
||||
/*! @brief Default move assignment operator. @return This actor. */
|
||||
Actor & operator=(Actor &&) = default;
|
||||
|
||||
/**
|
||||
* @brief Assigns the given component to an actor.
|
||||
*
|
||||
* A new instance of the given component is created and initialized with the
|
||||
* arguments provided (the component must have a proper constructor or be of
|
||||
* aggregate type). Then the component is assigned to the actor.<br/>
|
||||
* In case the actor already has a component of the given type, it's
|
||||
* replaced with the new one.
|
||||
*
|
||||
* @tparam Component Type of the component to create.
|
||||
* @tparam Args Types of arguments to use to construct the component.
|
||||
* @param args Parameters to use to initialize the component.
|
||||
* @return A reference to the newly created component.
|
||||
*/
|
||||
template<typename Component, typename... Args>
|
||||
Component & set(Args&&... args) {
|
||||
return reg.template accomodate<Component>(entity, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Removes the given component from an actor.
|
||||
* @tparam Component Type of the component to remove.
|
||||
*/
|
||||
template<typename Component>
|
||||
void unset() {
|
||||
reg.template remove<Component>(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if an actor has the given component.
|
||||
* @tparam Component Type of the component for which to perform the check.
|
||||
* @return True if the actor has the component, false otherwise.
|
||||
*/
|
||||
template<typename Component>
|
||||
bool has() const noexcept {
|
||||
return reg.template has<Component>(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a reference to the given component for an actor.
|
||||
* @tparam Component Type of the component to get.
|
||||
* @return A reference to the instance of the component owned by the entity.
|
||||
*/
|
||||
template<typename Component>
|
||||
const Component & get() const noexcept {
|
||||
return reg.template get<Component>(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a reference to the given component for an actor.
|
||||
* @tparam Component Type of the component to get.
|
||||
* @return A reference to the instance of the component owned by the entity.
|
||||
*/
|
||||
template<typename Component>
|
||||
Component & get() noexcept {
|
||||
return const_cast<Component &>(const_cast<const Actor *>(this)->get<Component>());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a reference to the underlying registry.
|
||||
* @return A reference to the underlying registry
|
||||
*/
|
||||
const registry_type & registry() const noexcept {
|
||||
return reg;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a reference to the underlying registry.
|
||||
* @return A reference to the underlying registry
|
||||
*/
|
||||
registry_type & registry() noexcept {
|
||||
return const_cast<registry_type &>(const_cast<const Actor *>(this)->registry());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Updates an actor, whatever it means to update it.
|
||||
* @param delta Elapsed time.
|
||||
*/
|
||||
virtual void update(delta_type delta) = 0;
|
||||
|
||||
private:
|
||||
registry_type ®
|
||||
Entity entity;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Default actor class.
|
||||
*
|
||||
* The default actor is the best choice for almost all the applications.<br/>
|
||||
* Users should have a really good reason to choose something different.
|
||||
*
|
||||
* @tparam Delta Type to use to provide elapsed time.
|
||||
*/
|
||||
template<typename Delta>
|
||||
using DefaultActor = Actor<std::uint32_t, Delta>;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_ENTITY_ACTOR_HPP
|
||||
@@ -6,7 +6,9 @@
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cassert>
|
||||
#include <algorithm>
|
||||
#include "../core/family.hpp"
|
||||
#include "sparse_set.hpp"
|
||||
#include "traits.hpp"
|
||||
@@ -17,7 +19,7 @@ namespace entt {
|
||||
|
||||
|
||||
/**
|
||||
* @brief A repository class for entities and components.
|
||||
* @brief Fast and reliable entity-component system.
|
||||
*
|
||||
* The registry is the core class of the entity-component framework.<br/>
|
||||
* It stores entities and arranges pools of components on a per request basis.
|
||||
@@ -28,10 +30,26 @@ namespace entt {
|
||||
*/
|
||||
template<typename Entity>
|
||||
class Registry {
|
||||
using tag_family = Family<struct InternalRegistryTagFamily>;
|
||||
using component_family = Family<struct InternalRegistryComponentFamily>;
|
||||
using view_family = Family<struct InternalRegistryViewFamily>;
|
||||
using traits_type = entt_traits<Entity>;
|
||||
|
||||
struct Attachee {
|
||||
Entity entity;
|
||||
};
|
||||
|
||||
template<typename Tag>
|
||||
struct Attaching: Attachee {
|
||||
// requirements for aggregates are relaxed only since C++17
|
||||
template<typename... Args>
|
||||
Attaching(Entity entity, Tag tag)
|
||||
: Attachee{entity}, tag{std::move(tag)}
|
||||
{}
|
||||
|
||||
Tag tag;
|
||||
};
|
||||
|
||||
template<typename Component>
|
||||
struct Pool: SparseSet<Entity, Component> {
|
||||
using test_fn_type = bool(Registry::*)(Entity) const;
|
||||
@@ -42,7 +60,7 @@ class Registry {
|
||||
|
||||
for(auto &&listener: listeners) {
|
||||
if((registry.*listener.second)(entity)) {
|
||||
listener.first.construct(entity);
|
||||
listener.first->construct(entity);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,20 +71,26 @@ class Registry {
|
||||
SparseSet<Entity, Component>::destroy(entity);
|
||||
|
||||
for(auto &&listener: listeners) {
|
||||
auto &handler = listener.first;
|
||||
auto *handler = listener.first;
|
||||
|
||||
if(handler.has(entity)) {
|
||||
handler.destroy(entity);
|
||||
if(handler->has(entity)) {
|
||||
handler->destroy(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void append(SparseSet<Entity> &handler, test_fn_type fn) {
|
||||
inline void append(SparseSet<Entity> *handler, test_fn_type fn) {
|
||||
listeners.emplace_back(handler, fn);
|
||||
}
|
||||
|
||||
inline void remove(SparseSet<Entity> *handler) {
|
||||
listeners.erase(std::remove_if(listeners.begin(), listeners.end(), [handler](auto &listener) {
|
||||
return listener.first == handler;
|
||||
}), listeners.end());
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<std::pair<SparseSet<Entity> &, test_fn_type>> listeners;
|
||||
std::vector<std::pair<SparseSet<Entity> *, test_fn_type>> listeners;
|
||||
};
|
||||
|
||||
template<typename Component>
|
||||
@@ -83,7 +107,6 @@ class Registry {
|
||||
|
||||
template<typename Component>
|
||||
Pool<Component> & pool() noexcept {
|
||||
assert(managed<Component>());
|
||||
return const_cast<Pool<Component> &>(const_cast<const Registry *>(this)->pool<Component>());
|
||||
}
|
||||
|
||||
@@ -102,6 +125,35 @@ class Registry {
|
||||
return pool<Component>();
|
||||
}
|
||||
|
||||
template<typename... Component>
|
||||
SparseSet<Entity> & handler() {
|
||||
static_assert(sizeof...(Component) > 1, "!");
|
||||
const auto vtype = view_family::type<Component...>();
|
||||
|
||||
if(!(vtype < handlers.size())) {
|
||||
handlers.resize(vtype + 1);
|
||||
}
|
||||
|
||||
if(!handlers[vtype]) {
|
||||
using accumulator_type = int[];
|
||||
|
||||
auto set = std::make_unique<SparseSet<Entity>>();
|
||||
|
||||
for(auto entity: view<Component...>()) {
|
||||
set->construct(entity);
|
||||
}
|
||||
|
||||
accumulator_type accumulator = {
|
||||
(ensure<Component>().append(set.get(), &Registry::has<Component...>), 0)...
|
||||
};
|
||||
|
||||
handlers[vtype] = std::move(set);
|
||||
(void)accumulator;
|
||||
}
|
||||
|
||||
return *handlers[vtype];
|
||||
}
|
||||
|
||||
public:
|
||||
/*! @brief Underlying entity identifier. */
|
||||
using entity_type = typename traits_type::entity_type;
|
||||
@@ -109,26 +161,62 @@ public:
|
||||
using version_type = typename traits_type::version_type;
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = std::size_t;
|
||||
/*! @brief Unsigned integer type. */
|
||||
using tag_type = typename tag_family::family_type;
|
||||
/*! @brief Unsigned integer type. */
|
||||
using component_type = typename component_family::family_type;
|
||||
|
||||
/*! @brief Default constructor, explicit on purpose. */
|
||||
explicit Registry() = default;
|
||||
/*! @brief Default destructor. */
|
||||
~Registry() = default;
|
||||
/*! @brief Default constructor. */
|
||||
Registry() = default;
|
||||
|
||||
/*! @brief Copying a sparse set isn't allowed. */
|
||||
/*! @brief Copying a registry isn't allowed. */
|
||||
Registry(const Registry &) = delete;
|
||||
/*! @brief Moving a sparse set isn't allowed. */
|
||||
Registry(Registry &&) = delete;
|
||||
/*! @brief Default move constructor. */
|
||||
Registry(Registry &&) = default;
|
||||
|
||||
/*! @brief Copying a sparse set isn't allowed. @return This sparse set. */
|
||||
/*! @brief Copying a registry isn't allowed. @return This registry. */
|
||||
Registry & operator=(const Registry &) = delete;
|
||||
/*! @brief Moving a sparse set isn't allowed. @return This sparse set. */
|
||||
Registry & operator=(Registry &&) = delete;
|
||||
/*! @brief Default move assignment operator. @return This registry. */
|
||||
Registry & operator=(Registry &&) = default;
|
||||
|
||||
/**
|
||||
* @brief Returns the numeric identifier of a type of tag at runtime.
|
||||
*
|
||||
* The given tag doesn't need to be necessarily in use. However, the
|
||||
* registry could decide to prepare internal data structures for it for
|
||||
* later uses.<br/>
|
||||
* Do not use this functionality to provide numeric identifiers to types at
|
||||
* runtime.
|
||||
*
|
||||
* @tparam Tag Type of tag to query.
|
||||
* @return Runtime numeric identifier of the given type of tag.
|
||||
*/
|
||||
template<typename Tag>
|
||||
tag_type tag() const noexcept {
|
||||
return tag_family::type<Tag>();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the numeric identifier of a type of component at runtime.
|
||||
*
|
||||
* The given component doesn't need to be necessarily in use. However, the
|
||||
* registry could decide to prepare internal data structures for it for
|
||||
* later uses.<br/>
|
||||
* Do not use this functionality to provide numeric identifiers to types at
|
||||
* runtime.
|
||||
*
|
||||
* @tparam Component Type of component to query.
|
||||
* @return Runtime numeric identifier of the given type of component.
|
||||
*/
|
||||
template<typename Component>
|
||||
component_type component() const noexcept {
|
||||
return component_family::type<Component>();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the number of existing components of the given type.
|
||||
* @tparam Component The type of the component to which to return the size.
|
||||
* @return The number of existing components of the given type.
|
||||
* @tparam Component Type of component of which to return the size.
|
||||
* @return Number of existing components of the given type.
|
||||
*/
|
||||
template<typename Component>
|
||||
size_type size() const noexcept {
|
||||
@@ -137,7 +225,7 @@ public:
|
||||
|
||||
/**
|
||||
* @brief Returns the number of entities still in use.
|
||||
* @return The number of entities still in use.
|
||||
* @return Number of entities still in use.
|
||||
*/
|
||||
size_type size() const noexcept {
|
||||
return entities.size() - available.size();
|
||||
@@ -145,7 +233,7 @@ public:
|
||||
|
||||
/**
|
||||
* @brief Returns the number of entities ever created.
|
||||
* @return The number of entities ever created.
|
||||
* @return Number of entities ever created.
|
||||
*/
|
||||
size_type capacity() const noexcept {
|
||||
return entities.size();
|
||||
@@ -153,7 +241,7 @@ public:
|
||||
|
||||
/**
|
||||
* @brief Checks whether the pool for the given component is empty.
|
||||
* @tparam Component The type of the component in which one is interested.
|
||||
* @tparam Component Type of component in which one is interested.
|
||||
* @return True if the pool for the given component is empty, false
|
||||
* otherwise.
|
||||
*/
|
||||
@@ -171,26 +259,28 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Verifies if the entity identifier still refers to a valid entity.
|
||||
* @brief Verifies if an entity identifier still refers to a valid entity.
|
||||
* @param entity An entity identifier, either valid or not.
|
||||
* @return True if the identifier is still valid, false otherwise.
|
||||
*/
|
||||
bool valid(entity_type entity) const noexcept {
|
||||
const auto entt = entity & traits_type::entity_mask;
|
||||
using promotion_type = std::conditional_t<sizeof(size_type) >= sizeof(entity_type), size_type, entity_type>;
|
||||
// explicit promotion to avoid warnings with std::uint16_t
|
||||
const entity_type entt = promotion_type{entity} & traits_type::entity_mask;
|
||||
return (entt < entities.size() && entities[entt] == entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the version stored along with the given entity identifier.
|
||||
* @brief Returns the version stored along with an entity identifier.
|
||||
* @param entity An entity identifier, either valid or not.
|
||||
* @return The version stored along with the given entity identifier.
|
||||
* @return Version stored along with the given entity identifier.
|
||||
*/
|
||||
version_type version(entity_type entity) const noexcept {
|
||||
return version_type((entity >> traits_type::version_shift) & traits_type::version_mask);
|
||||
return version_type((entity >> traits_type::entity_shift) & traits_type::version_mask);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the actual version for the given entity identifier.
|
||||
* @brief Returns the actual version for an entity identifier.
|
||||
*
|
||||
* In case entity identifers are stored around, this function can be used to
|
||||
* know if they are still valid or the entity has been destroyed and
|
||||
@@ -204,18 +294,21 @@ public:
|
||||
* registry doesn't own the given entity.
|
||||
*
|
||||
* @param entity A valid entity identifier.
|
||||
* @return The actual version for the given entity identifier.
|
||||
* @return Actual version for the given entity identifier.
|
||||
*/
|
||||
version_type current(entity_type entity) const noexcept {
|
||||
const auto entt = entity & traits_type::entity_mask;
|
||||
using promotion_type = std::conditional_t<sizeof(size_type) >= sizeof(entity_type), size_type, entity_type>;
|
||||
// explicit promotion to avoid warnings with std::uint16_t
|
||||
const auto entt = promotion_type{entity} & traits_type::entity_mask;
|
||||
assert(entt < entities.size());
|
||||
return version_type((entities[entt] >> traits_type::version_shift) & traits_type::version_mask);
|
||||
return version_type((entities[entt] >> traits_type::entity_shift) & traits_type::version_mask);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a new entity to which the given components are assigned.
|
||||
* @brief Returns a new entity initialized with the given components.
|
||||
*
|
||||
* There are two kinds of entity identifiers:
|
||||
*
|
||||
* * Newly created ones in case no entities have been previously destroyed.
|
||||
* * Recycled one with updated versions.
|
||||
*
|
||||
@@ -224,6 +317,36 @@ public:
|
||||
* function can be used to know if they are still valid or the entity has
|
||||
* been destroyed and potentially recycled.
|
||||
*
|
||||
* The returned entity has fully initialized components assigned.
|
||||
*
|
||||
* @tparam Component A list of components to assign to the entity.
|
||||
* @param components Instances with which to initialize components.
|
||||
* @return A valid entity identifier.
|
||||
*/
|
||||
template<typename... Component>
|
||||
entity_type create(Component&&... components) noexcept {
|
||||
using accumulator_type = int[];
|
||||
const auto entity = create();
|
||||
accumulator_type accumulator = { 0, (ensure<Component>().construct(*this, entity, std::forward<Component>(components)), 0)... };
|
||||
(void)accumulator;
|
||||
return entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a new entity to which the given components are assigned.
|
||||
*
|
||||
* There are two kinds of entity identifiers:
|
||||
*
|
||||
* * Newly created ones in case no entities have been previously destroyed.
|
||||
* * Recycled one with updated versions.
|
||||
*
|
||||
* Users should not care about the type of the returned entity identifier.
|
||||
* In case entity identifers are stored around, the `current` member
|
||||
* function can be used to know if they are still valid or the entity has
|
||||
* been destroyed and potentially recycled.
|
||||
*
|
||||
* The returned entity has default initialized components assigned.
|
||||
*
|
||||
* @tparam Component A list of components to assign to the entity.
|
||||
* @return A valid entity identifier.
|
||||
*/
|
||||
@@ -240,6 +363,7 @@ public:
|
||||
* @brief Creates a new entity and returns it.
|
||||
*
|
||||
* There are two kinds of entity identifiers:
|
||||
*
|
||||
* * Newly created ones in case no entities have been previously destroyed.
|
||||
* * Recycled one with updated versions.
|
||||
*
|
||||
@@ -248,6 +372,8 @@ public:
|
||||
* function can be used to know if they are still valid or the entity has
|
||||
* been destroyed and potentially recycled.
|
||||
*
|
||||
* The returned entity has no components assigned.
|
||||
*
|
||||
* @return A valid entity identifier.
|
||||
*/
|
||||
entity_type create() noexcept {
|
||||
@@ -255,7 +381,8 @@ public:
|
||||
|
||||
if(available.empty()) {
|
||||
entity = entity_type(entities.size());
|
||||
assert((entity >> traits_type::version_shift) == entity_type{});
|
||||
assert(entity < traits_type::entity_mask);
|
||||
assert((entity >> traits_type::entity_shift) == entity_type{});
|
||||
entities.push_back(entity);
|
||||
} else {
|
||||
entity = available.back();
|
||||
@@ -282,11 +409,12 @@ public:
|
||||
*/
|
||||
void destroy(entity_type entity) {
|
||||
assert(valid(entity));
|
||||
|
||||
const auto entt = entity & traits_type::entity_mask;
|
||||
const auto version = 1 + ((entity >> traits_type::version_shift) & traits_type::version_mask);
|
||||
entities[entt] = entt | (version << traits_type::version_shift);
|
||||
available.push_back(entity);
|
||||
const auto version = version_type{1} + ((entity >> traits_type::entity_shift) & traits_type::version_mask);
|
||||
const auto next = entt | (version << traits_type::entity_shift);
|
||||
|
||||
entities[entt] = next;
|
||||
available.push_back(next);
|
||||
|
||||
for(auto &&cpool: pools) {
|
||||
if(cpool && cpool->has(entity)) {
|
||||
@@ -296,7 +424,122 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns the given component to the given entity.
|
||||
* @brief Attaches a tag to an entity.
|
||||
*
|
||||
* Usually, pools of components allocate enough memory to store a bunch of
|
||||
* elements even if only one of them is used. On the other hand, there are
|
||||
* cases where all what is needed is a single instance component to attach
|
||||
* to an entity.<br/>
|
||||
* Tags are the right tool to achieve the purpose.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an invalid entity or to attach to an entity a tag that
|
||||
* already has an owner results in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode in case of
|
||||
* invalid entity or if the tag has been already attached to another entity.
|
||||
*
|
||||
* @tparam Tag Type of tag to create.
|
||||
* @tparam Args Types of arguments to use to construct the tag.
|
||||
* @param entity A valid entity identifier
|
||||
* @param args Parameters to use to initialize the tag.
|
||||
* @return A reference to the newly created tag.
|
||||
*/
|
||||
template<typename Tag, typename... Args>
|
||||
Tag & attach(entity_type entity, Args&&... args) {
|
||||
assert(valid(entity));
|
||||
assert(!has<Tag>());
|
||||
const auto ttype = tag_family::type<Tag>();
|
||||
|
||||
if(!(ttype < tags.size())) {
|
||||
tags.resize(ttype + 1);
|
||||
}
|
||||
|
||||
tags[ttype].reset(new Attaching<Tag>{entity, { std::forward<Args>(args)... }});
|
||||
|
||||
return static_cast<Attaching<Tag> *>(tags[ttype].get())->tag;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Removes a tag from its owner, if any.
|
||||
* @tparam Tag Type of tag to remove.
|
||||
*/
|
||||
template<typename Tag>
|
||||
void remove() {
|
||||
if(has<Tag>()) {
|
||||
tags[tag_family::type<Tag>()].reset();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if a tag has an owner.
|
||||
* @tparam Tag Type of tag for which to perform the check.
|
||||
* @return True if the tag already has an owner, false otherwise.
|
||||
*/
|
||||
template<typename Tag>
|
||||
bool has() const noexcept {
|
||||
const auto ttype = tag_family::type<Tag>();
|
||||
return (ttype < tags.size() &&
|
||||
// it's a valid tag
|
||||
tags[ttype] &&
|
||||
// the associated entity hasn't been destroyed in the meantime
|
||||
tags[ttype]->entity == (entities[tags[ttype]->entity & traits_type::entity_mask]));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a reference to a tag.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to get a tag that hasn't an owner results in undefined
|
||||
* behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* tag hasn't been previously attached to an entity.
|
||||
*
|
||||
* @tparam Tag Type of tag to get.
|
||||
* @return A reference to the tag.
|
||||
*/
|
||||
template<typename Tag>
|
||||
const Tag & get() const noexcept {
|
||||
assert(has<Tag>());
|
||||
return static_cast<Attaching<Tag> *>(tags[tag_family::type<Tag>()].get())->tag;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a reference to a tag.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to get a tag that hasn't an owner results in undefined
|
||||
* behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* tag hasn't been previously attached to an entity.
|
||||
*
|
||||
* @tparam Tag Type of tag to get.
|
||||
* @return A reference to the tag.
|
||||
*/
|
||||
template<typename Tag>
|
||||
Tag & get() noexcept {
|
||||
return const_cast<Tag &>(const_cast<const Registry *>(this)->get<Tag>());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Gets the owner of a tag, if any.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to get the owner of a tag that hasn't been previously attached
|
||||
* to an entity results in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* tag hasn't an owner.
|
||||
*
|
||||
* @tparam Tag Type of tag of which to get the owner.
|
||||
* @return A valid entity identifier.
|
||||
*/
|
||||
template<typename Tag>
|
||||
entity_type attachee() const noexcept {
|
||||
assert(has<Tag>());
|
||||
return tags[tag_family::type<Tag>()]->entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns the given component to an entity.
|
||||
*
|
||||
* A new instance of the given component is created and initialized with the
|
||||
* arguments provided (the component must have a proper constructor or be of
|
||||
@@ -309,10 +552,10 @@ public:
|
||||
* invalid entity or if the entity already owns an instance of the given
|
||||
* component.
|
||||
*
|
||||
* @tparam Component The type of the component to create.
|
||||
* @tparam Args The types of the arguments used to construct the component.
|
||||
* @tparam Component Type of component to create.
|
||||
* @tparam Args Types of arguments to use to construct the component.
|
||||
* @param entity A valid entity identifier.
|
||||
* @param args The parameters to use to initialize the component.
|
||||
* @param args Parameters to use to initialize the component.
|
||||
* @return A reference to the newly created component.
|
||||
*/
|
||||
template<typename Component, typename... Args>
|
||||
@@ -322,7 +565,7 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Removes the given component from the given entity.
|
||||
* @brief Removes the given component from an entity.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an invalid entity or to remove a component from an
|
||||
@@ -331,40 +574,39 @@ public:
|
||||
* invalid entity or if the entity doesn't own an instance of the given
|
||||
* component.
|
||||
*
|
||||
* @tparam Component The type of the component to remove.
|
||||
* @tparam Component Type of component to remove.
|
||||
* @param entity A valid entity identifier.
|
||||
*/
|
||||
template<typename Component>
|
||||
void remove(entity_type entity) {
|
||||
assert(valid(entity));
|
||||
return pool<Component>().destroy(entity);
|
||||
pool<Component>().destroy(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if the given entity has all the given components.
|
||||
* @brief Checks if an entity has all the given components.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an invalid entity results in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode in case of
|
||||
* invalid entity.
|
||||
*
|
||||
* @tparam Component The components for which to perform the check.
|
||||
* @tparam Component Components for which to perform the check.
|
||||
* @param entity A valid entity identifier.
|
||||
* @return True if the entity has all the components, false otherwise.
|
||||
*/
|
||||
template<typename... Component>
|
||||
bool has(entity_type entity) const noexcept {
|
||||
static_assert(sizeof...(Component) > 0, "!");
|
||||
assert(valid(entity));
|
||||
using accumulator_type = bool[];
|
||||
bool all = true;
|
||||
accumulator_type accumulator = { (all = all && managed<Component>() && pool<Component>().has(entity))... };
|
||||
accumulator_type accumulator = { all, (all = all && managed<Component>() && pool<Component>().has(entity))... };
|
||||
(void)accumulator;
|
||||
return all;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Gets a reference to the given component owned by the given entity.
|
||||
* @brief Returns a reference to the given component for an entity.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an invalid entity or to get a component from an entity
|
||||
@@ -373,7 +615,7 @@ public:
|
||||
* invalid entity or if the entity doesn't own an instance of the given
|
||||
* component.
|
||||
*
|
||||
* @tparam Component The type of the component to get.
|
||||
* @tparam Component Type of component to get.
|
||||
* @param entity A valid entity identifier.
|
||||
* @return A reference to the instance of the component owned by the entity.
|
||||
*/
|
||||
@@ -384,7 +626,7 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Gets a reference to the given component owned by the given entity.
|
||||
* @brief Returns a reference to the given component for an entity.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an invalid entity or to get a component from an entity
|
||||
@@ -393,7 +635,7 @@ public:
|
||||
* invalid entity or if the entity doesn't own an instance of the given
|
||||
* component.
|
||||
*
|
||||
* @tparam Component The type of the component to get.
|
||||
* @tparam Component Type of component to get.
|
||||
* @param entity A valid entity identifier.
|
||||
* @return A reference to the instance of the component owned by the entity.
|
||||
*/
|
||||
@@ -403,7 +645,7 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Replaces the given component for the given entity.
|
||||
* @brief Replaces the given component for an entity.
|
||||
*
|
||||
* A new instance of the given component is created and initialized with the
|
||||
* arguments provided (the component must have a proper constructor or be of
|
||||
@@ -416,10 +658,10 @@ public:
|
||||
* invalid entity or if the entity doesn't own an instance of the given
|
||||
* component.
|
||||
*
|
||||
* @tparam Component The type of the component to replace.
|
||||
* @tparam Args The types of the arguments used to construct the component.
|
||||
* @tparam Component Type of component to replace.
|
||||
* @tparam Args Types of arguments to use to construct the component.
|
||||
* @param entity A valid entity identifier.
|
||||
* @param args The parameters to use to initialize the component.
|
||||
* @param args Parameters to use to initialize the component.
|
||||
* @return A reference to the newly created component.
|
||||
*/
|
||||
template<typename Component, typename... Args>
|
||||
@@ -429,7 +671,7 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns or replaces the given component to the given entity.
|
||||
* @brief Assigns or replaces the given component for an entity.
|
||||
*
|
||||
* Equivalent to the following snippet (pseudocode):
|
||||
*
|
||||
@@ -449,10 +691,10 @@ public:
|
||||
* An assertion will abort the execution at runtime in debug mode in case of
|
||||
* invalid entity.
|
||||
*
|
||||
* @tparam Component The type of the component to assign or replace.
|
||||
* @tparam Args The types of the arguments used to construct the component.
|
||||
* @tparam Component Type of component to assign or replace.
|
||||
* @tparam Args Types of arguments to use to construct the component.
|
||||
* @param entity A valid entity identifier.
|
||||
* @param args The parameters to use to initialize the component.
|
||||
* @param args Parameters to use to initialize the component.
|
||||
* @return A reference to the newly created component.
|
||||
*/
|
||||
template<typename Component, typename... Args>
|
||||
@@ -466,7 +708,7 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sorts the pool of the given component.
|
||||
* @brief Sorts the pool of entities for the given component.
|
||||
*
|
||||
* The order of the elements in a pool is highly affected by assignements
|
||||
* of components to entities and deletions. Components are arranged to
|
||||
@@ -481,22 +723,16 @@ public:
|
||||
* comparison function should be equivalent to the following:
|
||||
*
|
||||
* @code{.cpp}
|
||||
* bool(auto e1, auto e2)
|
||||
* bool(const Component &, const Component &)
|
||||
* @endcode
|
||||
*
|
||||
* Where `e1` and `e2` are valid entity identifiers.
|
||||
*
|
||||
* @tparam Component The type of the components to sort.
|
||||
* @tparam Compare The type of the comparison function object.
|
||||
* @tparam Component Type of components to sort.
|
||||
* @tparam Compare Type of comparison function object.
|
||||
* @param compare A valid comparison function object.
|
||||
*/
|
||||
template<typename Component, typename Compare>
|
||||
void sort(Compare compare) {
|
||||
auto &cpool = ensure<Component>();
|
||||
|
||||
cpool.sort([&cpool, compare = std::move(compare)](auto lhs, auto rhs) {
|
||||
return compare(static_cast<const Component &>(cpool.get(lhs)), static_cast<const Component &>(cpool.get(rhs)));
|
||||
});
|
||||
ensure<Component>().sort(std::move(compare));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -526,8 +762,8 @@ public:
|
||||
*
|
||||
* Any subsequent change to `B` won't affect the order in `A`.
|
||||
*
|
||||
* @tparam To The type of the components to sort.
|
||||
* @tparam From The type of the components to use to sort.
|
||||
* @tparam To Type of components to sort.
|
||||
* @tparam From Type of components to use to sort.
|
||||
*/
|
||||
template<typename To, typename From>
|
||||
void sort() {
|
||||
@@ -535,7 +771,7 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Resets the given component for the given entity.
|
||||
* @brief Resets the given component for an entity.
|
||||
*
|
||||
* If the entity has an instance of the component, this function removes the
|
||||
* component from the entity. Otherwise it does nothing.
|
||||
@@ -545,7 +781,7 @@ public:
|
||||
* An assertion will abort the execution at runtime in debug mode in case of
|
||||
* invalid entity.
|
||||
*
|
||||
* @tparam Component The component to reset.
|
||||
* @tparam Component Type of component to reset.
|
||||
* @param entity A valid entity identifier.
|
||||
*/
|
||||
template<typename Component>
|
||||
@@ -567,7 +803,7 @@ public:
|
||||
* For each entity that has an instance of the given component, the
|
||||
* component itself is removed and thus destroyed.
|
||||
*
|
||||
* @tparam Component The component whose pool must be reset.
|
||||
* @tparam Component Type of component whose pool must be reset.
|
||||
*/
|
||||
template<typename Component>
|
||||
void reset() {
|
||||
@@ -583,7 +819,7 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Resets the whole registry.
|
||||
* @brief Resets a whole registry.
|
||||
*
|
||||
* Destroys all the entities. After a call to `reset`, all the entities
|
||||
* previously created are recycled with a new version number. In case entity
|
||||
@@ -592,13 +828,53 @@ public:
|
||||
*/
|
||||
void reset() {
|
||||
available.clear();
|
||||
pools.clear();
|
||||
|
||||
for(auto &&entity: entities) {
|
||||
const auto version = 1 + ((entity >> traits_type::version_shift) & traits_type::version_mask);
|
||||
entity = (entity & traits_type::entity_mask) | (version << traits_type::version_shift);
|
||||
const auto version = version_type{1} + ((entity >> traits_type::entity_shift) & traits_type::version_mask);
|
||||
entity = (entity & traits_type::entity_mask) | (version << traits_type::entity_shift);
|
||||
available.push_back(entity);
|
||||
}
|
||||
|
||||
for(auto &&handler: handlers) {
|
||||
if(handler) {
|
||||
handler->reset();
|
||||
}
|
||||
}
|
||||
|
||||
for(auto &&pool: pools) {
|
||||
if(pool) {
|
||||
pool->reset();
|
||||
}
|
||||
}
|
||||
|
||||
for(auto &&tag: tags) {
|
||||
tag.reset();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Iterate entities and applies them the given function object.
|
||||
*
|
||||
* The function object is invoked for each entity, no matter if it's in use
|
||||
* or not.<br/>
|
||||
* The signature of the function should be equivalent to the following:
|
||||
*
|
||||
* @code{.cpp}
|
||||
* void(entity_type);
|
||||
* @endcode
|
||||
*
|
||||
* Consider using a view if the goal is to iterate entities that have a
|
||||
* determinate set of components. A view is usually faster than combining
|
||||
* this function with a bunch of custom tests.
|
||||
*
|
||||
* @tparam Func Type of the function object to invoke.
|
||||
* @param func A valid function object.
|
||||
*/
|
||||
template<typename Func>
|
||||
void each(Func func) const {
|
||||
for(auto pos = entities.size(); pos > size_type{0}; --pos) {
|
||||
func(entities[pos-1]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -613,6 +889,7 @@ public:
|
||||
*
|
||||
* Standard views do their best to iterate the smallest set of candidate
|
||||
* entites. In particular:
|
||||
*
|
||||
* * Single component views are incredibly fast and iterate a packed array
|
||||
* of entities, all of which has the given component.
|
||||
* * Multi component views look at the number of entities available for each
|
||||
@@ -629,7 +906,7 @@ public:
|
||||
* @see View<Entity, Component>
|
||||
* @see PersistentView
|
||||
*
|
||||
* @tparam Component The type of the components used to construct the view.
|
||||
* @tparam Component Type of components used to construct the view.
|
||||
* @return A newly created standard view.
|
||||
*/
|
||||
template<typename... Component>
|
||||
@@ -651,35 +928,53 @@ public:
|
||||
* can be prepared with this function. Just use the same set of components
|
||||
* that would have been used otherwise to contruct the view.
|
||||
*
|
||||
* @tparam Component The types of the components used to prepare the view.
|
||||
* @tparam Component Types of components used to prepare the view.
|
||||
*/
|
||||
template<typename... Component>
|
||||
void prepare() {
|
||||
static_assert(sizeof...(Component) > 1, "!");
|
||||
const auto vtype = view_family::type<Component...>();
|
||||
handler<Component...>();
|
||||
}
|
||||
|
||||
if(!(vtype < handlers.size())) {
|
||||
handlers.resize(vtype + 1);
|
||||
}
|
||||
|
||||
if(!handlers[vtype]) {
|
||||
/**
|
||||
* @brief Discards all the data structures used for a given persitent view.
|
||||
*
|
||||
* Persistent views occupy memory, no matter if they are in use or not.<br/>
|
||||
* This function can be used to discard all the internal data structures
|
||||
* dedicated to a specific persisten view, with the goal of reducing the
|
||||
* memory pressure.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use a persistent view created before calling this function
|
||||
* results in undefined behavior. No assertion available in this case,
|
||||
* neither in debug mode nor in release mode.
|
||||
*
|
||||
* @tparam Component Types of components of the persistent view.
|
||||
*/
|
||||
template<typename... Component>
|
||||
void discard() {
|
||||
if(contains<Component...>()) {
|
||||
using accumulator_type = int[];
|
||||
|
||||
auto handler = std::make_unique<SparseSet<Entity>>();
|
||||
|
||||
for(auto entity: view<Component...>()) {
|
||||
handler->construct(entity);
|
||||
}
|
||||
|
||||
accumulator_type accumulator = {
|
||||
(ensure<Component>().append(*handler, &Registry::has<Component...>), 0)...
|
||||
};
|
||||
|
||||
handlers[vtype] = std::move(handler);
|
||||
const auto vtype = view_family::type<Component...>();
|
||||
auto *set = handlers[vtype].get();
|
||||
// if a set exists, pools have already been created for it
|
||||
accumulator_type accumulator = { (pool<Component>().remove(set), 0)... };
|
||||
handlers[vtype].reset();
|
||||
(void)accumulator;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if a persistent view has already been prepared.
|
||||
* @tparam Component Types of components of the persistent view.
|
||||
* @return True if the view has already been prepared, false otherwise.
|
||||
*/
|
||||
template<typename... Component>
|
||||
bool contains() const noexcept {
|
||||
static_assert(sizeof...(Component) > 1, "!");
|
||||
const auto vtype = view_family::type<Component...>();
|
||||
return vtype < handlers.size() && handlers[vtype];
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a persistent view for the given components.
|
||||
*
|
||||
@@ -694,6 +989,7 @@ public:
|
||||
* of components grows up and the most of the entities have all the given
|
||||
* components.<br/>
|
||||
* However they have also drawbacks:
|
||||
*
|
||||
* * Each kind of persistent view requires a dedicated data structure that
|
||||
* is allocated within the registry and it increases memory pressure.
|
||||
* * Internal data structures used to construct persistent views must be
|
||||
@@ -713,19 +1009,19 @@ public:
|
||||
* @see View<Entity, Component>
|
||||
* @see PersistentView
|
||||
*
|
||||
* @tparam Component The types of the components used to construct the view.
|
||||
* @tparam Component Types of components used to construct the view.
|
||||
* @return A newly created persistent view.
|
||||
*/
|
||||
template<typename... Component>
|
||||
PersistentView<Entity, Component...> persistent() {
|
||||
static_assert(sizeof...(Component) > 1, "!");
|
||||
prepare<Component...>();
|
||||
return PersistentView<Entity, Component...>{*handlers[view_family::type<Component...>()], ensure<Component>()...};
|
||||
// after the calls to handler, pools have already been created
|
||||
return PersistentView<Entity, Component...>{handler<Component...>(), pool<Component>()...};
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<std::unique_ptr<SparseSet<Entity>>> handlers;
|
||||
std::vector<std::unique_ptr<SparseSet<Entity>>> pools;
|
||||
std::vector<std::unique_ptr<Attachee>> tags;
|
||||
std::vector<entity_type> available;
|
||||
std::vector<entity_type> entities;
|
||||
};
|
||||
|
||||
@@ -3,10 +3,12 @@
|
||||
|
||||
|
||||
#include <algorithm>
|
||||
#include <numeric>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <cstddef>
|
||||
#include <cassert>
|
||||
#include <type_traits>
|
||||
#include "traits.hpp"
|
||||
|
||||
|
||||
@@ -87,6 +89,8 @@ class SparseSet<Entity> {
|
||||
std::size_t pos;
|
||||
};
|
||||
|
||||
static constexpr Entity in_use = (Entity{1} << traits_type::entity_shift);
|
||||
|
||||
public:
|
||||
/*! @brief Underlying entity identifier. */
|
||||
using entity_type = Entity;
|
||||
@@ -97,39 +101,39 @@ public:
|
||||
/*! @brief Input iterator type. */
|
||||
using iterator_type = Iterator;
|
||||
|
||||
/*! @brief Default constructor, explicit on purpose. */
|
||||
explicit SparseSet() noexcept = default;
|
||||
/*! @brief Default constructor. */
|
||||
SparseSet() noexcept = default;
|
||||
|
||||
/*! @brief Default destructor. */
|
||||
virtual ~SparseSet() noexcept = default;
|
||||
|
||||
/*! @brief Copying a sparse set isn't allowed. */
|
||||
SparseSet(const SparseSet &) = delete;
|
||||
/*! @brief Default move constructor. */
|
||||
SparseSet(SparseSet &&) = default;
|
||||
|
||||
/*! @brief Default destructor. */
|
||||
virtual ~SparseSet() noexcept = default;
|
||||
|
||||
/*! @brief Copying a sparse set isn't allowed. @return This sparse set. */
|
||||
SparseSet & operator=(const SparseSet &) = delete;
|
||||
/*! @brief Default move operator. @return This sparse set. */
|
||||
/*! @brief Default move assignment operator. @return This sparse set. */
|
||||
SparseSet & operator=(SparseSet &&) = default;
|
||||
|
||||
/**
|
||||
* @brief Returns the number of elements in the sparse set.
|
||||
* @brief Returns the number of elements in a sparse set.
|
||||
*
|
||||
* The number of elements is also the size of the internal packed array.
|
||||
* There is no guarantee that the internal sparse array has the same size.
|
||||
* Usually the size of the internal sparse array is equal or greater than
|
||||
* the one of the internal packed array.
|
||||
*
|
||||
* @return The number of elements.
|
||||
* @return Number of elements.
|
||||
*/
|
||||
size_type size() const noexcept {
|
||||
return direct.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks whether the sparse set is empty.
|
||||
* @return True is the sparse set is empty, false otherwise.
|
||||
* @brief Checks whether a sparse set is empty.
|
||||
* @return True if the sparse set is empty, false otherwise.
|
||||
*/
|
||||
bool empty() const noexcept {
|
||||
return direct.empty();
|
||||
@@ -188,17 +192,20 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if the sparse set contains the given entity.
|
||||
* @brief Checks if a sparse set contains an entity.
|
||||
* @param entity A valid entity identifier.
|
||||
* @return True if the sparse set contains the entity, false otherwise.
|
||||
*/
|
||||
bool has(entity_type entity) const noexcept {
|
||||
const auto entt = entity & traits_type::entity_mask;
|
||||
return entt < reverse.size() && reverse[entt] < direct.size() && direct[reverse[entt]] == entity;
|
||||
using promotion_type = std::conditional_t<sizeof(size_type) >= sizeof(entity_type), size_type, entity_type>;
|
||||
// explicit promotion to avoid warnings with std::uint16_t
|
||||
const auto entt = promotion_type{entity} & traits_type::entity_mask;
|
||||
// the in-use control bit permits to avoid accessing the direct vector
|
||||
return (entt < reverse.size()) && (reverse[entt] & in_use);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the position of the entity in the sparse set.
|
||||
* @brief Returns the position of an entity in a sparse set.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to get the position of an entity that doesn't belong to the
|
||||
@@ -211,11 +218,13 @@ public:
|
||||
*/
|
||||
pos_type get(entity_type entity) const noexcept {
|
||||
assert(has(entity));
|
||||
return reverse[entity & traits_type::entity_mask];
|
||||
const auto entt = entity & traits_type::entity_mask;
|
||||
// we must get rid of the in-use bit for it's not part of the position
|
||||
return reverse[entt] & ~in_use;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns an entity to the sparse set.
|
||||
* @brief Assigns an entity to a sparse set.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to assign an entity that already belongs to the sparse set
|
||||
@@ -224,25 +233,25 @@ public:
|
||||
* sparse set already contains the given entity.
|
||||
*
|
||||
* @param entity A valid entity identifier.
|
||||
* @return The position of the entity in the internal packed array.
|
||||
*/
|
||||
pos_type construct(entity_type entity) {
|
||||
void construct(entity_type entity) {
|
||||
assert(!has(entity));
|
||||
const auto entt = entity & traits_type::entity_mask;
|
||||
using promotion_type = std::conditional_t<sizeof(size_type) >= sizeof(entity_type), size_type, entity_type>;
|
||||
// explicit promotion to avoid warnings with std::uint16_t
|
||||
const auto entt = promotion_type{entity} & traits_type::entity_mask;
|
||||
|
||||
if(!(entt < reverse.size())) {
|
||||
reverse.resize(entt+1);
|
||||
reverse.resize(entt+1, pos_type{});
|
||||
}
|
||||
|
||||
const auto pos = pos_type(direct.size());
|
||||
reverse[entt] = pos;
|
||||
// we exploit the fact that pos_type is equal to entity_type and pos has
|
||||
// traits_type::version_mask bits unused we can use to mark it as in-use
|
||||
reverse[entt] = pos_type(direct.size()) | in_use;
|
||||
direct.emplace_back(entity);
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Removes the given entity from the sparse set.
|
||||
* @brief Removes an entity from a sparse set.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to remove an entity that doesn't belong to the sparse set
|
||||
@@ -256,14 +265,18 @@ public:
|
||||
assert(has(entity));
|
||||
const auto entt = entity & traits_type::entity_mask;
|
||||
const auto back = direct.back() & traits_type::entity_mask;
|
||||
const auto pos = reverse[entt];
|
||||
reverse[back] = pos;
|
||||
const auto pos = reverse[entt] & ~in_use;
|
||||
// the order matters: if back and entt are the same (for the sparse set
|
||||
// has size 1), switching the two lines below doesn't work as expected
|
||||
reverse[back] = pos | in_use;
|
||||
reverse[entt] = pos;
|
||||
// swapping isn't required here, we are getting rid of the last element
|
||||
direct[pos] = direct.back();
|
||||
direct.pop_back();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Swaps the position of the entities in the internal packed array.
|
||||
* @brief Swaps the position of two entities in the internal packed array.
|
||||
*
|
||||
* For what it's worth, this function affects both the internal sparse array
|
||||
* and the internal packed array. Users should not care of that anyway.
|
||||
@@ -274,49 +287,18 @@ public:
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* sparse set doesn't contain the given entities.
|
||||
*
|
||||
* @param lhs A valid entity identifier.
|
||||
* @param rhs A valid entity identifier.
|
||||
* @param lhs A valid position within the sparse set.
|
||||
* @param rhs A valid position within the sparse set.
|
||||
*/
|
||||
virtual void swap(entity_type lhs, entity_type rhs) {
|
||||
assert(has(lhs));
|
||||
assert(has(rhs));
|
||||
const auto le = lhs & traits_type::entity_mask;
|
||||
const auto re = rhs & traits_type::entity_mask;
|
||||
std::swap(direct[reverse[le]], direct[reverse[re]]);
|
||||
std::swap(reverse[le], reverse[re]);
|
||||
void swap(pos_type lhs, pos_type rhs) noexcept {
|
||||
assert(lhs < direct.size());
|
||||
assert(rhs < direct.size());
|
||||
std::swap(reverse[direct[lhs]], reverse[direct[rhs]]);
|
||||
std::swap(direct[lhs], direct[rhs]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sort entities according to the given comparison function.
|
||||
*
|
||||
* Sort the elements so that iterating the sparse set with a couple of
|
||||
* iterators returns them in the expected order. See `begin` and `end` for
|
||||
* more details.
|
||||
*
|
||||
* @note
|
||||
* Attempting to iterate elements using the raw pointer returned by `data`
|
||||
* gives no guarantees on the order, even though `sort` has been invoked.
|
||||
*
|
||||
* @tparam Compare The type of the comparison function.
|
||||
* @param compare A comparison function whose signature shall be equivalent
|
||||
* to: `bool(Entity, Entity)`.
|
||||
*/
|
||||
template<typename Compare>
|
||||
void sort(Compare compare) {
|
||||
std::vector<pos_type> copy{direct.cbegin(), direct.cend()};
|
||||
std::sort(copy.begin(), copy.end(), [compare = std::move(compare)](auto... args) {
|
||||
return !compare(args...);
|
||||
});
|
||||
|
||||
for(pos_type i = 0; i < copy.size(); ++i) {
|
||||
if(direct[i] != copy[i]) {
|
||||
swap(direct[i], copy[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sort entities according to their order in the given sparse set.
|
||||
* @brief Sort entities according to their order in another sparse set.
|
||||
*
|
||||
* Entities that are part of both the sparse sets are ordered internally
|
||||
* according to the order they have in `other`. All the other entities goes
|
||||
@@ -334,36 +316,27 @@ public:
|
||||
*
|
||||
* @param other The sparse sets that imposes the order of the entities.
|
||||
*/
|
||||
void respect(const SparseSet<Entity> &other) {
|
||||
struct Bool { bool value{false}; };
|
||||
std::vector<Bool> check(std::max(other.reverse.size(), reverse.size()));
|
||||
virtual void respect(const SparseSet<Entity> &other) noexcept {
|
||||
auto from = other.begin();
|
||||
auto to = other.end();
|
||||
|
||||
for(auto entity: other.direct) {
|
||||
check[entity & traits_type::entity_mask].value = true;
|
||||
}
|
||||
pos_type pos = direct.size() - 1;
|
||||
|
||||
sort([this, &other, &check](auto lhs, auto rhs) {
|
||||
const auto le = lhs & traits_type::entity_mask;
|
||||
const auto re = rhs & traits_type::entity_mask;
|
||||
while(pos > 0 && from != to) {
|
||||
if(has(*from)) {
|
||||
if(*from != direct[pos]) {
|
||||
swap(pos, get(*from));
|
||||
}
|
||||
|
||||
const bool bLhs = check[le].value;
|
||||
const bool bRhs = check[re].value;
|
||||
bool compare = false;
|
||||
|
||||
if(bLhs && bRhs) {
|
||||
compare = other.get(rhs) < other.get(lhs);
|
||||
} else if(!bLhs && !bRhs) {
|
||||
compare = re < le;
|
||||
} else {
|
||||
compare = bLhs;
|
||||
--pos;
|
||||
}
|
||||
|
||||
return compare;
|
||||
});
|
||||
++from;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Resets the sparse set.
|
||||
* @brief Resets a sparse set.
|
||||
*/
|
||||
virtual void reset() {
|
||||
reverse.clear();
|
||||
@@ -371,7 +344,7 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<entity_type> reverse;
|
||||
std::vector<pos_type> reverse;
|
||||
std::vector<entity_type> direct;
|
||||
};
|
||||
|
||||
@@ -396,7 +369,7 @@ private:
|
||||
* @sa SparseSet<Entity>
|
||||
*
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
* @tparam Type The type of the objects assigned to the entities.
|
||||
* @tparam Type Type of objects assigned to the entities.
|
||||
*/
|
||||
template<typename Entity, typename Type>
|
||||
class SparseSet<Entity, Type>: public SparseSet<Entity> {
|
||||
@@ -404,7 +377,7 @@ class SparseSet<Entity, Type>: public SparseSet<Entity> {
|
||||
|
||||
public:
|
||||
/*! @brief Type of the objects associated to the entities. */
|
||||
using type = Type;
|
||||
using object_type = Type;
|
||||
/*! @brief Underlying entity identifier. */
|
||||
using entity_type = typename underlying_type::entity_type;
|
||||
/*! @brief Entity dependent position type. */
|
||||
@@ -414,8 +387,8 @@ public:
|
||||
/*! @brief Input iterator type. */
|
||||
using iterator_type = typename underlying_type::iterator_type;
|
||||
|
||||
/*! @brief Default constructor, explicit on purpose. */
|
||||
explicit SparseSet() noexcept = default;
|
||||
/*! @brief Default constructor. */
|
||||
SparseSet() noexcept = default;
|
||||
|
||||
/*! @brief Copying a sparse set isn't allowed. */
|
||||
SparseSet(const SparseSet &) = delete;
|
||||
@@ -424,7 +397,7 @@ public:
|
||||
|
||||
/*! @brief Copying a sparse set isn't allowed. @return This sparse set. */
|
||||
SparseSet & operator=(const SparseSet &) = delete;
|
||||
/*! @brief Default move operator. @return This sparse set. */
|
||||
/*! @brief Default move assignment operator. @return This sparse set. */
|
||||
SparseSet & operator=(SparseSet &&) = default;
|
||||
|
||||
/**
|
||||
@@ -442,7 +415,7 @@ public:
|
||||
*
|
||||
* @return A pointer to the array of objects.
|
||||
*/
|
||||
const type * raw() const noexcept {
|
||||
const object_type * raw() const noexcept {
|
||||
return instances.data();
|
||||
}
|
||||
|
||||
@@ -461,12 +434,12 @@ public:
|
||||
*
|
||||
* @return A pointer to the array of objects.
|
||||
*/
|
||||
type * raw() noexcept {
|
||||
object_type * raw() noexcept {
|
||||
return instances.data();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the object associated to the given entity.
|
||||
* @brief Returns the object associated to an entity.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an entity that doesn't belong to the sparse set results
|
||||
@@ -477,12 +450,12 @@ public:
|
||||
* @param entity A valid entity identifier.
|
||||
* @return The object associated to the entity.
|
||||
*/
|
||||
const type & get(entity_type entity) const noexcept {
|
||||
const object_type & get(entity_type entity) const noexcept {
|
||||
return instances[underlying_type::get(entity)];
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the object associated to the given entity.
|
||||
* @brief Returns the object associated to an entity.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an entity that doesn't belong to the sparse set results
|
||||
@@ -493,12 +466,12 @@ public:
|
||||
* @param entity A valid entity identifier.
|
||||
* @return The object associated to the entity.
|
||||
*/
|
||||
type & get(entity_type entity) noexcept {
|
||||
return const_cast<type &>(const_cast<const SparseSet *>(this)->get(entity));
|
||||
object_type & get(entity_type entity) noexcept {
|
||||
return const_cast<object_type &>(const_cast<const SparseSet *>(this)->get(entity));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns an entity to the sparse set and constructs its object.
|
||||
* @brief Assigns an entity to a sparse set and constructs its object.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an entity that already belongs to the sparse set
|
||||
@@ -506,20 +479,21 @@ public:
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* sparse set already contains the given entity.
|
||||
*
|
||||
* @tparam Args The type of the params used to construct the object.
|
||||
* @tparam Args Types of arguments to use to construct the object.
|
||||
* @param entity A valid entity identifier.
|
||||
* @param args The params to use to construct an object for the entity.
|
||||
* @param args Parameters to use to construct an object for the entity.
|
||||
* @return The object associated to the entity.
|
||||
*/
|
||||
template<typename... Args>
|
||||
type & construct(entity_type entity, Args&&... args) {
|
||||
object_type & construct(entity_type entity, Args&&... args) {
|
||||
underlying_type::construct(entity);
|
||||
// emplace_back doesn't work well with PODs because of its placement new
|
||||
instances.push_back({ std::forward<Args>(args)... });
|
||||
return instances.back();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Removes an entity from the sparse set and destroies its object.
|
||||
* @brief Removes an entity from a sparse set and destroies its object.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an entity that doesn't belong to the sparse set results
|
||||
@@ -530,34 +504,105 @@ public:
|
||||
* @param entity A valid entity identifier.
|
||||
*/
|
||||
void destroy(entity_type entity) override {
|
||||
// swapping isn't required here, we are getting rid of the last element
|
||||
instances[underlying_type::get(entity)] = std::move(instances.back());
|
||||
instances.pop_back();
|
||||
underlying_type::destroy(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Swaps the two entities and their objects.
|
||||
* @brief Sort components according to the given comparison function.
|
||||
*
|
||||
* Sort the elements so that iterating the sparse set with a couple of
|
||||
* iterators returns them in the expected order. See `begin` and `end` for
|
||||
* more details.
|
||||
*
|
||||
* The comparison function object must return `true` if the first element
|
||||
* is _less_ than the second one, `false` otherwise. The signature of the
|
||||
* comparison function should be equivalent to the following:
|
||||
*
|
||||
* @code{.cpp}
|
||||
* bool(const Type &, const Type &)
|
||||
* @endcode
|
||||
*
|
||||
* @note
|
||||
* This function doesn't swap objects between entities. It exchanges entity
|
||||
* and object positions in the sparse set. It's used mainly for sorting.
|
||||
* Attempting to iterate elements using the raw pointer returned by `data`
|
||||
* gives no guarantees on the order, even though `sort` has been invoked.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use entities that don't belong to the sparse set results
|
||||
* in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* sparse set doesn't contain the given entities.
|
||||
*
|
||||
* @param lhs A valid entity identifier.
|
||||
* @param rhs A valid entity identifier.
|
||||
* @tparam Compare Type of comparison function object.
|
||||
* @param compare A valid comparison function object.
|
||||
*/
|
||||
void swap(entity_type lhs, entity_type rhs) override {
|
||||
std::swap(instances[underlying_type::get(lhs)], instances[underlying_type::get(rhs)]);
|
||||
underlying_type::swap(lhs, rhs);
|
||||
template<typename Compare>
|
||||
void sort(Compare compare) {
|
||||
std::vector<pos_type> copy(instances.size());
|
||||
std::iota(copy.begin(), copy.end(), 0);
|
||||
|
||||
std::sort(copy.begin(), copy.end(), [this, compare = std::move(compare)](auto lhs, auto rhs) {
|
||||
return compare(const_cast<const object_type &>(instances[rhs]), const_cast<const object_type &>(instances[lhs]));
|
||||
});
|
||||
|
||||
for(pos_type i = 0; i < copy.size(); ++i) {
|
||||
auto curr = i;
|
||||
auto next = copy[curr];
|
||||
|
||||
while(curr != next) {
|
||||
auto lhs = copy[curr];
|
||||
auto rhs = copy[next];
|
||||
std::swap(instances[lhs], instances[rhs]);
|
||||
underlying_type::swap(lhs, rhs);
|
||||
copy[curr] = curr;
|
||||
curr = next;
|
||||
next = copy[curr];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Resets the sparse set.
|
||||
* @brief Sort components according to the order of the entities in another
|
||||
* sparse set.
|
||||
*
|
||||
* Entities that are part of both the sparse sets are ordered internally
|
||||
* according to the order they have in `other`. All the other entities goes
|
||||
* to the end of the list and there are no guarantess on their order.
|
||||
* Components are sorted according to the entities to which they
|
||||
* belong.<br/>
|
||||
* In other terms, this function can be used to impose the same order on two
|
||||
* sets by using one of them as a master and the other one as a slave.
|
||||
*
|
||||
* Iterating the sparse set with a couple of iterators returns elements in
|
||||
* the expected order after a call to `sort`. See `begin` and `end` for more
|
||||
* details.
|
||||
*
|
||||
* @note
|
||||
* Attempting to iterate elements using the raw pointer returned by `data`
|
||||
* gives no guarantees on the order, even though `sort` has been invoked.
|
||||
*
|
||||
* @param other The sparse sets that imposes the order of the entities.
|
||||
*/
|
||||
void respect(const SparseSet<Entity> &other) noexcept override {
|
||||
auto from = other.begin();
|
||||
auto to = other.end();
|
||||
|
||||
pos_type pos = underlying_type::size() - 1;
|
||||
const auto *direct = underlying_type::data();
|
||||
|
||||
while(pos > 0 && from != to) {
|
||||
if(underlying_type::has(*from)) {
|
||||
if(*from != *(direct + pos)) {
|
||||
auto candidate = underlying_type::get(*from);
|
||||
std::swap(instances[pos], instances[candidate]);
|
||||
underlying_type::swap(pos, candidate);
|
||||
}
|
||||
|
||||
--pos;
|
||||
}
|
||||
|
||||
++from;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Resets a sparse set.
|
||||
*/
|
||||
void reset() override {
|
||||
underlying_type::reset();
|
||||
@@ -565,7 +610,7 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<type> instances;
|
||||
std::vector<object_type> instances;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ struct entt_traits;
|
||||
* @brief Entity traits for a 16 bits entity identifier.
|
||||
*
|
||||
* A 16 bits entity identifier guarantees:
|
||||
*
|
||||
* * 12 bits for the entity number (up to 4k entities).
|
||||
* * 4 bit for the version (resets in [0-15]).
|
||||
*/
|
||||
@@ -37,7 +38,7 @@ struct entt_traits<std::uint16_t> {
|
||||
/*! @brief Mask to use to get the version out of an identifier. */
|
||||
static constexpr auto version_mask = 0xF;
|
||||
/*! @brief Extent of the entity number within an identifier. */
|
||||
static constexpr auto version_shift = 12;
|
||||
static constexpr auto entity_shift = 12;
|
||||
};
|
||||
|
||||
|
||||
@@ -45,6 +46,7 @@ struct entt_traits<std::uint16_t> {
|
||||
* @brief Entity traits for a 32 bits entity identifier.
|
||||
*
|
||||
* A 32 bits entity identifier guarantees:
|
||||
*
|
||||
* * 24 bits for the entity number (suitable for almost all the games).
|
||||
* * 8 bit for the version (resets in [0-255]).
|
||||
*/
|
||||
@@ -60,7 +62,7 @@ struct entt_traits<std::uint32_t> {
|
||||
/*! @brief Mask to use to get the version out of an identifier. */
|
||||
static constexpr auto version_mask = 0xFF;
|
||||
/*! @brief Extent of the entity number within an identifier. */
|
||||
static constexpr auto version_shift = 24;
|
||||
static constexpr auto entity_shift = 24;
|
||||
};
|
||||
|
||||
|
||||
@@ -68,6 +70,7 @@ struct entt_traits<std::uint32_t> {
|
||||
* @brief Entity traits for a 64 bits entity identifier.
|
||||
*
|
||||
* A 64 bits entity identifier guarantees:
|
||||
*
|
||||
* * 40 bits for the entity number (an indecently large number).
|
||||
* * 24 bit for the version (an indecently large number).
|
||||
*/
|
||||
@@ -83,7 +86,7 @@ struct entt_traits<std::uint64_t> {
|
||||
/*! @brief Mask to use to get the version out of an identifier. */
|
||||
static constexpr auto version_mask = 0xFFFFFF;
|
||||
/*! @brief Extent of the entity number within an identifier. */
|
||||
static constexpr auto version_shift = 40;
|
||||
static constexpr auto entity_shift = 40;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
#include <algorithm>
|
||||
#include "sparse_set.hpp"
|
||||
|
||||
|
||||
@@ -46,7 +48,7 @@ namespace entt {
|
||||
* @sa View<Entity, Component>
|
||||
*
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
* @tparam Component The types of the components iterated by the view.
|
||||
* @tparam Component Types of components iterated by the view.
|
||||
*/
|
||||
template<typename Entity, typename... Component>
|
||||
class PersistentView final {
|
||||
@@ -69,6 +71,7 @@ public:
|
||||
* @brief Constructs a persistent view around a dedicated pool of entities.
|
||||
*
|
||||
* A persistent view is created out of:
|
||||
*
|
||||
* * A dedicated pool of entities that is shared between all the persistent
|
||||
* views of the same type.
|
||||
* * A bunch of pools of components to which to refer to get instances.
|
||||
@@ -76,13 +79,13 @@ public:
|
||||
* @param view Shared reference to a dedicated pool of entities.
|
||||
* @param pools References to pools of components.
|
||||
*/
|
||||
explicit PersistentView(view_type &view, pool_type<Component>&... pools) noexcept
|
||||
PersistentView(view_type &view, pool_type<Component>&... pools) noexcept
|
||||
: view{view}, pools{pools...}
|
||||
{}
|
||||
|
||||
/**
|
||||
* @brief Returns the number of entities that have the given components.
|
||||
* @return The number of entities that have the given components.
|
||||
* @return Number of entities that have the given components.
|
||||
*/
|
||||
size_type size() const noexcept {
|
||||
return view.size();
|
||||
@@ -154,7 +157,7 @@ public:
|
||||
* An assertion will abort the execution at runtime in debug mode if
|
||||
* the view doesn't contain the given entity.
|
||||
*
|
||||
* @tparam Comp The type of the component to get.
|
||||
* @tparam Comp Type of the component to get.
|
||||
* @param entity A valid entity identifier.
|
||||
* @return The component assigned to the entity.
|
||||
*/
|
||||
@@ -176,7 +179,7 @@ public:
|
||||
* An assertion will abort the execution at runtime in debug mode if
|
||||
* the view doesn't contain the given entity.
|
||||
*
|
||||
* @tparam Comp The type of the component to get.
|
||||
* @tparam Comp Type of the component to get.
|
||||
* @param entity A valid entity identifier.
|
||||
* @return The component assigned to the entity.
|
||||
*/
|
||||
@@ -185,6 +188,50 @@ public:
|
||||
return const_cast<Comp &>(const_cast<const PersistentView *>(this)->get<Comp>(entity));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Iterate the entities and applies them the given function object.
|
||||
*
|
||||
* The function object is invoked for each entity. It is provided with the
|
||||
* entity itself and a set of const references to all the components of the
|
||||
* view.<br/>
|
||||
* The signature of the function should be equivalent to the following:
|
||||
*
|
||||
* @code{.cpp}
|
||||
* void(entity_type, const Component &...);
|
||||
* @endcode
|
||||
*
|
||||
* @tparam Func Type of the function object to invoke.
|
||||
* @param func A valid function object.
|
||||
*/
|
||||
template<typename Func>
|
||||
void each(Func func) const {
|
||||
for(auto entity: *this) {
|
||||
func(entity, get<Component>(entity)...);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Iterate the entities and applies them the given function object.
|
||||
*
|
||||
* The function object is invoked for each entity. It is provided with the
|
||||
* entity itself and a set of references to all the components of the
|
||||
* view.<br/>
|
||||
* The signature of the function should be equivalent to the following:
|
||||
*
|
||||
* @code{.cpp}
|
||||
* void(entity_type, Component &...);
|
||||
* @endcode
|
||||
*
|
||||
* @tparam Func Type of the function object to invoke.
|
||||
* @param func A valid function object.
|
||||
*/
|
||||
template<typename Func>
|
||||
void each(Func func) {
|
||||
const_cast<const PersistentView *>(this)->each([&func](entity_type entity, const Component &... component) {
|
||||
func(entity, const_cast<Component &>(component)...);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sort the shared pool of entities according to the given component.
|
||||
*
|
||||
@@ -195,11 +242,11 @@ public:
|
||||
*
|
||||
* @note
|
||||
* The shared pool of entities and thus its order is affected by the changes
|
||||
* to each and every pool of components that it tracks. Therefore changes to
|
||||
* the pools of components can quickly ruin the order imposed to the pool of
|
||||
* entities shared between the persistent views.
|
||||
* to each and every pool that it tracks. Therefore changes to those pools
|
||||
* can quickly ruin the order imposed to the pool of entities shared between
|
||||
* the persistent views.
|
||||
*
|
||||
* @tparam Comp The type of the component to use to impose the order.
|
||||
* @tparam Comp Type of the component to use to impose the order.
|
||||
*/
|
||||
template<typename Comp>
|
||||
void sort() {
|
||||
@@ -248,24 +295,25 @@ private:
|
||||
* @sa PersistentView
|
||||
*
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
* @tparam First One of the components to iterate.
|
||||
* @tparam Other The rest of the components to iterate.
|
||||
* @tparam Component Types of components iterated by the view.
|
||||
*/
|
||||
template<typename Entity, typename First, typename... Other>
|
||||
template<typename Entity, typename... Component>
|
||||
class View final {
|
||||
template<typename Component>
|
||||
using pool_type = SparseSet<Entity, Component>;
|
||||
static_assert(sizeof...(Component) > 1, "!");
|
||||
|
||||
template<typename Comp>
|
||||
using pool_type = SparseSet<Entity, Comp>;
|
||||
|
||||
using base_pool_type = SparseSet<Entity>;
|
||||
using underlying_iterator_type = typename base_pool_type::iterator_type;
|
||||
using repo_type = std::tuple<pool_type<First> &, pool_type<Other> &...>;
|
||||
using repo_type = std::tuple<pool_type<Component> &...>;
|
||||
|
||||
class Iterator {
|
||||
inline bool valid() const noexcept {
|
||||
using accumulator_type = bool[];
|
||||
auto entity = *begin;
|
||||
bool all = std::get<pool_type<First> &>(pools).has(entity);
|
||||
accumulator_type accumulator = { (all = all && std::get<pool_type<Other> &>(pools).has(entity))... };
|
||||
bool all = true;
|
||||
accumulator_type accumulator = { all, (all = all && std::get<pool_type<Component> &>(pools).has(entity))... };
|
||||
(void)accumulator;
|
||||
return all;
|
||||
}
|
||||
@@ -320,11 +368,10 @@ public:
|
||||
|
||||
/**
|
||||
* @brief Constructs a view out of a bunch of pools of components.
|
||||
* @param pool A reference to a pool of components.
|
||||
* @param other Other references to pools of components.
|
||||
* @param pools References to pools of components.
|
||||
*/
|
||||
explicit View(pool_type<First> &pool, pool_type<Other>&... other) noexcept
|
||||
: pools{pool, other...}, view{nullptr}
|
||||
View(pool_type<Component>&... pools) noexcept
|
||||
: pools{pools...}, view{nullptr}
|
||||
{
|
||||
reset();
|
||||
}
|
||||
@@ -379,13 +426,13 @@ public:
|
||||
* An assertion will abort the execution at runtime in debug mode if
|
||||
* the view doesn't contain the given entity.
|
||||
*
|
||||
* @tparam Component The type of the component to get.
|
||||
* @tparam Comp Type of the component to get.
|
||||
* @param entity A valid entity identifier.
|
||||
* @return The component assigned to the entity.
|
||||
*/
|
||||
template<typename Component>
|
||||
const Component & get(entity_type entity) const noexcept {
|
||||
return std::get<pool_type<Component> &>(pools).get(entity);
|
||||
template<typename Comp>
|
||||
const Comp & get(entity_type entity) const noexcept {
|
||||
return std::get<pool_type<Comp> &>(pools).get(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -401,13 +448,57 @@ public:
|
||||
* An assertion will abort the execution at runtime in debug mode if
|
||||
* the view doesn't contain the given entity.
|
||||
*
|
||||
* @tparam Component The type of the component to get.
|
||||
* @tparam Comp Type of the component to get.
|
||||
* @param entity A valid entity identifier.
|
||||
* @return The component assigned to the entity.
|
||||
*/
|
||||
template<typename Component>
|
||||
Component & get(entity_type entity) noexcept {
|
||||
return const_cast<Component &>(const_cast<const View *>(this)->get<Component>(entity));
|
||||
template<typename Comp>
|
||||
Comp & get(entity_type entity) noexcept {
|
||||
return const_cast<Comp &>(const_cast<const View *>(this)->get<Comp>(entity));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Iterate the entities and applies them the given function object.
|
||||
*
|
||||
* The function object is invoked for each entity. It is provided with the
|
||||
* entity itself and a set of const references to all the components of the
|
||||
* view.<br/>
|
||||
* The signature of the function should be equivalent to the following:
|
||||
*
|
||||
* @code{.cpp}
|
||||
* void(entity_type, const Component &...);
|
||||
* @endcode
|
||||
*
|
||||
* @tparam Func Type of the function object to invoke.
|
||||
* @param func A valid function object.
|
||||
*/
|
||||
template<typename Func>
|
||||
void each(Func func) const {
|
||||
for(auto entity: *this) {
|
||||
func(entity, get<Component>(entity)...);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Iterate the entities and applies them the given function object.
|
||||
*
|
||||
* The function object is invoked for each entity. It is provided with the
|
||||
* entity itself and a set of references to all the components of the
|
||||
* view.<br/>
|
||||
* The signature of the function should be equivalent to the following:
|
||||
*
|
||||
* @code{.cpp}
|
||||
* void(entity_type, Component &...);
|
||||
* @endcode
|
||||
*
|
||||
* @tparam Func Type of the function object to invoke.
|
||||
* @param func A valid function object.
|
||||
*/
|
||||
template<typename Func>
|
||||
void each(Func func) {
|
||||
const_cast<const View *>(this)->each([&func](entity_type entity, const Component &... component) {
|
||||
func(entity, const_cast<Component &>(component)...);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -421,9 +512,10 @@ public:
|
||||
* meantime.
|
||||
*/
|
||||
void reset() {
|
||||
using accumulator_type = void *[];
|
||||
view = &std::get<pool_type<First> &>(pools);
|
||||
accumulator_type accumulator = { (std::get<pool_type<Other> &>(pools).size() < view->size() ? (view = &std::get<pool_type<Other> &>(pools)) : nullptr)... };
|
||||
using accumulator_type = size_type[];
|
||||
auto probe = [this](auto sz, auto &pool) { return pool.size() < sz ? (view = &pool, pool.size()) : sz; };
|
||||
size_type sz = std::max({ std::get<pool_type<Component> &>(pools).size()... }) + std::size_t{1};
|
||||
accumulator_type accumulator = { sz, (sz = probe(sz, std::get<pool_type<Component> &>(pools)))... };
|
||||
(void)accumulator;
|
||||
}
|
||||
|
||||
@@ -467,7 +559,7 @@ private:
|
||||
* @sa PersistentView
|
||||
*
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
* @tparam Component The type of the component iterated by the view.
|
||||
* @tparam Component Type of the component iterated by the view.
|
||||
*/
|
||||
template<typename Entity, typename Component>
|
||||
class View<Entity, Component> final {
|
||||
@@ -480,20 +572,20 @@ public:
|
||||
using entity_type = typename pool_type::entity_type;
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = typename pool_type::size_type;
|
||||
/*! The type of the component iterated by the view. */
|
||||
using raw_type = typename pool_type::type;
|
||||
/*! Type of the component iterated by the view. */
|
||||
using raw_type = typename pool_type::object_type;
|
||||
|
||||
/**
|
||||
* @brief Constructs a view out of a pool of components.
|
||||
* @param pool A reference to a pool of components.
|
||||
*/
|
||||
explicit View(pool_type &pool) noexcept
|
||||
View(pool_type &pool) noexcept
|
||||
: pool{pool}
|
||||
{}
|
||||
|
||||
/**
|
||||
* @brief Returns the number of entities that have the given component.
|
||||
* @return The number of entities that have the given component.
|
||||
* @return Number of entities that have the given component.
|
||||
*/
|
||||
size_type size() const noexcept {
|
||||
return pool.size();
|
||||
@@ -622,6 +714,48 @@ public:
|
||||
return const_cast<Component &>(const_cast<const View *>(this)->get(entity));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Iterate the entities and applies them the given function object.
|
||||
*
|
||||
* The function object is invoked for each entity. It is provided with the
|
||||
* entity itself and a const reference to the component of the view.<br/>
|
||||
* The signature of the function should be equivalent to the following:
|
||||
*
|
||||
* @code{.cpp}
|
||||
* void(entity_type, const Component &);
|
||||
* @endcode
|
||||
*
|
||||
* @tparam Func Type of the function object to invoke.
|
||||
* @param func A valid function object.
|
||||
*/
|
||||
template<typename Func>
|
||||
void each(Func func) const {
|
||||
for(auto entity: *this) {
|
||||
func(entity, get(entity));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Iterate the entities and applies them the given function object.
|
||||
*
|
||||
* The function object is invoked for each entity. It is provided with the
|
||||
* entity itself and a reference to the component of the view.<br/>
|
||||
* The signature of the function should be equivalent to the following:
|
||||
*
|
||||
* @code{.cpp}
|
||||
* void(entity_type, Component &);
|
||||
* @endcode
|
||||
*
|
||||
* @tparam Func Type of the function object to invoke.
|
||||
* @param func A valid function object.
|
||||
*/
|
||||
template<typename Func>
|
||||
void each(Func func) {
|
||||
const_cast<const View *>(this)->each([&func](entity_type entity, const Component &component) {
|
||||
func(entity, const_cast<Component &>(component));
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
pool_type &pool;
|
||||
};
|
||||
|
||||
@@ -1,7 +1,19 @@
|
||||
#include "core/family.hpp"
|
||||
#include "core/hashed_string.hpp"
|
||||
#include "core/ident.hpp"
|
||||
#include "entity/actor.hpp"
|
||||
#include "entity/registry.hpp"
|
||||
#include "entity/sparse_set.hpp"
|
||||
#include "entity/traits.hpp"
|
||||
#include "entity/view.hpp"
|
||||
#include "locator/locator.hpp"
|
||||
#include "process/process.hpp"
|
||||
#include "process/scheduler.hpp"
|
||||
#include "resource/cache.hpp"
|
||||
#include "resource/handle.hpp"
|
||||
#include "resource/loader.hpp"
|
||||
#include "signal/bus.hpp"
|
||||
#include "signal/delegate.hpp"
|
||||
#include "signal/emitter.hpp"
|
||||
#include "signal/sigh.hpp"
|
||||
#include "signal/signal.hpp"
|
||||
|
||||
115
src/entt/locator/locator.hpp
Normal file
115
src/entt/locator/locator.hpp
Normal file
@@ -0,0 +1,115 @@
|
||||
#ifndef ENTT_LOCATOR_LOCATOR_HPP
|
||||
#define ENTT_LOCATOR_LOCATOR_HPP
|
||||
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <cassert>
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/**
|
||||
* @brief Service locator, nothing more.
|
||||
*
|
||||
* A service locator can be used to do what it promises: locate services.<br/>
|
||||
* Usually service locators are tighly bound to the services they expose and
|
||||
* thus it's hard to define a general purpose class to do that. This template
|
||||
* based implementation tries to fill the gap and to get rid of the burden of
|
||||
* defining a different specific locator for each application.
|
||||
*
|
||||
* @tparam Service Type of service managed by the locator.
|
||||
*/
|
||||
template<typename Service>
|
||||
struct ServiceLocator final {
|
||||
/*! @brief Type of service offered. */
|
||||
using service_type = Service;
|
||||
|
||||
/*! @brief Default constructor, deleted on purpose. */
|
||||
ServiceLocator() = delete;
|
||||
/*! @brief Default destructor, deleted on purpose. */
|
||||
~ServiceLocator() = delete;
|
||||
|
||||
/**
|
||||
* @brief Tests if a valid service implementation is set.
|
||||
* @return True if the service is set, false otherwise.
|
||||
*/
|
||||
inline static bool empty() noexcept {
|
||||
return !static_cast<bool>(service);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a weak pointer to a service implementation, if any.
|
||||
*
|
||||
* Clients of a service shouldn't retain references to it. The recommended
|
||||
* way is to retrieve the service implementation currently set each and
|
||||
* every time the need of using it arises. Otherwise users can incur in
|
||||
* unexpected behaviors.
|
||||
*
|
||||
* @return A reference to the service implementation currently set, if any.
|
||||
*/
|
||||
inline static std::weak_ptr<Service> get() noexcept {
|
||||
return service;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a weak reference to a service implementation, if any.
|
||||
*
|
||||
* Clients of a service shouldn't retain references to it. The recommended
|
||||
* way is to retrieve the service implementation currently set each and
|
||||
* every time the need of using it arises. Otherwise users can incur in
|
||||
* unexpected behaviors.
|
||||
*
|
||||
* @warning
|
||||
* In case no service implementation has been set, a call to this function
|
||||
* results in undefined behavior.
|
||||
*
|
||||
* @return A reference to the service implementation currently set, if any.
|
||||
*/
|
||||
inline static Service & ref() noexcept {
|
||||
return *service;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets or replaces a service.
|
||||
* @tparam Impl Type of the new service to use.
|
||||
* @tparam Args Types of arguments to use to construct the service.
|
||||
* @param args Parameters to use to construct the service.
|
||||
*/
|
||||
template<typename Impl = Service, typename... Args>
|
||||
inline static void set(Args&&... args) {
|
||||
service = std::make_shared<Impl>(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets or replaces a service.
|
||||
* @param ptr Service to use to replace the current one.
|
||||
*/
|
||||
inline static void set(std::shared_ptr<Service> ptr) {
|
||||
assert(static_cast<bool>(ptr));
|
||||
service = std::move(ptr);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Resets a service.
|
||||
*
|
||||
* The service is no longer valid after a reset.
|
||||
*/
|
||||
inline static void reset() {
|
||||
service.reset();
|
||||
}
|
||||
|
||||
private:
|
||||
static std::shared_ptr<Service> service;
|
||||
};
|
||||
|
||||
|
||||
template<typename Service>
|
||||
std::shared_ptr<Service> ServiceLocator<Service>::service{};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_LOCATOR_LOCATOR_HPP
|
||||
335
src/entt/process/process.hpp
Normal file
335
src/entt/process/process.hpp
Normal file
@@ -0,0 +1,335 @@
|
||||
#ifndef ENTT_PROCESS_PROCESS_HPP
|
||||
#define ENTT_PROCESS_PROCESS_HPP
|
||||
|
||||
|
||||
#include <type_traits>
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
|
||||
struct BaseProcess {
|
||||
enum class State: unsigned int {
|
||||
UNINITIALIZED = 0,
|
||||
RUNNING,
|
||||
PAUSED,
|
||||
SUCCEEDED,
|
||||
FAILED,
|
||||
ABORTED,
|
||||
FINISHED
|
||||
};
|
||||
|
||||
template<State state>
|
||||
using tag = std::integral_constant<State, state>;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Base class for processes.
|
||||
*
|
||||
* This class stays true to the CRTP idiom. Derived classes must specify what's
|
||||
* the intended type for elapsed times.<br/>
|
||||
* A process should expose publicly the following member functions whether
|
||||
* required:
|
||||
*
|
||||
* * @code{.cpp}
|
||||
* void update(Delta);
|
||||
* @endcode
|
||||
* It's invoked once per tick until a process is explicitly aborted or it
|
||||
* terminates either with or without errors. Even though it's not mandatory to
|
||||
* declare this member function, as a rule of thumb each process should at
|
||||
* least define it to work properly.
|
||||
*
|
||||
* * @code{.cpp}
|
||||
* void init();
|
||||
* @endcode
|
||||
* It's invoked at the first tick, immediately before an update.
|
||||
*
|
||||
* * @code{.cpp}
|
||||
* void succeeded();
|
||||
* @endcode
|
||||
* It's invoked in case of success, immediately after an update and during the
|
||||
* same tick.
|
||||
*
|
||||
* * @code{.cpp}
|
||||
* void failed();
|
||||
* @endcode
|
||||
* It's invoked in case of errors, immediately after an update and during the
|
||||
* same tick.
|
||||
*
|
||||
* * @code{.cpp}
|
||||
* void aborted();
|
||||
* @endcode
|
||||
* It's invoked only if a process is explicitly aborted. There is no guarantee
|
||||
* that it executes in the same tick, this depends solely on whether the
|
||||
* process is aborted immediately or not.
|
||||
*
|
||||
* Derived classes can change the internal state of a process by invoking the
|
||||
* `succeed` and `fail` protected member functions and even pause or unpause the
|
||||
* process itself.
|
||||
*
|
||||
* @sa Scheduler
|
||||
*
|
||||
* @tparam Derived Actual type of process that extends the class template.
|
||||
* @tparam Delta Type to use to provide elapsed time.
|
||||
*/
|
||||
template<typename Derived, typename Delta>
|
||||
class Process: private BaseProcess {
|
||||
template<typename Target = Derived>
|
||||
auto tick(int, tag<State::UNINITIALIZED>)
|
||||
-> decltype(std::declval<Target>().init()) {
|
||||
static_cast<Target *>(this)->init();
|
||||
}
|
||||
|
||||
template<typename Target = Derived>
|
||||
auto tick(int, tag<State::RUNNING>, Delta delta)
|
||||
-> decltype(std::declval<Target>().update(delta)) {
|
||||
static_cast<Target *>(this)->update(delta);
|
||||
}
|
||||
|
||||
template<typename Target = Derived>
|
||||
auto tick(int, tag<State::SUCCEEDED>)
|
||||
-> decltype(std::declval<Target>().succeeded()) {
|
||||
static_cast<Target *>(this)->succeeded();
|
||||
}
|
||||
|
||||
template<typename Target = Derived>
|
||||
auto tick(int, tag<State::FAILED>)
|
||||
-> decltype(std::declval<Target>().failed()) {
|
||||
static_cast<Target *>(this)->failed();
|
||||
}
|
||||
|
||||
template<typename Target = Derived>
|
||||
auto tick(int, tag<State::ABORTED>)
|
||||
-> decltype(std::declval<Target>().aborted()) {
|
||||
static_cast<Target *>(this)->aborted();
|
||||
}
|
||||
|
||||
template<State S, typename... Args>
|
||||
void tick(char, tag<S>, Args&&...) {}
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief Terminates a process with success if it's still alive.
|
||||
*
|
||||
* The function is idempotent and it does nothing if the process isn't
|
||||
* alive.
|
||||
*/
|
||||
void succeed() noexcept {
|
||||
if(alive()) {
|
||||
current = State::SUCCEEDED;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Terminates a process with errors if it's still alive.
|
||||
*
|
||||
* The function is idempotent and it does nothing if the process isn't
|
||||
* alive.
|
||||
*/
|
||||
void fail() noexcept {
|
||||
if(alive()) {
|
||||
current = State::FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Stops a process if it's in a running state.
|
||||
*
|
||||
* The function is idempotent and it does nothing if the process isn't
|
||||
* running.
|
||||
*/
|
||||
void pause() noexcept {
|
||||
if(current == State::RUNNING) {
|
||||
current = State::PAUSED;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Restarts a process if it's paused.
|
||||
*
|
||||
* The function is idempotent and it does nothing if the process isn't
|
||||
* paused.
|
||||
*/
|
||||
void unpause() noexcept {
|
||||
if(current == State::PAUSED) {
|
||||
current = State::RUNNING;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
/*! @brief Type used to provide elapsed time. */
|
||||
using delta_type = Delta;
|
||||
|
||||
/*! @brief Default destructor. */
|
||||
~Process() noexcept {
|
||||
static_assert(std::is_base_of<Process, Derived>::value, "!");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Aborts a process if it's still alive.
|
||||
*
|
||||
* The function is idempotent and it does nothing if the process isn't
|
||||
* alive.
|
||||
*
|
||||
* @param immediately Requests an immediate operation.
|
||||
*/
|
||||
void abort(bool immediately = false) noexcept {
|
||||
if(alive()) {
|
||||
current = State::ABORTED;
|
||||
|
||||
if(immediately) {
|
||||
tick(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns true if a process is either running or paused.
|
||||
* @return True if the process is still alive, false otherwise.
|
||||
*/
|
||||
bool alive() const noexcept {
|
||||
return current == State::RUNNING || current == State::PAUSED;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns true if a process is already terminated.
|
||||
* @return True if the process is terminated, false otherwise.
|
||||
*/
|
||||
bool dead() const noexcept {
|
||||
return current == State::FINISHED;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns true if a process is currently paused.
|
||||
* @return True if the process is paused, false otherwise.
|
||||
*/
|
||||
bool paused() const noexcept {
|
||||
return current == State::PAUSED;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns true if a process terminated with errors.
|
||||
* @return True if the process terminated with errors, false otherwise.
|
||||
*/
|
||||
bool rejected() const noexcept {
|
||||
return stopped;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Updates a process and its internal state if required.
|
||||
* @param delta Elapsed time.
|
||||
*/
|
||||
void tick(Delta delta) {
|
||||
switch (current) {
|
||||
case State::UNINITIALIZED:
|
||||
tick(0, tag<State::UNINITIALIZED>{});
|
||||
current = State::RUNNING;
|
||||
// no break on purpose, tasks are executed immediately
|
||||
case State::RUNNING:
|
||||
tick(0, tag<State::RUNNING>{}, delta);
|
||||
default:
|
||||
// suppress warnings
|
||||
break;
|
||||
}
|
||||
|
||||
// if it's dead, it must be notified and removed immediately
|
||||
switch(current) {
|
||||
case State::SUCCEEDED:
|
||||
tick(0, tag<State::SUCCEEDED>{});
|
||||
current = State::FINISHED;
|
||||
break;
|
||||
case State::FAILED:
|
||||
tick(0, tag<State::FAILED>{});
|
||||
current = State::FINISHED;
|
||||
stopped = true;
|
||||
break;
|
||||
case State::ABORTED:
|
||||
tick(0, tag<State::ABORTED>{});
|
||||
current = State::FINISHED;
|
||||
stopped = true;
|
||||
break;
|
||||
default:
|
||||
// suppress warnings
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
State current{State::UNINITIALIZED};
|
||||
bool stopped{false};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Adaptor for lambdas and functors to turn them into processes.
|
||||
*
|
||||
* Lambdas and functors can't be used directly with a scheduler for they are not
|
||||
* properly defined processes with managed life cycles.<br/>
|
||||
* This class helps in filling the gap and turning lambdas and functors into
|
||||
* full featured processes usable by a scheduler.
|
||||
*
|
||||
* The signature of the function call operator should be equivalent to the
|
||||
* following:
|
||||
*
|
||||
* @code{.cpp}
|
||||
* void(Delta delta, auto succeed, auto fail);
|
||||
* @endcode
|
||||
*
|
||||
* Where:
|
||||
*
|
||||
* * `delta` is the elapsed time.
|
||||
* * `succeed` is a function to call when a process terminates with success.
|
||||
* * `fail` is a function to call when a process terminates with errors.
|
||||
*
|
||||
* The signature of the function call operator of both `succeed` and `fail`
|
||||
* is equivalent to the following:
|
||||
*
|
||||
* @code{.cpp}
|
||||
* void();
|
||||
* @endcode
|
||||
*
|
||||
* Usually users shouldn't worry about creating adaptors. A scheduler will
|
||||
* create them internally each and avery time a lambda or a functor is used as
|
||||
* a process.
|
||||
*
|
||||
* @sa Process
|
||||
* @sa Scheduler
|
||||
*
|
||||
* @tparam Func Actual type of process.
|
||||
* @tparam Delta Type to use to provide elapsed time.
|
||||
*/
|
||||
template<typename Func, typename Delta>
|
||||
struct ProcessAdaptor: Process<ProcessAdaptor<Func, Delta>, Delta>, private Func {
|
||||
/**
|
||||
* @brief Constructs a process adaptor from a lambda or a functor.
|
||||
* @tparam Args Types of arguments to use to initialize the actual process.
|
||||
* @param args Parameters to use to initialize the actual process.
|
||||
*/
|
||||
template<typename... Args>
|
||||
ProcessAdaptor(Args&&... args)
|
||||
: Func{std::forward<Args>(args)...}
|
||||
{}
|
||||
|
||||
/**
|
||||
* @brief Updates a process and its internal state if required.
|
||||
* @param delta Elapsed time.
|
||||
*/
|
||||
void update(Delta delta) {
|
||||
Func::operator()(delta, [this](){ this->succeed(); }, [this](){ this->fail(); });
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_PROCESS_PROCESS_HPP
|
||||
319
src/entt/process/scheduler.hpp
Normal file
319
src/entt/process/scheduler.hpp
Normal file
@@ -0,0 +1,319 @@
|
||||
#ifndef ENTT_PROCESS_SCHEDULER_HPP
|
||||
#define ENTT_PROCESS_SCHEDULER_HPP
|
||||
|
||||
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <iterator>
|
||||
#include <algorithm>
|
||||
#include <type_traits>
|
||||
#include "process.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/**
|
||||
* @brief Cooperative scheduler for processes.
|
||||
*
|
||||
* A cooperative scheduler runs processes and helps managing their life cycles.
|
||||
*
|
||||
* Each process is invoked once per tick. If a process terminates, it's
|
||||
* removed automatically from the scheduler and it's never invoked again.<br/>
|
||||
* A process can also have a child. In this case, the process is replaced with
|
||||
* its child when it terminates if it returns with success. In case of errors,
|
||||
* both the process and its child are discarded.
|
||||
*
|
||||
* Example of use (pseudocode):
|
||||
*
|
||||
* @code{.cpp}
|
||||
* scheduler.attach([](auto delta, auto succeed, auto fail) {
|
||||
* // code
|
||||
* }).then<MyProcess>(arguments...);
|
||||
* @endcode
|
||||
*
|
||||
* In order to invoke all scheduled processes, call the `update` member function
|
||||
* passing it the elapsed time to forward to the tasks.
|
||||
*
|
||||
* @sa Process
|
||||
*
|
||||
* @tparam Delta Type to use to provide elapsed time.
|
||||
*/
|
||||
template<typename Delta>
|
||||
class Scheduler final {
|
||||
template<typename T>
|
||||
struct tag { using type = T; };
|
||||
|
||||
struct ProcessHandler final {
|
||||
using instance_type = std::unique_ptr<void, void(*)(void *)>;
|
||||
using update_type = bool(*)(ProcessHandler &, Delta);
|
||||
using abort_type = void(*)(ProcessHandler &, bool);
|
||||
using next_type = std::unique_ptr<ProcessHandler>;
|
||||
|
||||
instance_type instance;
|
||||
update_type update;
|
||||
abort_type abort;
|
||||
next_type next;
|
||||
};
|
||||
|
||||
template<typename Lambda>
|
||||
struct Then final: Lambda {
|
||||
Then(Lambda &&lambda, ProcessHandler *handler)
|
||||
: Lambda{std::forward<Lambda>(lambda)}, handler{handler}
|
||||
{}
|
||||
|
||||
template<typename Proc, typename... Args>
|
||||
decltype(auto) then(Args&&... args) && {
|
||||
static_assert(std::is_base_of<Process<Proc, Delta>, Proc>::value, "!");
|
||||
handler = Lambda::operator()(handler, tag<Proc>{}, std::forward<Args>(args)...);
|
||||
return std::move(*this);
|
||||
}
|
||||
|
||||
template<typename Func>
|
||||
decltype(auto) then(Func &&func) && {
|
||||
using Proc = ProcessAdaptor<std::decay_t<Func>, Delta>;
|
||||
return std::move(*this).template then<Proc>(std::forward<Func>(func));
|
||||
}
|
||||
|
||||
private:
|
||||
ProcessHandler *handler;
|
||||
};
|
||||
|
||||
template<typename Proc>
|
||||
static bool update(ProcessHandler &handler, Delta delta) {
|
||||
auto *process = static_cast<Proc *>(handler.instance.get());
|
||||
process->tick(delta);
|
||||
|
||||
auto dead = process->dead();
|
||||
|
||||
if(dead) {
|
||||
if(handler.next && !process->rejected()) {
|
||||
handler = std::move(*handler.next);
|
||||
dead = handler.update(handler, delta);
|
||||
} else {
|
||||
handler.instance.reset();
|
||||
}
|
||||
}
|
||||
|
||||
return dead;
|
||||
}
|
||||
|
||||
template<typename Proc>
|
||||
static void abort(ProcessHandler &handler, bool immediately) {
|
||||
static_cast<Proc *>(handler.instance.get())->abort(immediately);
|
||||
}
|
||||
|
||||
template<typename Proc>
|
||||
static void deleter(void *proc) {
|
||||
delete static_cast<Proc *>(proc);
|
||||
}
|
||||
|
||||
auto then(ProcessHandler *handler) {
|
||||
auto lambda = [](ProcessHandler *handler, auto next, auto... args) {
|
||||
using Proc = typename decltype(next)::type;
|
||||
|
||||
if(handler) {
|
||||
auto proc = typename ProcessHandler::instance_type{new Proc{std::forward<decltype(args)>(args)...}, &Scheduler::deleter<Proc>};
|
||||
handler->next.reset(new ProcessHandler{std::move(proc), &Scheduler::update<Proc>, &Scheduler::abort<Proc>, nullptr});
|
||||
handler = handler->next.get();
|
||||
}
|
||||
|
||||
return handler;
|
||||
};
|
||||
|
||||
return Then<decltype(lambda)>{std::move(lambda), handler};
|
||||
}
|
||||
|
||||
public:
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = typename std::vector<ProcessHandler>::size_type;
|
||||
|
||||
/*! @brief Default constructor. */
|
||||
Scheduler() noexcept= default;
|
||||
|
||||
/*! @brief Copying a scheduler isn't allowed. */
|
||||
Scheduler(const Scheduler &) = delete;
|
||||
/*! @brief Default move constructor. */
|
||||
Scheduler(Scheduler &&) = default;
|
||||
|
||||
/*! @brief Copying a scheduler isn't allowed. @return This scheduler. */
|
||||
Scheduler & operator=(const Scheduler &) = delete;
|
||||
/*! @brief Default move assignament operator. @return This scheduler. */
|
||||
Scheduler & operator=(Scheduler &&) = default;
|
||||
|
||||
/**
|
||||
* @brief Number of processes currently scheduled.
|
||||
* @return Number of processes currently scheduled.
|
||||
*/
|
||||
size_type size() const noexcept {
|
||||
return handlers.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns true if at least a process is currently scheduled.
|
||||
* @return True if there are scheduled processes, false otherwise.
|
||||
*/
|
||||
bool empty() const noexcept {
|
||||
return handlers.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Discards all scheduled processes.
|
||||
*
|
||||
* Processes aren't aborted. They are discarded along with their children
|
||||
* and never executed again.
|
||||
*/
|
||||
void clear() {
|
||||
handlers.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Schedules a process for the next tick.
|
||||
*
|
||||
* Returned value is an opaque object that can be used to attach a child to
|
||||
* the given process. The child is automatically scheduled when the process
|
||||
* terminates and only if the process returns with success.
|
||||
*
|
||||
* Example of use (pseudocode):
|
||||
*
|
||||
* @code{.cpp}
|
||||
* // schedules a task in the form of a process class
|
||||
* scheduler.attach<MyProcess>(arguments...)
|
||||
* // appends a child in the form of a lambda function
|
||||
* .then([](auto delta, auto succeed, auto fail) {
|
||||
* // code
|
||||
* })
|
||||
* // appends a child in the form of another process class
|
||||
* .then<MyOtherProcess>();
|
||||
* @endcode
|
||||
*
|
||||
* @tparam Proc Type of process to schedule.
|
||||
* @tparam Args Types of arguments to use to initialize the process.
|
||||
* @param args Parameters to use to initialize the process.
|
||||
* @return An opaque object to use to concatenate processes.
|
||||
*/
|
||||
template<typename Proc, typename... Args>
|
||||
auto attach(Args&&... args) {
|
||||
static_assert(std::is_base_of<Process<Proc, Delta>, Proc>::value, "!");
|
||||
|
||||
auto proc = typename ProcessHandler::instance_type{new Proc{std::forward<Args>(args)...}, &Scheduler::deleter<Proc>};
|
||||
ProcessHandler handler{std::move(proc), &Scheduler::update<Proc>, &Scheduler::abort<Proc>, nullptr};
|
||||
handlers.push_back(std::move(handler));
|
||||
|
||||
return then(&handlers.back());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Schedules a process for the next tick.
|
||||
*
|
||||
* A process can be either a lambda or a functor. The scheduler wraps both
|
||||
* of them in a process adaptor internally.<br/>
|
||||
* The signature of the function call operator should be equivalent to the
|
||||
* following:
|
||||
*
|
||||
* @code{.cpp}
|
||||
* void(Delta delta, auto succeed, auto fail);
|
||||
* @endcode
|
||||
*
|
||||
* Where:
|
||||
*
|
||||
* * `delta` is the elapsed time.
|
||||
* * `succeed` is a function to call when a process terminates with success.
|
||||
* * `fail` is a function to call when a process terminates with errors.
|
||||
*
|
||||
* The signature of the function call operator of both `succeed` and `fail`
|
||||
* is equivalent to the following:
|
||||
*
|
||||
* @code{.cpp}
|
||||
* void();
|
||||
* @endcode
|
||||
*
|
||||
* Returned value is an opaque object that can be used to attach a child to
|
||||
* the given process. The child is automatically scheduled when the process
|
||||
* terminates and only if the process returns with success.
|
||||
*
|
||||
* Example of use (pseudocode):
|
||||
*
|
||||
* @code{.cpp}
|
||||
* // schedules a task in the form of a lambda function
|
||||
* scheduler.attach([](auto delta, auto succeed, auto fail) {
|
||||
* // code
|
||||
* })
|
||||
* // appends a child in the form of another lambda function
|
||||
* .then([](auto delta, auto succeed, auto fail) {
|
||||
* // code
|
||||
* })
|
||||
* // appends a child in the form of a process class
|
||||
* .then<MyProcess>(arguments...);
|
||||
* @endcode
|
||||
*
|
||||
* @sa ProcessAdaptor
|
||||
*
|
||||
* @tparam Func Type of process to schedule.
|
||||
* @param func Either a lambda or a functor to use as a process.
|
||||
* @return An opaque object to use to concatenate processes.
|
||||
*/
|
||||
template<typename Func>
|
||||
auto attach(Func &&func) {
|
||||
using Proc = ProcessAdaptor<std::decay_t<Func>, Delta>;
|
||||
return attach<Proc>(std::forward<Func>(func));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Updates all scheduled processes.
|
||||
*
|
||||
* All scheduled processes are executed in no specific order.<br/>
|
||||
* If a process terminates with success, it's replaced with its child, if
|
||||
* any. Otherwise, if a process terminates with an error, it's removed along
|
||||
* with its child.
|
||||
*
|
||||
* @param delta Elapsed time.
|
||||
*/
|
||||
void update(Delta delta) {
|
||||
bool clean = false;
|
||||
|
||||
for(auto i = handlers.size(); i > 0; --i) {
|
||||
auto &handler = handlers[i-1];
|
||||
const bool dead = handler.update(handler, delta);
|
||||
clean = clean || dead;
|
||||
}
|
||||
|
||||
if(clean) {
|
||||
handlers.erase(std::remove_if(handlers.begin(), handlers.end(), [](auto &handler) {
|
||||
return !handler.instance;
|
||||
}), handlers.end());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Aborts all scheduled processes.
|
||||
*
|
||||
* Unless an immediate operation is requested, the abort is scheduled for
|
||||
* the next tick. Processes won't be executed anymore in any case.<br/>
|
||||
* Once a process is fully aborted and thus finished, it's discarded along
|
||||
* with its child, if any.
|
||||
*
|
||||
* @param immediately Requests an immediate operation.
|
||||
*/
|
||||
void abort(bool immediately = false) {
|
||||
decltype(handlers) exec;
|
||||
exec.swap(handlers);
|
||||
|
||||
std::for_each(exec.begin(), exec.end(), [immediately](auto &handler) {
|
||||
handler.abort(handler, immediately);
|
||||
});
|
||||
|
||||
std::move(handlers.begin(), handlers.end(), std::back_inserter(exec));
|
||||
handlers.swap(exec);
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<ProcessHandler> handlers{};
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_PROCESS_SCHEDULER_HPP
|
||||
183
src/entt/resource/cache.hpp
Normal file
183
src/entt/resource/cache.hpp
Normal file
@@ -0,0 +1,183 @@
|
||||
#ifndef ENTT_RESOURCE_CACHE_HPP
|
||||
#define ENTT_RESOURCE_CACHE_HPP
|
||||
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include "../core/hashed_string.hpp"
|
||||
#include "handle.hpp"
|
||||
#include "loader.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/**
|
||||
* @brief Simple cache for resources of a given type.
|
||||
*
|
||||
* Minimal implementation of a cache for resources of a given type. It doesn't
|
||||
* offer much functionalities but it's suitable for small or medium sized
|
||||
* applications and can be freely inherited to add targeted functionalities for
|
||||
* large sized applications.
|
||||
*
|
||||
* @tparam Resource Type of resources managed by a cache.
|
||||
*/
|
||||
template<typename Resource>
|
||||
class ResourceCache {
|
||||
using container_type = std::unordered_map<HashedString::hash_type, std::shared_ptr<Resource>>;
|
||||
|
||||
public:
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = typename container_type::size_type;
|
||||
/*! @brief Type of resources managed by a cache. */
|
||||
using resource_type = HashedString;
|
||||
|
||||
/*! @brief Default constructor. */
|
||||
ResourceCache() = default;
|
||||
|
||||
/*! @brief Copying a cache isn't allowed. */
|
||||
ResourceCache(const ResourceCache &) noexcept = delete;
|
||||
/*! @brief Default move constructor. */
|
||||
ResourceCache(ResourceCache &&) noexcept = default;
|
||||
|
||||
/*! @brief Copying a cache isn't allowed. @return This cache. */
|
||||
ResourceCache & operator=(const ResourceCache &) noexcept = delete;
|
||||
/*! @brief Default move assignment operator. @return This cache. */
|
||||
ResourceCache & operator=(ResourceCache &&) noexcept = default;
|
||||
|
||||
/**
|
||||
* @brief Number of resources managed by a cache.
|
||||
* @return Number of resources currently stored.
|
||||
*/
|
||||
size_type size() const noexcept {
|
||||
return resources.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns true if a cache contains no resources, false otherwise.
|
||||
* @return True if the cache contains no resources, false otherwise.
|
||||
*/
|
||||
bool empty() const noexcept {
|
||||
return resources.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Clears a cache and discards all its resources.
|
||||
*
|
||||
* Handles are not invalidated and the memory used by a resource isn't
|
||||
* freed as long as at least a handle keeps the resource itself alive.
|
||||
*/
|
||||
void clear() noexcept {
|
||||
resources.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Loads the resource that corresponds to the given identifier.
|
||||
*
|
||||
* In case an identifier isn't already present in the cache, it loads its
|
||||
* resource and stores it aside for future uses. Arguments are forwarded
|
||||
* directly to the loader in order to construct properly the requested
|
||||
* resource.
|
||||
*
|
||||
* @note
|
||||
* If the identifier is already present in the cache, this function does
|
||||
* nothing and the arguments are simply discarded.
|
||||
*
|
||||
* @tparam Loader Type of loader to use to load the resource if required.
|
||||
* @tparam Args Types of arguments to use to load the resource if required.
|
||||
* @param id Unique resource identifier.
|
||||
* @param args Arguments to use to load the resource if required.
|
||||
* @return True if the resource is ready to use, false otherwise.
|
||||
*/
|
||||
template<typename Loader, typename... Args>
|
||||
bool load(resource_type id, Args&&... args) {
|
||||
static_assert(std::is_base_of<ResourceLoader<Loader, Resource>, Loader>::value, "!");
|
||||
|
||||
bool loaded = true;
|
||||
|
||||
if(resources.find(id) == resources.cend()) {
|
||||
std::shared_ptr<Resource> resource = Loader{}.get(std::forward<Args>(args)...);
|
||||
loaded = (static_cast<bool>(resource) ? (resources[id] = std::move(resource), loaded) : false);
|
||||
}
|
||||
|
||||
return loaded;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reloads a resource or loads it for the first time if not present.
|
||||
*
|
||||
* Equivalent to the following snippet (pseudocode):
|
||||
*
|
||||
* @code{.cpp}
|
||||
* cache.discard(id);
|
||||
* cache.load(id, args...);
|
||||
* @endcode
|
||||
*
|
||||
* Arguments are forwarded directly to the loader in order to construct
|
||||
* properly the requested resource.
|
||||
*
|
||||
* @tparam Loader Type of loader to use to load the resource.
|
||||
* @tparam Args Types of arguments to use to load the resource.
|
||||
* @param id Unique resource identifier.
|
||||
* @param args Arguments to use to load the resource.
|
||||
* @return True if the resource is ready to use, false otherwise.
|
||||
*/
|
||||
template<typename Loader, typename... Args>
|
||||
void reload(resource_type id, Args&&... args) {
|
||||
return (discard(id), load(id, std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Creates a handle for the given resource identifier.
|
||||
*
|
||||
* A resource handle can be in a either valid or invalid state. In other
|
||||
* terms, a resource handle is properly initialized with a resource if the
|
||||
* cache contains the resource itself. Otherwise the returned handle is
|
||||
* uninitialized and accessing it results in undefined behavior.
|
||||
*
|
||||
* @sa ResourceHandle
|
||||
*
|
||||
* @param id Unique resource identifier.
|
||||
* @return A handle for the given resource.
|
||||
*/
|
||||
ResourceHandle<Resource> handle(resource_type id) const {
|
||||
auto it = resources.find(id);
|
||||
return { it == resources.end() ? nullptr : it->second };
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if a cache contains the given identifier.
|
||||
* @param id Unique resource identifier.
|
||||
* @return True if the cache contains the resource, false otherwise.
|
||||
*/
|
||||
bool contains(resource_type id) const noexcept {
|
||||
return !(resources.find(id) == resources.cend());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Discards the resource that corresponds to the given identifier.
|
||||
*
|
||||
* Handles are not invalidated and the memory used by the resource isn't
|
||||
* freed as long as at least a handle keeps the resource itself alive.
|
||||
*
|
||||
* @param id Unique resource identifier.
|
||||
*/
|
||||
void discard(resource_type id) noexcept {
|
||||
auto it = resources.find(id);
|
||||
|
||||
if(it != resources.end()) {
|
||||
resources.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
container_type resources;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_RESOURCE_CACHE_HPP
|
||||
115
src/entt/resource/handle.hpp
Normal file
115
src/entt/resource/handle.hpp
Normal file
@@ -0,0 +1,115 @@
|
||||
#ifndef ENTT_RESOURCE_HANDLE_HPP
|
||||
#define ENTT_RESOURCE_HANDLE_HPP
|
||||
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <cassert>
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
template<typename Resource>
|
||||
class ResourceCache;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Shared resource handle.
|
||||
*
|
||||
* A shared resource handle is a small class that wraps a resource and keeps it
|
||||
* alive even if it's deleted from the cache. It can be either copied or
|
||||
* moved. A handle shares a reference to the same resource with all the other
|
||||
* handles constructed for the same identifier.<br/>
|
||||
* As a rule of thumb, resources should never be copied nor moved. Handles are
|
||||
* the way to go to keep references to them.
|
||||
*
|
||||
* @tparam Resource Type of resource managed by a handle.
|
||||
*/
|
||||
template<typename Resource>
|
||||
class ResourceHandle final {
|
||||
/*! @brief Resource handles are friends of their caches. */
|
||||
friend class ResourceCache<Resource>;
|
||||
|
||||
ResourceHandle(std::shared_ptr<Resource> res) noexcept
|
||||
: resource{std::move(res)}
|
||||
{}
|
||||
|
||||
public:
|
||||
/*! @brief Default copy constructor. */
|
||||
ResourceHandle(const ResourceHandle &) noexcept = default;
|
||||
/*! @brief Default move constructor. */
|
||||
ResourceHandle(ResourceHandle &&) noexcept = default;
|
||||
|
||||
/*! @brief Default copy assignment operator. @return This handle. */
|
||||
ResourceHandle & operator=(const ResourceHandle &) noexcept = default;
|
||||
/*! @brief Default move assignment operator. @return This handle. */
|
||||
ResourceHandle & operator=(ResourceHandle &&) noexcept = default;
|
||||
|
||||
/**
|
||||
* @brief Gets a reference to the managed resource.
|
||||
*
|
||||
* @warning
|
||||
* The behavior is undefined if the handle doesn't contain a resource.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* handle is empty.
|
||||
*
|
||||
* @return A reference to the managed resource.
|
||||
*/
|
||||
const Resource & get() const noexcept {
|
||||
assert(static_cast<bool>(resource));
|
||||
return *resource;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Casts a handle and gets a reference to the managed resource.
|
||||
*
|
||||
* @warning
|
||||
* The behavior is undefined if the handle doesn't contain a resource.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* handle is empty.
|
||||
*/
|
||||
inline operator const Resource & () const noexcept { return get(); }
|
||||
|
||||
/**
|
||||
* @brief Dereferences a handle to obtain the managed resource.
|
||||
*
|
||||
* @warning
|
||||
* The behavior is undefined if the handle doesn't contain a resource.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* handle is empty.
|
||||
*
|
||||
* @return A reference to the managed resource.
|
||||
*/
|
||||
inline const Resource & operator *() const noexcept { return get(); }
|
||||
|
||||
/**
|
||||
* @brief Gets a pointer to the managed resource from a handle .
|
||||
*
|
||||
* @warning
|
||||
* The behavior is undefined if the handle doesn't contain a resource.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* handle is empty.
|
||||
*
|
||||
* @return A pointer to the managed resource or `nullptr` if the handle
|
||||
* contains no resource at all.
|
||||
*/
|
||||
inline const Resource * operator ->() const noexcept {
|
||||
assert(static_cast<bool>(resource));
|
||||
return resource.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns true if the handle contains a resource, false otherwise.
|
||||
*/
|
||||
explicit operator bool() const { return static_cast<bool>(resource); }
|
||||
|
||||
private:
|
||||
std::shared_ptr<Resource> resource;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_RESOURCE_HANDLE_HPP
|
||||
62
src/entt/resource/loader.hpp
Normal file
62
src/entt/resource/loader.hpp
Normal file
@@ -0,0 +1,62 @@
|
||||
#ifndef ENTT_RESOURCE_LOADER_HPP
|
||||
#define ENTT_RESOURCE_LOADER_HPP
|
||||
|
||||
|
||||
#include <memory>
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
template<typename Resource>
|
||||
class ResourceCache;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Base class for resource loaders.
|
||||
*
|
||||
* Resource loaders must inherit from this class and stay true to the CRTP
|
||||
* idiom. Moreover, a resource loader must expose a public, const member
|
||||
* function named `load` that accepts a variable number of arguments and return
|
||||
* a shared pointer to the resource just created.<br/>
|
||||
* As an example:
|
||||
*
|
||||
* @code{.cpp}
|
||||
* struct MyResource {};
|
||||
*
|
||||
* struct MyLoader: entt::ResourceLoader<MyLoader, MyResource> {
|
||||
* std::shared_ptr<MyResource> load(int) const {
|
||||
* // use the integer value somehow
|
||||
* return std::make_shared<MyResource>();
|
||||
* }
|
||||
* };
|
||||
* @endcode
|
||||
*
|
||||
* In general, resource loaders should not have a state or retain data of any
|
||||
* type. They should let the cache manage their resources instead.
|
||||
*
|
||||
* @note
|
||||
* Base class and CRTP idiom aren't strictly required with the current
|
||||
* implementation. One could argue that a cache can easily work with loaders of
|
||||
* any type. However, future changes won't be breaking ones by forcing the use
|
||||
* of a base class today and that's why the model is already in its place.
|
||||
*
|
||||
* @tparam Loader Type of the derived class.
|
||||
* @tparam Resource Type of resource for which to use the loader.
|
||||
*/
|
||||
template<typename Loader, typename Resource>
|
||||
class ResourceLoader {
|
||||
/*! @brief Resource loaders are friends of their caches. */
|
||||
friend class ResourceCache<Resource>;
|
||||
|
||||
template<typename... Args>
|
||||
std::shared_ptr<Resource> get(Args&&... args) const {
|
||||
return static_cast<const Loader *>(this)->load(std::forward<Args>(args)...);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_RESOURCE_LOADER_HPP
|
||||
305
src/entt/signal/bus.hpp
Normal file
305
src/entt/signal/bus.hpp
Normal file
@@ -0,0 +1,305 @@
|
||||
#ifndef ENTT_SIGNAL_BUS_HPP
|
||||
#define ENTT_SIGNAL_BUS_HPP
|
||||
|
||||
|
||||
#include <cstddef>
|
||||
#include <utility>
|
||||
#include "signal.hpp"
|
||||
#include "sigh.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/**
|
||||
* @brief Minimal event bus.
|
||||
*
|
||||
* Primary template isn't defined on purpose. The main reason for which it
|
||||
* exists is to work around the doxygen's parsing capabilities. In fact, there
|
||||
* is no need to declare it actually.
|
||||
*/
|
||||
template<template<typename...> class, typename...>
|
||||
class Bus;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Event bus specialization for multiple types.
|
||||
*
|
||||
* The event bus is designed to allow an easy registration of specific member
|
||||
* functions to a bunch of signal handlers (either manager or unmanaged).
|
||||
* Classes must publicly expose the required member functions to allow the bus
|
||||
* to detect them for the purpose of registering and unregistering
|
||||
* instances.<br/>
|
||||
* In particular, for each event type `E`, a matching member function has the
|
||||
* following signature: `void receive(const E &)`. Events will be properly
|
||||
* redirected to all the listeners by calling the right member functions, if
|
||||
* any.
|
||||
*
|
||||
* @tparam Sig Type of signal handler to use.
|
||||
* @tparam Event The list of events managed by the bus.
|
||||
*/
|
||||
template<template<typename...> class Sig, typename Event, typename... Other>
|
||||
class Bus<Sig, Event, Other...>
|
||||
: private Bus<Sig, Event>, private Bus<Sig, Other>...
|
||||
{
|
||||
public:
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = std::size_t;
|
||||
|
||||
/**
|
||||
* @brief Unregisters all the member functions of an instance.
|
||||
*
|
||||
* A bus is used to convey a certain set of events. This method detects
|
||||
* and unregisters from the bus all the matching member functions of an
|
||||
* instance.<br/>
|
||||
* For each event type `E`, a matching member function has the following
|
||||
* signature: `void receive(const E &)`.
|
||||
*
|
||||
* @tparam Instance Type of instance to unregister.
|
||||
* @param instance A valid instance of the right type.
|
||||
*/
|
||||
template<typename Instance>
|
||||
void unreg(Instance instance) {
|
||||
using accumulator_type = int[];
|
||||
accumulator_type accumulator = {
|
||||
(Bus<Sig, Event>::unreg(instance), 0),
|
||||
(Bus<Sig, Other>::unreg(instance), 0)...
|
||||
};
|
||||
return void(accumulator);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Registers all the member functions of an instance.
|
||||
*
|
||||
* A bus is used to convey a certain set of events. This method detects
|
||||
* and registers to the bus all the matching member functions of an
|
||||
* instance.<br/>
|
||||
* For each event type `E`, a matching member function has the following
|
||||
* signature: `void receive(const E &)`.
|
||||
*
|
||||
* @tparam Instance Type of instance to register.
|
||||
* @param instance A valid instance of the right type.
|
||||
*/
|
||||
template<typename Instance>
|
||||
void reg(Instance instance) {
|
||||
using accumulator_type = int[];
|
||||
accumulator_type accumulator = {
|
||||
(Bus<Sig, Event>::reg(instance), 0),
|
||||
(Bus<Sig, Other>::reg(instance), 0)...
|
||||
};
|
||||
return void(accumulator);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Number of listeners connected to the bus.
|
||||
* @return Number of listeners currently connected.
|
||||
*/
|
||||
size_type size() const noexcept {
|
||||
using accumulator_type = std::size_t[];
|
||||
std::size_t sz = Bus<Sig, Event>::size();
|
||||
accumulator_type accumulator = { sz, (sz += Bus<Sig, Other>::size())... };
|
||||
return void(accumulator), sz;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns false if at least a listener is connected to the bus.
|
||||
* @return True if the bus has no listeners connected, false otherwise.
|
||||
*/
|
||||
bool empty() const noexcept {
|
||||
using accumulator_type = bool[];
|
||||
bool ret = Bus<Sig, Event>::empty();
|
||||
accumulator_type accumulator = { ret, (ret = ret && Bus<Sig, Other>::empty())... };
|
||||
return void(accumulator), ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Connects a free function to the bus.
|
||||
* @tparam Type Type of event to which to connect the function.
|
||||
* @tparam Function A valid free function pointer.
|
||||
*/
|
||||
template<typename Type, void(*Function)(const Type &)>
|
||||
void connect() {
|
||||
Bus<Sig, Type>::template connect<Function>();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects a free function from the bus.
|
||||
* @tparam Type Type of event from which to disconnect the function.
|
||||
* @tparam Function A valid free function pointer.
|
||||
*/
|
||||
template<typename Type, void(*Function)(const Type &)>
|
||||
void disconnect() {
|
||||
Bus<Sig, Type>::template disconnect<Function>();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Publishes an event.
|
||||
*
|
||||
* All the listeners are notified. Order isn't guaranteed.
|
||||
*
|
||||
* @tparam Type Type of event to publish.
|
||||
* @tparam Args Types of arguments to use to construct the event.
|
||||
* @param args Arguments to use to construct the event.
|
||||
*/
|
||||
template<typename Type, typename... Args>
|
||||
void publish(Args&&... args) {
|
||||
Bus<Sig, Type>::publish(std::forward<Args>(args)...);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Event bus specialization for a single type.
|
||||
*
|
||||
* The event bus is designed to allow an easy registration of a specific member
|
||||
* function to a signal handler (either manager or unmanaged).
|
||||
* Classes must publicly expose the required member function to allow the bus to
|
||||
* detect it for the purpose of registering and unregistering instances.<br/>
|
||||
* In particular, a matching member function has the following signature:
|
||||
* `void receive(const Event &)`. Events of the given type will be properly
|
||||
* redirected to all the listeners by calling the right member function, if any.
|
||||
*
|
||||
* @tparam Sig Type of signal handler to use.
|
||||
* @tparam Event Type of event managed by the bus.
|
||||
*/
|
||||
template<template<typename...> class Sig, typename Event>
|
||||
class Bus<Sig, Event> {
|
||||
using signal_type = Sig<void(const Event &)>;
|
||||
|
||||
template<typename Class>
|
||||
using instance_type = typename signal_type::template instance_type<Class>;
|
||||
|
||||
template<typename Class>
|
||||
auto disconnect(int, instance_type<Class> instance)
|
||||
-> decltype(std::declval<Class>().receive(std::declval<Event>()), void()) {
|
||||
signal.template disconnect<Class, &Class::receive>(std::move(instance));
|
||||
}
|
||||
|
||||
template<typename Class>
|
||||
auto connect(int, instance_type<Class> instance)
|
||||
-> decltype(std::declval<Class>().receive(std::declval<Event>()), void()) {
|
||||
signal.template connect<Class, &Class::receive>(std::move(instance));
|
||||
}
|
||||
|
||||
template<typename Class> void disconnect(char, instance_type<Class>) {}
|
||||
template<typename Class> void connect(char, instance_type<Class>) {}
|
||||
|
||||
public:
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = typename signal_type::size_type;
|
||||
|
||||
/**
|
||||
* @brief Unregisters member functions of instances.
|
||||
*
|
||||
* This method tries to detect and unregister from the bus matching member
|
||||
* functions of instances.<br/>
|
||||
* A matching member function has the following signature:
|
||||
* `void receive(const Event &)`.
|
||||
*
|
||||
* @tparam Class Type of instance to unregister.
|
||||
* @param instance A valid instance of the right type.
|
||||
*/
|
||||
template<typename Class>
|
||||
void unreg(instance_type<Class> instance) {
|
||||
disconnect(0, std::move(instance));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Tries to register an instance.
|
||||
*
|
||||
* This method tries to detect and register to the bus matching member
|
||||
* functions of instances.<br/>
|
||||
* A matching member function has the following signature:
|
||||
* `void receive(const Event &)`.
|
||||
*
|
||||
* @tparam Class Type of instance to register.
|
||||
* @param instance A valid instance of the right type.
|
||||
*/
|
||||
template<typename Class>
|
||||
void reg(instance_type<Class> instance) {
|
||||
connect(0, std::move(instance));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Number of listeners connected to the bus.
|
||||
* @return Number of listeners currently connected.
|
||||
*/
|
||||
size_type size() const noexcept {
|
||||
return signal.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns false if at least a listener is connected to the bus.
|
||||
* @return True if the bus has no listeners connected, false otherwise.
|
||||
*/
|
||||
bool empty() const noexcept {
|
||||
return signal.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Connects a free function to the bus.
|
||||
* @tparam Function A valid free function pointer.
|
||||
*/
|
||||
template<void(*Function)(const Event &)>
|
||||
void connect() {
|
||||
signal.template connect<Function>();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects a free function from the bus.
|
||||
* @tparam Function A valid free function pointer.
|
||||
*/
|
||||
template<void(*Function)(const Event &)>
|
||||
void disconnect() {
|
||||
signal.template disconnect<Function>();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Publishes an event.
|
||||
*
|
||||
* All the listeners are notified. Order isn't guaranteed.
|
||||
*
|
||||
* @tparam Args Types of arguments to use to construct the event.
|
||||
* @param args Arguments to use to construct the event.
|
||||
*/
|
||||
template<typename... Args>
|
||||
void publish(Args&&... args) {
|
||||
signal.publish({ std::forward<Args>(args)... });
|
||||
}
|
||||
|
||||
private:
|
||||
signal_type signal;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Managed event bus.
|
||||
*
|
||||
* A managed event bus uses the Signal class template as an underlying type. The
|
||||
* type of the instances is the one required by the signal handler:
|
||||
* `std::shared_ptr<Class>` (a shared pointer).
|
||||
*
|
||||
* @tparam Event The list of events managed by the bus.
|
||||
*/
|
||||
template<typename... Event>
|
||||
using ManagedBus = Bus<Signal, Event...>;
|
||||
|
||||
/**
|
||||
* @brief Unmanaged event bus.
|
||||
*
|
||||
* An unmanaged event bus uses the SigH class template as an underlying type.
|
||||
* The type of the instances is the one required by the signal handler:
|
||||
* `Class *` (a naked pointer).<br/>
|
||||
* When it comes to work with this kind of bus, users must guarantee that the
|
||||
* lifetimes of the instances overcome the one of the bus itself.
|
||||
*
|
||||
* @tparam Event The list of events managed by the bus.
|
||||
*/
|
||||
template<typename... Event>
|
||||
using UnmanagedBus = Bus<SigH, Event...>;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_SIGNAL_BUS_HPP
|
||||
137
src/entt/signal/delegate.hpp
Normal file
137
src/entt/signal/delegate.hpp
Normal file
@@ -0,0 +1,137 @@
|
||||
#ifndef ENTT_SIGNAL_DELEGATE_HPP
|
||||
#define ENTT_SIGNAL_DELEGATE_HPP
|
||||
|
||||
|
||||
#include <utility>
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/**
|
||||
* @brief Basic delegate implementation.
|
||||
*
|
||||
* Primary template isn't defined on purpose. All the specializations give a
|
||||
* compile-time error unless the template parameter is a function type.
|
||||
*/
|
||||
template<typename>
|
||||
class Delegate;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Utility class to send around functions and member functions.
|
||||
*
|
||||
* Unmanaged delegate for function pointers and member functions. Users of this
|
||||
* class are in charge of disconnecting instances before deleting them.
|
||||
*
|
||||
* A delegate can be used as general purpose invoker with no memory overhead for
|
||||
* free functions and member functions provided along with an instance on which
|
||||
* to invoke them.
|
||||
*
|
||||
* @tparam Ret Return type of a function type.
|
||||
* @tparam Args Types of arguments of a function type.
|
||||
*/
|
||||
template<typename Ret, typename... Args>
|
||||
class Delegate<Ret(Args...)> final {
|
||||
using proto_type = Ret(*)(void *, Args...);
|
||||
using stub_type = std::pair<void *, proto_type>;
|
||||
|
||||
static Ret fallback(void *, Args...) noexcept { return {}; }
|
||||
|
||||
template<Ret(*Function)(Args...)>
|
||||
static Ret proto(void *, Args... args) {
|
||||
return (Function)(args...);
|
||||
}
|
||||
|
||||
template<typename Class, Ret(Class::*Member)(Args...)>
|
||||
static Ret proto(void *instance, Args... args) {
|
||||
return (static_cast<Class *>(instance)->*Member)(args...);
|
||||
}
|
||||
|
||||
public:
|
||||
/*! @brief Default constructor. */
|
||||
Delegate() noexcept
|
||||
: stub{std::make_pair(nullptr, &fallback)}
|
||||
{}
|
||||
|
||||
/**
|
||||
* @brief Binds a free function to a delegate.
|
||||
* @tparam Function A valid free function pointer.
|
||||
*/
|
||||
template<Ret(*Function)(Args...)>
|
||||
void connect() noexcept {
|
||||
stub = std::make_pair(nullptr, &proto<Function>);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Connects a member function for a given instance to a delegate.
|
||||
*
|
||||
* The delegate isn't responsible for the connected object. Users must
|
||||
* guarantee that the lifetime of the instance overcomes the one of the
|
||||
* delegate.
|
||||
*
|
||||
* @tparam Class Type of class to which the member function belongs.
|
||||
* @tparam Member Member function to connect to the delegate.
|
||||
* @param instance A valid instance of type pointer to `Class`.
|
||||
*/
|
||||
template<typename Class, Ret(Class::*Member)(Args...)>
|
||||
void connect(Class *instance) noexcept {
|
||||
stub = std::make_pair(instance, &proto<Class, Member>);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Resets a delegate.
|
||||
*
|
||||
* After a reset, a delegate can be safely invoked with no effect.
|
||||
*/
|
||||
void reset() noexcept {
|
||||
stub = std::make_pair(nullptr, &fallback);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Triggers a delegate.
|
||||
* @param args Arguments to use to invoke the underlying function.
|
||||
* @return The value returned by the underlying function.
|
||||
*/
|
||||
Ret operator()(Args... args) {
|
||||
return stub.second(stub.first, args...);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if the contents of the two delegates are different.
|
||||
*
|
||||
* Two delegates are identical if they contain the same listener.
|
||||
*
|
||||
* @param other Delegate with which to compare.
|
||||
* @return True if the two delegates are identical, false otherwise.
|
||||
*/
|
||||
bool operator==(const Delegate<Ret(Args...)> &other) const noexcept {
|
||||
return stub.first == other.stub.first && stub.second == other.stub.second;
|
||||
}
|
||||
|
||||
private:
|
||||
stub_type stub;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Checks if the contents of the two delegates are different.
|
||||
*
|
||||
* Two delegates are identical if they contain the same listener.
|
||||
*
|
||||
* @tparam Ret Return type of a function type.
|
||||
* @tparam Args Types of arguments of a function type.
|
||||
* @param lhs A valid delegate object.
|
||||
* @param rhs A valid delegate object.
|
||||
* @return True if the two delegates are different, false otherwise.
|
||||
*/
|
||||
template<typename Ret, typename... Args>
|
||||
bool operator!=(const Delegate<Ret(Args...)> &lhs, const Delegate<Ret(Args...)> &rhs) noexcept {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_SIGNAL_DELEGATE_HPP
|
||||
222
src/entt/signal/dispatcher.hpp
Normal file
222
src/entt/signal/dispatcher.hpp
Normal file
@@ -0,0 +1,222 @@
|
||||
#ifndef ENTT_SIGNAL_DISPATCHER_HPP
|
||||
#define ENTT_SIGNAL_DISPATCHER_HPP
|
||||
|
||||
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <cstdint>
|
||||
#include "../core/family.hpp"
|
||||
#include "signal.hpp"
|
||||
#include "sigh.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/**
|
||||
* @brief Basic dispatcher implementation.
|
||||
*
|
||||
* A dispatcher can be used either to trigger an immediate event or to enqueue
|
||||
* events to be published all together once per tick.<br/>
|
||||
* Listeners are provided in the form of member functions. For each event of
|
||||
* type `Event`, listeners must have the following signature:
|
||||
* `void(const Event &)`. Member functions named `receive` are automatically
|
||||
* detected and registered or unregistered by the dispatcher.
|
||||
*
|
||||
* @tparam Sig Type of the signal handler to use.
|
||||
*/
|
||||
template<template<typename...> class Sig>
|
||||
class Dispatcher final {
|
||||
using event_family = Family<struct InternalDispatcherEventFamily>;
|
||||
|
||||
template<typename Class, typename Event>
|
||||
using instance_type = typename Sig<void(const Event &)>::template instance_type<Class>;
|
||||
|
||||
struct BaseSignalWrapper {
|
||||
virtual ~BaseSignalWrapper() = default;
|
||||
virtual void publish(std::size_t) = 0;
|
||||
};
|
||||
|
||||
template<typename Event>
|
||||
struct SignalWrapper final: BaseSignalWrapper {
|
||||
void publish(std::size_t current) override {
|
||||
for(const auto &event: events[current]) {
|
||||
signal.publish(event);
|
||||
}
|
||||
|
||||
events[current].clear();
|
||||
}
|
||||
|
||||
template<typename Class, void(Class::*Member)(const Event &)>
|
||||
inline void connect(instance_type<Class, Event> instance) noexcept {
|
||||
signal.template connect<Class, Member>(std::move(instance));
|
||||
}
|
||||
|
||||
template<typename Class, void(Class::*Member)(const Event &)>
|
||||
inline void disconnect(instance_type<Class, Event> instance) noexcept {
|
||||
signal.template disconnect<Class, Member>(std::move(instance));
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
inline void trigger(Args&&... args) {
|
||||
signal.publish({ std::forward<Args>(args)... });
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
inline void enqueue(std::size_t current, Args&&... args) {
|
||||
events[current].push_back({ std::forward<Args>(args)... });
|
||||
}
|
||||
|
||||
private:
|
||||
Sig<void(const Event &)> signal{};
|
||||
std::vector<Event> events[2];
|
||||
};
|
||||
|
||||
inline static std::size_t buffer(bool mode) {
|
||||
return mode ? 0 : 1;
|
||||
}
|
||||
|
||||
template<typename Event>
|
||||
SignalWrapper<Event> & wrapper() {
|
||||
const auto type = event_family::type<Event>();
|
||||
|
||||
if(!(type < wrappers.size())) {
|
||||
wrappers.resize(type + 1);
|
||||
}
|
||||
|
||||
if(!wrappers[type]) {
|
||||
wrappers[type] = std::make_unique<SignalWrapper<Event>>();
|
||||
}
|
||||
|
||||
return static_cast<SignalWrapper<Event> &>(*wrappers[type]);
|
||||
}
|
||||
|
||||
public:
|
||||
/*! @brief Default constructor. */
|
||||
Dispatcher() noexcept
|
||||
: wrappers{}, mode{false}
|
||||
{}
|
||||
|
||||
/**
|
||||
* @brief Registers a listener given in the form of a member function.
|
||||
*
|
||||
* A matching member function has the following signature:
|
||||
* `void receive(const Event &)`. Member functions named `receive` are
|
||||
* automatically detected and registered if available.
|
||||
*
|
||||
* @warning
|
||||
* Connecting a listener during an update may lead to unexpected behavior.
|
||||
* Register listeners before or after invoking the update if possible.
|
||||
*
|
||||
* @tparam Event Type of event to which to connect the function.
|
||||
* @tparam Class Type of class to which the member function belongs.
|
||||
* @tparam Member Member function to connect to the signal.
|
||||
* @param instance A valid instance of the right type.
|
||||
*/
|
||||
template<typename Event, typename Class, void(Class::*Member)(const Event &) = &Class::receive>
|
||||
void connect(instance_type<Class, Event> instance) noexcept {
|
||||
wrapper<Event>().template connect<Class, Member>(std::move(instance));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Unregisters a listener given in the form of a member function.
|
||||
*
|
||||
* A matching member function has the following signature:
|
||||
* `void receive(const Event &)`. Member functions named `receive` are
|
||||
* automatically detected and unregistered if available.
|
||||
*
|
||||
* @warning
|
||||
* Disonnecting a listener during an update may lead to unexpected behavior.
|
||||
* Unregister listeners before or after invoking the update if possible.
|
||||
*
|
||||
* @tparam Event Type of event from which to disconnect the function.
|
||||
* @tparam Class Type of class to which the member function belongs.
|
||||
* @tparam Member Member function to connect to the signal.
|
||||
* @param instance A valid instance of the right type.
|
||||
*/
|
||||
template<typename Event, typename Class, void(Class::*Member)(const Event &) = &Class::receive>
|
||||
void disconnect(instance_type<Class, Event> instance) noexcept {
|
||||
wrapper<Event>().template disconnect<Class, Member>(std::move(instance));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Triggers an immediate event of the given type.
|
||||
*
|
||||
* All the listeners registered for the given type are immediately notified.
|
||||
* The event is discarded after the execution.
|
||||
*
|
||||
* @tparam Event Type of event to trigger.
|
||||
* @tparam Args Types of arguments to use to construct the event.
|
||||
* @param args Arguments to use to construct the event.
|
||||
*/
|
||||
template<typename Event, typename... Args>
|
||||
void trigger(Args&&... args) {
|
||||
wrapper<Event>().trigger(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Enqueues an event of the given type.
|
||||
*
|
||||
* An event of the given type is queued. No listener is invoked. Use the
|
||||
* `update` member function to notify listeners when ready.
|
||||
*
|
||||
* @tparam Event Type of event to trigger.
|
||||
* @tparam Args Types of arguments to use to construct the event.
|
||||
* @param args Arguments to use to construct the event.
|
||||
*/
|
||||
template<typename Event, typename... Args>
|
||||
void enqueue(Args&&... args) {
|
||||
wrapper<Event>().enqueue(buffer(mode), std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Delivers all the pending events.
|
||||
*
|
||||
* This method is blocking and it doesn't return until all the events are
|
||||
* delivered to the registered listeners. It's responsability of the users
|
||||
* to reduce at a minimum the time spent in the bodies of the listeners.
|
||||
*/
|
||||
void update() {
|
||||
const auto buf = buffer(mode);
|
||||
mode = !mode;
|
||||
|
||||
for(auto &&wrapper: wrappers) {
|
||||
if(wrapper) {
|
||||
wrapper->publish(buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<std::unique_ptr<BaseSignalWrapper>> wrappers;
|
||||
bool mode;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Managed dispatcher.
|
||||
*
|
||||
* A managed dispatcher uses the Signal class template as an underlying type.
|
||||
* The type of the instances is the one required by the signal handler:
|
||||
* `std::shared_ptr<Class>` (a shared pointer).
|
||||
*/
|
||||
using ManagedDispatcher = Dispatcher<Signal>;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Unmanaged dispatcher.
|
||||
*
|
||||
* An unmanaged dispatcher uses the SigH class template as an underlying type.
|
||||
* The type of the instances is the one required by the signal handler:
|
||||
* `Class *` (a naked pointer).<br/>
|
||||
* When it comes to work with this kind of dispatcher, users must guarantee that
|
||||
* the lifetimes of the instances overcome the one of the dispatcher itself.
|
||||
*/
|
||||
using UnmanagedDispatcher = Dispatcher<SigH>;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_SIGNAL_DISPATCHER_HPP
|
||||
341
src/entt/signal/emitter.hpp
Normal file
341
src/entt/signal/emitter.hpp
Normal file
@@ -0,0 +1,341 @@
|
||||
#ifndef ENTT_SIGNAL_EMITTER_HPP
|
||||
#define ENTT_SIGNAL_EMITTER_HPP
|
||||
|
||||
|
||||
#include <type_traits>
|
||||
#include <functional>
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <list>
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/**
|
||||
* @brief General purpose event emitter.
|
||||
*
|
||||
* The emitter class template follows the CRTP idiom. To create a custom emitter
|
||||
* type, derived classes must inherit directly from the base class as:
|
||||
*
|
||||
* ```cpp
|
||||
* struct MyEmitter: Emitter<MyEmitter> {
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* Handlers for the type of events are created internally on the fly. It's not
|
||||
* required to specify in advance the full list of accepted types.<br/>
|
||||
* Moreover, whenever an event is published, an emitter provides the listeners
|
||||
* with a reference to itself along with a const reference to the event.
|
||||
* Therefore listeners have an handy way to work with it without incurring in
|
||||
* the need of capturing a reference to the emitter.
|
||||
*
|
||||
* @tparam Derived Actual type of emitter that extends the class template.
|
||||
*/
|
||||
template<typename Derived>
|
||||
class Emitter {
|
||||
struct BaseHandler {
|
||||
virtual ~BaseHandler() = default;
|
||||
virtual bool empty() const noexcept = 0;
|
||||
virtual void clear() noexcept = 0;
|
||||
};
|
||||
|
||||
template<typename Event>
|
||||
struct Handler final: BaseHandler {
|
||||
using listener_type = std::function<void(const Event &, Derived &)>;
|
||||
using element_type = std::pair<bool, listener_type>;
|
||||
using container_type = std::list<element_type>;
|
||||
using connection_type = typename container_type::iterator;
|
||||
|
||||
bool empty() const noexcept override {
|
||||
auto pred = [](auto &&element){ return element.first; };
|
||||
|
||||
return std::all_of(onceL.cbegin(), onceL.cend(), pred) &&
|
||||
std::all_of(onL.cbegin(), onL.cend(), pred);
|
||||
}
|
||||
|
||||
void clear() noexcept override {
|
||||
if(publishing) {
|
||||
auto func = [](auto &&element){ element.first = true; };
|
||||
std::for_each(onceL.begin(), onceL.end(), func);
|
||||
std::for_each(onL.begin(), onL.end(), func);
|
||||
} else {
|
||||
onceL.clear();
|
||||
onL.clear();
|
||||
}
|
||||
}
|
||||
|
||||
inline connection_type once(listener_type listener) {
|
||||
return onceL.emplace(onceL.cend(), false, std::move(listener));
|
||||
}
|
||||
|
||||
inline connection_type on(listener_type listener) {
|
||||
return onL.emplace(onL.cend(), false, std::move(listener));
|
||||
}
|
||||
|
||||
void erase(connection_type conn) noexcept {
|
||||
conn->first = true;
|
||||
|
||||
if(!publishing) {
|
||||
auto pred = [](auto &&element){ return element.first; };
|
||||
onceL.remove_if(pred);
|
||||
onL.remove_if(pred);
|
||||
}
|
||||
}
|
||||
|
||||
void publish(const Event &event, Derived &ref) {
|
||||
container_type currentL;
|
||||
onceL.swap(currentL);
|
||||
|
||||
auto func = [&event, &ref](auto &&element) {
|
||||
return element.first ? void() : element.second(event, ref);
|
||||
};
|
||||
|
||||
publishing = true;
|
||||
|
||||
std::for_each(onL.rbegin(), onL.rend(), func);
|
||||
std::for_each(currentL.rbegin(), currentL.rend(), func);
|
||||
|
||||
publishing = false;
|
||||
|
||||
onL.remove_if([](auto &&element){ return element.first; });
|
||||
}
|
||||
|
||||
private:
|
||||
bool publishing{false};
|
||||
container_type onceL{};
|
||||
container_type onL{};
|
||||
};
|
||||
|
||||
static std::size_t next() noexcept {
|
||||
static std::size_t counter = 0;
|
||||
return counter++;
|
||||
}
|
||||
|
||||
template<typename>
|
||||
static std::size_t type() noexcept {
|
||||
static std::size_t value = next();
|
||||
return value;
|
||||
}
|
||||
|
||||
template<typename Event>
|
||||
Handler<Event> & handler() noexcept {
|
||||
const std::size_t family = type<Event>();
|
||||
|
||||
if(!(family < handlers.size())) {
|
||||
handlers.resize(family+1);
|
||||
}
|
||||
|
||||
if(!handlers[family]) {
|
||||
handlers[family] = std::make_unique<Handler<Event>>();
|
||||
}
|
||||
|
||||
return static_cast<Handler<Event> &>(*handlers[family]);
|
||||
}
|
||||
|
||||
public:
|
||||
/** @brief Type of listeners accepted for the given type of event. */
|
||||
template<typename Event>
|
||||
using Listener = typename Handler<Event>::listener_type;
|
||||
|
||||
/**
|
||||
* @brief Generic connection type for events.
|
||||
*
|
||||
* Type of the connection object returned by the event emitter whenever a
|
||||
* listener for the given type is registered.<br/>
|
||||
* It can be used to break connections still in use.
|
||||
*
|
||||
* @tparam Event Type of event for which the connection is created.
|
||||
*/
|
||||
template<typename Event>
|
||||
struct Connection final: private Handler<Event>::connection_type {
|
||||
/** @brief Event emitters are friend classes of connections. */
|
||||
friend class Emitter;
|
||||
|
||||
/*! @brief Default constructor. */
|
||||
Connection() noexcept = default;
|
||||
|
||||
/**
|
||||
* @brief Creates a connection that wraps its underlying instance.
|
||||
* @param conn A connection object to wrap.
|
||||
*/
|
||||
Connection(typename Handler<Event>::connection_type conn)
|
||||
: Handler<Event>::connection_type{std::move(conn)}
|
||||
{}
|
||||
|
||||
/*! @brief Default copy constructor. */
|
||||
Connection(const Connection &) = default;
|
||||
/*! @brief Default move constructor. */
|
||||
Connection(Connection &&) = default;
|
||||
|
||||
/**
|
||||
* @brief Default copy assignament operator.
|
||||
* @return This connection.
|
||||
*/
|
||||
Connection & operator=(const Connection &) = default;
|
||||
|
||||
/**
|
||||
* @brief Default move assignment operator.
|
||||
* @return This connection.
|
||||
*/
|
||||
Connection & operator=(Connection &&) = default;
|
||||
};
|
||||
|
||||
/*! @brief Default constructor. */
|
||||
Emitter() noexcept = default;
|
||||
|
||||
/*! @brief Default destructor. */
|
||||
virtual ~Emitter() noexcept {
|
||||
static_assert(std::is_base_of<Emitter<Derived>, Derived>::value, "!");
|
||||
}
|
||||
|
||||
/*! @brief Copying an emitter isn't allowed. */
|
||||
Emitter(const Emitter &) = delete;
|
||||
/*! @brief Default move constructor. */
|
||||
Emitter(Emitter &&) = default;
|
||||
|
||||
/*! @brief Copying an emitter isn't allowed. @return This emitter. */
|
||||
Emitter & operator=(const Emitter &) = delete;
|
||||
/*! @brief Default move assignament operator. @return This emitter. */
|
||||
Emitter & operator=(Emitter &&) = default;
|
||||
|
||||
/**
|
||||
* @brief Emits the given event.
|
||||
*
|
||||
* All the listeners registered for the specific event type are invoked with
|
||||
* the given event. The event type must either have a proper constructor for
|
||||
* the arguments provided or be an aggregate type.
|
||||
*
|
||||
* @tparam Event Type of event to publish.
|
||||
* @tparam Args Types of arguments to use to construct the event.
|
||||
* @param args Parameters to use to initialize the event.
|
||||
*/
|
||||
template<typename Event, typename... Args>
|
||||
void publish(Args&&... args) {
|
||||
handler<Event>().publish({ std::forward<Args>(args)... }, *static_cast<Derived *>(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Registers a long-lived listener with the event emitter.
|
||||
*
|
||||
* This method can be used to register a listener designed to be invoked
|
||||
* more than once for the given event type.<br/>
|
||||
* The connection returned by the method can be freely discarded. It's meant
|
||||
* to be used later to disconnect the listener if required.
|
||||
*
|
||||
* The listener is as a callable object that can be moved and the type of
|
||||
* which is `void(const Event &, Derived &)`.
|
||||
*
|
||||
* @note
|
||||
* Whenever an event is emitted, the emitter provides the listener with a
|
||||
* reference to the derived class. Listeners don't have to capture those
|
||||
* instances for later uses.
|
||||
*
|
||||
* @tparam Event Type of event to which to connect the listener.
|
||||
* @param listener The listener to register.
|
||||
* @return Connection object that can be used to disconnect the listener.
|
||||
*/
|
||||
template<typename Event>
|
||||
Connection<Event> on(Listener<Event> listener) {
|
||||
return handler<Event>().on(std::move(listener));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Registers a short-lived listener with the event emitter.
|
||||
*
|
||||
* This method can be used to register a listener designed to be invoked
|
||||
* only once for the given event type.<br/>
|
||||
* The connection returned by the method can be freely discarded. It's meant
|
||||
* to be used later to disconnect the listener if required.
|
||||
*
|
||||
* The listener is as a callable object that can be moved and the type of
|
||||
* which is `void(const Event &, Derived &)`.
|
||||
*
|
||||
* @note
|
||||
* Whenever an event is emitted, the emitter provides the listener with a
|
||||
* reference to the derived class. Listeners don't have to capture those
|
||||
* instances for later uses.
|
||||
*
|
||||
* @tparam Event Type of event to which to connect the listener.
|
||||
* @param listener The listener to register.
|
||||
* @return Connection object that can be used to disconnect the listener.
|
||||
*/
|
||||
template<typename Event>
|
||||
Connection<Event> once(Listener<Event> listener) {
|
||||
return handler<Event>().once(std::move(listener));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects a listener from the event emitter.
|
||||
*
|
||||
* Do not use twice the same connection to disconnect a listener, it results
|
||||
* in undefined behavior. Once used, discard the connection object.
|
||||
*
|
||||
* @tparam Event Type of event of the connection.
|
||||
* @param conn A valid connection.
|
||||
*/
|
||||
template<typename Event>
|
||||
void erase(Connection<Event> conn) noexcept {
|
||||
handler<Event>().erase(std::move(conn));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects all the listeners for the given event type.
|
||||
*
|
||||
* All the connections previously returned for the given event are
|
||||
* invalidated. Using them results in undefined behaviour.
|
||||
*
|
||||
* @tparam Event Type of event to reset.
|
||||
*/
|
||||
template<typename Event>
|
||||
void clear() noexcept {
|
||||
handler<Event>().clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects all the listeners.
|
||||
*
|
||||
* All the connections previously returned are invalidated. Using them
|
||||
* results in undefined behaviour.
|
||||
*/
|
||||
void clear() noexcept {
|
||||
std::for_each(handlers.begin(), handlers.end(),
|
||||
[](auto &&handler){ if(handler) { handler->clear(); } });
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if there are listeners registered for the specific event.
|
||||
* @tparam Event Type of event to test.
|
||||
* @return True if there are no listeners registered, false otherwise.
|
||||
*/
|
||||
template<typename Event>
|
||||
bool empty() const noexcept {
|
||||
const std::size_t family = type<Event>();
|
||||
|
||||
return (!(family < handlers.size()) ||
|
||||
!handlers[family] ||
|
||||
static_cast<Handler<Event> &>(*handlers[family]).empty());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if there are listeners registered with the event emitter.
|
||||
* @return True if there are no listeners registered, false otherwise.
|
||||
*/
|
||||
bool empty() const noexcept {
|
||||
return std::all_of(handlers.cbegin(), handlers.cend(),
|
||||
[](auto &&handler){ return !handler || handler->empty(); });
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<std::unique_ptr<BaseHandler>> handlers{};
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_SIGNAL_EMITTER_HPP
|
||||
@@ -71,35 +71,40 @@ using DefaultCollectorType = typename DefaultCollector<Function>::collector_type
|
||||
|
||||
|
||||
/**
|
||||
* @brief Signal handler.
|
||||
* @brief Unmanaged signal handler declaration.
|
||||
*
|
||||
* Primary template isn't defined on purpose. All the specializations give a
|
||||
* compile-time error unless the template parameter is a function type.
|
||||
*
|
||||
* @tparam Function A valid function type.
|
||||
* @tparam Collector Type of collector to use, if any.
|
||||
*/
|
||||
template<typename Function, typename = DefaultCollectorType<Function>>
|
||||
template<typename Function, typename Collector = DefaultCollectorType<Function>>
|
||||
class SigH;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Signal handler.
|
||||
* @brief Unmanaged signal handler definition.
|
||||
*
|
||||
* Unmanaged signal handler. It works directly with naked pointers to classes
|
||||
* and pointers to member functions as well as pointers to free functions. Users
|
||||
* of this class are in charge of disconnecting instances before deleting them.
|
||||
*
|
||||
* This class serves mainly two purposes:
|
||||
* * Creating signals to be used later to notify a bunch of listeners.
|
||||
*
|
||||
* * Creating signals used later to notify a bunch of listeners.
|
||||
* * Collecting results from a set of functions like in a voting system.
|
||||
*
|
||||
* The default collector does nothing. To properly collect data, define and use
|
||||
* a class that has a call operator the signature of which is `bool(Param)` and:
|
||||
*
|
||||
* * `Param` is a type to which `Ret` can be converted.
|
||||
* * The return type is true if the handler must stop collecting data, false
|
||||
* otherwise.
|
||||
*
|
||||
* @tparam Ret Return type of a function type.
|
||||
* @tparam Args Types of the arguments of a function type.
|
||||
* @tparam Collector The type of the collector to use if any.
|
||||
* @tparam Args Types of arguments of a function type.
|
||||
* @tparam Collector Type of collector to use, if any.
|
||||
*/
|
||||
template<typename Ret, typename... Args, typename Collector>
|
||||
class SigH<Ret(Args...), Collector> final: private Invoker<Ret(Args...), Collector> {
|
||||
@@ -121,58 +126,23 @@ public:
|
||||
/*! @brief Collector type. */
|
||||
using collector_type = Collector;
|
||||
|
||||
/*! @brief Default constructor, explicit on purpose. */
|
||||
explicit SigH() noexcept = default;
|
||||
|
||||
/*! @brief Default destructor. */
|
||||
~SigH() noexcept = default;
|
||||
|
||||
/**
|
||||
* @brief Copy constructor, listeners are also connected to this signal.
|
||||
* @param other A signal to be used as source to initialize this instance.
|
||||
* @brief Instance type when it comes to connecting member functions.
|
||||
* @tparam Class Type of class to which the member function belongs.
|
||||
*/
|
||||
SigH(const SigH &other)
|
||||
: calls{other.calls}
|
||||
{}
|
||||
template<typename Class>
|
||||
using instance_type = Class *;
|
||||
|
||||
/**
|
||||
* @brief Default move constructor.
|
||||
* @param other A signal to be used as source to initialize this instance.
|
||||
*/
|
||||
SigH(SigH &&other): SigH{} {
|
||||
swap(*this, other);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assignment operator, listeners are also connected to this signal.
|
||||
* @param other A signal to be used as source to initialize this instance.
|
||||
* @return This signal.
|
||||
*/
|
||||
SigH & operator=(const SigH &other) {
|
||||
calls = other.calls;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Default move operator.
|
||||
* @param other A signal to be used as source to initialize this instance.
|
||||
* @return This signal.
|
||||
*/
|
||||
SigH & operator=(SigH &&other) {
|
||||
swap(*this, other);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief The number of listeners connected to the signal.
|
||||
* @return The number of listeners currently connected.
|
||||
* @brief Number of listeners connected to the signal.
|
||||
* @return Number of listeners currently connected.
|
||||
*/
|
||||
size_type size() const noexcept {
|
||||
return calls.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns true is at least a listener is connected to the signal.
|
||||
* @brief Returns false if at least a listener is connected to the signal.
|
||||
* @return True if the signal has no listeners connected, false otherwise.
|
||||
*/
|
||||
bool empty() const noexcept {
|
||||
@@ -180,16 +150,15 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects all the listeners from the signal.
|
||||
* @brief Disconnects all the listeners from a signal.
|
||||
*/
|
||||
void clear() noexcept {
|
||||
calls.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Connects a free function to the signal.
|
||||
* @brief Connects a free function to a signal.
|
||||
*
|
||||
* @note
|
||||
* The signal handler performs checks to avoid multiple connections for free
|
||||
* functions.
|
||||
*
|
||||
@@ -202,28 +171,25 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Connects the member function for the given instance to the signal.
|
||||
* @brief Connects a member function for a given instance to a signal.
|
||||
*
|
||||
* The signal isn't responsible for the connected object. Users must
|
||||
* guarantee that the lifetime of the instance overcomes the one of the
|
||||
* signal.
|
||||
* signal. On the other side, the signal handler performs checks to avoid
|
||||
* multiple connections for the same member function of a given instance.
|
||||
*
|
||||
* @warning
|
||||
* The signal handler performs checks to avoid multiple connections for the
|
||||
* same member function of a given instance.
|
||||
*
|
||||
* @tparam Class The type of the class to which the member function belongs.
|
||||
* @tparam Member The member function to connect to the signal.
|
||||
* @tparam Class Type of class to which the member function belongs.
|
||||
* @tparam Member Member function to connect to the signal.
|
||||
* @param instance A valid instance of type pointer to `Class`.
|
||||
*/
|
||||
template <typename Class, Ret(Class::*Member)(Args...)>
|
||||
void connect(Class *instance) {
|
||||
void connect(instance_type<Class> instance) {
|
||||
disconnect<Class, Member>(instance);
|
||||
calls.emplace_back(instance, &proto<Class, Member>);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects a free function from the signal.
|
||||
* @brief Disconnects a free function from a signal.
|
||||
* @tparam Function A valid free function pointer.
|
||||
*/
|
||||
template<Ret(*Function)(Args...)>
|
||||
@@ -233,30 +199,30 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects the given member function from the signal.
|
||||
* @tparam Class The type of the class to which the member function belongs.
|
||||
* @tparam Member The member function to connect to the signal.
|
||||
* @brief Disconnects the given member function from a signal.
|
||||
* @tparam Class Type of class to which the member function belongs.
|
||||
* @tparam Member Member function to connect to the signal.
|
||||
* @param instance A valid instance of type pointer to `Class`.
|
||||
*/
|
||||
template<typename Class, Ret(Class::*Member)(Args...)>
|
||||
void disconnect(Class *instance) {
|
||||
void disconnect(instance_type<Class> instance) {
|
||||
call_type target{instance, &proto<Class, Member>};
|
||||
calls.erase(std::remove(calls.begin(), calls.end(), std::move(target)), calls.end());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Removes all existing connections for the given instance.
|
||||
* @tparam Class The type of the class to which the member function belongs.
|
||||
* @tparam Class Type of class to which the member function belongs.
|
||||
* @param instance A valid instance of type pointer to `Class`.
|
||||
*/
|
||||
template<typename Class>
|
||||
void disconnect(Class *instance) {
|
||||
void disconnect(instance_type<Class> instance) {
|
||||
auto func = [instance](const call_type &call) { return call.first == instance; };
|
||||
calls.erase(std::remove_if(calls.begin(), calls.end(), std::move(func)), calls.end());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Triggers the signal.
|
||||
* @brief Triggers a signal.
|
||||
*
|
||||
* All the listeners are notified. Order isn't guaranteed.
|
||||
*
|
||||
@@ -305,7 +271,7 @@ public:
|
||||
* @return True if the two signals are identical, false otherwise.
|
||||
*/
|
||||
bool operator==(const SigH &other) const noexcept {
|
||||
return (calls.size() == other.calls.size()) && std::equal(calls.cbegin(), calls.cend(), other.calls.cbegin());
|
||||
return std::equal(calls.cbegin(), calls.cend(), other.calls.cbegin(), other.calls.cend());
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -320,7 +286,7 @@ private:
|
||||
* listeners registered exactly in the same order.
|
||||
*
|
||||
* @tparam Ret Return type of a function type.
|
||||
* @tparam Args Types of the arguments of a function type.
|
||||
* @tparam Args Types of arguments of a function type.
|
||||
* @param lhs A valid signal object.
|
||||
* @param rhs A valid signal object.
|
||||
* @return True if the two signals are different, false otherwise.
|
||||
@@ -331,17 +297,6 @@ bool operator!=(const SigH<Ret(Args...)> &lhs, const SigH<Ret(Args...)> &rhs) no
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Event handler.
|
||||
*
|
||||
* Unmanaged event handler. Collecting data for this kind of signals doesn't
|
||||
* make sense at all. Its sole purpose is to provide the listeners with the
|
||||
* given event.
|
||||
*/
|
||||
template<typename Event>
|
||||
using EventH = SigH<void(const Event &)>;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
227
src/entt/signal/signal.hpp
Normal file
227
src/entt/signal/signal.hpp
Normal file
@@ -0,0 +1,227 @@
|
||||
#ifndef ENTT_SIGNAL_SIGNAL_HPP
|
||||
#define ENTT_SIGNAL_SIGNAL_HPP
|
||||
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
#include <cstdint>
|
||||
#include <iterator>
|
||||
#include <algorithm>
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/**
|
||||
* @brief Managed signal handler declaration.
|
||||
*
|
||||
* Primary template isn't defined on purpose. All the specializations give a
|
||||
* compile-time error unless the template parameter is a function type.
|
||||
*/
|
||||
template<typename>
|
||||
class Signal;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Managed signal handler definition.
|
||||
*
|
||||
* Managed signal handler. It works with weak pointers to classes and pointers
|
||||
* to member functions as well as pointers to free functions. References are
|
||||
* automatically removed when the instances to which they point are freed.
|
||||
*
|
||||
* This class can be used to create signals used later to notify a bunch of
|
||||
* listeners.
|
||||
*
|
||||
* @tparam Args Types of arguments of a function type.
|
||||
*/
|
||||
template<typename... Args>
|
||||
class Signal<void(Args...)> final {
|
||||
using proto_type = bool(*)(std::weak_ptr<void> &, Args...);
|
||||
using call_type = std::pair<std::weak_ptr<void>, proto_type>;
|
||||
|
||||
template<void(*Function)(Args...)>
|
||||
static bool proto(std::weak_ptr<void> &, Args... args) {
|
||||
Function(args...);
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename Class, void(Class::*Member)(Args...)>
|
||||
static bool proto(std::weak_ptr<void> &wptr, Args... args) {
|
||||
bool ret = false;
|
||||
|
||||
if(!wptr.expired()) {
|
||||
auto ptr = std::static_pointer_cast<Class>(wptr.lock());
|
||||
(ptr.get()->*Member)(args...);
|
||||
ret = true;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public:
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = std::size_t;
|
||||
|
||||
/**
|
||||
* @brief Instance type when it comes to connecting member functions.
|
||||
* @tparam Class Type of class to which the member function belongs.
|
||||
*/
|
||||
template<typename Class>
|
||||
using instance_type = std::shared_ptr<Class>;
|
||||
|
||||
/**
|
||||
* @brief Number of listeners connected to the signal.
|
||||
* @return Number of listeners currently connected.
|
||||
*/
|
||||
size_type size() const noexcept {
|
||||
return calls.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns false if at least a listener is connected to the signal.
|
||||
* @return True if the signal has no listeners connected, false otherwise.
|
||||
*/
|
||||
bool empty() const noexcept {
|
||||
return calls.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects all the listeners from a signal.
|
||||
*/
|
||||
void clear() noexcept {
|
||||
calls.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Connects a free function to a signal.
|
||||
*
|
||||
* The signal handler performs checks to avoid multiple connections for free
|
||||
* functions.
|
||||
*
|
||||
* @tparam Function A valid free function pointer.
|
||||
*/
|
||||
template<void(*Function)(Args...)>
|
||||
void connect() {
|
||||
disconnect<Function>();
|
||||
calls.emplace_back(std::weak_ptr<void>{}, &proto<Function>);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Connects a member function for a given instance to a signal.
|
||||
*
|
||||
* The signal handler performs checks to avoid multiple connections for the
|
||||
* same member function of a given instance.
|
||||
*
|
||||
* @tparam Class Type of class to which the member function belongs.
|
||||
* @tparam Member Member function to connect to the signal.
|
||||
* @param instance A valid instance of type pointer to `Class`.
|
||||
*/
|
||||
template<typename Class, void(Class::*Member)(Args...)>
|
||||
void connect(instance_type<Class> instance) {
|
||||
disconnect<Class, Member>(instance);
|
||||
calls.emplace_back(std::move(instance), &proto<Class, Member>);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects a free function from a signal.
|
||||
* @tparam Function A valid free function pointer.
|
||||
*/
|
||||
template<void(*Function)(Args...)>
|
||||
void disconnect() {
|
||||
calls.erase(std::remove_if(calls.begin(), calls.end(),
|
||||
[](const call_type &call) { return call.second == &proto<Function> && !call.first.lock(); }
|
||||
), calls.end());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects the given member function from a signal.
|
||||
* @tparam Class Type of class to which the member function belongs.
|
||||
* @tparam Member Member function to connect to the signal.
|
||||
* @param instance A valid instance of type pointer to `Class`.
|
||||
*/
|
||||
template<typename Class, void(Class::*Member)(Args...)>
|
||||
void disconnect(instance_type<Class> instance) {
|
||||
calls.erase(std::remove_if(calls.begin(), calls.end(),
|
||||
[instance{std::move(instance)}](const call_type &call) { return call.second == &proto<Class, Member> && call.first.lock() == instance; }
|
||||
), calls.end());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Removes all existing connections for the given instance.
|
||||
* @tparam Class Type of class to which the member function belongs.
|
||||
* @param instance A valid instance of type pointer to `Class`.
|
||||
*/
|
||||
template<typename Class>
|
||||
void disconnect(instance_type<Class> instance) {
|
||||
calls.erase(std::remove_if(calls.begin(), calls.end(),
|
||||
[instance{std::move(instance)}](const call_type &call) { return call.first.lock() == instance; }
|
||||
), calls.end());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Triggers a signal.
|
||||
*
|
||||
* All the listeners are notified. Order isn't guaranteed.
|
||||
*
|
||||
* @param args Arguments to use to invoke listeners.
|
||||
*/
|
||||
void publish(Args... args) {
|
||||
for(auto it = calls.rbegin(), end = calls.rend(); it != end; it++) {
|
||||
if(!(it->second)(it->first, args...)) {
|
||||
calls.erase(std::next(it).base());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Swaps listeners between the two signals.
|
||||
* @param lhs A valid signal object.
|
||||
* @param rhs A valid signal object.
|
||||
*/
|
||||
friend void swap(Signal &lhs, Signal &rhs) {
|
||||
using std::swap;
|
||||
swap(lhs.calls, rhs.calls);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if the contents of the two signals are identical.
|
||||
*
|
||||
* Two signals are identical if they have the same size and the same
|
||||
* listeners registered exactly in the same order.
|
||||
*
|
||||
* @param other Signal with which to compare.
|
||||
* @return True if the two signals are identical, false otherwise.
|
||||
*/
|
||||
bool operator==(const Signal &other) const noexcept {
|
||||
return std::equal(calls.cbegin(), calls.cend(), other.calls.cbegin(), other.calls.cend(), [](const auto &lhs, const auto &rhs) {
|
||||
return (lhs.second == rhs.second) && (lhs.first.lock() == rhs.first.lock());
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<call_type> calls;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Checks if the contents of the two signals are different.
|
||||
*
|
||||
* Two signals are identical if they have the same size and the same
|
||||
* listeners registered exactly in the same order.
|
||||
*
|
||||
* @tparam Args Types of arguments of a function type.
|
||||
* @param lhs A valid signal object.
|
||||
* @param rhs A valid signal object.
|
||||
* @return True if the two signals are different, false otherwise.
|
||||
*/
|
||||
template<typename... Args>
|
||||
bool operator!=(const Signal<void(Args...)> &lhs, const Signal<void(Args...)> &rhs) noexcept {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_SIGNAL_SIGNAL_HPP
|
||||
@@ -2,31 +2,100 @@
|
||||
# Tests configuration
|
||||
#
|
||||
|
||||
set(COMMON_LINK_LIBS gtest_main Threads::Threads)
|
||||
include_directories(${PROJECT_SRC_DIR})
|
||||
|
||||
# Test core
|
||||
|
||||
add_executable(core entt/core/ident.cpp entt/core/family.cpp odr.cpp)
|
||||
target_link_libraries(core PRIVATE ${COMMON_LINK_LIBS})
|
||||
add_test(NAME core COMMAND core)
|
||||
|
||||
# Test entt
|
||||
|
||||
add_executable(entity entt/entity/registry.cpp entt/entity/sparse_set.cpp entt/entity/view.cpp odr.cpp)
|
||||
target_link_libraries(entity PRIVATE ${COMMON_LINK_LIBS})
|
||||
add_test(NAME entity COMMAND entity)
|
||||
add_library(odr OBJECT odr.cpp)
|
||||
|
||||
# Test benchmark
|
||||
|
||||
IF(CMAKE_BUILD_TYPE MATCHES Release)
|
||||
add_executable(benchmark entt/entity/benchmark.cpp odr.cpp)
|
||||
target_link_libraries(benchmark PRIVATE ${COMMON_LINK_LIBS})
|
||||
if(BUILD_BENCHMARK)
|
||||
add_executable(
|
||||
benchmark
|
||||
$<TARGET_OBJECTS:odr>
|
||||
benchmark/benchmark.cpp
|
||||
)
|
||||
target_link_libraries(benchmark PRIVATE gtest_main Threads::Threads)
|
||||
add_test(NAME benchmark COMMAND benchmark)
|
||||
ENDIF()
|
||||
endif()
|
||||
|
||||
# Test mod
|
||||
|
||||
if(BUILD_MOD)
|
||||
add_executable(
|
||||
mod
|
||||
$<TARGET_OBJECTS:odr>
|
||||
mod/duktape.c
|
||||
mod/mod.cpp
|
||||
)
|
||||
target_link_libraries(mod PRIVATE gtest_main Threads::Threads m)
|
||||
add_test(NAME mod COMMAND mod)
|
||||
endif()
|
||||
|
||||
# Test core
|
||||
|
||||
add_executable(
|
||||
core
|
||||
$<TARGET_OBJECTS:odr>
|
||||
entt/core/family.cpp
|
||||
entt/core/hashed_string.cpp
|
||||
entt/core/ident.cpp
|
||||
)
|
||||
target_link_libraries(core PRIVATE gtest_main Threads::Threads)
|
||||
add_test(NAME core COMMAND core)
|
||||
|
||||
# Test entity
|
||||
|
||||
add_executable(
|
||||
entity
|
||||
$<TARGET_OBJECTS:odr>
|
||||
entt/entity/actor.cpp
|
||||
entt/entity/registry.cpp
|
||||
entt/entity/sparse_set.cpp
|
||||
entt/entity/view.cpp
|
||||
)
|
||||
target_link_libraries(entity PRIVATE gtest_main Threads::Threads)
|
||||
add_test(NAME entity COMMAND entity)
|
||||
|
||||
# Test locator
|
||||
|
||||
add_executable(
|
||||
locator
|
||||
$<TARGET_OBJECTS:odr>
|
||||
entt/locator/locator.cpp
|
||||
)
|
||||
target_link_libraries(locator PRIVATE gtest_main Threads::Threads)
|
||||
add_test(NAME locator COMMAND locator)
|
||||
|
||||
# Test process
|
||||
|
||||
add_executable(
|
||||
process
|
||||
$<TARGET_OBJECTS:odr>
|
||||
entt/process/process.cpp
|
||||
entt/process/scheduler.cpp
|
||||
)
|
||||
target_link_libraries(process PRIVATE gtest_main Threads::Threads)
|
||||
add_test(NAME process COMMAND process)
|
||||
|
||||
# Test resource
|
||||
|
||||
add_executable(
|
||||
resource
|
||||
$<TARGET_OBJECTS:odr>
|
||||
entt/resource/resource.cpp
|
||||
)
|
||||
target_link_libraries(resource PRIVATE gtest_main Threads::Threads)
|
||||
add_test(NAME resource COMMAND resource)
|
||||
|
||||
# Test signal
|
||||
|
||||
add_executable(signal entt/signal/sigh.cpp odr.cpp)
|
||||
target_link_libraries(signal PRIVATE ${COMMON_LINK_LIBS})
|
||||
add_executable(
|
||||
signal
|
||||
$<TARGET_OBJECTS:odr>
|
||||
entt/signal/bus.cpp
|
||||
entt/signal/delegate.cpp
|
||||
entt/signal/dispatcher.cpp
|
||||
entt/signal/emitter.cpp
|
||||
entt/signal/sigh.cpp
|
||||
entt/signal/signal.cpp
|
||||
)
|
||||
target_link_libraries(signal PRIVATE gtest_main Threads::Threads)
|
||||
add_test(NAME signal COMMAND signal)
|
||||
|
||||
356
test/benchmark/benchmark.cpp
Normal file
356
test/benchmark/benchmark.cpp
Normal file
@@ -0,0 +1,356 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <iostream>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <chrono>
|
||||
#include <vector>
|
||||
#include <entt/entity/registry.hpp>
|
||||
|
||||
struct Position {
|
||||
std::uint64_t x;
|
||||
std::uint64_t y;
|
||||
};
|
||||
|
||||
struct Velocity {
|
||||
std::uint64_t x;
|
||||
std::uint64_t y;
|
||||
};
|
||||
|
||||
template<std::size_t>
|
||||
struct Comp {};
|
||||
|
||||
struct Timer final {
|
||||
Timer(): start{std::chrono::system_clock::now()} {}
|
||||
|
||||
void elapsed() {
|
||||
auto now = std::chrono::system_clock::now();
|
||||
std::cout << std::chrono::duration<double>(now - start).count() << " seconds" << std::endl;
|
||||
}
|
||||
|
||||
private:
|
||||
std::chrono::time_point<std::chrono::system_clock> start;
|
||||
};
|
||||
|
||||
TEST(Benchmark, Construct) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
std::cout << "Constructing 10000000 entities" << std::endl;
|
||||
|
||||
Timer timer;
|
||||
|
||||
for(std::uint64_t i = 0; i < 10000000L; i++) {
|
||||
registry.create();
|
||||
}
|
||||
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(Benchmark, Destroy) {
|
||||
entt::DefaultRegistry registry;
|
||||
std::vector<entt::DefaultRegistry::entity_type> entities{};
|
||||
|
||||
std::cout << "Destroying 10000000 entities" << std::endl;
|
||||
|
||||
for(std::uint64_t i = 0; i < 10000000L; i++) {
|
||||
entities.push_back(registry.create());
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
|
||||
for(auto entity: entities) {
|
||||
registry.destroy(entity);
|
||||
}
|
||||
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(Benchmark, IterateCreateDeleteSingleComponent) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
std::cout << "Looping 10000 times creating and deleting a random number of entities" << std::endl;
|
||||
|
||||
Timer timer;
|
||||
|
||||
auto view = registry.view<Position>();
|
||||
|
||||
for(int i = 0; i < 10000; i++) {
|
||||
for(int j = 0; j < 10000; j++) {
|
||||
registry.create<Position>();
|
||||
}
|
||||
|
||||
for(auto entity: view) {
|
||||
if(rand() % 2 == 0) {
|
||||
registry.destroy(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(Benchmark, IterateSingleComponent10M) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, one component" << std::endl;
|
||||
|
||||
for(std::uint64_t i = 0; i < 10000000L; i++) {
|
||||
registry.create<Position>();
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
registry.view<Position>().each([](auto, auto &) {});
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(Benchmark, IterateTwoComponents10M) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, two components" << std::endl;
|
||||
|
||||
for(std::uint64_t i = 0; i < 10000000L; i++) {
|
||||
registry.create<Position, Velocity>();
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
registry.view<Position, Velocity>().each([](auto, auto &...) {});
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(Benchmark, IterateTwoComponents10MHalf) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, two components, half of the entities have all the components" << std::endl;
|
||||
|
||||
for(std::uint64_t i = 0; i < 10000000L; i++) {
|
||||
auto entity = registry.create<Velocity>();
|
||||
if(i % 2) { registry.assign<Position>(entity); }
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
registry.view<Position, Velocity>().each([](auto, auto &...) {});
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(Benchmark, IterateTwoComponents10MOne) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, two components, only one entity has all the components" << std::endl;
|
||||
|
||||
for(std::uint64_t i = 0; i < 10000000L; i++) {
|
||||
auto entity = registry.create<Velocity>();
|
||||
if(i == 5000000L) { registry.assign<Position>(entity); }
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
registry.view<Position, Velocity>().each([](auto, auto &...) {});
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(Benchmark, IterateTwoComponentsPersistent10M) {
|
||||
entt::DefaultRegistry registry;
|
||||
registry.prepare<Position, Velocity>();
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, two components, persistent view" << std::endl;
|
||||
|
||||
for(std::uint64_t i = 0; i < 10000000L; i++) {
|
||||
registry.create<Position, Velocity>();
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
registry.persistent<Position, Velocity>().each([](auto, auto &...) {});
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(Benchmark, IterateTwoComponentsPersistent10MHalf) {
|
||||
entt::DefaultRegistry registry;
|
||||
registry.prepare<Position, Velocity>();
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, two components, persistent view, half of the entities have all the components" << std::endl;
|
||||
|
||||
for(std::uint64_t i = 0; i < 10000000L; i++) {
|
||||
auto entity = registry.create<Velocity>();
|
||||
if(i % 2) { registry.assign<Position>(entity); }
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
registry.persistent<Position, Velocity>().each([](auto, auto &...) {});
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(Benchmark, IterateTwoComponentsPersistent10MOne) {
|
||||
entt::DefaultRegistry registry;
|
||||
registry.prepare<Position, Velocity>();
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, two components, persistent view, only one entity has all the components" << std::endl;
|
||||
|
||||
for(std::uint64_t i = 0; i < 10000000L; i++) {
|
||||
auto entity = registry.create<Velocity>();
|
||||
if(i == 5000000L) { registry.assign<Position>(entity); }
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
registry.persistent<Position, Velocity>().each([](auto, auto &...) {});
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(Benchmark, IterateFiveComponents10M) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, five components" << std::endl;
|
||||
|
||||
for(std::uint64_t i = 0; i < 10000000L; i++) {
|
||||
registry.create<Position, Velocity, Comp<1>, Comp<2>, Comp<3>>();
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
registry.view<Position, Velocity, Comp<1>, Comp<2>, Comp<3>>().each([](auto, auto &...) {});
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(Benchmark, IterateTenComponents10M) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, ten components" << std::endl;
|
||||
|
||||
for(std::uint64_t i = 0; i < 10000000L; i++) {
|
||||
registry.create<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
registry.view<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>().each([](auto, auto &...) {});
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(Benchmark, IterateTenComponents10MHalf) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, ten components, half of the entities have all the components" << std::endl;
|
||||
|
||||
for(std::uint64_t i = 0; i < 10000000L; i++) {
|
||||
auto entity = registry.create<Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
|
||||
if(i % 2) { registry.assign<Position>(entity); }
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
registry.view<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>().each([](auto, auto &...) {});
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(Benchmark, IterateTenComponents10MOne) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, ten components, only one entity has all the components" << std::endl;
|
||||
|
||||
for(std::uint64_t i = 0; i < 10000000L; i++) {
|
||||
auto entity = registry.create<Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
|
||||
if(i == 5000000L) { registry.assign<Position>(entity); }
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
registry.view<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>().each([](auto, auto &...) {});
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(Benchmark, IterateFiveComponentsPersistent10M) {
|
||||
entt::DefaultRegistry registry;
|
||||
registry.prepare<Position, Velocity, Comp<1>, Comp<2>, Comp<3>>();
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, five components, persistent view" << std::endl;
|
||||
|
||||
for(std::uint64_t i = 0; i < 10000000L; i++) {
|
||||
registry.create<Position, Velocity, Comp<1>, Comp<2>, Comp<3>>();
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
registry.persistent<Position, Velocity, Comp<1>, Comp<2>, Comp<3>>().each([](auto, auto &...) {});
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(Benchmark, IterateTenComponentsPersistent10M) {
|
||||
entt::DefaultRegistry registry;
|
||||
registry.prepare<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, ten components, persistent view" << std::endl;
|
||||
|
||||
for(std::uint64_t i = 0; i < 10000000L; i++) {
|
||||
registry.create<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
registry.persistent<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>().each([](auto, auto &...) {});
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(Benchmark, IterateTenComponentsPersistent10MHalf) {
|
||||
entt::DefaultRegistry registry;
|
||||
registry.prepare<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, ten components, persistent view, half of the entities have all the components" << std::endl;
|
||||
|
||||
for(std::uint64_t i = 0; i < 10000000L; i++) {
|
||||
auto entity = registry.create<Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
|
||||
if(i % 2) { registry.assign<Position>(entity); }
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
registry.persistent<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>().each([](auto, auto &...) {});
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(Benchmark, IterateTenComponentsPersistent10MOne) {
|
||||
entt::DefaultRegistry registry;
|
||||
registry.prepare<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, ten components, persistent view, only one entity has all the components" << std::endl;
|
||||
|
||||
for(std::uint64_t i = 0; i < 10000000L; i++) {
|
||||
auto entity = registry.create<Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
|
||||
if(i == 5000000L) { registry.assign<Position>(entity); }
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
registry.persistent<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>().each([](auto, auto &...) {});
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(Benchmark, SortSingle) {
|
||||
entt::DefaultRegistry registry;
|
||||
std::vector<entt::DefaultRegistry::entity_type> entities{};
|
||||
|
||||
std::cout << "Sort 150000 entities, one component" << std::endl;
|
||||
|
||||
for(std::uint64_t i = 0; i < 150000L; i++) {
|
||||
auto entity = registry.create<Position>({ i, i });
|
||||
entities.push_back(entity);
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
|
||||
registry.sort<Position>([](const auto &lhs, const auto &rhs) {
|
||||
return lhs.x < rhs.x && lhs.y < rhs.y;
|
||||
});
|
||||
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(Benchmark, SortMulti) {
|
||||
entt::DefaultRegistry registry;
|
||||
std::vector<entt::DefaultRegistry::entity_type> entities{};
|
||||
|
||||
std::cout << "Sort 150000 entities, two components" << std::endl;
|
||||
|
||||
for(std::uint64_t i = 0; i < 150000L; i++) {
|
||||
auto entity = registry.create<Position, Velocity>({ i, i }, { i, i });
|
||||
entities.push_back(entity);
|
||||
}
|
||||
|
||||
registry.sort<Position>([](const auto &lhs, const auto &rhs) {
|
||||
return lhs.x < rhs.x && lhs.y < rhs.y;
|
||||
});
|
||||
|
||||
Timer timer;
|
||||
|
||||
registry.sort<Velocity, Position>();
|
||||
|
||||
timer.elapsed();
|
||||
}
|
||||
@@ -14,3 +14,9 @@ TEST(Family, Functionalities) {
|
||||
ASSERT_NE(myFamilyType, myOtherFamilyType);
|
||||
ASSERT_EQ(myFamilyType, yourFamilyType);
|
||||
}
|
||||
|
||||
TEST(Family, Uniqueness) {
|
||||
ASSERT_EQ(my_family::type<int>(), my_family::type<int &>());
|
||||
ASSERT_EQ(my_family::type<int>(), my_family::type<int &&>());
|
||||
ASSERT_EQ(my_family::type<int>(), my_family::type<const int &>());
|
||||
}
|
||||
|
||||
37
test/entt/core/hashed_string.cpp
Normal file
37
test/entt/core/hashed_string.cpp
Normal file
@@ -0,0 +1,37 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/core/hashed_string.hpp>
|
||||
|
||||
constexpr bool check(const char *str) {
|
||||
using hash_type = entt::HashedString::hash_type;
|
||||
|
||||
return (static_cast<hash_type>(entt::HashedString{str}) == entt::HashedString{str}
|
||||
&& static_cast<const char *>(entt::HashedString{str}) == str
|
||||
&& entt::HashedString{str} == entt::HashedString{str}
|
||||
&& !(entt::HashedString{str} != entt::HashedString{str}));
|
||||
}
|
||||
|
||||
TEST(HashedString, Constexprness) {
|
||||
// how would you test a constepxr otherwise?
|
||||
static_assert(check("foobar"), "!");
|
||||
ASSERT_TRUE(true);
|
||||
}
|
||||
|
||||
TEST(HashedString, Functionalities) {
|
||||
using hash_type = entt::HashedString::hash_type;
|
||||
|
||||
const char *bar = "bar";
|
||||
|
||||
auto fooHs = entt::HashedString{"foo"};
|
||||
auto barHs = entt::HashedString{bar};
|
||||
|
||||
ASSERT_NE(static_cast<hash_type>(fooHs), static_cast<hash_type>(barHs));
|
||||
ASSERT_EQ(static_cast<const char *>(fooHs), "foo");
|
||||
ASSERT_EQ(static_cast<const char *>(barHs), bar);
|
||||
|
||||
ASSERT_TRUE(fooHs == fooHs);
|
||||
ASSERT_FALSE(fooHs == barHs);
|
||||
|
||||
entt::HashedString hs{"foobar"};
|
||||
|
||||
ASSERT_EQ(static_cast<hash_type>(hs), 0x85944171f73967e8);
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
#include <type_traits>
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/core/ident.hpp>
|
||||
|
||||
@@ -24,3 +25,9 @@ TEST(Identifier, Uniqueness) {
|
||||
SUCCEED();
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Identifier, SingleType) {
|
||||
constexpr auto ID = entt::ident<A>;
|
||||
std::integral_constant<decltype(ID)::identifier_type, ID.get()> ic;
|
||||
(void)ic;
|
||||
}
|
||||
|
||||
57
test/entt/entity/actor.cpp
Normal file
57
test/entt/entity/actor.cpp
Normal file
@@ -0,0 +1,57 @@
|
||||
#include <functional>
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/entity/actor.hpp>
|
||||
#include <entt/entity/registry.hpp>
|
||||
|
||||
struct TestActor: entt::DefaultActor<unsigned int> {
|
||||
using entt::DefaultActor<unsigned int>::DefaultActor;
|
||||
void update(unsigned int) {}
|
||||
};
|
||||
|
||||
struct Position final {};
|
||||
struct Velocity final {};
|
||||
|
||||
TEST(Actor, Functionalities) {
|
||||
entt::DefaultRegistry registry;
|
||||
TestActor *actor = new TestActor{registry};
|
||||
const auto &cactor = *actor;
|
||||
|
||||
ASSERT_EQ(®istry, &actor->registry());
|
||||
ASSERT_EQ(®istry, &cactor.registry());
|
||||
ASSERT_TRUE(registry.empty<Position>());
|
||||
ASSERT_TRUE(registry.empty<Velocity>());
|
||||
ASSERT_FALSE(registry.empty());
|
||||
ASSERT_FALSE(actor->has<Position>());
|
||||
ASSERT_FALSE(actor->has<Velocity>());
|
||||
|
||||
const auto &position = actor->set<Position>();
|
||||
|
||||
ASSERT_EQ(&position, &actor->get<Position>());
|
||||
ASSERT_EQ(&position, &cactor.get<Position>());
|
||||
ASSERT_FALSE(registry.empty<Position>());
|
||||
ASSERT_TRUE(registry.empty<Velocity>());
|
||||
ASSERT_FALSE(registry.empty());
|
||||
ASSERT_TRUE(actor->has<Position>());
|
||||
ASSERT_FALSE(actor->has<Velocity>());
|
||||
|
||||
actor->unset<Position>();
|
||||
|
||||
ASSERT_TRUE(registry.empty<Position>());
|
||||
ASSERT_TRUE(registry.empty<Velocity>());
|
||||
ASSERT_FALSE(registry.empty());
|
||||
ASSERT_FALSE(actor->has<Position>());
|
||||
ASSERT_FALSE(actor->has<Velocity>());
|
||||
|
||||
actor->set<Position>();
|
||||
actor->set<Velocity>();
|
||||
|
||||
ASSERT_FALSE(registry.empty());
|
||||
ASSERT_FALSE(registry.empty<Position>());
|
||||
ASSERT_FALSE(registry.empty<Velocity>());
|
||||
|
||||
delete actor;
|
||||
|
||||
ASSERT_TRUE(registry.empty());
|
||||
ASSERT_TRUE(registry.empty<Position>());
|
||||
ASSERT_TRUE(registry.empty<Velocity>());
|
||||
}
|
||||
@@ -1,687 +0,0 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <iostream>
|
||||
#include <cstddef>
|
||||
#include <chrono>
|
||||
#include <vector>
|
||||
#include <entt/entity/registry.hpp>
|
||||
|
||||
struct Position {
|
||||
uint64_t x;
|
||||
uint64_t y;
|
||||
};
|
||||
|
||||
struct Velocity {
|
||||
uint64_t x;
|
||||
uint64_t y;
|
||||
};
|
||||
|
||||
template<std::size_t>
|
||||
struct Comp {};
|
||||
|
||||
struct Timer final {
|
||||
Timer(): start{std::chrono::system_clock::now()} {}
|
||||
|
||||
void elapsed() {
|
||||
auto now = std::chrono::system_clock::now();
|
||||
std::cout << std::chrono::duration<double>(now - start).count() << " seconds" << std::endl;
|
||||
}
|
||||
|
||||
private:
|
||||
std::chrono::time_point<std::chrono::system_clock> start;
|
||||
};
|
||||
|
||||
TEST(DefaultRegistry, Construct) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
std::cout << "Constructing 10000000 entities" << std::endl;
|
||||
|
||||
Timer timer;
|
||||
|
||||
for (uint64_t i = 0; i < 10000000L; i++) {
|
||||
registry.create();
|
||||
}
|
||||
|
||||
timer.elapsed();
|
||||
registry.reset();
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, Destroy) {
|
||||
entt::DefaultRegistry registry;
|
||||
std::vector<entt::DefaultRegistry::entity_type> entities{};
|
||||
|
||||
std::cout << "Destroying 10000000 entities" << std::endl;
|
||||
|
||||
for (uint64_t i = 0; i < 10000000L; i++) {
|
||||
entities.push_back(registry.create());
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
|
||||
for (auto entity: entities) {
|
||||
registry.destroy(entity);
|
||||
}
|
||||
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, IterateCreateDeleteSingleComponent) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
std::cout << "Looping 10000 times creating and deleting a random number of entities" << std::endl;
|
||||
|
||||
Timer timer;
|
||||
|
||||
auto view = registry.view<Position>();
|
||||
|
||||
for(int i = 0; i < 10000; i++) {
|
||||
for(int j = 0; j < 10000; j++) {
|
||||
registry.create<Position>();
|
||||
}
|
||||
|
||||
for(auto entity: view) {
|
||||
if(rand() % 2 == 0) {
|
||||
registry.destroy(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
timer.elapsed();
|
||||
registry.reset();
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, IterateSingleComponent10M) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, one component" << std::endl;
|
||||
|
||||
for (uint64_t i = 0; i < 10000000L; i++) {
|
||||
registry.create<Position>();
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
|
||||
auto view = registry.view<Position>();
|
||||
|
||||
for(auto entity: view) {
|
||||
auto &position = view.get(entity);
|
||||
(void)position;
|
||||
}
|
||||
|
||||
timer.elapsed();
|
||||
registry.reset();
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, IterateTwoComponents10M) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, two components" << std::endl;
|
||||
|
||||
for (uint64_t i = 0; i < 10000000L; i++) {
|
||||
registry.create<Position, Velocity>();
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
|
||||
auto view = registry.view<Position, Velocity>();
|
||||
|
||||
for(auto entity: view) {
|
||||
auto &position = view.get<Position>(entity);
|
||||
auto &velocity = view.get<Velocity>(entity);
|
||||
(void)position;
|
||||
(void)velocity;
|
||||
}
|
||||
|
||||
timer.elapsed();
|
||||
registry.reset();
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, IterateTwoComponents10MHalf) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, two components, half of the entities have all the components" << std::endl;
|
||||
|
||||
for (uint64_t i = 0; i < 10000000L; i++) {
|
||||
auto entity = registry.create<Velocity>();
|
||||
if(i % 2) { registry.assign<Position>(entity); }
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
|
||||
auto view = registry.view<Position, Velocity>();
|
||||
|
||||
for(auto entity: view) {
|
||||
auto &position = view.get<Position>(entity);
|
||||
auto &velocity = view.get<Velocity>(entity);
|
||||
(void)position;
|
||||
(void)velocity;
|
||||
}
|
||||
|
||||
timer.elapsed();
|
||||
registry.reset();
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, IterateTwoComponents10MOne) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, two components, only one entity has all the components" << std::endl;
|
||||
|
||||
for (uint64_t i = 0; i < 10000000L; i++) {
|
||||
auto entity = registry.create<Velocity>();
|
||||
if(i == 5000000L) { registry.assign<Position>(entity); }
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
|
||||
auto view = registry.view<Position, Velocity>();
|
||||
|
||||
for(auto entity: view) {
|
||||
auto &position = view.get<Position>(entity);
|
||||
auto &velocity = view.get<Velocity>(entity);
|
||||
(void)position;
|
||||
(void)velocity;
|
||||
}
|
||||
|
||||
timer.elapsed();
|
||||
registry.reset();
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, IterateTwoComponentsPersistent10M) {
|
||||
entt::DefaultRegistry registry;
|
||||
registry.prepare<Position, Velocity>();
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, two components, persistent view" << std::endl;
|
||||
|
||||
for (uint64_t i = 0; i < 10000000L; i++) {
|
||||
registry.create<Position, Velocity>();
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
|
||||
auto view = registry.persistent<Position, Velocity>();
|
||||
|
||||
for(auto entity: view) {
|
||||
auto &position = view.get<Position>(entity);
|
||||
auto &velocity = view.get<Velocity>(entity);
|
||||
(void)position;
|
||||
(void)velocity;
|
||||
}
|
||||
|
||||
timer.elapsed();
|
||||
registry.reset();
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, IterateTwoComponentsPersistent10MHalf) {
|
||||
entt::DefaultRegistry registry;
|
||||
registry.prepare<Position, Velocity>();
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, two components, persistent view, half of the entities have all the components" << std::endl;
|
||||
|
||||
for (uint64_t i = 0; i < 10000000L; i++) {
|
||||
auto entity = registry.create<Velocity>();
|
||||
if(i % 2) { registry.assign<Position>(entity); }
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
|
||||
auto view = registry.persistent<Position, Velocity>();
|
||||
|
||||
for(auto entity: view) {
|
||||
auto &position = view.get<Position>(entity);
|
||||
auto &velocity = view.get<Velocity>(entity);
|
||||
(void)position;
|
||||
(void)velocity;
|
||||
}
|
||||
|
||||
timer.elapsed();
|
||||
registry.reset();
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, IterateTwoComponentsPersistent10MOne) {
|
||||
entt::DefaultRegistry registry;
|
||||
registry.prepare<Position, Velocity>();
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, two components, persistent view, only one entity has all the components" << std::endl;
|
||||
|
||||
for (uint64_t i = 0; i < 10000000L; i++) {
|
||||
auto entity = registry.create<Velocity>();
|
||||
if(i == 5000000L) { registry.assign<Position>(entity); }
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
|
||||
auto view = registry.persistent<Position, Velocity>();
|
||||
|
||||
for(auto entity: view) {
|
||||
auto &position = view.get<Position>(entity);
|
||||
auto &velocity = view.get<Velocity>(entity);
|
||||
(void)position;
|
||||
(void)velocity;
|
||||
}
|
||||
|
||||
timer.elapsed();
|
||||
registry.reset();
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, IterateSingleComponent50M) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
std::cout << "Iterating over 50000000 entities, one component" << std::endl;
|
||||
|
||||
for (uint64_t i = 0; i < 50000000L; i++) {
|
||||
registry.create<Position>();
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
|
||||
auto view = registry.view<Position>();
|
||||
|
||||
for(auto entity: view) {
|
||||
auto &position = view.get(entity);
|
||||
(void)position;
|
||||
}
|
||||
|
||||
timer.elapsed();
|
||||
registry.reset();
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, IterateTwoComponents50M) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
std::cout << "Iterating over 50000000 entities, two components" << std::endl;
|
||||
|
||||
for (uint64_t i = 0; i < 50000000L; i++) {
|
||||
registry.create<Position, Velocity>();
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
|
||||
auto view = registry.view<Position, Velocity>();
|
||||
|
||||
for(auto entity: view) {
|
||||
auto &position = view.get<Position>(entity);
|
||||
auto &velocity = view.get<Velocity>(entity);
|
||||
(void)position;
|
||||
(void)velocity;
|
||||
}
|
||||
|
||||
timer.elapsed();
|
||||
registry.reset();
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, IterateTwoComponentsPersistent50M) {
|
||||
entt::DefaultRegistry registry;
|
||||
registry.prepare<Position, Velocity>();
|
||||
|
||||
std::cout << "Iterating over 50000000 entities, two components, persistent view" << std::endl;
|
||||
|
||||
for (uint64_t i = 0; i < 50000000L; i++) {
|
||||
registry.create<Position, Velocity>();
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
|
||||
auto view = registry.persistent<Position, Velocity>();
|
||||
|
||||
for(auto entity: view) {
|
||||
auto &position = view.get<Position>(entity);
|
||||
auto &velocity = view.get<Velocity>(entity);
|
||||
(void)position;
|
||||
(void)velocity;
|
||||
}
|
||||
|
||||
timer.elapsed();
|
||||
registry.reset();
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, IterateFiveComponents10M) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, five components" << std::endl;
|
||||
|
||||
for (uint64_t i = 0; i < 10000000L; i++) {
|
||||
registry.create<Position, Velocity, Comp<1>, Comp<2>, Comp<3>>();
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
|
||||
auto view = registry.view<Position, Velocity, Comp<1>, Comp<2>, Comp<3>>();
|
||||
|
||||
for(auto entity: view) {
|
||||
auto &position = view.get<Position>(entity);
|
||||
auto &velocity = view.get<Velocity>(entity);
|
||||
auto &comp1 = view.get<Comp<1>>(entity);
|
||||
auto &comp2 = view.get<Comp<2>>(entity);
|
||||
auto &comp3 = view.get<Comp<3>>(entity);
|
||||
(void)position;
|
||||
(void)velocity;
|
||||
(void)comp1;
|
||||
(void)comp2;
|
||||
(void)comp3;
|
||||
}
|
||||
|
||||
timer.elapsed();
|
||||
registry.reset();
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, IterateTenComponents10M) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, ten components" << std::endl;
|
||||
|
||||
for (uint64_t i = 0; i < 10000000L; i++) {
|
||||
registry.create<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
|
||||
auto view = registry.view<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
|
||||
|
||||
for(auto entity: view) {
|
||||
auto &position = view.get<Position>(entity);
|
||||
auto &velocity = view.get<Velocity>(entity);
|
||||
auto &comp1 = view.get<Comp<1>>(entity);
|
||||
auto &comp2 = view.get<Comp<2>>(entity);
|
||||
auto &comp3 = view.get<Comp<3>>(entity);
|
||||
auto &comp4 = view.get<Comp<4>>(entity);
|
||||
auto &comp5 = view.get<Comp<5>>(entity);
|
||||
auto &comp6 = view.get<Comp<6>>(entity);
|
||||
auto &comp7 = view.get<Comp<7>>(entity);
|
||||
auto &comp8 = view.get<Comp<8>>(entity);
|
||||
(void)position;
|
||||
(void)velocity;
|
||||
(void)comp1;
|
||||
(void)comp2;
|
||||
(void)comp3;
|
||||
(void)comp4;
|
||||
(void)comp5;
|
||||
(void)comp6;
|
||||
(void)comp7;
|
||||
(void)comp8;
|
||||
}
|
||||
|
||||
timer.elapsed();
|
||||
registry.reset();
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, IterateTenComponents10MHalf) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, ten components, half of the entities have all the components" << std::endl;
|
||||
|
||||
for (uint64_t i = 0; i < 10000000L; i++) {
|
||||
auto entity = registry.create<Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
|
||||
if(i % 2) { registry.assign<Position>(entity); }
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
|
||||
auto view = registry.view<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
|
||||
|
||||
for(auto entity: view) {
|
||||
auto &position = view.get<Position>(entity);
|
||||
auto &velocity = view.get<Velocity>(entity);
|
||||
auto &comp1 = view.get<Comp<1>>(entity);
|
||||
auto &comp2 = view.get<Comp<2>>(entity);
|
||||
auto &comp3 = view.get<Comp<3>>(entity);
|
||||
auto &comp4 = view.get<Comp<4>>(entity);
|
||||
auto &comp5 = view.get<Comp<5>>(entity);
|
||||
auto &comp6 = view.get<Comp<6>>(entity);
|
||||
auto &comp7 = view.get<Comp<7>>(entity);
|
||||
auto &comp8 = view.get<Comp<8>>(entity);
|
||||
(void)position;
|
||||
(void)velocity;
|
||||
(void)comp1;
|
||||
(void)comp2;
|
||||
(void)comp3;
|
||||
(void)comp4;
|
||||
(void)comp5;
|
||||
(void)comp6;
|
||||
(void)comp7;
|
||||
(void)comp8;
|
||||
}
|
||||
|
||||
timer.elapsed();
|
||||
registry.reset();
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, IterateTenComponents10MOne) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, ten components, only one entity has all the components" << std::endl;
|
||||
|
||||
for (uint64_t i = 0; i < 10000000L; i++) {
|
||||
auto entity = registry.create<Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
|
||||
if(i == 5000000L) { registry.assign<Position>(entity); }
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
|
||||
auto view = registry.view<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
|
||||
|
||||
for(auto entity: view) {
|
||||
auto &position = view.get<Position>(entity);
|
||||
auto &velocity = view.get<Velocity>(entity);
|
||||
auto &comp1 = view.get<Comp<1>>(entity);
|
||||
auto &comp2 = view.get<Comp<2>>(entity);
|
||||
auto &comp3 = view.get<Comp<3>>(entity);
|
||||
auto &comp4 = view.get<Comp<4>>(entity);
|
||||
auto &comp5 = view.get<Comp<5>>(entity);
|
||||
auto &comp6 = view.get<Comp<6>>(entity);
|
||||
auto &comp7 = view.get<Comp<7>>(entity);
|
||||
auto &comp8 = view.get<Comp<8>>(entity);
|
||||
(void)position;
|
||||
(void)velocity;
|
||||
(void)comp1;
|
||||
(void)comp2;
|
||||
(void)comp3;
|
||||
(void)comp4;
|
||||
(void)comp5;
|
||||
(void)comp6;
|
||||
(void)comp7;
|
||||
(void)comp8;
|
||||
}
|
||||
|
||||
timer.elapsed();
|
||||
registry.reset();
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, IterateFiveComponentsPersistent10M) {
|
||||
entt::DefaultRegistry registry;
|
||||
registry.prepare<Position, Velocity, Comp<1>, Comp<2>, Comp<3>>();
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, five components, persistent view" << std::endl;
|
||||
|
||||
for (uint64_t i = 0; i < 10000000L; i++) {
|
||||
registry.create<Position, Velocity, Comp<1>, Comp<2>, Comp<3>>();
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
|
||||
auto view = registry.persistent<Position, Velocity, Comp<1>, Comp<2>, Comp<3>>();
|
||||
|
||||
for(auto entity: view) {
|
||||
auto &position = view.get<Position>(entity);
|
||||
auto &velocity = view.get<Velocity>(entity);
|
||||
auto &comp1 = view.get<Comp<1>>(entity);
|
||||
auto &comp2 = view.get<Comp<2>>(entity);
|
||||
auto &comp3 = view.get<Comp<3>>(entity);
|
||||
(void)position;
|
||||
(void)velocity;
|
||||
(void)comp1;
|
||||
(void)comp2;
|
||||
(void)comp3;
|
||||
}
|
||||
|
||||
timer.elapsed();
|
||||
registry.reset();
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, IterateTenComponentsPersistent10M) {
|
||||
entt::DefaultRegistry registry;
|
||||
registry.prepare<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, ten components, persistent view" << std::endl;
|
||||
|
||||
for (uint64_t i = 0; i < 10000000L; i++) {
|
||||
registry.create<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
|
||||
auto view = registry.persistent<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
|
||||
|
||||
for(auto entity: view) {
|
||||
auto &position = view.get<Position>(entity);
|
||||
auto &velocity = view.get<Velocity>(entity);
|
||||
auto &comp1 = view.get<Comp<1>>(entity);
|
||||
auto &comp2 = view.get<Comp<2>>(entity);
|
||||
auto &comp3 = view.get<Comp<3>>(entity);
|
||||
auto &comp4 = view.get<Comp<4>>(entity);
|
||||
auto &comp5 = view.get<Comp<5>>(entity);
|
||||
auto &comp6 = view.get<Comp<6>>(entity);
|
||||
auto &comp7 = view.get<Comp<7>>(entity);
|
||||
auto &comp8 = view.get<Comp<8>>(entity);
|
||||
(void)position;
|
||||
(void)velocity;
|
||||
(void)comp1;
|
||||
(void)comp2;
|
||||
(void)comp3;
|
||||
(void)comp4;
|
||||
(void)comp5;
|
||||
(void)comp6;
|
||||
(void)comp7;
|
||||
(void)comp8;
|
||||
}
|
||||
|
||||
timer.elapsed();
|
||||
registry.reset();
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, IterateTenComponentsPersistent10MHalf) {
|
||||
entt::DefaultRegistry registry;
|
||||
registry.prepare<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, ten components, persistent view, half of the entities have all the components" << std::endl;
|
||||
|
||||
for (uint64_t i = 0; i < 10000000L; i++) {
|
||||
auto entity = registry.create<Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
|
||||
if(i % 2) { registry.assign<Position>(entity); }
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
|
||||
auto view = registry.persistent<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
|
||||
|
||||
for(auto entity: view) {
|
||||
auto &position = view.get<Position>(entity);
|
||||
auto &velocity = view.get<Velocity>(entity);
|
||||
auto &comp1 = view.get<Comp<1>>(entity);
|
||||
auto &comp2 = view.get<Comp<2>>(entity);
|
||||
auto &comp3 = view.get<Comp<3>>(entity);
|
||||
auto &comp4 = view.get<Comp<4>>(entity);
|
||||
auto &comp5 = view.get<Comp<5>>(entity);
|
||||
auto &comp6 = view.get<Comp<6>>(entity);
|
||||
auto &comp7 = view.get<Comp<7>>(entity);
|
||||
auto &comp8 = view.get<Comp<8>>(entity);
|
||||
(void)position;
|
||||
(void)velocity;
|
||||
(void)comp1;
|
||||
(void)comp2;
|
||||
(void)comp3;
|
||||
(void)comp4;
|
||||
(void)comp5;
|
||||
(void)comp6;
|
||||
(void)comp7;
|
||||
(void)comp8;
|
||||
}
|
||||
|
||||
timer.elapsed();
|
||||
registry.reset();
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, IterateTenComponentsPersistent10MOne) {
|
||||
entt::DefaultRegistry registry;
|
||||
registry.prepare<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, ten components, persistent view, only one entity has all the components" << std::endl;
|
||||
|
||||
for (uint64_t i = 0; i < 10000000L; i++) {
|
||||
auto entity = registry.create<Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
|
||||
if(i == 5000000L) { registry.assign<Position>(entity); }
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
|
||||
auto view = registry.persistent<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
|
||||
|
||||
for(auto entity: view) {
|
||||
auto &position = view.get<Position>(entity);
|
||||
auto &velocity = view.get<Velocity>(entity);
|
||||
auto &comp1 = view.get<Comp<1>>(entity);
|
||||
auto &comp2 = view.get<Comp<2>>(entity);
|
||||
auto &comp3 = view.get<Comp<3>>(entity);
|
||||
auto &comp4 = view.get<Comp<4>>(entity);
|
||||
auto &comp5 = view.get<Comp<5>>(entity);
|
||||
auto &comp6 = view.get<Comp<6>>(entity);
|
||||
auto &comp7 = view.get<Comp<7>>(entity);
|
||||
auto &comp8 = view.get<Comp<8>>(entity);
|
||||
(void)position;
|
||||
(void)velocity;
|
||||
(void)comp1;
|
||||
(void)comp2;
|
||||
(void)comp3;
|
||||
(void)comp4;
|
||||
(void)comp5;
|
||||
(void)comp6;
|
||||
(void)comp7;
|
||||
(void)comp8;
|
||||
}
|
||||
|
||||
timer.elapsed();
|
||||
registry.reset();
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, SortSingle) {
|
||||
entt::DefaultRegistry registry;
|
||||
std::vector<entt::DefaultRegistry::entity_type> entities{};
|
||||
|
||||
std::cout << "Sort 150000 entities, one component" << std::endl;
|
||||
|
||||
for (uint64_t i = 0; i < 150000L; i++) {
|
||||
auto entity = registry.create();
|
||||
entities.push_back(entity);
|
||||
registry.assign<Position>(entity, i, i);
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
|
||||
registry.sort<Position>([®istry](const auto &lhs, const auto &rhs) {
|
||||
return lhs.x < rhs.x && lhs.y < rhs.y;
|
||||
});
|
||||
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, SortMulti) {
|
||||
entt::DefaultRegistry registry;
|
||||
std::vector<entt::DefaultRegistry::entity_type> entities{};
|
||||
|
||||
std::cout << "Sort 150000 entities, two components" << std::endl;
|
||||
|
||||
for (uint64_t i = 0; i < 150000L; i++) {
|
||||
auto entity = registry.create();
|
||||
entities.push_back(entity);
|
||||
registry.assign<Position>(entity, i, i);
|
||||
registry.assign<Velocity>(entity, i, i);
|
||||
}
|
||||
|
||||
registry.sort<Position>([®istry](const auto &lhs, const auto &rhs) {
|
||||
return lhs.x < rhs.x && lhs.y < rhs.y;
|
||||
});
|
||||
|
||||
Timer timer;
|
||||
|
||||
registry.sort<Velocity, Position>();
|
||||
|
||||
timer.elapsed();
|
||||
}
|
||||
@@ -17,6 +17,9 @@ TEST(DefaultRegistry, Functionalities) {
|
||||
auto e1 = registry.create();
|
||||
auto e2 = registry.create<int, char>();
|
||||
|
||||
ASSERT_TRUE(registry.has<>(e1));
|
||||
ASSERT_TRUE(registry.has<>(e2));
|
||||
|
||||
ASSERT_EQ(registry.capacity(), entt::DefaultRegistry::size_type{2});
|
||||
ASSERT_EQ(registry.size<int>(), entt::DefaultRegistry::size_type{1});
|
||||
ASSERT_EQ(registry.size<char>(), entt::DefaultRegistry::size_type{1});
|
||||
@@ -121,18 +124,186 @@ TEST(DefaultRegistry, Functionalities) {
|
||||
ASSERT_TRUE(registry.empty<int>());
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, Each) {
|
||||
entt::DefaultRegistry registry;
|
||||
entt::DefaultRegistry::size_type tot;
|
||||
entt::DefaultRegistry::size_type match;
|
||||
|
||||
registry.create<int>();
|
||||
registry.create<int>();
|
||||
|
||||
tot = 0u;
|
||||
match = 0u;
|
||||
|
||||
registry.each([&](auto entity) {
|
||||
if(registry.has<int>(entity)) { ++match; }
|
||||
registry.create();
|
||||
++tot;
|
||||
});
|
||||
|
||||
ASSERT_EQ(tot, 2u);
|
||||
ASSERT_EQ(match, 2u);
|
||||
|
||||
tot = 0u;
|
||||
match = 0u;
|
||||
|
||||
registry.each([&](auto entity) {
|
||||
if(registry.has<int>(entity)) { ++match; }
|
||||
registry.destroy(entity);
|
||||
++tot;
|
||||
});
|
||||
|
||||
ASSERT_EQ(tot, 4u);
|
||||
ASSERT_EQ(match, 2u);
|
||||
|
||||
tot = 0u;
|
||||
match = 0u;
|
||||
|
||||
registry.each([&](auto entity) {
|
||||
if(registry.has<int>(entity)) { ++match; }
|
||||
++tot;
|
||||
});
|
||||
|
||||
ASSERT_EQ(tot, 4u);
|
||||
ASSERT_EQ(match, 0u);
|
||||
}
|
||||
|
||||
|
||||
TEST(DefaultRegistry, Types) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
ASSERT_EQ(registry.tag<int>(), registry.tag<int>());
|
||||
ASSERT_EQ(registry.component<int>(), registry.component<int>());
|
||||
|
||||
ASSERT_NE(registry.tag<int>(), registry.tag<double>());
|
||||
ASSERT_NE(registry.component<int>(), registry.component<double>());
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, CreateDestroyEntities) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
auto pre = registry.create<double>();
|
||||
registry.destroy(pre);
|
||||
auto post = registry.create<double>();
|
||||
|
||||
ASSERT_FALSE(registry.valid(pre));
|
||||
ASSERT_TRUE(registry.valid(post));
|
||||
ASSERT_NE(registry.version(pre), registry.version(post));
|
||||
ASSERT_EQ(registry.current(pre), registry.current(post));
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, AttachRemoveTags) {
|
||||
entt::DefaultRegistry registry;
|
||||
const auto &cregistry = registry;
|
||||
|
||||
ASSERT_FALSE(registry.has<int>());
|
||||
|
||||
auto entity = registry.create();
|
||||
registry.attach<int>(entity, 42);
|
||||
|
||||
ASSERT_TRUE(registry.has<int>());
|
||||
ASSERT_EQ(registry.get<int>(), 42);
|
||||
ASSERT_EQ(cregistry.get<int>(), 42);
|
||||
ASSERT_EQ(registry.attachee<int>(), entity);
|
||||
|
||||
registry.remove<int>();
|
||||
|
||||
ASSERT_FALSE(registry.has<int>());
|
||||
|
||||
registry.attach<int>(entity, 42);
|
||||
registry.destroy(entity);
|
||||
|
||||
ASSERT_FALSE(registry.has<int>());
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, StandardViews) {
|
||||
entt::DefaultRegistry registry;
|
||||
auto mview = registry.view<int, char>();
|
||||
auto iview = registry.view<int>();
|
||||
auto cview = registry.view<char>();
|
||||
|
||||
registry.create(0, 'c');
|
||||
registry.create(0);
|
||||
registry.create(0, 'c');
|
||||
|
||||
ASSERT_EQ(iview.size(), decltype(iview)::size_type{3});
|
||||
ASSERT_EQ(cview.size(), decltype(cview)::size_type{2});
|
||||
|
||||
decltype(mview)::size_type cnt{0};
|
||||
mview.each([&cnt](auto...) { ++cnt; });
|
||||
|
||||
ASSERT_EQ(cnt, decltype(mview)::size_type{2});
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, PersistentViews) {
|
||||
entt::DefaultRegistry registry;
|
||||
auto view = registry.persistent<int, char>();
|
||||
|
||||
ASSERT_TRUE((registry.contains<int, char>()));
|
||||
ASSERT_FALSE((registry.contains<int, double>()));
|
||||
|
||||
registry.prepare<int, double>();
|
||||
|
||||
ASSERT_TRUE((registry.contains<int, double>()));
|
||||
|
||||
registry.discard<int, double>();
|
||||
|
||||
ASSERT_FALSE((registry.contains<int, double>()));
|
||||
|
||||
registry.create(0, 'c');
|
||||
registry.create(0);
|
||||
registry.create(0, 'c');
|
||||
|
||||
decltype(view)::size_type cnt{0};
|
||||
view.each([&cnt](auto...) { ++cnt; });
|
||||
|
||||
ASSERT_EQ(cnt, decltype(view)::size_type{2});
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, CleanStandardViewsAfterReset) {
|
||||
entt::DefaultRegistry registry;
|
||||
auto view = registry.view<int>();
|
||||
registry.create(0);
|
||||
|
||||
ASSERT_EQ(view.size(), entt::DefaultRegistry::size_type{1});
|
||||
|
||||
registry.reset();
|
||||
|
||||
ASSERT_EQ(view.size(), entt::DefaultRegistry::size_type{0});
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, CleanPersistentViewsAfterReset) {
|
||||
entt::DefaultRegistry registry;
|
||||
auto view = registry.persistent<int, char>();
|
||||
registry.create(0, 'c');
|
||||
|
||||
ASSERT_EQ(view.size(), entt::DefaultRegistry::size_type{1});
|
||||
|
||||
registry.reset();
|
||||
|
||||
ASSERT_EQ(view.size(), entt::DefaultRegistry::size_type{0});
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, CleanTagsAfterReset) {
|
||||
entt::DefaultRegistry registry;
|
||||
auto entity = registry.create();
|
||||
registry.attach<int>(entity);
|
||||
|
||||
ASSERT_TRUE(registry.has<int>());
|
||||
|
||||
registry.reset();
|
||||
|
||||
ASSERT_FALSE(registry.has<int>());
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, SortSingle) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
auto e1 = registry.create();
|
||||
auto e2 = registry.create();
|
||||
auto e3 = registry.create();
|
||||
int val = 0;
|
||||
|
||||
auto val = 0;
|
||||
|
||||
registry.assign<int>(e1, val++);
|
||||
registry.assign<int>(e2, val++);
|
||||
registry.assign<int>(e3, val++);
|
||||
registry.create(val++);
|
||||
registry.create(val++);
|
||||
registry.create(val++);
|
||||
|
||||
for(auto entity: registry.view<int>()) {
|
||||
ASSERT_EQ(registry.get<int>(entity), --val);
|
||||
@@ -148,20 +319,12 @@ TEST(DefaultRegistry, SortSingle) {
|
||||
TEST(DefaultRegistry, SortMulti) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
auto e1 = registry.create();
|
||||
auto e2 = registry.create();
|
||||
auto e3 = registry.create();
|
||||
unsigned int uval = 0u;
|
||||
int ival = 0;
|
||||
|
||||
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++);
|
||||
registry.create(uval++, ival++);
|
||||
registry.create(uval++, ival++);
|
||||
registry.create(uval++, ival++);
|
||||
|
||||
for(auto entity: registry.view<unsigned int>()) {
|
||||
ASSERT_EQ(registry.get<unsigned int>(entity), --uval);
|
||||
|
||||
@@ -10,7 +10,9 @@ TEST(SparseSetNoType, Functionalities) {
|
||||
ASSERT_FALSE(set.has(0));
|
||||
ASSERT_FALSE(set.has(42));
|
||||
|
||||
ASSERT_EQ(set.construct(42), 0u);
|
||||
set.construct(42);
|
||||
|
||||
ASSERT_EQ(set.get(42), 0u);
|
||||
|
||||
ASSERT_FALSE(set.empty());
|
||||
ASSERT_EQ(set.size(), 1u);
|
||||
@@ -27,7 +29,9 @@ TEST(SparseSetNoType, Functionalities) {
|
||||
ASSERT_FALSE(set.has(0));
|
||||
ASSERT_FALSE(set.has(42));
|
||||
|
||||
ASSERT_EQ(set.construct(42), 0u);
|
||||
set.construct(42);
|
||||
|
||||
ASSERT_EQ(set.get(42), 0u);
|
||||
|
||||
set.reset();
|
||||
|
||||
@@ -45,9 +49,13 @@ TEST(SparseSetNoType, Functionalities) {
|
||||
TEST(SparseSetNoType, DataBeginEnd) {
|
||||
entt::SparseSet<unsigned int> set;
|
||||
|
||||
ASSERT_EQ(set.construct(3), 0u);
|
||||
ASSERT_EQ(set.construct(12), 1u);
|
||||
ASSERT_EQ(set.construct(42), 2u);
|
||||
set.construct(3);
|
||||
set.construct(12);
|
||||
set.construct(42);
|
||||
|
||||
ASSERT_EQ(set.get(3), 0u);
|
||||
ASSERT_EQ(set.get(12), 1u);
|
||||
ASSERT_EQ(set.get(42), 2u);
|
||||
|
||||
ASSERT_EQ(*(set.data() + 0u), 3u);
|
||||
ASSERT_EQ(*(set.data() + 1u), 12u);
|
||||
@@ -62,6 +70,12 @@ TEST(SparseSetNoType, DataBeginEnd) {
|
||||
ASSERT_EQ(begin, end);
|
||||
}
|
||||
|
||||
TEST(SparseSetWithType, AggregatesMustWork) {
|
||||
struct AggregateType { int value; };
|
||||
// the goal of this test is to enforce the requirements for aggregate types
|
||||
entt::SparseSet<unsigned int, AggregateType>{}.construct(0, 42);
|
||||
}
|
||||
|
||||
TEST(SparseSetWithType, Functionalities) {
|
||||
entt::SparseSet<unsigned int, int> set;
|
||||
|
||||
@@ -71,8 +85,9 @@ TEST(SparseSetWithType, Functionalities) {
|
||||
ASSERT_FALSE(set.has(0));
|
||||
ASSERT_FALSE(set.has(42));
|
||||
|
||||
ASSERT_EQ(set.construct(42, 3), 3);
|
||||
set.construct(42, 3);
|
||||
|
||||
ASSERT_EQ(set.get(42), 3);
|
||||
ASSERT_FALSE(set.empty());
|
||||
ASSERT_EQ(set.size(), 1u);
|
||||
ASSERT_NE(set.begin(), set.end());
|
||||
@@ -88,7 +103,9 @@ TEST(SparseSetWithType, Functionalities) {
|
||||
ASSERT_FALSE(set.has(0));
|
||||
ASSERT_FALSE(set.has(42));
|
||||
|
||||
ASSERT_EQ(set.construct(42, 12), 12);
|
||||
set.construct(42, 12);
|
||||
|
||||
ASSERT_EQ(set.get(42), 12);
|
||||
|
||||
set.reset();
|
||||
|
||||
@@ -106,9 +123,13 @@ TEST(SparseSetWithType, Functionalities) {
|
||||
TEST(SparseSetWithType, RawBeginEnd) {
|
||||
entt::SparseSet<unsigned int, int> set;
|
||||
|
||||
ASSERT_EQ(set.construct(3, 3), 3);
|
||||
ASSERT_EQ(set.construct(12, 6), 6);
|
||||
ASSERT_EQ(set.construct(42, 9), 9);
|
||||
set.construct(3, 3);
|
||||
set.construct(12, 6);
|
||||
set.construct(42, 9);
|
||||
|
||||
ASSERT_EQ(set.get(3), 3);
|
||||
ASSERT_EQ(set.get(12), 6);
|
||||
ASSERT_EQ(set.get(42), 9);
|
||||
|
||||
ASSERT_EQ(*(set.raw() + 0u), 3);
|
||||
ASSERT_EQ(*(set.raw() + 1u), 6);
|
||||
@@ -126,14 +147,20 @@ TEST(SparseSetWithType, RawBeginEnd) {
|
||||
TEST(SparseSetWithType, SortOrdered) {
|
||||
entt::SparseSet<unsigned int, int> 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.construct(12, 12);
|
||||
set.construct(42, 9);
|
||||
set.construct(7, 6);
|
||||
set.construct(3, 3);
|
||||
set.construct(9, 1);
|
||||
|
||||
set.sort([&set](auto lhs, auto rhs) {
|
||||
return set.get(lhs) < set.get(rhs);
|
||||
ASSERT_EQ(set.get(12), 12);
|
||||
ASSERT_EQ(set.get(42), 9);
|
||||
ASSERT_EQ(set.get(7), 6);
|
||||
ASSERT_EQ(set.get(3), 3);
|
||||
ASSERT_EQ(set.get(9), 1);
|
||||
|
||||
set.sort([](auto lhs, auto rhs) {
|
||||
return lhs < rhs;
|
||||
});
|
||||
|
||||
ASSERT_EQ(*(set.raw() + 0u), 12);
|
||||
@@ -156,14 +183,20 @@ TEST(SparseSetWithType, SortOrdered) {
|
||||
TEST(SparseSetWithType, SortReverse) {
|
||||
entt::SparseSet<unsigned int, int> 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.construct(12, 1);
|
||||
set.construct(42, 3);
|
||||
set.construct(7, 6);
|
||||
set.construct(3, 9);
|
||||
set.construct(9, 12);
|
||||
|
||||
set.sort([&set](auto lhs, auto rhs) {
|
||||
return set.get(lhs) < set.get(rhs);
|
||||
ASSERT_EQ(set.get(12), 1);
|
||||
ASSERT_EQ(set.get(42), 3);
|
||||
ASSERT_EQ(set.get(7), 6);
|
||||
ASSERT_EQ(set.get(3), 9);
|
||||
ASSERT_EQ(set.get(9), 12);
|
||||
|
||||
set.sort([](auto lhs, auto rhs) {
|
||||
return lhs < rhs;
|
||||
});
|
||||
|
||||
ASSERT_EQ(*(set.raw() + 0u), 12);
|
||||
@@ -186,14 +219,20 @@ TEST(SparseSetWithType, SortReverse) {
|
||||
TEST(SparseSetWithType, SortUnordered) {
|
||||
entt::SparseSet<unsigned int, int> 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.construct(12, 6);
|
||||
set.construct(42, 3);
|
||||
set.construct(7, 1);
|
||||
set.construct(3, 9);
|
||||
set.construct(9, 12);
|
||||
|
||||
set.sort([&set](auto lhs, auto rhs) {
|
||||
return set.get(lhs) < set.get(rhs);
|
||||
ASSERT_EQ(set.get(12), 6);
|
||||
ASSERT_EQ(set.get(42), 3);
|
||||
ASSERT_EQ(set.get(7), 1);
|
||||
ASSERT_EQ(set.get(3), 9);
|
||||
ASSERT_EQ(set.get(9), 12);
|
||||
|
||||
set.sort([](auto lhs, auto rhs) {
|
||||
return lhs < rhs;
|
||||
});
|
||||
|
||||
ASSERT_EQ(*(set.raw() + 0u), 12);
|
||||
@@ -218,9 +257,13 @@ TEST(SparseSetWithType, RespectDisjoint) {
|
||||
entt::SparseSet<unsigned int, int> rhs;
|
||||
const auto &clhs = lhs;
|
||||
|
||||
ASSERT_EQ(lhs.construct(3, 3), 3);
|
||||
ASSERT_EQ(lhs.construct(12, 6), 6);
|
||||
ASSERT_EQ(lhs.construct(42, 9), 9);
|
||||
lhs.construct(3, 3);
|
||||
lhs.construct(12, 6);
|
||||
lhs.construct(42, 9);
|
||||
|
||||
ASSERT_EQ(lhs.get(3), 3);
|
||||
ASSERT_EQ(lhs.get(12), 6);
|
||||
ASSERT_EQ(lhs.get(42), 9);
|
||||
|
||||
lhs.respect(rhs);
|
||||
|
||||
@@ -242,10 +285,15 @@ TEST(SparseSetWithType, RespectOverlap) {
|
||||
entt::SparseSet<unsigned int, int> rhs;
|
||||
const auto &clhs = lhs;
|
||||
|
||||
ASSERT_EQ(lhs.construct(3, 3), 3);
|
||||
ASSERT_EQ(lhs.construct(12, 6), 6);
|
||||
ASSERT_EQ(lhs.construct(42, 9), 9);
|
||||
ASSERT_EQ(rhs.construct(12, 6), 6);
|
||||
lhs.construct(3, 3);
|
||||
lhs.construct(12, 6);
|
||||
lhs.construct(42, 9);
|
||||
rhs.construct(12, 6);
|
||||
|
||||
ASSERT_EQ(lhs.get(3), 3);
|
||||
ASSERT_EQ(lhs.get(12), 6);
|
||||
ASSERT_EQ(lhs.get(42), 9);
|
||||
ASSERT_EQ(rhs.get(12), 6);
|
||||
|
||||
lhs.respect(rhs);
|
||||
|
||||
@@ -266,18 +314,31 @@ TEST(SparseSetWithType, RespectOrdered) {
|
||||
entt::SparseSet<unsigned int, int> lhs;
|
||||
entt::SparseSet<unsigned int, int> 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);
|
||||
lhs.construct(1, 0);
|
||||
lhs.construct(2, 0);
|
||||
lhs.construct(3, 0);
|
||||
lhs.construct(4, 0);
|
||||
lhs.construct(5, 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);
|
||||
ASSERT_EQ(lhs.get(1), 0);
|
||||
ASSERT_EQ(lhs.get(2), 0);
|
||||
ASSERT_EQ(lhs.get(3), 0);
|
||||
ASSERT_EQ(lhs.get(4), 0);
|
||||
ASSERT_EQ(lhs.get(5), 0);
|
||||
|
||||
rhs.construct(6, 0);
|
||||
rhs.construct(1, 0);
|
||||
rhs.construct(2, 0);
|
||||
rhs.construct(3, 0);
|
||||
rhs.construct(4, 0);
|
||||
rhs.construct(5, 0);
|
||||
|
||||
ASSERT_EQ(rhs.get(6), 0);
|
||||
ASSERT_EQ(rhs.get(1), 0);
|
||||
ASSERT_EQ(rhs.get(2), 0);
|
||||
ASSERT_EQ(rhs.get(3), 0);
|
||||
ASSERT_EQ(rhs.get(4), 0);
|
||||
ASSERT_EQ(rhs.get(5), 0);
|
||||
|
||||
rhs.respect(lhs);
|
||||
|
||||
@@ -299,18 +360,31 @@ TEST(SparseSetWithType, RespectReverse) {
|
||||
entt::SparseSet<unsigned int, int> lhs;
|
||||
entt::SparseSet<unsigned int, int> 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);
|
||||
lhs.construct(1, 0);
|
||||
lhs.construct(2, 0);
|
||||
lhs.construct(3, 0);
|
||||
lhs.construct(4, 0);
|
||||
lhs.construct(5, 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);
|
||||
ASSERT_EQ(lhs.get(1), 0);
|
||||
ASSERT_EQ(lhs.get(2), 0);
|
||||
ASSERT_EQ(lhs.get(3), 0);
|
||||
ASSERT_EQ(lhs.get(4), 0);
|
||||
ASSERT_EQ(lhs.get(5), 0);
|
||||
|
||||
rhs.construct(5, 0);
|
||||
rhs.construct(4, 0);
|
||||
rhs.construct(3, 0);
|
||||
rhs.construct(2, 0);
|
||||
rhs.construct(1, 0);
|
||||
rhs.construct(6, 0);
|
||||
|
||||
ASSERT_EQ(rhs.get(5), 0);
|
||||
ASSERT_EQ(rhs.get(4), 0);
|
||||
ASSERT_EQ(rhs.get(3), 0);
|
||||
ASSERT_EQ(rhs.get(2), 0);
|
||||
ASSERT_EQ(rhs.get(1), 0);
|
||||
ASSERT_EQ(rhs.get(6), 0);
|
||||
|
||||
rhs.respect(lhs);
|
||||
|
||||
@@ -332,18 +406,31 @@ TEST(SparseSetWithType, RespectUnordered) {
|
||||
entt::SparseSet<unsigned int, int> lhs;
|
||||
entt::SparseSet<unsigned int, int> 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);
|
||||
lhs.construct(1, 0);
|
||||
lhs.construct(2, 0);
|
||||
lhs.construct(3, 0);
|
||||
lhs.construct(4, 0);
|
||||
lhs.construct(5, 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);
|
||||
ASSERT_EQ(lhs.get(1), 0);
|
||||
ASSERT_EQ(lhs.get(2), 0);
|
||||
ASSERT_EQ(lhs.get(3), 0);
|
||||
ASSERT_EQ(lhs.get(4), 0);
|
||||
ASSERT_EQ(lhs.get(5), 0);
|
||||
|
||||
rhs.construct(3, 0);
|
||||
rhs.construct(2, 0);
|
||||
rhs.construct(6, 0);
|
||||
rhs.construct(1, 0);
|
||||
rhs.construct(4, 0);
|
||||
rhs.construct(5, 0);
|
||||
|
||||
ASSERT_EQ(rhs.get(3), 0);
|
||||
ASSERT_EQ(rhs.get(2), 0);
|
||||
ASSERT_EQ(rhs.get(6), 0);
|
||||
ASSERT_EQ(rhs.get(1), 0);
|
||||
ASSERT_EQ(rhs.get(4), 0);
|
||||
ASSERT_EQ(rhs.get(5), 0);
|
||||
|
||||
rhs.respect(lhs);
|
||||
|
||||
|
||||
@@ -56,6 +56,25 @@ TEST(View, SingleComponentEmpty) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST(View, SingleComponentEach) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
registry.create<int, char>();
|
||||
registry.create<int, char>();
|
||||
|
||||
auto view = registry.view<int>();
|
||||
const auto &cview = static_cast<const decltype(view) &>(view);
|
||||
std::size_t cnt = 0;
|
||||
|
||||
view.each([&cnt](auto, int &) { ++cnt; });
|
||||
|
||||
ASSERT_EQ(cnt, std::size_t{2});
|
||||
|
||||
cview.each([&cnt](auto, const int &) { --cnt; });
|
||||
|
||||
ASSERT_EQ(cnt, std::size_t{0});
|
||||
}
|
||||
|
||||
TEST(View, MultipleComponent) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
@@ -98,7 +117,26 @@ TEST(View, MultipleComponentEmpty) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST(PersistentView, MultipleComponentPrepare) {
|
||||
TEST(View, MultipleComponentEach) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
registry.create<int, char>();
|
||||
registry.create<int, char>();
|
||||
|
||||
auto view = registry.view<int, char>();
|
||||
const auto &cview = static_cast<const decltype(view) &>(view);
|
||||
std::size_t cnt = 0;
|
||||
|
||||
view.each([&cnt](auto, int &, char &) { ++cnt; });
|
||||
|
||||
ASSERT_EQ(cnt, std::size_t{2});
|
||||
|
||||
cview.each([&cnt](auto, const int &, const char &) { --cnt; });
|
||||
|
||||
ASSERT_EQ(cnt, std::size_t{0});
|
||||
}
|
||||
|
||||
TEST(PersistentView, Prepare) {
|
||||
entt::DefaultRegistry registry;
|
||||
registry.prepare<int, char>();
|
||||
|
||||
@@ -137,7 +175,7 @@ TEST(PersistentView, MultipleComponentPrepare) {
|
||||
ASSERT_EQ(view.begin(), view.end());
|
||||
}
|
||||
|
||||
TEST(PersistentView, MultipleComponentNoPrepare) {
|
||||
TEST(PersistentView, NoPrepare) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
auto e1 = registry.create<char>();
|
||||
@@ -175,7 +213,7 @@ TEST(PersistentView, MultipleComponentNoPrepare) {
|
||||
ASSERT_EQ(view.begin(), view.end());
|
||||
}
|
||||
|
||||
TEST(PersistentView, MultipleComponentEmpty) {
|
||||
TEST(PersistentView, Empty) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
registry.create<double, int, float>();
|
||||
@@ -192,6 +230,26 @@ TEST(PersistentView, MultipleComponentEmpty) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST(PersistentView, Each) {
|
||||
entt::DefaultRegistry registry;
|
||||
registry.prepare<int, char>();
|
||||
|
||||
registry.create<int, char>();
|
||||
registry.create<int, char>();
|
||||
|
||||
auto view = registry.persistent<int, char>();
|
||||
const auto &cview = static_cast<const decltype(view) &>(view);
|
||||
std::size_t cnt = 0;
|
||||
|
||||
view.each([&cnt](auto, int &, char &) { ++cnt; });
|
||||
|
||||
ASSERT_EQ(cnt, std::size_t{2});
|
||||
|
||||
cview.each([&cnt](auto, const int &, const char &) { --cnt; });
|
||||
|
||||
ASSERT_EQ(cnt, std::size_t{0});
|
||||
}
|
||||
|
||||
TEST(PersistentView, Sort) {
|
||||
entt::DefaultRegistry registry;
|
||||
registry.prepare<int, unsigned int>();
|
||||
|
||||
49
test/entt/locator/locator.cpp
Normal file
49
test/entt/locator/locator.cpp
Normal file
@@ -0,0 +1,49 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/locator/locator.hpp>
|
||||
|
||||
struct A {};
|
||||
|
||||
struct B {
|
||||
virtual void f(bool) = 0;
|
||||
bool check{false};
|
||||
};
|
||||
|
||||
struct D: B {
|
||||
D(int): B{} {}
|
||||
void f(bool b) override { check = b; }
|
||||
};
|
||||
|
||||
TEST(ServiceLocator, Functionalities) {
|
||||
using entt::ServiceLocator;
|
||||
|
||||
ASSERT_TRUE(ServiceLocator<A>::empty());
|
||||
ASSERT_TRUE(ServiceLocator<B>::empty());
|
||||
|
||||
ServiceLocator<A>::set();
|
||||
|
||||
ASSERT_FALSE(ServiceLocator<A>::empty());
|
||||
ASSERT_TRUE(ServiceLocator<B>::empty());
|
||||
|
||||
ServiceLocator<A>::reset();
|
||||
|
||||
ASSERT_TRUE(ServiceLocator<A>::empty());
|
||||
ASSERT_TRUE(ServiceLocator<B>::empty());
|
||||
|
||||
ServiceLocator<A>::set(std::make_shared<A>());
|
||||
|
||||
ASSERT_FALSE(ServiceLocator<A>::empty());
|
||||
ASSERT_TRUE(ServiceLocator<B>::empty());
|
||||
|
||||
ServiceLocator<B>::set<D>(42);
|
||||
|
||||
ASSERT_FALSE(ServiceLocator<A>::empty());
|
||||
ASSERT_FALSE(ServiceLocator<B>::empty());
|
||||
|
||||
ServiceLocator<B>::get().lock()->f(!ServiceLocator<B>::get().lock()->check);
|
||||
|
||||
ASSERT_TRUE(ServiceLocator<B>::get().lock()->check);
|
||||
|
||||
ServiceLocator<B>::ref().f(!ServiceLocator<B>::get().lock()->check);
|
||||
|
||||
ASSERT_FALSE(ServiceLocator<B>::get().lock()->check);
|
||||
}
|
||||
163
test/entt/process/process.cpp
Normal file
163
test/entt/process/process.cpp
Normal file
@@ -0,0 +1,163 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <cstdint>
|
||||
#include <entt/process/process.hpp>
|
||||
|
||||
struct FakeProcess: entt::Process<FakeProcess, int> {
|
||||
using process_type = entt::Process<FakeProcess, int>;
|
||||
|
||||
void succeed() noexcept { process_type::succeed(); }
|
||||
void fail() noexcept { process_type::fail(); }
|
||||
void pause() noexcept { process_type::pause(); }
|
||||
void unpause() noexcept { process_type::unpause(); }
|
||||
|
||||
void init() { initInvoked = true; }
|
||||
void update(delta_type) { updateInvoked = true; }
|
||||
void succeeded() { succeededInvoked = true; }
|
||||
void failed() { failedInvoked = true; }
|
||||
void aborted() { abortedInvoked = true; }
|
||||
|
||||
bool initInvoked{false};
|
||||
bool updateInvoked{false};
|
||||
bool succeededInvoked{false};
|
||||
bool failedInvoked{false};
|
||||
bool abortedInvoked{false};
|
||||
};
|
||||
|
||||
TEST(Process, Basics) {
|
||||
FakeProcess process;
|
||||
|
||||
ASSERT_FALSE(process.alive());
|
||||
ASSERT_FALSE(process.dead());
|
||||
ASSERT_FALSE(process.paused());
|
||||
|
||||
process.succeed();
|
||||
process.fail();
|
||||
process.abort();
|
||||
process.pause();
|
||||
process.unpause();
|
||||
|
||||
ASSERT_FALSE(process.alive());
|
||||
ASSERT_FALSE(process.dead());
|
||||
ASSERT_FALSE(process.paused());
|
||||
|
||||
process.tick(0);
|
||||
|
||||
ASSERT_TRUE(process.alive());
|
||||
ASSERT_FALSE(process.dead());
|
||||
ASSERT_FALSE(process.paused());
|
||||
|
||||
process.pause();
|
||||
|
||||
ASSERT_TRUE(process.alive());
|
||||
ASSERT_FALSE(process.dead());
|
||||
ASSERT_TRUE(process.paused());
|
||||
|
||||
process.unpause();
|
||||
|
||||
ASSERT_TRUE(process.alive());
|
||||
ASSERT_FALSE(process.dead());
|
||||
ASSERT_FALSE(process.paused());
|
||||
}
|
||||
|
||||
TEST(Process, Succeeded) {
|
||||
FakeProcess process;
|
||||
|
||||
process.tick(0);
|
||||
process.succeed();
|
||||
process.tick(0);
|
||||
|
||||
ASSERT_FALSE(process.alive());
|
||||
ASSERT_TRUE(process.dead());
|
||||
ASSERT_FALSE(process.paused());
|
||||
|
||||
ASSERT_TRUE(process.initInvoked);
|
||||
ASSERT_TRUE(process.updateInvoked);
|
||||
ASSERT_TRUE(process.succeededInvoked);
|
||||
ASSERT_FALSE(process.failedInvoked);
|
||||
ASSERT_FALSE(process.abortedInvoked);
|
||||
}
|
||||
|
||||
TEST(Process, Fail) {
|
||||
FakeProcess process;
|
||||
|
||||
process.tick(0);
|
||||
process.fail();
|
||||
process.tick(0);
|
||||
|
||||
ASSERT_FALSE(process.alive());
|
||||
ASSERT_TRUE(process.dead());
|
||||
ASSERT_FALSE(process.paused());
|
||||
|
||||
ASSERT_TRUE(process.initInvoked);
|
||||
ASSERT_TRUE(process.updateInvoked);
|
||||
ASSERT_FALSE(process.succeededInvoked);
|
||||
ASSERT_TRUE(process.failedInvoked);
|
||||
ASSERT_FALSE(process.abortedInvoked);
|
||||
}
|
||||
|
||||
TEST(Process, AbortNextTick) {
|
||||
FakeProcess process;
|
||||
|
||||
process.tick(0);
|
||||
process.abort();
|
||||
process.tick(0);
|
||||
|
||||
ASSERT_FALSE(process.alive());
|
||||
ASSERT_TRUE(process.dead());
|
||||
ASSERT_FALSE(process.paused());
|
||||
|
||||
ASSERT_TRUE(process.initInvoked);
|
||||
ASSERT_TRUE(process.updateInvoked);
|
||||
ASSERT_FALSE(process.succeededInvoked);
|
||||
ASSERT_FALSE(process.failedInvoked);
|
||||
ASSERT_TRUE(process.abortedInvoked);
|
||||
}
|
||||
|
||||
TEST(Process, AbortImmediately) {
|
||||
FakeProcess process;
|
||||
|
||||
process.tick(0);
|
||||
process.abort(true);
|
||||
|
||||
ASSERT_FALSE(process.alive());
|
||||
ASSERT_TRUE(process.dead());
|
||||
ASSERT_FALSE(process.paused());
|
||||
|
||||
ASSERT_TRUE(process.initInvoked);
|
||||
ASSERT_TRUE(process.updateInvoked);
|
||||
ASSERT_FALSE(process.succeededInvoked);
|
||||
ASSERT_FALSE(process.failedInvoked);
|
||||
ASSERT_TRUE(process.abortedInvoked);
|
||||
}
|
||||
|
||||
TEST(ProcessAdaptor, Resolved) {
|
||||
bool updated = false;
|
||||
auto lambda = [&updated](std::uint64_t, auto resolve, auto) {
|
||||
ASSERT_FALSE(updated);
|
||||
updated = true;
|
||||
resolve();
|
||||
};
|
||||
|
||||
auto process = entt::ProcessAdaptor<decltype(lambda), std::uint64_t>{lambda};
|
||||
|
||||
process.tick(0);
|
||||
|
||||
ASSERT_TRUE(process.dead());
|
||||
ASSERT_TRUE(updated);
|
||||
}
|
||||
|
||||
TEST(ProcessAdaptor, Rejected) {
|
||||
bool updated = false;
|
||||
auto lambda = [&updated](std::uint64_t, auto, auto rejected) {
|
||||
ASSERT_FALSE(updated);
|
||||
updated = true;
|
||||
rejected();
|
||||
};
|
||||
|
||||
auto process = entt::ProcessAdaptor<decltype(lambda), std::uint64_t>{lambda};
|
||||
|
||||
process.tick(0);
|
||||
|
||||
ASSERT_TRUE(process.rejected());
|
||||
ASSERT_TRUE(updated);
|
||||
}
|
||||
113
test/entt/process/scheduler.cpp
Normal file
113
test/entt/process/scheduler.cpp
Normal file
@@ -0,0 +1,113 @@
|
||||
#include <functional>
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/process/scheduler.hpp>
|
||||
#include <entt/process/process.hpp>
|
||||
|
||||
struct FooProcess: entt::Process<FooProcess, int> {
|
||||
FooProcess(std::function<void()> onUpdate, std::function<void()> onAborted)
|
||||
: onUpdate{onUpdate}, onAborted{onAborted}
|
||||
{}
|
||||
|
||||
void update(delta_type) { onUpdate(); }
|
||||
void aborted() { onAborted(); }
|
||||
|
||||
std::function<void()> onUpdate;
|
||||
std::function<void()> onAborted;
|
||||
};
|
||||
|
||||
struct SucceededProcess: entt::Process<SucceededProcess, int> {
|
||||
void update(delta_type) {
|
||||
ASSERT_FALSE(updated);
|
||||
updated = true;
|
||||
++invoked;
|
||||
succeed();
|
||||
}
|
||||
|
||||
static unsigned int invoked;
|
||||
bool updated = false;
|
||||
};
|
||||
|
||||
unsigned int SucceededProcess::invoked = 0;
|
||||
|
||||
struct FailedProcess: entt::Process<FailedProcess, int> {
|
||||
void update(delta_type) {
|
||||
ASSERT_FALSE(updated);
|
||||
updated = true;
|
||||
fail();
|
||||
}
|
||||
|
||||
bool updated = false;
|
||||
};
|
||||
|
||||
TEST(Scheduler, Functionalities) {
|
||||
entt::Scheduler<int> scheduler{};
|
||||
|
||||
bool updated = false;
|
||||
bool aborted = false;
|
||||
|
||||
ASSERT_EQ(scheduler.size(), entt::Scheduler<int>::size_type{});
|
||||
ASSERT_TRUE(scheduler.empty());
|
||||
|
||||
scheduler.attach<FooProcess>(
|
||||
[&updated](){ updated = true; },
|
||||
[&aborted](){ aborted = true; }
|
||||
);
|
||||
|
||||
ASSERT_NE(scheduler.size(), entt::Scheduler<int>::size_type{});
|
||||
ASSERT_FALSE(scheduler.empty());
|
||||
|
||||
scheduler.update(0);
|
||||
scheduler.abort(true);
|
||||
|
||||
ASSERT_TRUE(updated);
|
||||
ASSERT_TRUE(aborted);
|
||||
|
||||
ASSERT_NE(scheduler.size(), entt::Scheduler<int>::size_type{});
|
||||
ASSERT_FALSE(scheduler.empty());
|
||||
|
||||
scheduler.clear();
|
||||
|
||||
ASSERT_EQ(scheduler.size(), entt::Scheduler<int>::size_type{});
|
||||
ASSERT_TRUE(scheduler.empty());
|
||||
}
|
||||
|
||||
TEST(Scheduler, Then) {
|
||||
entt::Scheduler<int> scheduler;
|
||||
|
||||
scheduler.attach<SucceededProcess>()
|
||||
.then<SucceededProcess>()
|
||||
.then<FailedProcess>()
|
||||
.then<SucceededProcess>();
|
||||
|
||||
for(auto i = 0; i < 8; ++i) {
|
||||
scheduler.update(0);
|
||||
}
|
||||
|
||||
ASSERT_EQ(SucceededProcess::invoked, 2u);
|
||||
}
|
||||
|
||||
TEST(Scheduler, Functor) {
|
||||
entt::Scheduler<int> scheduler;
|
||||
|
||||
bool firstFunctor = false;
|
||||
bool secondFunctor = false;
|
||||
|
||||
scheduler.attach([&firstFunctor](auto, auto resolve, auto){
|
||||
ASSERT_FALSE(firstFunctor);
|
||||
firstFunctor = true;
|
||||
resolve();
|
||||
}).then([&secondFunctor](auto, auto, auto reject){
|
||||
ASSERT_FALSE(secondFunctor);
|
||||
secondFunctor = true;
|
||||
reject();
|
||||
}).then([](auto...){
|
||||
FAIL();
|
||||
});
|
||||
|
||||
for(auto i = 0; i < 8; ++i) {
|
||||
scheduler.update(0);
|
||||
}
|
||||
|
||||
ASSERT_TRUE(firstFunctor);
|
||||
ASSERT_TRUE(secondFunctor);
|
||||
}
|
||||
80
test/entt/resource/resource.cpp
Normal file
80
test/entt/resource/resource.cpp
Normal file
@@ -0,0 +1,80 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/resource/cache.hpp>
|
||||
|
||||
struct Resource { const int value; };
|
||||
|
||||
struct Loader: entt::ResourceLoader<Loader, Resource> {
|
||||
std::shared_ptr<Resource> load(int value) const {
|
||||
return std::shared_ptr<Resource>(new Resource{ value });
|
||||
}
|
||||
};
|
||||
|
||||
struct BrokenLoader: entt::ResourceLoader<BrokenLoader, Resource> {
|
||||
std::shared_ptr<Resource> load(int) const {
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
TEST(ResourceCache, Functionalities) {
|
||||
entt::ResourceCache<Resource> cache;
|
||||
|
||||
constexpr auto hs1 = entt::HashedString{"res1"};
|
||||
constexpr auto hs2 = entt::HashedString{"res2"};
|
||||
|
||||
ASSERT_EQ(cache.size(), entt::ResourceCache<Resource>::size_type{});
|
||||
ASSERT_TRUE(cache.empty());
|
||||
ASSERT_FALSE(cache.contains(hs1));
|
||||
ASSERT_FALSE(cache.contains(hs2));
|
||||
|
||||
ASSERT_FALSE(cache.load<BrokenLoader>(hs1, 42));
|
||||
|
||||
ASSERT_EQ(cache.size(), entt::ResourceCache<Resource>::size_type{});
|
||||
ASSERT_TRUE(cache.empty());
|
||||
ASSERT_FALSE(cache.contains(hs1));
|
||||
ASSERT_FALSE(cache.contains(hs2));
|
||||
|
||||
ASSERT_TRUE(cache.load<Loader>(hs1, 42));
|
||||
|
||||
ASSERT_NE(cache.size(), entt::ResourceCache<Resource>::size_type{});
|
||||
ASSERT_FALSE(cache.empty());
|
||||
ASSERT_TRUE(cache.contains(hs1));
|
||||
ASSERT_FALSE(cache.contains(hs2));
|
||||
ASSERT_EQ((*cache.handle(hs1)).value, 42);
|
||||
|
||||
ASSERT_TRUE(cache.load<Loader>(hs2, 42));
|
||||
|
||||
ASSERT_NE(cache.size(), entt::ResourceCache<Resource>::size_type{});
|
||||
ASSERT_FALSE(cache.empty());
|
||||
ASSERT_TRUE(cache.contains(hs1));
|
||||
ASSERT_TRUE(cache.contains(hs2));
|
||||
ASSERT_EQ((*cache.handle(hs1)).value, 42);
|
||||
ASSERT_EQ(cache.handle(hs2)->value, 42);
|
||||
|
||||
ASSERT_NO_THROW(cache.discard(hs1));
|
||||
|
||||
ASSERT_FALSE(cache.contains(hs1));
|
||||
ASSERT_TRUE(cache.contains(hs2));
|
||||
ASSERT_EQ(cache.handle(hs2)->value, 42);
|
||||
|
||||
ASSERT_TRUE(cache.load<Loader>(hs1, 42));
|
||||
ASSERT_NO_THROW(cache.clear());
|
||||
|
||||
ASSERT_EQ(cache.size(), entt::ResourceCache<Resource>::size_type{});
|
||||
ASSERT_TRUE(cache.empty());
|
||||
ASSERT_FALSE(cache.contains(hs1));
|
||||
ASSERT_FALSE(cache.contains(hs2));
|
||||
|
||||
ASSERT_TRUE(cache.load<Loader>(hs1, 42));
|
||||
|
||||
ASSERT_NE(cache.size(), entt::ResourceCache<Resource>::size_type{});
|
||||
ASSERT_FALSE(cache.empty());
|
||||
ASSERT_TRUE(cache.handle(hs1));
|
||||
ASSERT_FALSE(cache.handle(hs2));
|
||||
|
||||
ASSERT_TRUE(cache.handle(hs1));
|
||||
ASSERT_EQ(&cache.handle(hs1).get(), &static_cast<const Resource &>(cache.handle(hs1)));
|
||||
ASSERT_NO_THROW(cache.clear());
|
||||
|
||||
ASSERT_EQ(cache.size(), entt::ResourceCache<Resource>::size_type{});
|
||||
ASSERT_TRUE(cache.empty());
|
||||
}
|
||||
141
test/entt/signal/bus.cpp
Normal file
141
test/entt/signal/bus.cpp
Normal file
@@ -0,0 +1,141 @@
|
||||
#include <memory>
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/signal/bus.hpp>
|
||||
|
||||
struct EventA
|
||||
{
|
||||
EventA(int x, int y): value{x+y} {}
|
||||
int value;
|
||||
};
|
||||
|
||||
struct EventB {};
|
||||
struct EventC {};
|
||||
|
||||
struct MyListener
|
||||
{
|
||||
void receive(const EventA &) { A++; }
|
||||
static void listen(const EventB &) { B++; }
|
||||
void receive(const EventC &) { C++; }
|
||||
void reset() { A = 0; B = 0; C = 0; }
|
||||
int A{0};
|
||||
static int B;
|
||||
int C{0};
|
||||
};
|
||||
|
||||
int MyListener::B = 0;
|
||||
|
||||
template<typename Bus, typename Listener>
|
||||
void testRegUnregEmit(Listener listener) {
|
||||
Bus bus;
|
||||
|
||||
listener->reset();
|
||||
bus.template publish<EventA>(40, 2);
|
||||
bus.template publish<EventB>();
|
||||
bus.template publish<EventC>();
|
||||
|
||||
ASSERT_EQ(bus.size(), (decltype(bus.size()))0);
|
||||
ASSERT_TRUE(bus.empty());
|
||||
ASSERT_EQ(listener->A, 0);
|
||||
ASSERT_EQ(listener->B, 0);
|
||||
ASSERT_EQ(listener->C, 0);
|
||||
|
||||
bus.reg(listener);
|
||||
bus.template connect<EventB, &MyListener::listen>();
|
||||
|
||||
listener->reset();
|
||||
bus.template publish<EventA>(40, 2);
|
||||
bus.template publish<EventB>();
|
||||
bus.template publish<EventC>();
|
||||
|
||||
ASSERT_EQ(bus.size(), (decltype(bus.size()))3);
|
||||
ASSERT_FALSE(bus.empty());
|
||||
ASSERT_EQ(listener->A, 1);
|
||||
ASSERT_EQ(listener->B, 1);
|
||||
ASSERT_EQ(listener->C, 1);
|
||||
|
||||
bus.unreg(listener);
|
||||
|
||||
listener->reset();
|
||||
bus.template publish<EventA>(40, 2);
|
||||
bus.template publish<EventB>();
|
||||
bus.template publish<EventC>();
|
||||
|
||||
ASSERT_EQ(bus.size(), (decltype(bus.size()))1);
|
||||
ASSERT_FALSE(bus.empty());
|
||||
ASSERT_EQ(listener->A, 0);
|
||||
ASSERT_EQ(listener->B, 1);
|
||||
ASSERT_EQ(listener->C, 0);
|
||||
|
||||
bus.template disconnect<EventB, MyListener::listen>();
|
||||
|
||||
listener->reset();
|
||||
bus.template publish<EventA>(40, 2);
|
||||
bus.template publish<EventB>();
|
||||
bus.template publish<EventC>();
|
||||
|
||||
ASSERT_EQ(bus.size(), (decltype(bus.size()))0);
|
||||
ASSERT_TRUE(bus.empty());
|
||||
ASSERT_EQ(listener->A, 0);
|
||||
ASSERT_EQ(listener->B, 0);
|
||||
ASSERT_EQ(listener->C, 0);
|
||||
}
|
||||
|
||||
TEST(ManagedBus, RegUnregEmit) {
|
||||
using MyManagedBus = entt::ManagedBus<EventA, EventB, EventC>;
|
||||
testRegUnregEmit<MyManagedBus>(std::make_shared<MyListener>());
|
||||
}
|
||||
|
||||
TEST(ManagedBus, ExpiredListeners) {
|
||||
entt::ManagedBus<EventA, EventB, EventC> bus;
|
||||
auto listener = std::make_shared<MyListener>();
|
||||
|
||||
listener->reset();
|
||||
bus.reg(listener);
|
||||
bus.template publish<EventA>(40, 2);
|
||||
bus.template publish<EventB>();
|
||||
|
||||
ASSERT_EQ(bus.size(), (decltype(bus.size()))2);
|
||||
ASSERT_FALSE(bus.empty());
|
||||
ASSERT_EQ(listener->A, 1);
|
||||
ASSERT_EQ(listener->B, 0);
|
||||
|
||||
listener->reset();
|
||||
listener = nullptr;
|
||||
|
||||
ASSERT_EQ(bus.size(), (decltype(bus.size()))2);
|
||||
ASSERT_FALSE(bus.empty());
|
||||
|
||||
EXPECT_NO_THROW(bus.template publish<EventA>(40, 2));
|
||||
EXPECT_NO_THROW(bus.template publish<EventC>());
|
||||
|
||||
ASSERT_EQ(bus.size(), (decltype(bus.size()))0);
|
||||
ASSERT_TRUE(bus.empty());
|
||||
}
|
||||
|
||||
TEST(UnmanagedBus, RegUnregEmit) {
|
||||
using MyUnmanagedBus = entt::UnmanagedBus<EventA, EventB, EventC>;
|
||||
auto ptr = std::make_unique<MyListener>();
|
||||
testRegUnregEmit<MyUnmanagedBus>(ptr.get());
|
||||
}
|
||||
|
||||
TEST(UnmanagedBus, ExpiredListeners) {
|
||||
entt::UnmanagedBus<EventA, EventB, EventC> bus;
|
||||
auto listener = std::make_unique<MyListener>();
|
||||
|
||||
listener->reset();
|
||||
bus.reg(listener.get());
|
||||
bus.template publish<EventA>(40, 2);
|
||||
bus.template publish<EventB>();
|
||||
|
||||
ASSERT_EQ(bus.size(), (decltype(bus.size()))2);
|
||||
ASSERT_FALSE(bus.empty());
|
||||
ASSERT_EQ(listener->A, 1);
|
||||
ASSERT_EQ(listener->B, 0);
|
||||
|
||||
listener->reset();
|
||||
listener = nullptr;
|
||||
|
||||
// dangling pointer inside ... well, unmanaged means unmanaged!! :-)
|
||||
ASSERT_EQ(bus.size(), (decltype(bus.size()))2);
|
||||
ASSERT_FALSE(bus.empty());
|
||||
}
|
||||
45
test/entt/signal/delegate.cpp
Normal file
45
test/entt/signal/delegate.cpp
Normal file
@@ -0,0 +1,45 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/signal/delegate.hpp>
|
||||
|
||||
int f(int i) {
|
||||
return i*i;
|
||||
}
|
||||
|
||||
struct S {
|
||||
int f(int i) {
|
||||
return i+i;
|
||||
}
|
||||
};
|
||||
|
||||
TEST(Delegate, Functionalities) {
|
||||
entt::Delegate<int(int)> ffdel;
|
||||
entt::Delegate<int(int)> mfdel;
|
||||
S test;
|
||||
|
||||
ASSERT_EQ(ffdel(42), int{});
|
||||
ASSERT_EQ(mfdel(42), int{});
|
||||
|
||||
ffdel.connect<&f>();
|
||||
mfdel.connect<S, &S::f>(&test);
|
||||
|
||||
ASSERT_EQ(ffdel(3), 9);
|
||||
ASSERT_EQ(mfdel(3), 6);
|
||||
|
||||
ffdel.reset();
|
||||
mfdel.reset();
|
||||
|
||||
ASSERT_EQ(ffdel(42), int{});
|
||||
ASSERT_EQ(mfdel(42), int{});
|
||||
}
|
||||
|
||||
TEST(Delegate, Comparison) {
|
||||
entt::Delegate<int(int)> delegate;
|
||||
entt::Delegate<int(int)> def;
|
||||
delegate.connect<&f>();
|
||||
|
||||
ASSERT_EQ(def, entt::Delegate<int(int)>{});
|
||||
ASSERT_NE(def, delegate);
|
||||
|
||||
ASSERT_TRUE(def == entt::Delegate<int(int)>{});
|
||||
ASSERT_TRUE (def != delegate);
|
||||
}
|
||||
47
test/entt/signal/dispatcher.cpp
Normal file
47
test/entt/signal/dispatcher.cpp
Normal file
@@ -0,0 +1,47 @@
|
||||
#include <memory>
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/signal/dispatcher.hpp>
|
||||
|
||||
struct Event {};
|
||||
|
||||
struct Receiver {
|
||||
void receive(const Event &) { ++cnt; }
|
||||
void reset() { cnt = 0; }
|
||||
std::size_t cnt{0};
|
||||
};
|
||||
|
||||
template<typename Dispatcher, typename Rec>
|
||||
void testDispatcher(Rec receiver) {
|
||||
Dispatcher dispatcher;
|
||||
|
||||
dispatcher.template connect<Event>(receiver);
|
||||
dispatcher.template trigger<Event>();
|
||||
dispatcher.template enqueue<Event>();
|
||||
|
||||
ASSERT_EQ(receiver->cnt, static_cast<decltype(receiver->cnt)>(1));
|
||||
|
||||
dispatcher.update();
|
||||
dispatcher.update();
|
||||
dispatcher.template trigger<Event>();
|
||||
|
||||
ASSERT_EQ(receiver->cnt, static_cast<decltype(receiver->cnt)>(3));
|
||||
|
||||
receiver->reset();
|
||||
|
||||
dispatcher.template disconnect<Event>(receiver);
|
||||
dispatcher.template trigger<Event>();
|
||||
dispatcher.template enqueue<Event>();
|
||||
dispatcher.update();
|
||||
dispatcher.template trigger<Event>();
|
||||
|
||||
ASSERT_EQ(receiver->cnt, static_cast<decltype(receiver->cnt)>(0));
|
||||
}
|
||||
|
||||
TEST(ManagedDispatcher, Basics) {
|
||||
testDispatcher<entt::ManagedDispatcher>(std::make_shared<Receiver>());
|
||||
}
|
||||
|
||||
TEST(UnmanagedDispatcher, Basics) {
|
||||
auto ptr = std::make_unique<Receiver>();
|
||||
testDispatcher<entt::UnmanagedDispatcher>(ptr.get());
|
||||
}
|
||||
117
test/entt/signal/emitter.cpp
Normal file
117
test/entt/signal/emitter.cpp
Normal file
@@ -0,0 +1,117 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/signal/emitter.hpp>
|
||||
|
||||
struct TestEmitter: entt::Emitter<TestEmitter> {};
|
||||
|
||||
struct FooEvent { int i; char c; };
|
||||
struct BarEvent {};
|
||||
|
||||
TEST(Emitter, Clear) {
|
||||
TestEmitter emitter;
|
||||
|
||||
ASSERT_TRUE(emitter.empty());
|
||||
|
||||
emitter.on<FooEvent>([](const auto &, const auto &){});
|
||||
|
||||
ASSERT_FALSE(emitter.empty());
|
||||
ASSERT_FALSE(emitter.empty<FooEvent>());
|
||||
ASSERT_TRUE(emitter.empty<BarEvent>());
|
||||
|
||||
emitter.clear<BarEvent>();
|
||||
|
||||
ASSERT_FALSE(emitter.empty());
|
||||
ASSERT_FALSE(emitter.empty<FooEvent>());
|
||||
ASSERT_TRUE(emitter.empty<BarEvent>());
|
||||
|
||||
emitter.clear<FooEvent>();
|
||||
|
||||
ASSERT_TRUE(emitter.empty());
|
||||
ASSERT_TRUE(emitter.empty<FooEvent>());
|
||||
ASSERT_TRUE(emitter.empty<BarEvent>());
|
||||
|
||||
emitter.on<FooEvent>([](const auto &, const auto &){});
|
||||
emitter.on<BarEvent>([](const auto &, const auto &){});
|
||||
|
||||
ASSERT_FALSE(emitter.empty());
|
||||
ASSERT_FALSE(emitter.empty<FooEvent>());
|
||||
ASSERT_FALSE(emitter.empty<BarEvent>());
|
||||
|
||||
emitter.clear();
|
||||
|
||||
ASSERT_TRUE(emitter.empty());
|
||||
ASSERT_TRUE(emitter.empty<FooEvent>());
|
||||
ASSERT_TRUE(emitter.empty<BarEvent>());
|
||||
}
|
||||
|
||||
TEST(Emitter, ClearPublishing) {
|
||||
TestEmitter emitter;
|
||||
bool invoked = false;
|
||||
|
||||
ASSERT_TRUE(emitter.empty());
|
||||
|
||||
emitter.on<BarEvent>([&invoked](const auto &, auto &em){
|
||||
invoked = true;
|
||||
em.clear();
|
||||
});
|
||||
|
||||
emitter.publish<BarEvent>();
|
||||
|
||||
ASSERT_TRUE(emitter.empty());
|
||||
ASSERT_TRUE(invoked);
|
||||
}
|
||||
|
||||
TEST(Emitter, On) {
|
||||
TestEmitter emitter;
|
||||
|
||||
emitter.on<FooEvent>([](const auto &, const auto &){});
|
||||
|
||||
ASSERT_FALSE(emitter.empty());
|
||||
ASSERT_FALSE(emitter.empty<FooEvent>());
|
||||
|
||||
emitter.publish<FooEvent>(0, 'c');
|
||||
|
||||
ASSERT_FALSE(emitter.empty());
|
||||
ASSERT_FALSE(emitter.empty<FooEvent>());
|
||||
}
|
||||
|
||||
TEST(Emitter, Once) {
|
||||
TestEmitter emitter;
|
||||
|
||||
emitter.once<BarEvent>([](const auto &, const auto &){});
|
||||
|
||||
ASSERT_FALSE(emitter.empty());
|
||||
ASSERT_FALSE(emitter.empty<BarEvent>());
|
||||
|
||||
emitter.publish<BarEvent>();
|
||||
|
||||
ASSERT_TRUE(emitter.empty());
|
||||
ASSERT_TRUE(emitter.empty<BarEvent>());
|
||||
}
|
||||
|
||||
TEST(Emitter, OnceAndErase) {
|
||||
TestEmitter emitter;
|
||||
|
||||
auto conn = emitter.once<FooEvent>([](const auto &, const auto &){});
|
||||
|
||||
ASSERT_FALSE(emitter.empty());
|
||||
ASSERT_FALSE(emitter.empty<FooEvent>());
|
||||
|
||||
emitter.erase(conn);
|
||||
|
||||
ASSERT_TRUE(emitter.empty());
|
||||
ASSERT_TRUE(emitter.empty<FooEvent>());
|
||||
}
|
||||
|
||||
TEST(Emitter, OnAndErase) {
|
||||
TestEmitter emitter;
|
||||
|
||||
auto conn = emitter.on<BarEvent>([](const auto &, const auto &){});
|
||||
|
||||
ASSERT_FALSE(emitter.empty());
|
||||
ASSERT_FALSE(emitter.empty<BarEvent>());
|
||||
|
||||
emitter.erase(conn);
|
||||
|
||||
ASSERT_TRUE(emitter.empty());
|
||||
ASSERT_TRUE(emitter.empty<BarEvent>());
|
||||
}
|
||||
@@ -75,6 +75,17 @@ struct S {
|
||||
static void f(int &v) { v = 42; }
|
||||
};
|
||||
|
||||
TEST(SigH, Clear) {
|
||||
entt::SigH<void(int &)> sigh;
|
||||
sigh.connect<&S::f>();
|
||||
|
||||
ASSERT_FALSE(sigh.empty());
|
||||
|
||||
sigh.clear();
|
||||
|
||||
ASSERT_TRUE(sigh.empty());
|
||||
}
|
||||
|
||||
TEST(SigH, Functions) {
|
||||
entt::SigH<void(int &)> sigh;
|
||||
int v = 0;
|
||||
@@ -93,6 +104,8 @@ TEST(SigH, Functions) {
|
||||
ASSERT_TRUE(sigh.empty());
|
||||
ASSERT_EQ((entt::SigH<bool(int)>::size_type)0, sigh.size());
|
||||
ASSERT_EQ(0, v);
|
||||
|
||||
sigh.connect<&S::f>();
|
||||
}
|
||||
|
||||
TEST(SigH, Members) {
|
||||
|
||||
164
test/entt/signal/signal.cpp
Normal file
164
test/entt/signal/signal.cpp
Normal file
@@ -0,0 +1,164 @@
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/signal/signal.hpp>
|
||||
|
||||
struct S {
|
||||
static void f(const int &j) { i = j; }
|
||||
void g(const int &j) { i = j; }
|
||||
void h(const int &) {}
|
||||
static int i;
|
||||
};
|
||||
|
||||
int S::i = 0;
|
||||
|
||||
TEST(Signal, Lifetime) {
|
||||
using signal = entt::Signal<void(void)>;
|
||||
|
||||
ASSERT_NO_THROW(signal{});
|
||||
|
||||
signal src{}, other{};
|
||||
|
||||
ASSERT_NO_THROW(signal{src});
|
||||
ASSERT_NO_THROW(signal{std::move(other)});
|
||||
ASSERT_NO_THROW(src = other);
|
||||
ASSERT_NO_THROW(src = std::move(other));
|
||||
|
||||
ASSERT_NO_THROW(delete new signal{});
|
||||
}
|
||||
|
||||
TEST(Signal, Comparison) {
|
||||
struct S {
|
||||
void f() {}
|
||||
void g() {}
|
||||
};
|
||||
|
||||
entt::Signal<void()> sig1;
|
||||
entt::Signal<void()> sig2;
|
||||
|
||||
auto s1 = std::make_shared<S>();
|
||||
auto s2 = std::make_shared<S>();
|
||||
|
||||
sig1.connect<S, &S::f>(s1);
|
||||
sig2.connect<S, &S::f>(s2);
|
||||
|
||||
ASSERT_FALSE(sig1 == sig2);
|
||||
ASSERT_TRUE(sig1 != sig2);
|
||||
|
||||
sig1.disconnect<S, &S::f>(s1);
|
||||
sig2.disconnect<S, &S::f>(s2);
|
||||
|
||||
sig1.connect<S, &S::f>(s1);
|
||||
sig2.connect<S, &S::g>(s1);
|
||||
|
||||
ASSERT_FALSE(sig1 == sig2);
|
||||
ASSERT_TRUE(sig1 != sig2);
|
||||
|
||||
sig1.disconnect<S, &S::f>(s1);
|
||||
sig2.disconnect<S, &S::g>(s1);
|
||||
|
||||
ASSERT_TRUE(sig1 == sig2);
|
||||
ASSERT_FALSE(sig1 != sig2);
|
||||
|
||||
sig1.connect<S, &S::f>(s1);
|
||||
sig1.connect<S, &S::g>(s1);
|
||||
sig2.connect<S, &S::f>(s1);
|
||||
sig2.connect<S, &S::g>(s1);
|
||||
|
||||
ASSERT_TRUE(sig1 == sig2);
|
||||
|
||||
sig1.disconnect<S, &S::f>(s1);
|
||||
sig1.disconnect<S, &S::g>(s1);
|
||||
sig2.disconnect<S, &S::f>(s1);
|
||||
sig2.disconnect<S, &S::g>(s1);
|
||||
|
||||
sig1.connect<S, &S::f>(s1);
|
||||
sig1.connect<S, &S::g>(s1);
|
||||
sig2.connect<S, &S::g>(s1);
|
||||
sig2.connect<S, &S::f>(s1);
|
||||
|
||||
ASSERT_FALSE(sig1 == sig2);
|
||||
}
|
||||
|
||||
TEST(Signal, Clear) {
|
||||
entt::Signal<void(const int &)> signal;
|
||||
signal.connect<&S::f>();
|
||||
|
||||
ASSERT_FALSE(signal.empty());
|
||||
|
||||
signal.clear();
|
||||
|
||||
ASSERT_TRUE(signal.empty());
|
||||
}
|
||||
|
||||
TEST(Signal, Functions) {
|
||||
entt::Signal<void(const int &)> signal;
|
||||
auto val = S::i + 1;
|
||||
|
||||
signal.connect<&S::f>();
|
||||
signal.publish(val);
|
||||
|
||||
ASSERT_FALSE(signal.empty());
|
||||
ASSERT_EQ(entt::Signal<void(const int &)>::size_type{1}, signal.size());
|
||||
ASSERT_EQ(S::i, val);
|
||||
|
||||
signal.disconnect<&S::f>();
|
||||
signal.publish(val+1);
|
||||
|
||||
ASSERT_TRUE(signal.empty());
|
||||
ASSERT_EQ(entt::Signal<void(const int &)>::size_type{0}, signal.size());
|
||||
ASSERT_EQ(S::i, val);
|
||||
}
|
||||
|
||||
TEST(Signal, Members) {
|
||||
entt::Signal<void(const int &)> signal;
|
||||
auto ptr = std::make_shared<S>();
|
||||
auto val = S::i + 1;
|
||||
|
||||
signal.connect<S, &S::g>(ptr);
|
||||
signal.publish(val);
|
||||
|
||||
ASSERT_FALSE(signal.empty());
|
||||
ASSERT_EQ(entt::Signal<void(const int &)>::size_type{1}, signal.size());
|
||||
ASSERT_EQ(S::i, val);
|
||||
|
||||
signal.disconnect<S, &S::g>(ptr);
|
||||
signal.publish(val+1);
|
||||
|
||||
ASSERT_TRUE(signal.empty());
|
||||
ASSERT_EQ(entt::Signal<void(const int &)>::size_type{0}, signal.size());
|
||||
ASSERT_EQ(S::i, val);
|
||||
|
||||
++val;
|
||||
|
||||
signal.connect<S, &S::g>(ptr);
|
||||
signal.connect<S, &S::h>(ptr);
|
||||
signal.publish(val);
|
||||
|
||||
ASSERT_FALSE(signal.empty());
|
||||
ASSERT_EQ(entt::Signal<void(const int &)>::size_type{2}, signal.size());
|
||||
ASSERT_EQ(S::i, val);
|
||||
|
||||
signal.disconnect(ptr);
|
||||
signal.publish(val+1);
|
||||
|
||||
ASSERT_TRUE(signal.empty());
|
||||
ASSERT_EQ(entt::Signal<void(const int &)>::size_type{0}, signal.size());
|
||||
ASSERT_EQ(S::i, val);
|
||||
}
|
||||
|
||||
TEST(Signal, Cleanup) {
|
||||
entt::Signal<void(const int &)> signal;
|
||||
auto ptr = std::make_shared<S>();
|
||||
signal.connect<S, &S::g>(ptr);
|
||||
auto val = S::i;
|
||||
ptr = nullptr;
|
||||
|
||||
ASSERT_FALSE(signal.empty());
|
||||
ASSERT_EQ(S::i, val);
|
||||
|
||||
signal.publish(val);
|
||||
|
||||
ASSERT_TRUE(signal.empty());
|
||||
ASSERT_EQ(S::i, val);
|
||||
}
|
||||
3672
test/mod/duk_config.h
Normal file
3672
test/mod/duk_config.h
Normal file
File diff suppressed because it is too large
Load Diff
95118
test/mod/duktape.c
Normal file
95118
test/mod/duktape.c
Normal file
File diff suppressed because it is too large
Load Diff
1349
test/mod/duktape.h
Normal file
1349
test/mod/duktape.h
Normal file
File diff suppressed because it is too large
Load Diff
426
test/mod/mod.cpp
Normal file
426
test/mod/mod.cpp
Normal file
@@ -0,0 +1,426 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <cassert>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <entt/entity/registry.hpp>
|
||||
#include "duktape.h"
|
||||
|
||||
template<typename Type>
|
||||
struct tag { using type = Type; };
|
||||
|
||||
struct Position {
|
||||
double x;
|
||||
double y;
|
||||
};
|
||||
|
||||
struct Renderable {};
|
||||
|
||||
struct DuktapeRuntime {
|
||||
std::map<duk_uint_t, std::string> components;
|
||||
};
|
||||
|
||||
template<typename Comp>
|
||||
duk_ret_t set(duk_context *ctx, entt::DefaultRegistry ®istry) {
|
||||
const auto entity = duk_require_uint(ctx, 0);
|
||||
registry.accomodate<Comp>(entity);
|
||||
return 0;
|
||||
}
|
||||
|
||||
template<>
|
||||
duk_ret_t set<Position>(duk_context *ctx, entt::DefaultRegistry ®istry) {
|
||||
const auto entity = duk_require_uint(ctx, 0);
|
||||
const auto x = duk_require_number(ctx, 2);
|
||||
const auto y = duk_require_number(ctx, 3);
|
||||
registry.accomodate<Position>(entity, x, y);
|
||||
return 0;
|
||||
}
|
||||
|
||||
template<>
|
||||
duk_ret_t set<DuktapeRuntime>(duk_context *ctx, entt::DefaultRegistry ®istry) {
|
||||
const auto entity = duk_require_uint(ctx, 0);
|
||||
const auto type = duk_require_uint(ctx, 1);
|
||||
|
||||
duk_dup(ctx, 2);
|
||||
|
||||
if(!registry.has<DuktapeRuntime>(entity)) {
|
||||
registry.assign<DuktapeRuntime>(entity).components[type] = duk_json_encode(ctx, -1);
|
||||
} else {
|
||||
registry.get<DuktapeRuntime>(entity).components[type] = duk_json_encode(ctx, -1);
|
||||
}
|
||||
|
||||
duk_pop(ctx);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
template<typename Comp>
|
||||
duk_ret_t unset(duk_context *ctx, entt::DefaultRegistry ®istry) {
|
||||
const auto entity = duk_require_uint(ctx, 0);
|
||||
registry.remove<Comp>(entity);
|
||||
return 0;
|
||||
}
|
||||
|
||||
template<>
|
||||
duk_ret_t unset<DuktapeRuntime>(duk_context *ctx, entt::DefaultRegistry ®istry) {
|
||||
const auto entity = duk_require_uint(ctx, 0);
|
||||
const auto type = duk_require_uint(ctx, 1);
|
||||
|
||||
auto &components = registry.get<DuktapeRuntime>(entity).components;
|
||||
assert(components.find(type) != components.cend());
|
||||
components.erase(type);
|
||||
|
||||
if(components.empty()) {
|
||||
registry.remove<DuktapeRuntime>(entity);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
template<typename Comp>
|
||||
duk_ret_t has(duk_context *ctx, entt::DefaultRegistry ®istry) {
|
||||
const auto entity = duk_require_uint(ctx, 0);
|
||||
duk_push_boolean(ctx, registry.has<Comp>(entity));
|
||||
return 1;
|
||||
}
|
||||
|
||||
template<>
|
||||
duk_ret_t has<DuktapeRuntime>(duk_context *ctx, entt::DefaultRegistry ®istry) {
|
||||
const auto entity = duk_require_uint(ctx, 0);
|
||||
duk_push_boolean(ctx, registry.has<DuktapeRuntime>(entity));
|
||||
|
||||
if(registry.has<DuktapeRuntime>(entity)) {
|
||||
const auto type = duk_require_uint(ctx, 1);
|
||||
const auto &components = registry.get<DuktapeRuntime>(entity).components;
|
||||
duk_push_boolean(ctx, components.find(type) != components.cend());
|
||||
} else {
|
||||
duk_push_false(ctx);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
template<typename Comp>
|
||||
duk_ret_t get(duk_context *ctx, entt::DefaultRegistry ®istry) {
|
||||
assert(registry.has<Comp>(duk_require_uint(ctx, 0)));
|
||||
duk_push_object(ctx);
|
||||
return 1;
|
||||
}
|
||||
|
||||
template<>
|
||||
duk_ret_t get<Position>(duk_context *ctx, entt::DefaultRegistry ®istry) {
|
||||
const auto entity = duk_require_uint(ctx, 0);
|
||||
const auto &position = registry.get<Position>(entity);
|
||||
|
||||
const auto idx = duk_push_object(ctx);
|
||||
|
||||
duk_push_string(ctx, "x");
|
||||
duk_push_number(ctx, position.x);
|
||||
duk_def_prop(ctx, idx, DUK_DEFPROP_HAVE_VALUE);
|
||||
|
||||
duk_push_string(ctx, "y");
|
||||
duk_push_number(ctx, position.y);
|
||||
duk_def_prop(ctx, idx, DUK_DEFPROP_HAVE_VALUE);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
template<>
|
||||
duk_ret_t get<DuktapeRuntime>(duk_context *ctx, entt::DefaultRegistry ®istry) {
|
||||
const auto entity = duk_require_uint(ctx, 0);
|
||||
const auto type = duk_require_uint(ctx, 1);
|
||||
|
||||
auto &runtime = registry.get<DuktapeRuntime>(entity);
|
||||
assert(runtime.components.find(type) != runtime.components.cend());
|
||||
|
||||
duk_push_string(ctx, runtime.components[type].c_str());
|
||||
duk_json_decode(ctx, -1);
|
||||
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
class DuktapeRegistry {
|
||||
// I'm pretty sure I won't have more than 99 components in the example
|
||||
static constexpr entt::DefaultRegistry::component_type udef = 100;
|
||||
|
||||
struct Func {
|
||||
using func_type = duk_ret_t(*)(duk_context *, entt::DefaultRegistry &);
|
||||
using test_type = bool(entt::DefaultRegistry:: *)(entt::DefaultRegistry::entity_type) const;
|
||||
|
||||
func_type set;
|
||||
func_type unset;
|
||||
func_type has;
|
||||
func_type get;
|
||||
test_type test;
|
||||
};
|
||||
|
||||
template<typename... Comp>
|
||||
void reg() {
|
||||
using accumulator_type = int[];
|
||||
accumulator_type acc = { (func[registry.component<Comp>()] = {
|
||||
&::set<Comp>,
|
||||
&::unset<Comp>,
|
||||
&::has<Comp>,
|
||||
&::get<Comp>,
|
||||
&entt::DefaultRegistry::has<Comp>
|
||||
}, 0)... };
|
||||
(void)acc;
|
||||
}
|
||||
|
||||
static DuktapeRegistry & instance(duk_context *ctx) {
|
||||
duk_push_this(ctx);
|
||||
|
||||
duk_push_string(ctx, DUK_HIDDEN_SYMBOL("dreg"));
|
||||
duk_get_prop(ctx, -2);
|
||||
auto &dreg = *static_cast<DuktapeRegistry *>(duk_require_pointer(ctx, -1));
|
||||
duk_pop_2(ctx);
|
||||
|
||||
return dreg;
|
||||
}
|
||||
|
||||
template<Func::func_type Func::*Op>
|
||||
static duk_ret_t invoke(duk_context *ctx) {
|
||||
auto &dreg = instance(ctx);
|
||||
auto &func = dreg.func;
|
||||
auto ®istry = dreg.registry;
|
||||
auto type = duk_require_uint(ctx, 1);
|
||||
|
||||
if(type >= udef) {
|
||||
type = registry.component<DuktapeRuntime>();
|
||||
}
|
||||
|
||||
assert(func.find(type) != func.cend());
|
||||
|
||||
return (func[type].*Op)(ctx, registry);
|
||||
}
|
||||
|
||||
public:
|
||||
DuktapeRegistry(entt::DefaultRegistry ®istry)
|
||||
: registry{registry}
|
||||
{
|
||||
reg<Position, Renderable, DuktapeRuntime>();
|
||||
}
|
||||
|
||||
static duk_ret_t identifier(duk_context *ctx) {
|
||||
static auto next = udef;
|
||||
duk_push_uint(ctx, next++);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static duk_ret_t create(duk_context *ctx) {
|
||||
auto &dreg = instance(ctx);
|
||||
duk_push_uint(ctx, dreg.registry.create());
|
||||
return 1;
|
||||
}
|
||||
|
||||
static duk_ret_t set(duk_context *ctx) {
|
||||
return invoke<&Func::set>(ctx);
|
||||
}
|
||||
|
||||
static duk_ret_t unset(duk_context *ctx) {
|
||||
return invoke<&Func::unset>(ctx);
|
||||
}
|
||||
|
||||
static duk_ret_t has(duk_context *ctx) {
|
||||
return invoke<&Func::has>(ctx);
|
||||
}
|
||||
|
||||
static duk_ret_t get(duk_context *ctx) {
|
||||
return invoke<&Func::get>(ctx);
|
||||
}
|
||||
|
||||
static duk_ret_t entities(duk_context *ctx) {
|
||||
const duk_idx_t nargs = duk_get_top(ctx);
|
||||
auto &dreg = instance(ctx);
|
||||
duk_uarridx_t pos = 0;
|
||||
|
||||
duk_push_array(ctx);
|
||||
|
||||
dreg.registry.each([ctx, nargs, &pos, &dreg](auto entity) {
|
||||
auto ®istry = dreg.registry;
|
||||
auto &func = dreg.func;
|
||||
bool match = true;
|
||||
|
||||
for (duk_idx_t arg = 0; match && arg < nargs; arg++) {
|
||||
auto type = duk_require_uint(ctx, arg);
|
||||
|
||||
if(type < udef) {
|
||||
assert(func.find(type) != func.cend());
|
||||
match = (registry.*func[type].test)(entity);
|
||||
} else {
|
||||
const auto ctype = registry.component<DuktapeRuntime>();
|
||||
assert(func.find(ctype) != func.cend());
|
||||
match = (registry.*func[ctype].test)(entity);
|
||||
|
||||
if(match) {
|
||||
auto &components = registry.get<DuktapeRuntime>(entity).components;
|
||||
match = (components.find(type) != components.cend());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(match) {
|
||||
duk_push_uint(ctx, entity);
|
||||
duk_put_prop_index(ctx, -2, pos++);
|
||||
}
|
||||
});
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
private:
|
||||
std::map<duk_uint_t, Func> func;
|
||||
entt::DefaultRegistry ®istry;
|
||||
};
|
||||
|
||||
const duk_function_list_entry js_DuktapeRegistry_methods[] = {
|
||||
{ "identifier", &DuktapeRegistry::identifier, 0 },
|
||||
{ "create", &DuktapeRegistry::create, 0 },
|
||||
{ "set", &DuktapeRegistry::set, DUK_VARARGS },
|
||||
{ "unset", &DuktapeRegistry::unset, 2 },
|
||||
{ "has", &DuktapeRegistry::has, 2 },
|
||||
{ "get", &DuktapeRegistry::get, 2 },
|
||||
{ "entities", &DuktapeRegistry::entities, DUK_VARARGS },
|
||||
{ nullptr, nullptr, 0 }
|
||||
};
|
||||
|
||||
void exportTypes(duk_context *ctx, entt::DefaultRegistry ®istry) {
|
||||
auto exportType = [](auto *ctx, auto ®istry, auto idx, auto type, const auto *name) {
|
||||
duk_push_string(ctx, name);
|
||||
duk_push_uint(ctx, registry.template component<typename decltype(type)::type>());
|
||||
duk_def_prop(ctx, idx, DUK_DEFPROP_HAVE_VALUE | DUK_DEFPROP_CLEAR_WRITABLE);
|
||||
};
|
||||
|
||||
auto idx = duk_push_object(ctx);
|
||||
|
||||
exportType(ctx, registry, idx, tag<Position>{}, "POSITION");
|
||||
exportType(ctx, registry, idx, tag<Renderable>{}, "RENDERABLE");
|
||||
|
||||
duk_put_global_string(ctx, "Types");
|
||||
}
|
||||
|
||||
void exportDuktapeRegistry(duk_context *ctx, DuktapeRegistry &dreg) {
|
||||
auto idx = duk_push_object(ctx);
|
||||
|
||||
duk_push_string(ctx, DUK_HIDDEN_SYMBOL("dreg"));
|
||||
duk_push_pointer(ctx, &dreg);
|
||||
duk_put_prop(ctx, idx);
|
||||
|
||||
duk_put_function_list(ctx, idx, js_DuktapeRegistry_methods);
|
||||
duk_put_global_string(ctx, "Registry");
|
||||
}
|
||||
|
||||
TEST(Mod, Duktape) {
|
||||
entt::DefaultRegistry registry;
|
||||
DuktapeRegistry dreg{registry};
|
||||
duk_context *ctx = duk_create_heap_default();
|
||||
|
||||
if(!ctx) {
|
||||
FAIL();
|
||||
}
|
||||
|
||||
exportTypes(ctx, registry);
|
||||
exportDuktapeRegistry(ctx, dreg);
|
||||
|
||||
const char *s0 = ""
|
||||
"Types[\"PLAYING_CHARACTER\"] = Registry.identifier();"
|
||||
"Types[\"VELOCITY\"] = Registry.identifier();"
|
||||
"";
|
||||
|
||||
if(duk_peval_string(ctx, s0)) {
|
||||
FAIL();
|
||||
}
|
||||
|
||||
registry.create(Position{ 0., 0. }, Renderable{});
|
||||
registry.create(Position{ 0., 0. });
|
||||
|
||||
const char *s1 = ""
|
||||
"Registry.entities(Types.POSITION, Types.RENDERABLE).forEach(function(entity) {"
|
||||
"Registry.set(entity, Types.POSITION, 100., 100.);"
|
||||
"});"
|
||||
"var entity = Registry.create();"
|
||||
"Registry.set(entity, Types.POSITION, 100., 100.);"
|
||||
"Registry.set(entity, Types.RENDERABLE);"
|
||||
"";
|
||||
|
||||
if(duk_peval_string(ctx, s1)) {
|
||||
FAIL();
|
||||
}
|
||||
|
||||
ASSERT_EQ(registry.view<DuktapeRuntime>().size(), 0u);
|
||||
ASSERT_EQ(registry.view<Position>().size(), 3u);
|
||||
ASSERT_EQ(registry.view<Renderable>().size(), 2u);
|
||||
|
||||
registry.view<Position>().each([®istry](auto entity, const auto &position) {
|
||||
ASSERT_FALSE(registry.has<DuktapeRuntime>(entity));
|
||||
|
||||
if(registry.has<Renderable>(entity)) {
|
||||
ASSERT_EQ(position.x, 100.);
|
||||
ASSERT_EQ(position.y, 100.);
|
||||
} else {
|
||||
ASSERT_EQ(position.x, 0.);
|
||||
ASSERT_EQ(position.y, 0.);
|
||||
}
|
||||
});
|
||||
|
||||
const char *s2 = ""
|
||||
"Registry.entities(Types.POSITION).forEach(function(entity) {"
|
||||
"if(!Registry.has(entity, Types.RENDERABLE)) {"
|
||||
"Registry.set(entity, Types.VELOCITY, { \"dx\": -100., \"dy\": -100. });"
|
||||
"Registry.set(entity, Types.PLAYING_CHARACTER, {});"
|
||||
"}"
|
||||
"});"
|
||||
"";
|
||||
|
||||
if(duk_peval_string(ctx, s2)) {
|
||||
FAIL();
|
||||
}
|
||||
|
||||
ASSERT_EQ(registry.view<DuktapeRuntime>().size(), 1u);
|
||||
ASSERT_EQ(registry.view<Position>().size(), 3u);
|
||||
ASSERT_EQ(registry.view<Renderable>().size(), 2u);
|
||||
|
||||
registry.view<DuktapeRuntime>().each([](auto, const DuktapeRuntime &runtime) {
|
||||
ASSERT_EQ(runtime.components.size(), 2u);
|
||||
});
|
||||
|
||||
const char *s3 = ""
|
||||
"Registry.entities(Types.POSITION, Types.RENDERABLE, Types.VELOCITY, Types.PLAYING_CHARACTER).forEach(function(entity) {"
|
||||
"var velocity = Registry.get(entity, Types.VELOCITY);"
|
||||
"Registry.set(entity, Types.POSITION, velocity.dx, velocity.dy)"
|
||||
"});"
|
||||
"";
|
||||
|
||||
if(duk_peval_string(ctx, s3)) {
|
||||
FAIL();
|
||||
}
|
||||
|
||||
ASSERT_EQ(registry.view<DuktapeRuntime>().size(), 1u);
|
||||
ASSERT_EQ(registry.view<Position>().size(), 3u);
|
||||
ASSERT_EQ(registry.view<Renderable>().size(), 2u);
|
||||
|
||||
registry.view<Position, Renderable, DuktapeRuntime>().each([](auto, const Position &position, const auto &...) {
|
||||
ASSERT_EQ(position.x, -100.);
|
||||
ASSERT_EQ(position.y, -100.);
|
||||
});
|
||||
|
||||
const char *s4 = ""
|
||||
"Registry.entities(Types.VELOCITY, Types.PLAYING_CHARACTER).forEach(function(entity) {"
|
||||
"Registry.unset(entity, Types.VELOCITY);"
|
||||
"Registry.unset(entity, Types.PLAYING_CHARACTER);"
|
||||
"});"
|
||||
"Registry.entities(Types.POSITION).forEach(function(entity) {"
|
||||
"Registry.unset(entity, Types.POSITION);"
|
||||
"});"
|
||||
"";
|
||||
|
||||
if(duk_peval_string(ctx, s4)) {
|
||||
FAIL();
|
||||
}
|
||||
|
||||
ASSERT_EQ(registry.view<DuktapeRuntime>().size(), 0u);
|
||||
ASSERT_EQ(registry.view<Position>().size(), 0u);
|
||||
ASSERT_EQ(registry.view<Renderable>().size(), 2u);
|
||||
|
||||
duk_destroy_heap(ctx);
|
||||
}
|
||||
Reference in New Issue
Block a user