Source: lib/rate.js

  1. //
  2. // Licensed under the Apache License, Version 2.0 (the "License");
  3. // you may not use this file except in compliance with the License.
  4. // You may obtain a copy of the License at
  5. //
  6. // http://www.apache.org/licenses/LICENSE-2.0
  7. //
  8. // Unless required by applicable law or agreed to in writing, software
  9. // distributed under the License is distributed on an "AS IS" BASIS,
  10. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  11. // See the License for the specific language governing permissions and
  12. // limitations under the License.
  13. 'use strict';
  14. const rclnodejs = require('bindings')('rclnodejs');
  15. const Context = require('./context.js');
  16. const NodeOptions = require('./node_options.js');
  17. const NOP_FN = () => {};
  18. /**
  19. * A timer that runs at a regular frequency (hz).
  20. *
  21. * A client calls Rate#sleep() to block until the end of the current cycle.
  22. * This makes Rate useful for looping at a regular frequency (hz). Rate#sleep()
  23. * avoids blocking the JS event-loop by returning a Promise that the caller
  24. * should block on, e.g. use 'await rate.sleep()'.
  25. *
  26. * Note that Rate.sleep() does not prevent rclnodejs from invoking callbacks
  27. * such as a subscription or client if the entity's node is spinning. Thus
  28. * if your intent is to use rate to synchronize when callbacks are invoked
  29. * then use a spinOnce() just after rate.sleep() as in the example below.
  30. *
  31. * Rate runs within it's own private rcl context. This enables it to be
  32. * available immediately after construction. That is, unlike Timer, Rate
  33. * does not require a spin or spinOnce to be active.
  34. *
  35. * @example
  36. * async function run() {
  37. * await rclnodejs.init();
  38. * const node = rclnodejs.createNode('mynode');
  39. * const rate = node.createRate(1); // 1 hz
  40. * while (true) {
  41. * doSomeStuff();
  42. * await rate.sleep();
  43. * rclnodejs.spinOnce(node);
  44. * }
  45. * }
  46. */
  47. class Rate {
  48. /**
  49. * Create a new instance.
  50. * @hideconstructor
  51. * @param {number} hz - The frequency (hz) between (0.0,1000] hz,
  52. * @param {Timer} timer - The internal timer used by this instance.
  53. * default = 1 hz
  54. */
  55. constructor(hz, timer) {
  56. this._hz = hz;
  57. this._timer = timer;
  58. }
  59. /**
  60. * Get the frequency in hertz (hz) of this timer.
  61. *
  62. * @returns {number} - hertz
  63. */
  64. get frequency() {
  65. return this._hz;
  66. }
  67. /**
  68. * Returns a Promise that when waited on, will block the sender
  69. * until the end of the current timer cycle.
  70. *
  71. * If the Rate has been cancelled, calling this method will
  72. * result in an error.
  73. *
  74. * @example
  75. * (async () => {
  76. * await rate.sleep();
  77. * })();
  78. *
  79. * @returns {Promise} - Waiting on the promise will delay the sender
  80. * (not the Node event-loop) until the end of the current timer cycle.
  81. */
  82. async sleep() {
  83. if (this.isCanceled()) {
  84. throw new Error('Rate has been cancelled.');
  85. }
  86. return new Promise(resolve => {
  87. this._timer.callback = () => {
  88. this._timer.callback = NOP_FN;
  89. resolve();
  90. };
  91. });
  92. }
  93. /**
  94. * Permanently stops the timing behavior.
  95. *
  96. * @returns {undefined}
  97. */
  98. cancel() {
  99. this._timer.cancel();
  100. }
  101. /**
  102. * Determine if this rate has been cancelled.
  103. *
  104. * @returns {boolean} - True when cancel() has been called; False otherwise.
  105. */
  106. isCanceled() {
  107. return this._timer.isCanceled();
  108. }
  109. }
  110. /**
  111. * Internal class that creates Timer instances in a common private rcl context
  112. * for use with Rate. The private rcl context ensures that Rate timers do not
  113. * deadlock waiting for spinOnce/spin on the main rcl context.
  114. */
  115. class RateTimerServer {
  116. /**
  117. * Create a new instance.
  118. *
  119. * @constructor
  120. * @param {Node} parentNode - The parent node for which this server
  121. * supplies timers to.
  122. */
  123. constructor(parentNode) {
  124. this._context = new Context();
  125. // init rcl environment
  126. rclnodejs.init(this._context.handle());
  127. // create hidden node
  128. const nodeName = `_${parentNode.name()}_rate_timer_server`;
  129. const nodeNamespace = parentNode.namespace();
  130. this._node = new rclnodejs.ShadowNode();
  131. this._node.handle = rclnodejs.createNode(
  132. nodeName,
  133. nodeNamespace,
  134. this._context.handle()
  135. );
  136. const options = new NodeOptions();
  137. options.startParameterServices = false;
  138. options.parameterOverrides = parentNode.getParameters();
  139. options.automaticallyDeclareParametersFromOverrides = true;
  140. this._node.init(nodeName, nodeNamespace, this._context, options);
  141. // spin node
  142. this._node.startSpinning(this._context.handle(), 10);
  143. }
  144. /**
  145. * Create a new timer instance with callback set to NOP.
  146. *
  147. * @param {number} period - The period in milliseconds
  148. * @returns {Timer} - The new timer instance.
  149. */
  150. createTimer(period) {
  151. const timer = this._node.createTimer(period, () => {}, this._context);
  152. return timer;
  153. }
  154. /**
  155. * Permanently cancel all timers produced by this server and discontinue
  156. * the ability to create new Timers.
  157. *
  158. * The private rcl context is shutdown in the process and may not be
  159. * restarted.
  160. *
  161. * @returns {undefined}
  162. */
  163. shutdown() {
  164. this._node.destroy();
  165. this._context.shutdown();
  166. }
  167. }
  168. // module.exports = {Rate, RateTimerServer};
  169. module.exports = { Rate, RateTimerServer };