From: Maksym Mamontov Date: Tue, 23 Aug 2022 11:21:12 +0000 (+0300) Subject: Add subscriptions (to replace notifiers). X-Git-Url: https://git.stg.codes/stg.git/commitdiff_plain/1c529746ff07312e30e76fd933c628c658e3c77d Add subscriptions (to replace notifiers). --- diff --git a/include/stg/subscriptions.h b/include/stg/subscriptions.h new file mode 100644 index 00000000..1486e62d --- /dev/null +++ b/include/stg/subscriptions.h @@ -0,0 +1,84 @@ +#pragma once + +#include +#include +#include + +namespace STG +{ + +template +class Subscriptions +{ + public: + using Callback = std::function; + using Callbacks = std::list; + + 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 + Connection add(F&& f) + { + std::lock_guard lock(m_mutex); + return Connection(*this, m_callbacks.insert(m_callbacks.end(), Callback(std::forward(f)))); + } + + template + Connection add(C& c, void (C::*m)(T2s...)) + { + return add([&c, m](Ts&&... values){ (c.*m)(std::forward(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; +}; + +} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 250f5e06..a552cfbe 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -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 ) + +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 index 00000000..ebc3c920 --- /dev/null +++ b/tests/test_subscriptions.cpp @@ -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 +#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 unary; + BOOST_CHECK(unary.empty()); + BOOST_CHECK_EQUAL(unary.size(), 0); + + STG::Subscriptions 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 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 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 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 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()