diff --git a/include/etl/signal.h b/include/etl/signal.h new file mode 100644 index 000000000..82690daba --- /dev/null +++ b/include/etl/signal.h @@ -0,0 +1,250 @@ +///\file + +/****************************************************************************** +The MIT License(MIT) + +Embedded Template Library. +https://github.com/ETLCPP/etl +https://www.etlcpp.com + +Copyright(c) 2014 John Wellbelove, Mark Kitson + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files(the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions : + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +******************************************************************************/ + +#ifndef ETL_SIGNAL_INCLUDED +#define ETL_SIGNAL_INCLUDED + +#include + +#include "exception.h" +#include "error_handler.h" +#include "delegate.h" +#include "platform.h" + +#if defined(ETL_USING_STL) + #include + #include + +#if defined(ETL_CPP11_NOT_SUPPORTED) + #include "algorithm.h" +#endif // ETL_CPP11_NOT_SUPPORTED +#else + #include "algorithm.h" + #include "iterator.h" +#endif // ETL_USING_STL + +//***************************************************************************** +///\defgroup signal signal +/// Container that handles storing and calling callbacks. +///\ingroup containers +//***************************************************************************** + +namespace etl +{ + //*************************************************************************** + ///\ingroup signal + /// The base class for signal exceptions. + //*************************************************************************** + class signal_exception : public exception + { + public: + signal_exception(string_type reason_, string_type file_name_, numeric_type line_number_) + : exception{reason_, file_name_, line_number_} + { + } + }; + + class signal_full final : public signal_exception + { + public: + signal_full(string_type file_name_, numeric_type line_number_) + : signal_exception{ETL_ERROR_TEXT("signal:full", "A"), file_name_, line_number_} + { + } + }; + + + //*************************************************************************** + ///\ingroup signal + /// + ///\brief A lightweight signal class designed for efficient memory usage and + /// ability to store in ROM. + /// + ///\tparam T: Callback signature. + ///\tparam LENGTH: Maximum number of slots that can be connected to the signal. + ///\tparam TSlot: Function-object type or container type that can be invoked. + /// + ///\todo Support for return types other than void (aggregate etc.) + //*************************************************************************** + template > + class signal final + { + public: + using slot_type = TSlot; + using size_type = size_t; + + //************************************************************************* + /// Constructs the signal. + //************************************************************************* + template + ETL_CONSTEXPR14 explicit signal(TArgs&&... args) ETL_NOEXCEPT + : _slots{ETL_OR_STD::forward(args)...} + , _end{_slots + sizeof... (args)} + { + }; + + //************************************************************************* + ///\brief Invokes all the slots connected to the signal. + /// + ///\param args: Arguments to pass to the slots. + //************************************************************************* + template + void operator()(TArgs&&... args) const ETL_NOEXCEPT + { + ETL_OR_STD::for_each(begin(), end(), [&args...](const slot_type& s) { + s(ETL_OR_STD::forward(args)...); + }); + } + + //************************************************************************* + ///\brief Connects a slot to the signal. + /// + ///\param slot: To connect. + //************************************************************************* + void connect(const slot_type& slot) + { + ETL_ASSERT_OR_RETURN(!full(), ETL_ERROR(signal_full)); + (*_end) = slot; + _end = ETL_OR_STD::next(_end); + } + + //************************************************************************* + ///\brief Disconnects a slot from the signal. + /// + ///\param slot: To disconnect. + //************************************************************************* + void disconnect(const slot_type& slot) ETL_NOEXCEPT + { + const auto end_it = end(); + const auto it = ETL_OR_STD::find(begin(), end_it, slot); + if(it == end_it) + { + return; + } + + // Copy + replace idiom (expensive). Shifts all elements after 'it' one + // position to the left. + ETL_OR_STD::copy(ETL_OR_STD::next(it), end_it, it); + _end = ETL_OR_STD::prev(_end); + } + + //************************************************************************* + ///\brief Disconnects all slots from the signal. + //************************************************************************* + void disconnect_all() ETL_NOEXCEPT + { + _end = begin(); + } + + //************************************************************************* + ///\brief Checks if a slot is connected to the signal. + /// + ///\param slot: To check. + ///\return true if the slot is connected. + //************************************************************************* + ETL_CONSTEXPR bool connected(const slot_type& slot) const ETL_NOEXCEPT + { + return etl::any_of(begin(), end(), [&slot](const slot_type& s){return s == slot;}); + } + + //************************************************************************* + ///\return true if the signal has no slots connected. + //************************************************************************* + ETL_CONSTEXPR bool empty() const ETL_NOEXCEPT + { + return begin() == end(); + } + + //************************************************************************* + ///\return true if the signal has the maximum number of slots connected. + //************************************************************************* + ETL_CONSTEXPR bool full() const ETL_NOEXCEPT + { + return size() == max_size(); + } + + //************************************************************************* + ///\return Total number of slots that can be connected. + //************************************************************************* + ETL_CONSTEXPR size_type max_size() const ETL_NOEXCEPT + { + return LENGTH; + } + + //************************************************************************* + ///\return Total slots currently connected. + //************************************************************************* + ETL_CONSTEXPR size_type size() const ETL_NOEXCEPT + { + return ETL_OR_STD::distance(begin(), end()); + } + + private: + using iterator = slot_type*; + using const_iterator = const slot_type*; + + slot_type _slots[LENGTH]; + iterator _end; + + //************************************************************************* + ///\return Iterator to the beginning of the connected slots. + //************************************************************************* + ETL_CONSTEXPR14 iterator begin() ETL_NOEXCEPT + { + return _slots; + } + + //************************************************************************* + ///\return Const Iterator to the beginning of the connected slots. + //************************************************************************* + ETL_CONSTEXPR14 const_iterator begin() const ETL_NOEXCEPT + { + return _slots; + } + + //************************************************************************* + ///\return Iterator to the end of the connected slots. + //************************************************************************* + ETL_CONSTEXPR14 iterator end() ETL_NOEXCEPT + { + return _end; + } + + //************************************************************************* + ///\return Const Iterator to the end of the connected slots. + //************************************************************************* + ETL_CONSTEXPR14 const_iterator end() const ETL_NOEXCEPT + { + return _end; + } + }; +} + +#endif // ETL_SIGNAL_INCLUDED diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index ce9e2a664..2355ebdae 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -232,6 +232,7 @@ add_executable(etl_tests test_scaled_rounding.cpp test_set.cpp test_shared_message.cpp + test_signal.cpp test_singleton.cpp test_smallest.cpp test_span_dynamic_extent.cpp diff --git a/test/test_signal.cpp b/test/test_signal.cpp new file mode 100644 index 000000000..254fb3784 --- /dev/null +++ b/test/test_signal.cpp @@ -0,0 +1,313 @@ +/****************************************************************************** +The MIT License(MIT) + +Embedded Template Library. +https://github.com/ETLCPP/etl +https://www.etlcpp.com + +Copyright(c) 2014 John Wellbelove + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files(the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions : + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +******************************************************************************/ + +#include "UnitTest++/CheckMacros.h" +#include "unit_test_framework.h" + +#include +#include + +#include "etl/signal.h" + +namespace +{ + + constexpr auto total_output_methods_{5ULL + }; // free funciton, lambda, static class method, class method, functor. + + using signal_type = etl::signal; + using slot_type = signal_type::slot_type; + + //************************************************************************* + ///\brief Generic output free function + /// + ///\param out: output stream + ///\param str: input string + //************************************************************************* + void output(std::ostream& out, const std::string& str) + { + out << str; + } + + //************************************************************************* + ///\brief Free function that outputs "free" + /// + ///\param out: output + //************************************************************************* + void output_free(std::ostream& out) + { + output(out, "free"); + } + + //************************************************************************* + ///\brief Lambda that outputs "lambda" + /// + ///\param out: output + //************************************************************************* + auto output_lambda = [](std::ostream& out) { output(out, "lambda"); }; + + class example_class + { + public: + //************************************************************************* + ///\brief Method that outputs "static" + /// + ///\param out: output + //************************************************************************* + static void static_method(std::ostream& out) + { + output(out, "static"); + } + + //************************************************************************* + ///\brief Method that outputs "method" + /// + ///\param out: output + //************************************************************************* + void method(std::ostream& out) + { + output(out, "method"); + } + + //************************************************************************* + ///\brief Functor method that outputs "functor" + /// + ///\param out: output + //************************************************************************* + void operator()(std::ostream& out) + { + output(out, "functor"); + } + }; + + example_class example_; + + //************************************************************************* + ///\brief Makes the free function slot + /// + ///\return constexpr slot_type + //************************************************************************* + ETL_CONSTEXPR14 slot_type make_free_slot() + { + return slot_type::create(); + } + + //************************************************************************* + ///\brief Makes the lambda slot + /// + ///\return constexpr slot_type + //************************************************************************* + ETL_CONSTEXPR14 slot_type make_lambda_slot() + { + return slot_type{output_lambda}; + } + + //************************************************************************* + ///\brief Makes the static method slot + /// + ///\return constexpr slot_type + //************************************************************************* + ETL_CONSTEXPR14 slot_type make_static_slot() + { + return slot_type::create<&example_class::static_method>(); + } + + //************************************************************************* + ///\brief Makes the class method slot + /// + ///\return constexpr slot_type + //************************************************************************* + ETL_CONSTEXPR14 slot_type make_instance_slot() + { + return slot_type::create(); + } + + //************************************************************************* + ///\brief Makes the functor slot + /// + ///\return constexpr slot_type + //************************************************************************* + ETL_CONSTEXPR14 slot_type make_functor_slot() + { + return slot_type::create(); + } + +#if ETL_USING_CPP14 + constexpr signal_type constexpr_test_object_{ + make_free_slot(), + make_lambda_slot(), + make_static_slot(), + make_instance_slot(), + make_functor_slot() + }; +#endif // ETL_USING_CPP14 + + SUITE(signal_test) + { + TEST(construct) + { + signal_type test_object_; + CHECK_EQUAL(0U, test_object_.size()); + CHECK_EQUAL(total_output_methods_, test_object_.max_size()); + CHECK_TRUE(test_object_.empty()); + CHECK_FALSE(test_object_.full()); +#if ETL_USING_CPP14 + CHECK_EQUAL(total_output_methods_, constexpr_test_object_.size()); + CHECK_EQUAL(total_output_methods_, constexpr_test_object_.max_size()); + CHECK_FALSE(constexpr_test_object_.empty()); + CHECK_TRUE(constexpr_test_object_.full()); +#endif // ETL_USING_CPP14 + } + + TEST(connect) + { + signal_type test_object_; + const auto free_slot = make_free_slot(); + test_object_.connect(free_slot); + CHECK_EQUAL(1U, test_object_.size()); + CHECK_TRUE(test_object_.connected(free_slot)); + + const auto lambda_slot = make_lambda_slot(); + test_object_.connect(lambda_slot); + CHECK_EQUAL(2U, test_object_.size()); + CHECK_TRUE(test_object_.connected(lambda_slot)); + + const auto static_slot = make_static_slot(); + test_object_.connect(static_slot); + CHECK_EQUAL(3U, test_object_.size()); + CHECK_TRUE(test_object_.connected(static_slot)); + + const auto instance_slot = make_instance_slot(); + test_object_.connect(instance_slot); + CHECK_EQUAL(4U, test_object_.size()); + CHECK_TRUE(test_object_.connected(instance_slot)); + + const auto functor_slot = make_functor_slot(); + test_object_.connect(functor_slot); + CHECK_EQUAL(5U, test_object_.size()); + CHECK_TRUE(test_object_.connected(functor_slot)); + CHECK_TRUE(test_object_.full()); + } + + TEST(disconnect) + { + signal_type test_object_; + const auto free_slot = make_free_slot(); + const auto lambda_slot = make_lambda_slot(); + const auto static_slot = make_static_slot(); + const auto instance_slot = make_instance_slot(); + const auto functor_slot = make_functor_slot(); + test_object_.connect(free_slot); + test_object_.connect(lambda_slot); + test_object_.connect(static_slot); + test_object_.connect(instance_slot); + test_object_.connect(functor_slot); + + test_object_.disconnect(free_slot); + CHECK_EQUAL(4U, test_object_.size()); + CHECK_FALSE(test_object_.connected(free_slot)); + + // Try to remove it again - nothing should change. + test_object_.disconnect(free_slot); + CHECK_EQUAL(4U, test_object_.size()); + CHECK_FALSE(test_object_.connected(free_slot)); + + test_object_.disconnect(lambda_slot); + CHECK_EQUAL(3U, test_object_.size()); + CHECK_FALSE(test_object_.connected(lambda_slot)); + + test_object_.disconnect(static_slot); + CHECK_EQUAL(2U, test_object_.size()); + CHECK_FALSE(test_object_.connected(static_slot)); + + test_object_.disconnect(instance_slot); + CHECK_EQUAL(1U, test_object_.size()); + CHECK_FALSE(test_object_.connected(instance_slot)); + + test_object_.disconnect(functor_slot); + CHECK_EQUAL(0U, test_object_.size()); + CHECK_FALSE(test_object_.connected(functor_slot)); + } + } + + TEST(disconnect_all) + { + signal_type test_object_; + const auto free_slot = make_free_slot(); + const auto lambda_slot = make_lambda_slot(); + const auto static_slot = make_static_slot(); + const auto instance_slot = make_instance_slot(); + const auto functor_slot = make_functor_slot(); + test_object_.connect(free_slot); + test_object_.connect(lambda_slot); + test_object_.connect(static_slot); + test_object_.connect(instance_slot); + test_object_.connect(functor_slot); + + test_object_.disconnect_all(); + CHECK_EQUAL(0U, test_object_.size()); + CHECK_TRUE(test_object_.empty()); + CHECK_FALSE(test_object_.connected(free_slot)); + CHECK_FALSE(test_object_.connected(lambda_slot)); + CHECK_FALSE(test_object_.connected(static_slot)); + CHECK_FALSE(test_object_.connected(instance_slot)); + CHECK_FALSE(test_object_.connected(functor_slot)); + } + + TEST(call_empty) + { + std::stringstream ss; + signal_type test_object_; + test_object_(ss); + CHECK_EQUAL(std::string{""}, ss.str()); + } + + TEST(call) + { + signal_type test_object_; + test_object_.connect(make_free_slot()); + test_object_.connect(make_lambda_slot()); + test_object_.connect(make_static_slot()); + test_object_.connect(make_instance_slot()); + test_object_.connect(make_functor_slot()); + + std::stringstream ss; + test_object_(ss); + + // expect all signals got called + const std::string expected_string{"freelambdastaticmethodfunctor"}; + CHECK_EQUAL(expected_string, ss.str()); + +#if ETL_USING_CPP14 + std::stringstream ss2; + constexpr_test_object_(ss2); + CHECK_EQUAL(expected_string, ss2.str()); +#endif // ETL_USING_CPP14 + } + +}