From 10dfe7e9354d9f40821fc764bd234c2e4ec6db06 Mon Sep 17 00:00:00 2001 From: Michele Caini Date: Fri, 31 Mar 2023 13:59:53 +0200 Subject: [PATCH] sigh: allow disconnecting listeners during iterations (close #986) --- src/entt/signal/sigh.hpp | 33 ++++++++++++++++++++------------- test/entt/signal/sigh.cpp | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 13 deletions(-) diff --git a/src/entt/signal/sigh.hpp b/src/entt/signal/sigh.hpp index 8bc860c4c..a44c50a95 100644 --- a/src/entt/signal/sigh.hpp +++ b/src/entt/signal/sigh.hpp @@ -1,8 +1,8 @@ #ifndef ENTT_SIGNAL_SIGH_HPP #define ENTT_SIGNAL_SIGH_HPP -#include -#include +#include +#include #include #include #include @@ -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 void collect(Func func, Args... args) const { - for(auto &&call: calls) { + for(auto pos = calls.size(); pos; --pos) { if constexpr(std::is_void_v || !std::is_invocable_v) { - call(args...); + calls[pos - 1u](args...); if constexpr(std::is_invocable_r_v) { if(func()) { @@ -203,11 +203,11 @@ public: } } else { if constexpr(std::is_invocable_r_v) { - 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> { sink{*static_cast(signal)}.disconnect(); } + template + 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 void disconnect(Type &&...value_or_instance) { - auto &calls = signal->calls; delegate_type call{}; call.template connect(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; }); } } diff --git a/test/entt/signal/sigh.cpp b/test/entt/signal/sigh.cpp index 10981703b..0a4f96fd2 100644 --- a/test/entt/signal/sigh.cpp +++ b/test/entt/signal/sigh.cpp @@ -45,6 +45,12 @@ struct const_nonconst_noexcept { mutable int cnt{0}; }; +void connect_and_auto_disconnect(entt::sigh &sigh, const int &) { + entt::sink sink{sigh}; + sink.connect(); + sink.disconnect<&connect_and_auto_disconnect>(sigh); +} + TEST(SigH, Lifetime) { using signal = entt::sigh; @@ -427,6 +433,32 @@ TEST(SigH, UnboundMemberFunction) { ASSERT_TRUE(listener.k); } +TEST(SigH, ConnectAndAutoDisconnect) { + sigh_listener listener; + entt::sigh 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 allocator; entt::sigh sigh{allocator};