sigh: allow disconnecting listeners during iterations (close #986)

This commit is contained in:
Michele Caini
2023-03-31 13:59:53 +02:00
parent a9208a9565
commit 10dfe7e935
2 changed files with 52 additions and 13 deletions

View File

@@ -1,8 +1,8 @@
#ifndef ENTT_SIGNAL_SIGH_HPP
#define ENTT_SIGNAL_SIGH_HPP
#include <algorithm>
#include <functional>
#include <cstddef>
#include <memory>
#include <type_traits>
#include <utility>
#include <vector>
@@ -169,8 +169,8 @@ public:
* @param args Arguments to use to invoke listeners.
*/
void publish(Args... args) const {
for(auto &&call: std::as_const(calls)) {
call(args...);
for(auto pos = calls.size(); pos; --pos) {
calls[pos - 1u](args...);
}
}
@@ -190,9 +190,9 @@ public:
*/
template<typename Func>
void collect(Func func, Args... args) const {
for(auto &&call: calls) {
for(auto pos = calls.size(); pos; --pos) {
if constexpr(std::is_void_v<Ret> || !std::is_invocable_v<Func, Ret>) {
call(args...);
calls[pos - 1u](args...);
if constexpr(std::is_invocable_r_v<bool, Func>) {
if(func()) {
@@ -203,11 +203,11 @@ public:
}
} else {
if constexpr(std::is_invocable_r_v<bool, Func, Ret>) {
if(func(call(args...))) {
if(func(calls[pos - 1u](args...))) {
break;
}
} else {
func(call(args...));
func(calls[pos - 1u](args...));
}
}
}
@@ -372,6 +372,16 @@ class sink<sigh<Ret(Args...), Allocator>> {
sink{*static_cast<signal_type *>(signal)}.disconnect<Candidate>();
}
template<typename Func>
void disconnect_if(Func callback) {
for(auto pos = signal->calls.size(); pos; --pos) {
if(auto &elem = signal->calls[pos - 1u]; callback(elem)) {
elem = std::move(signal->calls.back());
signal->calls.pop_back();
}
}
}
public:
/**
* @brief Constructs a sink that is allowed to modify a given signal.
@@ -418,10 +428,9 @@ public:
*/
template<auto Candidate, typename... Type>
void disconnect(Type &&...value_or_instance) {
auto &calls = signal->calls;
delegate_type call{};
call.template connect<Candidate>(value_or_instance...);
calls.erase(std::remove(calls.begin(), calls.end(), std::move(call)), calls.end());
disconnect_if([&call](const auto &elem) { return elem == call; });
}
/**
@@ -431,9 +440,7 @@ public:
*/
void disconnect(const void *value_or_instance) {
if(value_or_instance) {
auto &calls = signal->calls;
auto predicate = [value_or_instance](const auto &delegate) { return delegate.data() == value_or_instance; };
calls.erase(std::remove_if(calls.begin(), calls.end(), std::move(predicate)), calls.end());
disconnect_if([value_or_instance](const auto &elem) { return elem.data() == value_or_instance; });
}
}

View File

@@ -45,6 +45,12 @@ struct const_nonconst_noexcept {
mutable int cnt{0};
};
void connect_and_auto_disconnect(entt::sigh<void(int &)> &sigh, const int &) {
entt::sink sink{sigh};
sink.connect<sigh_listener::f>();
sink.disconnect<&connect_and_auto_disconnect>(sigh);
}
TEST(SigH, Lifetime) {
using signal = entt::sigh<void(void)>;
@@ -427,6 +433,32 @@ TEST(SigH, UnboundMemberFunction) {
ASSERT_TRUE(listener.k);
}
TEST(SigH, ConnectAndAutoDisconnect) {
sigh_listener listener;
entt::sigh<void(int &)> sigh;
entt::sink sink{sigh};
int v = 0;
sink.connect<&sigh_listener::g>(listener);
sink.connect<&connect_and_auto_disconnect>(sigh);
ASSERT_FALSE(listener.k);
ASSERT_EQ(sigh.size(), 2u);
ASSERT_EQ(v, 0);
sigh.publish(v);
ASSERT_TRUE(listener.k);
ASSERT_EQ(sigh.size(), 2u);
ASSERT_EQ(v, 0);
sigh.publish(v);
ASSERT_FALSE(listener.k);
ASSERT_EQ(sigh.size(), 2u);
ASSERT_EQ(v, 42);
}
TEST(SigH, CustomAllocator) {
std::allocator<void (*)(int)> allocator;
entt::sigh<void(int), decltype(allocator)> sigh{allocator};