Skip to content

Commit 26983d1

Browse files
wayneparrottMinggang Wang
authored andcommitted
multiple context support #668
index.js * Replaced _nodes array with a Map<context,Array<Node>. A list of nodes is maintained for each context provided to rcl.init(context,argv). * Moved to end of file the import of node.js and time_source.js, the TimeSource property and ShadowNode inheritance function call. This change was made to workaround a circular dependency in rate.js. * Added context parameter to `isShutdown(context=Context.defaultContext())` node.js * Changed createRate() to return Promise<Rate> * Removed the context parameter from createTimer() rate.js * Reimplemented RateTimerServer to use public rclnodejs methods. * Added `init(): Promise<void>` method which creates internal private context and node test-init-shutdown.js * Updated to include a multiple-contexts testcase test-rate.js * Updated to use node.createRate(): Promise<Rate> correctly. node.d.ts * Removed context parameter from createTimer() method declaration. main.ts * Updated Rate tests for async node.createRate()
1 parent 7164d13 commit 26983d1

File tree

8 files changed

+164
-84
lines changed

8 files changed

+164
-84
lines changed

example/rate-example.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ async function main() {
3535
undefined,
3636
msg => console.log(`Received(${Date.now()}): ${msg.data}`)
3737
);
38-
const rate = node.createRate(0.5);
38+
const rate = await node.createRate(0.5);
3939

4040
setInterval(() => publisher.publish(`hello ${Date.now()}`), 10);
4141

index.js

Lines changed: 57 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ const fs = require('fs');
2424
const generator = require('./rosidl_gen/index.js');
2525
const loader = require('./lib/interface_loader.js');
2626
const logging = require('./lib/logging.js');
27-
const Node = require('./lib/node.js');
27+
// const Node = require('./lib/node.js');
2828
const NodeOptions = require('./lib/node_options.js');
2929
const {
3030
FloatingPointRange,
@@ -40,7 +40,6 @@ const rclnodejs = require('bindings')('rclnodejs');
4040
const tsdGenerator = require('./rostsd_gen/index.js');
4141
const validator = require('./lib/validator.js');
4242
const Time = require('./lib/time.js');
43-
const TimeSource = require('./lib/time_source.js');
4443
const ActionClient = require('./lib/action/client.js');
4544
const ActionServer = require('./lib/action/server.js');
4645
const ClientGoalHandle = require('./lib/action/client_goal_handle.js');
@@ -59,7 +58,7 @@ function inherits(target, source) {
5958
});
6059
}
6160

62-
inherits(rclnodejs.ShadowNode, Node);
61+
// inherits(rclnodejs.ShadowNode, Node);
6362

