diff --git a/include/etl/callback_timer_deferred_locked.h b/include/etl/callback_timer_deferred_locked.h new file mode 100644 index 000000000..104699787 --- /dev/null +++ b/include/etl/callback_timer_deferred_locked.h @@ -0,0 +1,209 @@ +/****************************************************************************** +The MIT License(MIT) + +Embedded Template Library. +https://github.com/ETLCPP/etl +https://www.etlcpp.com + +Copyright(c) 2021 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. +******************************************************************************/ + +#ifndef ETL_CALLBACK_TIMER_DEFERRED_LOCKED_INCLUDED +#define ETL_CALLBACK_TIMER_DEFERRED_LOCKED_INCLUDED + +#include "callback_timer_locked.h" +#include "etl/nullptr.h" +#include "etl/optional.h" +#include "priority_queue.h" +#include "delegate.h" + + +namespace etl +{ + //*************************************************************************** + /// The deferred callback timer + //*************************************************************************** + template + class callback_timer_deferred_locked : public etl::icallback_timer_locked + { + public: + + ETL_STATIC_ASSERT(MAX_TIMERS_ <= 254U, "No more than 254 timers are allowed"); + + typedef icallback_timer_locked::callback_type callback_type; + typedef icallback_timer_locked::try_lock_type try_lock_type; + typedef icallback_timer_locked::lock_type lock_type; + typedef icallback_timer_locked::unlock_type unlock_type; + + private: + class CallbackNode { + public: + callback_type callback; + uint_least8_t priority; + CallbackNode(callback_type &callback_,uint_least8_t priority_) : callback(callback_), priority(priority_) {} + bool operator < (const CallbackNode& p) const + { + return this->priority > p.priority; // comparison was inverted here to easy the code design + } + }; + + public: + //******************************************* + /// Constructor. + //******************************************* + callback_timer_deferred_locked() + : icallback_timer_locked(timer_array, MAX_TIMERS_) + { + } + + //******************************************* + /// Constructor. + //******************************************* + callback_timer_deferred_locked(try_lock_type try_lock_, lock_type lock_, unlock_type unlock_) + : icallback_timer_locked(timer_array, MAX_TIMERS_) + { + this->set_locks(try_lock_, lock_, unlock_); + } + + // Implement virtual functions + + bool tick(uint32_t count) final + { + if (enabled) + { + if (try_lock()) + { + // We have something to do? + bool has_active = !active_list.empty(); + + if (has_active) + { + while (has_active && (count >= active_list.front().delta)) + { + timer_data& timer = active_list.front(); + + count -= timer.delta; + + active_list.remove(timer.id, true); + + if (timer.callback.is_valid()) + { + if (!handler_queue.full()) + { + handler_queue.push(CallbackNode(timer.callback, timer_priorities[timer.id])); + } + } + + if (timer.repeating) + { + // Reinsert the timer. + timer.delta = timer.period; + active_list.insert(timer.id); + } + + has_active = !active_list.empty(); + } + + if (has_active) + { + // Subtract any remainder from the next due timeout. + active_list.front().delta -= count; + } + } + + unlock(); + + return true; + } + } + + return false; + } + + //******************************************* + /// Handles the work collected during the tick() call + /// You can call this function after tick() + /// or you can call this on another task to handle the timer events. + //******************************************* + void handle_deferred(void) + { + callback_type work_todo_callback; + do + { + lock(); + if (handler_queue.empty()) + { + work_todo_callback.clear(); + } + else + { + CallbackNode &work_todo_callback_node = handler_queue.top(); + work_todo_callback = work_todo_callback_node.callback; + handler_queue.pop(); + } + unlock(); + + work_todo_callback.call_if(); + }while (work_todo_callback.is_valid()); + } + + // Overloads + + //******************************************* + /// Register a timer. + //******************************************* + etl::timer::id::type register_timer(const callback_type& callback_, + uint32_t period_, + bool repeating_) + { + return register_timer(callback_, period_, repeating_, 0); + } + + //******************************************* + /// Register a timer with priority. + /// priority_ 0 is highest priority, 255 is lowest + /// Suggestion: this could be used as amonth of work to do and + /// less work will be done in first place. + //******************************************* + etl::timer::id::type register_timer(const callback_type& callback_, + uint32_t period_, + bool repeating_, + uint_least8_t priority_) + { + etl::timer::id::type id = icallback_timer_locked::register_timer(callback_, period_, repeating_); + + if (id != etl::timer::id::NO_TIMER) + { + timer_priorities[id] = priority_; + } + + return id; + } + + private: + + priority_queue handler_queue; + uint_least8_t timer_priorities[MAX_TIMERS_]; + timer_data timer_array[MAX_TIMERS_]; + }; +} + +#endif diff --git a/include/etl/callback_timer_locked.h b/include/etl/callback_timer_locked.h index 5d9d84c31..ff1fcc954 100644 --- a/include/etl/callback_timer_locked.h +++ b/include/etl/callback_timer_locked.h @@ -608,6 +608,9 @@ namespace etl template friend class callback_timer_locked; + template + friend class callback_timer_deferred_locked; + const uint_least8_t MAX_TIMERS; }; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index ce9e2a664..ee6a3f167 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -56,6 +56,7 @@ add_executable(etl_tests test_callback_timer_atomic.cpp test_callback_timer_interrupt.cpp test_callback_timer_locked.cpp + test_callback_timer_deferred_locked.cpp test_char_traits.cpp test_checksum.cpp test_circular_buffer.cpp diff --git a/test/test_callback_timer_deferred_locked.cpp b/test/test_callback_timer_deferred_locked.cpp new file mode 100644 index 000000000..b95f6083b --- /dev/null +++ b/test/test_callback_timer_deferred_locked.cpp @@ -0,0 +1,1213 @@ +/****************************************************************************** +The MIT License(MIT) + +Embedded Template Library. +https://github.com/ETLCPP/etl +https://www.etlcpp.com + +Copyright(c) 2021 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 "unit_test_framework.h" + +#include "etl/callback_timer_deferred_locked.h" +#include "etl/delegate.h" + +#include +#include +#include +#include +#include + +#if defined(ETL_COMPILER_MICROSOFT) + #include +#endif + +#define REALTIME_TEST 0 + +namespace +{ + uint64_t ticks = 0ULL; + + //*************************************************************************** + struct Locks + { + Locks() + : lock_count(0) + { + } + + void clear() + { + lock_count = 0; + } + + bool try_lock() + { + ++lock_count; + return true; + } + + void lock() + { + ++lock_count; + } + + void unlock() + { + --lock_count; + } + + int lock_count; + }; + + Locks locks; + + //*************************************************************************** + // Class callback via etl::function + //*************************************************************************** + class Object + { + public: + + Object() + : p_controller(nullptr) + { + } + + void callback() + { + tick_list.push_back(ticks); + } + + void callback2() + { + tick_list.push_back(ticks); + + p_controller->start(2); + p_controller->start(1); + } + + // Used for test priority + void callback_inc1() + { + tick_inc1 = ticks; + tick_list.push_back(ticks); + ticks++; + } + + void callback_inc2() + { + tick_inc2 = ticks; + tick_list.push_back(ticks); + ticks++; + } + + void callback_inc3() + { + tick_inc3 = ticks; + tick_list.push_back(ticks); + ticks++; + } + + void set_controller(etl::callback_timer_deferred_locked<3, 3>& controller) + { + p_controller = &controller; + } + + uint64_t tick_inc1; + uint64_t tick_inc2; + uint64_t tick_inc3; + + std::vector tick_list; + + etl::callback_timer_deferred_locked<3, 3>* p_controller; + }; + + using callback_type = etl::icallback_timer_locked::callback_type; + using try_lock_type = etl::icallback_timer_locked::try_lock_type; + using lock_type = etl::icallback_timer_locked::lock_type; + using unlock_type = etl::icallback_timer_locked::unlock_type; + + Object object; + callback_type member_callback = callback_type::create(); + callback_type member_callback2 = callback_type::create(); + callback_type member_callback_inc1 = callback_type::create(); + callback_type member_callback_inc2 = callback_type::create(); + callback_type member_callback_inc3 = callback_type::create(); + + //*************************************************************************** + // Free function callback via etl::function + //*************************************************************************** + std::vector free_tick_list1; + + void free_callback1() + { + free_tick_list1.push_back(ticks); + } + + callback_type free_function_callback = callback_type::create(); + + //*************************************************************************** + // Free function callback via function pointer + //*************************************************************************** + std::vector free_tick_list2; + + void free_callback2() + { + free_tick_list2.push_back(ticks); + } + + callback_type free_function_callback2 = callback_type::create(); + + SUITE(test_callback_timer_deferred_locked) + { + //************************************************************************* + TEST(callback_timer_deferred_locked_too_many_timers) + { + locks.clear(); + try_lock_type try_lock = try_lock_type::create(); + lock_type lock = lock_type::create(); + unlock_type unlock = unlock_type::create(); + + etl::callback_timer_deferred_locked<2, 2> timer_controller(try_lock, lock, unlock); + + etl::timer::id::type id1 = timer_controller.register_timer(member_callback, 37, etl::timer::mode::Single_Shot); + etl::timer::id::type id2 = timer_controller.register_timer(free_function_callback, 23, etl::timer::mode::Single_Shot); + etl::timer::id::type id3 = timer_controller.register_timer(free_function_callback2, 11, etl::timer::mode::Single_Shot); + + CHECK(id1 != etl::timer::id::NO_TIMER); + CHECK(id2 != etl::timer::id::NO_TIMER); + CHECK(id3 == etl::timer::id::NO_TIMER); + + timer_controller.clear(); + id3 = timer_controller.register_timer(free_function_callback2, 11, etl::timer::mode::Single_Shot); + CHECK(id3 != etl::timer::id::NO_TIMER); + + CHECK_EQUAL(0U, locks.lock_count); + } + + //************************************************************************* + TEST(callback_timer_deferred_locked_one_shot) + { + locks.clear(); + try_lock_type try_lock = try_lock_type::create(); + lock_type lock = lock_type::create(); + unlock_type unlock = unlock_type::create(); + + etl::callback_timer_deferred_locked<4, 4> timer_controller(try_lock, lock, unlock); + + etl::timer::id::type id1 = timer_controller.register_timer(member_callback, 37, etl::timer::mode::Single_Shot); + etl::timer::id::type id2 = timer_controller.register_timer(free_function_callback, 23, etl::timer::mode::Single_Shot); + etl::timer::id::type id3 = timer_controller.register_timer(free_function_callback2, 11, etl::timer::mode::Single_Shot); + + object.tick_list.clear(); + free_tick_list1.clear(); + free_tick_list2.clear(); + + timer_controller.start(id1); + timer_controller.start(id3); + timer_controller.start(id2); + + timer_controller.enable(true); + + ticks = 0; + + const uint32_t step = 1UL; + + while (ticks <= 100U) + { + ticks += step; + timer_controller.tick(step); + timer_controller.handle_deferred(); + } + + std::vector compare1 = { 37 }; + std::vector compare2 = { 23 }; + std::vector compare3 = { 11 }; + + CHECK(object.tick_list.size() != 0); + CHECK(free_tick_list1.size() != 0); + CHECK(free_tick_list2.size() != 0); + + CHECK_ARRAY_EQUAL(compare1.data(), object.tick_list.data(), compare1.size()); + CHECK_ARRAY_EQUAL(compare2.data(), free_tick_list1.data(), compare2.size()); + CHECK_ARRAY_EQUAL(compare3.data(), free_tick_list2.data(), compare3.size()); + + CHECK_EQUAL(0U, locks.lock_count); + } + + TEST(callback_timer_deferred_locked_one_shot_priority) + { + locks.clear(); + try_lock_type try_lock = try_lock_type::create(); + lock_type lock = lock_type::create(); + unlock_type unlock = unlock_type::create(); + + etl::callback_timer_deferred_locked<4, 4> timer_controller(try_lock, lock, unlock); + + etl::timer::id::type id1 = timer_controller.register_timer(member_callback_inc1, 1, etl::timer::mode::Single_Shot, 0); + etl::timer::id::type id2 = timer_controller.register_timer(member_callback_inc2, 1, etl::timer::mode::Single_Shot, 1); + etl::timer::id::type id3 = timer_controller.register_timer(member_callback_inc3, 1, etl::timer::mode::Single_Shot, 2); + + object.tick_list.clear(); + + timer_controller.start(id1); + timer_controller.start(id3); + timer_controller.start(id2); + + timer_controller.enable(true); + + ticks = 0; + + const uint32_t step = 1UL; + + while (ticks < 1UL) + { + ticks += step; + timer_controller.tick(step); + } + + ticks = 0; + timer_controller.handle_deferred(); + + std::vector compare1 = { 0, 1, 2 }; + + CHECK(object.tick_list.size() != 0); + + CHECK_ARRAY_EQUAL(compare1.data(), object.tick_list.data(), compare1.size()); + + CHECK_EQUAL(0U, object.tick_inc1); + CHECK_EQUAL(1U, object.tick_inc2); + CHECK_EQUAL(2U, object.tick_inc3); + + CHECK_EQUAL(0U, locks.lock_count); + } + + //************************************************************************* + TEST(message_timer_one_shot_after_timeout) + { + locks.clear(); + try_lock_type try_lock = try_lock_type::create(); + lock_type lock = lock_type::create(); + unlock_type unlock = unlock_type::create(); + + etl::callback_timer_deferred_locked<1, 1> timer_controller(try_lock, lock, unlock); + + etl::timer::id::type id1 = timer_controller.register_timer(member_callback, 37, etl::timer::mode::Single_Shot); + object.tick_list.clear(); + + timer_controller.start(id1); + timer_controller.enable(true); + + ticks = 0; + + const uint32_t step = 1U; + + while (ticks <= 100U) + { + ticks += step; + timer_controller.tick(step); + timer_controller.handle_deferred(); + } + + // Timer should have timed out. + + CHECK(timer_controller.set_period(id1, 50)); + timer_controller.start(id1); + + object.tick_list.clear(); + + ticks = 0; + + while (ticks <= 100U) + { + ticks += step; + timer_controller.tick(step); + timer_controller.handle_deferred(); + } + + // Timer should have timed out. + + CHECK_EQUAL(50U, *object.tick_list.data()); + + CHECK(timer_controller.unregister_timer(id1)); + CHECK(!timer_controller.unregister_timer(id1)); + CHECK(!timer_controller.start(id1)); + CHECK(!timer_controller.stop(id1)); + + CHECK_EQUAL(0U, locks.lock_count); + } + + //************************************************************************* + TEST(callback_timer_deferred_locked_repeating) + { + locks.clear(); + try_lock_type try_lock = try_lock_type::create(); + lock_type lock = lock_type::create(); + unlock_type unlock = unlock_type::create(); + + etl::callback_timer_deferred_locked<3, 3> timer_controller(try_lock, lock, unlock); + + etl::timer::id::type id1 = timer_controller.register_timer(member_callback, 37, etl::timer::mode::Repeating); + etl::timer::id::type id2 = timer_controller.register_timer(free_function_callback, 23, etl::timer::mode::Repeating); + etl::timer::id::type id3 = timer_controller.register_timer(free_function_callback2, 11, etl::timer::mode::Repeating); + + object.tick_list.clear(); + free_tick_list1.clear(); + free_tick_list2.clear(); + + timer_controller.start(id1); + timer_controller.start(id3); + timer_controller.start(id2); + + timer_controller.enable(true); + + ticks = 0; + + const uint32_t step = 1U; + + while (ticks <= 100U) + { + ticks += step; + timer_controller.tick(step); + timer_controller.handle_deferred(); + } + + std::vector compare1 = { 37, 74 }; + std::vector compare2 = { 23, 46, 69, 92 }; + std::vector compare3 = { 11, 22, 33, 44, 55, 66, 77, 88, 99 }; + + CHECK(object.tick_list.size() != 0); + CHECK(free_tick_list1.size() != 0); + CHECK(free_tick_list2.size() != 0); + + CHECK_ARRAY_EQUAL(compare1.data(), object.tick_list.data(), compare1.size()); + CHECK_ARRAY_EQUAL(compare2.data(), free_tick_list1.data(), compare2.size()); + CHECK_ARRAY_EQUAL(compare3.data(), free_tick_list2.data(), compare3.size()); + + CHECK_EQUAL(0U, locks.lock_count); + } + + //************************************************************************* + TEST(callback_timer_deferred_locked_repeating_bigger_step) + { + locks.clear(); + try_lock_type try_lock = try_lock_type::create(); + lock_type lock = lock_type::create(); + unlock_type unlock = unlock_type::create(); + + etl::callback_timer_deferred_locked<3, 3> timer_controller(try_lock, lock, unlock); + + etl::timer::id::type id1 = timer_controller.register_timer(member_callback, 37, etl::timer::mode::Repeating); + etl::timer::id::type id2 = timer_controller.register_timer(free_function_callback, 23, etl::timer::mode::Repeating); + etl::timer::id::type id3 = timer_controller.register_timer(free_function_callback2, 11, etl::timer::mode::Repeating); + + object.tick_list.clear(); + free_tick_list1.clear(); + free_tick_list2.clear(); + + timer_controller.start(id1); + timer_controller.start(id3); + timer_controller.start(id2); + + CHECK(!timer_controller.is_running()); + + timer_controller.enable(true); + + CHECK(timer_controller.is_running()); + + ticks = 0; + + const uint32_t step = 5U; + + while (ticks <= 100U) + { + ticks += step; + timer_controller.tick(step); + timer_controller.handle_deferred(); + } + + std::vector compare1 = { 40, 75 }; + std::vector compare2 = { 25, 50, 70, 95 }; + std::vector compare3 = { 15, 25, 35, 45, 55, 70, 80, 90, 100 }; + + CHECK(object.tick_list.size() != 0); + CHECK(free_tick_list1.size() != 0); + CHECK(free_tick_list2.size() != 0); + + CHECK_ARRAY_EQUAL(compare1.data(), object.tick_list.data(), compare1.size()); + CHECK_ARRAY_EQUAL(compare2.data(), free_tick_list1.data(), compare2.size()); + CHECK_ARRAY_EQUAL(compare3.data(), free_tick_list2.data(), compare3.size()); + + CHECK_EQUAL(0U, locks.lock_count); + } + + //************************************************************************* + TEST(callback_timer_deferred_locked_repeating_stop_start) + { + locks.clear(); + try_lock_type try_lock = try_lock_type::create(); + lock_type lock = lock_type::create(); + unlock_type unlock = unlock_type::create(); + + etl::callback_timer_deferred_locked<3, 3> timer_controller(try_lock, lock, unlock); + + etl::timer::id::type id1 = timer_controller.register_timer(member_callback, 37, etl::timer::mode::Repeating); + etl::timer::id::type id2 = timer_controller.register_timer(free_function_callback, 23, etl::timer::mode::Repeating); + etl::timer::id::type id3 = timer_controller.register_timer(free_function_callback2, 11, etl::timer::mode::Repeating); + + object.tick_list.clear(); + free_tick_list1.clear(); + free_tick_list2.clear(); + + timer_controller.start(id3); + timer_controller.start(id2); + + timer_controller.enable(true); + + ticks = 0; + + const uint32_t step = 1U; + + while (ticks <= 100U) + { + if (ticks == 40) + { + timer_controller.start(id1); + timer_controller.stop(id2); + } + + if (ticks == 80) + { + timer_controller.stop(id1); + timer_controller.start(id2); + } + + ticks += step; + timer_controller.tick(step); + timer_controller.handle_deferred(); + } + + std::vector compare1 = { 77 }; + std::vector compare2 = { 23 }; + std::vector compare3 = { 11, 22, 33, 44, 55, 66, 77, 88, 99 }; + + CHECK(object.tick_list.size() != 0); + CHECK(free_tick_list1.size() != 0); + CHECK(free_tick_list2.size() != 0); + + CHECK_ARRAY_EQUAL(compare1.data(), object.tick_list.data(), compare1.size()); + CHECK_ARRAY_EQUAL(compare2.data(), free_tick_list1.data(), compare2.size()); + CHECK_ARRAY_EQUAL(compare3.data(), free_tick_list2.data(), compare3.size()); + + CHECK_EQUAL(0U, locks.lock_count); + } + + //************************************************************************* + TEST(callback_timer_deferred_locked_timer_starts_timer_small_step) + { + locks.clear(); + try_lock_type try_lock = try_lock_type::create(); + lock_type lock = lock_type::create(); + unlock_type unlock = unlock_type::create(); + + etl::callback_timer_deferred_locked<3, 3> timer_controller(try_lock, lock, unlock); + + etl::timer::id::type id1 = timer_controller.register_timer(member_callback2, 100, etl::timer::mode::Single_Shot); + etl::timer::id::type id2 = timer_controller.register_timer(member_callback, 10, etl::timer::mode::Single_Shot); + etl::timer::id::type id3 = timer_controller.register_timer(member_callback, 22, etl::timer::mode::Single_Shot); + + (void)id2; + (void)id3; + + object.set_controller(timer_controller); + + object.tick_list.clear(); + + timer_controller.start(id1); + + timer_controller.enable(true); + + ticks = 0; + + const uint32_t step = 1U; + + while (ticks <= 200U) + { + ticks += step; + timer_controller.tick(step); + timer_controller.handle_deferred(); + } + + std::vector compare1 = { 100, 110, 122 }; + + CHECK(object.tick_list.size() != 0); + + CHECK_ARRAY_EQUAL(compare1.data(), object.tick_list.data(), compare1.size()); + + CHECK_EQUAL(0U, locks.lock_count); + } + + //************************************************************************* + TEST(callback_timer_deferred_locked_timer_starts_timer_big_step) + { + locks.clear(); + try_lock_type try_lock = try_lock_type::create(); + lock_type lock = lock_type::create(); + unlock_type unlock = unlock_type::create(); + + etl::callback_timer_deferred_locked<3, 3> timer_controller(try_lock, lock, unlock); + + etl::timer::id::type id1 = timer_controller.register_timer(member_callback2, 100, etl::timer::mode::Single_Shot); + etl::timer::id::type id2 = timer_controller.register_timer(member_callback, 10, etl::timer::mode::Single_Shot); + etl::timer::id::type id3 = timer_controller.register_timer(member_callback, 22, etl::timer::mode::Single_Shot); + + (void)id2; + (void)id3; + + object.set_controller(timer_controller); + + object.tick_list.clear(); + + timer_controller.start(id1); + + timer_controller.enable(true); + + ticks = 0; + + const uint32_t step = 3; + + while (ticks <= 200U) + { + ticks += step; + timer_controller.tick(step); + timer_controller.handle_deferred(); + } + + std::vector compare1 = { 102, 114, 126 }; + + CHECK(object.tick_list.size() != 0); + + CHECK_ARRAY_EQUAL(compare1.data(), object.tick_list.data(), compare1.size()); + + CHECK_EQUAL(0U, locks.lock_count); + } + + //************************************************************************* + TEST(callback_timer_deferred_locked_repeating_register_unregister) + { + locks.clear(); + try_lock_type try_lock = try_lock_type::create(); + lock_type lock = lock_type::create(); + unlock_type unlock = unlock_type::create(); + + etl::callback_timer_deferred_locked<3, 3> timer_controller(try_lock, lock, unlock); + + etl::timer::id::type id1; + etl::timer::id::type id2 = timer_controller.register_timer(free_function_callback, 23, etl::timer::mode::Repeating); + etl::timer::id::type id3 = timer_controller.register_timer(free_function_callback2, 11, etl::timer::mode::Repeating); + + object.tick_list.clear(); + free_tick_list1.clear(); + free_tick_list2.clear(); + + timer_controller.start(id3); + timer_controller.start(id2); + + timer_controller.enable(true); + + ticks = 0; + + const uint32_t step = 1U; + + while (ticks <= 100U) + { + if (ticks == 40) + { + timer_controller.unregister_timer(id2); + + id1 = timer_controller.register_timer(member_callback, 37, etl::timer::mode::Repeating); + timer_controller.start(id1); + } + + ticks += step; + timer_controller.tick(step); + timer_controller.handle_deferred(); + } + + std::vector compare1 = { 77 }; + std::vector compare2 = { 23 }; + std::vector compare3 = { 11, 22, 33, 44, 55, 66, 77, 88, 99 }; + + CHECK(object.tick_list.size() != 0); + CHECK(free_tick_list1.size() != 0); + CHECK(free_tick_list2.size() != 0); + + CHECK_ARRAY_EQUAL(compare1.data(), object.tick_list.data(), compare1.size()); + CHECK_ARRAY_EQUAL(compare2.data(), free_tick_list1.data(), compare2.size()); + CHECK_ARRAY_EQUAL(compare3.data(), free_tick_list2.data(), compare3.size()); + + CHECK_EQUAL(0U, locks.lock_count); + } + + //************************************************************************* + TEST(callback_timer_deferred_locked_repeating_clear) + { + locks.clear(); + try_lock_type try_lock = try_lock_type::create(); + lock_type lock = lock_type::create(); + unlock_type unlock = unlock_type::create(); + + etl::callback_timer_deferred_locked<3, 3> timer_controller(try_lock, lock, unlock); + + etl::timer::id::type id1 = timer_controller.register_timer(member_callback, 37, etl::timer::mode::Repeating); + etl::timer::id::type id2 = timer_controller.register_timer(free_function_callback, 23, etl::timer::mode::Repeating); + etl::timer::id::type id3 = timer_controller.register_timer(free_function_callback2, 11, etl::timer::mode::Repeating); + + object.tick_list.clear(); + free_tick_list1.clear(); + free_tick_list2.clear(); + + timer_controller.start(id1); + timer_controller.start(id3); + timer_controller.start(id2); + + timer_controller.enable(true); + + ticks = 0; + + const uint32_t step = 1U; + + while (ticks <= 100U) + { + ticks += step; + + if (ticks == 40) + { + timer_controller.clear(); + } + + timer_controller.tick(step); + timer_controller.handle_deferred(); + } + + std::vector compare1 = { 37 }; + std::vector compare2 = { 23 }; + std::vector compare3 = { 11, 22, 33 }; + + CHECK(object.tick_list.size() != 0); + CHECK(free_tick_list1.size() != 0); + CHECK(free_tick_list2.size() != 0); + + CHECK_ARRAY_EQUAL(compare1.data(), object.tick_list.data(), compare1.size()); + CHECK_ARRAY_EQUAL(compare2.data(), free_tick_list1.data(), compare2.size()); + CHECK_ARRAY_EQUAL(compare3.data(), free_tick_list2.data(), compare3.size()); + + CHECK_EQUAL(0U, locks.lock_count); + } + + //************************************************************************* + TEST(callback_timer_deferred_locked_delayed_immediate) + { + locks.clear(); + try_lock_type try_lock = try_lock_type::create(); + lock_type lock = lock_type::create(); + unlock_type unlock = unlock_type::create(); + + etl::callback_timer_deferred_locked<3, 3> timer_controller(try_lock, lock, unlock); + + etl::timer::id::type id1 = timer_controller.register_timer(member_callback, 37, etl::timer::mode::Repeating); + etl::timer::id::type id2 = timer_controller.register_timer(free_function_callback, 23, etl::timer::mode::Repeating); + etl::timer::id::type id3 = timer_controller.register_timer(free_function_callback2, 11, etl::timer::mode::Repeating); + + object.tick_list.clear(); + free_tick_list1.clear(); + free_tick_list2.clear(); + + timer_controller.enable(true); + + ticks = 5; + timer_controller.tick(uint32_t(ticks)); + timer_controller.handle_deferred(); + + timer_controller.start(id1, etl::timer::start::Immediate); + timer_controller.start(id2, etl::timer::start::Immediate); + timer_controller.start(id3, etl::timer::start::Delayed); + + const uint32_t step = 1U; + + while (ticks <= 100U) + { + ticks += step; + timer_controller.tick(step); + timer_controller.handle_deferred(); + } + + std::vector compare1 = { 6, 42, 79 }; + std::vector compare2 = { 6, 28, 51, 74, 97 }; + std::vector compare3 = { 16, 27, 38, 49, 60, 71, 82, 93 }; + + CHECK(object.tick_list.size() != 0); + CHECK(free_tick_list1.size() != 0); + CHECK(free_tick_list2.size() != 0); + + CHECK_ARRAY_EQUAL(compare1.data(), object.tick_list.data(), compare1.size()); + CHECK_ARRAY_EQUAL(compare2.data(), free_tick_list1.data(), compare2.size()); + CHECK_ARRAY_EQUAL(compare3.data(), free_tick_list2.data(), compare3.size()); + + CHECK_EQUAL(0U, locks.lock_count); + } + + //************************************************************************* + TEST(callback_timer_deferred_locked_one_shot_big_step_short_delay_insert) + { + locks.clear(); + try_lock_type try_lock = try_lock_type::create(); + lock_type lock = lock_type::create(); + unlock_type unlock = unlock_type::create(); + + etl::callback_timer_deferred_locked<3, 3> timer_controller(try_lock, lock, unlock); + + etl::timer::id::type id1 = timer_controller.register_timer(free_function_callback, 15, etl::timer::mode::Single_Shot); + etl::timer::id::type id2 = timer_controller.register_timer(free_function_callback2, 5, etl::timer::mode::Repeating); + + free_tick_list1.clear(); + free_tick_list2.clear(); + + timer_controller.start(id1); + timer_controller.start(id2); + + timer_controller.enable(true); + + ticks = 0; + + const uint32_t step = 11U; + + ticks += step; + timer_controller.tick(step); + timer_controller.handle_deferred(); + + ticks += step; + timer_controller.tick(step); + timer_controller.handle_deferred(); + + std::vector compare1 = { 22 }; + std::vector compare2 = { 11, 11, 22, 22 }; + + CHECK(free_tick_list1.size() != 0); + CHECK(free_tick_list2.size() != 0); + + CHECK_ARRAY_EQUAL(compare1.data(), free_tick_list1.data(), compare1.size()); + CHECK_ARRAY_EQUAL(compare2.data(), free_tick_list2.data(), compare2.size()); + + CHECK_EQUAL(0U, locks.lock_count); + } + + //************************************************************************* + TEST(callback_timer_deferred_locked_one_shot_empty_list_huge_tick_before_insert) + { + locks.clear(); + try_lock_type try_lock = try_lock_type::create(); + lock_type lock = lock_type::create(); + unlock_type unlock = unlock_type::create(); + + etl::callback_timer_deferred_locked<3, 3> timer_controller(try_lock, lock, unlock); + + etl::timer::id::type id1 = timer_controller.register_timer(free_function_callback, 5, etl::timer::mode::Single_Shot); + + free_tick_list1.clear(); + + timer_controller.start(id1); + + timer_controller.enable(true); + + ticks = 0; + + const uint32_t step = 5U; + + for (uint32_t i = 0U; i < step; ++i) + { + ++ticks; + timer_controller.tick(1); + timer_controller.handle_deferred(); + } + + // Huge tick count. + timer_controller.tick(UINT32_MAX - step + 1); + timer_controller.handle_deferred(); + + timer_controller.start(id1); + + for (uint32_t i = 0U; i < step; ++i) + { + ++ticks; + timer_controller.tick(1); + timer_controller.handle_deferred(); + } + std::vector compare1 = { 5, 10 }; + + CHECK(free_tick_list1.size() != 0); + + CHECK_ARRAY_EQUAL(compare1.data(), free_tick_list1.data(), compare1.size()); + + CHECK_EQUAL(0U, locks.lock_count); + } + + //************************************************************************* + class test_object + { + public: + + void call() + { + ++called; + } + + size_t called = 0UL; + }; + + TEST(callback_timer_deferred_locked_call_etl_delegate) + { + test_object test_obj; + callback_type delegate_callback = callback_type::create(test_obj); + + locks.clear(); + try_lock_type try_lock = try_lock_type::create(); + lock_type lock = lock_type::create(); + unlock_type unlock = unlock_type::create(); + + etl::callback_timer_deferred_locked<1, 1> timer_controller(try_lock, lock, unlock); + + timer_controller.enable(true); + + etl::timer::id::type id = timer_controller.register_timer(delegate_callback, 5, etl::timer::mode::Single_Shot); + timer_controller.start(id); + + timer_controller.tick(4); + timer_controller.handle_deferred(); + CHECK(test_obj.called == 0); + + timer_controller.tick(2); + timer_controller.handle_deferred(); + CHECK(test_obj.called == 1); + + CHECK_EQUAL(0U, locks.lock_count); + } + + //************************************************************************* + TEST(message_timer_time_to_next) + { + locks.clear(); + try_lock_type try_lock = try_lock_type::create(); + lock_type lock = lock_type::create(); + unlock_type unlock = unlock_type::create(); + + etl::callback_timer_deferred_locked<3, 3> timer_controller(try_lock, lock, unlock); + + etl::timer::id::type id1 = timer_controller.register_timer(member_callback, 37, etl::timer::mode::Repeating); + etl::timer::id::type id2 = timer_controller.register_timer(free_function_callback, 23, etl::timer::mode::Repeating); + etl::timer::id::type id3 = timer_controller.register_timer(free_function_callback2, 11, etl::timer::mode::Repeating); + + timer_controller.start(id1); + timer_controller.start(id3); + timer_controller.start(id2); + + timer_controller.enable(true); + + CHECK_EQUAL(11, timer_controller.time_to_next()); + + timer_controller.tick(7); + timer_controller.handle_deferred(); + CHECK_EQUAL(4, timer_controller.time_to_next()); + + timer_controller.tick(7); + timer_controller.handle_deferred(); + CHECK_EQUAL(8, timer_controller.time_to_next()); + + timer_controller.tick(7); + timer_controller.handle_deferred(); + CHECK_EQUAL(1, timer_controller.time_to_next()); + + timer_controller.tick(7); + timer_controller.handle_deferred(); + CHECK_EQUAL(5, timer_controller.time_to_next()); + + timer_controller.tick(7); + timer_controller.handle_deferred(); + CHECK_EQUAL(2, timer_controller.time_to_next()); + + timer_controller.tick(7); + timer_controller.handle_deferred(); + CHECK_EQUAL(2, timer_controller.time_to_next()); + + timer_controller.tick(7); + timer_controller.handle_deferred(); + CHECK_EQUAL(6, timer_controller.time_to_next()); + + timer_controller.tick(7); + timer_controller.handle_deferred(); + CHECK_EQUAL(10, timer_controller.time_to_next()); + + timer_controller.tick(7); + timer_controller.handle_deferred(); + CHECK_EQUAL(3, timer_controller.time_to_next()); + + timer_controller.tick(7); + timer_controller.handle_deferred(); + CHECK_EQUAL(4, timer_controller.time_to_next()); + } + + //************************************************************************* + TEST(callback_timer_is_active) + { + locks.clear(); + try_lock_type try_lock = try_lock_type::create(); + lock_type lock = lock_type::create(); + unlock_type unlock = unlock_type::create(); + + etl::callback_timer_deferred_locked<3, 3> timer_controller(try_lock, lock, unlock); + + etl::timer::id::type id1 = timer_controller.register_timer(member_callback, 37, etl::timer::mode::Single_Shot); + etl::timer::id::type id2 = timer_controller.register_timer(free_function_callback, 23, etl::timer::mode::Single_Shot); + etl::timer::id::type id3 = timer_controller.register_timer(free_function_callback2, 11, etl::timer::mode::Single_Shot); + + timer_controller.start(id1); + timer_controller.start(id3); + timer_controller.start(id2); + + timer_controller.enable(true); + + CHECK_TRUE(timer_controller.is_active(id1)); + CHECK_TRUE(timer_controller.is_active(id2)); + CHECK_TRUE(timer_controller.is_active(id3)); + + timer_controller.tick(11); + timer_controller.handle_deferred(); + CHECK_TRUE(timer_controller.is_active(id1)); + CHECK_TRUE(timer_controller.is_active(id2)); + CHECK_FALSE(timer_controller.is_active(id3)); + + timer_controller.tick(23 - 11); + timer_controller.handle_deferred(); + CHECK_TRUE(timer_controller.is_active(id1)); + CHECK_FALSE(timer_controller.is_active(id2)); + CHECK_FALSE(timer_controller.is_active(id3)); + + timer_controller.tick(37 - 23); + timer_controller.handle_deferred(); + CHECK_FALSE(timer_controller.is_active(id1)); + CHECK_FALSE(timer_controller.is_active(id2)); + CHECK_FALSE(timer_controller.is_active(id3)); + } + + //************************************************************************* + TEST(message_timer_time_to_next_with_has_active_timer) + { + locks.clear(); + try_lock_type try_lock = try_lock_type::create(); + lock_type lock = lock_type::create(); + unlock_type unlock = unlock_type::create(); + + etl::callback_timer_deferred_locked<3, 3> timer_controller(try_lock, lock, unlock); + + etl::timer::id::type id1 = timer_controller.register_timer(member_callback, 37, etl::timer::mode::Single_Shot); + etl::timer::id::type id2 = timer_controller.register_timer(free_function_callback, 23, etl::timer::mode::Single_Shot); + etl::timer::id::type id3 = timer_controller.register_timer(free_function_callback2, 11, etl::timer::mode::Single_Shot); + + timer_controller.start(id1); + timer_controller.start(id3); + timer_controller.start(id2); + + timer_controller.enable(true); + + timer_controller.tick(11); + timer_controller.handle_deferred(); + CHECK_EQUAL(12, timer_controller.time_to_next()); + CHECK_TRUE(timer_controller.has_active_timer()); + + timer_controller.tick(23); + timer_controller.handle_deferred(); + CHECK_EQUAL(3, timer_controller.time_to_next()); + CHECK_TRUE(timer_controller.has_active_timer()); + + timer_controller.tick(2); + timer_controller.handle_deferred(); + CHECK_EQUAL(1, timer_controller.time_to_next()); + CHECK_TRUE(timer_controller.has_active_timer()); + + timer_controller.tick(1); + timer_controller.handle_deferred(); + CHECK_EQUAL(static_cast(etl::timer::interval::No_Active_Interval), timer_controller.time_to_next()); + CHECK_FALSE(timer_controller.has_active_timer()); + } + + //************************************************************************* +#if REALTIME_TEST + + #if defined(ETL_TARGET_OS_WINDOWS) // Only Windows priority is currently supported + #define RAISE_THREAD_PRIORITY SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST) + #define FIX_PROCESSOR_AFFINITY SetThreadAffinityMask(GetCurrentThread(), 1); + #else + #define RAISE_THREAD_PRIORITY + #define FIX_PROCESSOR_AFFINITY + #endif + + etl::callback_timer_deferred_locked<3, 3> controller; + + //********************************* + struct ThreadLock + { + ThreadLock() + : lock_count(0) + { + } + + bool try_lock() + { + if (mutex.try_lock()) + { + ++lock_count; + + return true; + } + + return false; + } + + void lock() + { + mutex.lock(); + ++lock_count; + } + + void unlock() + { + mutex.unlock(); + --lock_count; + } + + std::mutex mutex; + int lock_count; + }; + + ThreadLock threadLock; + + //********************************* + void timer_event() + { + const uint32_t TICK = 1U; + uint32_t tick = TICK; + ticks = 1U; + + RAISE_THREAD_PRIORITY; + FIX_PROCESSOR_AFFINITY; + + while (ticks <= 1000U) + { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + + if (controller.tick(tick)) + { + tick = TICK; + } + else + { + tick += TICK; + } + + timer_controller.handle_deferred(); + + ++ticks; + } + } + + TEST(callback_timer_deferred_locked_threads) + { + FIX_PROCESSOR_AFFINITY; + + etl::timer::id::type id1 = controller.register_timer(member_callback, 400, etl::timer::mode::Single_Shot); + etl::timer::id::type id2 = controller.register_timer(free_function_callback, 100, etl::timer::mode::Repeating); + etl::timer::id::type id3 = controller.register_timer(free_function_callback2, 10, etl::timer::mode::Repeating); + + object.tick_list.clear(); + free_tick_list1.clear(); + free_tick_list2.clear(); + + try_lock_type try_lock_callback = try_lock_type::create(); + lock_type lock_callback = lock_type::create(); + unlock_type unlock_callback = unlock_type::create(); + + controller.set_locks(try_lock_callback, lock_callback, unlock_callback); + + controller.start(id1); + controller.start(id2); + //controller.start(id3); + + controller.enable(true); + + std::thread t1(timer_event); + + bool restart_1 = true; + + while (ticks <= 1000U) + { + if ((ticks > 200U) && (ticks < 500U)) + { + controller.stop(id3); + } + + if ((ticks > 600U) && (ticks < 800U)) + { + controller.start(id3); + } + + if ((ticks > 500U) && restart_1) + { + controller.start(id1); + restart_1 = false; + } + + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + + //Join the thread with the main thread + t1.join(); + + CHECK_EQUAL(2U, object.tick_list.size()); + CHECK_EQUAL(10U, free_tick_list1.size()); + CHECK(free_tick_list2.size() < 65U); + + std::vector compare1 = { 400, 900 }; + std::vector compare2 = { 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000 }; + + CHECK(object.tick_list.size() != 0); + CHECK(free_tick_list1.size() != 0); + CHECK(free_tick_list2.size() != 0); + + CHECK_ARRAY_EQUAL(compare1.data(), object.tick_list.data(), min(compare1.size(), object.tick_list.size())); + CHECK_ARRAY_EQUAL(compare2.data(), free_tick_list1.data(), min(compare2.size(), free_tick_list1.size())); + + CHECK_EQUAL(0U, threadLock.lock_count); + } +#endif + }; +}