@@ -36,6 +36,8 @@ typedef struct rcl_timer_impl_t
3636 atomic_uint_least64_t period ;
3737 // This is a time in nanoseconds since an unspecified time.
3838 atomic_uint_least64_t last_call_time ;
39+ // This is a time in nanoseconds since an unspecified time.
40+ atomic_uint_least64_t next_call_time ;
3941 // A flag which indicates if the timer is canceled.
4042 atomic_bool canceled ;
4143 // The user supplied allocator.
@@ -75,6 +77,7 @@ rcl_timer_init(
7577 atomic_init (& impl .callback , (uintptr_t )callback );
7678 atomic_init (& impl .period , period );
7779 atomic_init (& impl .last_call_time , now );
80+ atomic_init (& impl .next_call_time , now + period );
7881 atomic_init (& impl .canceled , false);
7982 impl .allocator = allocator ;
8083 timer -> impl = (rcl_timer_impl_t * )allocator .allocate (sizeof (rcl_timer_impl_t ), allocator .state );
@@ -127,11 +130,37 @@ rcl_timer_call(rcl_timer_t * timer)
127130 if (now_ret != RCL_RET_OK ) {
128131 return now_ret ; // rcl error state should already be set.
129132 }
133+ if (now < 0 ) {
134+ RCL_SET_ERROR_MSG ("clock now returned negative time point value" , * allocator );
135+ return RCL_RET_ERROR ;
136+ }
137+ uint64_t unow = (uint64_t )now ;
130138 rcl_time_point_value_t previous_ns =
131- rcl_atomic_exchange_uint64_t (& timer -> impl -> last_call_time , now );
139+ rcl_atomic_exchange_uint64_t (& timer -> impl -> last_call_time , unow );
132140 rcl_timer_callback_t typed_callback =
133141 (rcl_timer_callback_t )rcl_atomic_load_uintptr_t (& timer -> impl -> callback );
134142
143+ uint64_t next_call_time = rcl_atomic_load_uint64_t (& timer -> impl -> next_call_time );
144+ uint64_t period = rcl_atomic_load_uint64_t (& timer -> impl -> period );
145+ // always move the next call time by exactly period forward
146+ // don't use now as the base to avoid extending each cycle by the time
147+ // between the timer being ready and the callback being triggered
148+ next_call_time += period ;
149+ // in case the timer has missed at least once cycle
150+ if (next_call_time < unow ) {
151+ if (0 == period ) {
152+ // a timer with a period of zero is considered always ready
153+ next_call_time = unow ;
154+ } else {
155+ // move the next call time forward by as many periods as necessary
156+ uint64_t now_ahead = unow - next_call_time ;
157+ // rounding up without overflow
158+ uint64_t periods_ahead = 1 + (now_ahead - 1 ) / period ;
159+ next_call_time += periods_ahead * period ;
160+ }
161+ }
162+ rcl_atomic_store (& timer -> impl -> next_call_time , next_call_time );
163+
135164 if (typed_callback != NULL ) {
136165 int64_t since_last_call = now - previous_ns ;
137166 typed_callback (timer , since_last_call );
@@ -171,9 +200,8 @@ rcl_timer_get_time_until_next_call(const rcl_timer_t * timer, int64_t * time_unt
171200 if (ret != RCL_RET_OK ) {
172201 return ret ; // rcl error state should already be set.
173202 }
174- int64_t period = rcl_atomic_load_uint64_t (& timer -> impl -> period );
175203 * time_until_next_call =
176- ( rcl_atomic_load_uint64_t (& timer -> impl -> last_call_time ) + period ) - now ;
204+ rcl_atomic_load_uint64_t (& timer -> impl -> next_call_time ) - now ;
177205 return RCL_RET_OK ;
178206}
179207
@@ -282,7 +310,8 @@ rcl_timer_reset(rcl_timer_t * timer)
282310 if (now_ret != RCL_RET_OK ) {
283311 return now_ret ; // rcl error state should already be set.
284312 }
285- rcl_atomic_store (& timer -> impl -> last_call_time , now );
313+ int64_t period = rcl_atomic_load_uint64_t (& timer -> impl -> period );
314+ rcl_atomic_store (& timer -> impl -> next_call_time , now + period );
286315 rcl_atomic_store (& timer -> impl -> canceled , false);
287316 RCUTILS_LOG_DEBUG_NAMED (ROS_PACKAGE_NAME , "Timer successfully reset" )
288317 return RCL_RET_OK ;
0 commit comments