Skip to content

Commit 1b6cf74

Browse files
authored
feat: add descriptor namespaces (#1095)
This will add a new namespace called `descriptor` for each ROS package interface generated by `generate-ros-messages`. The new namespace is found inside the msg|srv|action namespace for each package. For example: ```typescript namespace builtin_interfaces { namespace msg { export interface Duration { sec: number; nanosec: number; } export interface DurationConstructor { new(other?: Duration): Duration; } export interface Time { sec: number; nanosec: number; } export interface TimeConstructor { new(other?: Time): Time; } namespace descriptor { export interface Duration { sec: 'int32'; nanosec: 'uint32'; } export interface Time { sec: 'int32'; nanosec: 'uint32'; } } } } ``` The descriptor interfaces always have the format `{field: "<interface_type>"}`. For example: ```typescript namespace geometry_msgs { namespace msg { namespace descriptor { export interface PointStamped { header: 'std_msgs/msg/Header'; point: 'geometry_msgs/msg/Point'; } } } } ``` Here is the discussion: [discussions/1091](#1091)
1 parent bc6d4e2 commit 1b6cf74

File tree

3 files changed

+213
-69
lines changed

3 files changed

+213
-69
lines changed

CONTRIBUTORS.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# rclnodejs contributors (sorted alphabetically)
22

3-
- **[Alaa El Jawad](https://github.com/ejalaa12), [Ian McElroy](https://github.com/imcelroy)**
3+
- **[Alaa El Jawad](https://github.com/ejalaa12)**
44

55
- Fix compatibility with ROS2 parameters array types
66
- Unit tests for all parameter types
@@ -20,6 +20,13 @@
2020

2121
- Benchmark test script
2222

23+
- **[Ian McElroy](https://github.com/imcelroy)**
24+
25+
- Add descriptor namespace for all interfaces, rostsd_gen improvements
26+
- Fix compatibility with ROS2 parameters array types
27+
- Unit tests for all parameter types
28+
- Handle concurrent ROS2 client calls, with unit tests
29+
2330
- **[Kenny Yuan](https://github.com/kenny-y)**
2431

2532
- Message features: JS generation, typed arrays, plain JS object, compound msgs, many others...

rostsd_gen/index.js

Lines changed: 167 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ const fs = require('fs');
3131
const loader = require('../lib/interface_loader.js');
3232
const pkgFilters = require('../rosidl_gen/filter.js');
3333

34+
const descriptorInterfaceNamespace = 'descriptor';
35+
3436
async function generateAll() {
3537
// load pkg and interface info (msgs and srvs)
3638
const generatedPath = path.join(__dirname, '../generated/');
@@ -119,47 +121,30 @@ function savePkgInfoAsTSD(pkgInfos, fd) {
119121
for (const subfolder of pkgInfo.subfolders.keys()) {
120122
fs.writeSync(fd, ` namespace ${subfolder} {\n`);
121123

122-
for (const rosInterface of pkgInfo.subfolders.get(subfolder)) {
123-
const type = rosInterface.type();
124-
const fullInterfaceName = `${type.pkgName}/${type.subFolder}/${type.interfaceName}`;
125-
const fullInterfacePath = `${type.pkgName}.${type.subFolder}.${type.interfaceName}`;
126-
const fullInterfaceConstructor = fullInterfacePath + 'Constructor';
127-
128-
if (isMsgInterface(rosInterface)) {
129-
// create message interface
130-
saveMsgAsTSD(rosInterface, fd);
131-
saveMsgConstructorAsTSD(rosInterface, fd);
132-
messagesMap[fullInterfaceName] = fullInterfacePath;
133-
} else if (isSrvInterface(rosInterface)) {
134-
if (
135-
!isValidService(rosInterface, pkgInfo.subfolders.get(subfolder))
136-
) {
137-
let type = rosInterface.type();
138-
console.log(
139-
`Incomplete service: ${type.pkgName}.${type.subFolder}.${type.interfaceName}.`
140-
);
141-
continue;
142-
}
143-
144-
// create service interface
145-
saveSrvAsTSD(rosInterface, fd);
146-
if (!isInternalActionSrvInterface(rosInterface)) {
147-
servicesMap[fullInterfaceName] = fullInterfaceConstructor;
148-
}
149-
} else if (isActionInterface(rosInterface)) {
150-
if (!isValidAction(rosInterface, pkgInfo.subfolders.get(subfolder))) {
151-
let type = rosInterface.type();
152-
console.log(
153-
`Incomplete action: ${type.pkgName}.${type.subFolder}.${type.interfaceName}.`
154-
);
155-
continue;
156-
}
157-
158-
// create action interface
159-
saveActionAsTSD(rosInterface, fd);
160-
actionsMap[fullInterfaceName] = fullInterfaceConstructor;
161-
}
162-
}
124+
// generate real msg/srv/action interfaces
125+
generateRosMsgInterfaces(
126+
pkgInfo,
127+
subfolder,
128+
messagesMap,
129+
servicesMap,
130+
actionsMap,
131+
fd
132+
);
133+
134+
// generate descriptor msg/srv/action interfaces
135+
fs.writeSync(fd, ` namespace ${descriptorInterfaceNamespace} {\n`);
136+
const willGenerateDescriptorInterface = true;
137+
generateRosMsgInterfaces(
138+
pkgInfo,
139+
subfolder,
140+
messagesMap,
141+
servicesMap,
142+
actionsMap,
143+
fd,
144+
willGenerateDescriptorInterface
145+
);
146+
// close namespace descriptor declare
147+
fs.writeSync(fd, ' }\n');
163148

164149
// close namespace declare
165150
fs.writeSync(fd, ' }\n');
@@ -238,16 +223,96 @@ function savePkgInfoAsTSD(pkgInfos, fd) {
238223
fs.writeSync(fd, '}\n');
239224
}
240225

241-
function saveMsgAsTSD(rosMsgInterface, fd) {
242-
fs.writeSync(
243-
fd,
244-
` export interface ${rosMsgInterface.type().interfaceName} {\n`
226+
function generateRosMsgInterfaces(
227+
pkgInfo,
228+
subfolder,
229+
messagesMap,
230+
servicesMap,
231+
actionsMap,
232+
fd,
233+
willGenerateDescriptorInterface = false
234+
) {
235+
const descriptorNamespaceName = willGenerateDescriptorInterface
236+
? `${descriptorInterfaceNamespace}/`
237+
: '';
238+
const descriptorNamespacePath = willGenerateDescriptorInterface
239+
? `${descriptorInterfaceNamespace}.`
240+
: '';
241+
for (const rosInterface of pkgInfo.subfolders.get(subfolder)) {
242+
const type = rosInterface.type();
243+
const fullInterfaceName = `${type.pkgName}/${type.subFolder}/${descriptorNamespaceName}${type.interfaceName}`;
244+
const fullInterfacePath = `${type.pkgName}.${type.subFolder}.${descriptorNamespacePath}${type.interfaceName}`;
245+
const fullInterfaceConstructor = fullInterfacePath + 'Constructor';
246+
247+
const indentStartLevel = willGenerateDescriptorInterface ? 4 : 3;
248+
if (isMsgInterface(rosInterface)) {
249+
// create message interface
250+
saveMsgAsTSD(
251+
rosInterface,
252+
fd,
253+
indentStartLevel,
254+
willGenerateDescriptorInterface
255+
);
256+
saveMsgConstructorAsTSD(rosInterface, fd, indentStartLevel);
257+
messagesMap[fullInterfaceName] = fullInterfacePath;
258+
} else if (isSrvInterface(rosInterface)) {
259+
if (!isValidService(rosInterface, pkgInfo.subfolders.get(subfolder))) {
260+
let type = rosInterface.type();
261+
console.log(
262+
`Incomplete service: ${type.pkgName}.${type.subFolder}.${type.interfaceName}.`
263+
);
264+
continue;
265+
}
266+
267+
// create service interface
268+
saveSrvAsTSD(rosInterface, fd, indentStartLevel);
269+
if (!isInternalActionSrvInterface(rosInterface)) {
270+
servicesMap[fullInterfaceName] = fullInterfaceConstructor;
271+
}
272+
} else if (isActionInterface(rosInterface)) {
273+
if (!isValidAction(rosInterface, pkgInfo.subfolders.get(subfolder))) {
274+
let type = rosInterface.type();
275+
console.log(
276+
`Incomplete action: ${type.pkgName}.${type.subFolder}.${type.interfaceName}.`
277+
);
278+
continue;
279+
}
280+
281+
// create action interface
282+
saveActionAsTSD(rosInterface, fd, indentStartLevel);
283+
actionsMap[fullInterfaceName] = fullInterfaceConstructor;
284+
}
285+
}
286+
}
287+
288+
function saveMsgAsTSD(
289+
rosMsgInterface,
290+
fd,
291+
indentLevel = 3,
292+
willGenerateDescriptorInterface = false
293+
) {
294+
const outerIndentSpacing = getIndentSpacing(indentLevel);
295+
const tmpl = indentString(
296+
`export interface ${rosMsgInterface.type().interfaceName} {\n`,
297+
outerIndentSpacing
245298
);
299+
fs.writeSync(fd, tmpl);
246300
const useSamePkg =
247301
isInternalActionMsgInterface(rosMsgInterface) ||
248302
isInternalServiceEventMsgInterface(rosMsgInterface);
249-
saveMsgFieldsAsTSD(rosMsgInterface, fd, 8, ';', '', useSamePkg);
250-
fs.writeSync(fd, ' }\n');
303+
const innerIndentLevel = indentLevel + 1;
304+
const innerIndentSpacing = getIndentSpacing(innerIndentLevel);
305+
saveMsgFieldsAsTSD(
306+
rosMsgInterface,
307+
fd,
308+
innerIndentSpacing,
309+
';',
310+
'',
311+
useSamePkg,
312+
willGenerateDescriptorInterface
313+
);
314+
const tmplEnd = indentString('}\n', outerIndentSpacing);
315+
fs.writeSync(fd, tmplEnd);
251316
}
252317

253318
/**
@@ -261,6 +326,7 @@ function saveMsgAsTSD(rosMsgInterface, fd) {
261326
* @param {string} typePrefix The prefix to put before the type name for
262327
* non-primitive types
263328
* @param {boolean} useSamePackageSubFolder Indicates if the sub folder name should be taken from the message
329+
* @param {boolean} willGenerateDescriptorInterface Indicates if descriptor interface is being generated
264330
* when the field type comes from the same package. This is needed for action interfaces. Defaults to false.
265331
* @returns {undefined}
266332
*/
@@ -270,7 +336,8 @@ function saveMsgFieldsAsTSD(
270336
indent = 0,
271337
lineEnd = ',',
272338
typePrefix = '',
273-
useSamePackageSubFolder = false
339+
useSamePackageSubFolder = false,
340+
willGenerateDescriptorInterface = false
274341
) {
275342
let type = rosMsgInterface.type();
276343
let fields = rosMsgInterface.ROSMessageDef.fields;
@@ -280,49 +347,62 @@ function saveMsgFieldsAsTSD(
280347
useSamePackageSubFolder && field.type.pkgName === type.pkgName
281348
? type.subFolder
282349
: 'msg';
283-
let fieldType = fieldType2JSName(field, subFolder);
350+
let fieldType = fieldType2JSName(
351+
field,
352+
subFolder,
353+
willGenerateDescriptorInterface
354+
);
284355
let tp = field.type.isPrimitiveType ? '' : typePrefix;
285356
if (typePrefix === 'rclnodejs.') {
286357
fieldType = 'any';
287358
tp = '';
288359
}
289360

290-
const tmpl = indentString(`${field.name}: ${tp}${fieldType}`, indent);
291-
fs.writeSync(fd, tmpl);
361+
let arrayString = '';
292362
if (field.type.isArray) {
293-
fs.writeSync(fd, '[]');
363+
arrayString = '[]';
364+
365+
if (field.type.isFixedSizeArray && willGenerateDescriptorInterface) {
366+
arrayString = `[${field.type.arraySize}]`;
367+
}
294368

295-
if (fieldType === 'number') {
369+
if (fieldType === 'number' && !willGenerateDescriptorInterface) {
296370
// for number[] include alternate typed-array types, e.g., number[] | uint8[]
297371
let jsTypedArrayName = fieldTypeArray2JSTypedArrayName(field.type.type);
298372

299373
if (jsTypedArrayName) {
300-
fs.writeSync(fd, ` | ${jsTypedArrayName}`);
374+
arrayString += ` | ${jsTypedArrayName}`;
301375
}
302376
}
303377
}
378+
const fieldString = willGenerateDescriptorInterface
379+
? `${field.name}: '${tp}${fieldType}${arrayString}'`
380+
: `${field.name}: ${tp}${fieldType}${arrayString}`;
381+
const tmpl = indentString(fieldString, indent);
382+
fs.writeSync(fd, tmpl);
304383

305384
fs.writeSync(fd, lineEnd);
306385
fs.writeSync(fd, '\n');
307386
}
308387
}
309388

310-
function saveMsgConstructorAsTSD(rosMsgInterface, fd) {
389+
function saveMsgConstructorAsTSD(rosMsgInterface, fd, indentLevel = 3) {
311390
const type = rosMsgInterface.type();
312391
const msgName = type.interfaceName;
313-
314-
fs.writeSync(fd, ` export interface ${msgName}Constructor {\n`);
392+
let interfaceTmpl = [`export interface ${msgName}Constructor {`];
315393

316394
for (const constant of rosMsgInterface.ROSMessageDef.constants) {
317395
const constantType = primitiveType2JSName(constant.type);
318-
fs.writeSync(fd, ` readonly ${constant.name}: ${constantType};\n`);
396+
interfaceTmpl.push(` readonly ${constant.name}: ${constantType};`);
319397
}
320-
321-
fs.writeSync(fd, ` new(other?: ${msgName}): ${msgName};\n`);
322-
fs.writeSync(fd, ' }\n');
398+
interfaceTmpl.push(` new(other?: ${msgName}): ${msgName};`);
399+
interfaceTmpl.push('}');
400+
interfaceTmpl.push('');
401+
const indentSpacing = getIndentSpacing(indentLevel);
402+
fs.writeSync(fd, indentLines(interfaceTmpl, indentSpacing).join('\n'));
323403
}
324404

325-
function saveSrvAsTSD(rosSrvInterface, fd) {
405+
function saveSrvAsTSD(rosSrvInterface, fd, indentLevel = 3) {
326406
const serviceName = rosSrvInterface.type().interfaceName;
327407

328408
const interfaceTemplate = [
@@ -332,11 +412,11 @@ function saveSrvAsTSD(rosSrvInterface, fd) {
332412
'}',
333413
'',
334414
];
335-
336-
fs.writeSync(fd, indentLines(interfaceTemplate, 6).join('\n'));
415+
const indentSpacing = getIndentSpacing(indentLevel);
416+
fs.writeSync(fd, indentLines(interfaceTemplate, indentSpacing).join('\n'));
337417
}
338418

339-
function saveActionAsTSD(rosActionInterface, fd) {
419+
function saveActionAsTSD(rosActionInterface, fd, indentLevel = 3) {
340420
const actionName = rosActionInterface.type().interfaceName;
341421

342422
const interfaceTemplate = [
@@ -347,8 +427,19 @@ function saveActionAsTSD(rosActionInterface, fd) {
347427
'}',
348428
'',
349429
];
430+
const indentSpacing = getIndentSpacing(indentLevel);
431+
fs.writeSync(fd, indentLines(interfaceTemplate, indentSpacing).join('\n'));
432+
}
350433

351-
fs.writeSync(fd, indentLines(interfaceTemplate, 6).join('\n'));
434+
/**
435+
* Get number of indent spaces for given level
436+
*
437+
* @param {*} indentLevel Indention level
438+
* @param {*} spacesPerLevel Number of spaces per level
439+
* @returns Total number of space
440+
*/
441+
function getIndentSpacing(indentLevel, spacesPerLevel = 2) {
442+
return indentLevel * spacesPerLevel;
352443
}
353444

354445
function isMsgInterface(rosInterface) {
@@ -451,7 +542,16 @@ function isValidAction(rosActionInterface, infos) {
451542
return matches === SUCCESS_MATCH_COUNT;
452543
}
453544

454-
function fieldType2JSName(fieldInfo, subFolder = 'msg') {
545+
function fieldType2JSName(
546+
fieldInfo,
547+
subFolder = 'msg',
548+
willGenerateDescriptorInterface = false
549+
) {
550+
if (willGenerateDescriptorInterface) {
551+
return fieldInfo.type.isPrimitiveType
552+
? `${fieldInfo.type.type}`
553+
: `${fieldInfo.type.pkgName}/${subFolder}/${fieldInfo.type.type}`;
554+
}
455555
return fieldInfo.type.isPrimitiveType
456556
? primitiveType2JSName(fieldInfo.type.type)
457557
: `${fieldInfo.type.pkgName}.${subFolder}.${fieldInfo.type.type}`;

0 commit comments

Comments
 (0)