]> git.stg.codes - stg.git/commitdiff
Add subscriptions (to replace notifiers).
authorMaksym Mamontov <madf@madf.info>
Tue, 23 Aug 2022 11:21:12 +0000 (14:21 +0300)
committerMaksym Mamontov <madf@madf.info>
Tue, 23 Aug 2022 11:21:12 +0000 (14:21 +0300)
include/stg/subscriptions.h [new file with mode: 0644]
tests/CMakeLists.txt
tests/test_subscriptions.cpp [new file with mode: 0644]

diff --git a/include/stg/subscriptions.h b/include/stg/subscriptions.h
new file mode 100644 (file)
index 0000000..1486e62
--- /dev/null
@@ -0,0 +1,84 @@
+#pragma once
+
+#include <list>
+#include <functional>
+#include <mutex>
+
+namespace STG
+{
+
+template <typename... Ts>
+class Subscriptions
+{
+    public:
+        using Callback = std::function<void (Ts...)>;
+        using Callbacks = std::list<Callback>;
+
+        class Connection
+        {
+            public:
+                Connection(Subscriptions& s, typename Callbacks::iterator i) noexcept
+                    : m_subscriptions(s), m_iterator(i), m_connected(true)
+                {}
+                ~Connection()
+                {
+                    disconnect();
+                }
+
+                void disconnect() noexcept
+                {
+                    if (!m_connected)
+                        return;
+                    m_subscriptions.remove(m_iterator);
+                    m_connected = false;
+                }
+            private:
+                Subscriptions& m_subscriptions;
+                typename Callbacks::iterator m_iterator;
+                bool m_connected;
+        };
+
+        template <typename F>
+        Connection add(F&& f)
+        {
+            std::lock_guard lock(m_mutex);
+            return Connection(*this, m_callbacks.insert(m_callbacks.end(), Callback(std::forward<F>(f))));
+        }
+
+        template <typename C, typename... T2s>
+        Connection add(C& c, void (C::*m)(T2s...))
+        {
+            return add([&c, m](Ts&&... values){ (c.*m)(std::forward<Ts>(values)...); });
+        }
+
+        void remove(typename Callbacks::iterator i)
+        {
+            std::lock_guard lock(m_mutex);
+            m_callbacks.erase(i);
+        }
+
+        void notify(Ts&&... values)
+        {
+            std::lock_guard lock(m_mutex);
+            for (auto& cb : m_callbacks)
+                cb(values...);
+        }
+
+        bool empty() const noexcept
+        {
+            std::lock_guard lock(m_mutex);
+            return m_callbacks.empty();
+        }
+
+        size_t size() const noexcept
+        {
+            std::lock_guard lock(m_mutex);
+            return m_callbacks.size();
+        }
+
+    private:
+        mutable std::mutex m_mutex;
+        Callbacks m_callbacks;
+};
+
+}
index 250f5e06ca521017eedce8dc59667c00286f8147..a552cfbe2732df1a6ef7150515d6deb8316326f6 100644 (file)
@@ -49,3 +49,7 @@ add_executable ( test_filter_params_log test_filter_params_log.cpp ../projects/s
 target_link_libraries ( test_filter_params_log logger scriptexecuter common Boost::unit_test_framework Threads::Threads )
 target_include_directories ( test_filter_params_log PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ../projects/stargazer )
 add_test ( filter_params_log test_filter_params_log )
 target_link_libraries ( test_filter_params_log logger scriptexecuter common Boost::unit_test_framework Threads::Threads )
 target_include_directories ( test_filter_params_log PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ../projects/stargazer )
 add_test ( filter_params_log test_filter_params_log )
+
+add_executable ( test_subscriptions test_subscriptions.cpp )
+target_link_libraries ( test_subscriptions Boost::unit_test_framework )
+add_test ( subscriptions test_subscriptions )
diff --git a/tests/test_subscriptions.cpp b/tests/test_subscriptions.cpp
new file mode 100644 (file)
index 0000000..ebc3c92
--- /dev/null
@@ -0,0 +1,223 @@
+#define BOOST_TEST_MODULE STGSubscriptions
+
+#include "stg/subscriptions.h"
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wold-style-cast"
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+#pragma GCC diagnostic ignored "-Wsign-compare"
+#pragma GCC diagnostic ignored "-Wparentheses"
+#include <boost/test/unit_test.hpp>
+#pragma GCC diagnostic pop
+
+namespace
+{
+
+struct Receiver
+{
+    Receiver() noexcept
+        : countR0(0),
+          countR1(0),
+          valueR1(0),
+          countR2(0),
+          value1R2(0)
+    {}
+
+    void r0() { ++countR0; }
+    void r1(uint8_t v) { ++countR1; valueR1 = v; }
+    void r2(uint8_t v1, const std::string& v2) { ++countR2; value1R2 = v1; value2R2 = v2; }
+
+    size_t countR0;
+    size_t countR1;
+    uint8_t valueR1;
+    size_t countR2;
+    uint8_t value1R2;
+    std::string value2R2;
+};
+
+}
+
+BOOST_AUTO_TEST_SUITE(Subscriptions)
+
+BOOST_AUTO_TEST_CASE(Construction)
+{
+    STG::Subscriptions<> nullary;
+    BOOST_CHECK(nullary.empty());
+    BOOST_CHECK_EQUAL(nullary.size(), 0);
+
+    STG::Subscriptions<uint8_t> unary;
+    BOOST_CHECK(unary.empty());
+    BOOST_CHECK_EQUAL(unary.size(), 0);
+
+    STG::Subscriptions<uint8_t, std::string> binary;
+    BOOST_CHECK(binary.empty());
+    BOOST_CHECK_EQUAL(binary.size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(AddingAndRemoving)
+{
+    Receiver r;
+    STG::Subscriptions<> nullary;
+    {
+        auto c1 = nullary.add(r, &Receiver::r0);
+        auto c2 = nullary.add([&r](){ r.r0(); });
+
+        BOOST_CHECK(!nullary.empty());
+        BOOST_CHECK_EQUAL(nullary.size(), 2);
+
+        c1.disconnect();
+
+        BOOST_CHECK(!nullary.empty());
+        BOOST_CHECK_EQUAL(nullary.size(), 1);
+    }
+    BOOST_CHECK(nullary.empty());
+    BOOST_CHECK_EQUAL(nullary.size(), 0);
+
+    STG::Subscriptions<uint8_t> unary;
+    {
+        auto c1 = unary.add(r, &Receiver::r1);
+        auto c2 = unary.add([&r](const auto& v){ r.r1(v); });
+
+        BOOST_CHECK(!unary.empty());
+        BOOST_CHECK_EQUAL(unary.size(), 2);
+
+        c1.disconnect();
+
+        BOOST_CHECK(!unary.empty());
+        BOOST_CHECK_EQUAL(unary.size(), 1);
+    }
+    BOOST_CHECK(unary.empty());
+    BOOST_CHECK_EQUAL(unary.size(), 0);
+
+    STG::Subscriptions<uint8_t, std::string> binary;
+    {
+        auto c1 = binary.add(r, &Receiver::r2);
+        auto c2 = binary.add([&r](const auto& v1, const auto& v2){ r.r2(v1, v2); });
+
+        BOOST_CHECK(!binary.empty());
+        BOOST_CHECK_EQUAL(binary.size(), 2);
+
+        c1.disconnect();
+
+        BOOST_CHECK(!binary.empty());
+        BOOST_CHECK_EQUAL(binary.size(), 1);
+    }
+    BOOST_CHECK(binary.empty());
+    BOOST_CHECK_EQUAL(binary.size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(Notification)
+{
+    Receiver r;
+
+    BOOST_CHECK_EQUAL(r.countR0, 0);
+    BOOST_CHECK_EQUAL(r.countR1, 0);
+    BOOST_CHECK_EQUAL(r.valueR1, 0);
+    BOOST_CHECK_EQUAL(r.countR2, 0);
+    BOOST_CHECK_EQUAL(r.value1R2, 0);
+    BOOST_CHECK_EQUAL(r.value2R2, "");
+
+    STG::Subscriptions<> nullary;
+    {
+        auto c1 = nullary.add(r, &Receiver::r0);
+        auto c2 = nullary.add([&r](){ r.r0(); });
+
+        nullary.notify();
+
+        BOOST_CHECK_EQUAL(r.countR0, 2);
+        BOOST_CHECK_EQUAL(r.countR1, 0);
+        BOOST_CHECK_EQUAL(r.valueR1, 0);
+        BOOST_CHECK_EQUAL(r.countR2, 0);
+        BOOST_CHECK_EQUAL(r.value1R2, 0);
+        BOOST_CHECK_EQUAL(r.value2R2, "");
+
+        c1.disconnect();
+        nullary.notify();
+
+        BOOST_CHECK_EQUAL(r.countR0, 3);
+        BOOST_CHECK_EQUAL(r.countR1, 0);
+        BOOST_CHECK_EQUAL(r.valueR1, 0);
+        BOOST_CHECK_EQUAL(r.countR2, 0);
+        BOOST_CHECK_EQUAL(r.value1R2, 0);
+        BOOST_CHECK_EQUAL(r.value2R2, "");
+    }
+
+    nullary.notify();
+
+    BOOST_CHECK_EQUAL(r.countR0, 3);
+    BOOST_CHECK_EQUAL(r.countR1, 0);
+    BOOST_CHECK_EQUAL(r.valueR1, 0);
+    BOOST_CHECK_EQUAL(r.countR2, 0);
+    BOOST_CHECK_EQUAL(r.value1R2, 0);
+    BOOST_CHECK_EQUAL(r.value2R2, "");
+
+    STG::Subscriptions<uint8_t> unary;
+    {
+        auto c1 = unary.add(r, &Receiver::r1);
+        auto c2 = unary.add([&r](const auto& v){ r.r1(v); });
+
+        unary.notify(42);
+
+        BOOST_CHECK_EQUAL(r.countR0, 3);
+        BOOST_CHECK_EQUAL(r.countR1, 2);
+        BOOST_CHECK_EQUAL(r.valueR1, 42);
+        BOOST_CHECK_EQUAL(r.countR2, 0);
+        BOOST_CHECK_EQUAL(r.value1R2, 0);
+        BOOST_CHECK_EQUAL(r.value2R2, "");
+
+        c1.disconnect();
+        unary.notify(13);
+
+        BOOST_CHECK_EQUAL(r.countR0, 3);
+        BOOST_CHECK_EQUAL(r.countR1, 3);
+        BOOST_CHECK_EQUAL(r.valueR1, 13);
+        BOOST_CHECK_EQUAL(r.countR2, 0);
+        BOOST_CHECK_EQUAL(r.value1R2, 0);
+        BOOST_CHECK_EQUAL(r.value2R2, "");
+    }
+
+    unary.notify(7);
+
+    BOOST_CHECK_EQUAL(r.countR0, 3);
+    BOOST_CHECK_EQUAL(r.countR1, 3);
+    BOOST_CHECK_EQUAL(r.valueR1, 13);
+    BOOST_CHECK_EQUAL(r.countR2, 0);
+    BOOST_CHECK_EQUAL(r.value1R2, 0);
+    BOOST_CHECK_EQUAL(r.value2R2, "");
+
+    STG::Subscriptions<uint8_t, std::string> binary;
+    {
+        auto c1 = binary.add(r, &Receiver::r2);
+        auto c2 = binary.add([&r](const auto& v1, const auto& v2){ r.r2(v1, v2); });
+
+        binary.notify(42, "Douglas");
+
+        BOOST_CHECK_EQUAL(r.countR0, 3);
+        BOOST_CHECK_EQUAL(r.countR1, 3);
+        BOOST_CHECK_EQUAL(r.valueR1, 13);
+        BOOST_CHECK_EQUAL(r.countR2, 2);
+        BOOST_CHECK_EQUAL(r.value1R2, 42);
+        BOOST_CHECK_EQUAL(r.value2R2, "Douglas");
+
+        c1.disconnect();
+        binary.notify(21, "Adams");
+
+        BOOST_CHECK_EQUAL(r.countR0, 3);
+        BOOST_CHECK_EQUAL(r.countR1, 3);
+        BOOST_CHECK_EQUAL(r.valueR1, 13);
+        BOOST_CHECK_EQUAL(r.countR2, 3);
+        BOOST_CHECK_EQUAL(r.value1R2, 21);
+        BOOST_CHECK_EQUAL(r.value2R2, "Adams");
+    }
+
+    binary.notify(13, "Devil's Dozen");
+
+    BOOST_CHECK_EQUAL(r.countR0, 3);
+    BOOST_CHECK_EQUAL(r.countR1, 3);
+    BOOST_CHECK_EQUAL(r.valueR1, 13);
+    BOOST_CHECK_EQUAL(r.countR2, 3);
+    BOOST_CHECK_EQUAL(r.value1R2, 21);
+    BOOST_CHECK_EQUAL(r.value2R2, "Adams");
+}
+
+BOOST_AUTO_TEST_SUITE_END()