signals on component creation/destruction (#62)

This commit is contained in:
Michele Caini
2018-04-06 22:04:58 +02:00
parent d417984ff3
commit e99d7e2c3c
16 changed files with 763 additions and 1067 deletions

204
README.md
View File

@@ -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
View File

@@ -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

View File

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

View File

@@ -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 &registry, 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 &registry, 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 &registry)
: registry{registry}
{}
template<typename... Args>
Component & construct(Registry &registry, 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 &registry;
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>()...};
}
/**

View File

@@ -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"

View File

@@ -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

View File

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

View File

@@ -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;

View File

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

View File

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

View File

@@ -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

View File

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

View File

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

View File

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

View File

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

View File

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