diff --git a/include/etl/intrusive_timer.h b/include/etl/intrusive_timer.h index 77a563a8..e4566c88 100644 --- a/include/etl/intrusive_timer.h +++ b/include/etl/intrusive_timer.h @@ -58,7 +58,11 @@ namespace etl { public: - typedef etl::delegate timer_update_callback_type; + //******************************************* + /// The callback type for notifying the user of the next timer that is due to expire. + /// Either returns a pointer to the next timer that is due to expire or etl::timer_node::end() if there is no next timer. + //******************************************* + typedef etl::delegate timer_notification_callback_type; //******************************************* /// Constructor. @@ -167,6 +171,8 @@ namespace etl if (enabled) { + bool notify = false; + if (!lock_policy.is_locked()) { lock_policy.lock(); @@ -181,8 +187,12 @@ namespace etl while (has_active && (count >= timer->delta)) { + // Calculate if we have any time left for the next timer. count -= timer->delta; + // The timer has expired, so reset the delta to 0. + timer->delta = 0; + active_list.remove(timer); // If the timer is repeating then add it back to the active list. @@ -198,6 +208,8 @@ namespace etl timer = active_list.first(); has_active = !active_list.empty(); + + notify = true; } if (has_active) @@ -208,10 +220,19 @@ namespace etl } } + // Get the next timer that is due to expire. + etl::timer_node* next_timer = active_list.first(); + lock_policy.unlock(); was_processed = true; + if (notify) + { + // The next timer has changed, so notify the user. + next_timer_notification.call_if(next_timer); + } + // Invoke all of the expired timers. invoke_expired_timers(); } @@ -222,7 +243,7 @@ namespace etl //******************************************* /// Starts a timer. //******************************************* - bool start(etl::timer_node& timer_, bool immediate_ = false) ETL_NOEXCEPT + bool start(etl::timer_node& timer_, bool immediate_ = false) { bool is_started = false; @@ -237,6 +258,8 @@ namespace etl } } + bool notify = (active_list.first() == &timer_); + lock_policy.unlock(); if (is_started && immediate_) @@ -245,83 +268,81 @@ namespace etl timer_.invoke(timer_.get_config()); } + if (notify) + { + // The next timer has changed, so notify the user. + next_timer_notification.call_if(active_list.first()); + } + return is_started; } //******************************************* /// Stops a timer. //******************************************* - bool stop(etl::timer_node* timer_) ETL_NOEXCEPT + bool stop(etl::timer_node& timer_) ETL_NOEXCEPT { bool is_stopped = false; lock_policy.lock(); - if (timer_->is_active()) + bool notify = false; + + if (timer_.is_active()) { + notify = (active_list.first() == &timer_); stop_impl(timer_); is_stopped = true; } + // Get the next timer that is due to expire. + etl::timer_node* next_timer = active_list.first(); + lock_policy.unlock(); + if (notify) + { + // The next timer has changed, so notify the user. + next_timer_notification.call_if(next_timer); + } + return is_stopped; } - //******************************************* - /// Sets a timer's period. - //******************************************* - bool set_period(etl::timer_node* timer_, uint32_t period_) ETL_NOEXCEPT - { - bool result = false; - - lock_policy.lock(); - - if (timer_->is_registered(this)) - { - if (timer_->is_active()) - { - stop_impl(timer_); - } - - timer_->set_period(period_); - result = true; - } - - lock_policy.unlock(); - - return result; - } - //******************************************* /// Sets a timer's period. //******************************************* bool set_period(etl::timer_node& timer_, uint32_t period_) ETL_NOEXCEPT - { - return set_period(&timer_, period_); - } - - //******************************************* - /// Sets a timer's mode. - //******************************************* - bool set_mode(etl::timer_node* timer_, etl::timer::mode::type mode_) ETL_NOEXCEPT { bool result = false; lock_policy.lock(); - if (timer_->is_registered(this)) + bool notify = false; + + if (timer_.is_registered(this)) { - if (timer_->is_active()) + if (timer_.is_active()) { + notify = (active_list.first() == &timer_); stop_impl(timer_); } - timer_->set_mode(mode_); + + timer_.set_period(period_); result = true; } + // Get the next timer that is due to expire. + etl::timer_node* next_timer = active_list.first(); + lock_policy.unlock(); + if (notify) + { + // The next timer has changed, so notify the user. + next_timer_notification.call_if(next_timer); + } + return result; } @@ -330,7 +351,35 @@ namespace etl //******************************************* bool set_mode(etl::timer_node& timer_, etl::timer::mode::type mode_) ETL_NOEXCEPT { - return set_mode(&timer_, mode_); + bool result = false; + + lock_policy.lock(); + + bool notify = false; + + if (timer_.is_registered(this)) + { + if (timer_.is_active()) + { + notify = (active_list.first() == &timer_); + stop_impl(timer_); + } + timer_.set_mode(mode_); + result = true; + } + + // Get the next timer that is due to expire. + etl::timer_node* next_timer = active_list.first(); + + lock_policy.unlock(); + + if (notify) + { + // The next timer has changed, so notify the user. + next_timer_notification.call_if(next_timer); + } + + return result; } //******************************************* @@ -356,7 +405,7 @@ namespace etl lock_policy.lock(); - if (!active_list.empty()) + if (has_active_timer()) { delta = active_list.first()->get_delta(); } @@ -374,6 +423,22 @@ namespace etl return timer_count; } + //******************************************* + /// Sets the update notification. + //******************************************* + void set_update_notification(timer_notification_callback_type callback) ETL_NOEXCEPT + { + next_timer_notification = callback; + } + + //******************************************* + /// Clears the update notification. + //******************************************* + void clear_time_next_notification() ETL_NOEXCEPT + { + next_timer_notification.clear(); + } + private: //******************************************* @@ -381,7 +446,7 @@ namespace etl //******************************************* etl::timer::id::type get_next_id() ETL_NOEXCEPT { - etl::timer::id::type next_id = 0; // IDs start at 0 + etl::timer::id::type next_id = 0; // IDs start at 0. etl::timer_node* timer = register_list.first(); @@ -389,33 +454,32 @@ namespace etl { if (timer->get_id() == next_id) { - // Already in use, so increment next_id and continue checking + // Already in use, so increment next_id and continue checking. ++next_id; } else if (timer->get_id() > next_id) { - // Found a gap in the IDs, so return the next_id + // Found a gap in the IDs, so return the next_id. return next_id; } timer = register_list.next(timer); } - // We never found a gap, so return the next available ID + // We never found a gap, so return the next available ID. return next_id; } //******************************************* /// Stops a timer without locking. - /// Returns the timer node if it was found, otherwise returns end(). //******************************************* - void stop_impl(etl::timer_node* timer_) ETL_NOEXCEPT + void stop_impl(etl::timer_node& timer_) ETL_NOEXCEPT { - active_list.remove(timer_); + active_list.remove(&timer_); } //******************************************* - /// Invoke for all expired timers. + /// Invoke all of the expired timers. /// This is called after the lock has been released to avoid deadlocks. //******************************************* void invoke_expired_timers() @@ -442,6 +506,8 @@ namespace etl etl::private_timer::timer_active_list active_list; etl::private_timer::timer_expired_list expired_list; + timer_notification_callback_type next_timer_notification; + bool enabled; size_t timer_count; }; diff --git a/include/etl/timer_node.h b/include/etl/timer_node.h index 994fb2ee..cfdda44d 100644 --- a/include/etl/timer_node.h +++ b/include/etl/timer_node.h @@ -224,6 +224,21 @@ namespace etl return config.get_id(); } + //*************************************************************************** + /// Returns a reference to the sentinel timer node used to mark the end of + /// the intrusive timer lists. + /// Implemented as a function-local static so that there is only ever one + /// instance, regardless of how many translation units include this header. + /// This avoids needing C++17 inline variables, so it remains compatible + /// with C++03 and C++11. + //*************************************************************************** + static etl::timer_node* end() ETL_NOEXCEPT + { + static etl::timer_node sentinel_node(etl::timer_config(0, 0)); + + return &sentinel_node; + } + private: //******************************************* @@ -266,21 +281,6 @@ namespace etl return ETL_NULLPTR; } - //*************************************************************************** - /// Returns a reference to the sentinel timer node used to mark the end of - /// the intrusive timer lists. - /// Implemented as a function-local static so that there is only ever one - /// instance, regardless of how many translation units include this header. - /// This avoids needing C++17 inline variables, so it remains compatible - /// with C++03 and C++11. - //*************************************************************************** - static etl::timer_node* end() ETL_NOEXCEPT - { - static etl::timer_node sentinel_node(etl::timer_config(0, 0)); - - return &sentinel_node; - } - //******************************************* /// Clear the next pointers. //******************************************* diff --git a/test/test_intrusive_timer.cpp b/test/test_intrusive_timer.cpp index b393160a..f8eb5b0a 100644 --- a/test/test_intrusive_timer.cpp +++ b/test/test_intrusive_timer.cpp @@ -142,11 +142,13 @@ namespace //************************************************************************* bool global_callback_called = false; const etl::timer_config* global_config; + uint64_t global_tick_count = 0; static void global_callback(const etl::timer_config& config) { global_callback_called = true; global_config = &config; + global_tick_count = ticks; } //************************************************************************* @@ -157,6 +159,7 @@ namespace Object() : member_callback_called(false) , member_config(ETL_NULLPTR) + , member_tick_count(0) { } @@ -164,10 +167,12 @@ namespace { member_callback_called = true; member_config = &config; + member_tick_count = ticks; } bool member_callback_called = false; const etl::timer_config* member_config; + uint64_t member_tick_count; }; static Object object; @@ -198,7 +203,7 @@ namespace : etl::message_router() , message_callback_called(false) , message_config(ETL_NULLPTR) - , value(0) + , message_value(0) { } @@ -207,7 +212,8 @@ namespace { message_callback_called = true; message_config = msg.config; - value = msg.value; + message_value = msg.value; + message_tick_count = ticks; } //******************************************* @@ -215,7 +221,8 @@ namespace bool message_callback_called; const etl::timer_config* message_config; - int value; + int message_value; + uint64_t message_tick_count; }; using MessageTimer = etl::timer_message_node; @@ -223,6 +230,33 @@ namespace MessageProcessor message_processor; CallbackMessage callback_message(42); + //************************************************************************* + // Notification of the next timer that is due to expire. + //************************************************************************* + struct notification_result_type + { + void clear() + { + timer = etl::timer_node::end(); + notified = false; + } + + const etl::timer_node* timer; + bool notified; + }; + + notification_result_type notification_result; + + void next_timer_notification(const etl::timer_node* timer_) + { + notification_result.notified = true; + notification_result.timer = timer_; + } + + using notification_type = etl::intrusive_timer::timer_notification_callback_type; + + notification_type notification_delegate = notification_type::create(next_timer_notification); + SUITE(test_intrusive_timer) { //************************************************************************* @@ -327,7 +361,7 @@ namespace etl::intrusive_timer timer_controller(lock_policy); - etl::timer_config config1(100, etl::timer::mode::Single_Shot); + etl::timer_config config1(37, etl::timer::mode::Single_Shot); CallbackDelegate callback_delegate1 = CallbackDelegate::create(global_callback); CallbackTimer callback_timer1(callback_delegate1, config1); @@ -357,7 +391,7 @@ namespace const uint32_t step = 1UL; - while (ticks <= 100U) + while (ticks <= 110U) { ticks += step; timer_controller.tick(step); @@ -367,29 +401,20 @@ namespace CHECK_EQUAL((int)config1_id, (int)global_config->get_id()); CHECK_EQUAL(config1.get_mode(), global_config->get_mode()); CHECK_EQUAL(config1.get_period(), global_config->get_period()); + CHECK_EQUAL(config1.get_period(), global_tick_count); CHECK_TRUE(object.member_callback_called); CHECK_EQUAL((int)config2_id, (int)object.member_config->get_id()); CHECK_EQUAL(config2.get_mode(), object.member_config->get_mode()); CHECK_EQUAL(config2.get_period(), object.member_config->get_period()); + CHECK_EQUAL(config2.get_period(), object.member_tick_count); CHECK_TRUE(message_processor.message_callback_called); CHECK_EQUAL((int)config3_id, (int)message_processor.message_config->get_id()); CHECK_EQUAL(config3.get_mode(), message_processor.message_config->get_mode()); CHECK_EQUAL(config3.get_period(), message_processor.message_config->get_period()); - CHECK_EQUAL(callback_message.value, message_processor.value); - - // 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(callback_message.value, message_processor.message_value); + CHECK_EQUAL(config3.get_period(), message_processor.message_tick_count); } // //************************************************************************* diff --git a/test/test_timer_lists.cpp b/test/test_timer_lists.cpp index f513102c..44593cbf 100644 --- a/test/test_timer_lists.cpp +++ b/test/test_timer_lists.cpp @@ -194,7 +194,7 @@ namespace etl::timer_node* timer = active_list.first(); - // The timers should be in the order of their deltas. + // The timers should be in the order of their periods. CHECK_EQUAL(&timer2, timer); timer = active_list.next(timer); CHECK_EQUAL(&timer3, timer); @@ -207,14 +207,25 @@ namespace timer = active_list.next(timer); CHECK_EQUAL(active_list.end(), timer); - CHECK_EQUAL(timer2.get_config().get_period(), timer2.get_delta()); - CHECK_EQUAL(timer3.get_config().get_period() - timer2.get_config().get_period(), timer3.get_delta()); - CHECK_EQUAL(timer1.get_config().get_period() - timer3.get_config().get_period(), timer1.get_delta()); - CHECK_EQUAL(timer4.get_config().get_period() - timer1.get_config().get_period(), timer4.get_delta()); - CHECK_EQUAL(timer5.get_config().get_period() - timer3.get_config().get_period(), timer5.get_delta()); + // Check the deltas are correct. + // The period order of the timers in the list is timer2, timer3, timer5, timer1, timer4. + // timer 5 has the same period as timer 3, so it should be after timer 3 in the list. + CHECK_EQUAL(timer2.get_period(), timer2.get_delta()); + CHECK_EQUAL(timer3.get_period() - timer2.get_period(), timer3.get_delta()); + CHECK_EQUAL(timer1.get_period() - timer3.get_period(), timer1.get_delta()); + CHECK_EQUAL(timer4.get_period() - timer1.get_period(), timer4.get_delta()); + CHECK_EQUAL(timer5.get_period() - timer3.get_period(), timer5.get_delta()); + // Remove timer3 from the list. active_list.remove(&timer3); + // Check the deltas are still correct. + // The period order of the timers in the list is now timer2, timer5, timer1, timer4. + CHECK_EQUAL(timer2.get_period(), timer2.get_delta()); + CHECK_EQUAL(timer5.get_period() - timer2.get_period(), timer5.get_delta()); + CHECK_EQUAL(timer1.get_period() - timer5.get_period(), timer1.get_delta()); + CHECK_EQUAL(timer4.get_period() - timer1.get_period(), timer4.get_delta()); + // Search the list to ensure the other timers are still present. bool timer1_found = false; bool timer2_found = false; @@ -256,12 +267,6 @@ namespace CHECK_TRUE(timer4_found); CHECK_TRUE(timer5_found); - // Check the deltas are still correct. - CHECK_EQUAL(timer2.get_config().get_period(), timer2.get_delta()); - CHECK_EQUAL(timer1.get_config().get_period() - timer3.get_config().get_period(), timer1.get_delta()); - CHECK_EQUAL(timer4.get_config().get_period() - timer1.get_config().get_period(), timer4.get_delta()); - CHECK_EQUAL(timer5.get_config().get_period() - timer2.get_config().get_period(), timer5.get_delta()); - // Clear the list. active_list.clear();