Added next timer notifications

This commit is contained in:
John Wellbelove 2026-10-23 18:42:35 +01:00
parent 78bdd671fa
commit 28ac16f035
4 changed files with 191 additions and 95 deletions

View File

@ -58,7 +58,11 @@ namespace etl
{
public:
typedef etl::delegate<void(etl::timer_node&, uint32_t delta)> 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<void(const etl::timer_node*)> 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;
};

View File

@ -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.
//*******************************************

View File

@ -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<MessageProcessor, CallbackMessage>()
, 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<CallbackMessage>;
@ -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<uint64_t> compare1 = {37};
// std::vector<uint64_t> compare2 = {23};
// std::vector<uint64_t> 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);
}
// //*************************************************************************

View File

@ -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();