Add timer autostart and TimerInfo callback support#1472
Conversation
There was a problem hiding this comment.
Pull request overview
This PR extends the timer API to support delayed start (autostart: false) and to optionally provide timer call metadata (TimerInfo) to JS callbacks when the underlying ROS 2 distro exposes rcl_timer_call_with_info, while keeping backward compatibility for older distros.
Changes:
- Extend
Node.createTimer()to accept an options object (currently{ autostart?: boolean }) while preserving the existing “pass a Clock as the 3rd arg” signature. - When supported by the native layer, call timers via
callTimerWithInfo()and pass{ expectedCallTime, actualCallTime }into the timer callback. - Update TypeScript typings and add runtime + type tests covering
autostart: false,TimerInfocallback injection, and options validation.
Reviewed changes
Copilot reviewed 5 out of 7 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| types/timer.d.ts | Tighten callTimerWithInfo() return type to TimerInfo and update doc comment. |
| types/node.d.ts | Add TimerInfo/TimerOptions, update timer callback type, and extend createTimer signature/docs. |
| test/types/index.test-d.ts | Type-level coverage for TimerInfo callback param and autostart: false options usage. |
| test/test-timer.js | Runtime tests for autostart: false, callback TimerInfo delivery, and option type validation. |
| src/rcl_timer_bindings.cpp | Native support for autostart (using rcl_timer_init2 when available, otherwise init+cancel). |
| lib/timer.js | JSDoc update for callTimerWithInfo() return shape. |
| lib/node.js | Implement options/clock argument parsing for createTimer + inject TimerInfo into callbacks when supported. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 7 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (1)
lib/timer.js:163
- The JSDoc return type for
callTimerWithInfo()claims it returns aTimerInfo-shaped object, but on Humble and earlier this method warns and returnsundefined. Update the JSDoc to reflect the actual return type (e.g., includeundefined) so generated docs and editors don’t mislead users.
/**
* Call a timer and starts counting again, retrieves actual and expected call time.
* @return {{expectedCallTime: bigint, actualCallTime: bigint}} - The timer information.
*/
callTimerWithInfo() {
if (DistroUtils.getDistroId() <= DistroUtils.getDistroId('humble')) {
console.warn(
'callTimerWithInfo is not supported by this version of ROS 2'
);
return;
}
return rclnodejs.callTimerWithInfo(this._handle);
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| let timerInfo; | ||
| if (typeof rclnodejs.callTimerWithInfo === 'function') { | ||
| timerInfo = rclnodejs.callTimerWithInfo(timer.handle); | ||
| } else { | ||
| rclnodejs.callTimer(timer.handle); | ||
| } | ||
| timer.callback(timerInfo); |
There was a problem hiding this comment.
Node.execute() now always calls timer.callback(timerInfo) even when timerInfo is undefined (i.e., when callTimerWithInfo is unavailable). That changes observable callback behavior: arguments.length becomes 1 instead of 0, which can break user callbacks that branch on arity. To preserve the existing callback contract, only pass the TimerInfo argument when it is actually available; otherwise invoke the callback with no arguments.
| let timerInfo; | |
| if (typeof rclnodejs.callTimerWithInfo === 'function') { | |
| timerInfo = rclnodejs.callTimerWithInfo(timer.handle); | |
| } else { | |
| rclnodejs.callTimer(timer.handle); | |
| } | |
| timer.callback(timerInfo); | |
| if (typeof rclnodejs.callTimerWithInfo === 'function') { | |
| let timerInfo = rclnodejs.callTimerWithInfo(timer.handle); | |
| timer.callback(timerInfo); | |
| } else { | |
| rclnodejs.callTimer(timer.handle); | |
| timer.callback(); | |
| } |
| bool lossless; | ||
| int64_t period_nsec = info[2].As<Napi::BigInt>().Int64Value(&lossless); | ||
| bool autostart = true; | ||
| if (info.Length() > 3 && info[3].IsBoolean()) { |
There was a problem hiding this comment.
Native CreateTimer silently ignores a non-boolean 4th argument and falls back to autostart = true. Since the JS layer can’t prevent direct calls to rclnodejs.createTimer, it would be safer and more consistent to throw a TypeError when a 4th argument is provided but is not a boolean, rather than silently changing behavior.
| if (info.Length() > 3 && info[3].IsBoolean()) { | |
| if (info.Length() > 3) { | |
| if (!info[3].IsBoolean()) { | |
| Napi::TypeError::New(env, "Timer autostart must be a boolean") | |
| .ThrowAsJavaScriptException(); | |
| return env.Undefined(); | |
| } |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 7 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (1)
lib/timer.js:163
- The JSDoc for
Timer.callTimerWithInfo()claims it returns an object with timestamps, but the method returnsundefinedon Humble and earlier (return;after warning). Please update the@returnannotation to includeundefinedand ideally note the distro gating, so docs match runtime behavior.
/**
* Call a timer and starts counting again, retrieves actual and expected call time.
* @return {{expectedCallTime: bigint, actualCallTime: bigint}} - The timer information.
*/
callTimerWithInfo() {
if (DistroUtils.getDistroId() <= DistroUtils.getDistroId('humble')) {
console.warn(
'callTimerWithInfo is not supported by this version of ROS 2'
);
return;
}
return rclnodejs.callTimerWithInfo(this._handle);
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- extend `Node.createTimer()` to accept `{ autostart?: boolean }` while preserving existing call signatures
- inject `TimerInfo` metadata into JS timer callbacks when native support is available, exposing `expectedCallTime` and `actualCallTime`
- wire native timer creation to honor `autostart`, with a compatible fallback for older ROS distros
- update timer typings for `TimerInfo`, `TimerOptions`, and the optional callback argument
- add runtime and type tests covering `autostart: false`, callback metadata, and option validation
- refresh timer API comments to match the new behavior
Fix: #1471
Node.createTimer()to accept{ autostart?: boolean }while preserving existing call signaturesTimerInfometadata into JS timer callbacks when native support is available, exposingexpectedCallTimeandactualCallTimeautostart, with a compatible fallback for older ROS distrosTimerInfo,TimerOptions, and the optional callback argumentautostart: false, callback metadata, and option validationFix: #1471