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 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()); + 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...); } } diff --git a/src/entt/signal/signal.hpp b/src/entt/signal/signal.hpp index 4628f2179..26e25bf22 100644 --- a/src/entt/signal/signal.hpp +++ b/src/entt/signal/signal.hpp @@ -40,25 +40,6 @@ class Signal final { using proto_type = bool(*)(std::weak_ptr &, Args...); using call_type = std::pair, proto_type>; - template - static bool proto(std::weak_ptr &, Args... args) { - Function(args...); - return true; - } - - template - static bool proto(std::weak_ptr &wptr, Args... args) { - bool ret = false; - - if(!wptr.expired()) { - auto ptr = std::static_pointer_cast(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 using instance_type = std::shared_ptr; + /** + * @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 Signal; + + template + static bool proto(std::weak_ptr &, Args... args) { + Function(args...); + return true; + } + + template + static bool proto(std::weak_ptr &wptr, Args... args) { + bool ret = false; + + if(!wptr.expired()) { + auto ptr = std::static_pointer_cast(wptr.lock()); + (ptr.get()->*Member)(args...); + ret = true; + } + + return ret; + } + + 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(std::weak_ptr{}, &proto); + } + + /** + * @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 + void connect(instance_type instance) { + disconnect(instance); + calls.emplace_back(std::move(instance), &proto); + } + + /** + * @brief Disconnects a free function from a signal. + * @tparam Function A valid free function pointer. + */ + template + void disconnect() { + calls.erase(std::remove_if(calls.begin(), calls.end(), + [](const call_type &call) { return call.second == &proto && !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 + void disconnect(instance_type instance) { + calls.erase(std::remove_if(calls.begin(), calls.end(), + [instance{std::move(instance)}](const call_type &call) { return call.second == &proto && 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 + void disconnect(instance_type 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 &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.
+ * 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(std::weak_ptr{}, &proto); - } - - /** - * @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 - void connect(instance_type instance) { - disconnect(instance); - calls.emplace_back(std::move(instance), &proto); - } - - /** - * @brief Disconnects a free function from a signal. - * @tparam Function A valid free function pointer. - */ - template - void disconnect() { - calls.erase(std::remove_if(calls.begin(), calls.end(), - [](const call_type &call) { return call.second == &proto && !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 - void disconnect(instance_type instance) { - calls.erase(std::remove_if(calls.begin(), calls.end(), - [instance{std::move(instance)}](const call_type &call) { return call.second == &proto && 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 - void disconnect(instance_type 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 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); } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 04d3c438f..bd6ce1cf5 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -117,7 +117,6 @@ add_test(NAME resource COMMAND resource) add_executable( signal $ - entt/signal/bus.cpp entt/signal/delegate.cpp entt/signal/dispatcher.cpp entt/signal/emitter.cpp diff --git a/test/entt/entity/registry.cpp b/test/entt/entity/registry.cpp index 0da64a446..b58eaa483 100644 --- a/test/entt/entity/registry.cpp +++ b/test/entt/entity/registry.cpp @@ -6,6 +6,21 @@ #include #include +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().begin(), dst.view().end()); ne(dst.view().begin(), dst.view().end()); } + +TEST(DefaultRegistry, Signals) { + entt::DefaultRegistry registry; + Listener listener; + + registry.construction().connect(&listener); + registry.destruction().connect(&listener); + + auto e0 = registry.create(); + auto e1 = registry.create(); + + registry.assign(e0); + registry.assign(e1); + + ASSERT_EQ(listener.counter, 2); + ASSERT_EQ(listener.last, e1); + + registry.remove(e0); + + ASSERT_EQ(listener.counter, 1); + ASSERT_EQ(listener.last, e0); + + registry.destruction().disconnect(&listener); + registry.remove(e1); + + ASSERT_EQ(listener.counter, 1); + ASSERT_EQ(listener.last, e0); + + registry.construction().disconnect(&listener); + registry.assign(e0); + registry.assign(e1); + + ASSERT_EQ(listener.counter, 1); + ASSERT_EQ(listener.last, e0); +} diff --git a/test/entt/signal/bus.cpp b/test/entt/signal/bus.cpp deleted file mode 100644 index 75e35d894..000000000 --- a/test/entt/signal/bus.cpp +++ /dev/null @@ -1,141 +0,0 @@ -#include -#include -#include - -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 -void testRegUnregEmit(Listener listener) { - Bus bus; - - listener->reset(); - bus.template publish(40, 2); - bus.template publish(); - bus.template publish(); - - 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(); - - listener->reset(); - bus.template publish(40, 2); - bus.template publish(); - bus.template publish(); - - 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(40, 2); - bus.template publish(); - bus.template publish(); - - 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(); - - listener->reset(); - bus.template publish(40, 2); - bus.template publish(); - bus.template publish(); - - 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; - testRegUnregEmit(std::make_shared()); -} - -TEST(ManagedBus, ExpiredListeners) { - entt::ManagedBus bus; - auto listener = std::make_shared(); - - listener->reset(); - bus.reg(listener); - bus.template publish(40, 2); - bus.template publish(); - - 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(40, 2)); - EXPECT_NO_THROW(bus.template publish()); - - ASSERT_EQ(bus.size(), (decltype(bus.size()))0); - ASSERT_TRUE(bus.empty()); -} - -TEST(UnmanagedBus, RegUnregEmit) { - using MyUnmanagedBus = entt::UnmanagedBus; - auto ptr = std::make_unique(); - testRegUnregEmit(ptr.get()); -} - -TEST(UnmanagedBus, ExpiredListeners) { - entt::UnmanagedBus bus; - auto listener = std::make_unique(); - - listener->reset(); - bus.reg(listener.get()); - bus.template publish(40, 2); - bus.template publish(); - - 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()); -} diff --git a/test/entt/signal/dispatcher.cpp b/test/entt/signal/dispatcher.cpp index fd511844f..058d442b0 100644 --- a/test/entt/signal/dispatcher.cpp +++ b/test/entt/signal/dispatcher.cpp @@ -14,7 +14,7 @@ template void testDispatcher(Rec receiver) { Dispatcher dispatcher; - dispatcher.template connect(receiver); + dispatcher.template sink().connect(receiver); dispatcher.template trigger(); dispatcher.template enqueue(); @@ -28,7 +28,7 @@ void testDispatcher(Rec receiver) { receiver->reset(); - dispatcher.template disconnect(receiver); + dispatcher.template sink().disconnect(receiver); dispatcher.template trigger(); dispatcher.template enqueue(); dispatcher.update(); diff --git a/test/entt/signal/sigh.cpp b/test/entt/signal/sigh.cpp index de6a1baf5..4cec50ea8 100644 --- a/test/entt/signal/sigh.cpp +++ b/test/entt/signal/sigh.cpp @@ -3,163 +3,18 @@ #include #include -TEST(SigH, Lifetime) { - using signal = entt::SigH; - - 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 sig1; - entt::SigH sig2; - - S s1; - S s2; - - sig1.connect(&s1); - sig2.connect(&s2); - - ASSERT_FALSE(sig1 == sig2); - ASSERT_TRUE(sig1 != sig2); - - sig1.disconnect(&s1); - sig2.disconnect(&s2); - - sig1.connect(&s1); - sig2.connect(&s1); - - ASSERT_FALSE(sig1 == sig2); - ASSERT_TRUE(sig1 != sig2); - - sig1.disconnect(&s1); - sig2.disconnect(&s1); - - ASSERT_TRUE(sig1 == sig2); - ASSERT_FALSE(sig1 != sig2); - - sig1.connect(&s1); - sig1.connect(&s1); - sig2.connect(&s1); - sig2.connect(&s1); - - ASSERT_TRUE(sig1 == sig2); - - sig1.disconnect(&s1); - sig1.disconnect(&s1); - sig2.disconnect(&s1); - sig2.disconnect(&s1); - - sig1.connect(&s1); - sig1.connect(&s1); - sig2.connect(&s1); - sig2.connect(&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 sigh; - sigh.connect<&S::f>(); - - ASSERT_FALSE(sigh.empty()); - - sigh.clear(); - - ASSERT_TRUE(sigh.empty()); -} - -TEST(SigH, Swap) { - entt::SigH sigh1; - entt::SigH 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 sigh; - int v = 0; - - sigh.connect<&S::f>(); - sigh.publish(v); - - ASSERT_FALSE(sigh.empty()); - ASSERT_EQ((entt::SigH::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::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 sigh; - - sigh.connect(ptr); - sigh.publish(42); - - ASSERT_TRUE(s.b); - ASSERT_FALSE(sigh.empty()); - ASSERT_EQ((entt::SigH::size_type)1, sigh.size()); - - sigh.disconnect(ptr); - sigh.publish(42); - - ASSERT_TRUE(s.b); - ASSERT_TRUE(sigh.empty()); - ASSERT_EQ((entt::SigH::size_type)0, sigh.size()); - - sigh.connect(ptr); - sigh.connect(ptr); - - ASSERT_FALSE(sigh.empty()); - ASSERT_EQ((entt::SigH::size_type)2, sigh.size()); - - sigh.disconnect(ptr); - - ASSERT_TRUE(sigh.empty()); - ASSERT_EQ((entt::SigH::size_type)0, sigh.size()); -} - template struct TestCollectAll { std::vector vec{}; @@ -190,10 +45,152 @@ struct TestCollectFirst { } }; +TEST(SigH, Lifetime) { + using signal = entt::SigH; + + 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 sig1; + entt::SigH sig2; + + S s1; + S s2; + + sig1.sink().connect(&s1); + sig2.sink().connect(&s2); + + ASSERT_FALSE(sig1 == sig2); + ASSERT_TRUE(sig1 != sig2); + + sig1.sink().disconnect(&s1); + sig2.sink().disconnect(&s2); + + sig1.sink().connect(&s1); + sig2.sink().connect(&s1); + + ASSERT_FALSE(sig1 == sig2); + ASSERT_TRUE(sig1 != sig2); + + sig1.sink().disconnect(&s1); + sig2.sink().disconnect(&s1); + + ASSERT_TRUE(sig1 == sig2); + ASSERT_FALSE(sig1 != sig2); + + sig1.sink().connect(&s1); + sig1.sink().connect(&s1); + sig2.sink().connect(&s1); + sig2.sink().connect(&s1); + + ASSERT_TRUE(sig1 == sig2); + + sig1.sink().disconnect(&s1); + sig1.sink().disconnect(&s1); + sig2.sink().disconnect(&s1); + sig2.sink().disconnect(&s1); + + sig1.sink().connect(&s1); + sig1.sink().connect(&s1); + sig2.sink().connect(&s1); + sig2.sink().connect(&s1); + + ASSERT_FALSE(sig1 == sig2); +} + +TEST(SigH, Clear) { + entt::SigH sigh; + sigh.sink().connect<&S::f>(); + + ASSERT_FALSE(sigh.empty()); + + sigh.sink().disconnect(); + + ASSERT_TRUE(sigh.empty()); +} + +TEST(SigH, Swap) { + entt::SigH sigh1; + entt::SigH 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 sigh; + int v = 0; + + sigh.sink().connect<&S::f>(); + sigh.publish(v); + + ASSERT_FALSE(sigh.empty()); + ASSERT_EQ((entt::SigH::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::size_type)0, sigh.size()); + ASSERT_EQ(0, v); + + sigh.sink().connect<&S::f>(); +} + +TEST(SigH, Members) { + S s; + S *ptr = &s; + entt::SigH sigh; + + sigh.sink().connect(ptr); + sigh.publish(42); + + ASSERT_TRUE(s.k); + ASSERT_FALSE(sigh.empty()); + ASSERT_EQ((entt::SigH::size_type)1, sigh.size()); + + sigh.sink().disconnect(ptr); + sigh.publish(42); + + ASSERT_TRUE(s.k); + ASSERT_TRUE(sigh.empty()); + ASSERT_EQ((entt::SigH::size_type)0, sigh.size()); + + sigh.sink().connect(ptr); + sigh.sink().connect(ptr); + + ASSERT_FALSE(sigh.empty()); + ASSERT_EQ((entt::SigH::size_type)2, sigh.size()); + + sigh.sink().disconnect(ptr); + + ASSERT_TRUE(sigh.empty()); + ASSERT_EQ((entt::SigH::size_type)0, sigh.size()); +} + TEST(SigH, Collector) { entt::SigH> sigh_void; - sigh_void.connect<&TestCollectAll::h>(); + sigh_void.sink().connect<&TestCollectAll::h>(); auto collector_void = sigh_void.collect(); ASSERT_FALSE(sigh_void.empty()); @@ -201,9 +198,9 @@ TEST(SigH, Collector) { entt::SigH> sigh_all; - sigh_all.connect<&TestCollectAll::f>(); - sigh_all.connect<&TestCollectAll::f>(); - sigh_all.connect<&TestCollectAll::g>(); + sigh_all.sink().connect<&TestCollectAll::f>(); + sigh_all.sink().connect<&TestCollectAll::f>(); + sigh_all.sink().connect<&TestCollectAll::g>(); auto collector_all = sigh_all.collect(); ASSERT_FALSE(sigh_all.empty()); @@ -214,8 +211,8 @@ TEST(SigH, Collector) { entt::SigH> sigh_first; - sigh_first.connect<&TestCollectFirst::f>(); - sigh_first.connect<&TestCollectFirst::f>(); + sigh_first.sink().connect<&TestCollectFirst::f>(); + sigh_first.sink().connect<&TestCollectFirst::f>(); auto collector_first = sigh_first.collect(); ASSERT_FALSE(sigh_first.empty()); diff --git a/test/entt/signal/signal.cpp b/test/entt/signal/signal.cpp index d5da089fc..b32a65da9 100644 --- a/test/entt/signal/signal.cpp +++ b/test/entt/signal/signal.cpp @@ -4,13 +4,18 @@ #include 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; @@ -28,65 +33,60 @@ TEST(Signal, Lifetime) { } TEST(Signal, Comparison) { - struct S { - void f() {} - void g() {} - }; - entt::Signal sig1; entt::Signal sig2; auto s1 = std::make_shared(); auto s2 = std::make_shared(); - sig1.connect(s1); - sig2.connect(s2); + sig1.sink().connect(s1); + sig2.sink().connect(s2); ASSERT_FALSE(sig1 == sig2); ASSERT_TRUE(sig1 != sig2); - sig1.disconnect(s1); - sig2.disconnect(s2); + sig1.sink().disconnect(s1); + sig2.sink().disconnect(s2); - sig1.connect(s1); - sig2.connect(s1); + sig1.sink().connect(s1); + sig2.sink().connect(s1); ASSERT_FALSE(sig1 == sig2); ASSERT_TRUE(sig1 != sig2); - sig1.disconnect(s1); - sig2.disconnect(s1); + sig1.sink().disconnect(s1); + sig2.sink().disconnect(s1); ASSERT_TRUE(sig1 == sig2); ASSERT_FALSE(sig1 != sig2); - sig1.connect(s1); - sig1.connect(s1); - sig2.connect(s1); - sig2.connect(s1); + sig1.sink().connect(s1); + sig1.sink().connect(s1); + sig2.sink().connect(s1); + sig2.sink().connect(s1); ASSERT_TRUE(sig1 == sig2); - sig1.disconnect(s1); - sig1.disconnect(s1); - sig2.disconnect(s1); - sig2.disconnect(s1); + sig1.sink().disconnect(s1); + sig1.sink().disconnect(s1); + sig2.sink().disconnect(s1); + sig2.sink().disconnect(s1); - sig1.connect(s1); - sig1.connect(s1); - sig2.connect(s1); - sig2.connect(s1); + sig1.sink().connect(s1); + sig1.sink().connect(s1); + sig2.sink().connect(s1); + sig2.sink().connect(s1); ASSERT_FALSE(sig1 == sig2); } TEST(Signal, Clear) { entt::Signal 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 sig1; entt::Signal 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 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::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::size_type{0}, signal.size()); - ASSERT_EQ(S::i, val); + ASSERT_EQ(S::k, val); } TEST(Signal, Members) { entt::Signal signal; auto ptr = std::make_shared(); - auto val = S::i + 1; + auto val = (S::k = 0) + 1; - signal.connect(ptr); + signal.sink().connect(ptr); signal.publish(val); ASSERT_FALSE(signal.empty()); ASSERT_EQ(entt::Signal::size_type{1}, 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::size_type{0}, signal.size()); - ASSERT_EQ(S::i, val); + ASSERT_EQ(S::k, val); ++val; - signal.connect(ptr); - signal.connect(ptr); + signal.sink().connect(ptr); + signal.sink().connect(ptr); signal.publish(val); ASSERT_FALSE(signal.empty()); ASSERT_EQ(entt::Signal::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::size_type{0}, signal.size()); - ASSERT_EQ(S::i, val); + ASSERT_EQ(S::k, val); } TEST(Signal, Cleanup) { entt::Signal signal; auto ptr = std::make_shared(); - signal.connect(ptr); - auto val = S::i; + signal.sink().connect(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); }