signals on component creation/destruction (#62)
This commit is contained in:
204
README.md
204
README.md
@@ -15,6 +15,7 @@
|
||||
* [Pay per use](#pay-per-use)
|
||||
* [Vademecum](#vademecum)
|
||||
* [The Registry, the Entity and the Component](#the-registry-the-entity-and-the-component)
|
||||
* [Observe changes](#observe-changes)
|
||||
* [Single instance components](#single-instance-components)
|
||||
* [Runtime components](#runtime-components)
|
||||
* [A journey through a plugin](#a-journey-through-a-plugin)
|
||||
@@ -44,7 +45,6 @@
|
||||
* [The resource, the loader and the cache](#the-resource-the-loader-and-the-cache)
|
||||
* [Crash Course: events, signals and everything in between](#crash-course-events-signals-and-everything-in-between)
|
||||
* [Signals](#signals)
|
||||
* [Compile-time event bus](#compile-time-event-bus)
|
||||
* [Delegate](#delegate)
|
||||
* [Event dispatcher](#event-dispatcher)
|
||||
* [Event emitter](#event-emitter)
|
||||
@@ -103,7 +103,7 @@ 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 an event bus.
|
||||
* Signal handlers of any type, delegates 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.
|
||||
* ...
|
||||
@@ -199,7 +199,7 @@ Dell XPS 13 out of the mid 2014):
|
||||
| Benchmark | EntityX (compile-time) | EnTT |
|
||||
|-----------|-------------|-------------|
|
||||
| Create 1M entities | 0.0167s | **0.0046s** |
|
||||
| Destroy 1M entities | 0.0053s | **0.0039s** |
|
||||
| Destroy 1M entities | 0.0053s | **0.0037s** |
|
||||
| Standard view, 1M entities, one component | 0.0012s | **1.9e-07s** |
|
||||
| Standard view, 1M entities, two components | 0.0012s | **3.8e-07s** |
|
||||
| Standard view, 1M entities, two components<br/>Half of the entities have all the components | 0.0009s | **3.8e-07s** |
|
||||
@@ -528,6 +528,47 @@ std::tuple<Position &, Velocity &> tup = registry.get<Position, Velocity>(entity
|
||||
The `get` member function template gives direct access to the component of an
|
||||
entity stored in the underlying data structures of the registry.
|
||||
|
||||
### Observe changes
|
||||
|
||||
Because of how the registry works internally, it stores a couple of signal
|
||||
handlers for each pool in order to notify some of its data structures on the
|
||||
construction and destruction of components.<br/>
|
||||
These signal handlers are also exposed and made available to users. This is the
|
||||
basic brick to build fancy things like blueprints and reactive systems.
|
||||
|
||||
To get a sink to be used to connect and disconnect listeners so as to be
|
||||
notified on the creation of a component, use the `construction` member function:
|
||||
|
||||
```cpp
|
||||
// connects a free function
|
||||
registry.construction<Position>().connect<&MyFreeFunction>();
|
||||
|
||||
// connects a member function
|
||||
registry.construction<Position>().connect<MyClass, &MyClass::member>(&instance);
|
||||
|
||||
// disconnects a free function
|
||||
registry.construction<Position>().disconnect<&MyFreeFunction>();
|
||||
|
||||
// disconnects a member function
|
||||
registry.construction<Position>().disconnect<MyClass, &MyClass::member>(&instance);
|
||||
```
|
||||
|
||||
To be notified when components are destroyed, use the `destruction` member
|
||||
function instead.
|
||||
|
||||
The function type of a listener is the same in both cases:
|
||||
|
||||
```cpp
|
||||
void(Registry<Entity> &, Entity);
|
||||
```
|
||||
|
||||
In other terms, a listener is provided with the registry that triggered the
|
||||
notification and the entity affected by the change. Note also that:
|
||||
|
||||
* Listeners are invoked **after** components have been assigned to entities.
|
||||
* Listeners are invoked **before** components have been removed from entities.
|
||||
* The order of invocation of the listeners isn't guaranteed in any case.
|
||||
|
||||
### Single instance components
|
||||
|
||||
In those cases where all what is needed is a single instance component, tags are
|
||||
@@ -937,6 +978,7 @@ All of them have pros and cons to take in consideration. In particular:
|
||||
* Standard views:
|
||||
|
||||
Pros:
|
||||
|
||||
* They work out-of-the-box and don't require any dedicated data structure.
|
||||
* Creating and destroying them isn't expensive at all because they don't have
|
||||
any type of initialization.
|
||||
@@ -946,18 +988,21 @@ All of them have pros and cons to take in consideration. In particular:
|
||||
* They don't affect any other operations of the registry.
|
||||
|
||||
Cons:
|
||||
|
||||
* Their performance tend to degenerate when the number of components to
|
||||
iterate grows up and the most of the entities have all of them.
|
||||
|
||||
* Persistent views:
|
||||
|
||||
Pros:
|
||||
|
||||
* Once prepared, creating and destroying them isn't expensive at all because
|
||||
they don't have any type of initialization.
|
||||
* They are the best tool for iterating entities for mmultiple components and
|
||||
most entities have them all.
|
||||
|
||||
Cons:
|
||||
|
||||
* They have dedicated data structures and thus affect the memory usage to a
|
||||
minimal extent.
|
||||
* If not previously prepared, the first time they are used they go through an
|
||||
@@ -969,6 +1014,7 @@ All of them have pros and cons to take in consideration. In particular:
|
||||
* Raw views:
|
||||
|
||||
Pros:
|
||||
|
||||
* They work out-of-the-box and don't require any dedicated data structure.
|
||||
* Creating and destroying them isn't expensive at all because they don't have
|
||||
any type of initialization.
|
||||
@@ -977,10 +1023,12 @@ All of them have pros and cons to take in consideration. In particular:
|
||||
* They don't affect any other operations of the registry.
|
||||
|
||||
Cons:
|
||||
|
||||
* They can be used to iterate only one type of component at a time.
|
||||
* They don't return the entity to which a component belongs to the caller.
|
||||
|
||||
To sum up and as a rule of thumb:
|
||||
|
||||
* Use a raw view to iterate components only (no entities) for a given type.
|
||||
* Use a standard view to iterate entities for a single component.
|
||||
* Use a standard view to iterate entities for multiple components when a
|
||||
@@ -1939,9 +1987,15 @@ 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 sink will take care of disconnecting them whenever they die. Unmanaged
|
||||
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 sink to avoid crashes due to different lifetimes.
|
||||
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.
|
||||
|
||||
### Managed signal handler
|
||||
|
||||
@@ -1962,7 +2016,8 @@ 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:
|
||||
signature can be easily connected to and disconnected from the signal by means
|
||||
of a sink:
|
||||
|
||||
```cpp
|
||||
void foo(int, char) { /* ... */ }
|
||||
@@ -1975,18 +2030,22 @@ struct S {
|
||||
|
||||
auto instance = std::make_shared<S>();
|
||||
|
||||
signal.connect<&foo>();
|
||||
signal.connect<S, &S::bar>(instance);
|
||||
signal.sink().connect<&foo>();
|
||||
signal.sink().connect<S, &S::bar>(instance);
|
||||
|
||||
// ...
|
||||
|
||||
signal.disconnect<&foo>();
|
||||
// disconnects a free function
|
||||
signal.sink().disconnect<&foo>();
|
||||
|
||||
// disconnect a specific member function of an instance ...
|
||||
signal.disconnect<S, &S::bar>(instance);
|
||||
// disconnects a specific member function of an instance ...
|
||||
signal.sink().disconnect<S, &S::bar>(instance);
|
||||
|
||||
// ... or an instance as a whole
|
||||
signal.disconnect(instance);
|
||||
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
|
||||
@@ -2000,8 +2059,8 @@ 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`), to reset it to its
|
||||
initial state (`clear`) and even to swap two handlers (`swap`).<br/>
|
||||
(`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.
|
||||
|
||||
@@ -2036,11 +2095,10 @@ 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`), to reset it to its initial state (`clear`) and even
|
||||
to swap two handlers (`swap`).
|
||||
least a listener (`empty`) and even to swap two handlers (`swap`).
|
||||
|
||||
Besides them, there are member functions to use both to connect and disconnect
|
||||
listeners in all their forms:
|
||||
listeners in all their forms by means of a sink::
|
||||
|
||||
```cpp
|
||||
void foo(int, char) { /* ... */ }
|
||||
@@ -2053,18 +2111,22 @@ struct S {
|
||||
|
||||
S instance;
|
||||
|
||||
signal.connect<&foo>();
|
||||
signal.connect<S, &S::bar>(&instance);
|
||||
signal.sink().connect<&foo>();
|
||||
signal.sink().connect<S, &S::bar>(&instance);
|
||||
|
||||
// ...
|
||||
|
||||
signal.disconnect<&foo>();
|
||||
// disconnects a free function
|
||||
signal.sink().disconnect<&foo>();
|
||||
|
||||
// disconnect a specific member function of an instance ...
|
||||
signal.disconnect<S, &S::bar>(&instance);
|
||||
signal.sink().disconnect<S, &S::bar>(&instance);
|
||||
|
||||
// ... or an instance as a whole
|
||||
signal.disconnect(&instance);
|
||||
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
|
||||
@@ -2095,8 +2157,8 @@ int g() { return 1; }
|
||||
|
||||
entt::SigH<int(), MyCollector<int>> signal;
|
||||
|
||||
signal.connect<&f>();
|
||||
signal.connect<&g>();
|
||||
signal.sink().connect<&f>();
|
||||
signal.sink().connect<&g>();
|
||||
|
||||
MyCollector collector = signal.collect();
|
||||
|
||||
@@ -2110,89 +2172,6 @@ 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.
|
||||
|
||||
## Compile-time event bus
|
||||
|
||||
A bus can be used to create a compile-time backbone for event management.<br/>
|
||||
The intended use is as a base class, which is the opposite of what the signals
|
||||
are meant for. Internally it uses either managed or unmanaged signal handlers,
|
||||
that is why there exist both a managed and an unmanaged event bus.
|
||||
|
||||
The API of a bus is a kind of subset of the one of a signal. First of all, it
|
||||
requires that all the types of events are specified when the bus is declared:
|
||||
|
||||
```cpp
|
||||
struct AnEvent { int value; };
|
||||
struct AnotherEvent {};
|
||||
|
||||
// define a managed bus that works with std::shared_ptr/std::weak_ptr
|
||||
entt::ManagedBus<AnEvent, AnotherEvent> managed;
|
||||
|
||||
// define an unmanaged bus that works with naked pointers
|
||||
entt::UnmanagedBus<AnEvent, AnotherEvent> unmanaged;
|
||||
```
|
||||
|
||||
For the sake of brevity, below is described the interface of the sole unmanaged
|
||||
bus. The interface of the managed bus 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 bus, its type must expose one
|
||||
or more member functions named `receive` of which the return types are `void`
|
||||
and the argument lists are `const E &`, for each type of event `E`.<br/>
|
||||
The `reg` member function is the way to go to register such an instance:
|
||||
|
||||
```cpp
|
||||
struct Listener
|
||||
{
|
||||
void receive(const AnEvent &) { /* ... */ }
|
||||
void receive(const AnotherEvent &) { /* ... */ }
|
||||
};
|
||||
|
||||
// ...
|
||||
|
||||
Listener listener;
|
||||
bus.reg(&listener);
|
||||
```
|
||||
|
||||
To disconnect an instance of a class from a bus, use the `unreg` member
|
||||
function instead:
|
||||
|
||||
```cpp
|
||||
bus.unreg(&listener);
|
||||
```
|
||||
|
||||
Each function that respects the accepted signature is automatically registered
|
||||
and/or unregistered. Note that invoking `unreg` with an instance of a class that
|
||||
hasn't been previously registered is a perfectly valid operation.
|
||||
|
||||
Free functions can be registered and unregistered as well by means of the
|
||||
dedicated member functions, namely `connect` and `disconnect`:
|
||||
|
||||
```cpp
|
||||
void foo(const AnEvent &) { /* ... */ }
|
||||
void bar(const AnotherEvent &) { /* ... */ }
|
||||
|
||||
// ...
|
||||
|
||||
bus.connect<AnEvent, &foo>();
|
||||
bus.connect<AnotherEvent, &bar>();
|
||||
|
||||
// ...
|
||||
|
||||
bus.disconnect<AnEvent, &foo>();
|
||||
bus.disconnect<AnotherEvent, &bar>();
|
||||
```
|
||||
|
||||
Whenever the need to send an event arises, it can be done through the `publish`
|
||||
member function:
|
||||
|
||||
```cpp
|
||||
bus.publish<AnEvent>(42);
|
||||
bus.publish<AnotherEvent>();
|
||||
```
|
||||
|
||||
Finally, there are another few functions to use to query the internal state of a
|
||||
bus like `empty` and `size` whose meaning is quite intuitive.
|
||||
|
||||
## Delegate
|
||||
|
||||
A delegate can be used as general purpose invoker with no memory overhead for
|
||||
@@ -2278,7 +2257,8 @@ 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:
|
||||
event must be provided to the `connect` member function of the sink bound to the
|
||||
specific event:
|
||||
|
||||
```cpp
|
||||
struct AnEvent { int value; };
|
||||
@@ -2293,16 +2273,16 @@ struct Listener
|
||||
// ...
|
||||
|
||||
Listener listener;
|
||||
dispatcher.connect<AnEvent>(&listener);
|
||||
dispatcher.connect<AnotherEvent, Listener, &Listener::method>(&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.disconnect<AnEvent>(&listener);
|
||||
dispatcher.disconnect<AnotherEvent, Listener, &Listener::method>(&listener);
|
||||
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
|
||||
|
||||
7
TODO
7
TODO
@@ -1,9 +1,10 @@
|
||||
* custom allocators and EnTT allocator-aware in general (long term feature, I don't actually need it at the moment) - see #22
|
||||
* to analyze, long term feature: systems organizer based on dependency graphs for implicit parallelism (I don't want to think anymore in future :-))
|
||||
* scene management (I prefer the concept of spaces, that is a kind of scene anyway)
|
||||
* blueprint registry - kind of factory to create entitites template for initialization (get rid of the extra versions of Registry::create)
|
||||
* 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
|
||||
* signals on component creation/destruction: crtp + internal detection, probably it works - test 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)
|
||||
* use tag dispatching to disambiguate between standard components and single instance components, then provide users with an common API
|
||||
* remove Actor::update (it's application dependent), allow tag instead
|
||||
* AOB
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
namespace entt {
|
||||
|
||||
|
||||
namespace {
|
||||
namespace details {
|
||||
|
||||
|
||||
template<typename... Types>
|
||||
@@ -87,7 +87,7 @@ private:
|
||||
* @tparam Types List of types for which to generate identifiers.
|
||||
*/
|
||||
template<typename... Types>
|
||||
constexpr auto ident = Identifier<std::decay_t<Types>...>{std::make_index_sequence<sizeof...(Types)>{}};
|
||||
constexpr auto ident = details::Identifier<std::decay_t<Types>...>{std::make_index_sequence<sizeof...(Types)>{}};
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <algorithm>
|
||||
#include <type_traits>
|
||||
#include "../core/family.hpp"
|
||||
#include "../signal/sigh.hpp"
|
||||
#include "entt_traits.hpp"
|
||||
#include "snapshot.hpp"
|
||||
#include "sparse_set.hpp"
|
||||
@@ -35,9 +36,27 @@ template<typename Entity>
|
||||
class Registry {
|
||||
using tag_family = Family<struct InternalRegistryTagFamily>;
|
||||
using component_family = Family<struct InternalRegistryComponentFamily>;
|
||||
using view_family = Family<struct InternalRegistryViewFamily>;
|
||||
using handler_family = Family<struct InternalRegistryHandlerFamily>;
|
||||
using traits_type = entt_traits<Entity>;
|
||||
|
||||
template<typename... Component>
|
||||
static void creating(Registry ®istry, Entity entity) {
|
||||
if(registry.has<Component...>(entity)) {
|
||||
const auto htype = handler_family::type<Component...>();
|
||||
registry.handlers[htype]->construct(entity);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename... Component>
|
||||
static void destroying(Registry ®istry, Entity entity) {
|
||||
const auto htype = handler_family::type<Component...>();
|
||||
auto &handler = registry.handlers[htype];
|
||||
|
||||
if(handler->has(entity)) {
|
||||
handler->destroy(entity);
|
||||
}
|
||||
}
|
||||
|
||||
struct Attachee {
|
||||
Entity entity;
|
||||
};
|
||||
@@ -55,45 +74,38 @@ class Registry {
|
||||
|
||||
template<typename Component>
|
||||
struct Pool: SparseSet<Entity, Component> {
|
||||
using test_fn_type = bool(Registry::*)(Entity) const;
|
||||
using sink_type = typename SigH<void(Registry &, Entity)>::Sink;
|
||||
|
||||
Pool(Registry ®istry)
|
||||
: registry{registry}
|
||||
{}
|
||||
|
||||
template<typename... Args>
|
||||
Component & construct(Registry ®istry, Entity entity, Args &&... args) {
|
||||
Component & construct(Entity entity, Args &&... args) {
|
||||
auto &component = SparseSet<Entity, Component>::construct(entity, std::forward<Args>(args)...);
|
||||
|
||||
for(auto &&listener: listeners) {
|
||||
if((registry.*listener.second)(entity)) {
|
||||
listener.first->construct(entity);
|
||||
}
|
||||
}
|
||||
|
||||
listeners.first.publish(registry, entity);
|
||||
return component;
|
||||
}
|
||||
|
||||
void destroy(Entity entity) override {
|
||||
listeners.second.publish(registry, entity);
|
||||
SparseSet<Entity, Component>::destroy(entity);
|
||||
|
||||
for(auto &&listener: listeners) {
|
||||
auto *handler = listener.first;
|
||||
|
||||
if(handler->has(entity)) {
|
||||
handler->destroy(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline void append(SparseSet<Entity> *handler, test_fn_type fn) {
|
||||
listeners.emplace_back(handler, fn);
|
||||
sink_type construction() noexcept {
|
||||
return listeners.first.sink();
|
||||
}
|
||||
|
||||
inline void remove(SparseSet<Entity> *handler) {
|
||||
listeners.erase(std::remove_if(listeners.begin(), listeners.end(), [handler](auto &listener) {
|
||||
return listener.first == handler;
|
||||
}), listeners.end());
|
||||
sink_type destruction() noexcept {
|
||||
return listeners.second.sink();
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<std::pair<SparseSet<Entity> *, test_fn_type>> listeners;
|
||||
Registry ®istry;
|
||||
std::pair<
|
||||
SigH<void(Registry &, Entity)>,
|
||||
SigH<void(Registry &, Entity)>
|
||||
> listeners;
|
||||
};
|
||||
|
||||
template<typename Component>
|
||||
@@ -122,40 +134,12 @@ class Registry {
|
||||
}
|
||||
|
||||
if(!pools[ctype]) {
|
||||
pools[ctype] = std::make_unique<Pool<Component>>();
|
||||
pools[ctype] = std::make_unique<Pool<Component>>(*this);
|
||||
}
|
||||
|
||||
return pool<Component>();
|
||||
}
|
||||
|
||||
template<typename... Component>
|
||||
SparseSet<Entity> & handler() {
|
||||
static_assert(sizeof...(Component) > 1, "!");
|
||||
const auto vtype = view_family::type<Component...>();
|
||||
|
||||
if(!(vtype < handlers.size())) {
|
||||
handlers.resize(vtype + 1);
|
||||
}
|
||||
|
||||
if(!handlers[vtype]) {
|
||||
using accumulator_type = int[];
|
||||
auto set = std::make_unique<SparseSet<Entity>>();
|
||||
|
||||
for(auto entity: view<Component...>()) {
|
||||
set->construct(entity);
|
||||
}
|
||||
|
||||
accumulator_type accumulator = {
|
||||
(assure<Component>().append(set.get(), &Registry::has<Component...>), 0)...
|
||||
};
|
||||
|
||||
handlers[vtype] = std::move(set);
|
||||
(void)accumulator;
|
||||
}
|
||||
|
||||
return *handlers[vtype];
|
||||
}
|
||||
|
||||
public:
|
||||
/*! @brief Underlying entity identifier. */
|
||||
using entity_type = typename traits_type::entity_type;
|
||||
@@ -168,6 +152,10 @@ public:
|
||||
/*! @brief Unsigned integer type. */
|
||||
using component_type = typename component_family::family_type;
|
||||
|
||||
/*! @brief Type of sink for the given component. */
|
||||
template<typename Component>
|
||||
using sink_type = typename Pool<Component>::sink_type;
|
||||
|
||||
/*! @brief Default constructor. */
|
||||
Registry() = default;
|
||||
|
||||
@@ -316,7 +304,6 @@ public:
|
||||
bool fast(entity_type entity) const noexcept {
|
||||
const auto pos = size_type(entity & traits_type::entity_mask);
|
||||
assert(pos < entities.size());
|
||||
// the in-use control bit permits to avoid accessing the direct vector
|
||||
return (entities[pos] == entity);
|
||||
}
|
||||
|
||||
@@ -375,7 +362,7 @@ public:
|
||||
entity_type create(Component &&... components) noexcept {
|
||||
using accumulator_type = int[];
|
||||
const auto entity = create();
|
||||
accumulator_type accumulator = { 0, (assure<std::decay_t<Component>>().construct(*this, entity, std::forward<Component>(components)), 0)... };
|
||||
accumulator_type accumulator = { 0, (assure<std::decay_t<Component>>().construct(entity, std::forward<Component>(components)), 0)... };
|
||||
(void)accumulator;
|
||||
return entity;
|
||||
}
|
||||
@@ -402,7 +389,7 @@ public:
|
||||
entity_type create() noexcept {
|
||||
using accumulator_type = int[];
|
||||
const auto entity = create();
|
||||
accumulator_type accumulator = { 0, (assure<Component>().construct(*this, entity), 0)... };
|
||||
accumulator_type accumulator = { 0, (assure<Component>().construct(entity), 0)... };
|
||||
(void)accumulator;
|
||||
return entity;
|
||||
}
|
||||
@@ -469,11 +456,11 @@ public:
|
||||
next = entt;
|
||||
++available;
|
||||
|
||||
for(auto &&cpool: pools) {
|
||||
std::for_each(pools.begin(), pools.end(), [entity](auto &cpool) {
|
||||
if(cpool && cpool->has(entity)) {
|
||||
cpool->destroy(entity);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -663,7 +650,7 @@ public:
|
||||
template<typename Component, typename... Args>
|
||||
Component & assign(entity_type entity, Args &&... args) {
|
||||
assert(valid(entity));
|
||||
return assure<Component>().construct(*this, entity, std::forward<Args>(args)...);
|
||||
return assure<Component>().construct(entity, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -845,7 +832,59 @@ public:
|
||||
|
||||
return (cpool.has(entity)
|
||||
? (cpool.get(entity) = Component{std::forward<Args>(args)...})
|
||||
: cpool.construct(*this, entity, std::forward<Args>(args)...));
|
||||
: cpool.construct(entity, std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a sink object for the given component.
|
||||
*
|
||||
* A sink is an opaque object used to connect listeners to components.<br/>
|
||||
* The sink returned by this function can be used to receive notifications
|
||||
* whenever a new instance of the given component is created and assigned to
|
||||
* an entity.
|
||||
*
|
||||
* The function type for a listener is:
|
||||
* @code{.cpp}
|
||||
* void(Registry<Entity> &, Entity);
|
||||
* @endcode
|
||||
*
|
||||
* Listeners are invoked **after** the component has been assigned to the
|
||||
* entity. The order of invocation of the listeners isn't guaranteed.
|
||||
*
|
||||
* @sa SigH::Sink
|
||||
*
|
||||
* @tparam Component Type of component of which to get the sink.
|
||||
* @return A temporary sink object.
|
||||
*/
|
||||
template<typename Component>
|
||||
sink_type<Component> construction() noexcept {
|
||||
return assure<Component>().construction();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a sink object for the given component.
|
||||
*
|
||||
* A sink is an opaque object used to connect listeners to components.<br/>
|
||||
* The sink returned by this function can be used to receive notifications
|
||||
* whenever an instance of the given component is removed from an entity and
|
||||
* thus destroyed.
|
||||
*
|
||||
* The function type for a listener is:
|
||||
* @code{.cpp}
|
||||
* void(Registry<Entity> &, Entity);
|
||||
* @endcode
|
||||
*
|
||||
* Listeners are invoked **before** the component has been removed from the
|
||||
* entity. The order of invocation of the listeners isn't guaranteed.
|
||||
*
|
||||
* @sa SigH::Sink
|
||||
*
|
||||
* @tparam Component Type of component of which to get the sink.
|
||||
* @return A temporary sink object.
|
||||
*/
|
||||
template<typename Component>
|
||||
sink_type<Component> destruction() noexcept {
|
||||
return assure<Component>().destruction();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1119,7 +1158,30 @@ public:
|
||||
*/
|
||||
template<typename... Component>
|
||||
void prepare() {
|
||||
handler<Component...>();
|
||||
static_assert(sizeof...(Component) > 1, "!");
|
||||
const auto htype = handler_family::type<Component...>();
|
||||
|
||||
if(!(htype < handlers.size())) {
|
||||
handlers.resize(htype + 1);
|
||||
}
|
||||
|
||||
if(!handlers[htype]) {
|
||||
using accumulator_type = int[];
|
||||
handlers[htype] = std::make_unique<SparseSet<entity_type>>();
|
||||
auto &handler = handlers[htype];
|
||||
|
||||
for(auto entity: view<Component...>()) {
|
||||
handler->construct(entity);
|
||||
}
|
||||
|
||||
auto connect = [](auto &pool) {
|
||||
pool.construction().template connect<&Registry::creating<Component...>>();
|
||||
pool.destruction().template connect<&Registry::destroying<Component...>>();
|
||||
};
|
||||
|
||||
accumulator_type accumulator = { (connect(assure<Component>()), 0)... };
|
||||
(void)accumulator;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1141,11 +1203,16 @@ public:
|
||||
void discard() {
|
||||
if(contains<Component...>()) {
|
||||
using accumulator_type = int[];
|
||||
const auto vtype = view_family::type<Component...>();
|
||||
auto *set = handlers[vtype].get();
|
||||
const auto htype = handler_family::type<Component...>();
|
||||
|
||||
auto disconnect = [](auto &pool) {
|
||||
pool.construction().template disconnect<&Registry::creating<Component...>>();
|
||||
pool.destruction().template disconnect<&Registry::destroying<Component...>>();
|
||||
};
|
||||
|
||||
// if a set exists, pools have already been created for it
|
||||
accumulator_type accumulator = { (pool<Component>().remove(set), 0)... };
|
||||
handlers[vtype].reset();
|
||||
accumulator_type accumulator = { (disconnect(pool<Component>()), 0)... };
|
||||
handlers[htype].reset();
|
||||
(void)accumulator;
|
||||
}
|
||||
}
|
||||
@@ -1158,8 +1225,8 @@ public:
|
||||
template<typename... Component>
|
||||
bool contains() const noexcept {
|
||||
static_assert(sizeof...(Component) > 1, "!");
|
||||
const auto vtype = view_family::type<Component...>();
|
||||
return vtype < handlers.size() && handlers[vtype];
|
||||
const auto htype = handler_family::type<Component...>();
|
||||
return (htype < handlers.size() && handlers[htype]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1202,8 +1269,9 @@ public:
|
||||
*/
|
||||
template<typename... Component>
|
||||
PersistentView<Entity, Component...> persistent() {
|
||||
// after the calls to handler, pools have already been created
|
||||
return PersistentView<Entity, Component...>{handler<Component...>(), pool<Component>()...};
|
||||
prepare<Component...>();
|
||||
const auto htype = handler_family::type<Component...>();
|
||||
return PersistentView<Entity, Component...>{*handlers[htype], pool<Component>()...};
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
#include "resource/cache.hpp"
|
||||
#include "resource/handle.hpp"
|
||||
#include "resource/loader.hpp"
|
||||
#include "signal/bus.hpp"
|
||||
#include "signal/delegate.hpp"
|
||||
#include "signal/dispatcher.hpp"
|
||||
#include "signal/emitter.hpp"
|
||||
|
||||
@@ -1,305 +0,0 @@
|
||||
#ifndef ENTT_SIGNAL_BUS_HPP
|
||||
#define ENTT_SIGNAL_BUS_HPP
|
||||
|
||||
|
||||
#include <cstddef>
|
||||
#include <utility>
|
||||
#include "signal.hpp"
|
||||
#include "sigh.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/**
|
||||
* @brief Minimal event bus.
|
||||
*
|
||||
* Primary template isn't defined on purpose. The main reason for which it
|
||||
* exists is to work around the doxygen's parsing capabilities. In fact, there
|
||||
* is no need to declare it actually.
|
||||
*/
|
||||
template<template<typename...> class, typename...>
|
||||
class Bus;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Event bus specialization for multiple types.
|
||||
*
|
||||
* The event bus is designed to allow an easy registration of specific member
|
||||
* functions to a bunch of signal handlers (either manager or unmanaged).
|
||||
* Classes must publicly expose the required member functions to allow the bus
|
||||
* to detect them for the purpose of registering and unregistering
|
||||
* instances.<br/>
|
||||
* In particular, for each event type `E`, a matching member function has the
|
||||
* following signature: `void receive(const E &)`. Events will be properly
|
||||
* redirected to all the listeners by calling the right member functions, if
|
||||
* any.
|
||||
*
|
||||
* @tparam Sig Type of signal handler to use.
|
||||
* @tparam Event The list of events managed by the bus.
|
||||
*/
|
||||
template<template<typename...> class Sig, typename Event, typename... Other>
|
||||
class Bus<Sig, Event, Other...>
|
||||
: private Bus<Sig, Event>, private Bus<Sig, Other>...
|
||||
{
|
||||
public:
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = std::size_t;
|
||||
|
||||
/**
|
||||
* @brief Unregisters all the member functions of an instance.
|
||||
*
|
||||
* A bus is used to convey a certain set of events. This method detects
|
||||
* and unregisters from the bus all the matching member functions of an
|
||||
* instance.<br/>
|
||||
* For each event type `E`, a matching member function has the following
|
||||
* signature: `void receive(const E &)`.
|
||||
*
|
||||
* @tparam Instance Type of instance to unregister.
|
||||
* @param instance A valid instance of the right type.
|
||||
*/
|
||||
template<typename Instance>
|
||||
void unreg(Instance instance) {
|
||||
using accumulator_type = int[];
|
||||
accumulator_type accumulator = {
|
||||
(Bus<Sig, Event>::unreg(instance), 0),
|
||||
(Bus<Sig, Other>::unreg(instance), 0)...
|
||||
};
|
||||
return void(accumulator);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Registers all the member functions of an instance.
|
||||
*
|
||||
* A bus is used to convey a certain set of events. This method detects
|
||||
* and registers to the bus all the matching member functions of an
|
||||
* instance.<br/>
|
||||
* For each event type `E`, a matching member function has the following
|
||||
* signature: `void receive(const E &)`.
|
||||
*
|
||||
* @tparam Instance Type of instance to register.
|
||||
* @param instance A valid instance of the right type.
|
||||
*/
|
||||
template<typename Instance>
|
||||
void reg(Instance instance) {
|
||||
using accumulator_type = int[];
|
||||
accumulator_type accumulator = {
|
||||
(Bus<Sig, Event>::reg(instance), 0),
|
||||
(Bus<Sig, Other>::reg(instance), 0)...
|
||||
};
|
||||
return void(accumulator);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Number of listeners connected to the bus.
|
||||
* @return Number of listeners currently connected.
|
||||
*/
|
||||
size_type size() const noexcept {
|
||||
using accumulator_type = std::size_t[];
|
||||
std::size_t sz = Bus<Sig, Event>::size();
|
||||
accumulator_type accumulator = { sz, (sz += Bus<Sig, Other>::size())... };
|
||||
return void(accumulator), sz;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns false if at least a listener is connected to the bus.
|
||||
* @return True if the bus has no listeners connected, false otherwise.
|
||||
*/
|
||||
bool empty() const noexcept {
|
||||
using accumulator_type = bool[];
|
||||
bool ret = Bus<Sig, Event>::empty();
|
||||
accumulator_type accumulator = { ret, (ret = ret && Bus<Sig, Other>::empty())... };
|
||||
return void(accumulator), ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Connects a free function to the bus.
|
||||
* @tparam Type Type of event to which to connect the function.
|
||||
* @tparam Function A valid free function pointer.
|
||||
*/
|
||||
template<typename Type, void(*Function)(const Type &)>
|
||||
void connect() {
|
||||
Bus<Sig, Type>::template connect<Function>();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects a free function from the bus.
|
||||
* @tparam Type Type of event from which to disconnect the function.
|
||||
* @tparam Function A valid free function pointer.
|
||||
*/
|
||||
template<typename Type, void(*Function)(const Type &)>
|
||||
void disconnect() {
|
||||
Bus<Sig, Type>::template disconnect<Function>();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Publishes an event.
|
||||
*
|
||||
* All the listeners are notified. Order isn't guaranteed.
|
||||
*
|
||||
* @tparam Type Type of event to publish.
|
||||
* @tparam Args Types of arguments to use to construct the event.
|
||||
* @param args Arguments to use to construct the event.
|
||||
*/
|
||||
template<typename Type, typename... Args>
|
||||
void publish(Args &&... args) {
|
||||
Bus<Sig, Type>::publish(std::forward<Args>(args)...);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Event bus specialization for a single type.
|
||||
*
|
||||
* The event bus is designed to allow an easy registration of a specific member
|
||||
* function to a signal handler (either manager or unmanaged).
|
||||
* Classes must publicly expose the required member function to allow the bus to
|
||||
* detect it for the purpose of registering and unregistering instances.<br/>
|
||||
* In particular, a matching member function has the following signature:
|
||||
* `void receive(const Event &)`. Events of the given type will be properly
|
||||
* redirected to all the listeners by calling the right member function, if any.
|
||||
*
|
||||
* @tparam Sig Type of signal handler to use.
|
||||
* @tparam Event Type of event managed by the bus.
|
||||
*/
|
||||
template<template<typename...> class Sig, typename Event>
|
||||
class Bus<Sig, Event> {
|
||||
using signal_type = Sig<void(const Event &)>;
|
||||
|
||||
template<typename Class>
|
||||
using instance_type = typename signal_type::template instance_type<Class>;
|
||||
|
||||
template<typename Class>
|
||||
auto disconnect(int, instance_type<Class> instance)
|
||||
-> decltype(std::declval<Class>().receive(std::declval<Event>()), void()) {
|
||||
signal.template disconnect<Class, &Class::receive>(std::move(instance));
|
||||
}
|
||||
|
||||
template<typename Class>
|
||||
auto connect(int, instance_type<Class> instance)
|
||||
-> decltype(std::declval<Class>().receive(std::declval<Event>()), void()) {
|
||||
signal.template connect<Class, &Class::receive>(std::move(instance));
|
||||
}
|
||||
|
||||
template<typename Class> void disconnect(char, instance_type<Class>) {}
|
||||
template<typename Class> void connect(char, instance_type<Class>) {}
|
||||
|
||||
public:
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = typename signal_type::size_type;
|
||||
|
||||
/**
|
||||
* @brief Unregisters member functions of instances.
|
||||
*
|
||||
* This method tries to detect and unregister from the bus matching member
|
||||
* functions of instances.<br/>
|
||||
* A matching member function has the following signature:
|
||||
* `void receive(const Event &)`.
|
||||
*
|
||||
* @tparam Class Type of instance to unregister.
|
||||
* @param instance A valid instance of the right type.
|
||||
*/
|
||||
template<typename Class>
|
||||
void unreg(instance_type<Class> instance) {
|
||||
disconnect(0, std::move(instance));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Tries to register an instance.
|
||||
*
|
||||
* This method tries to detect and register to the bus matching member
|
||||
* functions of instances.<br/>
|
||||
* A matching member function has the following signature:
|
||||
* `void receive(const Event &)`.
|
||||
*
|
||||
* @tparam Class Type of instance to register.
|
||||
* @param instance A valid instance of the right type.
|
||||
*/
|
||||
template<typename Class>
|
||||
void reg(instance_type<Class> instance) {
|
||||
connect(0, std::move(instance));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Number of listeners connected to the bus.
|
||||
* @return Number of listeners currently connected.
|
||||
*/
|
||||
size_type size() const noexcept {
|
||||
return signal.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns false if at least a listener is connected to the bus.
|
||||
* @return True if the bus has no listeners connected, false otherwise.
|
||||
*/
|
||||
bool empty() const noexcept {
|
||||
return signal.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Connects a free function to the bus.
|
||||
* @tparam Function A valid free function pointer.
|
||||
*/
|
||||
template<void(*Function)(const Event &)>
|
||||
void connect() {
|
||||
signal.template connect<Function>();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects a free function from the bus.
|
||||
* @tparam Function A valid free function pointer.
|
||||
*/
|
||||
template<void(*Function)(const Event &)>
|
||||
void disconnect() {
|
||||
signal.template disconnect<Function>();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Publishes an event.
|
||||
*
|
||||
* All the listeners are notified. Order isn't guaranteed.
|
||||
*
|
||||
* @tparam Args Types of arguments to use to construct the event.
|
||||
* @param args Arguments to use to construct the event.
|
||||
*/
|
||||
template<typename... Args>
|
||||
void publish(Args &&... args) {
|
||||
signal.publish({ std::forward<Args>(args)... });
|
||||
}
|
||||
|
||||
private:
|
||||
signal_type signal;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Managed event bus.
|
||||
*
|
||||
* A managed event bus uses the Signal class template as an underlying type. The
|
||||
* type of the instances is the one required by the signal handler:
|
||||
* `std::shared_ptr<Class>` (a shared pointer).
|
||||
*
|
||||
* @tparam Event The list of events managed by the bus.
|
||||
*/
|
||||
template<typename... Event>
|
||||
using ManagedBus = Bus<Signal, Event...>;
|
||||
|
||||
/**
|
||||
* @brief Unmanaged event bus.
|
||||
*
|
||||
* An unmanaged event bus uses the SigH class template as an underlying type.
|
||||
* The type of the instances is the one required by the signal handler:
|
||||
* `Class *` (a naked pointer).<br/>
|
||||
* When it comes to work with this kind of bus, users must guarantee that the
|
||||
* lifetimes of the instances overcome the one of the bus itself.
|
||||
*
|
||||
* @tparam Event The list of events managed by the bus.
|
||||
*/
|
||||
template<typename... Event>
|
||||
using UnmanagedBus = Bus<SigH, Event...>;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_SIGNAL_BUS_HPP
|
||||
@@ -40,6 +40,8 @@ class Dispatcher final {
|
||||
|
||||
template<typename Event>
|
||||
struct SignalWrapper final: BaseSignalWrapper {
|
||||
using sink_type = typename Sig<void(const Event &)>::Sink;
|
||||
|
||||
void publish(std::size_t current) override {
|
||||
for(const auto &event: events[current]) {
|
||||
signal.publish(event);
|
||||
@@ -48,14 +50,8 @@ class Dispatcher final {
|
||||
events[current].clear();
|
||||
}
|
||||
|
||||
template<typename Class, void(Class::*Member)(const Event &)>
|
||||
inline void connect(instance_type<Class, Event> instance) noexcept {
|
||||
signal.template connect<Class, Member>(std::move(instance));
|
||||
}
|
||||
|
||||
template<typename Class, void(Class::*Member)(const Event &)>
|
||||
inline void disconnect(instance_type<Class, Event> instance) noexcept {
|
||||
signal.template disconnect<Class, Member>(std::move(instance));
|
||||
inline sink_type sink() noexcept {
|
||||
return signal.sink();
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
@@ -93,52 +89,36 @@ class Dispatcher final {
|
||||
}
|
||||
|
||||
public:
|
||||
/*! @brief Type of sink for the given event. */
|
||||
template<typename Event>
|
||||
using sink_type = typename SignalWrapper<Event>::sink_type;
|
||||
|
||||
/*! @brief Default constructor. */
|
||||
Dispatcher() noexcept
|
||||
: wrappers{}, mode{false}
|
||||
{}
|
||||
|
||||
/**
|
||||
* @brief Registers a listener given in the form of a member function.
|
||||
* @brief Returns a sink object for the given event.
|
||||
*
|
||||
* A matching member function has the following signature:
|
||||
* `void receive(const Event &)`. Member functions named `receive` are
|
||||
* automatically detected and registered if available.
|
||||
* A sink is an opaque object used to connect listeners to events.
|
||||
*
|
||||
* @warning
|
||||
* Connecting a listener during an update may lead to unexpected behavior.
|
||||
* Register listeners before or after invoking the update if possible.
|
||||
* The function type for a listener is:
|
||||
* @code{.cpp}
|
||||
* void(const Event &)
|
||||
* @endcode
|
||||
*
|
||||
* @tparam Event Type of event to which to connect the function.
|
||||
* @tparam Class Type of class to which the member function belongs.
|
||||
* @tparam Member Member function to connect to the signal.
|
||||
* @param instance A valid instance of the right type.
|
||||
* 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, typename Class, void(Class::*Member)(const Event &) = &Class::receive>
|
||||
void connect(instance_type<Class, Event> instance) noexcept {
|
||||
wrapper<Event>().template connect<Class, Member>(std::move(instance));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Unregisters a listener given in the form of a member function.
|
||||
*
|
||||
* A matching member function has the following signature:
|
||||
* `void receive(const Event &)`. Member functions named `receive` are
|
||||
* automatically detected and unregistered if available.
|
||||
*
|
||||
* @warning
|
||||
* Disconnecting a listener during an update may lead to unexpected
|
||||
* behavior. Unregister listeners before or after invoking the update if
|
||||
* possible.
|
||||
*
|
||||
* @tparam Event Type of event from which to disconnect the function.
|
||||
* @tparam Class Type of class to which the member function belongs.
|
||||
* @tparam Member Member function to connect to the signal.
|
||||
* @param instance A valid instance of the right type.
|
||||
*/
|
||||
template<typename Event, typename Class, void(Class::*Member)(const Event &) = &Class::receive>
|
||||
void disconnect(instance_type<Class, Event> instance) noexcept {
|
||||
wrapper<Event>().template disconnect<Class, Member>(std::move(instance));
|
||||
template<typename Event>
|
||||
sink_type<Event> sink() noexcept {
|
||||
return wrapper<Event>().sink();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -182,9 +162,7 @@ public:
|
||||
const auto buf = buffer(mode);
|
||||
mode = !mode;
|
||||
|
||||
for(auto pos = wrappers.size(); pos; --pos) {
|
||||
auto &wrapper = wrappers[pos-1];
|
||||
|
||||
for(auto &&wrapper: wrappers) {
|
||||
if(wrapper) {
|
||||
wrapper->publish(buf);
|
||||
}
|
||||
|
||||
@@ -138,7 +138,7 @@ class Emitter {
|
||||
}
|
||||
|
||||
public:
|
||||
/** @brief Type of listeners accepted for the given type of event. */
|
||||
/** @brief Type of listeners accepted for the given event. */
|
||||
template<typename Event>
|
||||
using Listener = typename Handler<Event>::listener_type;
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
namespace entt {
|
||||
|
||||
|
||||
namespace {
|
||||
namespace details {
|
||||
|
||||
|
||||
template<typename, typename>
|
||||
@@ -83,7 +83,7 @@ using DefaultCollectorType = typename DefaultCollector<Function>::collector_type
|
||||
* @tparam Function A valid function type.
|
||||
* @tparam Collector Type of collector to use, if any.
|
||||
*/
|
||||
template<typename Function, typename Collector = DefaultCollectorType<Function>>
|
||||
template<typename Function, typename Collector = details::DefaultCollectorType<Function>>
|
||||
class SigH;
|
||||
|
||||
|
||||
@@ -111,18 +111,8 @@ class SigH;
|
||||
* @tparam Collector Type of collector to use, if any.
|
||||
*/
|
||||
template<typename Ret, typename... Args, typename Collector>
|
||||
class SigH<Ret(Args...), Collector> final: private Invoker<Ret(Args...), Collector> {
|
||||
using typename Invoker<Ret(Args...), Collector>::call_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...);
|
||||
}
|
||||
class SigH<Ret(Args...), Collector> final: private details::Invoker<Ret(Args...), Collector> {
|
||||
using call_type = typename details::Invoker<Ret(Args...), Collector>::call_type;
|
||||
|
||||
public:
|
||||
/*! @brief Unsigned integer type. */
|
||||
@@ -137,6 +127,112 @@ 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 {
|
||||
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.
|
||||
@@ -154,75 +250,16 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects all the listeners from a signal.
|
||||
*/
|
||||
void clear() noexcept {
|
||||
calls.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Connects a free function to a signal.
|
||||
* @brief Returns a sink object for the given signal.
|
||||
*
|
||||
* The signal handler performs checks to avoid multiple connections for free
|
||||
* functions.
|
||||
* 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.
|
||||
*
|
||||
* @tparam Function A valid free function pointer.
|
||||
* @return A temporary sink object.
|
||||
*/
|
||||
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...)>
|
||||
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());
|
||||
Sink sink() {
|
||||
return { calls };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -233,8 +270,7 @@ public:
|
||||
* @param args Arguments to use to invoke listeners.
|
||||
*/
|
||||
void publish(Args... args) {
|
||||
for(auto pos = calls.size(); pos; --pos) {
|
||||
auto &call = calls[pos-1];
|
||||
for(auto &&call: calls) {
|
||||
call.second(call.first, args...);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,25 +40,6 @@ class Signal<void(Args...)> final {
|
||||
using proto_type = bool(*)(std::weak_ptr<void> &, Args...);
|
||||
using call_type = std::pair<std::weak_ptr<void>, proto_type>;
|
||||
|
||||
template<void(*Function)(Args...)>
|
||||
static bool proto(std::weak_ptr<void> &, Args... args) {
|
||||
Function(args...);
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename Class, void(Class::*Member)(Args...)>
|
||||
static bool proto(std::weak_ptr<void> &wptr, Args... args) {
|
||||
bool ret = false;
|
||||
|
||||
if(!wptr.expired()) {
|
||||
auto ptr = std::static_pointer_cast<Class>(wptr.lock());
|
||||
(ptr.get()->*Member)(args...);
|
||||
ret = true;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public:
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = std::size_t;
|
||||
@@ -70,6 +51,121 @@ public:
|
||||
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 {
|
||||
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.
|
||||
@@ -87,76 +183,16 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects all the listeners from a signal.
|
||||
*/
|
||||
void clear() noexcept {
|
||||
calls.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Connects a free function to a signal.
|
||||
* @brief Returns a sink object for the given signal.
|
||||
*
|
||||
* The signal handler performs checks to avoid multiple connections for free
|
||||
* functions.
|
||||
* 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.
|
||||
*
|
||||
* @tparam Function A valid free function pointer.
|
||||
* @return A temporary sink object.
|
||||
*/
|
||||
template<void(*Function)(Args...)>
|
||||
void connect() {
|
||||
disconnect<Function>();
|
||||
calls.emplace_back(std::weak_ptr<void>{}, &proto<Function>);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Connects a member function for a given instance to a signal.
|
||||
*
|
||||
* The signal handler performs checks to avoid multiple connections for the
|
||||
* same member function of a given instance.
|
||||
*
|
||||
* @tparam Class Type of class to which the member function belongs.
|
||||
* @tparam Member Member function to connect to the signal.
|
||||
* @param instance A valid instance of type pointer to `Class`.
|
||||
*/
|
||||
template<typename Class, void(Class::*Member)(Args...)>
|
||||
void connect(instance_type<Class> instance) {
|
||||
disconnect<Class, Member>(instance);
|
||||
calls.emplace_back(std::move(instance), &proto<Class, Member>);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects a free function from a signal.
|
||||
* @tparam Function A valid free function pointer.
|
||||
*/
|
||||
template<void(*Function)(Args...)>
|
||||
void disconnect() {
|
||||
calls.erase(std::remove_if(calls.begin(), calls.end(),
|
||||
[](const call_type &call) { return call.second == &proto<Function> && !call.first.lock(); }
|
||||
), calls.end());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects the given member function from a signal.
|
||||
* @tparam Class Type of class to which the member function belongs.
|
||||
* @tparam Member Member function to connect to the signal.
|
||||
* @param instance A valid instance of type pointer to `Class`.
|
||||
*/
|
||||
template<typename Class, void(Class::*Member)(Args...)>
|
||||
void disconnect(instance_type<Class> instance) {
|
||||
calls.erase(std::remove_if(calls.begin(), calls.end(),
|
||||
[instance{std::move(instance)}](const call_type &call) { return call.second == &proto<Class, Member> && call.first.lock() == instance; }
|
||||
), calls.end());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Removes all existing connections for the given instance.
|
||||
* @tparam Class Type of class to which the member function belongs.
|
||||
* @param instance A valid instance of type pointer to `Class`.
|
||||
*/
|
||||
template<typename Class>
|
||||
void disconnect(instance_type<Class> instance) {
|
||||
calls.erase(std::remove_if(calls.begin(), calls.end(),
|
||||
[instance{std::move(instance)}](const call_type &call) { return call.first.lock() == instance; }
|
||||
), calls.end());
|
||||
Sink sink() {
|
||||
return { calls };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -169,9 +205,7 @@ public:
|
||||
void publish(Args... args) {
|
||||
std::vector<call_type> next;
|
||||
|
||||
for(auto pos = calls.size(); pos; --pos) {
|
||||
auto &call = calls[pos-1];
|
||||
|
||||
for(auto &&call: calls) {
|
||||
if((call.second)(call.first, args...)) {
|
||||
next.push_back(call);
|
||||
}
|
||||
|
||||
@@ -117,7 +117,6 @@ add_test(NAME resource COMMAND resource)
|
||||
add_executable(
|
||||
signal
|
||||
$<TARGET_OBJECTS:odr>
|
||||
entt/signal/bus.cpp
|
||||
entt/signal/delegate.cpp
|
||||
entt/signal/dispatcher.cpp
|
||||
entt/signal/emitter.cpp
|
||||
|
||||
@@ -6,6 +6,21 @@
|
||||
#include <entt/entity/entt_traits.hpp>
|
||||
#include <entt/entity/registry.hpp>
|
||||
|
||||
struct Listener {
|
||||
void incr(entt::DefaultRegistry &, entt::DefaultRegistry::entity_type entity) {
|
||||
last = entity;
|
||||
++counter;
|
||||
}
|
||||
|
||||
void decr(entt::DefaultRegistry &, entt::DefaultRegistry::entity_type entity) {
|
||||
last = entity;
|
||||
--counter;
|
||||
}
|
||||
|
||||
entt::DefaultRegistry::entity_type last;
|
||||
int counter{0};
|
||||
};
|
||||
|
||||
TEST(DefaultRegistry, Functionalities) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
@@ -508,3 +523,38 @@ TEST(DefaultRegistry, MergeTwoRegistries) {
|
||||
ne(dst.view<int, float, double>().begin(), dst.view<int, float, double>().end());
|
||||
ne(dst.view<char, float, int>().begin(), dst.view<char, float, int>().end());
|
||||
}
|
||||
|
||||
TEST(DefaultRegistry, Signals) {
|
||||
entt::DefaultRegistry registry;
|
||||
Listener listener;
|
||||
|
||||
registry.construction<int>().connect<Listener, &Listener::incr>(&listener);
|
||||
registry.destruction<int>().connect<Listener, &Listener::decr>(&listener);
|
||||
|
||||
auto e0 = registry.create();
|
||||
auto e1 = registry.create();
|
||||
|
||||
registry.assign<int>(e0);
|
||||
registry.assign<int>(e1);
|
||||
|
||||
ASSERT_EQ(listener.counter, 2);
|
||||
ASSERT_EQ(listener.last, e1);
|
||||
|
||||
registry.remove<int>(e0);
|
||||
|
||||
ASSERT_EQ(listener.counter, 1);
|
||||
ASSERT_EQ(listener.last, e0);
|
||||
|
||||
registry.destruction<int>().disconnect<Listener, &Listener::decr>(&listener);
|
||||
registry.remove<int>(e1);
|
||||
|
||||
ASSERT_EQ(listener.counter, 1);
|
||||
ASSERT_EQ(listener.last, e0);
|
||||
|
||||
registry.construction<int>().disconnect<Listener, &Listener::incr>(&listener);
|
||||
registry.assign<int>(e0);
|
||||
registry.assign<int>(e1);
|
||||
|
||||
ASSERT_EQ(listener.counter, 1);
|
||||
ASSERT_EQ(listener.last, e0);
|
||||
}
|
||||
|
||||
@@ -1,141 +0,0 @@
|
||||
#include <memory>
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/signal/bus.hpp>
|
||||
|
||||
struct EventA
|
||||
{
|
||||
EventA(int x, int y): value{x+y} {}
|
||||
int value;
|
||||
};
|
||||
|
||||
struct EventB {};
|
||||
struct EventC {};
|
||||
|
||||
struct MyListener
|
||||
{
|
||||
void receive(const EventA &) { A++; }
|
||||
static void listen(const EventB &) { B++; }
|
||||
void receive(const EventC &) { C++; }
|
||||
void reset() { A = 0; B = 0; C = 0; }
|
||||
int A{0};
|
||||
static int B;
|
||||
int C{0};
|
||||
};
|
||||
|
||||
int MyListener::B = 0;
|
||||
|
||||
template<typename Bus, typename Listener>
|
||||
void testRegUnregEmit(Listener listener) {
|
||||
Bus bus;
|
||||
|
||||
listener->reset();
|
||||
bus.template publish<EventA>(40, 2);
|
||||
bus.template publish<EventB>();
|
||||
bus.template publish<EventC>();
|
||||
|
||||
ASSERT_EQ(bus.size(), (decltype(bus.size()))0);
|
||||
ASSERT_TRUE(bus.empty());
|
||||
ASSERT_EQ(listener->A, 0);
|
||||
ASSERT_EQ(listener->B, 0);
|
||||
ASSERT_EQ(listener->C, 0);
|
||||
|
||||
bus.reg(listener);
|
||||
bus.template connect<EventB, &MyListener::listen>();
|
||||
|
||||
listener->reset();
|
||||
bus.template publish<EventA>(40, 2);
|
||||
bus.template publish<EventB>();
|
||||
bus.template publish<EventC>();
|
||||
|
||||
ASSERT_EQ(bus.size(), (decltype(bus.size()))3);
|
||||
ASSERT_FALSE(bus.empty());
|
||||
ASSERT_EQ(listener->A, 1);
|
||||
ASSERT_EQ(listener->B, 1);
|
||||
ASSERT_EQ(listener->C, 1);
|
||||
|
||||
bus.unreg(listener);
|
||||
|
||||
listener->reset();
|
||||
bus.template publish<EventA>(40, 2);
|
||||
bus.template publish<EventB>();
|
||||
bus.template publish<EventC>();
|
||||
|
||||
ASSERT_EQ(bus.size(), (decltype(bus.size()))1);
|
||||
ASSERT_FALSE(bus.empty());
|
||||
ASSERT_EQ(listener->A, 0);
|
||||
ASSERT_EQ(listener->B, 1);
|
||||
ASSERT_EQ(listener->C, 0);
|
||||
|
||||
bus.template disconnect<EventB, MyListener::listen>();
|
||||
|
||||
listener->reset();
|
||||
bus.template publish<EventA>(40, 2);
|
||||
bus.template publish<EventB>();
|
||||
bus.template publish<EventC>();
|
||||
|
||||
ASSERT_EQ(bus.size(), (decltype(bus.size()))0);
|
||||
ASSERT_TRUE(bus.empty());
|
||||
ASSERT_EQ(listener->A, 0);
|
||||
ASSERT_EQ(listener->B, 0);
|
||||
ASSERT_EQ(listener->C, 0);
|
||||
}
|
||||
|
||||
TEST(ManagedBus, RegUnregEmit) {
|
||||
using MyManagedBus = entt::ManagedBus<EventA, EventB, EventC>;
|
||||
testRegUnregEmit<MyManagedBus>(std::make_shared<MyListener>());
|
||||
}
|
||||
|
||||
TEST(ManagedBus, ExpiredListeners) {
|
||||
entt::ManagedBus<EventA, EventB, EventC> bus;
|
||||
auto listener = std::make_shared<MyListener>();
|
||||
|
||||
listener->reset();
|
||||
bus.reg(listener);
|
||||
bus.template publish<EventA>(40, 2);
|
||||
bus.template publish<EventB>();
|
||||
|
||||
ASSERT_EQ(bus.size(), (decltype(bus.size()))2);
|
||||
ASSERT_FALSE(bus.empty());
|
||||
ASSERT_EQ(listener->A, 1);
|
||||
ASSERT_EQ(listener->B, 0);
|
||||
|
||||
listener->reset();
|
||||
listener = nullptr;
|
||||
|
||||
ASSERT_EQ(bus.size(), (decltype(bus.size()))2);
|
||||
ASSERT_FALSE(bus.empty());
|
||||
|
||||
EXPECT_NO_THROW(bus.template publish<EventA>(40, 2));
|
||||
EXPECT_NO_THROW(bus.template publish<EventC>());
|
||||
|
||||
ASSERT_EQ(bus.size(), (decltype(bus.size()))0);
|
||||
ASSERT_TRUE(bus.empty());
|
||||
}
|
||||
|
||||
TEST(UnmanagedBus, RegUnregEmit) {
|
||||
using MyUnmanagedBus = entt::UnmanagedBus<EventA, EventB, EventC>;
|
||||
auto ptr = std::make_unique<MyListener>();
|
||||
testRegUnregEmit<MyUnmanagedBus>(ptr.get());
|
||||
}
|
||||
|
||||
TEST(UnmanagedBus, ExpiredListeners) {
|
||||
entt::UnmanagedBus<EventA, EventB, EventC> bus;
|
||||
auto listener = std::make_unique<MyListener>();
|
||||
|
||||
listener->reset();
|
||||
bus.reg(listener.get());
|
||||
bus.template publish<EventA>(40, 2);
|
||||
bus.template publish<EventB>();
|
||||
|
||||
ASSERT_EQ(bus.size(), (decltype(bus.size()))2);
|
||||
ASSERT_FALSE(bus.empty());
|
||||
ASSERT_EQ(listener->A, 1);
|
||||
ASSERT_EQ(listener->B, 0);
|
||||
|
||||
listener->reset();
|
||||
listener = nullptr;
|
||||
|
||||
// dangling pointer inside ... well, unmanaged means unmanaged!! :-)
|
||||
ASSERT_EQ(bus.size(), (decltype(bus.size()))2);
|
||||
ASSERT_FALSE(bus.empty());
|
||||
}
|
||||
@@ -14,7 +14,7 @@ template<typename Dispatcher, typename Rec>
|
||||
void testDispatcher(Rec receiver) {
|
||||
Dispatcher dispatcher;
|
||||
|
||||
dispatcher.template connect<Event>(receiver);
|
||||
dispatcher.template sink<Event>().connect(receiver);
|
||||
dispatcher.template trigger<Event>();
|
||||
dispatcher.template enqueue<Event>();
|
||||
|
||||
@@ -28,7 +28,7 @@ void testDispatcher(Rec receiver) {
|
||||
|
||||
receiver->reset();
|
||||
|
||||
dispatcher.template disconnect<Event>(receiver);
|
||||
dispatcher.template sink<Event>().disconnect(receiver);
|
||||
dispatcher.template trigger<Event>();
|
||||
dispatcher.template enqueue<Event>();
|
||||
dispatcher.update();
|
||||
|
||||
@@ -3,163 +3,18 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/signal/sigh.hpp>
|
||||
|
||||
TEST(SigH, Lifetime) {
|
||||
using signal = entt::SigH<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(SigH, Comparison) {
|
||||
struct S {
|
||||
void f() {}
|
||||
void g() {}
|
||||
};
|
||||
|
||||
entt::SigH<void()> sig1;
|
||||
entt::SigH<void()> sig2;
|
||||
|
||||
S s1;
|
||||
S s2;
|
||||
|
||||
sig1.connect<S, &S::f>(&s1);
|
||||
sig2.connect<S, &S::f>(&s2);
|
||||
|
||||
ASSERT_FALSE(sig1 == sig2);
|
||||
ASSERT_TRUE(sig1 != sig2);
|
||||
|
||||
sig1.disconnect<S, &S::f>(&s1);
|
||||
sig2.disconnect<S, &S::f>(&s2);
|
||||
|
||||
sig1.connect<S, &S::f>(&s1);
|
||||
sig2.connect<S, &S::g>(&s1);
|
||||
|
||||
ASSERT_FALSE(sig1 == sig2);
|
||||
ASSERT_TRUE(sig1 != sig2);
|
||||
|
||||
sig1.disconnect<S, &S::f>(&s1);
|
||||
sig2.disconnect<S, &S::g>(&s1);
|
||||
|
||||
ASSERT_TRUE(sig1 == sig2);
|
||||
ASSERT_FALSE(sig1 != sig2);
|
||||
|
||||
sig1.connect<S, &S::f>(&s1);
|
||||
sig1.connect<S, &S::g>(&s1);
|
||||
sig2.connect<S, &S::f>(&s1);
|
||||
sig2.connect<S, &S::g>(&s1);
|
||||
|
||||
ASSERT_TRUE(sig1 == sig2);
|
||||
|
||||
sig1.disconnect<S, &S::f>(&s1);
|
||||
sig1.disconnect<S, &S::g>(&s1);
|
||||
sig2.disconnect<S, &S::f>(&s1);
|
||||
sig2.disconnect<S, &S::g>(&s1);
|
||||
|
||||
sig1.connect<S, &S::f>(&s1);
|
||||
sig1.connect<S, &S::g>(&s1);
|
||||
sig2.connect<S, &S::g>(&s1);
|
||||
sig2.connect<S, &S::f>(&s1);
|
||||
|
||||
ASSERT_FALSE(sig1 == sig2);
|
||||
}
|
||||
|
||||
struct S {
|
||||
static void f(int &v) { v = 42; }
|
||||
|
||||
bool g(int) { k = !k; return true; }
|
||||
bool h(int) { return k; }
|
||||
|
||||
void i() {}
|
||||
void l() {}
|
||||
|
||||
bool k{false};
|
||||
};
|
||||
|
||||
TEST(SigH, Clear) {
|
||||
entt::SigH<void(int &)> sigh;
|
||||
sigh.connect<&S::f>();
|
||||
|
||||
ASSERT_FALSE(sigh.empty());
|
||||
|
||||
sigh.clear();
|
||||
|
||||
ASSERT_TRUE(sigh.empty());
|
||||
}
|
||||
|
||||
TEST(SigH, Swap) {
|
||||
entt::SigH<void(int &)> sigh1;
|
||||
entt::SigH<void(int &)> sigh2;
|
||||
|
||||
sigh1.connect<&S::f>();
|
||||
|
||||
ASSERT_FALSE(sigh1.empty());
|
||||
ASSERT_TRUE(sigh2.empty());
|
||||
|
||||
std::swap(sigh1, sigh2);
|
||||
|
||||
ASSERT_TRUE(sigh1.empty());
|
||||
ASSERT_FALSE(sigh2.empty());
|
||||
}
|
||||
|
||||
TEST(SigH, Functions) {
|
||||
entt::SigH<void(int &)> sigh;
|
||||
int v = 0;
|
||||
|
||||
sigh.connect<&S::f>();
|
||||
sigh.publish(v);
|
||||
|
||||
ASSERT_FALSE(sigh.empty());
|
||||
ASSERT_EQ((entt::SigH<bool(int)>::size_type)1, sigh.size());
|
||||
ASSERT_EQ(42, v);
|
||||
|
||||
v = 0;
|
||||
sigh.disconnect<&S::f>();
|
||||
sigh.publish(v);
|
||||
|
||||
ASSERT_TRUE(sigh.empty());
|
||||
ASSERT_EQ((entt::SigH<bool(int)>::size_type)0, sigh.size());
|
||||
ASSERT_EQ(0, v);
|
||||
|
||||
sigh.connect<&S::f>();
|
||||
}
|
||||
|
||||
TEST(SigH, Members) {
|
||||
struct S {
|
||||
bool f(int) { b = !b; return true; }
|
||||
bool g(int) { return b; }
|
||||
bool b{false};
|
||||
};
|
||||
|
||||
S s;
|
||||
S *ptr = &s;
|
||||
entt::SigH<bool(int)> sigh;
|
||||
|
||||
sigh.connect<S, &S::f>(ptr);
|
||||
sigh.publish(42);
|
||||
|
||||
ASSERT_TRUE(s.b);
|
||||
ASSERT_FALSE(sigh.empty());
|
||||
ASSERT_EQ((entt::SigH<bool(int)>::size_type)1, sigh.size());
|
||||
|
||||
sigh.disconnect<S, &S::f>(ptr);
|
||||
sigh.publish(42);
|
||||
|
||||
ASSERT_TRUE(s.b);
|
||||
ASSERT_TRUE(sigh.empty());
|
||||
ASSERT_EQ((entt::SigH<bool(int)>::size_type)0, sigh.size());
|
||||
|
||||
sigh.connect<S, &S::f>(ptr);
|
||||
sigh.connect<S, &S::g>(ptr);
|
||||
|
||||
ASSERT_FALSE(sigh.empty());
|
||||
ASSERT_EQ((entt::SigH<bool(int)>::size_type)2, sigh.size());
|
||||
|
||||
sigh.disconnect(ptr);
|
||||
|
||||
ASSERT_TRUE(sigh.empty());
|
||||
ASSERT_EQ((entt::SigH<bool(int)>::size_type)0, sigh.size());
|
||||
}
|
||||
|
||||
template<typename Ret>
|
||||
struct TestCollectAll {
|
||||
std::vector<Ret> vec{};
|
||||
@@ -190,10 +45,152 @@ struct TestCollectFirst {
|
||||
}
|
||||
};
|
||||
|
||||
TEST(SigH, Lifetime) {
|
||||
using signal = entt::SigH<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(SigH, Comparison) {
|
||||
entt::SigH<void()> sig1;
|
||||
entt::SigH<void()> sig2;
|
||||
|
||||
S s1;
|
||||
S s2;
|
||||
|
||||
sig1.sink().connect<S, &S::i>(&s1);
|
||||
sig2.sink().connect<S, &S::i>(&s2);
|
||||
|
||||
ASSERT_FALSE(sig1 == sig2);
|
||||
ASSERT_TRUE(sig1 != sig2);
|
||||
|
||||
sig1.sink().disconnect<S, &S::i>(&s1);
|
||||
sig2.sink().disconnect<S, &S::i>(&s2);
|
||||
|
||||
sig1.sink().connect<S, &S::i>(&s1);
|
||||
sig2.sink().connect<S, &S::l>(&s1);
|
||||
|
||||
ASSERT_FALSE(sig1 == sig2);
|
||||
ASSERT_TRUE(sig1 != sig2);
|
||||
|
||||
sig1.sink().disconnect<S, &S::i>(&s1);
|
||||
sig2.sink().disconnect<S, &S::l>(&s1);
|
||||
|
||||
ASSERT_TRUE(sig1 == sig2);
|
||||
ASSERT_FALSE(sig1 != sig2);
|
||||
|
||||
sig1.sink().connect<S, &S::i>(&s1);
|
||||
sig1.sink().connect<S, &S::l>(&s1);
|
||||
sig2.sink().connect<S, &S::i>(&s1);
|
||||
sig2.sink().connect<S, &S::l>(&s1);
|
||||
|
||||
ASSERT_TRUE(sig1 == sig2);
|
||||
|
||||
sig1.sink().disconnect<S, &S::i>(&s1);
|
||||
sig1.sink().disconnect<S, &S::l>(&s1);
|
||||
sig2.sink().disconnect<S, &S::i>(&s1);
|
||||
sig2.sink().disconnect<S, &S::l>(&s1);
|
||||
|
||||
sig1.sink().connect<S, &S::i>(&s1);
|
||||
sig1.sink().connect<S, &S::l>(&s1);
|
||||
sig2.sink().connect<S, &S::l>(&s1);
|
||||
sig2.sink().connect<S, &S::i>(&s1);
|
||||
|
||||
ASSERT_FALSE(sig1 == sig2);
|
||||
}
|
||||
|
||||
TEST(SigH, Clear) {
|
||||
entt::SigH<void(int &)> sigh;
|
||||
sigh.sink().connect<&S::f>();
|
||||
|
||||
ASSERT_FALSE(sigh.empty());
|
||||
|
||||
sigh.sink().disconnect();
|
||||
|
||||
ASSERT_TRUE(sigh.empty());
|
||||
}
|
||||
|
||||
TEST(SigH, Swap) {
|
||||
entt::SigH<void(int &)> sigh1;
|
||||
entt::SigH<void(int &)> sigh2;
|
||||
|
||||
sigh1.sink().connect<&S::f>();
|
||||
|
||||
ASSERT_FALSE(sigh1.empty());
|
||||
ASSERT_TRUE(sigh2.empty());
|
||||
|
||||
std::swap(sigh1, sigh2);
|
||||
|
||||
ASSERT_TRUE(sigh1.empty());
|
||||
ASSERT_FALSE(sigh2.empty());
|
||||
}
|
||||
|
||||
TEST(SigH, Functions) {
|
||||
entt::SigH<void(int &)> sigh;
|
||||
int v = 0;
|
||||
|
||||
sigh.sink().connect<&S::f>();
|
||||
sigh.publish(v);
|
||||
|
||||
ASSERT_FALSE(sigh.empty());
|
||||
ASSERT_EQ((entt::SigH<bool(int)>::size_type)1, sigh.size());
|
||||
ASSERT_EQ(42, v);
|
||||
|
||||
v = 0;
|
||||
sigh.sink().disconnect<&S::f>();
|
||||
sigh.publish(v);
|
||||
|
||||
ASSERT_TRUE(sigh.empty());
|
||||
ASSERT_EQ((entt::SigH<bool(int)>::size_type)0, sigh.size());
|
||||
ASSERT_EQ(0, v);
|
||||
|
||||
sigh.sink().connect<&S::f>();
|
||||
}
|
||||
|
||||
TEST(SigH, Members) {
|
||||
S s;
|
||||
S *ptr = &s;
|
||||
entt::SigH<bool(int)> sigh;
|
||||
|
||||
sigh.sink().connect<S, &S::g>(ptr);
|
||||
sigh.publish(42);
|
||||
|
||||
ASSERT_TRUE(s.k);
|
||||
ASSERT_FALSE(sigh.empty());
|
||||
ASSERT_EQ((entt::SigH<bool(int)>::size_type)1, sigh.size());
|
||||
|
||||
sigh.sink().disconnect<S, &S::g>(ptr);
|
||||
sigh.publish(42);
|
||||
|
||||
ASSERT_TRUE(s.k);
|
||||
ASSERT_TRUE(sigh.empty());
|
||||
ASSERT_EQ((entt::SigH<bool(int)>::size_type)0, sigh.size());
|
||||
|
||||
sigh.sink().connect<S, &S::g>(ptr);
|
||||
sigh.sink().connect<S, &S::h>(ptr);
|
||||
|
||||
ASSERT_FALSE(sigh.empty());
|
||||
ASSERT_EQ((entt::SigH<bool(int)>::size_type)2, sigh.size());
|
||||
|
||||
sigh.sink().disconnect(ptr);
|
||||
|
||||
ASSERT_TRUE(sigh.empty());
|
||||
ASSERT_EQ((entt::SigH<bool(int)>::size_type)0, sigh.size());
|
||||
}
|
||||
|
||||
TEST(SigH, Collector) {
|
||||
entt::SigH<void(), TestCollectAll<void>> sigh_void;
|
||||
|
||||
sigh_void.connect<&TestCollectAll<void>::h>();
|
||||
sigh_void.sink().connect<&TestCollectAll<void>::h>();
|
||||
auto collector_void = sigh_void.collect();
|
||||
|
||||
ASSERT_FALSE(sigh_void.empty());
|
||||
@@ -201,9 +198,9 @@ TEST(SigH, Collector) {
|
||||
|
||||
entt::SigH<int(), TestCollectAll<int>> sigh_all;
|
||||
|
||||
sigh_all.connect<&TestCollectAll<int>::f>();
|
||||
sigh_all.connect<&TestCollectAll<int>::f>();
|
||||
sigh_all.connect<&TestCollectAll<int>::g>();
|
||||
sigh_all.sink().connect<&TestCollectAll<int>::f>();
|
||||
sigh_all.sink().connect<&TestCollectAll<int>::f>();
|
||||
sigh_all.sink().connect<&TestCollectAll<int>::g>();
|
||||
auto collector_all = sigh_all.collect();
|
||||
|
||||
ASSERT_FALSE(sigh_all.empty());
|
||||
@@ -214,8 +211,8 @@ TEST(SigH, Collector) {
|
||||
|
||||
entt::SigH<int(), TestCollectFirst<int>> sigh_first;
|
||||
|
||||
sigh_first.connect<&TestCollectFirst<int>::f>();
|
||||
sigh_first.connect<&TestCollectFirst<int>::f>();
|
||||
sigh_first.sink().connect<&TestCollectFirst<int>::f>();
|
||||
sigh_first.sink().connect<&TestCollectFirst<int>::f>();
|
||||
auto collector_first = sigh_first.collect();
|
||||
|
||||
ASSERT_FALSE(sigh_first.empty());
|
||||
|
||||
@@ -4,13 +4,18 @@
|
||||
#include <entt/signal/signal.hpp>
|
||||
|
||||
struct S {
|
||||
static void f(const int &j) { i = j; }
|
||||
void g(const int &j) { i = j; }
|
||||
void h(const int &) {}
|
||||
static int i;
|
||||
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::i = 0;
|
||||
int S::k = 0;
|
||||
|
||||
TEST(Signal, Lifetime) {
|
||||
using signal = entt::Signal<void(void)>;
|
||||
@@ -28,65 +33,60 @@ TEST(Signal, Lifetime) {
|
||||
}
|
||||
|
||||
TEST(Signal, Comparison) {
|
||||
struct S {
|
||||
void f() {}
|
||||
void g() {}
|
||||
};
|
||||
|
||||
entt::Signal<void()> sig1;
|
||||
entt::Signal<void()> sig2;
|
||||
|
||||
auto s1 = std::make_shared<S>();
|
||||
auto s2 = std::make_shared<S>();
|
||||
|
||||
sig1.connect<S, &S::f>(s1);
|
||||
sig2.connect<S, &S::f>(s2);
|
||||
sig1.sink().connect<S, &S::g>(s1);
|
||||
sig2.sink().connect<S, &S::g>(s2);
|
||||
|
||||
ASSERT_FALSE(sig1 == sig2);
|
||||
ASSERT_TRUE(sig1 != sig2);
|
||||
|
||||
sig1.disconnect<S, &S::f>(s1);
|
||||
sig2.disconnect<S, &S::f>(s2);
|
||||
sig1.sink().disconnect<S, &S::g>(s1);
|
||||
sig2.sink().disconnect<S, &S::g>(s2);
|
||||
|
||||
sig1.connect<S, &S::f>(s1);
|
||||
sig2.connect<S, &S::g>(s1);
|
||||
sig1.sink().connect<S, &S::g>(s1);
|
||||
sig2.sink().connect<S, &S::h>(s1);
|
||||
|
||||
ASSERT_FALSE(sig1 == sig2);
|
||||
ASSERT_TRUE(sig1 != sig2);
|
||||
|
||||
sig1.disconnect<S, &S::f>(s1);
|
||||
sig2.disconnect<S, &S::g>(s1);
|
||||
sig1.sink().disconnect<S, &S::g>(s1);
|
||||
sig2.sink().disconnect<S, &S::h>(s1);
|
||||
|
||||
ASSERT_TRUE(sig1 == sig2);
|
||||
ASSERT_FALSE(sig1 != sig2);
|
||||
|
||||
sig1.connect<S, &S::f>(s1);
|
||||
sig1.connect<S, &S::g>(s1);
|
||||
sig2.connect<S, &S::f>(s1);
|
||||
sig2.connect<S, &S::g>(s1);
|
||||
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.disconnect<S, &S::f>(s1);
|
||||
sig1.disconnect<S, &S::g>(s1);
|
||||
sig2.disconnect<S, &S::f>(s1);
|
||||
sig2.disconnect<S, &S::g>(s1);
|
||||
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.connect<S, &S::f>(s1);
|
||||
sig1.connect<S, &S::g>(s1);
|
||||
sig2.connect<S, &S::g>(s1);
|
||||
sig2.connect<S, &S::f>(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.connect<&S::f>();
|
||||
signal.sink().connect<&S::f>();
|
||||
|
||||
ASSERT_FALSE(signal.empty());
|
||||
|
||||
signal.clear();
|
||||
signal.sink().disconnect();
|
||||
|
||||
ASSERT_TRUE(signal.empty());
|
||||
}
|
||||
@@ -95,7 +95,7 @@ TEST(Signal, Swap) {
|
||||
entt::Signal<void(const int &)> sig1;
|
||||
entt::Signal<void(const int &)> sig2;
|
||||
|
||||
sig1.connect<&S::f>();
|
||||
sig1.sink().connect<&S::f>();
|
||||
|
||||
ASSERT_FALSE(sig1.empty());
|
||||
ASSERT_TRUE(sig2.empty());
|
||||
@@ -108,72 +108,72 @@ TEST(Signal, Swap) {
|
||||
|
||||
TEST(Signal, Functions) {
|
||||
entt::Signal<void(const int &)> signal;
|
||||
auto val = S::i + 1;
|
||||
auto val = (S::k = 0) + 1;
|
||||
|
||||
signal.connect<&S::f>();
|
||||
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::i, val);
|
||||
ASSERT_EQ(S::k, val);
|
||||
|
||||
signal.disconnect<&S::f>();
|
||||
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::i, val);
|
||||
ASSERT_EQ(S::k, val);
|
||||
}
|
||||
|
||||
TEST(Signal, Members) {
|
||||
entt::Signal<void(const int &)> signal;
|
||||
auto ptr = std::make_shared<S>();
|
||||
auto val = S::i + 1;
|
||||
auto val = (S::k = 0) + 1;
|
||||
|
||||
signal.connect<S, &S::g>(ptr);
|
||||
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::i, val);
|
||||
ASSERT_EQ(S::k, val);
|
||||
|
||||
signal.disconnect<S, &S::g>(ptr);
|
||||
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::i, val);
|
||||
ASSERT_EQ(S::k, val);
|
||||
|
||||
++val;
|
||||
|
||||
signal.connect<S, &S::g>(ptr);
|
||||
signal.connect<S, &S::h>(ptr);
|
||||
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::i, val);
|
||||
ASSERT_EQ(S::k, val);
|
||||
|
||||
signal.disconnect(ptr);
|
||||
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::i, val);
|
||||
ASSERT_EQ(S::k, val);
|
||||
}
|
||||
|
||||
TEST(Signal, Cleanup) {
|
||||
entt::Signal<void(const int &)> signal;
|
||||
auto ptr = std::make_shared<S>();
|
||||
signal.connect<S, &S::g>(ptr);
|
||||
auto val = S::i;
|
||||
signal.sink().connect<S, &S::i>(ptr);
|
||||
auto val = (S::k = 0);
|
||||
ptr = nullptr;
|
||||
|
||||
ASSERT_FALSE(signal.empty());
|
||||
ASSERT_EQ(S::i, val);
|
||||
ASSERT_EQ(S::k, val);
|
||||
|
||||
signal.publish(val);
|
||||
|
||||
ASSERT_TRUE(signal.empty());
|
||||
ASSERT_EQ(S::i, val);
|
||||
ASSERT_EQ(S::k, val);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user