Skip to content

Commit 1fb3d8e

Browse files
committed
Add rcl_timer_set_on_reset_callback() support
1 parent a97a449 commit 1fb3d8e

5 files changed

Lines changed: 171 additions & 0 deletions

File tree

lib/timer.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,23 @@ class Timer {
105105
return rclnodejs.getTimerPeriod(this._handle);
106106
}
107107

108+
/**
109+
* Set the on reset callback.
110+
* @param {function} callback - The callback to be called when the timer is reset.
111+
* @return {undefined}
112+
*/
113+
setOnResetCallback(callback) {
114+
rclnodejs.setTimerOnResetCallback(this._handle, callback);
115+
}
116+
117+
/**
118+
* Clear the on reset callback.
119+
* @return {undefined}
120+
*/
121+
clearOnResetCallback() {
122+
rclnodejs.clearTimerOnResetCallback(this._handle);
123+
}
124+
108125
/**
109126
* Call a timer and starts counting again, retrieves actual and expected call time.
110127
* @return {object} - The timer information.

src/rcl_timer_bindings.cpp

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,36 @@
1717
#include <rcl/error_handling.h>
1818
#include <rcl/rcl.h>
1919

20+
#include <mutex>
21+
#include <unordered_map>
22+
2023
#include "macros.h"
2124
#include "rcl_handle.h"
2225
#include "rcl_utilities.h"
2326

2427
namespace rclnodejs {
2528

29+
struct TimerContext {
30+
Napi::ThreadSafeFunction on_reset_callback;
31+
};
32+
33+
static std::unordered_map<rcl_timer_t*, TimerContext*> g_timer_contexts;
34+
static std::mutex g_timer_contexts_mutex;
35+
36+
void TimerOnResetCallbackTrampoline(const void* user_data,
37+
size_t number_of_events) {
38+
const TimerContext* context = static_cast<const TimerContext*>(user_data);
39+
if (context) {
40+
auto callback = [](Napi::Env env, Napi::Function js_callback,
41+
size_t* events) {
42+
js_callback.Call({Napi::Number::New(env, *events)});
43+
delete events;
44+
};
45+
size_t* events_ptr = new size_t(number_of_events);
46+
context->on_reset_callback.BlockingCall(events_ptr, callback);
47+
}
48+
}
49+
2650
Napi::Value CreateTimer(const Napi::CallbackInfo& info) {
2751
Napi::Env env = info.Env();
2852

@@ -61,6 +85,17 @@ Napi::Value CreateTimer(const Napi::CallbackInfo& info) {
6185
auto js_obj =
6286
RclHandle::NewInstance(env, timer, clock_handle, [env](void* ptr) {
6387
rcl_timer_t* timer = reinterpret_cast<rcl_timer_t*>(ptr);
88+
89+
{
90+
std::lock_guard<std::mutex> lock(g_timer_contexts_mutex);
91+
auto it = g_timer_contexts.find(timer);
92+
if (it != g_timer_contexts.end()) {
93+
it->second->on_reset_callback.Release();
94+
delete it->second;
95+
g_timer_contexts.erase(it);
96+
}
97+
}
98+
6499
rcl_ret_t ret = rcl_timer_fini(timer);
65100
free(ptr);
66101
THROW_ERROR_IF_NOT_EQUAL_NO_RETURN(RCL_RET_OK, ret,
@@ -217,6 +252,61 @@ Napi::Value CallTimerWithInfo(const Napi::CallbackInfo& info) {
217252
}
218253
#endif
219254

255+
Napi::Value SetTimerOnResetCallback(const Napi::CallbackInfo& info) {
256+
Napi::Env env = info.Env();
257+
RclHandle* timer_handle = RclHandle::Unwrap(info[0].As<Napi::Object>());
258+
rcl_timer_t* timer = reinterpret_cast<rcl_timer_t*>(timer_handle->ptr());
259+
260+
if (!info[1].IsFunction()) {
261+
Napi::TypeError::New(env, "Callback must be a function")
262+
.ThrowAsJavaScriptException();
263+
return env.Undefined();
264+
}
265+
266+
Napi::Function callback = info[1].As<Napi::Function>();
267+
268+
std::lock_guard<std::mutex> lock(g_timer_contexts_mutex);
269+
TimerContext* context = nullptr;
270+
auto it = g_timer_contexts.find(timer);
271+
if (it == g_timer_contexts.end()) {
272+
context = new TimerContext();
273+
g_timer_contexts[timer] = context;
274+
} else {
275+
context = it->second;
276+
context->on_reset_callback.Release();
277+
}
278+
279+
context->on_reset_callback = Napi::ThreadSafeFunction::New(
280+
env, callback, "TimerOnResetCallback", 0, 1);
281+
282+
THROW_ERROR_IF_NOT_EQUAL(RCL_RET_OK,
283+
rcl_timer_set_on_reset_callback(
284+
timer, TimerOnResetCallbackTrampoline, context),
285+
rcl_get_error_string().str);
286+
287+
return env.Undefined();
288+
}
289+
290+
Napi::Value ClearTimerOnResetCallback(const Napi::CallbackInfo& info) {
291+
Napi::Env env = info.Env();
292+
RclHandle* timer_handle = RclHandle::Unwrap(info[0].As<Napi::Object>());
293+
rcl_timer_t* timer = reinterpret_cast<rcl_timer_t*>(timer_handle->ptr());
294+
295+
std::lock_guard<std::mutex> lock(g_timer_contexts_mutex);
296+
auto it = g_timer_contexts.find(timer);
297+
if (it != g_timer_contexts.end()) {
298+
it->second->on_reset_callback.Release();
299+
delete it->second;
300+
g_timer_contexts.erase(it);
301+
}
302+
303+
THROW_ERROR_IF_NOT_EQUAL(
304+
RCL_RET_OK, rcl_timer_set_on_reset_callback(timer, nullptr, nullptr),
305+
rcl_get_error_string().str);
306+
307+
return env.Undefined();
308+
}
309+
220310
Napi::Object InitTimerBindings(Napi::Env env, Napi::Object exports) {
221311
exports.Set("createTimer", Napi::Function::New(env, CreateTimer));
222312
exports.Set("isTimerReady", Napi::Function::New(env, IsTimerReady));
@@ -230,6 +320,10 @@ Napi::Object InitTimerBindings(Napi::Env env, Napi::Object exports) {
230320
Napi::Function::New(env, TimerGetTimeUntilNextCall));
231321
exports.Set("changeTimerPeriod", Napi::Function::New(env, ChangeTimerPeriod));
232322
exports.Set("getTimerPeriod", Napi::Function::New(env, GetTimerPeriod));
323+
exports.Set("setTimerOnResetCallback",
324+
Napi::Function::New(env, SetTimerOnResetCallback));
325+
exports.Set("clearTimerOnResetCallback",
326+
Napi::Function::New(env, ClearTimerOnResetCallback));
233327
#if ROS_VERSION > 2205 // 2205 == Humble
234328
exports.Set("callTimerWithInfo", Napi::Function::New(env, CallTimerWithInfo));
235329
#endif

test/test-timer.js

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,5 +166,52 @@ describe('rclnodejs Timer class testing', function () {
166166
timer.cancel();
167167
done();
168168
});
169+
170+
it('timer.setOnResetCallback', function (done) {
171+
var timer = node.createTimer(TIMER_INTERVAL, function () {});
172+
var called = false;
173+
timer.setOnResetCallback(function (events) {
174+
called = true;
175+
});
176+
timer.reset();
177+
178+
setTimeout(() => {
179+
assert.ok(called);
180+
timer.cancel();
181+
done();
182+
}, 100);
183+
184+
rclnodejs.spin(node);
185+
});
186+
187+
it('timer.clearOnResetCallback', function (done) {
188+
var timer = node.createTimer(TIMER_INTERVAL, function () {});
189+
var called = false;
190+
timer.setOnResetCallback(function (events) {
191+
called = true;
192+
});
193+
timer.clearOnResetCallback();
194+
timer.reset();
195+
196+
setTimeout(() => {
197+
assert.ok(!called);
198+
timer.cancel();
199+
done();
200+
}, 100);
201+
202+
rclnodejs.spin(node);
203+
});
204+
205+
it('timer callback should be called repeatedly', function (done) {
206+
let count = 0;
207+
const timer = node.createTimer(TIMER_INTERVAL, () => {
208+
count++;
209+
if (count >= 3) {
210+
timer.cancel();
211+
done();
212+
}
213+
});
214+
rclnodejs.spin(node);
215+
});
169216
});
170217
});

test/types/index.test-d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,8 @@ expectType<boolean>(timer.isCanceled());
289289
expectType<void>(timer.cancel());
290290
expectType<void>(timer.changeTimerPeriod(BigInt(100000)));
291291
expectType<bigint>(timer.timerPeriod());
292+
expectType<void>(timer.setOnResetCallback((_events: number) => {}));
293+
expectType<void>(timer.clearOnResetCallback());
292294
expectType<object>(timer.callTimerWithInfo());
293295

294296
// ---- Rate ----

types/timer.d.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,17 @@ declare module 'rclnodejs' {
5858
*/
5959
timerPeriod(): bigint;
6060

61+
/**
62+
* Set the on reset callback.
63+
* @param callback - The callback to be called when the timer is reset.
64+
*/
65+
setOnResetCallback(callback: (events: number) => void): void;
66+
67+
/**
68+
* Clear the on reset callback.
69+
*/
70+
clearOnResetCallback(): void;
71+
6172
/**
6273
* Call a timer and starts counting again, retrieves actual and expected call time.
6374
* @return - The timer information.

0 commit comments

Comments
 (0)