sigh: allocator support

This commit is contained in:
Michele Caini
2021-12-02 09:20:30 +01:00
parent 072ab88721
commit 37250843e1
7 changed files with 186 additions and 40 deletions

View File

@@ -1,6 +1,8 @@
#ifndef ENTT_SIGNAL_FWD_HPP
#define ENTT_SIGNAL_FWD_HPP
#include <memory>
namespace entt {
template<typename>
@@ -18,7 +20,7 @@ struct scoped_connection;
template<typename>
class sink;
template<typename>
template<typename Type, typename = std::allocator<delegate<Type>>>
class sigh;
} // namespace entt

View File

@@ -19,9 +19,9 @@ namespace entt {
* Primary template isn't defined on purpose. All the specializations give a
* compile-time error unless the template parameter is a function type.
*
* @tparam Function A valid function type.
* @tparam Type A valid signal handler type.
*/
template<typename Function>
template<typename Type>
class sink;
/**
@@ -30,9 +30,10 @@ class sink;
* Primary template isn't defined on purpose. All the specializations give a
* compile-time error unless the template parameter is a function type.
*
* @tparam Function A valid function type.
* @tparam Type A valid function type.
* @tparam Allocator Type of allocator used to manage memory and elements.
*/
template<typename Function>
template<typename Type, typename Allocator>
class sigh;
/**
@@ -49,17 +50,104 @@ class sigh;
*
* @tparam Ret Return type of a function type.
* @tparam Args Types of arguments of a function type.
* @tparam Allocator Type of allocator used to manage memory and elements.
*/
template<typename Ret, typename... Args>
class sigh<Ret(Args...)> {
template<typename Ret, typename... Args, typename Allocator>
class sigh<Ret(Args...), Allocator> {
/*! @brief A sink is allowed to modify a signal. */
friend class sink<Ret(Args...)>;
friend class sink<sigh<Ret(Args...), Allocator>>;
using allocator_traits = std::allocator_traits<Allocator>;
using alloc = typename allocator_traits::template rebind_alloc<delegate<Ret(Args...)>>;
using alloc_traits = typename std::allocator_traits<alloc>;
using container_type = std::vector<delegate<Ret(Args...)>, alloc>;
public:
/*! @brief Allocator type. */
using allocator_type = Allocator;
/*! @brief Unsigned integer type. */
using size_type = std::size_t;
/*! @brief Sink type. */
using sink_type = sink<Ret(Args...)>;
using sink_type = sink<sigh<Ret(Args...), Allocator>>;
/*! @brief Default constructor. */
sigh()
: sigh{allocator_type{}} {}
/**
* @brief Constructs a signal handler with a given allocator.
* @param allocator The allocator to use.
*/
explicit sigh(const allocator_type &allocator)
: calls{allocator} {}
/**
* @brief Default copy constructor.
* @param other The instance to copy from.
*/
sigh(const sigh &other)
: calls{other.calls} {}
/**
* @brief Allocator-extended copy constructor.
* @param other The instance to copy from.
* @param allocator The allocator to use.
*/
sigh(const sigh &other, const allocator_type &allocator)
: calls{other.calls, allocator} {}
/**
* @brief Default move constructor.
* @param other The instance to move from.
*/
sigh(sigh &&other) ENTT_NOEXCEPT
: calls{std::move(other.calls)} {}
/**
* @brief Allocator-extended move constructor.
* @param other The instance to move from.
* @param allocator The allocator to use.
*/
sigh(sigh &&other, const allocator_type &allocator) ENTT_NOEXCEPT
: calls{std::move(other.calls), allocator} {}
/**
* @brief Default copy assignment operator.
* @param other The instance to copy from.
* @return This signal handler.
*/
sigh &operator=(const sigh &other) {
calls = other.calls;
return *this;
}
/**
* @brief Default move assignment operator.
* @param other The instance to move from.
* @return This signal handler.
*/
sigh &operator=(sigh &&other) ENTT_NOEXCEPT {
calls = std::move(other.calls);
return *this;
}
/**
* @brief Exchanges the contents with those of a given signal handler.
* @param other Signal handler to exchange the content with.
*/
void swap(sigh &other) {
using std::swap;
swap(calls, other.calls);
}
/**
* @brief Returns the associated allocator.
* @return The associated allocator.
*/
[[nodiscard]] constexpr allocator_type get_allocator() const ENTT_NOEXCEPT {
return calls.get_allocator();
}
/**
* @brief Instance type when it comes to connecting member functions.
@@ -133,7 +221,7 @@ public:
}
private:
std::vector<delegate<Ret(Args...)>> calls;
container_type calls;
};
/**
@@ -271,10 +359,11 @@ private:
*
* @tparam Ret Return type of a function type.
* @tparam Args Types of arguments of a function type.
* @tparam Allocator Type of allocator used to manage memory and elements.
*/
template<typename Ret, typename... Args>
class sink<Ret(Args...)> {
using signal_type = sigh<Ret(Args...)>;
template<typename Ret, typename... Args, typename Allocator>
class sink<sigh<Ret(Args...), Allocator>> {
using signal_type = sigh<Ret(Args...), Allocator>;
using difference_type = typename std::iterator_traits<typename decltype(signal_type::calls)::iterator>::difference_type;
template<auto Candidate, typename Type>
@@ -292,7 +381,7 @@ public:
* @brief Constructs a sink that is allowed to modify a given signal.
* @param ref A valid reference to a signal object.
*/
sink(sigh<Ret(Args...)> &ref) ENTT_NOEXCEPT
sink(sigh<Ret(Args...), Allocator> &ref) ENTT_NOEXCEPT
: offset{},
signal{&ref} {}
@@ -507,14 +596,15 @@ private:
/**
* @brief Deduction guide.
*
* It allows to deduce the function type of a sink directly from the signal it
* refers to.
* It allows to deduce the signal handler type of a sink directly from the
* signal it refers to.
*
* @tparam Ret Return type of a function type.
* @tparam Args Types of arguments of a function type.
* @tparam Allocator Type of allocator used to manage memory and elements.
*/
template<typename Ret, typename... Args>
sink(sigh<Ret(Args...)> &) -> sink<Ret(Args...)>;
template<typename Ret, typename... Args, typename Allocator>
sink(sigh<Ret(Args...), Allocator> &) -> sink<sigh<Ret(Args...), Allocator>>;
} // namespace entt

View File

@@ -1,5 +1,5 @@
#ifndef ENTT_ENTITY_THROWING_ALLOCATOR_HPP
#define ENTT_ENTITY_THROWING_ALLOCATOR_HPP
#ifndef ENTT_COMMON_THROWING_ALLOCATOR_HPP
#define ENTT_COMMON_THROWING_ALLOCATOR_HPP
#include <cstddef>
#include <memory>

View File

@@ -1,19 +1,19 @@
#ifndef ENTT_ENTITY_THROWING_COMPONENT_HPP
#define ENTT_ENTITY_THROWING_COMPONENT_HPP
#ifndef ENTT_COMMON_THROWING_TYPE_HPP
#define ENTT_COMMON_THROWING_TYPE_HPP
namespace test {
class throwing_component {
class throwing_type {
struct test_exception {};
public:
using exception_type = test_exception;
static constexpr auto moved_from_value = -1;
throwing_component(int value)
throwing_type(int value)
: data{value} {}
throwing_component(const throwing_component &other)
throwing_type(const throwing_type &other)
: data{other.data} {
if(data == trigger_on_value) {
data = moved_from_value;
@@ -21,7 +21,7 @@ public:
}
}
throwing_component &operator=(const throwing_component &other) {
throwing_type &operator=(const throwing_type &other) {
if(other.data == trigger_on_value) {
data = moved_from_value;
throw exception_type{};

View File

@@ -7,7 +7,7 @@
#include <gtest/gtest.h>
#include <entt/entity/entity.hpp>
#include <entt/entity/sparse_set.hpp>
#include "throwing_allocator.hpp"
#include "../common/throwing_allocator.hpp"
struct empty_type {};

View File

@@ -7,8 +7,8 @@
#include <gtest/gtest.h>
#include <entt/entity/component.hpp>
#include <entt/entity/storage.hpp>
#include "throwing_allocator.hpp"
#include "throwing_component.hpp"
#include "../common/throwing_allocator.hpp"
#include "../common/throwing_type.hpp"
struct empty_stable_type {};
@@ -1480,28 +1480,28 @@ TEST(Storage, ThrowingAllocator) {
}
TEST(Storage, ThrowingComponent) {
entt::storage<test::throwing_component> pool;
test::throwing_component::trigger_on_value = 42;
entt::storage<test::throwing_type> pool;
test::throwing_type::trigger_on_value = 42;
// strong exception safety
ASSERT_THROW(pool.emplace(entt::entity{0}, test::throwing_component{42}), typename test::throwing_component::exception_type);
ASSERT_THROW(pool.emplace(entt::entity{0}, test::throwing_type{42}), typename test::throwing_type::exception_type);
ASSERT_TRUE(pool.empty());
const entt::entity entities[2u]{entt::entity{42}, entt::entity{1}};
const test::throwing_component components[2u]{42, 1};
const test::throwing_type components[2u]{42, 1};
// basic exception safety
ASSERT_THROW(pool.insert(std::begin(entities), std::end(entities), test::throwing_component{42}), typename test::throwing_component::exception_type);
ASSERT_THROW(pool.insert(std::begin(entities), std::end(entities), test::throwing_type{42}), typename test::throwing_type::exception_type);
ASSERT_EQ(pool.size(), 0u);
ASSERT_FALSE(pool.contains(entt::entity{1}));
// basic exception safety
ASSERT_THROW(pool.insert(std::begin(entities), std::end(entities), std::begin(components)), typename test::throwing_component::exception_type);
ASSERT_THROW(pool.insert(std::begin(entities), std::end(entities), std::begin(components)), typename test::throwing_type::exception_type);
ASSERT_EQ(pool.size(), 0u);
ASSERT_FALSE(pool.contains(entt::entity{1}));
// basic exception safety
ASSERT_THROW(pool.insert(std::rbegin(entities), std::rend(entities), std::rbegin(components)), typename test::throwing_component::exception_type);
ASSERT_THROW(pool.insert(std::rbegin(entities), std::rend(entities), std::rbegin(components)), typename test::throwing_type::exception_type);
ASSERT_EQ(pool.size(), 1u);
ASSERT_TRUE(pool.contains(entt::entity{1}));
ASSERT_EQ(pool.get(entt::entity{1}), 1);
@@ -1511,7 +1511,7 @@ TEST(Storage, ThrowingComponent) {
pool.emplace(entt::entity{42}, 42);
// basic exception safety
ASSERT_THROW(pool.erase(entt::entity{1}), typename test::throwing_component::exception_type);
ASSERT_THROW(pool.erase(entt::entity{1}), typename test::throwing_type::exception_type);
ASSERT_EQ(pool.size(), 2u);
ASSERT_TRUE(pool.contains(entt::entity{42}));
ASSERT_TRUE(pool.contains(entt::entity{1}));
@@ -1519,9 +1519,9 @@ TEST(Storage, ThrowingComponent) {
ASSERT_EQ(pool.at(1u), entt::entity{42});
ASSERT_EQ(pool.get(entt::entity{42}), 42);
// the element may have been moved but it's still there
ASSERT_EQ(pool.get(entt::entity{1}), test::throwing_component::moved_from_value);
ASSERT_EQ(pool.get(entt::entity{1}), test::throwing_type::moved_from_value);
test::throwing_component::trigger_on_value = 99;
test::throwing_type::trigger_on_value = 99;
pool.erase(entt::entity{1});
ASSERT_EQ(pool.size(), 1u);

View File

@@ -2,6 +2,8 @@
#include <vector>
#include <gtest/gtest.h>
#include <entt/signal/sigh.hpp>
#include "../common/throwing_allocator.hpp"
#include "../common/throwing_type.hpp"
struct sigh_listener {
static void f(int &v) {
@@ -122,7 +124,7 @@ TEST_F(SigH, Swap) {
ASSERT_FALSE(sigh1.empty());
ASSERT_TRUE(sigh2.empty());
std::swap(sigh1, sigh2);
sigh1.swap(sigh2);
ASSERT_TRUE(sink1.empty());
ASSERT_FALSE(sink2.empty());
@@ -530,3 +532,55 @@ TEST_F(SigH, UnboundMemberFunction) {
ASSERT_TRUE(listener.k);
}
TEST_F(SigH, CustomAllocator) {
test::throwing_allocator<entt::delegate<void(int)>> allocator;
auto test = [&](auto curr) {
ASSERT_EQ(curr.get_allocator(), allocator);
ASSERT_TRUE(curr.empty());
entt::sink sink{curr};
sigh_listener listener;
sink.template connect<&sigh_listener::i>(listener);
decltype(curr) copy{curr, allocator};
sink.disconnect(listener);
ASSERT_TRUE(curr.empty());
ASSERT_FALSE(copy.empty());
curr = copy;
ASSERT_FALSE(curr.empty());
ASSERT_FALSE(copy.empty());
decltype(curr) move{std::move(copy), allocator};
ASSERT_TRUE(copy.empty());
ASSERT_FALSE(move.empty());
sink = entt::sink{move};
sink.disconnect(&listener);
ASSERT_TRUE(copy.empty());
ASSERT_TRUE(move.empty());
sink.template connect<&sigh_listener::i>(listener);
copy.swap(move);
ASSERT_FALSE(copy.empty());
ASSERT_TRUE(move.empty());
sink = entt::sink{copy};
sink.disconnect();
ASSERT_TRUE(copy.empty());
ASSERT_TRUE(move.empty());
};
entt::sigh<void(int), decltype(allocator)> sigh{allocator};
test(sigh);
test(std::move(sigh));
}