emitter: full review

This commit is contained in:
Michele Caini
2022-04-12 11:15:22 +02:00
parent 88929ab730
commit 36af39e2b4
7 changed files with 140 additions and 315 deletions

2
TODO
View File

@@ -7,9 +7,9 @@ EXAMPLES
* support to polymorphic types (see #859)
WIP:
* emitter: runtime handlers, allocator support (ready for both already)
* view/group: no storage_traits dependency -> use storage instead of components for the definition
* resource<T>::operator</<=/>/>=
* simplify emitter (see uvw), runtime events
* basic_storage::bind for cross-registry setups
* uses-allocator construction: any (with allocator support), poly, ...
* process scheduler: reviews, use free lists internally

View File

@@ -1,10 +1,7 @@
#ifndef ENTT_SIGNAL_EMITTER_HPP
#define ENTT_SIGNAL_EMITTER_HPP
#include <algorithm>
#include <functional>
#include <iterator>
#include <list>
#include <memory>
#include <type_traits>
#include <utility>
@@ -20,8 +17,7 @@ namespace entt {
/**
* @brief General purpose event emitter.
*
* The emitter class template follows the CRTP idiom. To create a custom emitter
* type, derived classes must inherit directly from the base class as:
* To create an emitter type, derived classes must inherit from the base as:
*
* @code{.cpp}
* struct my_emitter: emitter<my_emitter> {
@@ -29,273 +25,99 @@ namespace entt {
* }
* @endcode
*
* Pools for the type of events are created internally on the fly. It's not
* required to specify in advance the full list of accepted types.<br/>
* Moreover, whenever an event is published, an emitter provides the listeners
* with a reference to itself along with a reference to the event. Therefore
* listeners have an handy way to work with it without incurring in the need of
* capturing a reference to the emitter.
* Handlers for the different events are created internally on the fly. It's not
* required to specify in advance the full list of accepted events.<br/>
* Moreover, whenever an event is published, an emitter also passes a reference
* to itself to its listeners.
*
* @tparam Derived Actual type of emitter that extends the class template.
* @tparam Derived Emitter type.
*/
template<typename Derived>
class emitter {
struct basic_pool {
virtual ~basic_pool() = default;
virtual bool empty() const ENTT_NOEXCEPT = 0;
virtual void clear() ENTT_NOEXCEPT = 0;
};
template<typename Type>
using function_type = std::function<void(Type &, Derived &)>;
template<typename Event>
struct pool_handler final: basic_pool {
static_assert(std::is_same_v<Event, std::decay_t<Event>>, "Invalid event type");
template<typename Type>
[[nodiscard]] function_type<Type> &assure() {
static_assert(std::is_same_v<Type, std::decay_t<Type>>, "Non-decayed types not allowed");
auto &&ptr = handlers[type_hash<Type>::value()];
using listener_type = std::function<void(Event &, Derived &)>;
using element_type = std::pair<bool, listener_type>;
using container_type = std::list<element_type>;
using connection_type = typename container_type::iterator;
[[nodiscard]] bool empty() const ENTT_NOEXCEPT override {
auto pred = [](auto &&element) { return element.first; };
return std::all_of(once_list.cbegin(), once_list.cend(), pred)
&& std::all_of(on_list.cbegin(), on_list.cend(), pred);
if(!ptr) {
ptr = std::make_shared<function_type<Type>>();
}
void clear() ENTT_NOEXCEPT override {
if(publishing) {
for(auto &&element: once_list) {
element.first = true;
}
for(auto &&element: on_list) {
element.first = true;
}
} else {
once_list.clear();
on_list.clear();
}
}
connection_type once(listener_type listener) {
return once_list.emplace(once_list.cend(), false, std::move(listener));
}
connection_type on(listener_type listener) {
return on_list.emplace(on_list.cend(), false, std::move(listener));
}
void erase(connection_type conn) {
conn->first = true;
if(!publishing) {
auto pred = [](auto &&element) { return element.first; };
once_list.remove_if(pred);
on_list.remove_if(pred);
}
}
void publish(Event &event, Derived &ref) {
container_type swap_list;
once_list.swap(swap_list);
publishing = true;
for(auto &&element: on_list) {
element.first ? void() : element.second(event, ref);
}
for(auto &&element: swap_list) {
element.first ? void() : element.second(event, ref);
}
publishing = false;
on_list.remove_if([](auto &&element) { return element.first; });
}
private:
bool publishing{false};
container_type once_list{};
container_type on_list{};
};
template<typename Event>
[[nodiscard]] pool_handler<Event> *assure() {
if(auto &&ptr = pools[type_hash<Event>::value()]; !ptr) {
auto *cpool = new pool_handler<Event>{};
ptr.reset(cpool);
return cpool;
} else {
return static_cast<pool_handler<Event> *>(ptr.get());
}
return *static_cast<function_type<Type> *>(ptr.get());
}
template<typename Event>
[[nodiscard]] const pool_handler<Event> *assure() const {
const auto it = pools.find(type_hash<Event>::value());
return (it == pools.cend()) ? nullptr : static_cast<const pool_handler<Event> *>(it->second.get());
template<typename Type>
[[nodiscard]] const function_type<Type> *assure() const {
const auto it = handlers.find(type_hash<Type>::value());
return (it == handlers.cend()) ? nullptr : static_cast<const function_type<Type> *>(it->second.get());
}
public:
/** @brief Type of listeners accepted for the given event. */
template<typename Event>
using listener = typename pool_handler<Event>::listener_type;
/**
* @brief Generic connection type for events.
*
* Type of the connection object returned by the event emitter whenever a
* listener for the given type is registered.<br/>
* It can be used to break connections still in use.
*
* @tparam Event Type of event for which the connection is created.
*/
template<typename Event>
struct connection: private pool_handler<Event>::connection_type {
/** @brief Event emitters are friend classes of connections. */
friend class emitter;
/*! @brief Default constructor. */
connection() ENTT_NOEXCEPT = default;
/**
* @brief Creates a connection that wraps its underlying instance.
* @param conn A connection object to wrap.
*/
connection(typename pool_handler<Event>::connection_type conn)
: pool_handler<Event>::connection_type{std::move(conn)} {}
};
/*! @brief Default constructor. */
emitter() = default;
emitter()
: handlers{} {}
/*! @brief Default destructor. */
virtual ~emitter() ENTT_NOEXCEPT {
static_assert(std::is_base_of_v<emitter<Derived>, Derived>, "Incorrect use of the class template");
static_assert(std::is_base_of_v<emitter<Derived>, Derived>, "Invalid emitter type");
}
/*! @brief Default move constructor. */
emitter(emitter &&) = default;
/*! @brief Default move assignment operator. @return This emitter. */
/**
* @brief Default move assignment operator.
* @return This emitter.
*/
emitter &operator=(emitter &&) = default;
/**
* @brief Emits the given event.
*
* All the listeners registered for the specific event type are invoked with
* the given event. The event type must either have a proper constructor for
* the arguments provided or be an aggregate type.
*
* @tparam Event Type of event to publish.
* @tparam Args Types of arguments to use to construct the event.
* @param args Parameters to use to initialize the event.
* @brief Publishes a given event.
* @tparam Type Type of event to trigger.
* @param value An instance of the given type of event.
*/
template<typename Event, typename... Args>
void publish(Args &&...args) {
Event instance{std::forward<Args>(args)...};
assure<Event>()->publish(instance, *static_cast<Derived *>(this));
}
/**
* @brief Registers a long-lived listener with the event emitter.
*
* This method can be used to register a listener designed to be invoked
* more than once for the given event type.<br/>
* The connection returned by the method can be freely discarded. It's meant
* to be used later to disconnect the listener if required.
*
* The listener is as a callable object that can be moved and the type of
* which is _compatible_ with `void(Event &, Derived &)`.
*
* @note
* Whenever an event is emitted, the emitter provides the listener with a
* reference to the derived class. Listeners don't have to capture those
* instances for later uses.
*
* @tparam Event Type of event to which to connect the listener.
* @param instance The listener to register.
* @return Connection object that can be used to disconnect the listener.
*/
template<typename Event>
connection<Event> on(listener<Event> instance) {
return assure<Event>()->on(std::move(instance));
}
/**
* @brief Registers a short-lived listener with the event emitter.
*
* This method can be used to register a listener designed to be invoked
* only once for the given event type.<br/>
* The connection returned by the method can be freely discarded. It's meant
* to be used later to disconnect the listener if required.
*
* The listener is as a callable object that can be moved and the type of
* which is _compatible_ with `void(Event &, Derived &)`.
*
* @note
* Whenever an event is emitted, the emitter provides the listener with a
* reference to the derived class. Listeners don't have to capture those
* instances for later uses.
*
* @tparam Event Type of event to which to connect the listener.
* @param instance The listener to register.
* @return Connection object that can be used to disconnect the listener.
*/
template<typename Event>
connection<Event> once(listener<Event> instance) {
return assure<Event>()->once(std::move(instance));
}
/**
* @brief Disconnects a listener from the event emitter.
*
* Do not use twice the same connection to disconnect a listener, it results
* in undefined behavior. Once used, discard the connection object.
*
* @tparam Event Type of event of the connection.
* @param conn A valid connection.
*/
template<typename Event>
void erase(connection<Event> conn) {
assure<Event>()->erase(std::move(conn));
}
/**
* @brief Disconnects all the listeners for the given event type.
*
* All the connections previously returned for the given event are
* invalidated. Using them results in undefined behavior.
*
* @tparam Event Type of event to reset.
*/
template<typename Event>
void clear() {
assure<Event>()->clear();
}
/**
* @brief Disconnects all the listeners.
*
* All the connections previously returned are invalidated. Using them
* results in undefined behavior.
*/
void clear() ENTT_NOEXCEPT {
for(auto &&cpool: pools) {
cpool.second->clear();
template<typename Type>
void publish(Type &&value) {
if(auto &handler = assure<std::remove_const_t<std::remove_reference_t<Type>>>(); handler) {
handler(value, *static_cast<Derived *>(this));
}
}
/**
* @brief Registers a listener with the event emitter.
* @tparam Type Type of event to which to connect the listener.
* @param func The listener to register.
*/
template<typename Type>
void on(std::function<void(Type &, Derived &)> func) {
assure<Type>() = std::move(func);
}
/**
* @brief Disconnects a listener from the event emitter.
* @tparam Type Type of event of the listener.
*/
template<typename Type>
void erase() {
handlers.erase(type_hash<std::remove_const_t<std::remove_reference_t<Type>>>::value());
}
/*! @brief Disconnects all the listeners. */
void clear() ENTT_NOEXCEPT {
handlers.clear();
}
/**
* @brief Checks if there are listeners registered for the specific event.
* @tparam Event Type of event to test.
* @tparam Type Type of event to test.
* @return True if there are no listeners registered, false otherwise.
*/
template<typename Event>
[[nodiscard]] bool empty() const {
const auto *cpool = assure<Event>();
return !cpool || cpool->empty();
template<typename Type>
[[nodiscard]] bool contains() const {
return handlers.contains(type_hash<std::remove_const_t<std::remove_reference_t<Type>>>::value());
}
/**
@@ -303,13 +125,11 @@ public:
* @return True if there are no listeners registered, false otherwise.
*/
[[nodiscard]] bool empty() const ENTT_NOEXCEPT {
return std::all_of(pools.cbegin(), pools.cend(), [](auto &&cpool) {
return cpool.second->empty();
});
return handlers.empty();
}
private:
dense_map<id_type, std::unique_ptr<basic_pool>, identity> pools{};
dense_map<id_type, std::shared_ptr<void>, identity> handlers{};
};
} // namespace entt

View File

@@ -1,3 +1,5 @@
#include <functional>
#include <utility>
#include <gtest/gtest.h>
#include <entt/signal/emitter.hpp>
@@ -11,123 +13,119 @@ struct foo_event {
struct bar_event {};
struct quux_event {};
TEST(Emitter, Move) {
test_emitter emitter;
emitter.on<foo_event>([](auto &, const auto &) {});
ASSERT_FALSE(emitter.empty());
ASSERT_TRUE(emitter.contains<foo_event>());
test_emitter other{std::move(emitter)};
ASSERT_FALSE(other.empty());
ASSERT_TRUE(other.contains<foo_event>());
ASSERT_TRUE(emitter.empty());
emitter = std::move(other);
ASSERT_FALSE(emitter.empty());
ASSERT_TRUE(emitter.contains<foo_event>());
ASSERT_TRUE(other.empty());
}
TEST(Emitter, Clear) {
test_emitter emitter;
ASSERT_TRUE(emitter.empty());
emitter.on<foo_event>([](auto &, const auto &) {});
emitter.once<quux_event>([](const auto &, const auto &) {});
emitter.on<quux_event>([](const auto &, const auto &) {});
ASSERT_FALSE(emitter.empty());
ASSERT_FALSE(emitter.empty<foo_event>());
ASSERT_FALSE(emitter.empty<quux_event>());
ASSERT_TRUE(emitter.empty<bar_event>());
ASSERT_TRUE(emitter.contains<foo_event>());
ASSERT_TRUE(emitter.contains<quux_event>());
ASSERT_FALSE(emitter.contains<bar_event>());
emitter.clear<bar_event>();
emitter.erase<bar_event>();
ASSERT_FALSE(emitter.empty());
ASSERT_FALSE(emitter.empty<foo_event>());
ASSERT_FALSE(emitter.empty<quux_event>());
ASSERT_TRUE(emitter.empty<bar_event>());
ASSERT_TRUE(emitter.contains<foo_event>());
ASSERT_TRUE(emitter.contains<quux_event>());
ASSERT_FALSE(emitter.contains<bar_event>());
emitter.clear<foo_event>();
emitter.erase<foo_event>();
ASSERT_FALSE(emitter.empty());
ASSERT_TRUE(emitter.empty<foo_event>());
ASSERT_FALSE(emitter.empty<quux_event>());
ASSERT_TRUE(emitter.empty<bar_event>());
ASSERT_FALSE(emitter.contains<foo_event>());
ASSERT_TRUE(emitter.contains<quux_event>());
ASSERT_FALSE(emitter.contains<bar_event>());
emitter.on<foo_event>([](auto &, const auto &) {});
emitter.on<bar_event>([](const auto &, const auto &) {});
ASSERT_FALSE(emitter.empty());
ASSERT_FALSE(emitter.empty<foo_event>());
ASSERT_FALSE(emitter.empty<quux_event>());
ASSERT_FALSE(emitter.empty<bar_event>());
ASSERT_TRUE(emitter.contains<foo_event>());
ASSERT_TRUE(emitter.contains<quux_event>());
ASSERT_TRUE(emitter.contains<bar_event>());
emitter.clear();
ASSERT_TRUE(emitter.empty());
ASSERT_TRUE(emitter.empty<foo_event>());
ASSERT_TRUE(emitter.empty<bar_event>());
ASSERT_FALSE(emitter.contains<foo_event>());
ASSERT_FALSE(emitter.contains<bar_event>());
}
TEST(Emitter, ClearPublishing) {
TEST(Emitter, ClearFromCallback) {
test_emitter emitter;
ASSERT_TRUE(emitter.empty());
emitter.once<foo_event>([](auto &, auto &em) {
em.template once<foo_event>([](auto &, auto &) {});
em.template clear<foo_event>();
emitter.on<foo_event>([](auto &, auto &owner) {
owner.template on<foo_event>([](auto &, auto &) {});
owner.template erase<foo_event>();
});
emitter.on<bar_event>([](const auto &, auto &em) {
em.template once<bar_event>([](const auto &, auto &) {});
em.template clear<bar_event>();
emitter.on<bar_event>([](const auto &, auto &owner) {
owner.template on<bar_event>([](const auto &, auto &) {});
owner.template erase<bar_event>();
});
ASSERT_FALSE(emitter.empty());
emitter.publish<foo_event>();
emitter.publish<bar_event>();
emitter.publish(foo_event{});
emitter.publish(bar_event{});
ASSERT_TRUE(emitter.empty());
}
TEST(Emitter, On) {
test_emitter emitter;
int value{};
emitter.on<foo_event>([](auto &, const auto &) {});
emitter.on<foo_event>([&value](auto &event, const auto &) {
value = event.i;
});
ASSERT_FALSE(emitter.empty());
ASSERT_FALSE(emitter.empty<foo_event>());
ASSERT_TRUE(emitter.contains<foo_event>());
ASSERT_EQ(value, 0);
emitter.publish<foo_event>(0, 'c');
emitter.publish(foo_event{42, 'c'});
ASSERT_FALSE(emitter.empty());
ASSERT_FALSE(emitter.empty<foo_event>());
}
TEST(Emitter, Once) {
test_emitter emitter;
emitter.once<bar_event>([](const auto &, const auto &) {});
ASSERT_FALSE(emitter.empty());
ASSERT_FALSE(emitter.empty<bar_event>());
emitter.publish<bar_event>();
ASSERT_TRUE(emitter.empty());
ASSERT_TRUE(emitter.empty<bar_event>());
}
TEST(Emitter, OnceAndErase) {
test_emitter emitter;
auto conn = emitter.once<foo_event>([](auto &, const auto &) {});
ASSERT_FALSE(emitter.empty());
ASSERT_FALSE(emitter.empty<foo_event>());
emitter.erase(conn);
ASSERT_TRUE(emitter.empty());
ASSERT_TRUE(emitter.empty<foo_event>());
ASSERT_EQ(value, 42);
}
TEST(Emitter, OnAndErase) {
test_emitter emitter;
std::function<void(bar_event &, test_emitter &)> func{};
auto conn = emitter.on<bar_event>([](const auto &, const auto &) {});
emitter.on(func);
ASSERT_FALSE(emitter.empty());
ASSERT_FALSE(emitter.empty<bar_event>());
ASSERT_TRUE(emitter.contains<bar_event>());
emitter.erase(conn);
emitter.erase<bar_event>();
ASSERT_TRUE(emitter.empty());
ASSERT_TRUE(emitter.empty<bar_event>());
ASSERT_FALSE(emitter.contains<bar_event>());
}

View File

@@ -3,7 +3,7 @@
#include "types.h"
ENTT_API void emit(test_emitter &emitter) {
emitter.publish<event>();
emitter.publish<message>(42);
emitter.publish<message>(3);
emitter.publish(event{});
emitter.publish(message{42});
emitter.publish(message{3});
}

View File

@@ -11,7 +11,11 @@ TEST(Lib, Emitter) {
ASSERT_EQ(value, 0);
emitter.once<message>([&](message msg, test_emitter &) { value = msg.payload; });
emitter.on<message>([&](message msg, test_emitter &emitter) {
value = msg.payload;
emitter.erase<message>();
});
emit(emitter);
ASSERT_EQ(value, 42);

View File

@@ -11,7 +11,10 @@ TEST(Lib, Emitter) {
ASSERT_EQ(value, 0);
emitter.once<message>([&](message msg, test_emitter &) { value = msg.payload; });
emitter.on<message>([&](message msg, test_emitter &emitter) {
value = msg.payload;
emitter.erase<message>();
});
cr_plugin ctx;
cr_plugin_load(ctx, PLUGIN);

View File

@@ -5,9 +5,9 @@
CR_EXPORT int cr_main(cr_plugin *ctx, cr_op operation) {
switch(operation) {
case CR_STEP:
static_cast<test_emitter *>(ctx->userdata)->publish<event>();
static_cast<test_emitter *>(ctx->userdata)->publish<message>(42);
static_cast<test_emitter *>(ctx->userdata)->publish<message>(3);
static_cast<test_emitter *>(ctx->userdata)->publish(event{});
static_cast<test_emitter *>(ctx->userdata)->publish(message{42});
static_cast<test_emitter *>(ctx->userdata)->publish(message{3});
break;
case CR_CLOSE:
case CR_LOAD: