Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
06426e4fd7 | ||
|
|
c55a97c24d | ||
|
|
0d61289bf3 | ||
|
|
bf10cbc70b | ||
|
|
2d945e426b | ||
|
|
13250887fa | ||
|
|
3507c22968 | ||
|
|
cc3f98ebcd | ||
|
|
4116e2d6ac | ||
|
|
48eab6b4a7 | ||
|
|
25866b5369 | ||
|
|
c4dd06fa45 | ||
|
|
4846d211e0 | ||
|
|
a586ad1237 | ||
|
|
b701c9c464 | ||
|
|
d0f20ed2bf | ||
|
|
0f64a2f3b0 |
@@ -77,3 +77,4 @@ deploy:
|
|||||||
script: scripts/update_packages.sh $TRAVIS_TAG
|
script: scripts/update_packages.sh $TRAVIS_TAG
|
||||||
on:
|
on:
|
||||||
tags: true
|
tags: true
|
||||||
|
condition: “$TRAVIS_BRANCH” = “$TRAVIS_TAG”
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ endif()
|
|||||||
# Project configuration
|
# Project configuration
|
||||||
#
|
#
|
||||||
|
|
||||||
project(EnTT VERSION 2.7.2)
|
project(EnTT VERSION 2.7.3)
|
||||||
|
|
||||||
include(GNUInstallDirs)
|
include(GNUInstallDirs)
|
||||||
|
|
||||||
@@ -218,6 +218,4 @@ add_custom_target(
|
|||||||
README.md
|
README.md
|
||||||
TODO
|
TODO
|
||||||
.travis.yml
|
.travis.yml
|
||||||
docs/CONTRIBUTING.md
|
|
||||||
docs/extra.dox
|
|
||||||
)
|
)
|
||||||
|
|||||||
8
TODO
8
TODO
@@ -1,15 +1,17 @@
|
|||||||
* custom allocators and EnTT allocator-aware in general (long term feature, I don't actually need it at the moment) - see #22
|
* custom allocators and EnTT allocator-aware in general (long term feature, I don't actually need it at the moment) - see #22
|
||||||
* scene management (I prefer the concept of spaces, that is a kind of scene anyway)
|
* scene management (I prefer the concept of spaces, that is a kind of scene anyway)
|
||||||
* review doc: separate it in multiple md/dox files, reduce the readme to a minimum and provide users with links to the online documentation on gh-pages
|
|
||||||
* debugging tools (#60): the issue online already contains interesting tips on this, look at it
|
* debugging tools (#60): the issue online already contains interesting tips on this, look at it
|
||||||
* define basic reactive systems (track entities to which component is attached, track entities from which component is removed, and so on)
|
* define basic reactive systems (track entities to which component is attached, track entities from which component is removed, and so on)
|
||||||
* define systems as composable mixins (initializazion, reactive, update, whatever) with flexible auto-detected arguments (registry, views, etc)
|
* define systems as composable mixins (initializazion, reactive, update, whatever) with flexible auto-detected arguments (registry, views, etc)
|
||||||
* create dedicated flat map based on types implementation (sort of "type map") for types to use within the registry and so on...
|
|
||||||
* registry::create with a "hint" on the entity identifier to use, it should ease combining multiple registries
|
* registry::create with a "hint" on the entity identifier to use, it should ease combining multiple registries
|
||||||
|
* deep copy of a registry (or use the snapshot stuff to copy components and keep intact ids at least)
|
||||||
|
* is it possible to iterate all the components assigned to an entity through a common base class?
|
||||||
* optimize for empty components, it would be a mid improvement in terms of memory usage
|
* optimize for empty components, it would be a mid improvement in terms of memory usage
|
||||||
* add some lazy iterative sorters like "single bubble sort loop"
|
|
||||||
* can we do more for shared libraries? who knows... see #144
|
* can we do more for shared libraries? who knows... see #144
|
||||||
* work stealing job system (see #100)
|
* work stealing job system (see #100)
|
||||||
|
* make view copyable/moveable
|
||||||
* reflection system (maybe)
|
* reflection system (maybe)
|
||||||
* C++17. That's all.
|
* C++17. That's all.
|
||||||
* AOB
|
* AOB
|
||||||
|
* lower case names (?)
|
||||||
|
* tag_t and the others, create constexpr var
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ configuration:
|
|||||||
|
|
||||||
before_build:
|
before_build:
|
||||||
- cd %BUILD_DIR%
|
- cd %BUILD_DIR%
|
||||||
- cmake .. -DCMAKE_CXX_FLAGS=/D_SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING -G"Visual Studio 15 2017"
|
- cmake .. -DCMAKE_CXX_FLAGS=/W1 -G"Visual Studio 15 2017"
|
||||||
|
|
||||||
build:
|
build:
|
||||||
parallel: true
|
parallel: true
|
||||||
|
|||||||
@@ -20,3 +20,17 @@ install(
|
|||||||
DIRECTORY ${DOXY_OUTPUT_DIRECTORY}/html
|
DIRECTORY ${DOXY_OUTPUT_DIRECTORY}/html
|
||||||
DESTINATION share/${PROJECT_NAME}-${PROJECT_VERSION}/
|
DESTINATION share/${PROJECT_NAME}-${PROJECT_VERSION}/
|
||||||
)
|
)
|
||||||
|
|
||||||
|
add_custom_target(
|
||||||
|
docs_aob
|
||||||
|
SOURCES
|
||||||
|
CONTRIBUTING.md
|
||||||
|
core.md
|
||||||
|
entity.md
|
||||||
|
locator.md
|
||||||
|
process.md
|
||||||
|
resource.md
|
||||||
|
shared.md
|
||||||
|
signal.md
|
||||||
|
extra.dox
|
||||||
|
)
|
||||||
|
|||||||
167
docs/core.md
Normal file
167
docs/core.md
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
# Crash Course: core functionalities
|
||||||
|
|
||||||
|
<!--
|
||||||
|
@cond TURN_OFF_DOXYGEN
|
||||||
|
-->
|
||||||
|
# Table of Contents
|
||||||
|
|
||||||
|
* [Introduction](#introduction)
|
||||||
|
* [Compile-time identifiers](#compile-time-identifiers)
|
||||||
|
* [Runtime identifiers](#runtime-identifiers)
|
||||||
|
* [Hashed strings](#hashed-strings)
|
||||||
|
* [Conflicts](#conflicts)
|
||||||
|
* [Monostate](#monostate)
|
||||||
|
<!--
|
||||||
|
@endcond TURN_OFF_DOXYGEN
|
||||||
|
-->
|
||||||
|
|
||||||
|
# Introduction
|
||||||
|
|
||||||
|
`EnTT` comes with a bunch of core functionalities mostly used by the other parts
|
||||||
|
of the library itself.<br/>
|
||||||
|
Hardly users will include these features in their code, but it's worth
|
||||||
|
describing what `EnTT` offers so as not to reinvent the wheel in case of need.
|
||||||
|
|
||||||
|
# Compile-time identifiers
|
||||||
|
|
||||||
|
Sometimes it's useful to be able to give unique identifiers to types at
|
||||||
|
compile-time.<br/>
|
||||||
|
There are plenty of different solutions out there and I could have used one of
|
||||||
|
them. However, I decided to spend my time to define a compact and versatile tool
|
||||||
|
that fully embraces what the modern C++ has to offer.
|
||||||
|
|
||||||
|
The _result of my efforts_ is the `Identifier` class template:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <ident.hpp>
|
||||||
|
|
||||||
|
// defines the identifiers for the given types
|
||||||
|
using ID = entt::Identifier<AType, AnotherType>;
|
||||||
|
|
||||||
|
// ...
|
||||||
|
|
||||||
|
switch(aTypeIdentifier) {
|
||||||
|
case ID::get<AType>():
|
||||||
|
// ...
|
||||||
|
break;
|
||||||
|
case ID::get<AnotherType>():
|
||||||
|
// ...
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This is all what the class template has to offer: a static `get` member function
|
||||||
|
that returns a numerical identifier for the given type. It can be used in any
|
||||||
|
context where constant expressions are required.
|
||||||
|
|
||||||
|
As long as the list remains unchanged, identifiers are also guaranteed to be the
|
||||||
|
same for every run. In case they have been used in a production environment and
|
||||||
|
a type has to be removed, one can just use a placeholder to left the other
|
||||||
|
identifiers unchanged:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
template<typename> struct IgnoreType {};
|
||||||
|
|
||||||
|
using ID = entt::Identifier<
|
||||||
|
ATypeStillValid,
|
||||||
|
IgnoreType<ATypeNoLongerValid>,
|
||||||
|
AnotherTypeStillValid
|
||||||
|
>;
|
||||||
|
```
|
||||||
|
|
||||||
|
A bit ugly to see, but it works at least.
|
||||||
|
|
||||||
|
# Runtime identifiers
|
||||||
|
|
||||||
|
Sometimes it's useful to be able to give unique identifiers to types at
|
||||||
|
runtime.<br/>
|
||||||
|
There are plenty of different solutions out there and I could have used one of
|
||||||
|
them. In fact, I adapted the most common one to my requirements and used it
|
||||||
|
extensively within the entire library.
|
||||||
|
|
||||||
|
It's the `Family` class. Here is an example of use directly from the
|
||||||
|
entity-component system:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using component_family = entt::Family<struct InternalRegistryComponentFamily>;
|
||||||
|
|
||||||
|
// ...
|
||||||
|
|
||||||
|
template<typename Component>
|
||||||
|
component_type component() const noexcept {
|
||||||
|
return component_family::type<Component>();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This is all what a _family_ has to offer: a `type` member function that returns
|
||||||
|
a numerical identifier for the given type.
|
||||||
|
|
||||||
|
Please, note that identifiers aren't guaranteed to be the same for every run.
|
||||||
|
Indeed it mostly depends on the flow of execution.
|
||||||
|
|
||||||
|
# Hashed strings
|
||||||
|
|
||||||
|
A hashed string is a zero overhead resource identifier. Users can use
|
||||||
|
human-readable identifiers in the codebase while using their numeric
|
||||||
|
counterparts at runtime, thus without affecting performance.<br/>
|
||||||
|
The class has an implicit `constexpr` constructor that chews a bunch of
|
||||||
|
characters. Once created, all what one can do with it is getting back the
|
||||||
|
original string or converting it into a number.<br/>
|
||||||
|
The good part is that a hashed string can be used wherever a constant expression
|
||||||
|
is required and no _string-to-number_ conversion will take place at runtime if
|
||||||
|
used carefully.
|
||||||
|
|
||||||
|
Example of use:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
auto load(entt::HashedString::hash_type resource) {
|
||||||
|
// uses the numeric representation of the resource to load and return it
|
||||||
|
}
|
||||||
|
|
||||||
|
auto resource = load(entt::HashedString{"gui/background"});
|
||||||
|
```
|
||||||
|
|
||||||
|
There is also a _user defined literal_ dedicated to hashed strings to make them
|
||||||
|
more user-friendly:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
constexpr auto str = "text"_hs;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Conflicts
|
||||||
|
|
||||||
|
The hashed string class uses internally FNV-1a to compute the numeric
|
||||||
|
counterpart of a string. Because of the _pigeonhole principle_, conflicts are
|
||||||
|
possible. This is a fact.<br/>
|
||||||
|
There is no silver bullet to solve the problem of conflicts when dealing with
|
||||||
|
hashing functions. In this case, the best solution seemed to be to give up.
|
||||||
|
That's all.<br/>
|
||||||
|
After all, human-readable resource identifiers aren't something strictly defined
|
||||||
|
and over which users have not the control. Choosing a slightly different
|
||||||
|
identifier is probably the best solution to make the conflict disappear in this
|
||||||
|
case.
|
||||||
|
|
||||||
|
# Monostate
|
||||||
|
|
||||||
|
The monostate pattern is often presented as an alternative to a singleton based
|
||||||
|
configuration system. This is exactly its purpose in `EnTT`. Moreover, this
|
||||||
|
implementation is thread safe by design (hopefully).<br/>
|
||||||
|
Keys are represented by hashed strings, values are basic types like `int`s or
|
||||||
|
`bool`s. Values of different types can be associated to each key, even more than
|
||||||
|
one at a time. Because of this, users must pay attention to use the same type
|
||||||
|
both during an assignment and when they try to read back their data. Otherwise,
|
||||||
|
they will probably incur in unexpected results.
|
||||||
|
|
||||||
|
Example of use:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
entt::Monostate<entt::HashedString{"mykey"}>{} = true;
|
||||||
|
entt::Monostate<"mykey"_hs>{} = 42;
|
||||||
|
|
||||||
|
// ...
|
||||||
|
|
||||||
|
const bool b = entt::Monostate<"mykey"_hs>{};
|
||||||
|
const int i = entt::Monostate<entt::HashedString{"mykey"}>{};
|
||||||
|
```
|
||||||
1400
docs/entity.md
Normal file
1400
docs/entity.md
Normal file
File diff suppressed because it is too large
Load Diff
75
docs/locator.md
Normal file
75
docs/locator.md
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
# Crash Course: service locator
|
||||||
|
|
||||||
|
<!--
|
||||||
|
@cond TURN_OFF_DOXYGEN
|
||||||
|
-->
|
||||||
|
# Table of Contents
|
||||||
|
|
||||||
|
* [Introduction](#introduction)
|
||||||
|
* [Service locator](#service-locator)
|
||||||
|
<!--
|
||||||
|
@endcond TURN_OFF_DOXYGEN
|
||||||
|
-->
|
||||||
|
|
||||||
|
# Introduction
|
||||||
|
|
||||||
|
Usually service locators are tightly bound to the services they expose and it's
|
||||||
|
hard to define a general purpose solution. 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.<br/>
|
||||||
|
This class is tiny, partially unsafe and thus risky to use. Moreover it doesn't
|
||||||
|
fit probably most of the scenarios in which a service locator is required. Look
|
||||||
|
at it as a small tool that can sometimes be useful if users know how to handle
|
||||||
|
it.
|
||||||
|
|
||||||
|
# Service locator
|
||||||
|
|
||||||
|
The API is straightforward. The basic idea is that services are implemented by
|
||||||
|
means of interfaces and rely on polymorphism.<br/>
|
||||||
|
The locator is instantiated with the base type of the service if any and a
|
||||||
|
concrete implementation is provided along with all the parameters required to
|
||||||
|
initialize it. As an example:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// the service has no base type, a locator is used to treat it as a kind of singleton
|
||||||
|
entt::ServiceLocator<MyService>::set(params...);
|
||||||
|
|
||||||
|
// sets up an opaque service
|
||||||
|
entt::ServiceLocator<AudioInterface>::set<AudioImplementation>(params...);
|
||||||
|
|
||||||
|
// resets (destroys) the service
|
||||||
|
entt::ServiceLocator<AudioInterface>::reset();
|
||||||
|
```
|
||||||
|
|
||||||
|
The locator can also be queried to know if an active service is currently set
|
||||||
|
and to retrieve it if necessary (either as a pointer or as a reference):
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// no service currently set
|
||||||
|
auto empty = entt::ServiceLocator<AudioInterface>::empty();
|
||||||
|
|
||||||
|
// gets a (possibly empty) shared pointer to the service ...
|
||||||
|
std::shared_ptr<AudioInterface> ptr = entt::ServiceLocator<AudioInterface>::get();
|
||||||
|
|
||||||
|
// ... or a reference, but it's undefined behaviour if the service isn't set yet
|
||||||
|
AudioInterface &ref = entt::ServiceLocator<AudioInterface>::ref();
|
||||||
|
```
|
||||||
|
|
||||||
|
A common use is to wrap the different locators in a container class, creating
|
||||||
|
aliases for the various services:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
struct Locator {
|
||||||
|
using Camera = entt::ServiceLocator<CameraInterface>;
|
||||||
|
using Audio = entt::ServiceLocator<AudioInterface>;
|
||||||
|
// ...
|
||||||
|
};
|
||||||
|
|
||||||
|
// ...
|
||||||
|
|
||||||
|
void init() {
|
||||||
|
Locator::Camera::set<CameraNull>();
|
||||||
|
Locator::Audio::set<AudioImplementation>(params...);
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
211
docs/process.md
Normal file
211
docs/process.md
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
# Crash Course: cooperative scheduler
|
||||||
|
|
||||||
|
<!--
|
||||||
|
@cond TURN_OFF_DOXYGEN
|
||||||
|
-->
|
||||||
|
# Table of Contents
|
||||||
|
|
||||||
|
* [Introduction](#introduction)
|
||||||
|
* [The process](#the-process)
|
||||||
|
* [Adaptor](#adaptor)
|
||||||
|
* [The scheduler](#the-scheduler)
|
||||||
|
<!--
|
||||||
|
@endcond TURN_OFF_DOXYGEN
|
||||||
|
-->
|
||||||
|
|
||||||
|
# Introduction
|
||||||
|
|
||||||
|
Sometimes processes are a useful tool to work around the strict definition of a
|
||||||
|
system and introduce logic in a different way, usually without resorting to the
|
||||||
|
introduction of other components.
|
||||||
|
|
||||||
|
`EnTT` offers a minimal support to this paradigm by introducing a few classes
|
||||||
|
that users can use to define and execute cooperative processes.
|
||||||
|
|
||||||
|
# The process
|
||||||
|
|
||||||
|
A typical process must inherit from the `Process` class template that stays true
|
||||||
|
to the CRTP idiom. Moreover, derived classes must specify what's the intended
|
||||||
|
type for elapsed times.
|
||||||
|
|
||||||
|
A process should expose publicly the following member functions whether
|
||||||
|
required (note that it isn't required to define a function unless the derived
|
||||||
|
class wants to _override_ the default behavior):
|
||||||
|
|
||||||
|
* `void update(Delta, void *);`
|
||||||
|
|
||||||
|
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. The `void *` parameter is an opaque pointer
|
||||||
|
to user data (if any) forwarded directly to the process during an update.
|
||||||
|
|
||||||
|
* `void init(void *);`
|
||||||
|
|
||||||
|
It's invoked at the first tick, immediately before an update. The `void *`
|
||||||
|
parameter is an opaque pointer to user data (if any) forwarded directly to the
|
||||||
|
process during an update.
|
||||||
|
|
||||||
|
* `void succeeded();`
|
||||||
|
|
||||||
|
It's invoked in case of success, immediately after an update and during the
|
||||||
|
same tick.
|
||||||
|
|
||||||
|
* `void failed();`
|
||||||
|
|
||||||
|
It's invoked in case of errors, immediately after an update and during the
|
||||||
|
same tick.
|
||||||
|
|
||||||
|
* `void aborted();`
|
||||||
|
|
||||||
|
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 also change the internal state of a process by invoking
|
||||||
|
`succeed` and `fail`, as well as `pause` and `unpause` the process itself. All
|
||||||
|
these are protected member functions made available to be able to manage the
|
||||||
|
life cycle of a process from a derived class.
|
||||||
|
|
||||||
|
Here is a minimal example for the sake of curiosity:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
struct MyProcess: entt::Process<MyProcess, std::uint32_t> {
|
||||||
|
using delta_type = std::uint32_t;
|
||||||
|
|
||||||
|
void update(delta_type delta, void *) {
|
||||||
|
remaining = delta > remaining ? delta_type{] : (remaining - delta);
|
||||||
|
|
||||||
|
// ...
|
||||||
|
|
||||||
|
if(!remaining) {
|
||||||
|
succeed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void init(void *data) {
|
||||||
|
remaining = *static_cast<delta_type *>(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
delta_type remaining;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## Adaptor
|
||||||
|
|
||||||
|
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 function call operator has a signature similar to the one of the `update`
|
||||||
|
function of a process but for the fact that it receives two extra arguments to
|
||||||
|
call whenever a process is terminated with success or with an error:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
void(Delta delta, void *data, auto succeed, auto fail);
|
||||||
|
```
|
||||||
|
|
||||||
|
Parameters have the following meaning:
|
||||||
|
|
||||||
|
* `delta` is the elapsed time.
|
||||||
|
* `data` is an opaque pointer to user data if any, `nullptr` otherwise.
|
||||||
|
* `succeed` is a function to call when a process terminates with success.
|
||||||
|
* `fail` is a function to call when a process terminates with errors.
|
||||||
|
|
||||||
|
Both `succeed` and `fail` accept no parameters at all.
|
||||||
|
|
||||||
|
Note that usually users shouldn't worry about creating adaptors at all. A
|
||||||
|
scheduler creates them internally each and every time a lambda or a functor is
|
||||||
|
used as a process.
|
||||||
|
|
||||||
|
# The scheduler
|
||||||
|
|
||||||
|
A cooperative scheduler runs different processes and helps managing their life
|
||||||
|
cycles.
|
||||||
|
|
||||||
|
Each process is invoked once per tick. If it terminates, it's removed
|
||||||
|
automatically from the scheduler and it's never invoked again. Otherwise it's
|
||||||
|
a good candidate to run once more the next tick.<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. This way, it's easy to create
|
||||||
|
chain of processes to run sequentially.
|
||||||
|
|
||||||
|
Using a scheduler is straightforward. To create it, users must provide only the
|
||||||
|
type for the elapsed times and no arguments at all:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
Scheduler<std::uint32_t> scheduler;
|
||||||
|
```
|
||||||
|
|
||||||
|
It has member functions to query its internal data structures, like `empty` or
|
||||||
|
`size`, as well as a `clear` utility to reset it to a clean state:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// checks if there are processes still running
|
||||||
|
const auto empty = scheduler.empty();
|
||||||
|
|
||||||
|
// gets the number of processes still running
|
||||||
|
Scheduler<std::uint32_t>::size_type size = scheduler.size();
|
||||||
|
|
||||||
|
// resets the scheduler to its initial state and discards all the processes
|
||||||
|
scheduler.clear();
|
||||||
|
```
|
||||||
|
|
||||||
|
To attach a process to a scheduler there are mainly two ways:
|
||||||
|
|
||||||
|
* If the process inherits from the `Process` class template, it's enough to
|
||||||
|
indicate its type and submit all the parameters required to construct it to
|
||||||
|
the `attach` member function:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
scheduler.attach<MyProcess>("foobar");
|
||||||
|
```
|
||||||
|
|
||||||
|
* Otherwise, in case of a lambda or a functor, it's enough to provide an
|
||||||
|
instance of the class to the `attach` member function:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
scheduler.attach([](auto...){ /* ... */ });
|
||||||
|
```
|
||||||
|
|
||||||
|
In both cases, the return value is an opaque object that offers a `then` member
|
||||||
|
function to use to create chains of processes to run sequentially.<br/>
|
||||||
|
As a minimal example of use:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// schedules a task in the form of a lambda function
|
||||||
|
scheduler.attach([](auto delta, void *, auto succeed, auto fail) {
|
||||||
|
// ...
|
||||||
|
})
|
||||||
|
// appends a child in the form of another lambda function
|
||||||
|
.then([](auto delta, void *, auto succeed, auto fail) {
|
||||||
|
// ...
|
||||||
|
})
|
||||||
|
// appends a child in the form of a process class
|
||||||
|
.then<MyProcess>();
|
||||||
|
```
|
||||||
|
|
||||||
|
To update a scheduler and thus all its processes, the `update` member function
|
||||||
|
is the way to go:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// updates all the processes, no user data are provided
|
||||||
|
scheduler.update(delta);
|
||||||
|
|
||||||
|
// updates all the processes and provides them with custom data
|
||||||
|
scheduler.update(delta, &data);
|
||||||
|
```
|
||||||
|
|
||||||
|
In addition to these functions, the scheduler offers an `abort` member function
|
||||||
|
that can be used to discard all the running processes at once:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// aborts all the processes abruptly ...
|
||||||
|
scheduler.abort(true);
|
||||||
|
|
||||||
|
// ... or gracefully during the next tick
|
||||||
|
scheduler.abort();
|
||||||
|
```
|
||||||
240
docs/resource.md
Normal file
240
docs/resource.md
Normal file
@@ -0,0 +1,240 @@
|
|||||||
|
# Crash Course: resource management
|
||||||
|
|
||||||
|
<!--
|
||||||
|
@cond TURN_OFF_DOXYGEN
|
||||||
|
-->
|
||||||
|
# Table of Contents
|
||||||
|
|
||||||
|
* [Introduction](#introduction)
|
||||||
|
* [The resource, the loader and the cache](#the-resource-the-loader-and-the-cache)
|
||||||
|
<!--
|
||||||
|
@endcond TURN_OFF_DOXYGEN
|
||||||
|
-->
|
||||||
|
|
||||||
|
# Introduction
|
||||||
|
|
||||||
|
Resource management is usually one of the most critical part of a software like
|
||||||
|
a game. Solutions are often tuned to the particular application. There exist
|
||||||
|
several approaches and all of them are perfectly fine as long as they fit the
|
||||||
|
requirements of the piece of software in which they are used.<br/>
|
||||||
|
Examples are loading everything on start, loading on request, predictive
|
||||||
|
loading, and so on.
|
||||||
|
|
||||||
|
`EnTT` doesn't pretend to offer a _one-fits-all_ solution for the different
|
||||||
|
cases. Instead, it offers a minimal and perhaps trivial cache that can be useful
|
||||||
|
most of the time during prototyping and sometimes even in a production
|
||||||
|
environment.<br/>
|
||||||
|
For those interested in the subject, the plan is to improve it considerably over
|
||||||
|
time in terms of performance, memory usage and functionalities. Hoping to make
|
||||||
|
it, of course, one step at a time.
|
||||||
|
|
||||||
|
# The resource, the loader and the cache
|
||||||
|
|
||||||
|
There are three main actors in the model: the resource, the loader and the
|
||||||
|
cache.
|
||||||
|
|
||||||
|
The _resource_ is whatever users want it to be. An image, a video, an audio,
|
||||||
|
whatever. There are no limits.<br/>
|
||||||
|
As a minimal example:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
struct MyResource { const int value; };
|
||||||
|
```
|
||||||
|
|
||||||
|
A _loader_ is a class the aim of which is to load a specific resource. It has to
|
||||||
|
inherit directly from the dedicated base class as in the following example:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
struct MyLoader final: entt::ResourceLoader<MyLoader, MyResource> {
|
||||||
|
// ...
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Where `MyResource` is the type of resources it creates.<br/>
|
||||||
|
A resource loader must also expose a public const member function named `load`
|
||||||
|
that accepts a variable number of arguments and returns a shared pointer to a
|
||||||
|
resource.<br/>
|
||||||
|
As an example:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
struct MyLoader: entt::ResourceLoader<MyLoader, MyResource> {
|
||||||
|
std::shared_ptr<MyResource> load(int value) const {
|
||||||
|
// ...
|
||||||
|
return std::shared_ptr<MyResource>(new MyResource{ value });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
In general, resource loaders should not have a state or retain data of any type.
|
||||||
|
They should let the cache manage their resources instead.<br/>
|
||||||
|
As a side 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.
|
||||||
|
|
||||||
|
Finally, a cache is a specialization of a class template tailored to a specific
|
||||||
|
resource:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using MyResourceCache = entt::ResourceCache<MyResource>;
|
||||||
|
|
||||||
|
// ...
|
||||||
|
|
||||||
|
MyResourceCache cache{};
|
||||||
|
```
|
||||||
|
|
||||||
|
The idea is to create different caches for different types of resources and to
|
||||||
|
manage each one independently and in the most appropriate way.<br/>
|
||||||
|
As a (very) trivial example, audio tracks can survive in most of the scenes of
|
||||||
|
an application while meshes can be associated with a single scene and then
|
||||||
|
discarded when users leave it.
|
||||||
|
|
||||||
|
A cache offers a set of basic functionalities to query its internal state and to
|
||||||
|
_organize_ it:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// gets the number of resources managed by a cache
|
||||||
|
const auto size = cache.size();
|
||||||
|
|
||||||
|
// checks if a cache contains at least a valid resource
|
||||||
|
const auto empty = cache.empty();
|
||||||
|
|
||||||
|
// clears a cache and discards its content
|
||||||
|
cache.clear();
|
||||||
|
```
|
||||||
|
|
||||||
|
Besides these member functions, it contains what is needed to load, use and
|
||||||
|
discard resources of the given type.<br/>
|
||||||
|
Before to explore this part of the interface, it makes sense to mention how
|
||||||
|
resources are identified. The type of the identifiers to use is defined as:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
entt::ResourceCache<Resource>::resource_type
|
||||||
|
```
|
||||||
|
|
||||||
|
Where `resource_type` is an alias for `entt::HashedString`. Therefore, resource
|
||||||
|
identifiers are created explicitly as in the following example:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
constexpr auto identifier = entt::ResourceCache<Resource>::resource_type{"my/resource/identifier"};
|
||||||
|
// this is equivalent to the following
|
||||||
|
constexpr auto hs = entt::HashedString{"my/resource/identifier"};
|
||||||
|
```
|
||||||
|
|
||||||
|
The class `HashedString` is described in a dedicated section, so I won't do in
|
||||||
|
details here.
|
||||||
|
|
||||||
|
Resources are loaded and thus stored in a cache through the `load` member
|
||||||
|
function. It accepts the loader to use as a template parameter, the resource
|
||||||
|
identifier and the parameters used to construct the resource as arguments:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// uses the identifier declared above
|
||||||
|
cache.load<MyLoader>(identifier, 0);
|
||||||
|
|
||||||
|
// uses a const char * directly as an identifier
|
||||||
|
cache.load<MyLoader>("another/identifier", 42);
|
||||||
|
```
|
||||||
|
|
||||||
|
The return value can be used to know if the resource has been loaded correctly.
|
||||||
|
In case the loader returns an invalid pointer or the resource already exists in
|
||||||
|
the cache, a false value is returned:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
if(!cache.load<MyLoader>("another/identifier", 42)) {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Unfortunately, in this case there is no way to know what was the problem
|
||||||
|
exactly. However, before trying to load a resource or after an error, one can
|
||||||
|
use the `contains` member function to know if a cache already contains a
|
||||||
|
specific resource:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
auto exists = cache.contains("my/identifier");
|
||||||
|
```
|
||||||
|
|
||||||
|
There exists also a member function to use to force a reload of an already
|
||||||
|
existing resource if needed:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
auto result = cache.reload<MyLoader>("another/identifier", 42);
|
||||||
|
```
|
||||||
|
|
||||||
|
As above, the function returns true in case of success, false otherwise. The
|
||||||
|
sole difference in this case is that an error necessarily means that the loader
|
||||||
|
has failed for some reasons to load the resource.<br/>
|
||||||
|
Note that the `reload` member function is a kind of alias of the following
|
||||||
|
snippet:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
cache.discard(identifier);
|
||||||
|
cache.load<MyLoader>(identifier, 42);
|
||||||
|
```
|
||||||
|
|
||||||
|
Where the `discard` member function is used to get rid of a resource if loaded.
|
||||||
|
In case the cache doesn't contain a resource for the given identifier, the
|
||||||
|
function does nothing and returns immediately.
|
||||||
|
|
||||||
|
So far, so good. Resources are finally loaded and stored within the cache.<br/>
|
||||||
|
They are returned to users in the form of handles. To get one of them:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
auto handle = cache.handle("my/identifier");
|
||||||
|
```
|
||||||
|
|
||||||
|
The idea behind a handle is the same of the flyweight pattern. In other terms,
|
||||||
|
resources aren't copied around. Instead, instances are shared between handles.
|
||||||
|
Users of a resource owns a handle and it guarantees that a resource isn't
|
||||||
|
destroyed until all the handles are destroyed, even if the resource itself is
|
||||||
|
removed from the cache.<br/>
|
||||||
|
Handles are tiny objects both movable and copyable. They returns the contained
|
||||||
|
resource as a const reference on request:
|
||||||
|
|
||||||
|
* By means of the `get` member function:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
const auto &resource = handle.get();
|
||||||
|
```
|
||||||
|
|
||||||
|
* Using the proper cast operator:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
const auto &resource = handle;
|
||||||
|
```
|
||||||
|
|
||||||
|
* Through the dereference operator:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
const auto &resource = *handle;
|
||||||
|
```
|
||||||
|
|
||||||
|
The resource can also be accessed directly using the arrow operator if required:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
auto value = handle->value;
|
||||||
|
```
|
||||||
|
|
||||||
|
To test if a handle is still valid, the cast operator to `bool` allows users to
|
||||||
|
use it in a guard:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
if(handle) {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Finally, in case there is the need to load a resource and thus to get a handle
|
||||||
|
without storing the resource itself in the cache, users can rely on the `temp`
|
||||||
|
member function template.<br/>
|
||||||
|
The declaration is similar to the one of `load` but for the fact that it doesn't
|
||||||
|
return a boolean value. Instead, it returns a (possibly invalid) handle for the
|
||||||
|
resource:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
auto handle = cache.temp<MyLoader>("another/identifier", 42);
|
||||||
|
```
|
||||||
|
|
||||||
|
Do not forget to test the handle for validity. Otherwise, getting the reference
|
||||||
|
to the resource it points may result in undefined behavior.
|
||||||
39
docs/shared.md
Normal file
39
docs/shared.md
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
### EnTT and shared libraries
|
||||||
|
|
||||||
|
To make sure that an application and a shared library that use both `EnTT` can
|
||||||
|
interact correctly when symbols are hidden by default, there are some tricks to
|
||||||
|
follow.<br/>
|
||||||
|
In particular and in order to avoid undefined behaviors, all the instantiation
|
||||||
|
of the `Family` class template shall be made explicit along with the system-wide
|
||||||
|
specifier to use to export them.
|
||||||
|
|
||||||
|
At the time I'm writing this document, the classes that use internally the above
|
||||||
|
mentioned class template are `Dispatcher`, `Emitter` and `Registry`. Therefore
|
||||||
|
and as an example, if you use the `Registry` class template in your shared
|
||||||
|
library and want to set symbols visibility to _hidden_ by default, the following
|
||||||
|
lines are required to allow it to function properly with a client that also uses
|
||||||
|
the `Registry` somehow:
|
||||||
|
|
||||||
|
* On GNU/Linux:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
namespace entt {
|
||||||
|
template class __attribute__((visibility("default"))) Family<struct InternalRegistryTagFamily>;
|
||||||
|
template class __attribute__((visibility("default"))) Family<struct InternalRegistryComponentFamily>;
|
||||||
|
template class __attribute__((visibility("default"))) Family<struct InternalRegistryHandlerFamily>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
* On Windows:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
namespace entt {
|
||||||
|
template class __declspec(dllexport) Family<struct InternalRegistryTagFamily>;
|
||||||
|
template class __declspec(dllexport) Family<struct InternalRegistryComponentFamily>;
|
||||||
|
template class __declspec(dllexport) Family<struct InternalRegistryHandlerFamily>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Otherwise, the risk is that type identifiers are different between the shared
|
||||||
|
library and the application and this will prevent the whole thing from
|
||||||
|
functioning correctly for obvious reasons.
|
||||||
412
docs/signal.md
Normal file
412
docs/signal.md
Normal file
@@ -0,0 +1,412 @@
|
|||||||
|
# Crash Course: events, signals and everything in between
|
||||||
|
|
||||||
|
<!--
|
||||||
|
@cond TURN_OFF_DOXYGEN
|
||||||
|
-->
|
||||||
|
# Table of Contents
|
||||||
|
|
||||||
|
* [Introduction](#introduction)
|
||||||
|
* [Signals](#signals)
|
||||||
|
* [Delegate](#delegate)
|
||||||
|
* [Event dispatcher](#event-dispatcher)
|
||||||
|
* [Event emitter](#event-emitter)
|
||||||
|
<!--
|
||||||
|
@endcond TURN_OFF_DOXYGEN
|
||||||
|
-->
|
||||||
|
|
||||||
|
# Introduction
|
||||||
|
|
||||||
|
Signals are usually a core part of games and software architectures in
|
||||||
|
general.<br/>
|
||||||
|
Roughly speaking, they help to decouple the various parts of a system while
|
||||||
|
allowing them to communicate with each other somehow.
|
||||||
|
|
||||||
|
The so called _modern C++_ comes with a tool that can be useful in these terms,
|
||||||
|
the `std::function`. As an example, it can be used to create delegates.<br/>
|
||||||
|
However, there is no guarantee that an `std::function` does not perform
|
||||||
|
allocations under the hood and this could be problematic sometimes. Furthermore,
|
||||||
|
it solves a problem but may not adapt well to other requirements that may arise
|
||||||
|
from time to time.
|
||||||
|
|
||||||
|
In case that the flexibility and potential of an `std::function` are not
|
||||||
|
required or where you are looking for something different, `EnTT` offers a full
|
||||||
|
set of classes to solve completely different problems.
|
||||||
|
|
||||||
|
# Signals
|
||||||
|
|
||||||
|
Signal handlers work with naked pointers, function pointers and pointers to
|
||||||
|
member functions. Listeners can be any kind of objects and users are in charge
|
||||||
|
of connecting and disconnecting them from a signal to avoid crashes due to
|
||||||
|
different lifetimes. On the other side, performance shouldn't be affected that
|
||||||
|
much by the presence of such a signal handler.<br/>
|
||||||
|
A signal handler can be used as a private data member without exposing any
|
||||||
|
_publish_ functionality to the clients of a class. The basic idea is to impose a
|
||||||
|
clear separation between the signal itself and its _sink_ class, that is a tool
|
||||||
|
to be used to connect and disconnect listeners on the fly.
|
||||||
|
|
||||||
|
The API of a signal handler is straightforward. The most important thing is that
|
||||||
|
it comes in two forms: with and without a collector. In case a signal is
|
||||||
|
associated with a collector, all the values returned by the listeners can be
|
||||||
|
literally _collected_ and used later by the caller. Otherwise it works just like
|
||||||
|
a plain signal that emits events from time to time.<br/>
|
||||||
|
|
||||||
|
**Note**: collectors are allowed only in case of function types whose the return
|
||||||
|
type isn't `void` for obvious reasons.
|
||||||
|
|
||||||
|
To create instances of signal handlers there exist mainly two ways:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// no collector type
|
||||||
|
entt::SigH<void(int, char)> signal;
|
||||||
|
|
||||||
|
// explicit collector type
|
||||||
|
entt::SigH<void(int, char), MyCollector<bool>> collector;
|
||||||
|
```
|
||||||
|
|
||||||
|
As expected, they offer all the basic functionalities required to know how many
|
||||||
|
listeners they contain (`size`) or if they contain at least a listener (`empty`)
|
||||||
|
and even to swap two signal handlers (`swap`).
|
||||||
|
|
||||||
|
Besides them, there are member functions to use both to connect and disconnect
|
||||||
|
listeners in all their forms by means of a sink:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
void foo(int, char) { /* ... */ }
|
||||||
|
|
||||||
|
struct S {
|
||||||
|
void bar(int, char) { /* ... */ }
|
||||||
|
};
|
||||||
|
|
||||||
|
// ...
|
||||||
|
|
||||||
|
S instance;
|
||||||
|
|
||||||
|
signal.sink().connect<&foo>();
|
||||||
|
signal.sink().connect<S, &S::bar>(&instance);
|
||||||
|
|
||||||
|
// ...
|
||||||
|
|
||||||
|
// disconnects a free function
|
||||||
|
signal.sink().disconnect<&foo>();
|
||||||
|
|
||||||
|
// disconnect a specific member function of an instance ...
|
||||||
|
signal.sink().disconnect<S, &S::bar>(&instance);
|
||||||
|
|
||||||
|
// ... or an instance as a whole
|
||||||
|
signal.sink().disconnect(&instance);
|
||||||
|
|
||||||
|
// discards all the listeners at once
|
||||||
|
signal.sink().disconnect();
|
||||||
|
```
|
||||||
|
|
||||||
|
Once listeners are attached (or even if there are no listeners at all), events
|
||||||
|
and data in general can be published through a signal by means of the `publish`
|
||||||
|
member function:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
signal.publish(42, 'c');
|
||||||
|
```
|
||||||
|
|
||||||
|
To collect data, the `collect` member function should be used instead. Below is
|
||||||
|
a minimal example to show how to use it:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
struct MyCollector {
|
||||||
|
std::vector<int> vec{};
|
||||||
|
|
||||||
|
bool operator()(int v) noexcept {
|
||||||
|
vec.push_back(v);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
int f() { return 0; }
|
||||||
|
int g() { return 1; }
|
||||||
|
|
||||||
|
// ...
|
||||||
|
|
||||||
|
entt::SigH<int(), MyCollector<int>> signal;
|
||||||
|
|
||||||
|
signal.sink().connect<&f>();
|
||||||
|
signal.sink().connect<&g>();
|
||||||
|
|
||||||
|
MyCollector collector = signal.collect();
|
||||||
|
|
||||||
|
assert(collector.vec[0] == 0);
|
||||||
|
assert(collector.vec[1] == 1);
|
||||||
|
```
|
||||||
|
|
||||||
|
As shown above, a collector must expose a function operator that accepts as an
|
||||||
|
argument a type to which the return type of the listeners can be converted.
|
||||||
|
Moreover, it has to return a boolean value that is false to stop collecting
|
||||||
|
data, true otherwise. This way one can avoid calling all the listeners in case
|
||||||
|
it isn't necessary.
|
||||||
|
|
||||||
|
# Delegate
|
||||||
|
|
||||||
|
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.<br/>
|
||||||
|
It does not claim to be a drop-in replacement for an `std::function`, so do not
|
||||||
|
expect to use it whenever an `std::function` fits well. However, it can be used
|
||||||
|
to send opaque delegates around to be used to invoke functions as needed.
|
||||||
|
|
||||||
|
The interface is trivial. It offers a default constructor to create empty
|
||||||
|
delegates:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
entt::Delegate<int(int)> delegate{};
|
||||||
|
```
|
||||||
|
|
||||||
|
All what is needed to create an instance is to specify the type of the function
|
||||||
|
the delegate will _contain_, that is the signature of the free function or the
|
||||||
|
member function one wants to assign to it.
|
||||||
|
|
||||||
|
Attempting to use an empty delegate by invoking its function call operator
|
||||||
|
results in undefined behavior, most likely a crash actually. Before to use a
|
||||||
|
delegate, it must be initialized.<br/>
|
||||||
|
There exist two functions to do that, both named `connect`:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
int f(int i) { return i; }
|
||||||
|
|
||||||
|
struct MyStruct {
|
||||||
|
int f(int i) { return i }
|
||||||
|
};
|
||||||
|
|
||||||
|
// bind a free function to the delegate
|
||||||
|
delegate.connect<&f>();
|
||||||
|
|
||||||
|
// bind a member function to the delegate
|
||||||
|
MyStruct instance;
|
||||||
|
delegate.connect<MyStruct, &MyStruct::f>(&instance);
|
||||||
|
```
|
||||||
|
|
||||||
|
It hasn't a `disconnect` counterpart. Instead, there exists a `reset` member
|
||||||
|
function to clear it.<br/>
|
||||||
|
The `empty` member function can be used to know if a delegate is empty:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
const auto empty = delegate.empty();
|
||||||
|
```
|
||||||
|
|
||||||
|
Finally, to invoke a delegate, the function call operator is the way to go as
|
||||||
|
usual:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
auto ret = delegate(42);
|
||||||
|
```
|
||||||
|
|
||||||
|
Probably too much small and pretty poor of functionalities, but the delegate
|
||||||
|
class can help in a lot of cases and it has shown that it is worth keeping it
|
||||||
|
within the library.
|
||||||
|
|
||||||
|
# Event dispatcher
|
||||||
|
|
||||||
|
The event dispatcher class is designed so as to be used in a loop. It allows
|
||||||
|
users both to trigger immediate events or to queue events to be published all
|
||||||
|
together once per tick.<br/>
|
||||||
|
This class shares part of its API with the one of the signal handler, but it
|
||||||
|
doesn't require that all the types of events are specified when declared:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// define a general purpose dispatcher that works with naked pointers
|
||||||
|
entt::Dispatcher dispatcher{};
|
||||||
|
```
|
||||||
|
|
||||||
|
In order to register an instance of a class to a dispatcher, its type must
|
||||||
|
expose one or more member functions of which the return types are `void` and the
|
||||||
|
argument lists are `const E &`, for each type of event `E`.<br/>
|
||||||
|
To ease the development, member functions that are named `receive` are
|
||||||
|
automatically detected and have not to be explicitly specified when registered.
|
||||||
|
In all the other cases, the name of the member function aimed to receive the
|
||||||
|
event must be provided to the `connect` member function of the sink bound to the
|
||||||
|
specific event:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
struct AnEvent { int value; };
|
||||||
|
struct AnotherEvent {};
|
||||||
|
|
||||||
|
struct Listener
|
||||||
|
{
|
||||||
|
void receive(const AnEvent &) { /* ... */ }
|
||||||
|
void method(const AnotherEvent &) { /* ... */ }
|
||||||
|
};
|
||||||
|
|
||||||
|
// ...
|
||||||
|
|
||||||
|
Listener listener;
|
||||||
|
dispatcher.sink<AnEvent>().connect(&listener);
|
||||||
|
dispatcher.sink<AnotherEvent>().connect<Listener, &Listener::method>(&listener);
|
||||||
|
```
|
||||||
|
|
||||||
|
The `disconnect` member function follows the same pattern and can be used to
|
||||||
|
selectively remove listeners:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
dispatcher.sink<AnEvent>().disconnect(&listener);
|
||||||
|
dispatcher.sink<AnotherEvent>().disconnect<Listener, &Listener::method>(&listener);
|
||||||
|
```
|
||||||
|
|
||||||
|
The `trigger` member function serves the purpose of sending an immediate event
|
||||||
|
to all the listeners registered so far. It offers a convenient approach that
|
||||||
|
relieves users from having to create the event itself. Instead, it's enough to
|
||||||
|
specify the type of event and provide all the parameters required to construct
|
||||||
|
it.<br/>
|
||||||
|
As an example:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
dispatcher.trigger<AnEvent>(42);
|
||||||
|
dispatcher.trigger<AnotherEvent>();
|
||||||
|
```
|
||||||
|
|
||||||
|
Listeners are invoked immediately, order of execution isn't guaranteed. This
|
||||||
|
method can be used to push around urgent messages like an _is terminating_
|
||||||
|
notification on a mobile app.
|
||||||
|
|
||||||
|
On the other hand, the `enqueue` member function queues messages together and
|
||||||
|
allows to maintain control over the moment they are sent to listeners. The
|
||||||
|
signature of this method is more or less the same of `trigger`:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
dispatcher.enqueue<AnEvent>(42);
|
||||||
|
dispatcher.enqueue<AnotherEvent>();
|
||||||
|
```
|
||||||
|
|
||||||
|
Events are stored aside until the `update` member function is invoked, then all
|
||||||
|
the messages that are still pending are sent to the listeners at once:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// emits all the events of the given type at once
|
||||||
|
dispatcher.update<MyEvent>();
|
||||||
|
|
||||||
|
// emits all the events queued so far at once
|
||||||
|
dispatcher.update();
|
||||||
|
```
|
||||||
|
|
||||||
|
This way users can embed the dispatcher in a loop and literally dispatch events
|
||||||
|
once per tick to their systems.
|
||||||
|
|
||||||
|
# Event emitter
|
||||||
|
|
||||||
|
A general purpose event emitter thought mainly for those cases where it comes to
|
||||||
|
working with asynchronous stuff.<br/>
|
||||||
|
Originally designed to fit the requirements of
|
||||||
|
[`uvw`](https://github.com/skypjack/uvw) (a wrapper for `libuv` written in
|
||||||
|
modern C++), it was adapted later to be included in this library.
|
||||||
|
|
||||||
|
To create a custom emitter type, derived classes must inherit directly from the
|
||||||
|
base class as:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
struct MyEmitter: Emitter<MyEmitter> {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The full list of accepted types of events isn't required. Handlers are created
|
||||||
|
internally on the fly and thus each type of event is accepted by default.
|
||||||
|
|
||||||
|
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 itself.<br/>
|
||||||
|
In addition, an opaque object is returned each time a connection is established
|
||||||
|
between an emitter and a listener, allowing the caller to disconnect them at a
|
||||||
|
later time.<br/>
|
||||||
|
The opaque object used to handle connections is both movable and copyable. On
|
||||||
|
the other side, an event emitter is movable but not copyable by default.
|
||||||
|
|
||||||
|
To create new instances of an emitter, no arguments are required:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
MyEmitter emitter{};
|
||||||
|
```
|
||||||
|
|
||||||
|
Listeners must be movable and callable objects (free functions, lambdas,
|
||||||
|
functors, `std::function`s, whatever) whose function type is:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
void(const Event &, MyEmitter &)
|
||||||
|
```
|
||||||
|
|
||||||
|
Where `Event` is the type of event they want to listen.<br/>
|
||||||
|
There are two ways to attach a listener to an event emitter that differ
|
||||||
|
slightly from each other:
|
||||||
|
|
||||||
|
* To register a long-lived listener, use the `on` member function. It is meant
|
||||||
|
to register a listener designed to be invoked more than once for the given
|
||||||
|
event type.<br/>
|
||||||
|
As an example:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
auto conn = emitter.on<MyEvent>([](const MyEvent &event, MyEmitter &emitter) {
|
||||||
|
// ...
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
The connection object can be freely discarded. Otherwise, it can be used later
|
||||||
|
to disconnect the listener if required.
|
||||||
|
|
||||||
|
* To register a short-lived listener, use the `once` member function. It is
|
||||||
|
meant to register a listener designed to be invoked only once for the given
|
||||||
|
event type. The listener is automatically disconnected after the first
|
||||||
|
invocation.<br/>
|
||||||
|
As an example:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
auto conn = emitter.once<MyEvent>([](const MyEvent &event, MyEmitter &emitter) {
|
||||||
|
// ...
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
The connection object can be freely discarded. Otherwise, it can be used later
|
||||||
|
to disconnect the listener if required.
|
||||||
|
|
||||||
|
In both cases, the connection object can be used with the `erase` member
|
||||||
|
function:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
emitter.erase(conn);
|
||||||
|
```
|
||||||
|
|
||||||
|
There are also two member functions to use either to disconnect all the
|
||||||
|
listeners for a given type of event or to clear the emitter:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// removes all the listener for the specific event
|
||||||
|
emitter.clear<MyEvent>();
|
||||||
|
|
||||||
|
// removes all the listeners registered so far
|
||||||
|
emitter.clear();
|
||||||
|
```
|
||||||
|
|
||||||
|
To send an event to all the listeners that are interested in it, the `publish`
|
||||||
|
member function offers a convenient approach that relieves users from having to
|
||||||
|
create the event:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
struct MyEvent { int i; };
|
||||||
|
|
||||||
|
// ...
|
||||||
|
|
||||||
|
emitter.publish<MyEvent>(42);
|
||||||
|
```
|
||||||
|
|
||||||
|
Finally, the `empty` member function tests if there exists at least either a
|
||||||
|
listener registered with the event emitter or to a given type of event:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
bool empty;
|
||||||
|
|
||||||
|
// checks if there is any listener registered for the specific event
|
||||||
|
empty = emitter.empty<MyEvent>();
|
||||||
|
|
||||||
|
// checks it there are listeners registered with the event emitter
|
||||||
|
empty = emitter.empty();
|
||||||
|
```
|
||||||
|
|
||||||
|
In general, the event emitter is a handy tool when the derived classes _wrap_
|
||||||
|
asynchronous operations, because it introduces a _nice-to-have_ model based on
|
||||||
|
events and listeners that kindly hides the complexity behind the scenes. However
|
||||||
|
it is not limited to such uses.
|
||||||
@@ -52,7 +52,7 @@ public:
|
|||||||
* @tparam N Number of characters of the identifier.
|
* @tparam N Number of characters of the identifier.
|
||||||
* @param str Human-readable identifer.
|
* @param str Human-readable identifer.
|
||||||
*/
|
*/
|
||||||
template <std::size_t N>
|
template<std::size_t N>
|
||||||
constexpr HashedString(const char (&str)[N]) ENTT_NOEXCEPT
|
constexpr HashedString(const char (&str)[N]) ENTT_NOEXCEPT
|
||||||
: hash{helper(offset, str)}, str{str}
|
: hash{helper(offset, str)}, str{str}
|
||||||
{}
|
{}
|
||||||
|
|||||||
@@ -12,15 +12,15 @@
|
|||||||
namespace entt {
|
namespace entt {
|
||||||
|
|
||||||
|
|
||||||
namespace internal {
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @cond TURN_OFF_DOXYGEN
|
* @cond TURN_OFF_DOXYGEN
|
||||||
* Internal details not to be documented.
|
* Internal details not to be documented.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
|
||||||
template<typename...>
|
template<typename...>
|
||||||
struct IsPartOf;
|
struct IsPartOf;
|
||||||
|
|
||||||
@@ -31,15 +31,15 @@ template<typename Type>
|
|||||||
struct IsPartOf<Type>: std::false_type {};
|
struct IsPartOf<Type>: std::false_type {};
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal details not to be documented.
|
* Internal details not to be documented.
|
||||||
* @endcond TURN_OFF_DOXYGEN
|
* @endcond TURN_OFF_DOXYGEN
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Types identifiers.
|
* @brief Types identifiers.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include <utility>
|
#include <utility>
|
||||||
#include "../config/config.h"
|
#include "../config/config.h"
|
||||||
#include "registry.hpp"
|
#include "registry.hpp"
|
||||||
|
#include "entity.hpp"
|
||||||
|
|
||||||
|
|
||||||
namespace entt {
|
namespace entt {
|
||||||
@@ -31,23 +32,54 @@ struct Actor {
|
|||||||
* @param reg An entity-component system properly initialized.
|
* @param reg An entity-component system properly initialized.
|
||||||
*/
|
*/
|
||||||
Actor(Registry<Entity> ®)
|
Actor(Registry<Entity> ®)
|
||||||
: reg{reg}, entt{reg.create()}
|
: reg{®}, entt{reg.create()}
|
||||||
{}
|
{}
|
||||||
|
|
||||||
/*! @brief Default destructor. */
|
/*! @brief Default destructor. */
|
||||||
virtual ~Actor() {
|
virtual ~Actor() {
|
||||||
reg.destroy(entt);
|
reg->destroy(entt);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*! @brief Default copy constructor. */
|
/*! @brief Copying an actor isn't allowed. */
|
||||||
Actor(const Actor &) = default;
|
Actor(const Actor &) = delete;
|
||||||
/*! @brief Default move constructor. */
|
|
||||||
Actor(Actor &&) = default;
|
/**
|
||||||
|
* @brief Move constructor.
|
||||||
|
*
|
||||||
|
* After actor move construction, instances that have been moved from are
|
||||||
|
* placed in a valid but unspecified state. It's highly discouraged to
|
||||||
|
* continue using them.
|
||||||
|
*
|
||||||
|
* @param other The instance to move from.
|
||||||
|
*/
|
||||||
|
Actor(Actor &&other)
|
||||||
|
: reg{other.reg}, entt{other.entt}
|
||||||
|
{
|
||||||
|
other.entt = entt::null;
|
||||||
|
}
|
||||||
|
|
||||||
/*! @brief Default copy assignment operator. @return This actor. */
|
/*! @brief Default copy assignment operator. @return This actor. */
|
||||||
Actor & operator=(const Actor &) = default;
|
Actor & operator=(const Actor &) = delete;
|
||||||
/*! @brief Default move assignment operator. @return This actor. */
|
|
||||||
Actor & operator=(Actor &&) = default;
|
/**
|
||||||
|
* @brief Move assignment operator.
|
||||||
|
*
|
||||||
|
* After actor move assignment, instances that have been moved from are
|
||||||
|
* placed in a valid but unspecified state. It's highly discouraged to
|
||||||
|
* continue using them.
|
||||||
|
*
|
||||||
|
* @param other The instance to move from.
|
||||||
|
* @return This actor.
|
||||||
|
*/
|
||||||
|
Actor & operator=(Actor &&other) {
|
||||||
|
if(this != &other) {
|
||||||
|
auto tmp{std::move(other)};
|
||||||
|
std::swap(reg, tmp.reg);
|
||||||
|
std::swap(entt, tmp.entt);
|
||||||
|
}
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Assigns the given tag to an actor.
|
* @brief Assigns the given tag to an actor.
|
||||||
@@ -64,7 +96,7 @@ struct Actor {
|
|||||||
*/
|
*/
|
||||||
template<typename Tag, typename... Args>
|
template<typename Tag, typename... Args>
|
||||||
Tag & assign(tag_t, Args &&... args) {
|
Tag & assign(tag_t, Args &&... args) {
|
||||||
return (reg.template remove<Tag>(), reg.template assign<Tag>(tag_t{}, entt, std::forward<Args>(args)...));
|
return (reg->template remove<Tag>(), reg->template assign<Tag>(tag_t{}, entt, std::forward<Args>(args)...));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -83,7 +115,7 @@ struct Actor {
|
|||||||
*/
|
*/
|
||||||
template<typename Component, typename... Args>
|
template<typename Component, typename... Args>
|
||||||
Component & assign(Args &&... args) {
|
Component & assign(Args &&... args) {
|
||||||
return reg.template accommodate<Component>(entt, std::forward<Args>(args)...);
|
return reg->template accommodate<Component>(entt, std::forward<Args>(args)...);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -93,7 +125,7 @@ struct Actor {
|
|||||||
template<typename Tag>
|
template<typename Tag>
|
||||||
void remove(tag_t) {
|
void remove(tag_t) {
|
||||||
assert(has<Tag>(tag_t{}));
|
assert(has<Tag>(tag_t{}));
|
||||||
reg.template remove<Tag>();
|
reg->template remove<Tag>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -102,7 +134,7 @@ struct Actor {
|
|||||||
*/
|
*/
|
||||||
template<typename Component>
|
template<typename Component>
|
||||||
void remove() {
|
void remove() {
|
||||||
reg.template remove<Component>(entt);
|
reg->template remove<Component>(entt);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -112,7 +144,7 @@ struct Actor {
|
|||||||
*/
|
*/
|
||||||
template<typename Tag>
|
template<typename Tag>
|
||||||
bool has(tag_t) const ENTT_NOEXCEPT {
|
bool has(tag_t) const ENTT_NOEXCEPT {
|
||||||
return (reg.template has<Tag>() && (reg.template attachee<Tag>() == entt));
|
return (reg->template has<Tag>() && (reg->template attachee<Tag>() == entt));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -122,7 +154,7 @@ struct Actor {
|
|||||||
*/
|
*/
|
||||||
template<typename Component>
|
template<typename Component>
|
||||||
bool has() const ENTT_NOEXCEPT {
|
bool has() const ENTT_NOEXCEPT {
|
||||||
return reg.template has<Component>(entt);
|
return reg->template has<Component>(entt);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -133,7 +165,7 @@ struct Actor {
|
|||||||
template<typename Tag>
|
template<typename Tag>
|
||||||
const Tag & get(tag_t) const ENTT_NOEXCEPT {
|
const Tag & get(tag_t) const ENTT_NOEXCEPT {
|
||||||
assert(has<Tag>(tag_t{}));
|
assert(has<Tag>(tag_t{}));
|
||||||
return reg.template get<Tag>();
|
return reg->template get<Tag>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -153,7 +185,7 @@ struct Actor {
|
|||||||
*/
|
*/
|
||||||
template<typename Component>
|
template<typename Component>
|
||||||
const Component & get() const ENTT_NOEXCEPT {
|
const Component & get() const ENTT_NOEXCEPT {
|
||||||
return reg.template get<Component>(entt);
|
return reg->template get<Component>(entt);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -171,7 +203,7 @@ struct Actor {
|
|||||||
* @return A reference to the underlying registry.
|
* @return A reference to the underlying registry.
|
||||||
*/
|
*/
|
||||||
inline const registry_type & registry() const ENTT_NOEXCEPT {
|
inline const registry_type & registry() const ENTT_NOEXCEPT {
|
||||||
return reg;
|
return *reg;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -191,7 +223,7 @@ struct Actor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
registry_type ®
|
registry_type * reg;
|
||||||
Entity entt;
|
Entity entt;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
230
src/entt/entity/attachee.hpp
Normal file
230
src/entt/entity/attachee.hpp
Normal file
@@ -0,0 +1,230 @@
|
|||||||
|
#ifndef ENTT_ENTITY_ATTACHEE_HPP
|
||||||
|
#define ENTT_ENTITY_ATTACHEE_HPP
|
||||||
|
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <utility>
|
||||||
|
#include <type_traits>
|
||||||
|
#include "../config/config.h"
|
||||||
|
#include "entity.hpp"
|
||||||
|
|
||||||
|
|
||||||
|
namespace entt {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Attachee.
|
||||||
|
*
|
||||||
|
* Primary template isn't defined on purpose. All the specializations give a
|
||||||
|
* compile-time error, but for a few reasonable cases.
|
||||||
|
*/
|
||||||
|
template<typename...>
|
||||||
|
class Attachee;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Basic attachee implementation.
|
||||||
|
*
|
||||||
|
* Convenience data structure used to store single instance components.
|
||||||
|
*
|
||||||
|
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||||
|
*/
|
||||||
|
template<typename Entity>
|
||||||
|
class Attachee<Entity> {
|
||||||
|
public:
|
||||||
|
/*! @brief Underlying entity identifier. */
|
||||||
|
using entity_type = Entity;
|
||||||
|
|
||||||
|
/*! @brief Default constructor. */
|
||||||
|
Attachee() ENTT_NOEXCEPT
|
||||||
|
: owner{null}
|
||||||
|
{}
|
||||||
|
|
||||||
|
/*! @brief Default copy constructor. */
|
||||||
|
Attachee(const Attachee &) = default;
|
||||||
|
/*! @brief Default move constructor. */
|
||||||
|
Attachee(Attachee &&) = default;
|
||||||
|
|
||||||
|
/*! @brief Default copy assignment operator. @return This attachee. */
|
||||||
|
Attachee & operator=(const Attachee &) = default;
|
||||||
|
/*! @brief Default move assignment operator. @return This attachee. */
|
||||||
|
Attachee & operator=(Attachee &&) = default;
|
||||||
|
|
||||||
|
/*! @brief Default destructor. */
|
||||||
|
virtual ~Attachee() ENTT_NOEXCEPT = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the owner of an attachee.
|
||||||
|
* @return A valid entity identifier if an owner exists, the null entity
|
||||||
|
* identifier otherwise.
|
||||||
|
*/
|
||||||
|
inline entity_type get() const ENTT_NOEXCEPT {
|
||||||
|
return owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Assigns an entity to an attachee.
|
||||||
|
*
|
||||||
|
* @warning
|
||||||
|
* Attempting to assigns an entity to an attachee that already has an owner
|
||||||
|
* results in undefined behavior.<br/>
|
||||||
|
* An assertion will abort the execution at runtime in debug mode in case
|
||||||
|
* the attachee already has an owner.
|
||||||
|
*
|
||||||
|
* @param entity A valid entity identifier.
|
||||||
|
*/
|
||||||
|
inline void construct(const entity_type entity) ENTT_NOEXCEPT {
|
||||||
|
assert(owner == null);
|
||||||
|
owner = entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Removes an entity from an attachee.
|
||||||
|
*
|
||||||
|
* @warning
|
||||||
|
* Attempting to free an empty attachee results in undefined behavior.<br/>
|
||||||
|
* An assertion will abort the execution at runtime in debug mode if the
|
||||||
|
* attachee is already empty.
|
||||||
|
*/
|
||||||
|
virtual void destroy() ENTT_NOEXCEPT {
|
||||||
|
assert(owner != null);
|
||||||
|
owner = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
entity_type owner;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Extended attachee implementation.
|
||||||
|
*
|
||||||
|
* This specialization of an attachee associates an object to an entity. The
|
||||||
|
* main purpose of this class is to use attachees to store tags in a Registry.
|
||||||
|
* It guarantees fast access both to the element and to the entity.
|
||||||
|
*
|
||||||
|
* @sa Attachee<Entity>
|
||||||
|
*
|
||||||
|
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||||
|
* @tparam Type Type of object assigned to the entity.
|
||||||
|
*/
|
||||||
|
template<typename Entity, typename Type>
|
||||||
|
class Attachee<Entity, Type>: public Attachee<Entity> {
|
||||||
|
using underlying_type = Attachee<Entity>;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/*! @brief Type of the object associated to the attachee. */
|
||||||
|
using object_type = Type;
|
||||||
|
/*! @brief Underlying entity identifier. */
|
||||||
|
using entity_type = typename underlying_type::entity_type;
|
||||||
|
|
||||||
|
/*! @brief Default constructor. */
|
||||||
|
Attachee() ENTT_NOEXCEPT = default;
|
||||||
|
|
||||||
|
/*! @brief Copying an attachee isn't allowed. */
|
||||||
|
Attachee(const Attachee &) = delete;
|
||||||
|
/*! @brief Moving an attachee isn't allowed. */
|
||||||
|
Attachee(Attachee &&) = delete;
|
||||||
|
|
||||||
|
/*! @brief Copying an attachee isn't allowed. @return This attachee. */
|
||||||
|
Attachee & operator=(const Attachee &) = delete;
|
||||||
|
/*! @brief Moving an attachee isn't allowed. @return This attachee. */
|
||||||
|
Attachee & operator=(Attachee &&) = delete;
|
||||||
|
|
||||||
|
/*! @brief Default destructor. */
|
||||||
|
~Attachee() {
|
||||||
|
if(underlying_type::get() != null) {
|
||||||
|
reinterpret_cast<Type *>(&storage)->~Type();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the object associated to an attachee.
|
||||||
|
*
|
||||||
|
* @warning
|
||||||
|
* Attempting to query an empty attachee results in undefined behavior.<br/>
|
||||||
|
* An assertion will abort the execution at runtime in debug mode if the
|
||||||
|
* attachee is empty.
|
||||||
|
*
|
||||||
|
* @return The object associated to the attachee.
|
||||||
|
*/
|
||||||
|
const Type & get() const ENTT_NOEXCEPT {
|
||||||
|
assert(underlying_type::get() != null);
|
||||||
|
return *reinterpret_cast<const Type *>(&storage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the object associated to an attachee.
|
||||||
|
*
|
||||||
|
* @warning
|
||||||
|
* Attempting to query an empty attachee results in undefined behavior.<br/>
|
||||||
|
* An assertion will abort the execution at runtime in debug mode if the
|
||||||
|
* attachee is empty.
|
||||||
|
*
|
||||||
|
* @return The object associated to the attachee.
|
||||||
|
*/
|
||||||
|
Type & get() ENTT_NOEXCEPT {
|
||||||
|
return const_cast<Type &>(const_cast<const Attachee *>(this)->get());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Assigns an entity to an attachee and constructs its object.
|
||||||
|
*
|
||||||
|
* @warning
|
||||||
|
* Attempting to assigns an entity to an attachee that already has an owner
|
||||||
|
* results in undefined behavior.<br/>
|
||||||
|
* An assertion will abort the execution at runtime in debug mode in case
|
||||||
|
* the attachee already has an owner.
|
||||||
|
*
|
||||||
|
* @tparam Args Types of arguments to use to construct the object.
|
||||||
|
* @param entity A valid entity identifier.
|
||||||
|
* @param args Parameters to use to construct an object for the entity.
|
||||||
|
* @return The object associated to the attachee.
|
||||||
|
*/
|
||||||
|
template<typename... Args>
|
||||||
|
Type & construct(entity_type entity, Args &&... args) ENTT_NOEXCEPT {
|
||||||
|
underlying_type::construct(entity);
|
||||||
|
new (&storage) Type{std::forward<Args>(args)...};
|
||||||
|
return *reinterpret_cast<Type *>(&storage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Removes an entity from an attachee and destroies its object.
|
||||||
|
*
|
||||||
|
* @warning
|
||||||
|
* Attempting to free an empty attachee results in undefined behavior.<br/>
|
||||||
|
* An assertion will abort the execution at runtime in debug mode if the
|
||||||
|
* attachee is already empty.
|
||||||
|
*/
|
||||||
|
void destroy() ENTT_NOEXCEPT override {
|
||||||
|
reinterpret_cast<Type *>(&storage)->~Type();
|
||||||
|
underlying_type::destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Changes the owner of an attachee.
|
||||||
|
*
|
||||||
|
* The ownership of the attachee is transferred from one entity to another.
|
||||||
|
*
|
||||||
|
* @warning
|
||||||
|
* Attempting to transfer the ownership of an attachee that hasn't an owner
|
||||||
|
* results in undefined behavior.<br/>
|
||||||
|
* An assertion will abort the execution at runtime in debug mode in case
|
||||||
|
* the attachee hasn't an owner yet.
|
||||||
|
*
|
||||||
|
* @param entity A valid entity identifier.
|
||||||
|
*/
|
||||||
|
void move(const entity_type entity) ENTT_NOEXCEPT {
|
||||||
|
underlying_type::destroy();
|
||||||
|
underlying_type::construct(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::aligned_storage_t<sizeof(Type), alignof(Type)> storage;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#endif // ENTT_ENTITY_ATTACHEE_HPP
|
||||||
@@ -9,17 +9,13 @@
|
|||||||
namespace entt {
|
namespace entt {
|
||||||
|
|
||||||
|
|
||||||
namespace internal {
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @cond TURN_OFF_DOXYGEN
|
* @cond TURN_OFF_DOXYGEN
|
||||||
* Internal details not to be documented.
|
* Internal details not to be documented.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
template<typename Entity>
|
namespace internal {
|
||||||
static constexpr auto null = ~typename entt_traits<Entity>::entity_type{};
|
|
||||||
|
|
||||||
|
|
||||||
struct Null {
|
struct Null {
|
||||||
@@ -27,7 +23,8 @@ struct Null {
|
|||||||
|
|
||||||
template<typename Entity>
|
template<typename Entity>
|
||||||
constexpr operator Entity() const ENTT_NOEXCEPT {
|
constexpr operator Entity() const ENTT_NOEXCEPT {
|
||||||
return null<Entity>;
|
using traits_type = entt::entt_traits<Entity>;
|
||||||
|
return traits_type::entity_mask | (traits_type::version_mask << traits_type::entity_shift);
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr bool operator==(Null) const ENTT_NOEXCEPT {
|
constexpr bool operator==(Null) const ENTT_NOEXCEPT {
|
||||||
@@ -40,12 +37,12 @@ struct Null {
|
|||||||
|
|
||||||
template<typename Entity>
|
template<typename Entity>
|
||||||
constexpr bool operator==(const Entity entity) const ENTT_NOEXCEPT {
|
constexpr bool operator==(const Entity entity) const ENTT_NOEXCEPT {
|
||||||
return entity == null<Entity>;
|
return entity == static_cast<Entity>(*this);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename Entity>
|
template<typename Entity>
|
||||||
constexpr bool operator!=(const Entity entity) const ENTT_NOEXCEPT {
|
constexpr bool operator!=(const Entity entity) const ENTT_NOEXCEPT {
|
||||||
return entity != null<Entity>;
|
return entity != static_cast<Entity>(*this);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -62,15 +59,15 @@ constexpr bool operator!=(const Entity entity, Null null) ENTT_NOEXCEPT {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal details not to be documented.
|
* Internal details not to be documented.
|
||||||
* @endcond TURN_OFF_DOXYGEN
|
* @endcond TURN_OFF_DOXYGEN
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Null entity.
|
* @brief Null entity.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -36,9 +36,9 @@ struct entt_traits<std::uint16_t> {
|
|||||||
using difference_type = std::int32_t;
|
using difference_type = std::int32_t;
|
||||||
|
|
||||||
/*! @brief Mask to use to get the entity number out of an identifier. */
|
/*! @brief Mask to use to get the entity number out of an identifier. */
|
||||||
static constexpr auto entity_mask = 0xFFF;
|
static constexpr std::uint16_t entity_mask = 0xFFF;
|
||||||
/*! @brief Mask to use to get the version out of an identifier. */
|
/*! @brief Mask to use to get the version out of an identifier. */
|
||||||
static constexpr auto version_mask = 0xF;
|
static constexpr std::uint16_t version_mask = 0xF;
|
||||||
/*! @brief Extent of the entity number within an identifier. */
|
/*! @brief Extent of the entity number within an identifier. */
|
||||||
static constexpr auto entity_shift = 12;
|
static constexpr auto entity_shift = 12;
|
||||||
};
|
};
|
||||||
@@ -62,9 +62,9 @@ struct entt_traits<std::uint32_t> {
|
|||||||
using difference_type = std::int64_t;
|
using difference_type = std::int64_t;
|
||||||
|
|
||||||
/*! @brief Mask to use to get the entity number out of an identifier. */
|
/*! @brief Mask to use to get the entity number out of an identifier. */
|
||||||
static constexpr auto entity_mask = 0xFFFFF;
|
static constexpr std::uint32_t entity_mask = 0xFFFFF;
|
||||||
/*! @brief Mask to use to get the version out of an identifier. */
|
/*! @brief Mask to use to get the version out of an identifier. */
|
||||||
static constexpr auto version_mask = 0xFFF;
|
static constexpr std::uint32_t version_mask = 0xFFF;
|
||||||
/*! @brief Extent of the entity number within an identifier. */
|
/*! @brief Extent of the entity number within an identifier. */
|
||||||
static constexpr auto entity_shift = 20;
|
static constexpr auto entity_shift = 20;
|
||||||
};
|
};
|
||||||
@@ -88,9 +88,9 @@ struct entt_traits<std::uint64_t> {
|
|||||||
using difference_type = std::int64_t;
|
using difference_type = std::int64_t;
|
||||||
|
|
||||||
/*! @brief Mask to use to get the entity number out of an identifier. */
|
/*! @brief Mask to use to get the entity number out of an identifier. */
|
||||||
static constexpr auto entity_mask = 0xFFFFFFFF;
|
static constexpr std::uint64_t entity_mask = 0xFFFFFFFF;
|
||||||
/*! @brief Mask to use to get the version out of an identifier. */
|
/*! @brief Mask to use to get the version out of an identifier. */
|
||||||
static constexpr auto version_mask = 0xFFFFFFFF;
|
static constexpr std::uint64_t version_mask = 0xFFFFFFFF;
|
||||||
/*! @brief Extent of the entity number within an identifier. */
|
/*! @brief Extent of the entity number within an identifier. */
|
||||||
static constexpr auto entity_shift = 32;
|
static constexpr auto entity_shift = 32;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ void dependency(Registry<Entity> ®istry, const Entity entity) {
|
|||||||
* assigned to an entity:
|
* assigned to an entity:
|
||||||
* @code{.cpp}
|
* @code{.cpp}
|
||||||
* entt::DefaultRegistry registry;
|
* entt::DefaultRegistry registry;
|
||||||
* entt::dependency<AType, AnotherType>(registry.construction<MyType>());
|
* entt::connect<AType, AnotherType>(registry.construction<MyType>());
|
||||||
* @endcode
|
* @endcode
|
||||||
*
|
*
|
||||||
* @tparam Dependency Types of components to assign to an entity if triggered.
|
* @tparam Dependency Types of components to assign to an entity if triggered.
|
||||||
@@ -52,7 +52,7 @@ void dependency(Registry<Entity> ®istry, const Entity entity) {
|
|||||||
* @param sink A sink object properly initialized.
|
* @param sink A sink object properly initialized.
|
||||||
*/
|
*/
|
||||||
template<typename... Dependency, typename Entity>
|
template<typename... Dependency, typename Entity>
|
||||||
void dependency(Sink<void(Registry<Entity> &, const Entity)> sink) {
|
inline void connect(Sink<void(Registry<Entity> &, const Entity)> sink) {
|
||||||
sink.template connect<dependency<Entity, Dependency...>>();
|
sink.template connect<dependency<Entity, Dependency...>>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,7 +67,7 @@ void dependency(Sink<void(Registry<Entity> &, const Entity)> sink) {
|
|||||||
* components `AType` and `AnotherType`:
|
* components `AType` and `AnotherType`:
|
||||||
* @code{.cpp}
|
* @code{.cpp}
|
||||||
* entt::DefaultRegistry registry;
|
* entt::DefaultRegistry registry;
|
||||||
* entt::dependency<AType, AnotherType>(entt::break_t{}, registry.construction<MyType>());
|
* entt::disconnect<AType, AnotherType>(registry.construction<MyType>());
|
||||||
* @endcode
|
* @endcode
|
||||||
*
|
*
|
||||||
* @tparam Dependency Types of components used to create the dependency.
|
* @tparam Dependency Types of components used to create the dependency.
|
||||||
@@ -75,7 +75,7 @@ void dependency(Sink<void(Registry<Entity> &, const Entity)> sink) {
|
|||||||
* @param sink A sink object properly initialized.
|
* @param sink A sink object properly initialized.
|
||||||
*/
|
*/
|
||||||
template<typename... Dependency, typename Entity>
|
template<typename... Dependency, typename Entity>
|
||||||
void dependency(break_t, Sink<void(Registry<Entity> &, const Entity)> sink) {
|
inline void disconnect(Sink<void(Registry<Entity> &, const Entity)> sink) {
|
||||||
sink.template disconnect<dependency<Entity, Dependency...>>();
|
sink.template disconnect<dependency<Entity, Dependency...>>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include "../config/config.h"
|
#include "../config/config.h"
|
||||||
#include "registry.hpp"
|
#include "registry.hpp"
|
||||||
|
#include "entity.hpp"
|
||||||
|
|
||||||
|
|
||||||
namespace entt {
|
namespace entt {
|
||||||
@@ -95,7 +96,7 @@ public:
|
|||||||
registry{other.registry},
|
registry{other.registry},
|
||||||
entity{other.entity}
|
entity{other.entity}
|
||||||
{
|
{
|
||||||
other.entity = ~entity_type{};
|
other.entity = entt::null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*! @brief Copying a prototype isn't allowed. @return This Prototype. */
|
/*! @brief Copying a prototype isn't allowed. @return This Prototype. */
|
||||||
@@ -139,7 +140,7 @@ public:
|
|||||||
basic_fn_type *assign = [](const Prototype &prototype, Registry<Entity> &other, const Entity dst) {
|
basic_fn_type *assign = [](const Prototype &prototype, Registry<Entity> &other, const Entity dst) {
|
||||||
if(!other.template has<Component>(dst)) {
|
if(!other.template has<Component>(dst)) {
|
||||||
const auto &wrapper = prototype.registry->template get<Wrapper<Component>>(prototype.entity);
|
const auto &wrapper = prototype.registry->template get<Wrapper<Component>>(prototype.entity);
|
||||||
other.template accommodate<Component>(dst, wrapper.component);
|
other.template assign<Component>(dst, wrapper.component);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,8 @@
|
|||||||
#include "../core/algorithm.hpp"
|
#include "../core/algorithm.hpp"
|
||||||
#include "../core/family.hpp"
|
#include "../core/family.hpp"
|
||||||
#include "../signal/sigh.hpp"
|
#include "../signal/sigh.hpp"
|
||||||
|
#include "attachee.hpp"
|
||||||
|
#include "entity.hpp"
|
||||||
#include "entt_traits.hpp"
|
#include "entt_traits.hpp"
|
||||||
#include "snapshot.hpp"
|
#include "snapshot.hpp"
|
||||||
#include "sparse_set.hpp"
|
#include "sparse_set.hpp"
|
||||||
@@ -44,6 +46,78 @@ class Registry {
|
|||||||
using signal_type = SigH<void(Registry &, const Entity)>;
|
using signal_type = SigH<void(Registry &, const Entity)>;
|
||||||
using traits_type = entt_traits<Entity>;
|
using traits_type = entt_traits<Entity>;
|
||||||
|
|
||||||
|
template<typename Component>
|
||||||
|
struct Pool: SparseSet<Entity, Component> {
|
||||||
|
Pool(Registry *registry) ENTT_NOEXCEPT
|
||||||
|
: registry{registry}
|
||||||
|
{}
|
||||||
|
|
||||||
|
template<typename... Args>
|
||||||
|
Component & construct(const Entity entity, Args &&... args) {
|
||||||
|
auto &component = SparseSet<Entity, Component>::construct(entity, std::forward<Args>(args)...);
|
||||||
|
ctor.publish(*registry, entity);
|
||||||
|
return component;
|
||||||
|
}
|
||||||
|
|
||||||
|
void destroy(const Entity entity) override {
|
||||||
|
dtor.publish(*registry, entity);
|
||||||
|
SparseSet<Entity, Component>::destroy(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
typename signal_type::sink_type construction() ENTT_NOEXCEPT {
|
||||||
|
return ctor.sink();
|
||||||
|
}
|
||||||
|
|
||||||
|
typename signal_type::sink_type destruction() ENTT_NOEXCEPT {
|
||||||
|
return dtor.sink();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Registry *registry;
|
||||||
|
signal_type ctor;
|
||||||
|
signal_type dtor;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename Tag>
|
||||||
|
struct Attaching: Attachee<Entity, Tag> {
|
||||||
|
Attaching(Registry *registry)
|
||||||
|
: registry{registry}
|
||||||
|
{}
|
||||||
|
|
||||||
|
template<typename... Args>
|
||||||
|
Tag & construct(const Entity entity, Args &&... args) ENTT_NOEXCEPT {
|
||||||
|
auto &tag = Attachee<Entity, Tag>::construct(entity, std::forward<Args>(args)...);
|
||||||
|
ctor.publish(*registry, entity);
|
||||||
|
return tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
void destroy() ENTT_NOEXCEPT override {
|
||||||
|
dtor.publish(*registry, Attachee<Entity>::get());
|
||||||
|
Attachee<Entity, Tag>::destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
Entity move(const Entity entity) ENTT_NOEXCEPT {
|
||||||
|
const auto owner = Attachee<Entity>::get();
|
||||||
|
dtor.publish(*registry, owner);
|
||||||
|
Attachee<Entity, Tag>::move(entity);
|
||||||
|
ctor.publish(*registry, entity);
|
||||||
|
return owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
typename signal_type::sink_type construction() ENTT_NOEXCEPT {
|
||||||
|
return ctor.sink();
|
||||||
|
}
|
||||||
|
|
||||||
|
typename signal_type::sink_type destruction() ENTT_NOEXCEPT {
|
||||||
|
return dtor.sink();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Registry *registry;
|
||||||
|
signal_type ctor;
|
||||||
|
signal_type dtor;
|
||||||
|
};
|
||||||
|
|
||||||
template<typename handler_family::family_type(*Type)(), typename... Component>
|
template<typename handler_family::family_type(*Type)(), typename... Component>
|
||||||
static void creating(Registry ®istry, const Entity entity) {
|
static void creating(Registry ®istry, const Entity entity) {
|
||||||
if(registry.has<Component...>(entity)) {
|
if(registry.has<Component...>(entity)) {
|
||||||
@@ -57,28 +131,44 @@ class Registry {
|
|||||||
return handler.has(entity) ? handler.destroy(entity) : void();
|
return handler.has(entity) ? handler.destroy(entity) : void();
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Attachee {
|
template<typename Tag>
|
||||||
Attachee(const Entity entity) ENTT_NOEXCEPT: entity{entity} {}
|
inline bool managed(tag_t) const ENTT_NOEXCEPT {
|
||||||
virtual ~Attachee() = default;
|
const auto ttype = tag_family::type<Tag>();
|
||||||
Entity entity;
|
return ttype < tags.size() && tags[ttype];
|
||||||
};
|
}
|
||||||
|
|
||||||
|
template<typename Component>
|
||||||
|
inline bool managed() const ENTT_NOEXCEPT {
|
||||||
|
const auto ctype = component_family::type<Component>();
|
||||||
|
return ctype < pools.size() && pools[ctype];
|
||||||
|
}
|
||||||
|
|
||||||
template<typename Tag>
|
template<typename Tag>
|
||||||
struct Attaching: Attachee {
|
inline const Attaching<Tag> & pool(tag_t) const ENTT_NOEXCEPT {
|
||||||
// requirements for aggregates are relaxed only since C++17
|
assert(managed<Tag>(tag_t{}));
|
||||||
template<typename... Args>
|
return static_cast<const Attaching<Tag> &>(*tags[tag_family::type<Tag>()]);
|
||||||
Attaching(const Entity entity, Args &&... args)
|
}
|
||||||
: Attachee{entity}, tag{std::forward<Args>(args)...}
|
|
||||||
{}
|
|
||||||
|
|
||||||
Tag tag;
|
template<typename Tag>
|
||||||
};
|
inline Attaching<Tag> & pool(tag_t) ENTT_NOEXCEPT {
|
||||||
|
return const_cast<Attaching<Tag> &>(const_cast<const Registry *>(this)->pool<Tag>(tag_t{}));
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Component>
|
||||||
|
inline const Pool<Component> & pool() const ENTT_NOEXCEPT {
|
||||||
|
assert(managed<Component>());
|
||||||
|
return static_cast<const Pool<Component> &>(*pools[component_family::type<Component>()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Component>
|
||||||
|
inline Pool<Component> & pool() ENTT_NOEXCEPT {
|
||||||
|
return const_cast<Pool<Component> &>(const_cast<const Registry *>(this)->pool<Component>());
|
||||||
|
}
|
||||||
|
|
||||||
template<typename Comp, std::size_t Pivot, typename... Component, std::size_t... Indexes>
|
template<typename Comp, std::size_t Pivot, typename... Component, std::size_t... Indexes>
|
||||||
void connect(std::index_sequence<Indexes...>) {
|
void connect(std::index_sequence<Indexes...>) {
|
||||||
auto &cpool = pools[component_family::type<Comp>()];
|
pool<Comp>().construction().template connect<&Registry::creating<&handler_family::type<Component...>, std::tuple_element_t<(Indexes < Pivot ? Indexes : (Indexes+1)), std::tuple<Component...>>...>>();
|
||||||
std::get<1>(cpool).sink().template connect<&Registry::creating<&handler_family::type<Component...>, std::tuple_element_t<(Indexes < Pivot ? Indexes : (Indexes+1)), std::tuple<Component...>>...>>();
|
pool<Comp>().destruction().template connect<&Registry::destroying<Component...>>();
|
||||||
std::get<2>(cpool).sink().template connect<&Registry::destroying<Component...>>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename... Component, std::size_t... Indexes>
|
template<typename... Component, std::size_t... Indexes>
|
||||||
@@ -90,9 +180,8 @@ class Registry {
|
|||||||
|
|
||||||
template<typename Comp, std::size_t Pivot, typename... Component, std::size_t... Indexes>
|
template<typename Comp, std::size_t Pivot, typename... Component, std::size_t... Indexes>
|
||||||
void disconnect(std::index_sequence<Indexes...>) {
|
void disconnect(std::index_sequence<Indexes...>) {
|
||||||
auto &cpool = pools[component_family::type<Comp>()];
|
pool<Comp>().construction().template disconnect<&Registry::creating<&handler_family::type<Component...>, std::tuple_element_t<(Indexes < Pivot ? Indexes : (Indexes+1)), std::tuple<Component...>>...>>();
|
||||||
std::get<1>(cpool).sink().template disconnect<&Registry::creating<&handler_family::type<Component...>, std::tuple_element_t<(Indexes < Pivot ? Indexes : (Indexes+1)), std::tuple<Component...>>...>>();
|
pool<Comp>().destruction().template disconnect<&Registry::destroying<Component...>>();
|
||||||
std::get<2>(cpool).sink().template disconnect<&Registry::destroying<Component...>>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename... Component, std::size_t... Indexes>
|
template<typename... Component, std::size_t... Indexes>
|
||||||
@@ -103,24 +192,6 @@ class Registry {
|
|||||||
(void)accumulator;
|
(void)accumulator;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename Component>
|
|
||||||
inline bool managed() const ENTT_NOEXCEPT {
|
|
||||||
const auto ctype = component_family::type<Component>();
|
|
||||||
return ctype < pools.size() && std::get<0>(pools[ctype]);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename Component>
|
|
||||||
inline const SparseSet<Entity, Component> & pool() const ENTT_NOEXCEPT {
|
|
||||||
assert(managed<Component>());
|
|
||||||
const auto ctype = component_family::type<Component>();
|
|
||||||
return static_cast<SparseSet<Entity, Component> &>(*std::get<0>(pools[ctype]));
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename Component>
|
|
||||||
inline SparseSet<Entity, Component> & pool() ENTT_NOEXCEPT {
|
|
||||||
return const_cast<SparseSet<Entity, Component> &>(const_cast<const Registry *>(this)->pool<Component>());
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename Component>
|
template<typename Component>
|
||||||
void assure() {
|
void assure() {
|
||||||
const auto ctype = component_family::type<Component>();
|
const auto ctype = component_family::type<Component>();
|
||||||
@@ -129,10 +200,8 @@ class Registry {
|
|||||||
pools.resize(ctype + 1);
|
pools.resize(ctype + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto &cpool = std::get<0>(pools[ctype]);
|
if(!pools[ctype]) {
|
||||||
|
pools[ctype] = std::make_unique<Pool<Component>>(this);
|
||||||
if(!cpool) {
|
|
||||||
cpool = std::make_unique<SparseSet<Entity, Component>>();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,6 +212,10 @@ class Registry {
|
|||||||
if(!(ttype < tags.size())) {
|
if(!(ttype < tags.size())) {
|
||||||
tags.resize(ttype + 1);
|
tags.resize(ttype + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(!tags[ttype]) {
|
||||||
|
tags[ttype] = std::make_unique<Attaching<Tag>>(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@@ -402,7 +475,7 @@ public:
|
|||||||
* @return The version stored along with the given entity identifier.
|
* @return The version stored along with the given entity identifier.
|
||||||
*/
|
*/
|
||||||
version_type version(const entity_type entity) const ENTT_NOEXCEPT {
|
version_type version(const entity_type entity) const ENTT_NOEXCEPT {
|
||||||
return version_type((entity >> traits_type::entity_shift) & traits_type::version_mask);
|
return version_type(entity >> traits_type::entity_shift);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -425,7 +498,7 @@ public:
|
|||||||
version_type current(const entity_type entity) const ENTT_NOEXCEPT {
|
version_type current(const entity_type entity) const ENTT_NOEXCEPT {
|
||||||
const auto pos = size_type(entity & traits_type::entity_mask);
|
const auto pos = size_type(entity & traits_type::entity_mask);
|
||||||
assert(pos < entities.size());
|
assert(pos < entities.size());
|
||||||
return version_type((entities[pos] >> traits_type::entity_shift) & traits_type::version_mask);
|
return version_type(entities[pos] >> traits_type::entity_shift);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -450,9 +523,9 @@ public:
|
|||||||
|
|
||||||
if(available) {
|
if(available) {
|
||||||
const auto entt = next;
|
const auto entt = next;
|
||||||
const auto version = entities[entt] & (~traits_type::entity_mask);
|
const auto version = entities[entt] & (traits_type::version_mask << traits_type::entity_shift);
|
||||||
entity = entt | version;
|
|
||||||
next = entities[entt] & traits_type::entity_mask;
|
next = entities[entt] & traits_type::entity_mask;
|
||||||
|
entity = entt | version;
|
||||||
entities[entt] = entity;
|
entities[entt] = entity;
|
||||||
--available;
|
--available;
|
||||||
} else {
|
} else {
|
||||||
@@ -510,22 +583,18 @@ public:
|
|||||||
assert(valid(entity));
|
assert(valid(entity));
|
||||||
|
|
||||||
for(auto pos = pools.size(); pos; --pos) {
|
for(auto pos = pools.size(); pos; --pos) {
|
||||||
auto &tup = pools[pos-1];
|
auto &cpool = pools[pos-1];
|
||||||
auto &cpool = std::get<0>(tup);
|
|
||||||
|
|
||||||
if(cpool && cpool->has(entity)) {
|
if(cpool && cpool->has(entity)) {
|
||||||
std::get<2>(tup).publish(*this, entity);
|
|
||||||
cpool->destroy(entity);
|
cpool->destroy(entity);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
for(auto pos = tags.size(); pos; --pos) {
|
for(auto pos = tags.size(); pos; --pos) {
|
||||||
auto &tup = tags[pos-1];
|
auto &tag = tags[pos-1];
|
||||||
auto &tag = std::get<0>(tup);
|
|
||||||
|
|
||||||
if(tag && tag->entity == entity) {
|
if(tag && tag->get() == entity) {
|
||||||
std::get<2>(tup).publish(*this, entity);
|
tag->destroy();
|
||||||
tag.reset();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -534,7 +603,7 @@ public:
|
|||||||
|
|
||||||
// lengthens the implicit list of destroyed entities
|
// lengthens the implicit list of destroyed entities
|
||||||
const auto entt = entity & traits_type::entity_mask;
|
const auto entt = entity & traits_type::entity_mask;
|
||||||
const auto version = (((entity >> traits_type::entity_shift) + 1) & traits_type::version_mask) << traits_type::entity_shift;
|
const auto version = ((entity >> traits_type::entity_shift) + 1) << traits_type::entity_shift;
|
||||||
const auto node = (available ? next : ((entt + 1) & traits_type::entity_mask)) | version;
|
const auto node = (available ? next : ((entt + 1) & traits_type::entity_mask)) | version;
|
||||||
entities[entt] = node;
|
entities[entt] = node;
|
||||||
next = entt;
|
next = entt;
|
||||||
@@ -589,10 +658,7 @@ public:
|
|||||||
assert(valid(entity));
|
assert(valid(entity));
|
||||||
assert(!has<Tag>());
|
assert(!has<Tag>());
|
||||||
assure<Tag>(tag_t{});
|
assure<Tag>(tag_t{});
|
||||||
auto &tup = tags[tag_family::type<Tag>()];
|
return pool<Tag>(tag_t{}).construct(entity, std::forward<Args>(args)...);
|
||||||
std::get<0>(tup).reset(new Attaching<Tag>{entity, std::forward<Args>(args)...});
|
|
||||||
std::get<1>(tup).publish(*this, entity);
|
|
||||||
return get<Tag>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -619,9 +685,7 @@ public:
|
|||||||
Component & assign(const entity_type entity, Args &&... args) {
|
Component & assign(const entity_type entity, Args &&... args) {
|
||||||
assert(valid(entity));
|
assert(valid(entity));
|
||||||
assure<Component>();
|
assure<Component>();
|
||||||
pool<Component>().construct(entity, std::forward<Args>(args)...);
|
return pool<Component>().construct(entity, std::forward<Args>(args)...);
|
||||||
std::get<1>(pools[component_family::type<Component>()]).publish(*this, entity);
|
|
||||||
return pool<Component>().get(entity);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -630,12 +694,7 @@ public:
|
|||||||
*/
|
*/
|
||||||
template<typename Tag>
|
template<typename Tag>
|
||||||
void remove() {
|
void remove() {
|
||||||
if(has<Tag>()) {
|
return has<Tag>() ? pool<Tag>(tag_t{}).destroy() : void();
|
||||||
auto &tup = tags[tag_family::type<Tag>()];
|
|
||||||
auto &tag = std::get<0>(tup);
|
|
||||||
std::get<2>(tup).publish(*this, tag->entity);
|
|
||||||
tag.reset();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -655,8 +714,6 @@ public:
|
|||||||
void remove(const entity_type entity) {
|
void remove(const entity_type entity) {
|
||||||
assert(valid(entity));
|
assert(valid(entity));
|
||||||
assert(managed<Component>());
|
assert(managed<Component>());
|
||||||
const auto ctype = component_family::type<Component>();
|
|
||||||
std::get<2>(pools[ctype]).publish(*this, entity);
|
|
||||||
pool<Component>().destroy(entity);
|
pool<Component>().destroy(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -667,16 +724,7 @@ public:
|
|||||||
*/
|
*/
|
||||||
template<typename Tag>
|
template<typename Tag>
|
||||||
bool has() const ENTT_NOEXCEPT {
|
bool has() const ENTT_NOEXCEPT {
|
||||||
const auto ttype = tag_family::type<Tag>();
|
return managed<Tag>(tag_t{}) && tags[tag_family::type<Tag>()]->get() != null;
|
||||||
bool found = false;
|
|
||||||
|
|
||||||
if(ttype < tags.size()) {
|
|
||||||
auto &tag = std::get<0>(tags[ttype]);
|
|
||||||
// it's a valid tag and the associated entity hasn't been destroyed in the meantime
|
|
||||||
found = tag && (tag->entity == (entities[tag->entity & traits_type::entity_mask]));
|
|
||||||
}
|
|
||||||
|
|
||||||
return found;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -734,7 +782,7 @@ public:
|
|||||||
template<typename Tag>
|
template<typename Tag>
|
||||||
const Tag & get() const ENTT_NOEXCEPT {
|
const Tag & get() const ENTT_NOEXCEPT {
|
||||||
assert(has<Tag>());
|
assert(has<Tag>());
|
||||||
return static_cast<Attaching<Tag> *>(std::get<0>(tags[tag_family::type<Tag>()]).get())->tag;
|
return pool<Tag>(tag_t{}).get();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -902,28 +950,18 @@ public:
|
|||||||
entity_type move(const entity_type entity) ENTT_NOEXCEPT {
|
entity_type move(const entity_type entity) ENTT_NOEXCEPT {
|
||||||
assert(valid(entity));
|
assert(valid(entity));
|
||||||
assert(has<Tag>());
|
assert(has<Tag>());
|
||||||
auto &tag = std::get<0>(tags[tag_family::type<Tag>()]);
|
return pool<Tag>(tag_t{}).move(entity);
|
||||||
const auto owner = tag->entity;
|
|
||||||
tag->entity = entity;
|
|
||||||
return owner;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Gets the owner of the given tag, if any.
|
* @brief Gets the owner of the given tag, if any.
|
||||||
*
|
|
||||||
* @warning
|
|
||||||
* Attempting to get the owner of a tag that hasn't been previously attached
|
|
||||||
* 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.
|
* @tparam Tag Type of tag of which to get the owner.
|
||||||
* @return A valid entity identifier.
|
* @return A valid entity identifier if an owner exists, the null entity
|
||||||
|
* identifier otherwise.
|
||||||
*/
|
*/
|
||||||
template<typename Tag>
|
template<typename Tag>
|
||||||
entity_type attachee() const ENTT_NOEXCEPT {
|
entity_type attachee() const ENTT_NOEXCEPT {
|
||||||
assert(has<Tag>());
|
return managed<Tag>(tag_t{}) ? tags[tag_family::type<Tag>()]->get() : null;
|
||||||
return std::get<0>(tags[tag_family::type<Tag>()])->entity;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -957,9 +995,9 @@ public:
|
|||||||
assure<Component>();
|
assure<Component>();
|
||||||
auto &cpool = pool<Component>();
|
auto &cpool = pool<Component>();
|
||||||
|
|
||||||
return (cpool.has(entity)
|
return cpool.has(entity)
|
||||||
? cpool.get(entity) = Component{std::forward<Args>(args)...}
|
? cpool.get(entity) = Component{std::forward<Args>(args)...}
|
||||||
: cpool.construct(entity, std::forward<Args>(args)...));
|
: cpool.construct(entity, std::forward<Args>(args)...);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -988,7 +1026,7 @@ public:
|
|||||||
template<typename Tag>
|
template<typename Tag>
|
||||||
sink_type construction(tag_t) ENTT_NOEXCEPT {
|
sink_type construction(tag_t) ENTT_NOEXCEPT {
|
||||||
assure<Tag>(tag_t{});
|
assure<Tag>(tag_t{});
|
||||||
return std::get<1>(tags[tag_family::type<Tag>()]).sink();
|
return pool<Tag>(tag_t{}).construction();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1017,7 +1055,7 @@ public:
|
|||||||
template<typename Component>
|
template<typename Component>
|
||||||
sink_type construction() ENTT_NOEXCEPT {
|
sink_type construction() ENTT_NOEXCEPT {
|
||||||
assure<Component>();
|
assure<Component>();
|
||||||
return std::get<1>(pools[component_family::type<Component>()]).sink();
|
return pool<Component>().construction();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1046,7 +1084,7 @@ public:
|
|||||||
template<typename Tag>
|
template<typename Tag>
|
||||||
sink_type destruction(tag_t) ENTT_NOEXCEPT {
|
sink_type destruction(tag_t) ENTT_NOEXCEPT {
|
||||||
assure<Tag>(tag_t{});
|
assure<Tag>(tag_t{});
|
||||||
return std::get<2>(tags[tag_family::type<Tag>()]).sink();
|
return pool<Tag>(tag_t{}).destruction();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1075,7 +1113,7 @@ public:
|
|||||||
template<typename Component>
|
template<typename Component>
|
||||||
sink_type destruction() ENTT_NOEXCEPT {
|
sink_type destruction() ENTT_NOEXCEPT {
|
||||||
assure<Component>();
|
assure<Component>();
|
||||||
return std::get<2>(pools[component_family::type<Component>()]).sink();
|
return pool<Component>().destruction();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1180,11 +1218,9 @@ public:
|
|||||||
void reset(const entity_type entity) {
|
void reset(const entity_type entity) {
|
||||||
assert(valid(entity));
|
assert(valid(entity));
|
||||||
assure<Component>();
|
assure<Component>();
|
||||||
const auto ctype = component_family::type<Component>();
|
auto &cpool = pool<Component>();
|
||||||
auto &cpool = *std::get<0>(pools[ctype]);
|
|
||||||
|
|
||||||
if(cpool.has(entity)) {
|
if(cpool.has(entity)) {
|
||||||
std::get<2>(pools[ctype]).publish(*this, entity);
|
|
||||||
cpool.destroy(entity);
|
cpool.destroy(entity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1200,12 +1236,9 @@ public:
|
|||||||
template<typename Component>
|
template<typename Component>
|
||||||
void reset() {
|
void reset() {
|
||||||
assure<Component>();
|
assure<Component>();
|
||||||
const auto ctype = component_family::type<Component>();
|
auto &cpool = pool<Component>();
|
||||||
auto &cpool = *std::get<0>(pools[ctype]);
|
|
||||||
auto &sig = std::get<2>(pools[ctype]);
|
|
||||||
|
|
||||||
for(const auto entity: cpool) {
|
for(const auto entity: static_cast<SparseSet<Entity> &>(cpool)) {
|
||||||
sig.publish(*this, entity);
|
|
||||||
cpool.destroy(entity);
|
cpool.destroy(entity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1278,13 +1311,13 @@ public:
|
|||||||
bool orphan = true;
|
bool orphan = true;
|
||||||
|
|
||||||
for(std::size_t i = 0; i < pools.size() && orphan; ++i) {
|
for(std::size_t i = 0; i < pools.size() && orphan; ++i) {
|
||||||
const auto &pool = std::get<0>(pools[i]);
|
const auto &cpool = pools[i];
|
||||||
orphan = !(pool && pool->has(entity));
|
orphan = !(cpool && cpool->has(entity));
|
||||||
}
|
}
|
||||||
|
|
||||||
for(std::size_t i = 0; i < tags.size() && orphan; ++i) {
|
for(std::size_t i = 0; i < tags.size() && orphan; ++i) {
|
||||||
const auto &tag = std::get<0>(tags[i]);
|
const auto &tag = tags[i];
|
||||||
orphan = !(tag && (tag->entity == entity));
|
orphan = !(tag && (tag->get() == entity));
|
||||||
}
|
}
|
||||||
|
|
||||||
return orphan;
|
return orphan;
|
||||||
@@ -1532,7 +1565,7 @@ public:
|
|||||||
std::vector<const SparseSet<Entity> *> set(last - first);
|
std::vector<const SparseSet<Entity> *> set(last - first);
|
||||||
|
|
||||||
std::transform(first, last, set.begin(), [this](const component_type ctype) {
|
std::transform(first, last, set.begin(), [this](const component_type ctype) {
|
||||||
return ctype < pools.size() ? std::get<0>(pools[ctype]).get() : nullptr;
|
return ctype < pools.size() ? pools[ctype].get() : nullptr;
|
||||||
});
|
});
|
||||||
|
|
||||||
return RuntimeView<Entity>{std::move(set)};
|
return RuntimeView<Entity>{std::move(set)};
|
||||||
@@ -1550,13 +1583,13 @@ public:
|
|||||||
*/
|
*/
|
||||||
Snapshot<Entity> snapshot() const ENTT_NOEXCEPT {
|
Snapshot<Entity> snapshot() const ENTT_NOEXCEPT {
|
||||||
using follow_fn_type = entity_type(const Registry &, const entity_type);
|
using follow_fn_type = entity_type(const Registry &, const entity_type);
|
||||||
const entity_type seed = available ? (next | (entities[next] & ~traits_type::entity_mask)) : next;
|
const entity_type seed = available ? (next | (entities[next] & (traits_type::version_mask << traits_type::entity_shift))) : next;
|
||||||
|
|
||||||
follow_fn_type *follow = [](const Registry ®istry, const entity_type entity) -> entity_type {
|
follow_fn_type *follow = [](const Registry ®istry, const entity_type entity) -> entity_type {
|
||||||
const auto &entities = registry.entities;
|
const auto &entities = registry.entities;
|
||||||
const auto entt = entity & traits_type::entity_mask;
|
const auto entt = entity & traits_type::entity_mask;
|
||||||
const auto next = entities[entt] & traits_type::entity_mask;
|
const auto next = entities[entt] & traits_type::entity_mask;
|
||||||
return (next | (entities[next] & ~traits_type::entity_mask));
|
return (next | (entities[next] & (traits_type::version_mask << traits_type::entity_shift)));
|
||||||
};
|
};
|
||||||
|
|
||||||
return { *this, seed, follow };
|
return { *this, seed, follow };
|
||||||
@@ -1596,7 +1629,7 @@ public:
|
|||||||
|
|
||||||
if(destroyed) {
|
if(destroyed) {
|
||||||
registry.destroy(entity);
|
registry.destroy(entity);
|
||||||
const auto version = (entity & (~traits_type::entity_mask));
|
const auto version = entity & (traits_type::version_mask << traits_type::entity_shift);
|
||||||
entities[entt] = ((entities[entt] & traits_type::entity_mask) | version);
|
entities[entt] = ((entities[entt] & traits_type::entity_mask) | version);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -1606,8 +1639,8 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
std::vector<std::unique_ptr<SparseSet<Entity>>> handlers;
|
std::vector<std::unique_ptr<SparseSet<Entity>>> handlers;
|
||||||
std::vector<std::tuple<std::unique_ptr<SparseSet<Entity>>, signal_type, signal_type>> pools;
|
std::vector<std::unique_ptr<SparseSet<Entity>>> pools;
|
||||||
std::vector<std::tuple<std::unique_ptr<Attachee>, signal_type, signal_type>> tags;
|
std::vector<std::unique_ptr<Attachee<Entity>>> tags;
|
||||||
std::vector<entity_type> entities;
|
std::vector<entity_type> entities;
|
||||||
size_type available{};
|
size_type available{};
|
||||||
entity_type next{};
|
entity_type next{};
|
||||||
|
|||||||
@@ -119,11 +119,15 @@ public:
|
|||||||
const Snapshot & destroyed(Archive &archive) const {
|
const Snapshot & destroyed(Archive &archive) const {
|
||||||
auto size = registry.size() - registry.alive();
|
auto size = registry.size() - registry.alive();
|
||||||
archive(static_cast<Entity>(size));
|
archive(static_cast<Entity>(size));
|
||||||
auto curr = seed;
|
|
||||||
|
|
||||||
for(; size; --size) {
|
if(size) {
|
||||||
|
auto curr = seed;
|
||||||
archive(curr);
|
archive(curr);
|
||||||
curr = follow(registry, curr);
|
|
||||||
|
for(--size; size; --size) {
|
||||||
|
curr = follow(registry, curr);
|
||||||
|
archive(curr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return *this;
|
return *this;
|
||||||
|
|||||||
@@ -122,7 +122,8 @@ class SparseSet<Entity> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
reference operator[](const difference_type value) const ENTT_NOEXCEPT {
|
reference operator[](const difference_type value) const ENTT_NOEXCEPT {
|
||||||
return (*direct)[index-value-1];
|
const auto pos = size_type(index-value-1);
|
||||||
|
return (*direct)[pos];
|
||||||
}
|
}
|
||||||
|
|
||||||
bool operator==(const Iterator &other) const ENTT_NOEXCEPT {
|
bool operator==(const Iterator &other) const ENTT_NOEXCEPT {
|
||||||
@@ -150,7 +151,8 @@ class SparseSet<Entity> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pointer operator->() const ENTT_NOEXCEPT {
|
pointer operator->() const ENTT_NOEXCEPT {
|
||||||
return &(*direct)[index-1];
|
const auto pos = size_type(index-1);
|
||||||
|
return &(*direct)[pos];
|
||||||
}
|
}
|
||||||
|
|
||||||
inline reference operator*() const ENTT_NOEXCEPT {
|
inline reference operator*() const ENTT_NOEXCEPT {
|
||||||
@@ -422,7 +424,8 @@ public:
|
|||||||
*/
|
*/
|
||||||
size_type get(const entity_type entity) const ENTT_NOEXCEPT {
|
size_type get(const entity_type entity) const ENTT_NOEXCEPT {
|
||||||
assert(has(entity));
|
assert(has(entity));
|
||||||
return reverse[entity & traits_type::entity_mask];
|
const auto pos = size_type(entity & traits_type::entity_mask);
|
||||||
|
return size_type(reverse[pos]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -463,10 +466,10 @@ public:
|
|||||||
virtual void destroy(const entity_type entity) {
|
virtual void destroy(const entity_type entity) {
|
||||||
assert(has(entity));
|
assert(has(entity));
|
||||||
const auto back = direct.back();
|
const auto back = direct.back();
|
||||||
auto &candidate = reverse[entity & traits_type::entity_mask];
|
auto &candidate = reverse[size_type(entity & traits_type::entity_mask)];
|
||||||
// swapping isn't required here, we are getting rid of the last element
|
// swapping isn't required here, we are getting rid of the last element
|
||||||
reverse[back & traits_type::entity_mask] = candidate;
|
reverse[back & traits_type::entity_mask] = candidate;
|
||||||
direct[candidate] = back;
|
direct[size_type(candidate)] = back;
|
||||||
candidate = null;
|
candidate = null;
|
||||||
direct.pop_back();
|
direct.pop_back();
|
||||||
}
|
}
|
||||||
@@ -637,7 +640,8 @@ class SparseSet<Entity, Type>: public SparseSet<Entity> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
reference operator[](const difference_type value) const ENTT_NOEXCEPT {
|
reference operator[](const difference_type value) const ENTT_NOEXCEPT {
|
||||||
return (*instances)[index-value-1];
|
const auto pos = size_type(index-value-1);
|
||||||
|
return (*instances)[pos];
|
||||||
}
|
}
|
||||||
|
|
||||||
bool operator==(const Iterator &other) const ENTT_NOEXCEPT {
|
bool operator==(const Iterator &other) const ENTT_NOEXCEPT {
|
||||||
@@ -665,7 +669,8 @@ class SparseSet<Entity, Type>: public SparseSet<Entity> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pointer operator->() const ENTT_NOEXCEPT {
|
pointer operator->() const ENTT_NOEXCEPT {
|
||||||
return &(*instances)[index-1];
|
const auto pos = size_type(index-1);
|
||||||
|
return &(*instances)[pos];
|
||||||
}
|
}
|
||||||
|
|
||||||
inline reference operator*() const ENTT_NOEXCEPT {
|
inline reference operator*() const ENTT_NOEXCEPT {
|
||||||
|
|||||||
@@ -17,10 +17,6 @@ struct persistent_t final {};
|
|||||||
struct raw_t final {};
|
struct raw_t final {};
|
||||||
|
|
||||||
|
|
||||||
/*! @brief Break type used to disambiguate overloads. */
|
|
||||||
struct break_t final {};
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include "core/ident.hpp"
|
#include "core/ident.hpp"
|
||||||
#include "core/monostate.hpp"
|
#include "core/monostate.hpp"
|
||||||
#include "entity/actor.hpp"
|
#include "entity/actor.hpp"
|
||||||
|
#include "entity/attachee.hpp"
|
||||||
#include "entity/entity.hpp"
|
#include "entity/entity.hpp"
|
||||||
#include "entity/entt_traits.hpp"
|
#include "entity/entt_traits.hpp"
|
||||||
#include "entity/helper.hpp"
|
#include "entity/helper.hpp"
|
||||||
|
|||||||
@@ -42,6 +42,11 @@ class Delegate<Ret(Args...)> final {
|
|||||||
return (Function)(args...);
|
return (Function)(args...);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<typename Class, Ret(Class:: *Member)(Args...) const>
|
||||||
|
static Ret proto(void *instance, Args... args) {
|
||||||
|
return (static_cast<const Class *>(instance)->*Member)(args...);
|
||||||
|
}
|
||||||
|
|
||||||
template<typename Class, Ret(Class:: *Member)(Args...)>
|
template<typename Class, Ret(Class:: *Member)(Args...)>
|
||||||
static Ret proto(void *instance, Args... args) {
|
static Ret proto(void *instance, Args... args) {
|
||||||
return (static_cast<Class *>(instance)->*Member)(args...);
|
return (static_cast<Class *>(instance)->*Member)(args...);
|
||||||
@@ -71,6 +76,22 @@ public:
|
|||||||
stub = std::make_pair(nullptr, &proto<Function>);
|
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...) const>
|
||||||
|
void connect(Class *instance) ENTT_NOEXCEPT {
|
||||||
|
stub = std::make_pair(instance, &proto<Class, Member>);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Connects a member function for a given instance to a delegate.
|
* @brief Connects a member function for a given instance to a delegate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -11,23 +11,33 @@
|
|||||||
namespace entt {
|
namespace entt {
|
||||||
|
|
||||||
|
|
||||||
namespace internal {
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @cond TURN_OFF_DOXYGEN
|
* @cond TURN_OFF_DOXYGEN
|
||||||
* Internal details not to be documented.
|
* Internal details not to be documented.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
|
||||||
|
template<typename>
|
||||||
|
struct sigh_traits;
|
||||||
|
|
||||||
|
|
||||||
|
template<typename Ret, typename... Args>
|
||||||
|
struct sigh_traits<Ret(Args...)> {
|
||||||
|
using proto_fn_type = Ret(void *, Args...);
|
||||||
|
using call_type = std::pair<void *, proto_fn_type *>;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
template<typename, typename>
|
template<typename, typename>
|
||||||
struct Invoker;
|
struct Invoker;
|
||||||
|
|
||||||
|
|
||||||
template<typename Ret, typename... Args, typename Collector>
|
template<typename Ret, typename... Args, typename Collector>
|
||||||
struct Invoker<Ret(Args...), Collector> {
|
struct Invoker<Ret(Args...), Collector> {
|
||||||
using proto_fn_type = Ret(void *, Args...);
|
using proto_fn_type = typename sigh_traits<Ret(Args...)>::proto_fn_type;
|
||||||
using call_type = std::pair<void *, proto_fn_type *>;
|
|
||||||
|
|
||||||
virtual ~Invoker() = default;
|
virtual ~Invoker() = default;
|
||||||
|
|
||||||
@@ -39,8 +49,7 @@ struct Invoker<Ret(Args...), Collector> {
|
|||||||
|
|
||||||
template<typename... Args, typename Collector>
|
template<typename... Args, typename Collector>
|
||||||
struct Invoker<void(Args...), Collector> {
|
struct Invoker<void(Args...), Collector> {
|
||||||
using proto_fn_type = void(void *, Args...);
|
using proto_fn_type = typename sigh_traits<void(Args...)>::proto_fn_type;
|
||||||
using call_type = std::pair<void *, proto_fn_type *>;
|
|
||||||
|
|
||||||
virtual ~Invoker() = default;
|
virtual ~Invoker() = default;
|
||||||
|
|
||||||
@@ -78,15 +87,15 @@ template<typename Function>
|
|||||||
using DefaultCollectorType = typename DefaultCollector<Function>::collector_type;
|
using DefaultCollectorType = typename DefaultCollector<Function>::collector_type;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal details not to be documented.
|
* Internal details not to be documented.
|
||||||
* @endcond TURN_OFF_DOXYGEN
|
* @endcond TURN_OFF_DOXYGEN
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Sink implementation.
|
* @brief Sink implementation.
|
||||||
*
|
*
|
||||||
@@ -132,20 +141,24 @@ class Sink<Ret(Args...)> final {
|
|||||||
template<typename, typename>
|
template<typename, typename>
|
||||||
friend class SigH;
|
friend class SigH;
|
||||||
|
|
||||||
using proto_fn_type = Ret(void *, Args...);
|
using call_type = typename internal::sigh_traits<Ret(Args...)>::call_type;
|
||||||
using call_type = std::pair<void *, proto_fn_type *>;
|
|
||||||
|
|
||||||
template<Ret(*Function)(Args...)>
|
template<Ret(*Function)(Args...)>
|
||||||
static Ret proto(void *, Args... args) {
|
static Ret proto(void *, Args... args) {
|
||||||
return (Function)(args...);
|
return (Function)(args...);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<typename Class, Ret(Class:: *Member)(Args... args) const>
|
||||||
|
static Ret proto(void *instance, Args... args) {
|
||||||
|
return (static_cast<const Class *>(instance)->*Member)(args...);
|
||||||
|
}
|
||||||
|
|
||||||
template<typename Class, Ret(Class:: *Member)(Args... args)>
|
template<typename Class, Ret(Class:: *Member)(Args... args)>
|
||||||
static Ret proto(void *instance, Args... args) {
|
static Ret proto(void *instance, Args... args) {
|
||||||
return (static_cast<Class *>(instance)->*Member)(args...);
|
return (static_cast<Class *>(instance)->*Member)(args...);
|
||||||
}
|
}
|
||||||
|
|
||||||
Sink(std::vector<call_type> &calls) ENTT_NOEXCEPT
|
Sink(std::vector<call_type> *calls) ENTT_NOEXCEPT
|
||||||
: calls{calls}
|
: calls{calls}
|
||||||
{}
|
{}
|
||||||
|
|
||||||
@@ -161,7 +174,7 @@ public:
|
|||||||
template<Ret(*Function)(Args...)>
|
template<Ret(*Function)(Args...)>
|
||||||
void connect() {
|
void connect() {
|
||||||
disconnect<Function>();
|
disconnect<Function>();
|
||||||
calls.emplace_back(nullptr, &proto<Function>);
|
calls->emplace_back(nullptr, &proto<Function>);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -177,10 +190,29 @@ public:
|
|||||||
* @tparam Member Member function to connect to the signal.
|
* @tparam Member Member function to connect to the signal.
|
||||||
* @param instance A valid instance of type pointer to `Class`.
|
* @param instance A valid instance of type pointer to `Class`.
|
||||||
*/
|
*/
|
||||||
template <typename Class, Ret(Class:: *Member)(Args...) = &Class::receive>
|
template<typename Class, Ret(Class:: *Member)(Args...) const = &Class::receive>
|
||||||
void connect(Class *instance) {
|
void connect(Class *instance) {
|
||||||
disconnect<Class, Member>(instance);
|
disconnect<Class, Member>(instance);
|
||||||
calls.emplace_back(instance, &proto<Class, Member>);
|
calls->emplace_back(instance, &proto<Class, Member>);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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. On the other side, the signal handler performs checks to
|
||||||
|
* avoid multiple connections for the same member function of a given
|
||||||
|
* instance.
|
||||||
|
*
|
||||||
|
* @tparam Class Type of class to which the member function belongs.
|
||||||
|
* @tparam Member Member function to connect to the signal.
|
||||||
|
* @param instance A valid instance of type pointer to `Class`.
|
||||||
|
*/
|
||||||
|
template<typename Class, Ret(Class:: *Member)(Args...) = &Class::receive>
|
||||||
|
void connect(Class *instance) {
|
||||||
|
disconnect<Class, Member>(instance);
|
||||||
|
calls->emplace_back(instance, &proto<Class, Member>);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -190,7 +222,19 @@ public:
|
|||||||
template<Ret(*Function)(Args...)>
|
template<Ret(*Function)(Args...)>
|
||||||
void disconnect() {
|
void disconnect() {
|
||||||
call_type target{nullptr, &proto<Function>};
|
call_type target{nullptr, &proto<Function>};
|
||||||
calls.erase(std::remove(calls.begin(), calls.end(), std::move(target)), calls.end());
|
calls->erase(std::remove(calls->begin(), calls->end(), std::move(target)), 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, Ret(Class:: *Member)(Args...) const>
|
||||||
|
void disconnect(Class *instance) {
|
||||||
|
call_type target{instance, &proto<Class, Member>};
|
||||||
|
calls->erase(std::remove(calls->begin(), calls->end(), std::move(target)), calls->end());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -202,7 +246,7 @@ public:
|
|||||||
template<typename Class, Ret(Class:: *Member)(Args...)>
|
template<typename Class, Ret(Class:: *Member)(Args...)>
|
||||||
void disconnect(Class *instance) {
|
void disconnect(Class *instance) {
|
||||||
call_type target{instance, &proto<Class, Member>};
|
call_type target{instance, &proto<Class, Member>};
|
||||||
calls.erase(std::remove(calls.begin(), calls.end(), std::move(target)), calls.end());
|
calls->erase(std::remove(calls->begin(), calls->end(), std::move(target)), calls->end());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -213,18 +257,18 @@ public:
|
|||||||
template<typename Class>
|
template<typename Class>
|
||||||
void disconnect(Class *instance) {
|
void disconnect(Class *instance) {
|
||||||
auto func = [instance](const call_type &call) { return call.first == 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());
|
calls->erase(std::remove_if(calls->begin(), calls->end(), std::move(func)), calls->end());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Disconnects all the listeners from a signal.
|
* @brief Disconnects all the listeners from a signal.
|
||||||
*/
|
*/
|
||||||
void disconnect() {
|
void disconnect() {
|
||||||
calls.clear();
|
calls->clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::vector<call_type> &calls;
|
std::vector<call_type> *calls;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -253,7 +297,7 @@ private:
|
|||||||
*/
|
*/
|
||||||
template<typename Ret, typename... Args, typename Collector>
|
template<typename Ret, typename... Args, typename Collector>
|
||||||
class SigH<Ret(Args...), Collector> final: private internal::Invoker<Ret(Args...), Collector> {
|
class SigH<Ret(Args...), Collector> final: private internal::Invoker<Ret(Args...), Collector> {
|
||||||
using call_type = typename internal::Invoker<Ret(Args...), Collector>::call_type;
|
using call_type = typename internal::sigh_traits<Ret(Args...)>::call_type;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/*! @brief Unsigned integer type. */
|
/*! @brief Unsigned integer type. */
|
||||||
@@ -296,7 +340,7 @@ public:
|
|||||||
* @return A temporary sink object.
|
* @return A temporary sink object.
|
||||||
*/
|
*/
|
||||||
sink_type sink() ENTT_NOEXCEPT {
|
sink_type sink() ENTT_NOEXCEPT {
|
||||||
return { calls };
|
return { &calls };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ set_target_properties(odr PROPERTIES CXX_EXTENSIONS OFF)
|
|||||||
target_compile_definitions(odr PRIVATE $<TARGET_PROPERTY:EnTT,INTERFACE_COMPILE_DEFINITIONS>)
|
target_compile_definitions(odr PRIVATE $<TARGET_PROPERTY:EnTT,INTERFACE_COMPILE_DEFINITIONS>)
|
||||||
target_compile_features(odr PRIVATE $<TARGET_PROPERTY:EnTT,INTERFACE_COMPILE_FEATURES>)
|
target_compile_features(odr PRIVATE $<TARGET_PROPERTY:EnTT,INTERFACE_COMPILE_FEATURES>)
|
||||||
target_compile_options(odr PRIVATE $<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-pedantic -Wall>)
|
target_compile_options(odr PRIVATE $<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-pedantic -Wall>)
|
||||||
|
target_compile_options(odr PRIVATE $<$<CXX_COMPILER_ID:MSVC>:/EHsc>)
|
||||||
|
|
||||||
macro(SETUP_AND_ADD_TEST TEST_NAME TEST_SOURCE)
|
macro(SETUP_AND_ADD_TEST TEST_NAME TEST_SOURCE)
|
||||||
add_executable(${TEST_NAME} $<TARGET_OBJECTS:odr> ${TEST_SOURCE})
|
add_executable(${TEST_NAME} $<TARGET_OBJECTS:odr> ${TEST_SOURCE})
|
||||||
@@ -18,6 +19,7 @@ macro(SETUP_AND_ADD_TEST TEST_NAME TEST_SOURCE)
|
|||||||
target_compile_definitions(${TEST_NAME} PRIVATE $<TARGET_PROPERTY:EnTT,INTERFACE_COMPILE_DEFINITIONS>)
|
target_compile_definitions(${TEST_NAME} PRIVATE $<TARGET_PROPERTY:EnTT,INTERFACE_COMPILE_DEFINITIONS>)
|
||||||
target_compile_features(${TEST_NAME} PRIVATE $<TARGET_PROPERTY:EnTT,INTERFACE_COMPILE_FEATURES>)
|
target_compile_features(${TEST_NAME} PRIVATE $<TARGET_PROPERTY:EnTT,INTERFACE_COMPILE_FEATURES>)
|
||||||
target_compile_options(${TEST_NAME} PRIVATE $<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-pedantic -Wall>)
|
target_compile_options(${TEST_NAME} PRIVATE $<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-pedantic -Wall>)
|
||||||
|
target_compile_options(${TEST_NAME} PRIVATE $<$<CXX_COMPILER_ID:MSVC>:/EHsc>)
|
||||||
add_test(NAME ${TEST_NAME} COMMAND ${TEST_NAME})
|
add_test(NAME ${TEST_NAME} COMMAND ${TEST_NAME})
|
||||||
endmacro()
|
endmacro()
|
||||||
|
|
||||||
@@ -65,6 +67,7 @@ SETUP_AND_ADD_TEST(monostate entt/core/monostate.cpp)
|
|||||||
# Test entity
|
# Test entity
|
||||||
|
|
||||||
SETUP_AND_ADD_TEST(actor entt/entity/actor.cpp)
|
SETUP_AND_ADD_TEST(actor entt/entity/actor.cpp)
|
||||||
|
SETUP_AND_ADD_TEST(attachee entt/entity/attachee.cpp)
|
||||||
SETUP_AND_ADD_TEST(entity entt/entity/entity.cpp)
|
SETUP_AND_ADD_TEST(entity entt/entity/entity.cpp)
|
||||||
SETUP_AND_ADD_TEST(helper entt/entity/helper.cpp)
|
SETUP_AND_ADD_TEST(helper entt/entity/helper.cpp)
|
||||||
SETUP_AND_ADD_TEST(prototype entt/entity/prototype.cpp)
|
SETUP_AND_ADD_TEST(prototype entt/entity/prototype.cpp)
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ TEST(HashedString, Functionalities) {
|
|||||||
auto barHs = entt::HashedString{bar};
|
auto barHs = entt::HashedString{bar};
|
||||||
|
|
||||||
ASSERT_NE(static_cast<hash_type>(fooHs), static_cast<hash_type>(barHs));
|
ASSERT_NE(static_cast<hash_type>(fooHs), static_cast<hash_type>(barHs));
|
||||||
ASSERT_EQ(static_cast<const char *>(fooHs), "foo");
|
ASSERT_STREQ(static_cast<const char *>(fooHs), "foo");
|
||||||
ASSERT_EQ(static_cast<const char *>(barHs), bar);
|
ASSERT_STREQ(static_cast<const char *>(barHs), bar);
|
||||||
|
|
||||||
ASSERT_EQ(fooHs, fooHs);
|
ASSERT_EQ(fooHs, fooHs);
|
||||||
ASSERT_NE(fooHs, barHs);
|
ASSERT_NE(fooHs, barHs);
|
||||||
|
|||||||
69
test/entt/entity/attachee.cpp
Normal file
69
test/entt/entity/attachee.cpp
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
#include <unordered_set>
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <entt/entity/attachee.hpp>
|
||||||
|
|
||||||
|
TEST(AttacheeNoType, Functionalities) {
|
||||||
|
entt::Attachee<std::uint64_t> attachee;
|
||||||
|
|
||||||
|
attachee.construct(42u);
|
||||||
|
|
||||||
|
ASSERT_EQ(attachee.get(), 42u);
|
||||||
|
|
||||||
|
attachee.destroy();
|
||||||
|
|
||||||
|
ASSERT_NE(attachee.get(), 42u);
|
||||||
|
|
||||||
|
(void)entt::Attachee<std::uint64_t>{std::move(attachee)};
|
||||||
|
entt::Attachee<std::uint64_t> other;
|
||||||
|
other = std::move(attachee);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(AttacheeWithType, Functionalities) {
|
||||||
|
entt::Attachee<std::uint64_t, int> attachee;
|
||||||
|
const auto &cattachee = attachee;
|
||||||
|
|
||||||
|
attachee.construct(42u, 3);
|
||||||
|
|
||||||
|
ASSERT_EQ(attachee.get(), 3);
|
||||||
|
ASSERT_EQ(cattachee.get(), 3);
|
||||||
|
ASSERT_EQ(attachee.Attachee<std::uint64_t>::get(), 42u);
|
||||||
|
|
||||||
|
attachee.move(0u);
|
||||||
|
|
||||||
|
ASSERT_EQ(attachee.get(), 3);
|
||||||
|
ASSERT_EQ(cattachee.get(), 3);
|
||||||
|
ASSERT_EQ(attachee.Attachee<std::uint64_t>::get(), 0u);
|
||||||
|
|
||||||
|
attachee.destroy();
|
||||||
|
|
||||||
|
ASSERT_NE(attachee.Attachee<std::uint64_t>::get(), 0u);
|
||||||
|
ASSERT_NE(attachee.Attachee<std::uint64_t>::get(), 42u);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(AttacheeWithType, AggregatesMustWork) {
|
||||||
|
struct AggregateType { int value; };
|
||||||
|
// the goal of this test is to enforce the requirements for aggregate types
|
||||||
|
entt::Attachee<std::uint64_t, AggregateType>{}.construct(0, 42);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(AttacheeWithType, TypesFromStandardTemplateLibraryMustWork) {
|
||||||
|
// see #37 - this test shouldn't crash, that's all
|
||||||
|
entt::Attachee<std::uint64_t, std::unordered_set<int>> attachee;
|
||||||
|
attachee.construct(0).insert(42);
|
||||||
|
attachee.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(AttacheeWithType, MoveOnlyComponent) {
|
||||||
|
struct MoveOnlyComponent {
|
||||||
|
MoveOnlyComponent() = default;
|
||||||
|
~MoveOnlyComponent() = default;
|
||||||
|
MoveOnlyComponent(const MoveOnlyComponent &) = delete;
|
||||||
|
MoveOnlyComponent(MoveOnlyComponent &&) = default;
|
||||||
|
MoveOnlyComponent & operator=(const MoveOnlyComponent &) = delete;
|
||||||
|
MoveOnlyComponent & operator=(MoveOnlyComponent &&) = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
// it's purpose is to ensure that move only components are always accepted
|
||||||
|
entt::Attachee<std::uint64_t, MoveOnlyComponent> attachee;
|
||||||
|
(void)attachee;
|
||||||
|
}
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
TEST(Helper, Dependency) {
|
TEST(Helper, Dependency) {
|
||||||
entt::DefaultRegistry registry;
|
entt::DefaultRegistry registry;
|
||||||
const auto entity = registry.create();
|
const auto entity = registry.create();
|
||||||
entt::dependency<double, float>(registry.construction<int>());
|
entt::connect<double, float>(registry.construction<int>());
|
||||||
|
|
||||||
ASSERT_FALSE(registry.has<double>(entity));
|
ASSERT_FALSE(registry.has<double>(entity));
|
||||||
ASSERT_FALSE(registry.has<float>(entity));
|
ASSERT_FALSE(registry.has<float>(entity));
|
||||||
@@ -42,7 +42,7 @@ TEST(Helper, Dependency) {
|
|||||||
registry.remove<int>(entity);
|
registry.remove<int>(entity);
|
||||||
registry.remove<double>(entity);
|
registry.remove<double>(entity);
|
||||||
registry.remove<float>(entity);
|
registry.remove<float>(entity);
|
||||||
entt::dependency<double, float>(entt::break_t{}, registry.construction<int>());
|
entt::disconnect<double, float>(registry.construction<int>());
|
||||||
registry.assign<int>(entity);
|
registry.assign<int>(entity);
|
||||||
|
|
||||||
ASSERT_FALSE(registry.has<double>(entity));
|
ASSERT_FALSE(registry.has<double>(entity));
|
||||||
|
|||||||
@@ -376,8 +376,10 @@ TEST(DefaultRegistry, CreateDestroyEntities) {
|
|||||||
TEST(DefaultRegistry, AttachSetRemoveTags) {
|
TEST(DefaultRegistry, AttachSetRemoveTags) {
|
||||||
entt::DefaultRegistry registry;
|
entt::DefaultRegistry registry;
|
||||||
const auto &cregistry = registry;
|
const auto &cregistry = registry;
|
||||||
|
const typename decltype(registry)::entity_type null = entt::null;
|
||||||
|
|
||||||
ASSERT_FALSE(registry.has<int>());
|
ASSERT_FALSE(registry.has<int>());
|
||||||
|
ASSERT_EQ(registry.attachee<int>(), null);
|
||||||
|
|
||||||
const auto entity = registry.create();
|
const auto entity = registry.create();
|
||||||
registry.assign<int>(entt::tag_t{}, entity, 42);
|
registry.assign<int>(entt::tag_t{}, entity, 42);
|
||||||
@@ -411,6 +413,7 @@ TEST(DefaultRegistry, AttachSetRemoveTags) {
|
|||||||
ASSERT_FALSE(registry.has<int>());
|
ASSERT_FALSE(registry.has<int>());
|
||||||
ASSERT_FALSE(registry.has<int>(entt::tag_t{}, entity));
|
ASSERT_FALSE(registry.has<int>(entt::tag_t{}, entity));
|
||||||
ASSERT_FALSE(registry.has<int>(entt::tag_t{}, other));
|
ASSERT_FALSE(registry.has<int>(entt::tag_t{}, other));
|
||||||
|
ASSERT_EQ(registry.attachee<int>(), null);
|
||||||
|
|
||||||
registry.assign<int>(entt::tag_t{}, entity, 42);
|
registry.assign<int>(entt::tag_t{}, entity, 42);
|
||||||
registry.destroy(entity);
|
registry.destroy(entity);
|
||||||
@@ -811,3 +814,14 @@ TEST(DefaultRegistry, DestroyByTagAndComponents) {
|
|||||||
registry.destroy<double>(entt::tag_t{});
|
registry.destroy<double>(entt::tag_t{});
|
||||||
registry.destroy<float>(entt::tag_t{});
|
registry.destroy<float>(entt::tag_t{});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(DefaultRegistry, SignalsOnAccommodate) {
|
||||||
|
entt::DefaultRegistry registry;
|
||||||
|
const auto entity = registry.create();
|
||||||
|
|
||||||
|
registry.prepare<int, char>();
|
||||||
|
registry.assign<int>(entity);
|
||||||
|
registry.accommodate<char>(entity);
|
||||||
|
|
||||||
|
ASSERT_FALSE((registry.view<int, char>(entt::persistent_t{}).empty()));
|
||||||
|
}
|
||||||
|
|||||||
@@ -400,8 +400,8 @@ TEST(SparseSetWithType, Functionalities) {
|
|||||||
ASSERT_FALSE(set.has(0));
|
ASSERT_FALSE(set.has(0));
|
||||||
ASSERT_FALSE(set.has(42));
|
ASSERT_FALSE(set.has(42));
|
||||||
|
|
||||||
(void)entt::SparseSet<std::uint64_t>{std::move(set)};
|
(void)entt::SparseSet<std::uint64_t, int>{std::move(set)};
|
||||||
entt::SparseSet<std::uint64_t> other;
|
entt::SparseSet<std::uint64_t, int> other;
|
||||||
other = std::move(set);
|
other = std::move(set);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
#include <entt/entity/view.hpp>
|
#include <entt/entity/view.hpp>
|
||||||
|
|
||||||
TEST(PersistentView, Prepare) {
|
TEST(PersistentView, Prepare) {
|
||||||
entt::Registry<std::uint64_t> registry;
|
entt::DefaultRegistry registry;
|
||||||
registry.prepare<int, char>();
|
registry.prepare<int, char>();
|
||||||
auto view = registry.view<int, char>(entt::persistent_t{});
|
auto view = registry.view<int, char>(entt::persistent_t{});
|
||||||
const auto &cview = view;
|
const auto &cview = view;
|
||||||
@@ -57,7 +57,7 @@ TEST(PersistentView, Prepare) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST(PersistentView, NoPrepare) {
|
TEST(PersistentView, NoPrepare) {
|
||||||
entt::Registry<std::uint64_t> registry;
|
entt::DefaultRegistry registry;
|
||||||
auto view = registry.view<int, char>(entt::persistent_t{});
|
auto view = registry.view<int, char>(entt::persistent_t{});
|
||||||
|
|
||||||
ASSERT_TRUE(view.empty());
|
ASSERT_TRUE(view.empty());
|
||||||
@@ -106,7 +106,7 @@ TEST(PersistentView, NoPrepare) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST(PersistentView, ElementAccess) {
|
TEST(PersistentView, ElementAccess) {
|
||||||
entt::Registry<std::uint64_t> registry;
|
entt::DefaultRegistry registry;
|
||||||
auto view = registry.view<int, char>(entt::persistent_t{});
|
auto view = registry.view<int, char>(entt::persistent_t{});
|
||||||
const auto &cview = view;
|
const auto &cview = view;
|
||||||
|
|
||||||
@@ -125,7 +125,7 @@ TEST(PersistentView, ElementAccess) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST(PersistentView, Contains) {
|
TEST(PersistentView, Contains) {
|
||||||
entt::Registry<std::uint64_t> registry;
|
entt::DefaultRegistry registry;
|
||||||
|
|
||||||
const auto e0 = registry.create();
|
const auto e0 = registry.create();
|
||||||
registry.assign<int>(e0);
|
registry.assign<int>(e0);
|
||||||
@@ -144,7 +144,7 @@ TEST(PersistentView, Contains) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST(PersistentView, Empty) {
|
TEST(PersistentView, Empty) {
|
||||||
entt::Registry<std::uint64_t> registry;
|
entt::DefaultRegistry registry;
|
||||||
|
|
||||||
const auto e0 = registry.create();
|
const auto e0 = registry.create();
|
||||||
registry.assign<double>(e0);
|
registry.assign<double>(e0);
|
||||||
@@ -167,7 +167,7 @@ TEST(PersistentView, Empty) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST(PersistentView, Each) {
|
TEST(PersistentView, Each) {
|
||||||
entt::Registry<std::uint64_t> registry;
|
entt::DefaultRegistry registry;
|
||||||
registry.prepare<int, char>();
|
registry.prepare<int, char>();
|
||||||
|
|
||||||
const auto e0 = registry.create();
|
const auto e0 = registry.create();
|
||||||
@@ -192,7 +192,7 @@ TEST(PersistentView, Each) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST(PersistentView, Sort) {
|
TEST(PersistentView, Sort) {
|
||||||
entt::Registry<std::uint64_t> registry;
|
entt::DefaultRegistry registry;
|
||||||
registry.prepare<int, unsigned int>();
|
registry.prepare<int, unsigned int>();
|
||||||
|
|
||||||
const auto e0 = registry.create();
|
const auto e0 = registry.create();
|
||||||
@@ -227,7 +227,7 @@ TEST(PersistentView, Sort) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST(SingleComponentView, Functionalities) {
|
TEST(SingleComponentView, Functionalities) {
|
||||||
entt::Registry<std::uint64_t> registry;
|
entt::DefaultRegistry registry;
|
||||||
auto view = registry.view<char>();
|
auto view = registry.view<char>();
|
||||||
const auto &cview = view;
|
const auto &cview = view;
|
||||||
|
|
||||||
@@ -274,7 +274,7 @@ TEST(SingleComponentView, Functionalities) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST(SingleComponentView, ElementAccess) {
|
TEST(SingleComponentView, ElementAccess) {
|
||||||
entt::Registry<std::uint64_t> registry;
|
entt::DefaultRegistry registry;
|
||||||
auto view = registry.view<int>();
|
auto view = registry.view<int>();
|
||||||
const auto &cview = view;
|
const auto &cview = view;
|
||||||
|
|
||||||
@@ -291,7 +291,7 @@ TEST(SingleComponentView, ElementAccess) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST(SingleComponentView, Contains) {
|
TEST(SingleComponentView, Contains) {
|
||||||
entt::Registry<std::uint64_t> registry;
|
entt::DefaultRegistry registry;
|
||||||
|
|
||||||
const auto e0 = registry.create();
|
const auto e0 = registry.create();
|
||||||
registry.assign<int>(e0);
|
registry.assign<int>(e0);
|
||||||
@@ -308,7 +308,7 @@ TEST(SingleComponentView, Contains) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST(SingleComponentView, Empty) {
|
TEST(SingleComponentView, Empty) {
|
||||||
entt::Registry<std::uint64_t> registry;
|
entt::DefaultRegistry registry;
|
||||||
|
|
||||||
const auto e0 = registry.create();
|
const auto e0 = registry.create();
|
||||||
registry.assign<char>(e0);
|
registry.assign<char>(e0);
|
||||||
@@ -319,7 +319,7 @@ TEST(SingleComponentView, Empty) {
|
|||||||
|
|
||||||
auto view = registry.view<int>();
|
auto view = registry.view<int>();
|
||||||
|
|
||||||
ASSERT_EQ(view.size(), entt::Registry<std::uint64_t>::size_type{0});
|
ASSERT_EQ(view.size(), entt::DefaultRegistry::size_type{0});
|
||||||
|
|
||||||
for(auto entity: view) {
|
for(auto entity: view) {
|
||||||
(void)entity;
|
(void)entity;
|
||||||
@@ -328,7 +328,7 @@ TEST(SingleComponentView, Empty) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST(SingleComponentView, Each) {
|
TEST(SingleComponentView, Each) {
|
||||||
entt::Registry<std::uint64_t> registry;
|
entt::DefaultRegistry registry;
|
||||||
|
|
||||||
registry.assign<int>(registry.create());
|
registry.assign<int>(registry.create());
|
||||||
registry.assign<int>(registry.create());
|
registry.assign<int>(registry.create());
|
||||||
@@ -347,7 +347,7 @@ TEST(SingleComponentView, Each) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST(MultipleComponentView, Functionalities) {
|
TEST(MultipleComponentView, Functionalities) {
|
||||||
entt::Registry<std::uint64_t> registry;
|
entt::DefaultRegistry registry;
|
||||||
auto view = registry.view<int, char>();
|
auto view = registry.view<int, char>();
|
||||||
const auto &cview = view;
|
const auto &cview = view;
|
||||||
|
|
||||||
@@ -388,7 +388,7 @@ TEST(MultipleComponentView, Functionalities) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST(MultipleComponentView, Iterator) {
|
TEST(MultipleComponentView, Iterator) {
|
||||||
entt::Registry<std::uint64_t> registry;
|
entt::DefaultRegistry registry;
|
||||||
const auto entity = registry.create();
|
const auto entity = registry.create();
|
||||||
registry.assign<int>(entity);
|
registry.assign<int>(entity);
|
||||||
registry.assign<char>(entity);
|
registry.assign<char>(entity);
|
||||||
@@ -410,7 +410,7 @@ TEST(MultipleComponentView, Iterator) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST(MultipleComponentView, ConstIterator) {
|
TEST(MultipleComponentView, ConstIterator) {
|
||||||
entt::Registry<std::uint64_t> registry;
|
entt::DefaultRegistry registry;
|
||||||
const auto entity = registry.create();
|
const auto entity = registry.create();
|
||||||
registry.assign<int>(entity);
|
registry.assign<int>(entity);
|
||||||
registry.assign<char>(entity);
|
registry.assign<char>(entity);
|
||||||
@@ -432,7 +432,7 @@ TEST(MultipleComponentView, ConstIterator) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST(MultipleComponentView, Contains) {
|
TEST(MultipleComponentView, Contains) {
|
||||||
entt::Registry<std::uint64_t> registry;
|
entt::DefaultRegistry registry;
|
||||||
|
|
||||||
const auto e0 = registry.create();
|
const auto e0 = registry.create();
|
||||||
registry.assign<int>(e0);
|
registry.assign<int>(e0);
|
||||||
@@ -451,7 +451,7 @@ TEST(MultipleComponentView, Contains) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST(MultipleComponentView, Empty) {
|
TEST(MultipleComponentView, Empty) {
|
||||||
entt::Registry<std::uint64_t> registry;
|
entt::DefaultRegistry registry;
|
||||||
|
|
||||||
const auto e0 = registry.create();
|
const auto e0 = registry.create();
|
||||||
registry.assign<double>(e0);
|
registry.assign<double>(e0);
|
||||||
@@ -471,7 +471,7 @@ TEST(MultipleComponentView, Empty) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST(MultipleComponentView, Each) {
|
TEST(MultipleComponentView, Each) {
|
||||||
entt::Registry<std::uint64_t> registry;
|
entt::DefaultRegistry registry;
|
||||||
|
|
||||||
const auto e0 = registry.create();
|
const auto e0 = registry.create();
|
||||||
registry.assign<int>(e0);
|
registry.assign<int>(e0);
|
||||||
@@ -495,7 +495,7 @@ TEST(MultipleComponentView, Each) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST(MultipleComponentView, EachWithHoles) {
|
TEST(MultipleComponentView, EachWithHoles) {
|
||||||
entt::Registry<std::uint64_t> registry;
|
entt::DefaultRegistry registry;
|
||||||
|
|
||||||
const auto e0 = registry.create();
|
const auto e0 = registry.create();
|
||||||
const auto e1 = registry.create();
|
const auto e1 = registry.create();
|
||||||
@@ -520,7 +520,7 @@ TEST(MultipleComponentView, EachWithHoles) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST(RawView, Functionalities) {
|
TEST(RawView, Functionalities) {
|
||||||
entt::Registry<std::uint64_t> registry;
|
entt::DefaultRegistry registry;
|
||||||
auto view = registry.view<char>(entt::raw_t{});
|
auto view = registry.view<char>(entt::raw_t{});
|
||||||
const auto &cview = view;
|
const auto &cview = view;
|
||||||
|
|
||||||
@@ -575,7 +575,7 @@ TEST(RawView, Functionalities) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST(RawView, ElementAccess) {
|
TEST(RawView, ElementAccess) {
|
||||||
entt::Registry<std::uint64_t> registry;
|
entt::DefaultRegistry registry;
|
||||||
auto view = registry.view<int>(entt::raw_t{});
|
auto view = registry.view<int>(entt::raw_t{});
|
||||||
const auto &cview = view;
|
const auto &cview = view;
|
||||||
|
|
||||||
@@ -592,7 +592,7 @@ TEST(RawView, ElementAccess) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST(RawView, Empty) {
|
TEST(RawView, Empty) {
|
||||||
entt::Registry<std::uint64_t> registry;
|
entt::DefaultRegistry registry;
|
||||||
|
|
||||||
const auto e0 = registry.create();
|
const auto e0 = registry.create();
|
||||||
registry.assign<char>(e0);
|
registry.assign<char>(e0);
|
||||||
@@ -603,7 +603,7 @@ TEST(RawView, Empty) {
|
|||||||
|
|
||||||
auto view = registry.view<int>(entt::raw_t{});
|
auto view = registry.view<int>(entt::raw_t{});
|
||||||
|
|
||||||
ASSERT_EQ(view.size(), entt::Registry<std::uint64_t>::size_type{0});
|
ASSERT_EQ(view.size(), entt::DefaultRegistry::size_type{0});
|
||||||
|
|
||||||
for(auto &&component: view) {
|
for(auto &&component: view) {
|
||||||
(void)component;
|
(void)component;
|
||||||
@@ -612,7 +612,7 @@ TEST(RawView, Empty) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST(RawView, Each) {
|
TEST(RawView, Each) {
|
||||||
entt::Registry<std::uint64_t> registry;
|
entt::DefaultRegistry registry;
|
||||||
|
|
||||||
registry.assign<int>(registry.create(), 1);
|
registry.assign<int>(registry.create(), 1);
|
||||||
registry.assign<int>(registry.create(), 3);
|
registry.assign<int>(registry.create(), 3);
|
||||||
@@ -631,7 +631,7 @@ TEST(RawView, Each) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST(RuntimeView, Functionalities) {
|
TEST(RuntimeView, Functionalities) {
|
||||||
entt::Registry<std::uint64_t> registry;
|
entt::DefaultRegistry registry;
|
||||||
using component_type = typename decltype(registry)::component_type;
|
using component_type = typename decltype(registry)::component_type;
|
||||||
|
|
||||||
// forces the creation of the pools
|
// forces the creation of the pools
|
||||||
@@ -677,7 +677,7 @@ TEST(RuntimeView, Functionalities) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST(RuntimeView, Iterator) {
|
TEST(RuntimeView, Iterator) {
|
||||||
entt::Registry<std::uint64_t> registry;
|
entt::DefaultRegistry registry;
|
||||||
using component_type = typename decltype(registry)::component_type;
|
using component_type = typename decltype(registry)::component_type;
|
||||||
|
|
||||||
const auto entity = registry.create();
|
const auto entity = registry.create();
|
||||||
@@ -702,7 +702,7 @@ TEST(RuntimeView, Iterator) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST(RuntimeView, ConstIterator) {
|
TEST(RuntimeView, ConstIterator) {
|
||||||
entt::Registry<std::uint64_t> registry;
|
entt::DefaultRegistry registry;
|
||||||
using component_type = typename decltype(registry)::component_type;
|
using component_type = typename decltype(registry)::component_type;
|
||||||
|
|
||||||
const auto entity = registry.create();
|
const auto entity = registry.create();
|
||||||
@@ -727,7 +727,7 @@ TEST(RuntimeView, ConstIterator) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST(RuntimeView, Contains) {
|
TEST(RuntimeView, Contains) {
|
||||||
entt::Registry<std::uint64_t> registry;
|
entt::DefaultRegistry registry;
|
||||||
using component_type = typename decltype(registry)::component_type;
|
using component_type = typename decltype(registry)::component_type;
|
||||||
|
|
||||||
const auto e0 = registry.create();
|
const auto e0 = registry.create();
|
||||||
@@ -748,7 +748,7 @@ TEST(RuntimeView, Contains) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST(RuntimeView, Empty) {
|
TEST(RuntimeView, Empty) {
|
||||||
entt::Registry<std::uint64_t> registry;
|
entt::DefaultRegistry registry;
|
||||||
using component_type = typename decltype(registry)::component_type;
|
using component_type = typename decltype(registry)::component_type;
|
||||||
|
|
||||||
const auto e0 = registry.create();
|
const auto e0 = registry.create();
|
||||||
@@ -770,7 +770,7 @@ TEST(RuntimeView, Empty) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST(RuntimeView, Each) {
|
TEST(RuntimeView, Each) {
|
||||||
entt::Registry<std::uint64_t> registry;
|
entt::DefaultRegistry registry;
|
||||||
using component_type = typename decltype(registry)::component_type;
|
using component_type = typename decltype(registry)::component_type;
|
||||||
|
|
||||||
const auto e0 = registry.create();
|
const auto e0 = registry.create();
|
||||||
@@ -791,7 +791,7 @@ TEST(RuntimeView, Each) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST(RuntimeView, EachWithHoles) {
|
TEST(RuntimeView, EachWithHoles) {
|
||||||
entt::Registry<std::uint64_t> registry;
|
entt::DefaultRegistry registry;
|
||||||
using component_type = typename decltype(registry)::component_type;
|
using component_type = typename decltype(registry)::component_type;
|
||||||
|
|
||||||
const auto e0 = registry.create();
|
const auto e0 = registry.create();
|
||||||
@@ -813,7 +813,7 @@ TEST(RuntimeView, EachWithHoles) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST(RuntimeView, MissingPool) {
|
TEST(RuntimeView, MissingPool) {
|
||||||
entt::Registry<std::uint64_t> registry;
|
entt::DefaultRegistry registry;
|
||||||
using component_type = typename decltype(registry)::component_type;
|
using component_type = typename decltype(registry)::component_type;
|
||||||
|
|
||||||
const auto e0 = registry.create();
|
const auto e0 = registry.create();
|
||||||
@@ -840,7 +840,7 @@ TEST(RuntimeView, MissingPool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST(RuntimeView, EmptyRange) {
|
TEST(RuntimeView, EmptyRange) {
|
||||||
entt::Registry<std::uint64_t> registry;
|
entt::DefaultRegistry registry;
|
||||||
using component_type = typename decltype(registry)::component_type;
|
using component_type = typename decltype(registry)::component_type;
|
||||||
|
|
||||||
const auto e0 = registry.create();
|
const auto e0 = registry.create();
|
||||||
|
|||||||
@@ -11,6 +11,14 @@ struct DelegateFunctor {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct ConstNonConstNoExcept {
|
||||||
|
void f() { ++cnt; }
|
||||||
|
void g() noexcept { ++cnt; }
|
||||||
|
void h() const { ++cnt; }
|
||||||
|
void i() const noexcept { ++cnt; }
|
||||||
|
mutable int cnt{0};
|
||||||
|
};
|
||||||
|
|
||||||
TEST(Delegate, Functionalities) {
|
TEST(Delegate, Functionalities) {
|
||||||
entt::Delegate<int(int)> ffdel;
|
entt::Delegate<int(int)> ffdel;
|
||||||
entt::Delegate<int(int)> mfdel;
|
entt::Delegate<int(int)> mfdel;
|
||||||
@@ -46,3 +54,22 @@ TEST(Delegate, Comparison) {
|
|||||||
ASSERT_TRUE(def == entt::Delegate<int(int)>{});
|
ASSERT_TRUE(def == entt::Delegate<int(int)>{});
|
||||||
ASSERT_TRUE (def != delegate);
|
ASSERT_TRUE (def != delegate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(Delegate, ConstNonConstNoExcept) {
|
||||||
|
entt::Delegate<void()> delegate;
|
||||||
|
ConstNonConstNoExcept functor;
|
||||||
|
|
||||||
|
delegate.connect<ConstNonConstNoExcept, &ConstNonConstNoExcept::f>(&functor);
|
||||||
|
delegate();
|
||||||
|
|
||||||
|
delegate.connect<ConstNonConstNoExcept, &ConstNonConstNoExcept::g>(&functor);
|
||||||
|
delegate();
|
||||||
|
|
||||||
|
delegate.connect<ConstNonConstNoExcept, &ConstNonConstNoExcept::h>(&functor);
|
||||||
|
delegate();
|
||||||
|
|
||||||
|
delegate.connect<ConstNonConstNoExcept, &ConstNonConstNoExcept::i>(&functor);
|
||||||
|
delegate();
|
||||||
|
|
||||||
|
ASSERT_EQ(functor.cnt, 4);
|
||||||
|
}
|
||||||
|
|||||||
@@ -45,6 +45,14 @@ struct TestCollectFirst {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct ConstNonConstNoExcept {
|
||||||
|
void f() { ++cnt; }
|
||||||
|
void g() noexcept { ++cnt; }
|
||||||
|
void h() const { ++cnt; }
|
||||||
|
void i() const noexcept { ++cnt; }
|
||||||
|
mutable int cnt{0};
|
||||||
|
};
|
||||||
|
|
||||||
TEST(SigH, Lifetime) {
|
TEST(SigH, Lifetime) {
|
||||||
using signal = entt::SigH<void(void)>;
|
using signal = entt::SigH<void(void)>;
|
||||||
|
|
||||||
@@ -220,3 +228,24 @@ TEST(SigH, Collector) {
|
|||||||
ASSERT_EQ(static_cast<std::vector<int>::size_type>(1), collector_first.vec.size());
|
ASSERT_EQ(static_cast<std::vector<int>::size_type>(1), collector_first.vec.size());
|
||||||
ASSERT_EQ(42, collector_first.vec[0]);
|
ASSERT_EQ(42, collector_first.vec[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(SigH, ConstNonConstNoExcept) {
|
||||||
|
entt::SigH<void()> sigh;
|
||||||
|
ConstNonConstNoExcept functor;
|
||||||
|
|
||||||
|
sigh.sink().connect<ConstNonConstNoExcept, &ConstNonConstNoExcept::f>(&functor);
|
||||||
|
sigh.sink().connect<ConstNonConstNoExcept, &ConstNonConstNoExcept::g>(&functor);
|
||||||
|
sigh.sink().connect<ConstNonConstNoExcept, &ConstNonConstNoExcept::h>(&functor);
|
||||||
|
sigh.sink().connect<ConstNonConstNoExcept, &ConstNonConstNoExcept::i>(&functor);
|
||||||
|
sigh.publish();
|
||||||
|
|
||||||
|
ASSERT_EQ(functor.cnt, 4);
|
||||||
|
|
||||||
|
sigh.sink().disconnect<ConstNonConstNoExcept, &ConstNonConstNoExcept::f>(&functor);
|
||||||
|
sigh.sink().disconnect<ConstNonConstNoExcept, &ConstNonConstNoExcept::g>(&functor);
|
||||||
|
sigh.sink().disconnect<ConstNonConstNoExcept, &ConstNonConstNoExcept::h>(&functor);
|
||||||
|
sigh.sink().disconnect<ConstNonConstNoExcept, &ConstNonConstNoExcept::i>(&functor);
|
||||||
|
sigh.publish();
|
||||||
|
|
||||||
|
ASSERT_EQ(functor.cnt, 4);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user