removed signal + added dependency function(s)
This commit is contained in:
174
README.md
174
README.md
@@ -25,6 +25,8 @@
|
||||
* [Continuous loader](#continuous-loader)
|
||||
* [Archives](#archives)
|
||||
* [One example to rule them all](#one-example-to-rule-them-all)
|
||||
* [Helpers](#helpers)
|
||||
* [Dependency function](#dependency-function)
|
||||
* [View: to persist or not to persist?](#view-to-persist-or-not-to-persist)
|
||||
* [Standard View](#standard-view)
|
||||
* [Single component standard view](#single-component-standard-view)
|
||||
@@ -103,7 +105,8 @@ Here is a brief list of what it offers today:
|
||||
* The smallest and most basic implementation of a service locator ever seen.
|
||||
* A cooperative scheduler for processes of any type.
|
||||
* All what is needed for resource management (cache, loaders, handles).
|
||||
* Signal handlers of any type, delegates and a tiny event dispatcher.
|
||||
* Delegates, signal handlers (with built-in support for collectors) and a tiny
|
||||
event dispatcher.
|
||||
* A general purpose event emitter, that is a CRTP idiom based class template.
|
||||
* An event dispatcher for immediate and delayed events to integrate in loops.
|
||||
* ...
|
||||
@@ -961,6 +964,33 @@ the best way to do it. However, feel free to use it at your own risk.
|
||||
The basic idea is to store everything in a group of queues in memory, then bring
|
||||
everything back to the registry with different loaders.
|
||||
|
||||
### Helpers
|
||||
|
||||
The so called _helpers_ are small classes and functions mainly designed to offer
|
||||
built-in support for the most basic functionalities.<br/>
|
||||
The list of helpers will grow longer as time passes and new ideas come out.
|
||||
|
||||
#### Dependency function
|
||||
|
||||
A _dependency function_ is a predefined listener, actually a function template
|
||||
to use to automatically assign components to an entity when a type has a
|
||||
dependency on some other types.<br/>
|
||||
The following adds components `AType` and `AnotherType` whenever `MyType` is
|
||||
assigned to an entity:
|
||||
|
||||
```cpp
|
||||
entt::dependency<AType, AnotherType>(registry.construction<MyType>());
|
||||
```
|
||||
|
||||
A component is assigned to an entity and thus default initialized only in case
|
||||
the entity itself hasn't it yet. It means that already existent components won't
|
||||
be overriden.<br/>
|
||||
A dependency can easily be broken by means of the same function template:
|
||||
|
||||
```cpp
|
||||
entt::dependency<AType, AnotherType>(entt::break_op_t{}, registry.construction<MyType>());
|
||||
```
|
||||
|
||||
## View: to persist or not to persist?
|
||||
|
||||
First of all, it is worth answering an obvious question: why views?<br/>
|
||||
@@ -1984,107 +2014,26 @@ offers a full set of classes to solve completely different problems.
|
||||
|
||||
## Signals
|
||||
|
||||
There are two types of signal handlers in `EnTT`, internally called _managed_
|
||||
and _unmanaged_.<br/>
|
||||
They differ in the way they work around the tradeoff between performance, memory
|
||||
usage and safety. Managed listeners must be wrapped in an `std::shared_ptr` and
|
||||
the signal will take care of disconnecting them whenever they die. Unmanaged
|
||||
listeners can be any kind of objects and the client is in charge of connecting
|
||||
and disconnecting them from a signal to avoid crashes due to different
|
||||
lifetimes.<br/>
|
||||
Both solutions follow the same pattern to allow users to use a signal as a
|
||||
private data member and therefore not expose any publish functionality to the
|
||||
clients of their classes. 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.
|
||||
Signal handlers work with naked pointers, function pointers and pointers to
|
||||
member functions. Listeners can be any kind of objects and the user is 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.
|
||||
|
||||
### Managed signal handler
|
||||
|
||||
A managed signal handler works with weak pointers to classes and pointers to
|
||||
member functions as well as pointers to free functions. References are
|
||||
automatically removed when the instances to which they point are freed.<br/>
|
||||
In other terms, users can simply connect a listener and forget about it, thus
|
||||
getting rid of the burden of controlling its lifetime. The drawback is that
|
||||
listeners must be allocated on the dynamic storage and wrapped into an
|
||||
`std::shared_ptr`. Performance and memory management can suffer from this in
|
||||
real world softwares.
|
||||
|
||||
To create an instance of this type of handler, the function type is all what is
|
||||
needed:
|
||||
|
||||
```cpp
|
||||
entt::Signal<void(int, char)> signal;
|
||||
```
|
||||
|
||||
From now on, free functions and member functions that respect the given
|
||||
signature can be easily connected to and disconnected from the signal by means
|
||||
of a sink:
|
||||
|
||||
```cpp
|
||||
void foo(int, char) { /* ... */ }
|
||||
|
||||
struct S {
|
||||
void bar(int, char) { /* ... */ }
|
||||
};
|
||||
|
||||
// ...
|
||||
|
||||
auto instance = std::make_shared<S>();
|
||||
|
||||
signal.sink().connect<&foo>();
|
||||
signal.sink().connect<S, &S::bar>(instance);
|
||||
|
||||
// ...
|
||||
|
||||
// disconnects a free function
|
||||
signal.sink().disconnect<&foo>();
|
||||
|
||||
// disconnects 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');
|
||||
```
|
||||
|
||||
This is more or less all what a managed signal handler has to offer.<br/>
|
||||
A bunch of other member functions are exposed actually. As an example, there is
|
||||
a method to use to know how many listeners a managed signal handler contains
|
||||
(`size`) or if it contains at least a listener (`empty`) and even to swap two
|
||||
handlers (`swap`).<br/>
|
||||
Refer to the [official documentation](https://skypjack.github.io/entt/) for all
|
||||
the details.
|
||||
|
||||
### Unmanaged signal handler
|
||||
|
||||
An unmanaged signal handler works with naked pointers to classes and pointers to
|
||||
member functions as well as pointers to free functions. Removing references when
|
||||
the instances to which they point are freed is in charge to the users.<br/>
|
||||
In other terms, users must explicitly disconnect a listener before to delete the
|
||||
class to which it belongs, thus taking care of the lifetime of each instance. On
|
||||
the other side, performance shouldn't be affected that much by the presence of
|
||||
such a signal handler.
|
||||
|
||||
The API of an unmanaged signal handler is similar to the one of a managed signal
|
||||
handler.<br/>
|
||||
The most important difference is that it comes in two forms: with and without a
|
||||
collector. In case it is associated with a collector, all the values returned by
|
||||
the listeners can be literally _collected_ and used later by the caller.<br/>
|
||||
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 this type of handler there exist mainly two ways:
|
||||
To create instances of signal handlers there exist mainly two ways:
|
||||
|
||||
```cpp
|
||||
// no collector type
|
||||
@@ -2094,9 +2043,9 @@ entt::SigH<void(int, char)> signal;
|
||||
entt::SigH<void(int, char), MyCollector<bool>> collector;
|
||||
```
|
||||
|
||||
As expected, an unmanaged signal handler offers all the basic functionalities
|
||||
required to know how many listeners it contains (`size`) or if it contains at
|
||||
least a listener (`empty`) and even to swap two handlers (`swap`).
|
||||
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::
|
||||
@@ -2231,27 +2180,14 @@ within the framework.
|
||||
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/>
|
||||
Internally it uses either managed or unmanaged signal handlers, that is why
|
||||
there exist both a managed and an unmanaged event dispatcher.
|
||||
|
||||
This class shares part of its API with the one of the signals, but it doesn't
|
||||
require that all the types of events are specified when declared:
|
||||
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 managed dispatcher that works with std::shared_ptr/std::weak_ptr
|
||||
entt::Dispatcher<entt::Signal> managed{};
|
||||
|
||||
// define an unmanaged dispatcher that works with naked pointers
|
||||
entt::Dispatcher<entt::SigH> unmanaged{};
|
||||
// define a general purpose dispatcher that works with naked pointers
|
||||
entt::Dispatcher dispatcher{};
|
||||
```
|
||||
|
||||
Actually there exist two aliases for the classes shown in the previous example:
|
||||
`entt::ManagedDispatcher` and `entt::UnmanagedDispatcher`.
|
||||
|
||||
For the sake of brevity, below is described the interface of the sole unmanaged
|
||||
dispatcher. The interface of the managed dispatcher is almost the same but for
|
||||
the fact that it accepts smart pointers instead of naked pointers.
|
||||
|
||||
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/>
|
||||
@@ -2315,6 +2251,10 @@ 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();
|
||||
```
|
||||
|
||||
|
||||
5
TODO
5
TODO
@@ -4,6 +4,9 @@
|
||||
* debugging tools (#60): the issue online already contains interesting tips on this, look at it
|
||||
* define a macro for the noexcept policy, so as to provide users with an easy way to disable exception handling
|
||||
* define basic reactive systems (track entities to which component is attached, track entities from which component is removed, and so on)
|
||||
* blueprint registry - kind of factory to create entitites template for initialization (use signals, it's trivial this way)
|
||||
* ease the assignment of tags as string (use a template class with a non-type template parameter behind the scene)
|
||||
* turn anonymous namespaces in details:: so as to avoid issues related to internal vs external linkage
|
||||
* dictionary based dependency class (templates copied over) + prefabs (shared state/copy-on-write)
|
||||
* remove Actor::update (it's application dependent), allow tag instead
|
||||
* "singleton mode" for tags (see #66)
|
||||
* AOB
|
||||
|
||||
86
src/entt/entity/helper.hpp
Normal file
86
src/entt/entity/helper.hpp
Normal file
@@ -0,0 +1,86 @@
|
||||
#ifndef ENTT_ENTITY_HELPER_HPP
|
||||
#define ENTT_ENTITY_HELPER_HPP
|
||||
|
||||
|
||||
#include "../signal/sigh.hpp"
|
||||
#include "registry.hpp"
|
||||
#include "utility"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/**
|
||||
* @brief Dependency function prototype.
|
||||
*
|
||||
* A _dependency function_ is a built-in listener to use to automatically assign
|
||||
* components to an entity when a type has a dependency on some other types.
|
||||
*
|
||||
* This is a prototype function to use to create dependencies.<br/>
|
||||
* It isn't intended for direct use, even if nothing forbids using it freely.
|
||||
*
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
* @tparam Component Types of components to assign to an entity if triggered.
|
||||
* @param registry A valid reference to a registry.
|
||||
* @param entity A valid entity identifier.
|
||||
*/
|
||||
template<typename Entity, typename... Component>
|
||||
void dependency(Registry<Entity> ®istry, Entity entity) {
|
||||
using accumulator_type = int[];
|
||||
accumulator_type accumulator = { ((registry.template has<Component>(entity) ? void() : (registry.template assign<Component>(entity), void())), 0)... };
|
||||
(void)accumulator;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Connects a dependency function to the given sink.
|
||||
*
|
||||
* A _dependency function_ is a built-in listener to use to automatically assign
|
||||
* components to an entity when a type has a dependency on some other types.
|
||||
*
|
||||
* The following adds components `AType` and `AnotherType` whenever `MyType` is
|
||||
* assigned to an entity:
|
||||
* @code{.cpp}
|
||||
* entt::DefaultRegistry registry;
|
||||
* entt::dependency<AType, AnotherType>(registry.construction<MyType>());
|
||||
* @endcode
|
||||
*
|
||||
* @tparam Dependency Types of components to assign to an entity if triggered.
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
* @param sink A sink object properly initialized.
|
||||
*/
|
||||
template<typename... Dependency, typename Entity>
|
||||
void dependency(Sink<void(Registry<Entity> &, Entity)> sink) {
|
||||
using func_type = void(*)(Registry<Entity> &, Entity);
|
||||
sink.template connect<static_cast<func_type>(&dependency<Entity, Dependency...>)>();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Disconnects a dependency function from the given sink.
|
||||
*
|
||||
* A _dependency function_ is a built-in listener to use to automatically assign
|
||||
* components to an entity when a type has a dependency on some other types.
|
||||
*
|
||||
* The following breaks the dependency between the component `MyType` and the
|
||||
* components `AType` and `AnotherType`:
|
||||
* @code{.cpp}
|
||||
* entt::DefaultRegistry registry;
|
||||
* entt::dependency<AType, AnotherType>(entt::break_op_t{}, registry.construction<MyType>());
|
||||
* @endcode
|
||||
*
|
||||
* @tparam Dependency Types of components used to create the dependency.
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
* @param sink A sink object properly initialized.
|
||||
*/
|
||||
template<typename... Dependency, typename Entity>
|
||||
void dependency(break_op_t, Sink<void(Registry<Entity> &, Entity)> sink) {
|
||||
using func_type = void(*)(Registry<Entity> &, Entity);
|
||||
sink.template disconnect<static_cast<func_type>(&dependency<Entity, Dependency...>)>();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_ENTITY_HELPER_HPP
|
||||
@@ -75,7 +75,7 @@ class Registry {
|
||||
|
||||
template<typename Component>
|
||||
struct Pool: SparseSet<Entity, Component> {
|
||||
using sink_type = typename SigH<void(Registry &, Entity)>::Sink;
|
||||
using sink_type = typename SigH<void(Registry &, Entity)>::sink_type;
|
||||
|
||||
Pool(Registry ®istry)
|
||||
: registry{registry}
|
||||
|
||||
@@ -9,11 +9,20 @@ namespace entt {
|
||||
* @brief Tag class type.
|
||||
*
|
||||
* An empty class type used to disambiguate the overloads of some member
|
||||
* functions of the registry.
|
||||
* functions.
|
||||
*/
|
||||
struct tag_type_t final {};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Break type.
|
||||
*
|
||||
* An empty class type used to disambiguate the overloads of some member
|
||||
* functions.
|
||||
*/
|
||||
struct break_op_t final {};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "core/ident.hpp"
|
||||
#include "entity/actor.hpp"
|
||||
#include "entity/entt_traits.hpp"
|
||||
#include "entity/helper.hpp"
|
||||
#include "entity/registry.hpp"
|
||||
#include "entity/snapshot.hpp"
|
||||
#include "entity/sparse_set.hpp"
|
||||
@@ -18,4 +19,3 @@
|
||||
#include "signal/dispatcher.hpp"
|
||||
#include "signal/emitter.hpp"
|
||||
#include "signal/sigh.hpp"
|
||||
#include "signal/signal.hpp"
|
||||
|
||||
@@ -6,8 +6,9 @@
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <cstdint>
|
||||
#include <algorithm>
|
||||
#include <type_traits>
|
||||
#include "../core/family.hpp"
|
||||
#include "signal.hpp"
|
||||
#include "sigh.hpp"
|
||||
|
||||
|
||||
@@ -20,34 +21,40 @@ namespace entt {
|
||||
* A dispatcher can be used either to trigger an immediate event or to enqueue
|
||||
* events to be published all together once per tick.<br/>
|
||||
* Listeners are provided in the form of member functions. For each event of
|
||||
* type `Event`, listeners must have the following signature:
|
||||
* `void(const Event &)`. Member functions named `receive` are automatically
|
||||
* detected and registered or unregistered by the dispatcher.
|
||||
* type `Event`, listeners must have the following function type:
|
||||
* @code{.cpp}
|
||||
* void(const Event &)
|
||||
* @endcode
|
||||
*
|
||||
* @tparam Sig Type of the signal handler to use.
|
||||
* Member functions named `receive` are automatically detected and registered or
|
||||
* unregistered by the dispatcher. The type of the instances is `Class *` (a
|
||||
* naked pointer). It means that users must guarantee that the lifetimes of the
|
||||
* instances overcome the one of the dispatcher itself to avoid crashes.
|
||||
*/
|
||||
template<template<typename...> class Sig>
|
||||
class Dispatcher final {
|
||||
using event_family = Family<struct InternalDispatcherEventFamily>;
|
||||
|
||||
template<typename Class, typename Event>
|
||||
using instance_type = typename Sig<void(const Event &)>::template instance_type<Class>;
|
||||
using instance_type = typename SigH<void(const Event &)>::template instance_type<Class>;
|
||||
|
||||
struct BaseSignalWrapper {
|
||||
virtual ~BaseSignalWrapper() = default;
|
||||
virtual void publish(std::size_t) = 0;
|
||||
virtual void publish() = 0;
|
||||
};
|
||||
|
||||
template<typename Event>
|
||||
struct SignalWrapper final: BaseSignalWrapper {
|
||||
using sink_type = typename Sig<void(const Event &)>::Sink;
|
||||
using sink_type = typename SigH<void(const Event &)>::sink_type;
|
||||
|
||||
void publish(std::size_t current) override {
|
||||
void publish() override {
|
||||
for(const auto &event: events[current]) {
|
||||
signal.publish(event);
|
||||
}
|
||||
|
||||
events[current].clear();
|
||||
|
||||
++current;
|
||||
current %= std::extent<decltype(events)>::value;
|
||||
}
|
||||
|
||||
inline sink_type sink() noexcept {
|
||||
@@ -60,19 +67,16 @@ class Dispatcher final {
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
inline void enqueue(std::size_t current, Args &&... args) {
|
||||
inline void enqueue(Args &&... args) {
|
||||
events[current].push_back({ std::forward<Args>(args)... });
|
||||
}
|
||||
|
||||
private:
|
||||
Sig<void(const Event &)> signal{};
|
||||
SigH<void(const Event &)> signal{};
|
||||
std::vector<Event> events[2];
|
||||
int current{};
|
||||
};
|
||||
|
||||
inline static std::size_t buffer(bool mode) {
|
||||
return mode ? 0 : 1;
|
||||
}
|
||||
|
||||
template<typename Event>
|
||||
SignalWrapper<Event> & wrapper() {
|
||||
const auto type = event_family::type<Event>();
|
||||
@@ -93,11 +97,6 @@ public:
|
||||
template<typename Event>
|
||||
using sink_type = typename SignalWrapper<Event>::sink_type;
|
||||
|
||||
/*! @brief Default constructor. */
|
||||
Dispatcher() noexcept
|
||||
: wrappers{}, mode{false}
|
||||
{}
|
||||
|
||||
/**
|
||||
* @brief Returns a sink object for the given event.
|
||||
*
|
||||
@@ -110,14 +109,13 @@ public:
|
||||
*
|
||||
* The order of invocation of the listeners isn't guaranteed.
|
||||
*
|
||||
* @sa Signal::Sink
|
||||
* @sa SigH::Sink
|
||||
*
|
||||
* @tparam Event Type of event of which to get the sink.
|
||||
* @return A temporary sink object.
|
||||
*/
|
||||
template<typename Event>
|
||||
sink_type<Event> sink() noexcept {
|
||||
inline sink_type<Event> sink() noexcept {
|
||||
return wrapper<Event>().sink();
|
||||
}
|
||||
|
||||
@@ -132,7 +130,7 @@ public:
|
||||
* @param args Arguments to use to construct the event.
|
||||
*/
|
||||
template<typename Event, typename... Args>
|
||||
void trigger(Args &&... args) {
|
||||
inline void trigger(Args &&... args) {
|
||||
wrapper<Event>().trigger(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
@@ -147,56 +145,42 @@ public:
|
||||
* @param args Arguments to use to construct the event.
|
||||
*/
|
||||
template<typename Event, typename... Args>
|
||||
void enqueue(Args &&... args) {
|
||||
wrapper<Event>().enqueue(buffer(mode), std::forward<Args>(args)...);
|
||||
inline void enqueue(Args &&... args) {
|
||||
wrapper<Event>().enqueue(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Delivers all the pending events of the given type.
|
||||
*
|
||||
* This method is blocking and it doesn't return until all the events are
|
||||
* delivered to the registered listeners. It's responsibility of the users
|
||||
* to reduce at a minimum the time spent in the bodies of the listeners.
|
||||
*
|
||||
* @tparam Event Type of events to send.
|
||||
*/
|
||||
template<typename Event>
|
||||
inline void update() {
|
||||
wrapper<Event>().publish();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Delivers all the pending events.
|
||||
*
|
||||
* This method is blocking and it doesn't return until all the events are
|
||||
* delivered to the registered listeners. It's responsability of the users
|
||||
* delivered to the registered listeners. It's responsibility of the users
|
||||
* to reduce at a minimum the time spent in the bodies of the listeners.
|
||||
*/
|
||||
void update() {
|
||||
const auto buf = buffer(mode);
|
||||
mode = !mode;
|
||||
|
||||
for(auto &&wrapper: wrappers) {
|
||||
if(wrapper) {
|
||||
wrapper->publish(buf);
|
||||
}
|
||||
}
|
||||
inline void update() {
|
||||
std::for_each(wrappers.begin(), wrappers.end(), [](auto &&wrapper) {
|
||||
return wrapper ? wrapper->publish() : void();
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<std::unique_ptr<BaseSignalWrapper>> wrappers;
|
||||
bool mode;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Managed dispatcher.
|
||||
*
|
||||
* A managed dispatcher uses the Signal class template as an underlying type.
|
||||
* The type of the instances is the one required by the signal handler:
|
||||
* `std::shared_ptr<Class>` (a shared pointer).
|
||||
*/
|
||||
using ManagedDispatcher = Dispatcher<Signal>;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Unmanaged dispatcher.
|
||||
*
|
||||
* An unmanaged dispatcher uses the SigH class template as an underlying type.
|
||||
* The type of the instances is the one required by the signal handler:
|
||||
* `Class *` (a naked pointer).<br/>
|
||||
* When it comes to work with this kind of dispatcher, users must guarantee that
|
||||
* the lifetimes of the instances overcome the one of the dispatcher itself.
|
||||
*/
|
||||
using UnmanagedDispatcher = Dispatcher<SigH>;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -74,6 +74,18 @@ using DefaultCollectorType = typename DefaultCollector<Function>::collector_type
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Sink implementation.
|
||||
*
|
||||
* Primary template isn't defined on purpose. All the specializations give a
|
||||
* compile-time error unless the template parameter is a function type.
|
||||
*
|
||||
* @tparam Function A valid function type.
|
||||
*/
|
||||
template<typename Function>
|
||||
class Sink;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Unmanaged signal handler declaration.
|
||||
*
|
||||
@@ -87,6 +99,122 @@ template<typename Function, typename Collector = DefaultCollectorType<Function>>
|
||||
class SigH;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Sink implementation.
|
||||
*
|
||||
* A sink is an opaque object used to connect listeners to signals.<br/>
|
||||
* The function type for a listener is the one of the signal to which it
|
||||
* belongs.
|
||||
*
|
||||
* The clear separation between a signal and a sink permits to store the
|
||||
* former as private data member without exposing the publish functionality
|
||||
* to the users of a class.
|
||||
*
|
||||
* @tparam Ret Return type of a function type.
|
||||
* @tparam Args Types of arguments of a function type.
|
||||
*/
|
||||
template<typename Ret, typename... Args>
|
||||
class Sink<Ret(Args...)> final {
|
||||
/*! @brief A signal is allowed to create sinks. */
|
||||
template<typename, typename>
|
||||
friend class SigH;
|
||||
|
||||
using proto_type = Ret(*)(void *, Args...);
|
||||
using call_type = std::pair<void *, proto_type>;
|
||||
|
||||
template<Ret(*Function)(Args...)>
|
||||
static Ret proto(void *, Args... args) {
|
||||
return (Function)(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)
|
||||
: calls{calls}
|
||||
{}
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Connects a free function to a signal.
|
||||
*
|
||||
* The signal handler performs checks to avoid multiple connections for
|
||||
* free functions.
|
||||
*
|
||||
* @tparam Function A valid free function pointer.
|
||||
*/
|
||||
template<Ret(*Function)(Args...)>
|
||||
void connect() {
|
||||
disconnect<Function>();
|
||||
calls.emplace_back(nullptr, &proto<Function>);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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>);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects a free function from a signal.
|
||||
* @tparam Function A valid free function pointer.
|
||||
*/
|
||||
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());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects the given member function from a signal.
|
||||
* @tparam Class Type of class to which the member function belongs.
|
||||
* @tparam Member Member function to connect to the signal.
|
||||
* @param instance A valid instance of type pointer to `Class`.
|
||||
*/
|
||||
template<typename Class, Ret(Class::*Member)(Args...)>
|
||||
void disconnect(Class *instance) {
|
||||
call_type target{instance, &proto<Class, Member>};
|
||||
calls.erase(std::remove(calls.begin(), calls.end(), std::move(target)), calls.end());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Removes all existing connections for the given instance.
|
||||
* @tparam Class Type of class to which the member function belongs.
|
||||
* @param instance A valid instance of type pointer to `Class`.
|
||||
*/
|
||||
template<typename Class>
|
||||
void disconnect(Class *instance) {
|
||||
auto func = [instance](const call_type &call) { return call.first == instance; };
|
||||
calls.erase(std::remove_if(calls.begin(), calls.end(), std::move(func)), calls.end());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects all the listeners from a signal.
|
||||
*/
|
||||
void disconnect() {
|
||||
calls.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<call_type> &calls;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Unmanaged signal handler definition.
|
||||
*
|
||||
@@ -119,6 +247,8 @@ public:
|
||||
using size_type = typename std::vector<call_type>::size_type;
|
||||
/*! @brief Collector type. */
|
||||
using collector_type = Collector;
|
||||
/*! @brief Sink type. */
|
||||
using sink_type = Sink<Ret(Args...)>;
|
||||
|
||||
/**
|
||||
* @brief Instance type when it comes to connecting member functions.
|
||||
@@ -127,113 +257,6 @@ public:
|
||||
template<typename Class>
|
||||
using instance_type = Class *;
|
||||
|
||||
/**
|
||||
* @brief Sink implementation.
|
||||
*
|
||||
* A sink is an opaque object used to connect listeners to signals.<br/>
|
||||
* The function type for a listener is the one of the signal to which it
|
||||
* belongs.
|
||||
*
|
||||
* The clear separation between a signal and a sink permits to store the
|
||||
* former as private data member without exposing the publish functionality
|
||||
* to the users of a class.
|
||||
*/
|
||||
class Sink final {
|
||||
/*! @brief A signal is allowed to create sinks. */
|
||||
friend class SigH;
|
||||
|
||||
template<Ret(*Function)(Args...)>
|
||||
static Ret proto(void *, Args... args) {
|
||||
return (Function)(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)
|
||||
: calls{calls}
|
||||
{}
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Connects a free function to a signal.
|
||||
*
|
||||
* The signal handler performs checks to avoid multiple connections for
|
||||
* free functions.
|
||||
*
|
||||
* @tparam Function A valid free function pointer.
|
||||
*/
|
||||
template<Ret(*Function)(Args...)>
|
||||
void connect() {
|
||||
disconnect<Function>();
|
||||
calls.emplace_back(nullptr, &proto<Function>);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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(instance_type<Class> instance) {
|
||||
disconnect<Class, Member>(instance);
|
||||
calls.emplace_back(instance, &proto<Class, Member>);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects a free function from a signal.
|
||||
* @tparam Function A valid free function pointer.
|
||||
*/
|
||||
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());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects the given member function from a signal.
|
||||
* @tparam Class Type of class to which the member function belongs.
|
||||
* @tparam Member Member function to connect to the signal.
|
||||
* @param instance A valid instance of type pointer to `Class`.
|
||||
*/
|
||||
template<typename Class, Ret(Class::*Member)(Args...)>
|
||||
void disconnect(instance_type<Class> instance) {
|
||||
call_type target{instance, &proto<Class, Member>};
|
||||
calls.erase(std::remove(calls.begin(), calls.end(), std::move(target)), calls.end());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Removes all existing connections for the given instance.
|
||||
* @tparam Class Type of class to which the member function belongs.
|
||||
* @param instance A valid instance of type pointer to `Class`.
|
||||
*/
|
||||
template<typename Class>
|
||||
void disconnect(instance_type<Class> instance) {
|
||||
auto func = [instance](const call_type &call) { return call.first == instance; };
|
||||
calls.erase(std::remove_if(calls.begin(), calls.end(), std::move(func)), calls.end());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects all the listeners from a signal.
|
||||
*/
|
||||
void disconnect() {
|
||||
calls.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<call_type> &calls;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Number of listeners connected to the signal.
|
||||
* @return Number of listeners currently connected.
|
||||
@@ -259,7 +282,7 @@ public:
|
||||
*
|
||||
* @return A temporary sink object.
|
||||
*/
|
||||
Sink sink() {
|
||||
sink_type sink() {
|
||||
return { calls };
|
||||
}
|
||||
|
||||
|
||||
@@ -1,268 +0,0 @@
|
||||
#ifndef ENTT_SIGNAL_SIGNAL_HPP
|
||||
#define ENTT_SIGNAL_SIGNAL_HPP
|
||||
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
#include <cstdint>
|
||||
#include <iterator>
|
||||
#include <algorithm>
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/**
|
||||
* @brief Managed signal handler declaration.
|
||||
*
|
||||
* Primary template isn't defined on purpose. All the specializations give a
|
||||
* compile-time error unless the template parameter is a function type.
|
||||
*/
|
||||
template<typename>
|
||||
class Signal;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Managed signal handler definition.
|
||||
*
|
||||
* Managed signal handler. It works with weak pointers to classes and pointers
|
||||
* to member functions as well as pointers to free functions. References are
|
||||
* automatically removed when the instances to which they point are freed.
|
||||
*
|
||||
* This class can be used to create signals used later to notify a bunch of
|
||||
* listeners.
|
||||
*
|
||||
* @tparam Args Types of arguments of a function type.
|
||||
*/
|
||||
template<typename... Args>
|
||||
class Signal<void(Args...)> final {
|
||||
using proto_type = bool(*)(std::weak_ptr<void> &, Args...);
|
||||
using call_type = std::pair<std::weak_ptr<void>, proto_type>;
|
||||
|
||||
public:
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = std::size_t;
|
||||
|
||||
/**
|
||||
* @brief Instance type when it comes to connecting member functions.
|
||||
* @tparam Class Type of class to which the member function belongs.
|
||||
*/
|
||||
template<typename Class>
|
||||
using instance_type = std::shared_ptr<Class>;
|
||||
|
||||
/**
|
||||
* @brief Sink implementation.
|
||||
*
|
||||
* A sink is an opaque object used to connect listeners to signals.<br/>
|
||||
* The function type for a listener is the one of the signal to which it
|
||||
* belongs.
|
||||
*
|
||||
* The clear separation between a signal and a sink permits to store the
|
||||
* former as private data member without exposing the publish functionality
|
||||
* to the users of a class.
|
||||
*/
|
||||
class Sink final {
|
||||
/*! @brief A signal is allowed to create sinks. */
|
||||
friend class Signal;
|
||||
|
||||
template<void(*Function)(Args...)>
|
||||
static bool proto(std::weak_ptr<void> &, Args... args) {
|
||||
Function(args...);
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename Class, void(Class::*Member)(Args...)>
|
||||
static bool proto(std::weak_ptr<void> &wptr, Args... args) {
|
||||
bool ret = false;
|
||||
|
||||
if(!wptr.expired()) {
|
||||
auto ptr = std::static_pointer_cast<Class>(wptr.lock());
|
||||
(ptr.get()->*Member)(args...);
|
||||
ret = true;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Sink(std::vector<call_type> &calls)
|
||||
: calls{calls}
|
||||
{}
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Connects a free function to a signal.
|
||||
*
|
||||
* The signal handler performs checks to avoid multiple connections for
|
||||
* free functions.
|
||||
*
|
||||
* @tparam Function A valid free function pointer.
|
||||
*/
|
||||
template<void(*Function)(Args...)>
|
||||
void connect() {
|
||||
disconnect<Function>();
|
||||
calls.emplace_back(std::weak_ptr<void>{}, &proto<Function>);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Connects a member function for a given instance to a signal.
|
||||
*
|
||||
* The signal handler performs checks to avoid multiple connections for
|
||||
* the same member function of a given instance.
|
||||
*
|
||||
* @tparam Class Type of class to which the member function belongs.
|
||||
* @tparam Member Member function to connect to the signal.
|
||||
* @param instance A valid instance of type pointer to `Class`.
|
||||
*/
|
||||
template<typename Class, void(Class::*Member)(Args...) = &Class::receive>
|
||||
void connect(instance_type<Class> instance) {
|
||||
disconnect<Class, Member>(instance);
|
||||
calls.emplace_back(std::move(instance), &proto<Class, Member>);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects a free function from a signal.
|
||||
* @tparam Function A valid free function pointer.
|
||||
*/
|
||||
template<void(*Function)(Args...)>
|
||||
void disconnect() {
|
||||
calls.erase(std::remove_if(calls.begin(), calls.end(),
|
||||
[](const call_type &call) { return call.second == &proto<Function> && !call.first.lock(); }
|
||||
), calls.end());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects the given member function from a signal.
|
||||
* @tparam Class Type of class to which the member function belongs.
|
||||
* @tparam Member Member function to connect to the signal.
|
||||
* @param instance A valid instance of type pointer to `Class`.
|
||||
*/
|
||||
template<typename Class, void(Class::*Member)(Args...)>
|
||||
void disconnect(instance_type<Class> instance) {
|
||||
calls.erase(std::remove_if(calls.begin(), calls.end(),
|
||||
[instance{std::move(instance)}](const call_type &call) { return call.second == &proto<Class, Member> && call.first.lock() == instance; }
|
||||
), calls.end());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Removes all existing connections for the given instance.
|
||||
* @tparam Class Type of class to which the member function belongs.
|
||||
* @param instance A valid instance of type pointer to `Class`.
|
||||
*/
|
||||
template<typename Class>
|
||||
void disconnect(instance_type<Class> instance) {
|
||||
calls.erase(std::remove_if(calls.begin(), calls.end(),
|
||||
[instance{std::move(instance)}](const call_type &call) { return call.first.lock() == instance; }
|
||||
), calls.end());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects all the listeners from a signal.
|
||||
*/
|
||||
void disconnect() {
|
||||
calls.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<call_type> &calls;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Number of listeners connected to the signal.
|
||||
* @return Number of listeners currently connected.
|
||||
*/
|
||||
size_type size() const noexcept {
|
||||
return calls.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns false if at least a listener is connected to the signal.
|
||||
* @return True if the signal has no listeners connected, false otherwise.
|
||||
*/
|
||||
bool empty() const noexcept {
|
||||
return calls.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a sink object for the given signal.
|
||||
*
|
||||
* A sink is an opaque object used to connect listeners to signals.<br/>
|
||||
* The function type for a listener is the one of the signal to which it
|
||||
* belongs. The order of invocation of the listeners isn't guaranteed.
|
||||
*
|
||||
* @return A temporary sink object.
|
||||
*/
|
||||
Sink sink() {
|
||||
return { calls };
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Triggers a signal.
|
||||
*
|
||||
* All the listeners are notified. Order isn't guaranteed.
|
||||
*
|
||||
* @param args Arguments to use to invoke listeners.
|
||||
*/
|
||||
void publish(Args... args) {
|
||||
std::vector<call_type> next;
|
||||
|
||||
for(auto &&call: calls) {
|
||||
if((call.second)(call.first, args...)) {
|
||||
next.push_back(call);
|
||||
}
|
||||
}
|
||||
|
||||
calls.swap(next);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Swaps listeners between the two signals.
|
||||
* @param lhs A valid signal object.
|
||||
* @param rhs A valid signal object.
|
||||
*/
|
||||
friend void swap(Signal &lhs, Signal &rhs) {
|
||||
using std::swap;
|
||||
swap(lhs.calls, rhs.calls);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if the contents of the two signals are identical.
|
||||
*
|
||||
* Two signals are identical if they have the same size and the same
|
||||
* listeners registered exactly in the same order.
|
||||
*
|
||||
* @param other Signal with which to compare.
|
||||
* @return True if the two signals are identical, false otherwise.
|
||||
*/
|
||||
bool operator==(const Signal &other) const noexcept {
|
||||
return std::equal(calls.cbegin(), calls.cend(), other.calls.cbegin(), other.calls.cend(), [](const auto &lhs, const auto &rhs) {
|
||||
return (lhs.second == rhs.second) && (lhs.first.lock() == rhs.first.lock());
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<call_type> calls;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Checks if the contents of the two signals are different.
|
||||
*
|
||||
* Two signals are identical if they have the same size and the same
|
||||
* listeners registered exactly in the same order.
|
||||
*
|
||||
* @tparam Args Types of arguments of a function type.
|
||||
* @param lhs A valid signal object.
|
||||
* @param rhs A valid signal object.
|
||||
* @return True if the two signals are different, false otherwise.
|
||||
*/
|
||||
template<typename... Args>
|
||||
bool operator!=(const Signal<void(Args...)> &lhs, const Signal<void(Args...)> &rhs) noexcept {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_SIGNAL_SIGNAL_HPP
|
||||
@@ -73,6 +73,7 @@ add_executable(
|
||||
entity
|
||||
$<TARGET_OBJECTS:odr>
|
||||
entt/entity/actor.cpp
|
||||
entt/entity/helper.cpp
|
||||
entt/entity/registry.cpp
|
||||
entt/entity/snapshot.cpp
|
||||
entt/entity/sparse_set.cpp
|
||||
@@ -121,7 +122,6 @@ add_executable(
|
||||
entt/signal/dispatcher.cpp
|
||||
entt/signal/emitter.cpp
|
||||
entt/signal/sigh.cpp
|
||||
entt/signal/signal.cpp
|
||||
)
|
||||
target_link_libraries(signal PRIVATE gtest_main Threads::Threads)
|
||||
add_test(NAME signal COMMAND signal)
|
||||
|
||||
49
test/entt/entity/helper.cpp
Normal file
49
test/entt/entity/helper.cpp
Normal file
@@ -0,0 +1,49 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/entity/helper.hpp>
|
||||
#include <entt/entity/registry.hpp>
|
||||
|
||||
TEST(Dependency, Functionalities) {
|
||||
entt::DefaultRegistry registry;
|
||||
const auto entity = registry.create();
|
||||
entt::dependency<double, float>(registry.construction<int>());
|
||||
|
||||
ASSERT_FALSE(registry.has<double>(entity));
|
||||
ASSERT_FALSE(registry.has<float>(entity));
|
||||
|
||||
registry.assign<char>(entity);
|
||||
|
||||
ASSERT_FALSE(registry.has<double>(entity));
|
||||
ASSERT_FALSE(registry.has<float>(entity));
|
||||
|
||||
registry.assign<int>(entity);
|
||||
|
||||
ASSERT_TRUE(registry.has<double>(entity));
|
||||
ASSERT_TRUE(registry.has<float>(entity));
|
||||
ASSERT_EQ(registry.get<double>(entity), .0);
|
||||
ASSERT_EQ(registry.get<float>(entity), .0f);
|
||||
|
||||
registry.get<double>(entity) = .3;
|
||||
registry.get<float>(entity) = .1f;
|
||||
registry.remove<int>(entity);
|
||||
registry.assign<int>(entity);
|
||||
|
||||
ASSERT_EQ(registry.get<double>(entity), .3);
|
||||
ASSERT_EQ(registry.get<float>(entity), .1f);
|
||||
|
||||
registry.remove<int>(entity);
|
||||
registry.remove<float>(entity);
|
||||
registry.assign<int>(entity);
|
||||
|
||||
ASSERT_TRUE(registry.has<float>(entity));
|
||||
ASSERT_EQ(registry.get<double>(entity), .3);
|
||||
ASSERT_EQ(registry.get<float>(entity), .0f);
|
||||
|
||||
registry.remove<int>(entity);
|
||||
registry.remove<double>(entity);
|
||||
registry.remove<float>(entity);
|
||||
entt::dependency<double, float>(entt::break_op_t{}, registry.construction<int>());
|
||||
registry.assign<int>(entity);
|
||||
|
||||
ASSERT_FALSE(registry.has<double>(entity));
|
||||
ASSERT_FALSE(registry.has<float>(entity));
|
||||
}
|
||||
@@ -2,46 +2,39 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/signal/dispatcher.hpp>
|
||||
|
||||
struct Event {};
|
||||
struct AnEvent {};
|
||||
struct AnotherEvent {};
|
||||
|
||||
struct Receiver {
|
||||
void receive(const Event &) { ++cnt; }
|
||||
void receive(const AnEvent &) { ++cnt; }
|
||||
void reset() { cnt = 0; }
|
||||
std::size_t cnt{0};
|
||||
int cnt{0};
|
||||
};
|
||||
|
||||
template<typename Dispatcher, typename Rec>
|
||||
void testDispatcher(Rec receiver) {
|
||||
Dispatcher dispatcher;
|
||||
TEST(Dispatcher, Functionalities) {
|
||||
entt::Dispatcher dispatcher;
|
||||
Receiver receiver;
|
||||
|
||||
dispatcher.template sink<Event>().connect(receiver);
|
||||
dispatcher.template trigger<Event>();
|
||||
dispatcher.template enqueue<Event>();
|
||||
dispatcher.template sink<AnEvent>().connect(&receiver);
|
||||
dispatcher.template trigger<AnEvent>();
|
||||
dispatcher.template enqueue<AnEvent>();
|
||||
dispatcher.template enqueue<AnotherEvent>();
|
||||
dispatcher.update<AnotherEvent>();
|
||||
|
||||
ASSERT_EQ(receiver->cnt, static_cast<decltype(receiver->cnt)>(1));
|
||||
ASSERT_EQ(receiver.cnt, 1);
|
||||
|
||||
dispatcher.update<AnEvent>();
|
||||
dispatcher.template trigger<AnEvent>();
|
||||
|
||||
ASSERT_EQ(receiver.cnt, 3);
|
||||
|
||||
receiver.reset();
|
||||
|
||||
dispatcher.template sink<AnEvent>().disconnect(&receiver);
|
||||
dispatcher.template trigger<AnEvent>();
|
||||
dispatcher.template enqueue<AnEvent>();
|
||||
dispatcher.update();
|
||||
dispatcher.update();
|
||||
dispatcher.template trigger<Event>();
|
||||
dispatcher.template trigger<AnEvent>();
|
||||
|
||||
ASSERT_EQ(receiver->cnt, static_cast<decltype(receiver->cnt)>(3));
|
||||
|
||||
receiver->reset();
|
||||
|
||||
dispatcher.template sink<Event>().disconnect(receiver);
|
||||
dispatcher.template trigger<Event>();
|
||||
dispatcher.template enqueue<Event>();
|
||||
dispatcher.update();
|
||||
dispatcher.template trigger<Event>();
|
||||
|
||||
ASSERT_EQ(receiver->cnt, static_cast<decltype(receiver->cnt)>(0));
|
||||
}
|
||||
|
||||
TEST(ManagedDispatcher, Basics) {
|
||||
testDispatcher<entt::ManagedDispatcher>(std::make_shared<Receiver>());
|
||||
}
|
||||
|
||||
TEST(UnmanagedDispatcher, Basics) {
|
||||
auto ptr = std::make_unique<Receiver>();
|
||||
testDispatcher<entt::UnmanagedDispatcher>(ptr.get());
|
||||
ASSERT_EQ(receiver.cnt, 0);
|
||||
}
|
||||
|
||||
@@ -1,179 +0,0 @@
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/signal/signal.hpp>
|
||||
|
||||
struct S {
|
||||
static void f(const int &j) { k = j; }
|
||||
|
||||
void g() {}
|
||||
void h() {}
|
||||
|
||||
void i(const int &j) { k = j; }
|
||||
void l(const int &) {}
|
||||
|
||||
static int k;
|
||||
};
|
||||
|
||||
int S::k = 0;
|
||||
|
||||
TEST(Signal, Lifetime) {
|
||||
using signal = entt::Signal<void(void)>;
|
||||
|
||||
ASSERT_NO_THROW(signal{});
|
||||
|
||||
signal src{}, other{};
|
||||
|
||||
ASSERT_NO_THROW(signal{src});
|
||||
ASSERT_NO_THROW(signal{std::move(other)});
|
||||
ASSERT_NO_THROW(src = other);
|
||||
ASSERT_NO_THROW(src = std::move(other));
|
||||
|
||||
ASSERT_NO_THROW(delete new signal{});
|
||||
}
|
||||
|
||||
TEST(Signal, Comparison) {
|
||||
entt::Signal<void()> sig1;
|
||||
entt::Signal<void()> sig2;
|
||||
|
||||
auto s1 = std::make_shared<S>();
|
||||
auto s2 = std::make_shared<S>();
|
||||
|
||||
sig1.sink().connect<S, &S::g>(s1);
|
||||
sig2.sink().connect<S, &S::g>(s2);
|
||||
|
||||
ASSERT_FALSE(sig1 == sig2);
|
||||
ASSERT_TRUE(sig1 != sig2);
|
||||
|
||||
sig1.sink().disconnect<S, &S::g>(s1);
|
||||
sig2.sink().disconnect<S, &S::g>(s2);
|
||||
|
||||
sig1.sink().connect<S, &S::g>(s1);
|
||||
sig2.sink().connect<S, &S::h>(s1);
|
||||
|
||||
ASSERT_FALSE(sig1 == sig2);
|
||||
ASSERT_TRUE(sig1 != sig2);
|
||||
|
||||
sig1.sink().disconnect<S, &S::g>(s1);
|
||||
sig2.sink().disconnect<S, &S::h>(s1);
|
||||
|
||||
ASSERT_TRUE(sig1 == sig2);
|
||||
ASSERT_FALSE(sig1 != sig2);
|
||||
|
||||
sig1.sink().connect<S, &S::g>(s1);
|
||||
sig1.sink().connect<S, &S::h>(s1);
|
||||
sig2.sink().connect<S, &S::g>(s1);
|
||||
sig2.sink().connect<S, &S::h>(s1);
|
||||
|
||||
ASSERT_TRUE(sig1 == sig2);
|
||||
|
||||
sig1.sink().disconnect<S, &S::g>(s1);
|
||||
sig1.sink().disconnect<S, &S::h>(s1);
|
||||
sig2.sink().disconnect<S, &S::g>(s1);
|
||||
sig2.sink().disconnect<S, &S::h>(s1);
|
||||
|
||||
sig1.sink().connect<S, &S::g>(s1);
|
||||
sig1.sink().connect<S, &S::h>(s1);
|
||||
sig2.sink().connect<S, &S::h>(s1);
|
||||
sig2.sink().connect<S, &S::g>(s1);
|
||||
|
||||
ASSERT_FALSE(sig1 == sig2);
|
||||
}
|
||||
|
||||
TEST(Signal, Clear) {
|
||||
entt::Signal<void(const int &)> signal;
|
||||
signal.sink().connect<&S::f>();
|
||||
|
||||
ASSERT_FALSE(signal.empty());
|
||||
|
||||
signal.sink().disconnect();
|
||||
|
||||
ASSERT_TRUE(signal.empty());
|
||||
}
|
||||
|
||||
TEST(Signal, Swap) {
|
||||
entt::Signal<void(const int &)> sig1;
|
||||
entt::Signal<void(const int &)> sig2;
|
||||
|
||||
sig1.sink().connect<&S::f>();
|
||||
|
||||
ASSERT_FALSE(sig1.empty());
|
||||
ASSERT_TRUE(sig2.empty());
|
||||
|
||||
std::swap(sig1, sig2);
|
||||
|
||||
ASSERT_TRUE(sig1.empty());
|
||||
ASSERT_FALSE(sig2.empty());
|
||||
}
|
||||
|
||||
TEST(Signal, Functions) {
|
||||
entt::Signal<void(const int &)> signal;
|
||||
auto val = (S::k = 0) + 1;
|
||||
|
||||
signal.sink().connect<&S::f>();
|
||||
signal.publish(val);
|
||||
|
||||
ASSERT_FALSE(signal.empty());
|
||||
ASSERT_EQ(entt::Signal<void(const int &)>::size_type{1}, signal.size());
|
||||
ASSERT_EQ(S::k, val);
|
||||
|
||||
signal.sink().disconnect<&S::f>();
|
||||
signal.publish(val+1);
|
||||
|
||||
ASSERT_TRUE(signal.empty());
|
||||
ASSERT_EQ(entt::Signal<void(const int &)>::size_type{0}, signal.size());
|
||||
ASSERT_EQ(S::k, val);
|
||||
}
|
||||
|
||||
TEST(Signal, Members) {
|
||||
entt::Signal<void(const int &)> signal;
|
||||
auto ptr = std::make_shared<S>();
|
||||
auto val = (S::k = 0) + 1;
|
||||
|
||||
signal.sink().connect<S, &S::i>(ptr);
|
||||
signal.publish(val);
|
||||
|
||||
ASSERT_FALSE(signal.empty());
|
||||
ASSERT_EQ(entt::Signal<void(const int &)>::size_type{1}, signal.size());
|
||||
ASSERT_EQ(S::k, val);
|
||||
|
||||
signal.sink().disconnect<S, &S::i>(ptr);
|
||||
signal.publish(val+1);
|
||||
|
||||
ASSERT_TRUE(signal.empty());
|
||||
ASSERT_EQ(entt::Signal<void(const int &)>::size_type{0}, signal.size());
|
||||
ASSERT_EQ(S::k, val);
|
||||
|
||||
++val;
|
||||
|
||||
signal.sink().connect<S, &S::i>(ptr);
|
||||
signal.sink().connect<S, &S::l>(ptr);
|
||||
signal.publish(val);
|
||||
|
||||
ASSERT_FALSE(signal.empty());
|
||||
ASSERT_EQ(entt::Signal<void(const int &)>::size_type{2}, signal.size());
|
||||
ASSERT_EQ(S::k, val);
|
||||
|
||||
signal.sink().disconnect(ptr);
|
||||
signal.publish(val+1);
|
||||
|
||||
ASSERT_TRUE(signal.empty());
|
||||
ASSERT_EQ(entt::Signal<void(const int &)>::size_type{0}, signal.size());
|
||||
ASSERT_EQ(S::k, val);
|
||||
}
|
||||
|
||||
TEST(Signal, Cleanup) {
|
||||
entt::Signal<void(const int &)> signal;
|
||||
auto ptr = std::make_shared<S>();
|
||||
signal.sink().connect<S, &S::i>(ptr);
|
||||
auto val = (S::k = 0);
|
||||
ptr = nullptr;
|
||||
|
||||
ASSERT_FALSE(signal.empty());
|
||||
ASSERT_EQ(S::k, val);
|
||||
|
||||
signal.publish(val);
|
||||
|
||||
ASSERT_TRUE(signal.empty());
|
||||
ASSERT_EQ(S::k, val);
|
||||
}
|
||||
Reference in New Issue
Block a user