Compare commits

..

17 Commits

Author SHA1 Message Date
Michele Caini
06426e4fd7 updated version 2018-09-02 22:48:24 +02:00
Michele Caini
c55a97c24d updated TODO 2018-09-01 16:26:58 +02:00
Michele Caini
0d61289bf3 fixed #135 2018-09-01 16:21:59 +02:00
Michele Caini
bf10cbc70b review: documentation 2018-09-01 14:57:06 +02:00
Michele Caini
2d945e426b fixed #133 2018-08-29 23:10:03 +02:00
Michele Caini
13250887fa review: dependency 2018-08-22 15:51:13 +02:00
Michele Caini
3507c22968 bug fixing (Snapshot::destroyed - #128) 2018-08-22 14:22:54 +02:00
Michele Caini
cc3f98ebcd fixed tests (#129) 2018-08-22 14:00:11 +02:00
Michele Caini
4116e2d6ac added some projects to the entt-in-action list 2018-08-19 14:23:31 +02:00
Michele Caini
48eab6b4a7 minor changes 2018-08-11 14:44:41 +02:00
Michele Caini
25866b5369 fixed typo 2018-08-11 01:09:44 +02:00
Michele Caini
c4dd06fa45 delegate/signal: support for const member functions 2018-08-11 00:54:43 +02:00
Michele Caini
4846d211e0 updated TODO list 2018-08-06 14:07:34 +02:00
Michele Caini
a586ad1237 updated build system 2018-08-05 15:17:48 +02:00
Michele Caini
b701c9c464 review 2018-08-05 15:10:56 +02:00
Michele Caini
d0f20ed2bf updated tests 2018-08-05 13:54:21 +02:00
Michele Caini
0f64a2f3b0 updated .travis.yml (see #110) 2018-08-03 08:17:03 +02:00
37 changed files with 3343 additions and 2732 deletions

View File

@@ -77,3 +77,4 @@ deploy:
script: scripts/update_packages.sh $TRAVIS_TAG
on:
tags: true
condition: “$TRAVIS_BRANCH” = “$TRAVIS_TAG”

View File

@@ -16,7 +16,7 @@ endif()
# Project configuration
#
project(EnTT VERSION 2.7.2)
project(EnTT VERSION 2.7.3)
include(GNUInstallDirs)
@@ -218,6 +218,4 @@ add_custom_target(
README.md
TODO
.travis.yml
docs/CONTRIBUTING.md
docs/extra.dox
)

2508
README.md

File diff suppressed because it is too large Load Diff

8
TODO
View File

@@ -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
* 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
* define basic reactive systems (track entities to which component is attached, track entities from which component is removed, and so on)
* define systems as composable mixins (initializazion, reactive, update, whatever) with flexible auto-detected arguments (registry, views, etc)
* create dedicated flat map based on types implementation (sort of "type map") for types to use within the registry and so on...
* 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
* add some lazy iterative sorters like "single bubble sort loop"
* can we do more for shared libraries? who knows... see #144
* work stealing job system (see #100)
* make view copyable/moveable
* reflection system (maybe)
* C++17. That's all.
* AOB
* lower case names (?)
* tag_t and the others, create constexpr var

View File

@@ -14,7 +14,7 @@ configuration:
before_build:
- 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:
parallel: true

View File

@@ -20,3 +20,17 @@ install(
DIRECTORY ${DOXY_OUTPUT_DIRECTORY}/html
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
View 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

File diff suppressed because it is too large Load Diff

75
docs/locator.md Normal file
View 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
View 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
View 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
View 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
View 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.

View File

@@ -52,7 +52,7 @@ public:
* @tparam N Number of characters of the identifier.
* @param str Human-readable identifer.
*/
template <std::size_t N>
template<std::size_t N>
constexpr HashedString(const char (&str)[N]) ENTT_NOEXCEPT
: hash{helper(offset, str)}, str{str}
{}

View File

@@ -12,15 +12,15 @@
namespace entt {
namespace internal {
/**
* @cond TURN_OFF_DOXYGEN
* Internal details not to be documented.
*/
namespace internal {
template<typename...>
struct IsPartOf;
@@ -31,15 +31,15 @@ template<typename Type>
struct IsPartOf<Type>: std::false_type {};
}
/**
* Internal details not to be documented.
* @endcond TURN_OFF_DOXYGEN
*/
}
/**
* @brief Types identifiers.
*

View File

@@ -6,6 +6,7 @@
#include <utility>
#include "../config/config.h"
#include "registry.hpp"
#include "entity.hpp"
namespace entt {
@@ -31,23 +32,54 @@ struct Actor {
* @param reg An entity-component system properly initialized.
*/
Actor(Registry<Entity> &reg)
: reg{reg}, entt{reg.create()}
: reg{&reg}, entt{reg.create()}
{}
/*! @brief Default destructor. */
virtual ~Actor() {
reg.destroy(entt);
reg->destroy(entt);
}
/*! @brief Default copy constructor. */
Actor(const Actor &) = default;
/*! @brief Default move constructor. */
Actor(Actor &&) = default;
/*! @brief Copying an actor isn't allowed. */
Actor(const Actor &) = delete;
/**
* @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. */
Actor & operator=(const Actor &) = default;
/*! @brief Default move assignment operator. @return This actor. */
Actor & operator=(Actor &&) = default;
Actor & operator=(const Actor &) = delete;
/**
* @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.
@@ -64,7 +96,7 @@ struct Actor {
*/
template<typename Tag, typename... Args>
Tag & assign(tag_t, Args &&... args) {
return (reg.template remove<Tag>(), reg.template assign<Tag>(tag_t{}, entt, std::forward<Args>(args)...));
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>
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>
void remove(tag_t) {
assert(has<Tag>(tag_t{}));
reg.template remove<Tag>();
reg->template remove<Tag>();
}
/**
@@ -102,7 +134,7 @@ struct Actor {
*/
template<typename Component>
void remove() {
reg.template remove<Component>(entt);
reg->template remove<Component>(entt);
}
/**
@@ -112,7 +144,7 @@ struct Actor {
*/
template<typename Tag>
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>
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>
const Tag & get(tag_t) const ENTT_NOEXCEPT {
assert(has<Tag>(tag_t{}));
return reg.template get<Tag>();
return reg->template get<Tag>();
}
/**
@@ -153,7 +185,7 @@ struct Actor {
*/
template<typename Component>
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.
*/
inline const registry_type & registry() const ENTT_NOEXCEPT {
return reg;
return *reg;
}
/**
@@ -191,7 +223,7 @@ struct Actor {
}
private:
registry_type &reg;
registry_type * reg;
Entity entt;
};

View 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

View File

@@ -9,17 +9,13 @@
namespace entt {
namespace internal {
/**
* @cond TURN_OFF_DOXYGEN
* Internal details not to be documented.
*/
template<typename Entity>
static constexpr auto null = ~typename entt_traits<Entity>::entity_type{};
namespace internal {
struct Null {
@@ -27,7 +23,8 @@ struct Null {
template<typename Entity>
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 {
@@ -40,12 +37,12 @@ struct Null {
template<typename Entity>
constexpr bool operator==(const Entity entity) const ENTT_NOEXCEPT {
return entity == null<Entity>;
return entity == static_cast<Entity>(*this);
}
template<typename Entity>
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.
* @endcond TURN_OFF_DOXYGEN
*/
}
/**
* @brief Null entity.
*

View File

@@ -36,9 +36,9 @@ struct entt_traits<std::uint16_t> {
using difference_type = std::int32_t;
/*! @brief Mask to use to get the entity number out of an identifier. */
static constexpr auto entity_mask = 0xFFF;
static constexpr std::uint16_t entity_mask = 0xFFF;
/*! @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. */
static constexpr auto entity_shift = 12;
};
@@ -62,9 +62,9 @@ struct entt_traits<std::uint32_t> {
using difference_type = std::int64_t;
/*! @brief Mask to use to get the entity number out of an identifier. */
static constexpr auto entity_mask = 0xFFFFF;
static constexpr std::uint32_t entity_mask = 0xFFFFF;
/*! @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. */
static constexpr auto entity_shift = 20;
};
@@ -88,9 +88,9 @@ struct entt_traits<std::uint64_t> {
using difference_type = std::int64_t;
/*! @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. */
static constexpr auto version_mask = 0xFFFFFFFF;
static constexpr std::uint64_t version_mask = 0xFFFFFFFF;
/*! @brief Extent of the entity number within an identifier. */
static constexpr auto entity_shift = 32;
};

View File

@@ -44,7 +44,7 @@ void dependency(Registry<Entity> &registry, const Entity entity) {
* assigned to an entity:
* @code{.cpp}
* entt::DefaultRegistry registry;
* entt::dependency<AType, AnotherType>(registry.construction<MyType>());
* entt::connect<AType, AnotherType>(registry.construction<MyType>());
* @endcode
*
* @tparam Dependency Types of components to assign to an entity if triggered.
@@ -52,7 +52,7 @@ void dependency(Registry<Entity> &registry, const Entity entity) {
* @param sink A sink object properly initialized.
*/
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...>>();
}
@@ -67,7 +67,7 @@ void dependency(Sink<void(Registry<Entity> &, const Entity)> sink) {
* components `AType` and `AnotherType`:
* @code{.cpp}
* entt::DefaultRegistry registry;
* entt::dependency<AType, AnotherType>(entt::break_t{}, registry.construction<MyType>());
* entt::disconnect<AType, AnotherType>(registry.construction<MyType>());
* @endcode
*
* @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.
*/
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...>>();
}

View File

@@ -9,6 +9,7 @@
#include <unordered_map>
#include "../config/config.h"
#include "registry.hpp"
#include "entity.hpp"
namespace entt {
@@ -95,7 +96,7 @@ public:
registry{other.registry},
entity{other.entity}
{
other.entity = ~entity_type{};
other.entity = entt::null;
}
/*! @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) {
if(!other.template has<Component>(dst)) {
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);
}
};

View File

@@ -16,6 +16,8 @@
#include "../core/algorithm.hpp"
#include "../core/family.hpp"
#include "../signal/sigh.hpp"
#include "attachee.hpp"
#include "entity.hpp"
#include "entt_traits.hpp"
#include "snapshot.hpp"
#include "sparse_set.hpp"
@@ -44,6 +46,78 @@ class Registry {
using signal_type = SigH<void(Registry &, const 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>
static void creating(Registry &registry, const Entity entity) {
if(registry.has<Component...>(entity)) {
@@ -57,28 +131,44 @@ class Registry {
return handler.has(entity) ? handler.destroy(entity) : void();
}
struct Attachee {
Attachee(const Entity entity) ENTT_NOEXCEPT: entity{entity} {}
virtual ~Attachee() = default;
Entity entity;
};
template<typename Tag>
inline bool managed(tag_t) const ENTT_NOEXCEPT {
const auto ttype = tag_family::type<Tag>();
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>
struct Attaching: Attachee {
// requirements for aggregates are relaxed only since C++17
template<typename... Args>
Attaching(const Entity entity, Args &&... args)
: Attachee{entity}, tag{std::forward<Args>(args)...}
{}
inline const Attaching<Tag> & pool(tag_t) const ENTT_NOEXCEPT {
assert(managed<Tag>(tag_t{}));
return static_cast<const Attaching<Tag> &>(*tags[tag_family::type<Tag>()]);
}
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>
void connect(std::index_sequence<Indexes...>) {
auto &cpool = pools[component_family::type<Comp>()];
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...>>...>>();
std::get<2>(cpool).sink().template connect<&Registry::destroying<Component...>>();
pool<Comp>().construction().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...>>();
}
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>
void disconnect(std::index_sequence<Indexes...>) {
auto &cpool = pools[component_family::type<Comp>()];
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...>>...>>();
std::get<2>(cpool).sink().template disconnect<&Registry::destroying<Component...>>();
pool<Comp>().construction().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...>>();
}
template<typename... Component, std::size_t... Indexes>
@@ -103,24 +192,6 @@ class Registry {
(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>
void assure() {
const auto ctype = component_family::type<Component>();
@@ -129,10 +200,8 @@ class Registry {
pools.resize(ctype + 1);
}
auto &cpool = std::get<0>(pools[ctype]);
if(!cpool) {
cpool = std::make_unique<SparseSet<Entity, Component>>();
if(!pools[ctype]) {
pools[ctype] = std::make_unique<Pool<Component>>(this);
}
}
@@ -143,6 +212,10 @@ class Registry {
if(!(ttype < tags.size())) {
tags.resize(ttype + 1);
}
if(!tags[ttype]) {
tags[ttype] = std::make_unique<Attaching<Tag>>(this);
}
}
public:
@@ -402,7 +475,7 @@ public:
* @return The version stored along with the given entity identifier.
*/
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 {
const auto pos = size_type(entity & traits_type::entity_mask);
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) {
const auto entt = next;
const auto version = entities[entt] & (~traits_type::entity_mask);
entity = entt | version;
const auto version = entities[entt] & (traits_type::version_mask << traits_type::entity_shift);
next = entities[entt] & traits_type::entity_mask;
entity = entt | version;
entities[entt] = entity;
--available;
} else {
@@ -510,22 +583,18 @@ public:
assert(valid(entity));
for(auto pos = pools.size(); pos; --pos) {
auto &tup = pools[pos-1];
auto &cpool = std::get<0>(tup);
auto &cpool = pools[pos-1];
if(cpool && cpool->has(entity)) {
std::get<2>(tup).publish(*this, entity);
cpool->destroy(entity);
}
};
for(auto pos = tags.size(); pos; --pos) {
auto &tup = tags[pos-1];
auto &tag = std::get<0>(tup);
auto &tag = tags[pos-1];
if(tag && tag->entity == entity) {
std::get<2>(tup).publish(*this, entity);
tag.reset();
if(tag && tag->get() == entity) {
tag->destroy();
}
};
@@ -534,7 +603,7 @@ public:
// lengthens the implicit list of destroyed entities
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;
entities[entt] = node;
next = entt;
@@ -589,10 +658,7 @@ public:
assert(valid(entity));
assert(!has<Tag>());
assure<Tag>(tag_t{});
auto &tup = tags[tag_family::type<Tag>()];
std::get<0>(tup).reset(new Attaching<Tag>{entity, std::forward<Args>(args)...});
std::get<1>(tup).publish(*this, entity);
return get<Tag>();
return pool<Tag>(tag_t{}).construct(entity, std::forward<Args>(args)...);
}
/**
@@ -619,9 +685,7 @@ public:
Component & assign(const entity_type entity, Args &&... args) {
assert(valid(entity));
assure<Component>();
pool<Component>().construct(entity, std::forward<Args>(args)...);
std::get<1>(pools[component_family::type<Component>()]).publish(*this, entity);
return pool<Component>().get(entity);
return pool<Component>().construct(entity, std::forward<Args>(args)...);
}
/**
@@ -630,12 +694,7 @@ public:
*/
template<typename Tag>
void remove() {
if(has<Tag>()) {
auto &tup = tags[tag_family::type<Tag>()];
auto &tag = std::get<0>(tup);
std::get<2>(tup).publish(*this, tag->entity);
tag.reset();
}
return has<Tag>() ? pool<Tag>(tag_t{}).destroy() : void();
}
/**
@@ -655,8 +714,6 @@ public:
void remove(const entity_type entity) {
assert(valid(entity));
assert(managed<Component>());
const auto ctype = component_family::type<Component>();
std::get<2>(pools[ctype]).publish(*this, entity);
pool<Component>().destroy(entity);
}
@@ -667,16 +724,7 @@ public:
*/
template<typename Tag>
bool has() const ENTT_NOEXCEPT {
const auto ttype = tag_family::type<Tag>();
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;
return managed<Tag>(tag_t{}) && tags[tag_family::type<Tag>()]->get() != null;
}
/**
@@ -734,7 +782,7 @@ public:
template<typename Tag>
const Tag & get() const ENTT_NOEXCEPT {
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 {
assert(valid(entity));
assert(has<Tag>());
auto &tag = std::get<0>(tags[tag_family::type<Tag>()]);
const auto owner = tag->entity;
tag->entity = entity;
return owner;
return pool<Tag>(tag_t{}).move(entity);
}
/**
* @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.
* @return A valid entity identifier.
* @return A valid entity identifier if an owner exists, the null entity
* identifier otherwise.
*/
template<typename Tag>
entity_type attachee() const ENTT_NOEXCEPT {
assert(has<Tag>());
return std::get<0>(tags[tag_family::type<Tag>()])->entity;
return managed<Tag>(tag_t{}) ? tags[tag_family::type<Tag>()]->get() : null;
}
/**
@@ -957,9 +995,9 @@ public:
assure<Component>();
auto &cpool = pool<Component>();
return (cpool.has(entity)
return cpool.has(entity)
? 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>
sink_type construction(tag_t) ENTT_NOEXCEPT {
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>
sink_type construction() ENTT_NOEXCEPT {
assure<Component>();
return std::get<1>(pools[component_family::type<Component>()]).sink();
return pool<Component>().construction();
}
/**
@@ -1046,7 +1084,7 @@ public:
template<typename Tag>
sink_type destruction(tag_t) ENTT_NOEXCEPT {
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>
sink_type destruction() ENTT_NOEXCEPT {
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) {
assert(valid(entity));
assure<Component>();
const auto ctype = component_family::type<Component>();
auto &cpool = *std::get<0>(pools[ctype]);
auto &cpool = pool<Component>();
if(cpool.has(entity)) {
std::get<2>(pools[ctype]).publish(*this, entity);
cpool.destroy(entity);
}
}
@@ -1200,12 +1236,9 @@ public:
template<typename Component>
void reset() {
assure<Component>();
const auto ctype = component_family::type<Component>();
auto &cpool = *std::get<0>(pools[ctype]);
auto &sig = std::get<2>(pools[ctype]);
auto &cpool = pool<Component>();
for(const auto entity: cpool) {
sig.publish(*this, entity);
for(const auto entity: static_cast<SparseSet<Entity> &>(cpool)) {
cpool.destroy(entity);
}
}
@@ -1278,13 +1311,13 @@ public:
bool orphan = true;
for(std::size_t i = 0; i < pools.size() && orphan; ++i) {
const auto &pool = std::get<0>(pools[i]);
orphan = !(pool && pool->has(entity));
const auto &cpool = pools[i];
orphan = !(cpool && cpool->has(entity));
}
for(std::size_t i = 0; i < tags.size() && orphan; ++i) {
const auto &tag = std::get<0>(tags[i]);
orphan = !(tag && (tag->entity == entity));
const auto &tag = tags[i];
orphan = !(tag && (tag->get() == entity));
}
return orphan;
@@ -1532,7 +1565,7 @@ public:
std::vector<const SparseSet<Entity> *> set(last - first);
std::transform(first, last, set.begin(), [this](const component_type ctype) {
return ctype < pools.size() ? std::get<0>(pools[ctype]).get() : nullptr;
return ctype < pools.size() ? pools[ctype].get() : nullptr;
});
return RuntimeView<Entity>{std::move(set)};
@@ -1550,13 +1583,13 @@ public:
*/
Snapshot<Entity> snapshot() const ENTT_NOEXCEPT {
using follow_fn_type = entity_type(const Registry &, const entity_type);
const entity_type seed = available ? (next | (entities[next] & ~traits_type::entity_mask)) : next;
const entity_type seed = available ? (next | (entities[next] & (traits_type::version_mask << traits_type::entity_shift))) : next;
follow_fn_type *follow = [](const Registry &registry, const entity_type entity) -> entity_type {
const auto &entities = registry.entities;
const auto entt = entity & traits_type::entity_mask;
const auto next = entities[entt] & traits_type::entity_mask;
return (next | (entities[next] & ~traits_type::entity_mask));
return (next | (entities[next] & (traits_type::version_mask << traits_type::entity_shift)));
};
return { *this, seed, follow };
@@ -1596,7 +1629,7 @@ public:
if(destroyed) {
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);
}
};
@@ -1606,8 +1639,8 @@ public:
private:
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::tuple<std::unique_ptr<Attachee>, signal_type, signal_type>> tags;
std::vector<std::unique_ptr<SparseSet<Entity>>> pools;
std::vector<std::unique_ptr<Attachee<Entity>>> tags;
std::vector<entity_type> entities;
size_type available{};
entity_type next{};

View File

@@ -119,11 +119,15 @@ public:
const Snapshot & destroyed(Archive &archive) const {
auto size = registry.size() - registry.alive();
archive(static_cast<Entity>(size));
auto curr = seed;
for(; size; --size) {
if(size) {
auto curr = seed;
archive(curr);
curr = follow(registry, curr);
for(--size; size; --size) {
curr = follow(registry, curr);
archive(curr);
}
}
return *this;

View File

@@ -122,7 +122,8 @@ class SparseSet<Entity> {
}
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 {
@@ -150,7 +151,8 @@ class SparseSet<Entity> {
}
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 {
@@ -422,7 +424,8 @@ public:
*/
size_type get(const entity_type entity) const ENTT_NOEXCEPT {
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) {
assert(has(entity));
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
reverse[back & traits_type::entity_mask] = candidate;
direct[candidate] = back;
direct[size_type(candidate)] = back;
candidate = null;
direct.pop_back();
}
@@ -637,7 +640,8 @@ class SparseSet<Entity, Type>: public SparseSet<Entity> {
}
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 {
@@ -665,7 +669,8 @@ class SparseSet<Entity, Type>: public SparseSet<Entity> {
}
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 {

View File

@@ -17,10 +17,6 @@ struct persistent_t final {};
struct raw_t final {};
/*! @brief Break type used to disambiguate overloads. */
struct break_t final {};
}

View File

@@ -4,6 +4,7 @@
#include "core/ident.hpp"
#include "core/monostate.hpp"
#include "entity/actor.hpp"
#include "entity/attachee.hpp"
#include "entity/entity.hpp"
#include "entity/entt_traits.hpp"
#include "entity/helper.hpp"

View File

@@ -42,6 +42,11 @@ class Delegate<Ret(Args...)> final {
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...)>
static Ret proto(void *instance, Args... args) {
return (static_cast<Class *>(instance)->*Member)(args...);
@@ -71,6 +76,22 @@ public:
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.
*

View File

@@ -11,23 +11,33 @@
namespace entt {
namespace internal {
/**
* @cond TURN_OFF_DOXYGEN
* 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>
struct Invoker;
template<typename Ret, typename... Args, typename Collector>
struct Invoker<Ret(Args...), Collector> {
using proto_fn_type = Ret(void *, Args...);
using call_type = std::pair<void *, proto_fn_type *>;
using proto_fn_type = typename sigh_traits<Ret(Args...)>::proto_fn_type;
virtual ~Invoker() = default;
@@ -39,8 +49,7 @@ struct Invoker<Ret(Args...), Collector> {
template<typename... Args, typename Collector>
struct Invoker<void(Args...), Collector> {
using proto_fn_type = void(void *, Args...);
using call_type = std::pair<void *, proto_fn_type *>;
using proto_fn_type = typename sigh_traits<void(Args...)>::proto_fn_type;
virtual ~Invoker() = default;
@@ -78,15 +87,15 @@ template<typename Function>
using DefaultCollectorType = typename DefaultCollector<Function>::collector_type;
}
/**
* Internal details not to be documented.
* @endcond TURN_OFF_DOXYGEN
*/
}
/**
* @brief Sink implementation.
*
@@ -132,20 +141,24 @@ class Sink<Ret(Args...)> final {
template<typename, typename>
friend class SigH;
using proto_fn_type = Ret(void *, Args...);
using call_type = std::pair<void *, proto_fn_type *>;
using call_type = typename internal::sigh_traits<Ret(Args...)>::call_type;
template<Ret(*Function)(Args...)>
static Ret proto(void *, Args... 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)>
static Ret proto(void *instance, Args... 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}
{}
@@ -161,7 +174,7 @@ public:
template<Ret(*Function)(Args...)>
void connect() {
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.
* @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) {
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...)>
void disconnect() {
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...)>
void disconnect(Class *instance) {
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>
void disconnect(Class *instance) {
auto func = [instance](const call_type &call) { return call.first == instance; };
calls.erase(std::remove_if(calls.begin(), calls.end(), std::move(func)), calls.end());
calls->erase(std::remove_if(calls->begin(), calls->end(), std::move(func)), calls->end());
}
/**
* @brief Disconnects all the listeners from a signal.
*/
void disconnect() {
calls.clear();
calls->clear();
}
private:
std::vector<call_type> &calls;
std::vector<call_type> *calls;
};
@@ -253,7 +297,7 @@ private:
*/
template<typename Ret, typename... Args, typename Collector>
class SigH<Ret(Args...), Collector> final: private internal::Invoker<Ret(Args...), Collector> {
using call_type = typename internal::Invoker<Ret(Args...), Collector>::call_type;
using call_type = typename internal::sigh_traits<Ret(Args...)>::call_type;
public:
/*! @brief Unsigned integer type. */
@@ -296,7 +340,7 @@ public:
* @return A temporary sink object.
*/
sink_type sink() ENTT_NOEXCEPT {
return { calls };
return { &calls };
}
/**

View File

@@ -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_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 $<$<CXX_COMPILER_ID:MSVC>:/EHsc>)
macro(SETUP_AND_ADD_TEST TEST_NAME 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_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 $<$<CXX_COMPILER_ID:MSVC>:/EHsc>)
add_test(NAME ${TEST_NAME} COMMAND ${TEST_NAME})
endmacro()
@@ -65,6 +67,7 @@ SETUP_AND_ADD_TEST(monostate entt/core/monostate.cpp)
# Test entity
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(helper entt/entity/helper.cpp)
SETUP_AND_ADD_TEST(prototype entt/entity/prototype.cpp)

View File

@@ -11,8 +11,8 @@ TEST(HashedString, Functionalities) {
auto barHs = entt::HashedString{bar};
ASSERT_NE(static_cast<hash_type>(fooHs), static_cast<hash_type>(barHs));
ASSERT_EQ(static_cast<const char *>(fooHs), "foo");
ASSERT_EQ(static_cast<const char *>(barHs), bar);
ASSERT_STREQ(static_cast<const char *>(fooHs), "foo");
ASSERT_STREQ(static_cast<const char *>(barHs), bar);
ASSERT_EQ(fooHs, fooHs);
ASSERT_NE(fooHs, barHs);

View 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;
}

View File

@@ -6,7 +6,7 @@
TEST(Helper, Dependency) {
entt::DefaultRegistry registry;
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<float>(entity));
@@ -42,7 +42,7 @@ TEST(Helper, Dependency) {
registry.remove<int>(entity);
registry.remove<double>(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);
ASSERT_FALSE(registry.has<double>(entity));

View File

@@ -376,8 +376,10 @@ TEST(DefaultRegistry, CreateDestroyEntities) {
TEST(DefaultRegistry, AttachSetRemoveTags) {
entt::DefaultRegistry registry;
const auto &cregistry = registry;
const typename decltype(registry)::entity_type null = entt::null;
ASSERT_FALSE(registry.has<int>());
ASSERT_EQ(registry.attachee<int>(), null);
const auto entity = registry.create();
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>(entt::tag_t{}, entity));
ASSERT_FALSE(registry.has<int>(entt::tag_t{}, other));
ASSERT_EQ(registry.attachee<int>(), null);
registry.assign<int>(entt::tag_t{}, entity, 42);
registry.destroy(entity);
@@ -811,3 +814,14 @@ TEST(DefaultRegistry, DestroyByTagAndComponents) {
registry.destroy<double>(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()));
}

View File

@@ -400,8 +400,8 @@ TEST(SparseSetWithType, Functionalities) {
ASSERT_FALSE(set.has(0));
ASSERT_FALSE(set.has(42));
(void)entt::SparseSet<std::uint64_t>{std::move(set)};
entt::SparseSet<std::uint64_t> other;
(void)entt::SparseSet<std::uint64_t, int>{std::move(set)};
entt::SparseSet<std::uint64_t, int> other;
other = std::move(set);
}

View File

@@ -5,7 +5,7 @@
#include <entt/entity/view.hpp>
TEST(PersistentView, Prepare) {
entt::Registry<std::uint64_t> registry;
entt::DefaultRegistry registry;
registry.prepare<int, char>();
auto view = registry.view<int, char>(entt::persistent_t{});
const auto &cview = view;
@@ -57,7 +57,7 @@ TEST(PersistentView, Prepare) {
}
TEST(PersistentView, NoPrepare) {
entt::Registry<std::uint64_t> registry;
entt::DefaultRegistry registry;
auto view = registry.view<int, char>(entt::persistent_t{});
ASSERT_TRUE(view.empty());
@@ -106,7 +106,7 @@ TEST(PersistentView, NoPrepare) {
}
TEST(PersistentView, ElementAccess) {
entt::Registry<std::uint64_t> registry;
entt::DefaultRegistry registry;
auto view = registry.view<int, char>(entt::persistent_t{});
const auto &cview = view;
@@ -125,7 +125,7 @@ TEST(PersistentView, ElementAccess) {
}
TEST(PersistentView, Contains) {
entt::Registry<std::uint64_t> registry;
entt::DefaultRegistry registry;
const auto e0 = registry.create();
registry.assign<int>(e0);
@@ -144,7 +144,7 @@ TEST(PersistentView, Contains) {
}
TEST(PersistentView, Empty) {
entt::Registry<std::uint64_t> registry;
entt::DefaultRegistry registry;
const auto e0 = registry.create();
registry.assign<double>(e0);
@@ -167,7 +167,7 @@ TEST(PersistentView, Empty) {
}
TEST(PersistentView, Each) {
entt::Registry<std::uint64_t> registry;
entt::DefaultRegistry registry;
registry.prepare<int, char>();
const auto e0 = registry.create();
@@ -192,7 +192,7 @@ TEST(PersistentView, Each) {
}
TEST(PersistentView, Sort) {
entt::Registry<std::uint64_t> registry;
entt::DefaultRegistry registry;
registry.prepare<int, unsigned int>();
const auto e0 = registry.create();
@@ -227,7 +227,7 @@ TEST(PersistentView, Sort) {
}
TEST(SingleComponentView, Functionalities) {
entt::Registry<std::uint64_t> registry;
entt::DefaultRegistry registry;
auto view = registry.view<char>();
const auto &cview = view;
@@ -274,7 +274,7 @@ TEST(SingleComponentView, Functionalities) {
}
TEST(SingleComponentView, ElementAccess) {
entt::Registry<std::uint64_t> registry;
entt::DefaultRegistry registry;
auto view = registry.view<int>();
const auto &cview = view;
@@ -291,7 +291,7 @@ TEST(SingleComponentView, ElementAccess) {
}
TEST(SingleComponentView, Contains) {
entt::Registry<std::uint64_t> registry;
entt::DefaultRegistry registry;
const auto e0 = registry.create();
registry.assign<int>(e0);
@@ -308,7 +308,7 @@ TEST(SingleComponentView, Contains) {
}
TEST(SingleComponentView, Empty) {
entt::Registry<std::uint64_t> registry;
entt::DefaultRegistry registry;
const auto e0 = registry.create();
registry.assign<char>(e0);
@@ -319,7 +319,7 @@ TEST(SingleComponentView, Empty) {
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) {
(void)entity;
@@ -328,7 +328,7 @@ TEST(SingleComponentView, Empty) {
}
TEST(SingleComponentView, Each) {
entt::Registry<std::uint64_t> registry;
entt::DefaultRegistry registry;
registry.assign<int>(registry.create());
registry.assign<int>(registry.create());
@@ -347,7 +347,7 @@ TEST(SingleComponentView, Each) {
}
TEST(MultipleComponentView, Functionalities) {
entt::Registry<std::uint64_t> registry;
entt::DefaultRegistry registry;
auto view = registry.view<int, char>();
const auto &cview = view;
@@ -388,7 +388,7 @@ TEST(MultipleComponentView, Functionalities) {
}
TEST(MultipleComponentView, Iterator) {
entt::Registry<std::uint64_t> registry;
entt::DefaultRegistry registry;
const auto entity = registry.create();
registry.assign<int>(entity);
registry.assign<char>(entity);
@@ -410,7 +410,7 @@ TEST(MultipleComponentView, Iterator) {
}
TEST(MultipleComponentView, ConstIterator) {
entt::Registry<std::uint64_t> registry;
entt::DefaultRegistry registry;
const auto entity = registry.create();
registry.assign<int>(entity);
registry.assign<char>(entity);
@@ -432,7 +432,7 @@ TEST(MultipleComponentView, ConstIterator) {
}
TEST(MultipleComponentView, Contains) {
entt::Registry<std::uint64_t> registry;
entt::DefaultRegistry registry;
const auto e0 = registry.create();
registry.assign<int>(e0);
@@ -451,7 +451,7 @@ TEST(MultipleComponentView, Contains) {
}
TEST(MultipleComponentView, Empty) {
entt::Registry<std::uint64_t> registry;
entt::DefaultRegistry registry;
const auto e0 = registry.create();
registry.assign<double>(e0);
@@ -471,7 +471,7 @@ TEST(MultipleComponentView, Empty) {
}
TEST(MultipleComponentView, Each) {
entt::Registry<std::uint64_t> registry;
entt::DefaultRegistry registry;
const auto e0 = registry.create();
registry.assign<int>(e0);
@@ -495,7 +495,7 @@ TEST(MultipleComponentView, Each) {
}
TEST(MultipleComponentView, EachWithHoles) {
entt::Registry<std::uint64_t> registry;
entt::DefaultRegistry registry;
const auto e0 = registry.create();
const auto e1 = registry.create();
@@ -520,7 +520,7 @@ TEST(MultipleComponentView, EachWithHoles) {
}
TEST(RawView, Functionalities) {
entt::Registry<std::uint64_t> registry;
entt::DefaultRegistry registry;
auto view = registry.view<char>(entt::raw_t{});
const auto &cview = view;
@@ -575,7 +575,7 @@ TEST(RawView, Functionalities) {
}
TEST(RawView, ElementAccess) {
entt::Registry<std::uint64_t> registry;
entt::DefaultRegistry registry;
auto view = registry.view<int>(entt::raw_t{});
const auto &cview = view;
@@ -592,7 +592,7 @@ TEST(RawView, ElementAccess) {
}
TEST(RawView, Empty) {
entt::Registry<std::uint64_t> registry;
entt::DefaultRegistry registry;
const auto e0 = registry.create();
registry.assign<char>(e0);
@@ -603,7 +603,7 @@ TEST(RawView, Empty) {
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) {
(void)component;
@@ -612,7 +612,7 @@ TEST(RawView, Empty) {
}
TEST(RawView, Each) {
entt::Registry<std::uint64_t> registry;
entt::DefaultRegistry registry;
registry.assign<int>(registry.create(), 1);
registry.assign<int>(registry.create(), 3);
@@ -631,7 +631,7 @@ TEST(RawView, Each) {
}
TEST(RuntimeView, Functionalities) {
entt::Registry<std::uint64_t> registry;
entt::DefaultRegistry registry;
using component_type = typename decltype(registry)::component_type;
// forces the creation of the pools
@@ -677,7 +677,7 @@ TEST(RuntimeView, Functionalities) {
}
TEST(RuntimeView, Iterator) {
entt::Registry<std::uint64_t> registry;
entt::DefaultRegistry registry;
using component_type = typename decltype(registry)::component_type;
const auto entity = registry.create();
@@ -702,7 +702,7 @@ TEST(RuntimeView, Iterator) {
}
TEST(RuntimeView, ConstIterator) {
entt::Registry<std::uint64_t> registry;
entt::DefaultRegistry registry;
using component_type = typename decltype(registry)::component_type;
const auto entity = registry.create();
@@ -727,7 +727,7 @@ TEST(RuntimeView, ConstIterator) {
}
TEST(RuntimeView, Contains) {
entt::Registry<std::uint64_t> registry;
entt::DefaultRegistry registry;
using component_type = typename decltype(registry)::component_type;
const auto e0 = registry.create();
@@ -748,7 +748,7 @@ TEST(RuntimeView, Contains) {
}
TEST(RuntimeView, Empty) {
entt::Registry<std::uint64_t> registry;
entt::DefaultRegistry registry;
using component_type = typename decltype(registry)::component_type;
const auto e0 = registry.create();
@@ -770,7 +770,7 @@ TEST(RuntimeView, Empty) {
}
TEST(RuntimeView, Each) {
entt::Registry<std::uint64_t> registry;
entt::DefaultRegistry registry;
using component_type = typename decltype(registry)::component_type;
const auto e0 = registry.create();
@@ -791,7 +791,7 @@ TEST(RuntimeView, Each) {
}
TEST(RuntimeView, EachWithHoles) {
entt::Registry<std::uint64_t> registry;
entt::DefaultRegistry registry;
using component_type = typename decltype(registry)::component_type;
const auto e0 = registry.create();
@@ -813,7 +813,7 @@ TEST(RuntimeView, EachWithHoles) {
}
TEST(RuntimeView, MissingPool) {
entt::Registry<std::uint64_t> registry;
entt::DefaultRegistry registry;
using component_type = typename decltype(registry)::component_type;
const auto e0 = registry.create();
@@ -840,7 +840,7 @@ TEST(RuntimeView, MissingPool) {
}
TEST(RuntimeView, EmptyRange) {
entt::Registry<std::uint64_t> registry;
entt::DefaultRegistry registry;
using component_type = typename decltype(registry)::component_type;
const auto e0 = registry.create();

View File

@@ -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) {
entt::Delegate<int(int)> ffdel;
entt::Delegate<int(int)> mfdel;
@@ -46,3 +54,22 @@ TEST(Delegate, Comparison) {
ASSERT_TRUE(def == entt::Delegate<int(int)>{});
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);
}

View File

@@ -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) {
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(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);
}