diff --git a/include/etl/callback_timer_interrupt.h b/include/etl/callback_timer_interrupt.h new file mode 100644 index 00000000..942af3d3 --- /dev/null +++ b/include/etl/callback_timer_interrupt.h @@ -0,0 +1,602 @@ +/****************************************************************************** +The MIT License(MIT) + +Embedded Template Library. +https://github.com/ETLCPP/etl +https://www.etlcpp.com + +Copyright(c) 2022 jwellbelove + +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_INTERRUPT_INCLUDED +#define ETL_CALLBACK_TIMER_INTERRUPT_INCLUDED + +#include + +#include "platform.h" + +#include "algorithm.h" +#include "nullptr.h" +#include "delegate.h" +#include "static_assert.h" +#include "timer.h" +#include "error_handler.h" +#include "placement_new.h" + +namespace etl +{ + //*************************************************************************** + /// Interface for callback timer + //*************************************************************************** + template + class icallback_timer_interrupt + { + public: + + typedef etl::delegate callback_type; + + //******************************************* + /// Register a timer. + //******************************************* + etl::timer::id::type register_timer(const callback_type& callback_, + uint32_t period_, + bool repeating_) + { + etl::timer::id::type id = etl::timer::id::NO_TIMER; + + bool is_space = (number_of_registered_timers < MAX_TIMERS); + + if (is_space) + { + // Search for the free space. + for (uint_least8_t i = 0U; i < MAX_TIMERS; ++i) + { + timer_data& timer = timer_array[i]; + + if (timer.id == etl::timer::id::NO_TIMER) + { + TInterruptGuard guard; + (void)guard; // Silence 'unused variable warnings. + + // Create in-place. + new (&timer) timer_data(i, callback_, period_, repeating_); + ++number_of_registered_timers; + id = i; + break; + } + } + } + + return id; + } + + //******************************************* + /// Unregister a timer. + //******************************************* + bool unregister_timer(etl::timer::id::type id_) + { + bool result = false; + + if (id_ != etl::timer::id::NO_TIMER) + { + timer_data& timer = timer_array[id_]; + + if (timer.id != etl::timer::id::NO_TIMER) + { + if (timer.is_active()) + { + TInterruptGuard guard; + (void)guard; // Silence 'unused variable warnings. + + active_list.remove(timer.id, false); + } + + // Reset in-place. + new (&timer) timer_data(); + --number_of_registered_timers; + + result = true; + } + } + + return result; + } + + //******************************************* + /// Enable/disable the timer. + //******************************************* + void enable(bool state_) + { + enabled = state_; + } + + //******************************************* + /// Get the enable/disable state. + //******************************************* + bool is_running() const + { + return enabled; + } + + //******************************************* + /// Clears the timer of data. + //******************************************* + void clear() + { + { + TInterruptGuard guard; + (void)guard; // Silence 'unused variable warnings. + + active_list.clear(); + number_of_registered_timers = 0; + } + + for (uint8_t i = 0U; i < MAX_TIMERS; ++i) + { + ::new (&timer_array[i]) timer_data(); + } + } + + //******************************************* + // Called by the timer service to indicate the + // amount of time that has elapsed since the last successful call to 'tick'. + // Returns true if the tick was processed, + // false if not. + //******************************************* + bool tick(uint32_t count) + { + if (enabled) + { + // 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()) + { + timer.callback(); + } + + 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; + } + } + + return true; + } + + return false; + } + + //******************************************* + /// Starts a timer. + //******************************************* + bool start(etl::timer::id::type id_, bool immediate_ = false) + { + bool result = false; + + // Valid timer id? + if (id_ != etl::timer::id::NO_TIMER) + { + timer_data& timer = timer_array[id_]; + + // Registered timer? + if (timer.id != etl::timer::id::NO_TIMER) + { + // Has a valid period. + if (timer.period != etl::timer::state::INACTIVE) + { + TInterruptGuard guard; + (void)guard; // Silence 'unused variable warnings. + + if (timer.is_active()) + { + active_list.remove(timer.id, false); + } + + timer.delta = immediate_ ? 0U : timer.period; + active_list.insert(timer.id); + + result = true; + } + } + } + + return result; + } + + //******************************************* + /// Stops a timer. + //******************************************* + bool stop(etl::timer::id::type id_) + { + bool result = false; + + // Valid timer id? + if (id_ != etl::timer::id::NO_TIMER) + { + timer_data& timer = timer_array[id_]; + + // Registered timer? + if (timer.id != etl::timer::id::NO_TIMER) + { + if (timer.is_active()) + { + TInterruptGuard guard; + (void)guard; // Silence 'unused variable warnings. + + active_list.remove(timer.id, false); + } + + result = true; + } + } + + return result; + } + + //******************************************* + /// Sets a timer's period. + //******************************************* + bool set_period(etl::timer::id::type id_, uint32_t period_) + { + if (stop(id_)) + { + timer_array[id_].period = period_; + return true; + } + + return false; + } + + //******************************************* + /// Sets a timer's mode. + //******************************************* + bool set_mode(etl::timer::id::type id_, bool repeating_) + { + if (stop(id_)) + { + timer_array[id_].repeating = repeating_; + return true; + } + + return false; + } + + protected: + + //************************************************************************* + /// The configuration of a timer. + struct timer_data + { + //******************************************* + timer_data() + : callback() + , period(0U) + , delta(etl::timer::state::INACTIVE) + , id(etl::timer::id::NO_TIMER) + , previous(etl::timer::id::NO_TIMER) + , next(etl::timer::id::NO_TIMER) + , repeating(true) + { + } + + //******************************************* + /// ETL delegate callback + //******************************************* + timer_data(etl::timer::id::type id_, + callback_type callback_, + uint32_t period_, + bool repeating_) + : callback(callback_) + , period(period_) + , delta(etl::timer::state::INACTIVE) + , id(id_) + , previous(etl::timer::id::NO_TIMER) + , next(etl::timer::id::NO_TIMER) + , repeating(repeating_) + { + } + + //******************************************* + /// Returns true if the timer is active. + //******************************************* + bool is_active() const + { + return delta != etl::timer::state::INACTIVE; + } + + //******************************************* + /// Sets the timer to the inactive state. + //******************************************* + void set_inactive() + { + delta = etl::timer::state::INACTIVE; + } + + callback_type callback; + uint32_t period; + uint32_t delta; + etl::timer::id::type id; + uint_least8_t previous; + uint_least8_t next; + bool repeating; + + private: + + // Disabled. + timer_data(const timer_data& other) ETL_DELETE; + timer_data& operator =(const timer_data& other) ETL_DELETE; + }; + + //******************************************* + /// Constructor. + //******************************************* + icallback_timer_interrupt(timer_data* const timer_array_, const uint_least8_t MAX_TIMERS_) + : timer_array(timer_array_), + active_list(timer_array_), + enabled(false), + number_of_registered_timers(0U), + MAX_TIMERS(MAX_TIMERS_) + { + } + + private: + + //************************************************************************* + /// A specialised intrusive linked list for timer data. + //************************************************************************* + class timer_list + { + public: + + //******************************* + timer_list(timer_data* ptimers_) + : head(etl::timer::id::NO_TIMER) + , tail(etl::timer::id::NO_TIMER) + , current(etl::timer::id::NO_TIMER) + , ptimers(ptimers_) + { + } + + //******************************* + bool empty() const + { + return head == etl::timer::id::NO_TIMER; + } + + //******************************* + // Inserts the timer at the correct delta position + //******************************* + void insert(etl::timer::id::type id_) + { + timer_data& timer = ptimers[id_]; + + if (head == etl::timer::id::NO_TIMER) + { + // No entries yet. + head = id_; + tail = id_; + timer.previous = etl::timer::id::NO_TIMER; + timer.next = etl::timer::id::NO_TIMER; + } + else + { + // We already have entries. + etl::timer::id::type test_id = begin(); + + while (test_id != etl::timer::id::NO_TIMER) + { + timer_data& test = ptimers[test_id]; + + // Find the correct place to insert. + if (timer.delta <= test.delta) + { + if (test.id == head) + { + head = timer.id; + } + + // Insert before test. + timer.previous = test.previous; + test.previous = timer.id; + timer.next = test.id; + + // Adjust the next delta to compensate. + test.delta -= timer.delta; + + if (timer.previous != etl::timer::id::NO_TIMER) + { + ptimers[timer.previous].next = timer.id; + } + break; + } + else + { + timer.delta -= test.delta; + } + + test_id = next(test_id); + } + + // Reached the end? + if (test_id == etl::timer::id::NO_TIMER) + { + // Tag on to the tail. + ptimers[tail].next = timer.id; + timer.previous = tail; + timer.next = etl::timer::id::NO_TIMER; + tail = timer.id; + } + } + } + + //******************************* + void remove(etl::timer::id::type id_, bool has_expired) + { + timer_data& timer = ptimers[id_]; + + if (head == id_) + { + head = timer.next; + } + else + { + ptimers[timer.previous].next = timer.next; + } + + if (tail == id_) + { + tail = timer.previous; + } + else + { + ptimers[timer.next].previous = timer.previous; + } + + if (!has_expired) + { + // Adjust the next delta. + if (timer.next != etl::timer::id::NO_TIMER) + { + ptimers[timer.next].delta += timer.delta; + } + } + + timer.previous = etl::timer::id::NO_TIMER; + timer.next = etl::timer::id::NO_TIMER; + timer.delta = etl::timer::state::INACTIVE; + } + + //******************************* + timer_data& front() + { + return ptimers[head]; + } + + //******************************* + etl::timer::id::type begin() + { + current = head; + return current; + } + + //******************************* + etl::timer::id::type previous(etl::timer::id::type last) + { + current = ptimers[last].previous; + return current; + } + + //******************************* + etl::timer::id::type next(etl::timer::id::type last) + { + current = ptimers[last].next; + return current; + } + + //******************************* + void clear() + { + etl::timer::id::type id = begin(); + + while (id != etl::timer::id::NO_TIMER) + { + timer_data& timer = ptimers[id]; + id = next(id); + timer.next = etl::timer::id::NO_TIMER; + } + + head = etl::timer::id::NO_TIMER; + tail = etl::timer::id::NO_TIMER; + current = etl::timer::id::NO_TIMER; + } + + private: + + etl::timer::id::type head; + etl::timer::id::type tail; + etl::timer::id::type current; + + timer_data* const ptimers; + }; + + // The array of timer data structures. + timer_data* const timer_array; + + // The list of active timers. + timer_list active_list; + + volatile bool enabled; + volatile uint_least8_t number_of_registered_timers; + + public: + + const uint_least8_t MAX_TIMERS; + }; + + //*************************************************************************** + /// The callback timer + //*************************************************************************** + template + class callback_timer_interrupt : public etl::icallback_timer_interrupt + { + public: + + ETL_STATIC_ASSERT(MAX_TIMERS_ <= 254U, "No more than 254 timers are allowed"); + + typedef typename icallback_timer_interrupt::callback_type callback_type; + + //******************************************* + /// Constructor. + //******************************************* + callback_timer_interrupt() + : icallback_timer_interrupt(timer_array, MAX_TIMERS_) + { + } + + private: + + typename icallback_timer_interrupt::timer_data timer_array[MAX_TIMERS_]; + }; +} + +#endif diff --git a/include/etl/message_timer_interrupt.h b/include/etl/message_timer_interrupt.h new file mode 100644 index 00000000..3018fb1f --- /dev/null +++ b/include/etl/message_timer_interrupt.h @@ -0,0 +1,625 @@ +/****************************************************************************** +The MIT License(MIT) + +Embedded Template Library. +https://github.com/ETLCPP/etl +https://www.etlcpp.com + +Copyright(c) 2022 jwellbelove + +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_MESSAGE_TIMER_INTERRUPT_INCLUDED +#define ETL_MESSAGE_TIMER_INTERRUPT_INCLUDED + +#include +#include "algorithm.h" + +#include "platform.h" +#include "nullptr.h" +#include "message_types.h" +#include "message.h" +#include "message_router.h" +#include "message_bus.h" +#include "static_assert.h" +#include "timer.h" +#include "delegate.h" + +namespace etl +{ + //*************************************************************************** + /// Interface for message timer + //*************************************************************************** + template + class imessage_timer_interrupt + { + public: + + typedef etl::delegate callback_type; + + public: + + //******************************************* + /// Register a timer. + //******************************************* + etl::timer::id::type register_timer(const etl::imessage& message_, + etl::imessage_router& router_, + uint32_t period_, + bool repeating_, + etl::message_router_id_t destination_router_id_ = etl::imessage_router::ALL_MESSAGE_ROUTERS) + { + etl::timer::id::type id = etl::timer::id::NO_TIMER; + + bool is_space = (number_of_registered_timers < MAX_TIMERS); + + if (is_space) + { + // There's no point adding null message routers. + if (!router_.is_null_router()) + { + // Search for the free space. + for (uint_least8_t i = 0U; i < MAX_TIMERS; ++i) + { + timer_data& timer = timer_array[i]; + + if (timer.id == etl::timer::id::NO_TIMER) + { + TInterruptGuard guard; + (void)guard; // Silence 'unused variable warnings. + + // Create in-place. + new (&timer) timer_data(i, message_, router_, period_, repeating_, destination_router_id_); + ++number_of_registered_timers; + id = i; + break; + } + } + } + } + + return id; + } + + //******************************************* + /// Unregister a timer. + //******************************************* + bool unregister_timer(etl::timer::id::type id_) + { + bool result = false; + + if (id_ != etl::timer::id::NO_TIMER) + { + timer_data& timer = timer_array[id_]; + + if (timer.id != etl::timer::id::NO_TIMER) + { + if (timer.is_active()) + { + TInterruptGuard guard; + (void)guard; // Silence 'unused variable warnings. + + active_list.remove(timer.id, true); + } + + // Reset in-place. + new (&timer) timer_data(); + --number_of_registered_timers; + + result = true; + } + } + + return result; + } + + //******************************************* + /// Enable/disable the timer. + //******************************************* + void enable(bool state_) + { + enabled = state_; + } + + //******************************************* + /// Get the enable/disable state. + //******************************************* + bool is_running() const + { + return enabled; + } + + //******************************************* + /// Clears the timer of data. + //******************************************* + void clear() + { + { + TInterruptGuard guard; + (void)guard; // Silence 'unused variable warnings. + + active_list.clear(); + } + + for (int i = 0; i < MAX_TIMERS; ++i) + { + new (&timer_array[i]) timer_data(); + } + + number_of_registered_timers = 0U; + } + + //******************************************* + // Called by the timer service to indicate the + // amount of time that has elapsed since the last successful call to 'tick'. + // Returns true if the tick was processed, + // false if not. + //******************************************* + bool tick(uint32_t count) + { + if (enabled) + { + // 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.p_router != ETL_NULLPTR) + { + timer.p_router->receive(timer.destination_router_id, *(timer.p_message)); + } + + 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; + } + } + + return true; + } + + return false; + } + + //******************************************* + /// Starts a timer. + //******************************************* + bool start(etl::timer::id::type id_, bool immediate_ = false) + { + bool result = false; + + // Valid timer id? + if (id_ != etl::timer::id::NO_TIMER) + { + timer_data& timer = timer_array[id_]; + + // Registered timer? + if (timer.id != etl::timer::id::NO_TIMER) + { + // Has a valid period. + if (timer.period != etl::timer::state::INACTIVE) + { + TInterruptGuard guard; + (void)guard; // Silence 'unused variable warnings. + + if (timer.is_active()) + { + active_list.remove(timer.id, false); + } + + timer.delta = immediate_ ? 0 : timer.period; + active_list.insert(timer.id); + + result = true; + } + } + } + + return result; + } + + //******************************************* + /// Stops a timer. + //******************************************* + bool stop(etl::timer::id::type id_) + { + bool result = false; + + // Valid timer id? + if (id_ != etl::timer::id::NO_TIMER) + { + timer_data& timer = timer_array[id_]; + + // Registered timer? + if (timer.id != etl::timer::id::NO_TIMER) + { + if (timer.is_active()) + { + TInterruptGuard guard; + (void)guard; // Silence 'unused variable warnings. + + active_list.remove(timer.id, false); + } + + result = true; + } + } + + return result; + } + + //******************************************* + /// Sets a timer's period. + //******************************************* + bool set_period(etl::timer::id::type id_, uint32_t period_) + { + if (stop(id_)) + { + timer_array[id_].period = period_; + return true; + } + + return false; + } + + //******************************************* + /// Sets a timer's mode. + //******************************************* + bool set_mode(etl::timer::id::type id_, bool repeating_) + { + if (stop(id_)) + { + timer_array[id_].repeating = repeating_; + return true; + } + + return false; + } + + protected: + + //************************************************************************* + /// The configuration of a timer. + struct timer_data + { + //******************************************* + timer_data() + : p_message(ETL_NULLPTR) + , p_router(ETL_NULLPTR) + , period(0) + , delta(etl::timer::state::INACTIVE) + , destination_router_id(etl::imessage_bus::ALL_MESSAGE_ROUTERS) + , id(etl::timer::id::NO_TIMER) + , previous(etl::timer::id::NO_TIMER) + , next(etl::timer::id::NO_TIMER) + , repeating(true) + { + } + + //******************************************* + timer_data(etl::timer::id::type id_, + const etl::imessage& message_, + etl::imessage_router& irouter_, + uint32_t period_, + bool repeating_, + etl::message_router_id_t destination_router_id_ = etl::imessage_bus::ALL_MESSAGE_ROUTERS) + : p_message(&message_) + , p_router(&irouter_) + , period(period_) + , delta(etl::timer::state::INACTIVE) + , destination_router_id(destination_router_id_) + , id(id_) + , previous(etl::timer::id::NO_TIMER) + , next(etl::timer::id::NO_TIMER) + , repeating(repeating_) + { + } + + //******************************************* + /// Returns true if the timer is active. + //******************************************* + bool is_active() const + { + return delta != etl::timer::state::INACTIVE; + } + + //******************************************* + /// Sets the timer to the inactive state. + //******************************************* + void set_inactive() + { + delta = etl::timer::state::INACTIVE; + } + + const etl::imessage* p_message; + etl::imessage_router* p_router; + uint32_t period; + uint32_t delta; + etl::message_router_id_t destination_router_id; + etl::timer::id::type id; + uint_least8_t previous; + uint_least8_t next; + bool repeating; + + private: + + // Disabled. + timer_data(const timer_data& other); + timer_data& operator =(const timer_data& other); + }; + + //******************************************* + /// Constructor. + //******************************************* + imessage_timer_interrupt(timer_data* const timer_array_, const uint_least8_t MAX_TIMERS_) + : timer_array(timer_array_) + , active_list(timer_array_) + , enabled(false) + , number_of_registered_timers(0U) + , MAX_TIMERS(MAX_TIMERS_) + { + } + + //******************************************* + /// Destructor. + //******************************************* + ~imessage_timer_interrupt() + { + } + + private: + + //************************************************************************* + /// A specialised intrusive linked list for timer data. + //************************************************************************* + class timer_list + { + public: + + //******************************* + timer_list(timer_data* ptimers_) + : head(etl::timer::id::NO_TIMER) + , tail(etl::timer::id::NO_TIMER) + , current(etl::timer::id::NO_TIMER) + , ptimers(ptimers_) + { + } + + //******************************* + bool empty() const + { + return head == etl::timer::id::NO_TIMER; + } + + //******************************* + // Inserts the timer at the correct delta position + //******************************* + void insert(etl::timer::id::type id_) + { + timer_data& timer = ptimers[id_]; + + if (head == etl::timer::id::NO_TIMER) + { + // No entries yet. + head = id_; + tail = id_; + timer.previous = etl::timer::id::NO_TIMER; + timer.next = etl::timer::id::NO_TIMER; + } + else + { + // We already have entries. + etl::timer::id::type test_id = begin(); + + while (test_id != etl::timer::id::NO_TIMER) + { + timer_data& test = ptimers[test_id]; + + // Find the correct place to insert. + if (timer.delta <= test.delta) + { + if (test.id == head) + { + head = timer.id; + } + + // Insert before test. + timer.previous = test.previous; + test.previous = timer.id; + timer.next = test.id; + + // Adjust the next delta to compensate. + test.delta -= timer.delta; + + if (timer.previous != etl::timer::id::NO_TIMER) + { + ptimers[timer.previous].next = timer.id; + } + break; + } + else + { + timer.delta -= test.delta; + } + + test_id = next(test_id); + } + + // Reached the end? + if (test_id == etl::timer::id::NO_TIMER) + { + // Tag on to the tail. + ptimers[tail].next = timer.id; + timer.previous = tail; + timer.next = etl::timer::id::NO_TIMER; + tail = timer.id; + } + } + } + + //******************************* + void remove(etl::timer::id::type id_, bool has_expired) + { + timer_data& timer = ptimers[id_]; + + if (head == id_) + { + head = timer.next; + } + else + { + ptimers[timer.previous].next = timer.next; + } + + if (tail == id_) + { + tail = timer.previous; + } + else + { + ptimers[timer.next].previous = timer.previous; + } + + if (!has_expired) + { + // Adjust the next delta. + if (timer.next != etl::timer::id::NO_TIMER) + { + ptimers[timer.next].delta += timer.delta; + } + } + + timer.previous = etl::timer::id::NO_TIMER; + timer.next = etl::timer::id::NO_TIMER; + timer.delta = etl::timer::state::INACTIVE; + } + + //******************************* + timer_data& front() + { + return ptimers[head]; + } + + //******************************* + etl::timer::id::type begin() + { + current = head; + return current; + } + + //******************************* + etl::timer::id::type previous(etl::timer::id::type last) + { + current = ptimers[last].previous; + return current; + } + + //******************************* + etl::timer::id::type next(etl::timer::id::type last) + { + current = ptimers[last].next; + return current; + } + + //******************************* + void clear() + { + etl::timer::id::type id = begin(); + + while (id != etl::timer::id::NO_TIMER) + { + timer_data& timer = ptimers[id]; + id = next(id); + timer.next = etl::timer::id::NO_TIMER; + } + + head = etl::timer::id::NO_TIMER; + tail = etl::timer::id::NO_TIMER; + current = etl::timer::id::NO_TIMER; + } + + private: + + etl::timer::id::type head; + etl::timer::id::type tail; + etl::timer::id::type current; + + timer_data* const ptimers; + }; + + // The array of timer data structures. + timer_data* const timer_array; + + // The list of active timers. + timer_list active_list; + + volatile bool enabled; + volatile uint_least8_t number_of_registered_timers; + + public: + + const uint_least8_t MAX_TIMERS; + }; + + //*************************************************************************** + /// The message timer + //*************************************************************************** + template + class message_timer_interrupt : public etl::imessage_timer_interrupt + { + public: + + ETL_STATIC_ASSERT(MAX_TIMERS_ <= 254, "No more than 254 timers are allowed"); + + typedef typename imessage_timer_interrupt::callback_type callback_type; + + //******************************************* + /// Constructor. + //******************************************* + message_timer_interrupt() + : imessage_timer_interrupt(timer_array, MAX_TIMERS_) + { + } + + private: + + typename etl::imessage_timer_interrupt::timer_data timer_array[MAX_TIMERS_]; + }; +} + +#endif diff --git a/test/test_callback_timer_interrupt.cpp b/test/test_callback_timer_interrupt.cpp new file mode 100644 index 00000000..2aa4c3ce --- /dev/null +++ b/test/test_callback_timer_interrupt.cpp @@ -0,0 +1,826 @@ +/****************************************************************************** +The MIT License(MIT) + +Embedded Template Library. +https://github.com/ETLCPP/etl +https://www.etlcpp.com + +Copyright(c) 2022 jwellbelove + +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_interrupt.h" +#include "etl/delegate.h" + +#include + +namespace +{ + uint64_t ticks = 0ULL; + + //*************************************************************************** + struct ScopedGuard + { + ScopedGuard() + { + ++guard_count; + } + + ~ScopedGuard() + { + --guard_count; + } + + volatile static int guard_count; + }; + + volatile int ScopedGuard::guard_count = 0; + + //*************************************************************************** + struct TimerLogEntry + { + etl::timer::id::type id; + uint64_t time_called; + }; + + //*************************************************************************** + // Class callback via etl::delegate + //*************************************************************************** + class Test + { + public: + + Test() + : 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); + } + + void set_controller(etl::callback_timer_interrupt<3, ScopedGuard>& controller) + { + p_controller = &controller; + } + + std::vector tick_list; + + etl::callback_timer_interrupt<3, ScopedGuard>* p_controller; + }; + + using callback_type = etl::icallback_timer_interrupt::callback_type; + + Test test; + callback_type member_callback = callback_type::create(); + callback_type member_callback2 = 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_interrupt) + { + //************************************************************************* + TEST(callback_timer_interrupt_too_many_timers) + { + etl::callback_timer_interrupt<2, ScopedGuard> timer_controller; + + 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, ScopedGuard::guard_count); + } + + //************************************************************************* + TEST(callback_timer_interrupt_one_shot) + { + etl::callback_timer_interrupt<4, ScopedGuard> timer_controller; + + 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); + + test.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); + } + + std::vector compare1 = { 37 }; + std::vector compare2 = { 23 }; + std::vector compare3 = { 11 }; + + CHECK(test.tick_list.size() != 0); + CHECK(free_tick_list1.size() != 0); + CHECK(free_tick_list2.size() != 0); + + CHECK_ARRAY_EQUAL(compare1.data(), test.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, ScopedGuard::guard_count); + } + + //************************************************************************* + TEST(message_timer_one_shot_after_timeout) + { + etl::callback_timer_interrupt<1, ScopedGuard> timer_controller; + + etl::timer::id::type id1 = timer_controller.register_timer(member_callback, 37, etl::timer::mode::SINGLE_SHOT); + test.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 should have timed out. + + CHECK(timer_controller.set_period(id1, 50)); + timer_controller.start(id1); + + test.tick_list.clear(); + + ticks = 0; + + while (ticks <= 100U) + { + ticks += step; + timer_controller.tick(step); + } + + // Timer should have timed out. + + CHECK_EQUAL(50U, *test.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, ScopedGuard::guard_count); + } + + //************************************************************************* + TEST(callback_timer_interrupt_repeating) + { + etl::callback_timer_interrupt<3, ScopedGuard> timer_controller; + + 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); + + test.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); + } + + 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(test.tick_list.size() != 0); + CHECK(free_tick_list1.size() != 0); + CHECK(free_tick_list2.size() != 0); + + CHECK_ARRAY_EQUAL(compare1.data(), test.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, ScopedGuard::guard_count); + } + + //************************************************************************* + TEST(callback_timer_interrupt_repeating_bigger_step) + { + etl::callback_timer_interrupt<3, ScopedGuard> timer_controller; + + 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); + + test.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); + } + + 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(test.tick_list.size() != 0); + CHECK(free_tick_list1.size() != 0); + CHECK(free_tick_list2.size() != 0); + + CHECK_ARRAY_EQUAL(compare1.data(), test.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, ScopedGuard::guard_count); + } + + //************************************************************************* + TEST(callback_timer_interrupt_repeating_stop_start) + { + etl::callback_timer_interrupt<3, ScopedGuard> timer_controller; + + 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); + + test.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); + } + + std::vector compare1 = { 77 }; + std::vector compare2 = { 23 }; + std::vector compare3 = { 11, 22, 33, 44, 55, 66, 77, 88, 99 }; + + CHECK(test.tick_list.size() != 0); + CHECK(free_tick_list1.size() != 0); + CHECK(free_tick_list2.size() != 0); + + CHECK_ARRAY_EQUAL(compare1.data(), test.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, ScopedGuard::guard_count); + } + + //************************************************************************* + TEST(callback_timer_interrupt_timer_starts_timer_small_step) + { + etl::callback_timer_interrupt<3, ScopedGuard> timer_controller; + + 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; + + test.set_controller(timer_controller); + + test.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); + } + + std::vector compare1 = { 100, 110, 122 }; + + CHECK(test.tick_list.size() != 0); + + CHECK_ARRAY_EQUAL(compare1.data(), test.tick_list.data(), compare1.size()); + + CHECK_EQUAL(0U, ScopedGuard::guard_count); + } + + //************************************************************************* + TEST(callback_timer_interrupt_timer_starts_timer_big_step) + { + etl::callback_timer_interrupt<3, ScopedGuard> timer_controller; + + 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; + + test.set_controller(timer_controller); + + test.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); + } + + std::vector compare1 = { 102, 111, 123 }; + + CHECK(test.tick_list.size() != 0); + + CHECK_ARRAY_EQUAL(compare1.data(), test.tick_list.data(), compare1.size()); + + CHECK_EQUAL(0U, ScopedGuard::guard_count); + } + + //************************************************************************* + TEST(callback_timer_interrupt_repeating_register_unregister) + { + etl::callback_timer_interrupt<3, ScopedGuard> timer_controller; + + 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); + + test.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); + } + + std::vector compare1 = { 77 }; + std::vector compare2 = { 23 }; + std::vector compare3 = { 11, 22, 33, 44, 55, 66, 77, 88, 99 }; + + CHECK(test.tick_list.size() != 0); + CHECK(free_tick_list1.size() != 0); + CHECK(free_tick_list2.size() != 0); + + CHECK_ARRAY_EQUAL(compare1.data(), test.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, ScopedGuard::guard_count); + } + + //************************************************************************* + TEST(callback_timer_interrupt_repeating_clear) + { + etl::callback_timer_interrupt<3, ScopedGuard> timer_controller; + + 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); + + test.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); + } + + std::vector compare1 = { 37 }; + std::vector compare2 = { 23 }; + std::vector compare3 = { 11, 22, 33 }; + + CHECK(test.tick_list.size() != 0); + CHECK(free_tick_list1.size() != 0); + CHECK(free_tick_list2.size() != 0); + + CHECK_ARRAY_EQUAL(compare1.data(), test.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, ScopedGuard::guard_count); + } + + //************************************************************************* + TEST(callback_timer_interrupt_delayed_immediate) + { + etl::callback_timer_interrupt<3, ScopedGuard> timer_controller; + + 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); + + test.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.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); + } + + 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(test.tick_list.size() != 0); + CHECK(free_tick_list1.size() != 0); + CHECK(free_tick_list2.size() != 0); + + CHECK_ARRAY_EQUAL(compare1.data(), test.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, ScopedGuard::guard_count); + } + + //************************************************************************* + TEST(callback_timer_interrupt_one_shot_big_step_short_delay_insert) + { + etl::callback_timer_interrupt<3, ScopedGuard> timer_controller; + + 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); + + ticks += step; + timer_controller.tick(step); + + 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, ScopedGuard::guard_count); + } + + //************************************************************************* + TEST(callback_timer_interrupt_one_shot_empty_list_huge_tick_before_insert) + { + etl::callback_timer_interrupt<3, ScopedGuard> timer_controller; + + 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); + } + + // Huge tick count. + timer_controller.tick(UINT32_MAX - step + 1); + + timer_controller.start(id1); + + for (uint32_t i = 0U; i < step; ++i) + { + ++ticks; + timer_controller.tick(1); + } + 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, ScopedGuard::guard_count); + } + + //************************************************************************* + class test_object + { + public: + + void call() + { + ++called; + } + + size_t called = 0UL; + }; + + TEST(callback_timer_interrupt_call_etl_delegate) + { + test_object test_obj; + callback_type delegate_callback = callback_type::create(test_obj); + + etl::callback_timer_interrupt<1, ScopedGuard> timer_controller; + + 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); + CHECK(test_obj.called == 0); + + timer_controller.tick(2); + CHECK(test_obj.called == 1); + + CHECK_EQUAL(0U, ScopedGuard::guard_count); + } + + //************************************************************************* + TEST(callback_timer_interrupt_log_timer_calls) + { + std::vector timer_log; + + etl::callback_timer_interrupt<4, ScopedGuard> timer_controller; + size_t timer_count = 0U; + + // Create the callbacks. + callback_type delegate_callback0([&]() + { + timer_log.push_back(TimerLogEntry{ 0, timer_count }); + }); + + callback_type delegate_callback1([&]() + { + timer_log.push_back(TimerLogEntry{ 1, timer_count }); + }); + + callback_type delegate_callback2([&]() + { + timer_log.push_back(TimerLogEntry{ 2, timer_count }); + }); + + callback_type delegate_callback3([&]() + { + timer_log.push_back(TimerLogEntry{ 3, timer_count }); + timer_controller.start(1); + }); + + timer_log.clear(); + timer_controller.enable(true); + + constexpr uint32_t T0 = 2U; + constexpr uint32_t T1 = 3U; + constexpr uint32_t T2 = 4U; + constexpr uint32_t T3 = 5U; + + // Register the timers. + etl::timer::id::type id0 = timer_controller.register_timer(delegate_callback0, T0, etl::timer::mode::REPEATING); + etl::timer::id::type id1 = timer_controller.register_timer(delegate_callback1, T1, etl::timer::mode::SINGLE_SHOT); + etl::timer::id::type id2 = timer_controller.register_timer(delegate_callback2, T2, etl::timer::mode::REPEATING); + etl::timer::id::type id3 = timer_controller.register_timer(delegate_callback3, T3, etl::timer::mode::REPEATING); + + // Start the repeating timers. + timer_controller.start(id0); + timer_controller.start(id2); + timer_controller.start(id3); + + // Run the timer. + for (int i = 1; i < 50; ++i) + { + ++timer_count; + timer_controller.tick(1); + } + + // Check the results log. + for (auto t : timer_log) + { + switch (t.id) + { + case 0: + { + CHECK_EQUAL(0, t.time_called % 2); + break; + } + + case 1: + { + CHECK_EQUAL(0, (t.time_called % 5) % 3); + break; + } + + case 2: + { + CHECK_EQUAL(0, t.time_called % 4); + break; + } + + case 3: + { + CHECK_EQUAL(0, t.time_called % 5); + break; + } + + default: + { + CHECK(false); + break; + } + } + } + + // Check the + CHECK_EQUAL(0U, ScopedGuard::guard_count); + } + }; +} diff --git a/test/test_message_timer_interrupt.cpp b/test/test_message_timer_interrupt.cpp new file mode 100644 index 00000000..bcfd1ddc --- /dev/null +++ b/test/test_message_timer_interrupt.cpp @@ -0,0 +1,770 @@ +/****************************************************************************** +The MIT License(MIT) + +Embedded Template Library. +https://github.com/ETLCPP/etl +https://www.etlcpp.com + +Copyright(c) 2022 jwellbelove + +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/message_router.h" +#include "etl/message_bus.h" +#include "etl/message_timer_interrupt.h" + +#include +#include + +//*************************************************************************** +// The set of messages. +//*************************************************************************** +namespace +{ + uint64_t ticks = 0; + + //*************************************************************************** + struct ScopedGuard + { + ScopedGuard() + { + ++guard_count; + } + + ~ScopedGuard() + { + --guard_count; + } + + volatile static int guard_count; + }; + + volatile int ScopedGuard::guard_count = 0; + + //*************************************************************************** + struct TimerLogEntry + { + etl::timer::id::type id; + uint64_t time_called; + }; + + //*************************************************************************** + enum + { + MESSAGE1, + MESSAGE2, + MESSAGE3, + MESSAGE4 + }; + + enum + { + ROUTER1 = 1, + }; + + struct Message1 : public etl::message + { + }; + + struct Message2 : public etl::message + { + }; + + struct Message3 : public etl::message + { + }; + + struct Message4 : public etl::message + { + }; + + Message1 message1; + Message2 message2; + Message3 message3; + Message4 message4; + + //*************************************************************************** + // Router that handles messages 1, 2, 3 + //*************************************************************************** + class Router1 : public etl::message_router + { + public: + + Router1() + : message_router(ROUTER1) + { + } + + void on_receive(const Message1&) + { + message1.push_back(ticks); + } + + void on_receive(const Message2&) + { + message2.push_back(ticks); + } + + void on_receive(const Message3&) + { + message3.push_back(ticks); + } + + void on_receive_unknown(const etl::imessage&) + { + } + + void clear() + { + message1.clear(); + message2.clear(); + message3.clear(); + } + + std::vector message1; + std::vector message2; + std::vector message3; + }; + + //*************************************************************************** + // Bus that handles messages 1, 2, 3 + //*************************************************************************** + class Bus1 : public etl::message_bus<1> + { + + }; + + //*********************************** + Router1 router1; + Bus1 bus1; + + SUITE(test_message_timer_interrupt) + { + //************************************************************************* + TEST(message_timer_too_many_timers) + { + etl::message_timer_interrupt<2, ScopedGuard> timer_controller; + + etl::timer::id::type id1 = timer_controller.register_timer(message1, router1, 37, etl::timer::mode::SINGLE_SHOT); + etl::timer::id::type id2 = timer_controller.register_timer(message2, router1, 23, etl::timer::mode::SINGLE_SHOT); + etl::timer::id::type id3 = timer_controller.register_timer(message3, router1, 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(message3, router1, 11, etl::timer::mode::SINGLE_SHOT); + CHECK(id3 != etl::timer::id::NO_TIMER); + + CHECK_EQUAL(0U, ScopedGuard::guard_count); + } + + //************************************************************************* + TEST(message_timer_one_shot) + { + etl::message_timer_interrupt<3, ScopedGuard> timer_controller; + + etl::timer::id::type id1 = timer_controller.register_timer(message1, router1, 37, etl::timer::mode::SINGLE_SHOT); + etl::timer::id::type id2 = timer_controller.register_timer(message2, router1, 23, etl::timer::mode::SINGLE_SHOT); + etl::timer::id::type id3 = timer_controller.register_timer(message3, router1, 11, etl::timer::mode::SINGLE_SHOT); + + router1.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); + } + + std::vector compare1 = { 37ULL }; + std::vector compare2 = { 23ULL }; + std::vector compare3 = { 11ULL }; + + CHECK_ARRAY_EQUAL(compare1.data(), router1.message1.data(), compare1.size()); + CHECK_ARRAY_EQUAL(compare2.data(), router1.message2.data(), compare2.size()); + CHECK_ARRAY_EQUAL(compare3.data(), router1.message3.data(), compare3.size()); + + CHECK_EQUAL(0U, ScopedGuard::guard_count); + } + + //************************************************************************* + TEST(message_timer_one_shot_after_timeout) + { + etl::message_timer_interrupt<1, ScopedGuard> timer_controller; + + etl::timer::id::type id1 = timer_controller.register_timer(message1, router1, 37, etl::timer::mode::SINGLE_SHOT); + router1.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 should have timed out. + + CHECK(timer_controller.set_period(id1, 50)); + timer_controller.start(id1); + + router1.clear(); + + ticks = 0; + + while (ticks <= 100U) + { + ticks += step; + timer_controller.tick(step); + } + + // Timer should have timed out. + + CHECK_EQUAL(50U, *router1.message1.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, ScopedGuard::guard_count); + } + + //************************************************************************* + TEST(message_timer_repeating) + { + etl::message_timer_interrupt<3, ScopedGuard> timer_controller; + + etl::timer::id::type id1 = timer_controller.register_timer(message1, router1, 37, etl::timer::mode::REPEATING); + etl::timer::id::type id2 = timer_controller.register_timer(message2, router1, 23, etl::timer::mode::REPEATING); + etl::timer::id::type id3 = timer_controller.register_timer(message3, router1, 11, etl::timer::mode::REPEATING); + + router1.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); + } + + std::vector compare1 = { 37ULL, 74ULL }; + std::vector compare2 = { 23ULL, 46ULL, 69ULL, 92ULL }; + std::vector compare3 = { 11ULL, 22ULL, 33ULL, 44ULL, 55ULL, 66ULL, 77ULL, 88ULL, 99ULL }; + + CHECK_ARRAY_EQUAL(compare1.data(), router1.message1.data(), compare1.size()); + CHECK_ARRAY_EQUAL(compare2.data(), router1.message2.data(), compare2.size()); + CHECK_ARRAY_EQUAL(compare3.data(), router1.message3.data(), compare3.size()); + + CHECK_EQUAL(0U, ScopedGuard::guard_count); + } + + //************************************************************************* + TEST(message_timer_repeating_bigger_step) + { + etl::message_timer_interrupt<3, ScopedGuard> timer_controller; + + etl::timer::id::type id1 = timer_controller.register_timer(message1, router1, 37, etl::timer::mode::REPEATING); + etl::timer::id::type id2 = timer_controller.register_timer(message2, router1, 23, etl::timer::mode::REPEATING); + etl::timer::id::type id3 = timer_controller.register_timer(message3, router1, 11, etl::timer::mode::REPEATING); + + router1.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 = 5UL; + + while (ticks <= 100U) + { + ticks += step; + timer_controller.tick(step); + } + + std::vector compare1 = { 40ULL, 75ULL }; + std::vector compare2 = { 25ULL, 50ULL, 70ULL, 95ULL }; + std::vector compare3 = { 15ULL, 25ULL, 35ULL, 45ULL, 55ULL, 70ULL, 80ULL, 90ULL, 100ULL }; + + CHECK_ARRAY_EQUAL(compare1.data(), router1.message1.data(), compare1.size()); + CHECK_ARRAY_EQUAL(compare2.data(), router1.message2.data(), compare2.size()); + CHECK_ARRAY_EQUAL(compare3.data(), router1.message3.data(), compare3.size()); + + CHECK_EQUAL(0U, ScopedGuard::guard_count); + } + + //************************************************************************* + TEST(message_timer_repeating_stop_start) + { + etl::message_timer_interrupt<3, ScopedGuard> timer_controller; + + etl::timer::id::type id1 = timer_controller.register_timer(message1, router1, 37, etl::timer::mode::REPEATING); + etl::timer::id::type id2 = timer_controller.register_timer(message2, router1, 23, etl::timer::mode::REPEATING); + etl::timer::id::type id3 = timer_controller.register_timer(message3, router1, 11, etl::timer::mode::REPEATING); + + router1.clear(); + + timer_controller.start(id3); + timer_controller.start(id2); + + timer_controller.enable(true); + + ticks = 0; + + const uint32_t step = 1UL; + + 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); + } + + std::vector compare1 = { 77ULL }; + std::vector compare2 = { 23ULL }; + std::vector compare3 = { 11ULL, 22ULL, 33ULL, 44ULL, 55ULL, 66ULL, 77ULL, 88ULL, 99ULL }; + + CHECK_ARRAY_EQUAL(compare1.data(), router1.message1.data(), compare1.size()); + CHECK_ARRAY_EQUAL(compare2.data(), router1.message2.data(), compare2.size()); + CHECK_ARRAY_EQUAL(compare3.data(), router1.message3.data(), compare3.size()); + + CHECK_EQUAL(0U, ScopedGuard::guard_count); + } + + //************************************************************************* + TEST(message_timer_repeating_register_unregister) + { + etl::message_timer_interrupt<3, ScopedGuard> timer_controller; + + etl::timer::id::type id1; + etl::timer::id::type id2 = timer_controller.register_timer(message2, router1, 23, etl::timer::mode::REPEATING); + etl::timer::id::type id3 = timer_controller.register_timer(message3, router1, 11, etl::timer::mode::REPEATING); + + router1.clear(); + + timer_controller.start(id3); + timer_controller.start(id2); + + timer_controller.enable(true); + + ticks = 0; + + const uint32_t step = 1UL; + + while (ticks <= 100U) + { + if (ticks == 40) + { + timer_controller.unregister_timer(id2); + + id1 = timer_controller.register_timer(message1, router1, 37, etl::timer::mode::REPEATING); + timer_controller.start(id1); + } + + ticks += step; + timer_controller.tick(step); + } + + std::vector compare1 = { 77ULL }; + std::vector compare2 = { 23ULL }; + std::vector compare3 = { 11ULL, 22ULL, 33ULL, 44ULL, 55ULL, 66ULL, 77ULL, 88ULL, 99ULL }; + + CHECK_ARRAY_EQUAL(compare1.data(), router1.message1.data(), compare1.size()); + CHECK_ARRAY_EQUAL(compare2.data(), router1.message2.data(), compare2.size()); + CHECK_ARRAY_EQUAL(compare3.data(), router1.message3.data(), compare3.size()); + + CHECK_EQUAL(0U, ScopedGuard::guard_count); + } + + //************************************************************************* + TEST(message_timer_repeating_clear) + { + etl::message_timer_interrupt<3, ScopedGuard> timer_controller; + + etl::timer::id::type id1 = timer_controller.register_timer(message1, router1, 37, etl::timer::mode::REPEATING); + etl::timer::id::type id2 = timer_controller.register_timer(message2, router1, 23, etl::timer::mode::REPEATING); + etl::timer::id::type id3 = timer_controller.register_timer(message3, router1, 11, etl::timer::mode::REPEATING); + + router1.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; + + if (ticks == 40) + { + timer_controller.clear(); + } + + timer_controller.tick(step); + } + + std::vector compare1 = { 37ULL }; + std::vector compare2 = { 23ULL }; + std::vector compare3 = { 11ULL, 22ULL, 33ULL }; + + CHECK_ARRAY_EQUAL(compare1.data(), router1.message1.data(), compare1.size()); + CHECK_ARRAY_EQUAL(compare2.data(), router1.message2.data(), compare2.size()); + CHECK_ARRAY_EQUAL(compare3.data(), router1.message3.data(), compare3.size()); + + CHECK_EQUAL(0U, ScopedGuard::guard_count); + } + + //************************************************************************* + TEST(message_timer_route_through_bus) + { + etl::message_timer_interrupt<3, ScopedGuard> timer_controller; + + etl::timer::id::type id1 = timer_controller.register_timer(message1, bus1, 37, etl::timer::mode::SINGLE_SHOT, ROUTER1); + etl::timer::id::type id2 = timer_controller.register_timer(message2, bus1, 23, etl::timer::mode::SINGLE_SHOT, ROUTER1); + etl::timer::id::type id3 = timer_controller.register_timer(message3, bus1, 11, etl::timer::mode::SINGLE_SHOT, etl::imessage_router::ALL_MESSAGE_ROUTERS); + + bus1.subscribe(router1); + + router1.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); + } + + std::vector compare1 = { 37ULL }; + std::vector compare2 = { 23ULL }; + std::vector compare3 = { 11ULL }; + + CHECK_ARRAY_EQUAL(compare1.data(), router1.message1.data(), compare1.size()); + CHECK_ARRAY_EQUAL(compare2.data(), router1.message2.data(), compare2.size()); + CHECK_ARRAY_EQUAL(compare3.data(), router1.message3.data(), compare3.size()); + + CHECK_EQUAL(0U, ScopedGuard::guard_count); + } + + //************************************************************************* + TEST(message_timer_immediate_delayed) + { + etl::message_timer_interrupt<3, ScopedGuard> timer_controller; + + etl::timer::id::type id1 = timer_controller.register_timer(message1, router1, 37, etl::timer::mode::REPEATING); + etl::timer::id::type id2 = timer_controller.register_timer(message2, router1, 23, etl::timer::mode::REPEATING); + etl::timer::id::type id3 = timer_controller.register_timer(message3, router1, 11, etl::timer::mode::REPEATING); + + router1.clear(); + + timer_controller.start(id1); + timer_controller.start(id3); + timer_controller.start(id2); + + timer_controller.enable(true); + + ticks = 5; + timer_controller.tick(uint32_t(ticks)); + + 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 = 1UL; + + while (ticks <= 100U) + { + ticks += step; + timer_controller.tick(step); + } + + std::vector compare1 = { 6ULL, 42ULL, 79ULL }; + std::vector compare2 = { 6ULL, 28ULL, 51ULL, 74ULL, 97ULL }; + std::vector compare3 = { 16ULL, 27ULL, 38ULL, 49ULL, 60ULL, 71ULL, 82ULL, 93ULL }; + + CHECK_ARRAY_EQUAL(compare1.data(), router1.message1.data(), compare1.size()); + CHECK_ARRAY_EQUAL(compare2.data(), router1.message2.data(), compare2.size()); + CHECK_ARRAY_EQUAL(compare3.data(), router1.message3.data(), compare3.size()); + + CHECK_EQUAL(0U, ScopedGuard::guard_count); + } + + //************************************************************************* + TEST(message_timer_one_shot_big_step_short_delay_insert) + { + etl::message_timer_interrupt<3, ScopedGuard> timer_controller; + + etl::timer::id::type id1 = timer_controller.register_timer(message1, router1, 15, etl::timer::mode::SINGLE_SHOT); + etl::timer::id::type id2 = timer_controller.register_timer(message2, router1, 5, etl::timer::mode::REPEATING); + + router1.clear(); + + timer_controller.start(id1); + timer_controller.start(id2); + + timer_controller.enable(true); + + ticks = 0; + + const uint32_t step = 11UL; + + ticks += step; + timer_controller.tick(step); + + ticks += step; + timer_controller.tick(step); + + std::vector compare1 = { 22 }; + std::vector compare2 = { 11, 11, 22, 22 }; + + CHECK_ARRAY_EQUAL(compare1.data(), router1.message1.data(), compare1.size()); + CHECK_ARRAY_EQUAL(compare2.data(), router1.message2.data(), compare2.size()); + + CHECK_EQUAL(0U, ScopedGuard::guard_count); + } + + //************************************************************************* + TEST(message_timer_one_shot_empty_list_huge_tick_before_insert) + { + etl::message_timer_interrupt<3, ScopedGuard> timer_controller; + + etl::timer::id::type id1 = timer_controller.register_timer(message1, router1, 5, etl::timer::mode::SINGLE_SHOT); + + router1.clear(); + + timer_controller.start(id1); + + timer_controller.enable(true); + + ticks = 0; + + const uint32_t step = 5ULL; + + for (uint32_t i = 0UL; i < step; ++i) + { + ++ticks; + timer_controller.tick(1); + } + + // Huge tick count. + timer_controller.tick(UINT32_MAX - step + 1); + + timer_controller.start(id1); + + for (uint32_t i = 0UL; i < step; ++i) + { + ++ticks; + timer_controller.tick(1); + } + std::vector compare1 = { 5, 10 }; + + CHECK_ARRAY_EQUAL(compare1.data(), router1.message1.data(), compare1.size()); + + CHECK_EQUAL(0U, ScopedGuard::guard_count); + } + + //************************************************************************* + + + + class RouterLog : public etl::message_router + { + public: + + //********************************* + RouterLog(etl::imessage_timer_interrupt& timer_controller_) + : message_router(ROUTER1) + , timer_count(0) + , timer_controller(timer_controller_) + { + } + + //********************************* + void on_receive(const Message1&) + { + // Id0 + timer_log.push_back(TimerLogEntry{ 0, timer_count }); + } + + //********************************* + void on_receive(const Message2&) + { + // Id1 + timer_log.push_back(TimerLogEntry{ 1, timer_count }); + } + + //********************************* + void on_receive(const Message3&) + { + // Id2 + timer_log.push_back(TimerLogEntry{ 2, timer_count }); + } + + //********************************* + void on_receive(const Message4&) + { + // Id3 + timer_log.push_back(TimerLogEntry{ 3, timer_count }); + timer_controller.start(1); + } + + //********************************* + void on_receive_unknown(const etl::imessage&) + { + timer_log.push_back(TimerLogEntry{ 99, timer_count }); + } + + size_t timer_count; + std::vector timer_log; + etl::imessage_timer_interrupt& timer_controller; + }; + + TEST(message_timer_interrupt_log_timer_calls) + { + etl::message_timer_interrupt<4, ScopedGuard> timer_controller; + RouterLog router(timer_controller); + + timer_controller.enable(true); + + constexpr uint32_t T1 = 2U; + constexpr uint32_t T2 = 3U; + constexpr uint32_t T3 = 4U; + constexpr uint32_t T4 = 5U; + + // Register the timers. + etl::timer::id::type id1 = timer_controller.register_timer(message1, router, T1, etl::timer::mode::REPEATING); + etl::timer::id::type id2 = timer_controller.register_timer(message2, router, T2, etl::timer::mode::SINGLE_SHOT); + etl::timer::id::type id3 = timer_controller.register_timer(message3, router, T3, etl::timer::mode::REPEATING); + etl::timer::id::type id4 = timer_controller.register_timer(message4, router, T4, etl::timer::mode::REPEATING); + + // Start the repeating timers. + timer_controller.start(id1); + timer_controller.start(id3); + timer_controller.start(id4); + + // Run the timer. + for (int i = 1; i < 50; ++i) + { + ++router.timer_count; + timer_controller.tick(1); + } + + // Check the results log. + for (auto t : router.timer_log) + { + switch (t.id) + { + case 0: + { + CHECK_EQUAL(0, t.time_called % 2); + break; + } + + case 1: + { + CHECK_EQUAL(0, (t.time_called % 5) % 3); + break; + } + + case 2: + { + CHECK_EQUAL(0, t.time_called % 4); + break; + } + + case 3: + { + CHECK_EQUAL(0, t.time_called % 5); + break; + } + + default: + { + CHECK(false); + break; + } + } + } + + // Check the + CHECK_EQUAL(0U, ScopedGuard::guard_count); + } + }; +}