dispatcher: allocator support

This commit is contained in:
Michele Caini
2022-03-04 14:51:44 +01:00
parent 594b3b5531
commit 8a350acf39
4 changed files with 158 additions and 27 deletions

2
TODO
View File

@@ -4,7 +4,7 @@
WIP:
* get rid of storage_traits class template
* uses-allocator construction: any (with allocator support), cache, dispatcher, poly, ...
* uses-allocator construction: any (with allocator support), cache, poly, ...
* add an ENTT_NOEXCEPT with args and use it to make ie compressed_pair conditionally noexcept
* process scheduler: reviews, use free lists internally
* runtime events (emitter)

View File

@@ -1,6 +1,7 @@
#ifndef ENTT_SIGNAL_DISPATCHER_HPP
#define ENTT_SIGNAL_DISPATCHER_HPP
#include <algorithm>
#include <cstddef>
#include <memory>
#include <type_traits>
@@ -8,6 +9,7 @@
#include <vector>
#include "../config/config.h"
#include "../container/dense_map.hpp"
#include "../core/compressed_pair.hpp"
#include "../core/fwd.hpp"
#include "../core/type_info.hpp"
#include "../core/utility.hpp"
@@ -31,10 +33,21 @@ struct basic_dispatcher_handler {
virtual std::size_t size() const ENTT_NOEXCEPT = 0;
};
template<typename Event>
struct dispatcher_handler final: basic_dispatcher_handler {
template<typename Event, typename Allocator>
class dispatcher_handler final: public basic_dispatcher_handler {
static_assert(std::is_same_v<Event, std::decay_t<Event>>, "Invalid event type");
using alloc_traits = std::allocator_traits<Allocator>;
using signal_type = sigh<void(Event &), typename alloc_traits::template rebind_alloc<void (*)(Event &)>>;
using container_type = std::vector<Event, typename alloc_traits::template rebind_alloc<Event>>;
public:
using allocator_type = Allocator;
dispatcher_handler(const allocator_type &allocator)
: signal{allocator},
events{allocator} {}
void publish() override {
const auto length = events.size();
@@ -76,8 +89,8 @@ struct dispatcher_handler final: basic_dispatcher_handler {
}
private:
sigh<void(Event &)> signal{};
std::vector<Event> events;
signal_type signal;
container_type events;
};
} // namespace internal
@@ -98,40 +111,103 @@ private:
*
* The dispatcher creates instances of the `sigh` class internally. Refer to the
* documentation of the latter for more details.
*
* @tparam Allocator Type of allocator used to manage memory and elements.
*/
class dispatcher {
template<typename Allocator>
class basic_dispatcher {
template<typename Event>
[[nodiscard]] internal::dispatcher_handler<Event> &assure(const id_type id) {
if(auto &&ptr = pools[id]; !ptr) {
auto *cpool = new internal::dispatcher_handler<Event>{};
ptr.reset(cpool);
return *cpool;
} else {
return static_cast<internal::dispatcher_handler<Event> &>(*ptr);
using handler_type = internal::dispatcher_handler<Event, Allocator>;
using key_type = id_type;
// std::shared_ptr because of its type erased allocator which is pretty useful here
using mapped_type = std::shared_ptr<internal::basic_dispatcher_handler>;
using alloc_traits = std::allocator_traits<Allocator>;
using container_allocator = typename alloc_traits::template rebind_alloc<std::pair<const key_type, mapped_type>>;
using container_type = dense_map<id_type, mapped_type, identity, std::equal_to<id_type>, container_allocator>;
template<typename Event>
[[nodiscard]] handler_type<Event> &assure(const id_type id) {
auto &&ptr = pools.first()[id];
if(!ptr) {
const auto &allocator = pools.second();
ptr = std::allocate_shared<handler_type<Event>>(allocator, allocator);
}
return static_cast<handler_type<Event> &>(*ptr);
}
template<typename Event>
[[nodiscard]] const internal::dispatcher_handler<Event> *assure(const id_type id) const {
if(const auto it = pools.find(id); it != pools.end()) {
return static_cast<const internal::dispatcher_handler<Event> *>(it->second.get());
[[nodiscard]] const handler_type<Event> *assure(const id_type id) const {
auto &container = pools.first();
if(const auto it = container.find(id); it != container.end()) {
return static_cast<const handler_type<Event> *>(it->second.get());
}
return nullptr;
}
public:
/*! @brief Allocator type. */
using allocator_type = Allocator;
/*! @brief Unsigned integer type. */
using size_type = std::size_t;
/*! @brief Default constructor. */
dispatcher() = default;
basic_dispatcher()
: basic_dispatcher{allocator_type{}} {}
/*! @brief Default move constructor. */
dispatcher(dispatcher &&) = default;
/**
* @brief Constructs a dispatcher with a given allocator.
* @param allocator The allocator to use.
*/
explicit basic_dispatcher(const allocator_type &allocator)
: pools{allocator, allocator} {}
/*! @brief Default move assignment operator. @return This dispatcher. */
dispatcher &operator=(dispatcher &&) = default;
/**
* @brief Move constructor.
* @param other The instance to move from.
*/
basic_dispatcher(basic_dispatcher &&other) ENTT_NOEXCEPT
: pools{std::move(other.pools)} {}
/**
* @brief Allocator-extended move constructor.
* @param other The instance to move from.
* @param allocator The allocator to use.
*/
basic_dispatcher(basic_dispatcher &&other, const allocator_type &allocator) ENTT_NOEXCEPT
: pools{container_type{std::move(other.pools.first()), allocator}, allocator} {}
/**
* @brief Move assignment operator.
* @param other The instance to move from.
* @return This dispatcher.
*/
basic_dispatcher &operator=(basic_dispatcher &&other) ENTT_NOEXCEPT {
pools = std::move(other.pools);
return *this;
}
/**
* @brief Exchanges the contents with those of a given dispatcher.
* @param other Dispatcher to exchange the content with.
*/
void swap(basic_dispatcher &other) {
using std::swap;
swap(pools, other.pools);
}
/**
* @brief Returns the associated allocator.
* @return The associated allocator.
*/
[[nodiscard]] constexpr allocator_type get_allocator() const ENTT_NOEXCEPT {
return pools.second();
}
/**
* @brief Returns the number of pending events for a given type.
@@ -152,7 +228,7 @@ public:
size_type size() const ENTT_NOEXCEPT {
size_type count{};
for(auto &&cpool: pools) {
for(auto &&cpool: pools.first()) {
count += cpool.second->size();
}
@@ -266,7 +342,7 @@ public:
*/
template<typename Type>
void disconnect(Type *value_or_instance) {
for(auto &&cpool: pools) {
for(auto &&cpool: pools.first()) {
cpool.second->disconnect(value_or_instance);
}
}
@@ -283,7 +359,7 @@ public:
/*! @brief Discards all the events queued so far. */
void clear() ENTT_NOEXCEPT {
for(auto &&cpool: pools) {
for(auto &&cpool: pools.first()) {
cpool.second->clear();
}
}
@@ -300,13 +376,13 @@ public:
/*! @brief Delivers all the pending events. */
void update() const {
for(auto &&cpool: pools) {
for(auto &&cpool: pools.first()) {
cpool.second->publish();
}
}
private:
dense_map<id_type, std::unique_ptr<internal::basic_dispatcher_handler>, identity> pools;
compressed_pair<container_type, allocator_type> pools;
};
} // namespace entt

View File

@@ -8,7 +8,8 @@ namespace entt {
template<typename>
class delegate;
class dispatcher;
template<typename = std::allocator<char>>
class basic_dispatcher;
template<typename>
class emitter;
@@ -23,6 +24,9 @@ class sink;
template<typename Type, typename = std::allocator<Type *>>
class sigh;
/*! @brief Alias declaration for the most common use case. */
using dispatcher = basic_dispatcher<>;
} // namespace entt
#endif

View File

@@ -1,3 +1,4 @@
#include <memory>
#include <utility>
#include <gtest/gtest.h>
#include <entt/core/hashed_string.hpp>
@@ -29,8 +30,12 @@ struct receiver {
TEST(Dispatcher, Functionalities) {
entt::dispatcher dispatcher;
entt::dispatcher other;
receiver receiver;
ASSERT_NO_FATAL_FAILURE(entt::dispatcher{std::move(dispatcher)});
ASSERT_NO_FATAL_FAILURE(dispatcher = std::move(other));
ASSERT_EQ(dispatcher.size<an_event>(), 0u);
ASSERT_EQ(dispatcher.size(), 0u);
@@ -87,6 +92,32 @@ TEST(Dispatcher, Functionalities) {
ASSERT_EQ(receiver.cnt, 0);
}
TEST(Dispatcher, Swap) {
entt::dispatcher dispatcher;
entt::dispatcher other;
receiver receiver;
dispatcher.sink<an_event>().connect<&receiver::receive>(receiver);
dispatcher.enqueue<an_event>();
ASSERT_EQ(dispatcher.size(), 1u);
ASSERT_EQ(other.size(), 0u);
ASSERT_EQ(receiver.cnt, 0);
dispatcher.swap(other);
dispatcher.update();
ASSERT_EQ(dispatcher.size(), 0u);
ASSERT_EQ(other.size(), 1u);
ASSERT_EQ(receiver.cnt, 0);
other.update();
ASSERT_EQ(dispatcher.size(), 0u);
ASSERT_EQ(other.size(), 0u);
ASSERT_EQ(receiver.cnt, 1);
}
TEST(Dispatcher, StopAndGo) {
entt::dispatcher dispatcher;
receiver receiver;
@@ -154,3 +185,23 @@ TEST(Dispatcher, NamedQueue) {
ASSERT_EQ(receiver.cnt, 3);
}
TEST(Dispatcher, CustomAllocator) {
std::allocator<char> allocator;
entt::dispatcher dispatcher{allocator};
receiver receiver;
ASSERT_EQ(dispatcher.get_allocator(), allocator);
ASSERT_FALSE(dispatcher.get_allocator() != allocator);
dispatcher.enqueue<an_event>();
decltype(dispatcher) other{std::move(dispatcher), allocator};
ASSERT_EQ(dispatcher.size(), 0u);
ASSERT_EQ(other.size<an_event>(), 1u);
dispatcher = std::move(other);
ASSERT_EQ(dispatcher.size<an_event>(), 1u);
ASSERT_EQ(other.size(), 0u);
}