Skip to content

Commit 266eaeb

Browse files
wayneparrottMinggang Wang
authored andcommitted
Support creation of Node subclasses (#754)
Added ability to create Node subclasses. This PR maintains the public 0.17 api. Fix #753
1 parent 6af329a commit 266eaeb

File tree

15 files changed

+1172
-168
lines changed

15 files changed

+1172
-168
lines changed

.eslintrc.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ globals:
66
after: true
77
constantly: true
88
ignorePatterns: ['generated/', 'types/interfaces.d.ts']
9+
parser: 'babel-eslint'
910
rules:
1011
prettier/prettier: 'error'
1112
extends: ['prettier']

index.js

Lines changed: 52 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -49,14 +49,6 @@ const {
4949
getActionServerNamesAndTypesByNode,
5050
getActionNamesAndTypes,
5151
} = require('./lib/action/graph.js');
52-
const Lifecycle = require('./lib/lifecycle.js');
53-
54-
function inherits(target, source) {
55-
const properties = Object.getOwnPropertyNames(source.prototype);
56-
properties.forEach((property) => {
57-
target.prototype[property] = source.prototype[property];
58-
});
59-
}
6052

6153
/**
6254
* Get the version of the generator that was used for the currently present interfaces.
@@ -94,9 +86,6 @@ async function getCurrentGeneratorVersion() {
9486
let rcl = {
9587
_rosVersionChecked: false,
9688

97-
// Map<Context,Array<Node>
98-
_contextToNodeArrayMap: new Map(),
99-
10089
/** {@link Clock} class */
10190
Clock: Clock,
10291

@@ -124,9 +113,6 @@ let rcl = {
124113
/** {@link IntegerRange} class */
125114
IntegerRange: IntegerRange,
126115

127-
/** Lifecycle namespace */
128-
lifecycle: Lifecycle,
129-
130116
/** {@link Logging} class */
131117
logging: logging,
132118

@@ -189,20 +175,15 @@ let rcl = {
189175
* @param {NodeOptions} [options=NodeOptions.defaultOptions] - The options to configure the new node behavior.
190176
* @return {Node} A new instance of the specified node.
191177
* @throws {Error} If the given context is not registered.
178+
* @deprecated since 0.18.0, Use new Node constructor.
192179
*/
193180
createNode(
194181
nodeName,
195182
namespace = '',
196183
context = Context.defaultContext(),
197184
options = NodeOptions.defaultOptions
198185
) {
199-
return _createNode(
200-
nodeName,
201-
namespace,
202-
context,
203-
options,
204-
rclnodejs.ShadowNode
205-
);
186+
return new this.Node(nodeName, namespace, context, options);
206187
},
207188

208189
/**
@@ -214,24 +195,27 @@ let rcl = {
214195
* @param {NodeOptions} [options=NodeOptions.defaultOptions] - The options to configure the new node behavior.
215196
* @return {LifecycleNode} A new instance of the specified node.
216197
* @throws {Error} If the given context is not registered.
198+
* @deprecated since 0.18.0, Use new LifecycleNode constructor.
217199
*/
218200
createLifecycleNode(
219201
nodeName,
220202
namespace = '',
221203
context = Context.defaultContext(),
222204
options = NodeOptions.defaultOptions
223205
) {
224-
return _createNode(
206+
return new this.lifecycle.LifecycleNode(
225207
nodeName,
226208
namespace,
227209
context,
228-
options,
229-
Lifecycle.LifecycleNode
210+
options
230211
);
231212
},
232213

233214
/**
234-
* Initialize the module.
215+
* Initialize an RCL environment, i.e., a Context, and regenerate the javascript
216+
* message files if the output format of the message-generator tool has changed.
217+
* The context serves as a container for nodes, publishers, subscribers, etc. and
218+
* must be initialized before use.
235219
* @param {Context} [context=Context.defaultContext()] - The context to initialize.
236220
* @param {string[]} argv - Process command line arguments.
237221
* @return {Promise<undefined>} A Promise.
@@ -240,7 +224,7 @@ let rcl = {
240224
*/
241225
async init(context = Context.defaultContext(), argv = process.argv) {
242226
// check if context has already been initialized
243-
if (this._contextToNodeArrayMap.has(context)) {
227+
if (!context.isUninitialized()) {
244228
throw new Error('The context has already been initialized.');
245229
}
246230

@@ -252,9 +236,7 @@ let rcl = {
252236
throw new TypeError('argv elements must be strings (and not null).');
253237
}
254238

255-
// initialize context
256239
rclnodejs.init(context.handle, argv);
257-
this._contextToNodeArrayMap.set(context, []);
258240

259241
if (this._rosVersionChecked) {
260242
// no further processing required
@@ -272,77 +254,69 @@ let rcl = {
272254
}
273255

274256
await generator.generateAll(forced);
257+
// TODO determine if tsd generateAll() should be here
275258
this._rosVersionChecked = true;
276259
},
277260

278261
/**
279-
* Start to spin the node, which triggers the event loop to start to check the incoming events.
280-
* @param {Node} node - The node to be spun.
262+
* Start detection and processing of units of work.
263+
* @param {Node} node - The node to be spun up.
281264
* @param {number} [timeout=10] - Timeout to wait in milliseconds. Block forever if negative. Don't wait if 0.
282265
* @throws {Error} If the node is already spinning.
283266
* @return {undefined}
267+
* @deprecated since 0.18.0, Use Node.spin(timeout)
284268
*/
285269
spin(node, timeout = 10) {
286-
if (!(node instanceof rclnodejs.ShadowNode)) {
287-
throw new TypeError('Invalid argument.');
288-
}
289-
if (node.spinning) {
290-
throw new Error('The node is already spinning.');
291-
}
292-
node.startSpinning(timeout);
270+
node.spin(timeout);
293271
},
294272

295273
/**
296-
* Execute one item of work or wait until a timeout expires.
297-
* @param {Node} node - The node to be spun once.
274+
* Spin the node and trigger the event loop to check for one incoming event. Thereafter the node
275+
* will not received additional events until running additional calls to spin() or spinOnce().
276+
* @param {Node} node - The node to be spun.
298277
* @param {number} [timeout=10] - Timeout to wait in milliseconds. Block forever if negative. Don't wait if 0.
299278
* @throws {Error} If the node is already spinning.
300279
* @return {undefined}
280+
* @deprecated since 0.18.0, Use Node.spinOnce(timeout)
301281
*/
302282
spinOnce(node, timeout = 10) {
303-
if (!(node instanceof rclnodejs.ShadowNode)) {
304-
throw new TypeError('Invalid argument.');
305-
}
306-
if (node.spinning) {
307-
throw new Error('The node is already spinning.');
308-
}
309-
node.spinOnce(node.context.handle, timeout);
283+
node.spinOnce(timeout);
310284
},
311285

312286
/**
313-
* Shuts down the given context by shutting down and destroying all nodes contained within.
314-
*
315-
* If no context is explicitly given, only the default context will be shut down, and not all of them.
316-
* This follows the semantics of [rclpy.shutdown()]{@link http://docs.ros2.org/latest/api/rclpy/api/init_shutdown.html#rclpy.shutdown}.
287+
* Shutdown an RCL environment identified by a context. The shutdown process will
288+
* destroy all nodes and related resources in the context. If no context is
289+
* explicitly given, the default context will be shut down.
290+
* This follows the semantics of
291+
* [rclpy.shutdown()]{@link http://docs.ros2.org/latest/api/rclpy/api/init_shutdown.html#rclpy.shutdown}.
317292
*
318-
* @param {Context} [context=Context.defaultContext()] - The context to be shutdown.
319-
* @return {undefined}
320-
* @throws {Error} If there is a problem shutting down the context or while destroying or shutting down a node within it.
293+
* @param { Context } [context = Context.defaultContext()] - The context to be shutdown.
294+
* @return { undefined }
295+
* @throws { Error } If there is a problem shutting down the context or while destroying or shutting down a node within it.
321296
*/
322297
shutdown(context = Context.defaultContext()) {
323-
if (this.isShutdown(context)) {
324-
debug(
325-
`The module rclnodejs (with context handle ${context.handle}) has been shutdown.`
326-
);
327-
} else {
328-
// shutdown and remove all nodes assigned to context
329-
this._contextToNodeArrayMap.get(context).forEach((node) => {
330-
node.stopSpinning();
331-
node.destroy();
332-
});
333-
this._contextToNodeArrayMap.delete(context);
334-
335-
context.shutdown();
298+
context.shutdown();
299+
},
300+
301+
/**
302+
* Shutdown all RCL environments via their contexts.
303+
* @return { undefined }
304+
* @throws { Error } If there is a problem shutting down the context or while destroying or shutting down a node within it.
305+
*/
306+
shutdownAll() {
307+
for (const context of Context.instances) {
308+
this.shutdown(context);
336309
}
337310
},
338311

339312
/**
340-
* A predicate for testing if a context has been shutdown.
313+
* Determine if an RCL environment identified by a context argument
314+
* has been shutdown.
341315
* @param {Context} [context=Context.defaultContext()] - The context to inspect.
342316
* @return {boolean} Return true if the module is shut down, otherwise return false.
343317
*/
344318
isShutdown(context = Context.defaultContext()) {
345-
return !this._contextToNodeArrayMap.has(context);
319+
return !context.isInitialized();
346320
},
347321

348322
/**
@@ -434,56 +408,26 @@ const _sigHandler = () => {
434408
// shuts down all live contexts. Applications that wishes to use their own signal handlers
435409
// should call `rclnodejs.removeSignalHandlers`.
436410
debug('Catch ctrl+c event and will cleanup and terminate.');
437-
for (const ctx of rcl._contextToNodeArrayMap.keys()) {
438-
rcl.shutdown(ctx);
439-
}
411+
rcl.shutdownAll();
440412
};
441413
process.on('SIGINT', _sigHandler);
442414

443415
module.exports = rcl;
444416

445417
// The following statements are located here to work around a
446-
// circular dependency issue occurring in rate.js
418+
// circular dependency issue occurring in rate.js.
419+
// Do not change the order of the following imports.
447420
const Node = require('./lib/node.js');
421+
422+
/** {@link Node} class */
423+
rcl.Node = Node;
424+
448425
const TimeSource = require('./lib/time_source.js');
449426

450427
/** {@link TimeSource} class */
451428
rcl.TimeSource = TimeSource;
452429

453-
inherits(rclnodejs.ShadowNode, Node);
454-
455-
function _createNode(
456-
nodeName,
457-
namespace = '',
458-
context = Context.defaultContext(),
459-
options = NodeOptions.defaultOptions,
460-
nodeClass
461-
) {
462-
if (typeof nodeName !== 'string' || typeof namespace !== 'string') {
463-
throw new TypeError('Invalid argument.');
464-
}
465-
466-
if (!rcl._contextToNodeArrayMap.has(context)) {
467-
throw new Error(
468-
'Invalid context. Must call rclnodejs(context) before using the context'
469-
);
470-
}
471-
472-
let handle = rclnodejs.createNode(nodeName, namespace, context.handle);
473-
let node = new nodeClass();
474-
node.handle = handle;
475-
Object.defineProperty(node, 'handle', {
476-
configurable: false,
477-
writable: false,
478-
}); // make read-only
479-
node.context = context;
480-
node.init(nodeName, namespace, context, options);
481-
debug(
482-
'Finish initializing node, name = %s and namespace = %s.',
483-
nodeName,
484-
namespace
485-
);
430+
const Lifecycle = require('./lib/lifecycle.js');
486431

487-
rcl._contextToNodeArrayMap.get(context).push(node);
488-
return node;
489-
}
432+
/** Lifecycle namespace */
433+
rcl.lifecycle = Lifecycle;

0 commit comments

Comments
 (0)