Просмотр исходного кода

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

Michele Caini 2 лет назад
Родитель
Сommit
10dfe7e935
2 измененных файлов с 52 добавлено и 13 удалено
  1. 20 13
      src/entt/signal/sigh.hpp
  2. 32 0
      test/entt/signal/sigh.cpp

+ 20 - 13
src/entt/signal/sigh.hpp

@@ -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; });
         }
     }
 

+ 32 - 0
test/entt/signal/sigh.cpp

@@ -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};