6463
function getCurrentGeneratorVersion() {
6564
let jsonFilePath = path.join(generator.generatedRoot, 'generator.json');
@@ -90,8 +89,12 @@ function getCurrentGeneratorVersion() {
9089
* @exports rclnodejs
9190
*/
9291
let rcl = {
93-
_initialized: false,
94-
_nodes: [],
92+
93+
// flag identifies when module has been init'ed
94+
initialized: false,
95+
96+
// Map<Context,Array<Node>
97+
_nodes: new Map(),
9598

9699
/** {@link Clock} class */
97100
Clock: Clock,
@@ -144,9 +147,6 @@ let rcl = {
144147
/** {@link Time} class */
145148
Time: Time,
146149

147-
/** {@link TimeSource} class */
148-
TimeSource: TimeSource,
149-
150150
/** {@link module:validator|validator} object */
151151
validator: validator,
152152

@@ -195,19 +195,22 @@ let rcl = {
195195
throw new TypeError('Invalid argument.');
196196
}
197197

198+
if (!this._nodes.has(context)) {
199+
throw new Error('Invalid context. Must call rclnodejs(context) before using the context');
200+
}
201+
198202
let handle = rclnodejs.createNode(nodeName, namespace, context.handle());
199203
let node = new rclnodejs.ShadowNode();
200204
node.handle = handle;
201205
node.context = context;
202-
203206
node.init(nodeName, namespace, context, options);
204207
debug(
205208
'Finish initializing node, name = %s and namespace = %s.',
206209
nodeName,
207210
namespace
208211
);
209212

210-
this._nodes.push(node);
213+
this._nodes.get(context).push(node);
211214
return node;
212215
},
213216

@@ -218,7 +221,13 @@ let rcl = {
218221
* @return {Promise<undefined>} A Promise.
219222
*/
220223
init(context = Context.defaultContext(), argv = process.argv) {
224+
221225
return new Promise((resolve, reject) => {
226+
// check if context has already been initialized
227+
if (this._nodes.has(context)) {
228+
throw new Error('The module rclnodejs has been initialized.');
229+
};
230+
222231
// check argv for correct value and state
223232
if (!Array.isArray(argv)) {
224233
throw new TypeError('argv must be an array.');
@@ -227,6 +236,10 @@ let rcl = {
227236
throw new TypeError('argv elements must not be null');
228237
}
229238

239+
// setup internal state for context outside promise.
240+
rclnodejs.init(context.handle(), argv);
241+
this._nodes.set(context, []);
242+
230243
let that = this;
231244
if (!this._initialized) {
232245
getCurrentGeneratorVersion()
@@ -245,8 +258,6 @@ let rcl = {
245258
generator
246259
.generateAll(forced)
247260
.then(() => {
248-
this._context = context;
249-
rclnodejs.init(context.handle(), argv);
250261
this._initialized = true;
251262
resolve();
252263
})
@@ -258,7 +269,7 @@ let rcl = {
258269
reject(e);
259270
});
260271
} else {
261-
throw new Error('The module rclnodejs has been initialized.');
272+
resolve();
262273
}
263274
});
264275
},
@@ -276,7 +287,7 @@ let rcl = {
276287
if (node.spinning) {
277288
throw new Error('The node is already spinning.');
278289
}
279-
node.startSpinning(this._context.handle(), timeout);
290+
node.startSpinning(timeout);
280291
},
281292

282293
/**
@@ -292,37 +303,47 @@ let rcl = {
292303
if (node.spinning) {
293304
throw new Error('The node is already spinning.');
294305
}
295-
node.spinOnce(this._context.handle(), timeout);
306+
node.spinOnce(node.context.handle(), timeout);
296307
},
297308

298309
/**
299310
* @param {Context} context - The context to be shutdown.
300311
* @return {undefined}
301312
*/
302-
shutdown(context) {
303-
if (!this._initialized) {
304-
throw new Error('The module rclnodejs has been shut.');
313+
shutdown(context = Context.defaultContext()) {
314+
if (this.isShutdown(context)) {
315+
throw new Error('The module rclnodejs has been shutdown.');
316+
return;
305317
}
306318

307-
this._nodes.forEach(node => {
319+
// check for non-existant or deleted context
320+
// if (context === Context.defaultContext()) {
321+
// console.log('default ctx', this._nodes.has(Context.defaultContext()));
322+
// }
323+
if (!this._nodes.has(context)) {console.log('xxx'); return;}
324+
325+
// shutdown and remove all nodes assigned to context
326+
this._nodes.get(context).forEach(node => {
308327
node.stopSpinning();
309328
node.destroy();
310329
});
311-
if (!context) {
330+
this._nodes.delete(context);
331+
332+
// shutdown context
333+
if (context === Context.defaultContext()) {
312334
Context.shutdownDefaultContext();
313335
} else {
314336
context.shutdown();
315337
}
316-
this._nodes = [];
317-
this._initialized = false;
318-
this._context = undefined;
319338
},
339+
320340
/**
321-
* Return status that whether the module is shut down.
341+
* A predictate for testing if a context has been shutdown.
342+
* @param {Context} [context=defaultContext] - The context to inspect
322343
* @return {boolean} Return true if the module is shut down, otherwise return false.
323344
*/
324-
isShutdown() {
325-
return !this._initialized;
345+
isShutdown(context = Context.defaultContext()) {
346+
return !this._nodes.has(context);
326347
},
327348

328349
/**
@@ -414,3 +435,13 @@ process.on('SIGINT', () => {
414435
});
415436

416437
module.exports = rcl;
438+
439+
// The following statements are located here to work around a
440+
// circular dependency issue occuring in rate.js
441+
const Node = require('./lib/node.js');
442+
const TimeSource = require('./lib/time_source.js');
443+
444+
/** {@link TimeSource} class */
445+
rcl.TimeSource = TimeSource;
446+
447+
inherits(rclnodejs.ShadowNode, Node);

lib/node.js

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -330,8 +330,8 @@ class Node {
330330
});
331331
}
332332

333-
startSpinning(context, timeout) {
334-
this.start(context, timeout);
333+
startSpinning(timeout) {
334+
this.start(this.context.handle(), timeout);
335335
this.spinning = true;
336336
}
337337

@@ -382,16 +382,20 @@ class Node {
382382
* Create a Timer.
383383
* @param {number} period - The number representing period in millisecond.
384384
* @param {function} callback - The callback to be called when timeout.
385-
* @param {Context} context - The context, default is Context.defaultContext().
386-
* @param {Clock} clock - The clock which the timer gets time from.
385+
* @param {Clock} [clock] - The clock which the timer gets time from.
387386
* @return {Timer} - An instance of Timer.
388387
*/
389388
createTimer(
390389
period,
391390
callback,
392-
context = Context.defaultContext(),
393391
clock = null
394392
) {
393+
if (arguments.length === 3 && !(arguments[2] instanceof Clock)) {
394+
clock = null;
395+
} else if (arguments.length === 4) {
396+
clock = arguments[3];
397+
}
398+
395399
if (typeof period !== 'number' || typeof callback !== 'function') {
396400
throw new TypeError('Invalid argument');
397401
}
@@ -413,7 +417,7 @@ class Node {
413417

414418
let timerHandle = rclnodejs.createTimer(
415419
timerClock.handle,
416-
context.handle(),
420+
this.context.handle(),
417421
period
418422
);
419423
let timer = new Timer(timerHandle, period, callback);
@@ -428,9 +432,9 @@ class Node {
428432
* Create a Rate.
429433
*
430434
* @param {number} hz - The frequency of the rate timer; default is 1 hz.
431-
* @returns {Rate} - New instance
435+
* @returns {Promise<Rate>} - Promise resolving to new instance of Rate.
432436
*/
433-
createRate(hz = 1) {
437+
async createRate(hz = 1) {
434438
if (typeof hz !== 'number') {
435439
throw new TypeError('Invalid argument');
436440
}
@@ -445,6 +449,7 @@ class Node {
445449
// lazy initialize rateTimerServer
446450
if (!this._rateTimerServer) {
447451
this._rateTimerServer = new Rates.RateTimerServer(this);
452+
await this._rateTimerServer.init();
448453
}
449454

450455
const period = Math.round(1000 / hz);

lib/rate.js

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
'use strict';
1515

16-
const rclnodejs = require('bindings')('rclnodejs');
16+
const rclnodejs = require('../index.js');
1717
const Context = require('./context.js');
1818
const NodeOptions = require('./node_options.js');
1919

@@ -40,7 +40,7 @@ const NOP_FN = () => {};
4040
* async function run() {
4141
* await rclnodejs.init();
4242
* const node = rclnodejs.createNode('mynode');
43-
* const rate = node.createRate(1); // 1 hz
43+
* const rate = await node.createRate(1); // 1 hz
4444
* while (true) {
4545
* doSomeStuff();
4646
* await rate.sleep();
@@ -131,30 +131,32 @@ class RateTimerServer {
131131
* supplies timers to.
132132
*/
133133
constructor(parentNode) {
134+
this._parentNode = parentNode;
134135
this._context = new Context();
136+
}
135137

136-
// init rcl environment
137-
rclnodejs.init(this._context.handle());
138+
/**
139+
* Setup the server's rcl context and node in preparation for creating
140+
* rate timer instances.
141+
*
142+
* @returns {undefined}
143+
*/
144+
async init() {
145+
await rclnodejs.init(this._context);
138146

139147
// create hidden node
140-
const nodeName = `_${parentNode.name()}_rate_timer_server`;
141-
const nodeNamespace = parentNode.namespace();
142-
this._node = new rclnodejs.ShadowNode();
143-
this._node.handle = rclnodejs.createNode(
144-
nodeName,
145-
nodeNamespace,
146-
this._context.handle()
147-
);
148-
148+
const nodeName = `_${this._parentNode.name()}_rate_timer_server`;
149+
const nodeNamespace = this._parentNode.namespace();
149150
const options = new NodeOptions();
150151
options.startParameterServices = false;
151-
options.parameterOverrides = parentNode.getParameters();
152+
options.parameterOverrides = this._parentNode.getParameters();
152153
options.automaticallyDeclareParametersFromOverrides = true;
153154

154-
this._node.init(nodeName, nodeNamespace, this._context, options);
155+
this._node =
156+
rclnodejs.createNode(nodeName, nodeNamespace, this._context, options);
155157

156158
// spin node
157-
this._node.startSpinning(this._context.handle(), 10);
159+
rclnodejs.spin(this._node, 10);
158160
}
159161

160162
/**
@@ -164,7 +166,7 @@ class RateTimerServer {
164166
* @returns {Timer} - The new timer instance.
165167
*/
166168
createTimer(period) {
167-
const timer = this._node.createTimer(period, () => {}, this._context);
169+
const timer = this._node.createTimer(period, () => {});
168170
return timer;
169171
}
170172

@@ -178,8 +180,7 @@ class RateTimerServer {
178180
* @returns {undefined}
179181
*/
180182
shutdown() {
181-
this._node.destroy();
182-
this._context.shutdown();
183+
rclnodejs.shutdown(this._context);
183184
}
184185
}
185186
// module.exports = {Rate, RateTimerServer};

test/test-init-shutdown.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,4 +160,39 @@ describe('Node destroy testing', function() {
160160
rclnodejs.createNode('my_node');
161161
});
162162
});
163+
164+
it('rclnodejs multiple contexts init shutdown sequence', async function() {
165+
await rclnodejs.init();
166+
assert.ok(!rclnodejs.isShutdown());
167+
168+
let ctx = new rclnodejs.Context();
169+
await rclnodejs.init(ctx);
170+
assert.ok(!rclnodejs.isShutdown(ctx));
171+
assert.ok(!rclnodejs.isShutdown());
172+
173+
assert.doesNotThrow(() => rclnodejs.shutdown());
174+
assert.ok(rclnodejs.isShutdown());
175+
assert.ok(!rclnodejs.isShutdown(ctx));
176+
177+
assert.doesNotThrow(() => rclnodejs.shutdown(ctx));
178+
assert.ok(rclnodejs.isShutdown());
179+
assert.ok(rclnodejs.isShutdown(ctx));
180+
181+
// repeat
182+
await rclnodejs.init();
183+
assert.ok(!rclnodejs.isShutdown());
184+
185+
ctx = new rclnodejs.Context();
186+
await rclnodejs.init(ctx);
187+
assert.ok(!rclnodejs.isShutdown(ctx));
188+
assert.ok(!rclnodejs.isShutdown());
189+
190+
assert.doesNotThrow(() => rclnodejs.shutdown());
191+
assert.ok(rclnodejs.isShutdown());
192+
assert.ok(!rclnodejs.isShutdown(ctx));
193+
194+
assert.doesNotThrow(() => rclnodejs.shutdown(ctx));
195+
assert.ok(rclnodejs.isShutdown());
196+
assert.ok(rclnodejs.isShutdown(ctx));
197+
});
163198
});

0 commit comments

Comments
 (0)