diff --git a/README.md b/README.md
index 2572bf647..a95f3e798 100644
--- a/README.md
+++ b/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
Half of the entities have all the components | 0.0009s | **3.8e-07s** |
@@ -528,6 +528,47 @@ std::tuple tup = registry.get(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.
+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().connect<&MyFreeFunction>();
+
+// connects a member function
+registry.construction().connect(&instance);
+
+// disconnects a free function
+registry.construction().disconnect<&MyFreeFunction>();
+
+// disconnects a member function
+registry.construction().disconnect(&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);
+```
+
+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_.
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.
+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 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();
-signal.connect<&foo>();
-signal.connect(instance);
+signal.sink().connect<&foo>();
+signal.sink().connect(instance);
// ...
-signal.disconnect<&foo>();
+// disconnects a free function
+signal.sink().disconnect<&foo>();
-// disconnect a specific member function of an instance ...
-signal.disconnect(instance);
+// disconnects a specific member function of an instance ...
+signal.sink().disconnect(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.
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`).
+(`size`) or if it contains at least a listener (`empty`) and even to swap two
+handlers (`swap`).
Refer to the [official documentation](https://skypjack.github.io/entt/) for all
the details.
@@ -2036,11 +2095,10 @@ entt::SigH> 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(&instance);
+signal.sink().connect<&foo>();
+signal.sink().connect(&instance);
// ...
-signal.disconnect<&foo>();
+// disconnects a free function
+signal.sink().disconnect<&foo>();
// disconnect a specific member function of an instance ...
-signal.disconnect(&instance);
+signal.sink().disconnect(&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> 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.
-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 managed;
-
-// define an unmanaged bus that works with naked pointers
-entt::UnmanagedBus 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`.
-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();
-bus.connect();
-
-// ...
-
-bus.disconnect();
-bus.disconnect();
-```
-
-Whenever the need to send an event arises, it can be done through the `publish`
-member function:
-
-```cpp
-bus.publish(42);
-bus.publish();
-```
-
-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`.
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(&listener);
-dispatcher.connect(&listener);
+dispatcher.sink().connect(&listener);
+dispatcher.sink().connect(&listener);
```
The `disconnect` member function follows the same pattern and can be used to
selectively remove listeners:
```cpp
-dispatcher.disconnect(&listener);
-dispatcher.disconnect(&listener);
+dispatcher.sink().disconnect(&listener);
+dispatcher.sink().disconnect(&listener);
```
The `trigger` member function serves the purpose of sending an immediate event
diff --git a/TODO b/TODO
index c7bd3700b..8848d63bb 100644
--- a/TODO
+++ b/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
diff --git a/src/entt/core/ident.hpp b/src/entt/core/ident.hpp
index 51298ef0c..87330a206 100644
--- a/src/entt/core/ident.hpp
+++ b/src/entt/core/ident.hpp
@@ -10,7 +10,7 @@
namespace entt {
-namespace {
+namespace details {
template
@@ -87,7 +87,7 @@ private:
* @tparam Types List of types for which to generate identifiers.
*/
template
-constexpr auto ident = Identifier...>{std::make_index_sequence{}};
+constexpr auto ident = details::Identifier...>{std::make_index_sequence{}};
}
diff --git a/src/entt/entity/registry.hpp b/src/entt/entity/registry.hpp
index 07732dfaa..9aa7aa8a7 100644
--- a/src/entt/entity/registry.hpp
+++ b/src/entt/entity/registry.hpp
@@ -12,6 +12,7 @@
#include
#include
#include "../core/family.hpp"
+#include "../signal/sigh.hpp"
#include "entt_traits.hpp"
#include "snapshot.hpp"
#include "sparse_set.hpp"
@@ -35,9 +36,27 @@ template
class Registry {
using tag_family = Family;
using component_family = Family;
- using view_family = Family;
+ using handler_family = Family;
using traits_type = entt_traits;
+ template
+ static void creating(Registry ®istry, Entity entity) {
+ if(registry.has(entity)) {
+ const auto htype = handler_family::type();
+ registry.handlers[htype]->construct(entity);
+ }
+ }
+
+ template
+ static void destroying(Registry ®istry, Entity entity) {
+ const auto htype = handler_family::type();
+ auto &handler = registry.handlers[htype];
+
+ if(handler->has(entity)) {
+ handler->destroy(entity);
+ }
+ }
+
struct Attachee {
Entity entity;
};
@@ -55,45 +74,38 @@ class Registry {
template
struct Pool: SparseSet {
- using test_fn_type = bool(Registry::*)(Entity) const;
+ using sink_type = typename SigH::Sink;
+
+ Pool(Registry ®istry)
+ : registry{registry}
+ {}
template
- Component & construct(Registry ®istry, Entity entity, Args &&... args) {
+ Component & construct(Entity entity, Args &&... args) {
auto &component = SparseSet::construct(entity, std::forward(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::destroy(entity);
-
- for(auto &&listener: listeners) {
- auto *handler = listener.first;
-
- if(handler->has(entity)) {
- handler->destroy(entity);
- }
- }
}
- inline void append(SparseSet *handler, test_fn_type fn) {
- listeners.emplace_back(handler, fn);
+ sink_type construction() noexcept {
+ return listeners.first.sink();
}
- inline void remove(SparseSet *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 *, test_fn_type>> listeners;
+ Registry ®istry;
+ std::pair<
+ SigH,
+ SigH
+ > listeners;
};
template
@@ -122,40 +134,12 @@ class Registry {
}
if(!pools[ctype]) {
- pools[ctype] = std::make_unique>();
+ pools[ctype] = std::make_unique>(*this);
}
return pool();
}
- template
- SparseSet & handler() {
- static_assert(sizeof...(Component) > 1, "!");
- const auto vtype = view_family::type();
-
- if(!(vtype < handlers.size())) {
- handlers.resize(vtype + 1);
- }
-
- if(!handlers[vtype]) {
- using accumulator_type = int[];
- auto set = std::make_unique>();
-
- for(auto entity: view()) {
- set->construct(entity);
- }
-
- accumulator_type accumulator = {
- (assure().append(set.get(), &Registry::has), 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
+ using sink_type = typename Pool::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>().construct(*this, entity, std::forward(components)), 0)... };
+ accumulator_type accumulator = { 0, (assure>().construct(entity, std::forward(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().construct(*this, entity), 0)... };
+ accumulator_type accumulator = { 0, (assure().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
Component & assign(entity_type entity, Args &&... args) {
assert(valid(entity));
- return assure().construct(*this, entity, std::forward(args)...);
+ return assure().construct(entity, std::forward(args)...);
}
/**
@@ -845,7 +832,59 @@ public:
return (cpool.has(entity)
? (cpool.get(entity) = Component{std::forward(args)...})
- : cpool.construct(*this, entity, std::forward(args)...));
+ : cpool.construct(entity, std::forward(args)...));
+ }
+
+ /**
+ * @brief Returns a sink object for the given component.
+ *
+ * A sink is an opaque object used to connect listeners to components.
+ * 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);
+ * @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
+ sink_type construction() noexcept {
+ return assure().construction();
+ }
+
+ /**
+ * @brief Returns a sink object for the given component.
+ *
+ * A sink is an opaque object used to connect listeners to components.
+ * 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);
+ * @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
+ sink_type destruction() noexcept {
+ return assure().destruction();
}
/**
@@ -1119,7 +1158,30 @@ public:
*/
template
void prepare() {
- handler();
+ static_assert(sizeof...(Component) > 1, "!");
+ const auto htype = handler_family::type();
+
+ if(!(htype < handlers.size())) {
+ handlers.resize(htype + 1);
+ }
+
+ if(!handlers[htype]) {
+ using accumulator_type = int[];
+ handlers[htype] = std::make_unique>();
+ auto &handler = handlers[htype];
+
+ for(auto entity: view()) {
+ handler->construct(entity);
+ }
+
+ auto connect = [](auto &pool) {
+ pool.construction().template connect<&Registry::creating>();
+ pool.destruction().template connect<&Registry::destroying>();
+ };
+
+ accumulator_type accumulator = { (connect(assure()), 0)... };
+ (void)accumulator;
+ }
}
/**
@@ -1141,11 +1203,16 @@ public:
void discard() {
if(contains()) {
using accumulator_type = int[];
- const auto vtype = view_family::type();
- auto *set = handlers[vtype].get();
+ const auto htype = handler_family::type();
+
+ auto disconnect = [](auto &pool) {
+ pool.construction().template disconnect<&Registry::creating>();
+ pool.destruction().template disconnect<&Registry::destroying>();
+ };
+
// if a set exists, pools have already been created for it
- accumulator_type accumulator = { (pool().remove(set), 0)... };
- handlers[vtype].reset();
+ accumulator_type accumulator = { (disconnect(pool()), 0)... };
+ handlers[htype].reset();
(void)accumulator;
}
}
@@ -1158,8 +1225,8 @@ public:
template
bool contains() const noexcept {
static_assert(sizeof...(Component) > 1, "!");
- const auto vtype = view_family::type();
- return vtype < handlers.size() && handlers[vtype];
+ const auto htype = handler_family::type();
+ return (htype < handlers.size() && handlers[htype]);
}
/**
@@ -1202,8 +1269,9 @@ public:
*/
template
PersistentView persistent() {
- // after the calls to handler, pools have already been created
- return PersistentView{handler(), pool()...};
+ prepare();
+ const auto htype = handler_family::type();
+ return PersistentView{*handlers[htype], pool()...};
}
/**
diff --git a/src/entt/entt.hpp b/src/entt/entt.hpp
index 6e431402a..ca0547686 100644
--- a/src/entt/entt.hpp
+++ b/src/entt/entt.hpp
@@ -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"
diff --git a/src/entt/signal/bus.hpp b/src/entt/signal/bus.hpp
deleted file mode 100644
index d6287cfc3..000000000
--- a/src/entt/signal/bus.hpp
+++ /dev/null
@@ -1,305 +0,0 @@
-#ifndef ENTT_SIGNAL_BUS_HPP
-#define ENTT_SIGNAL_BUS_HPP
-
-
-#include
-#include
-#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 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.
- * 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 class Sig, typename Event, typename... Other>
-class Bus
- : private Bus, private Bus...
-{
-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.
- * 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
- void unreg(Instance instance) {
- using accumulator_type = int[];
- accumulator_type accumulator = {
- (Bus::unreg(instance), 0),
- (Bus::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.
- * 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
- void reg(Instance instance) {
- using accumulator_type = int[];
- accumulator_type accumulator = {
- (Bus::reg(instance), 0),
- (Bus::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::size();
- accumulator_type accumulator = { sz, (sz += Bus::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::empty();
- accumulator_type accumulator = { ret, (ret = ret && Bus::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
- void connect() {
- Bus::template connect();
- }
-
- /**
- * @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
- void disconnect() {
- Bus::template disconnect();
- }
-
- /**
- * @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
- void publish(Args &&... args) {
- Bus::publish(std::forward(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.
- * 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 class Sig, typename Event>
-class Bus {
- using signal_type = Sig;
-
- template
- using instance_type = typename signal_type::template instance_type;
-
- template
- auto disconnect(int, instance_type instance)
- -> decltype(std::declval().receive(std::declval()), void()) {
- signal.template disconnect(std::move(instance));
- }
-
- template
- auto connect(int, instance_type instance)
- -> decltype(std::declval().receive(std::declval()), void()) {
- signal.template connect(std::move(instance));
- }
-
- template void disconnect(char, instance_type) {}
- template void connect(char, instance_type) {}
-
-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.
- * 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
- void unreg(instance_type 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.
- * 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
- void reg(instance_type 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 connect() {
- signal.template connect();
- }
-
- /**
- * @brief Disconnects a free function from the bus.
- * @tparam Function A valid free function pointer.
- */
- template
- void disconnect() {
- signal.template disconnect();
- }
-
- /**
- * @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
- void publish(Args &&... args) {
- signal.publish({ std::forward(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` (a shared pointer).
- *
- * @tparam Event The list of events managed by the bus.
- */
-template
-using ManagedBus = Bus;
-
-/**
- * @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).
- * 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
-using UnmanagedBus = Bus;
-
-
-}
-
-
-#endif // ENTT_SIGNAL_BUS_HPP
diff --git a/src/entt/signal/dispatcher.hpp b/src/entt/signal/dispatcher.hpp
index 1c6f8abad..f013278f5 100644
--- a/src/entt/signal/dispatcher.hpp
+++ b/src/entt/signal/dispatcher.hpp
@@ -40,6 +40,8 @@ class Dispatcher final {
template
struct SignalWrapper final: BaseSignalWrapper {
+ using sink_type = typename Sig::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
- inline void connect(instance_type instance) noexcept {
- signal.template connect(std::move(instance));
- }
-
- template
- inline void disconnect(instance_type instance) noexcept {
- signal.template disconnect(std::move(instance));
+ inline sink_type sink() noexcept {
+ return signal.sink();
}
template
@@ -93,52 +89,36 @@ class Dispatcher final {
}
public:
+ /*! @brief Type of sink for the given event. */
+ template
+ using sink_type = typename SignalWrapper::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
- void connect(instance_type instance) noexcept {
- wrapper().template connect(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
- void disconnect(instance_type instance) noexcept {
- wrapper().template disconnect(std::move(instance));
+ template
+ sink_type sink() noexcept {
+ return wrapper().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);
}
diff --git a/src/entt/signal/emitter.hpp b/src/entt/signal/emitter.hpp
index 18e0e0e78..451917547 100644
--- a/src/entt/signal/emitter.hpp
+++ b/src/entt/signal/emitter.hpp
@@ -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
using Listener = typename Handler::listener_type;
diff --git a/src/entt/signal/sigh.hpp b/src/entt/signal/sigh.hpp
index bef2d7ef1..62753e7fd 100644
--- a/src/entt/signal/sigh.hpp
+++ b/src/entt/signal/sigh.hpp
@@ -10,7 +10,7 @@
namespace entt {
-namespace {
+namespace details {
template
@@ -83,7 +83,7 @@ using DefaultCollectorType = typename DefaultCollector::collector_type
* @tparam Function A valid function type.
* @tparam Collector Type of collector to use, if any.
*/
-template>
+template>
class SigH;
@@ -111,18 +111,8 @@ class SigH;
* @tparam Collector Type of collector to use, if any.
*/
template
-class SigH final: private Invoker {
- using typename Invoker::call_type;
-
- template
- static Ret proto(void *, Args... args) {
- return (Function)(args...);
- }
-
- template
- static Ret proto(void *instance, Args... args) {
- return (static_cast(instance)->*Member)(args...);
- }
+class SigH final: private details::Invoker {
+ using call_type = typename details::Invoker::call_type;
public:
/*! @brief Unsigned integer type. */
@@ -137,6 +127,112 @@ public:
template
using instance_type = Class *;
+ /**
+ * @brief Sink implementation.
+ *
+ * A sink is an opaque object used to connect listeners to signals.
+ * 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
+ static Ret proto(void *, Args... args) {
+ return (Function)(args...);
+ }
+
+ template
+ static Ret proto(void *instance, Args... args) {
+ return (static_cast(instance)->*Member)(args...);
+ }
+
+ Sink(std::vector &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 connect() {
+ disconnect();
+ calls.emplace_back(nullptr, &proto);
+ }
+
+ /**
+ * @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
+ void connect(instance_type instance) {
+ disconnect(instance);
+ calls.emplace_back(instance, &proto);
+ }
+
+ /**
+ * @brief Disconnects a free function from a signal.
+ * @tparam Function A valid free function pointer.
+ */
+ template
+ void disconnect() {
+ call_type target{nullptr, &proto};
+ 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
+ void disconnect(instance_type instance) {
+ call_type target{instance, &proto};
+ 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
+ void disconnect(instance_type 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 &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.
+ * 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 connect() {
- disconnect();
- calls.emplace_back(nullptr, &proto);
- }
-
- /**
- * @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
- void connect(instance_type