Skip to content

Commit 85c5925

Browse files
CON-191, CON-190 64-bit numner support (#64)
* Fixed linting problems + committed the lint config file. Generated the linting config file with the following configuration: sam@rti-10678:~/working/trees/connector/rticonnextdds-connector-js$ eslint --init ✔ How would you like to use ESLint? · style ✔ What type of modules does your project use? · commonjs ✔ Which framework does your project use? · none ✔ Does your project use TypeScript? · No / Yes ✔ Where does your code run? · browser ✔ How would you like to define a style for your project? · guide ✔ Which style guide do you want to follow? · standard ✔ What format do you want your config file to be in? · JSON * Fixed eslint file to state we use Node, not Browser * CON-191: Added 64-bit number support. * CON-191: Added docs * CON-191: writers' edits to doc changes * CON-190: Fixed existing unit tests There was some left over state by the test that loads an additional API, this was causing later discovery checks to fail. Have made all tests independent. * CON-191: Extended testing to cover all use-cases Also discovered that getJSON cannot be used for large ints (and in fact is worse than getNumber since we cannot detect when it would corrupt the value). * CON-191: Finished docs * CON-191: Minor doc edit Co-authored-by: Sam Raeburn <sam@rti.com> Co-authored-by: rkorte <rkorte@rti.com>
1 parent 2c5b560 commit 85c5925

11 files changed

Lines changed: 376 additions & 80 deletions

docs/data.rst

Lines changed: 51 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -129,18 +129,6 @@ To set any numeric type, including enumerations:
129129
output.instance.setNumber('my_double', 2.14)
130130
output.instance.setNumber('my_enum', 2)
131131
132-
.. warning::
133-
The range of values for a numeric field is determined by the type
134-
used to define that field in the configuration file. However,
135-
``setNumber`` and ``getNumber`` can't handle 64-bit integers
136-
(*int64* and *uint64*) whose absolute values are larger than 2^53.
137-
This is a *Connector* limitation due to the use of *double* as an
138-
intermediate representation.
139-
140-
When ``setNumber`` or ``getNumber`` detect this situation, they will raise
141-
an :class:`DDSError`. ``getJson`` and ``setJson`` do not have this
142-
limitation and can handle any 64-bit integer.
143-
144132
To set booleans:
145133

146134
.. code-block::
@@ -170,7 +158,7 @@ Similarly, to get a field in a :class:`Input` sample, use the appropriate
170158
getter: :meth:`SampleIterator.getNumber()`, :meth:`SampleIterator.getBoolean()`,
171159
:meth:`SampleIterator.getString()`, or the type-independent
172160
:meth:`SampleIterator.get()`.
173-
``getString`` also works with numeric fields, returning the number as a string:
161+
:meth:`SampleIterator.getString` also works with numeric fields, returning the number as a string:
174162

175163
.. code-block::
176164
@@ -191,17 +179,63 @@ getter: :meth:`SampleIterator.getNumber()`, :meth:`SampleIterator.getBoolean()`,
191179
192180
193181
.. note::
194-
The typed getters and setters perform better than ``set``
195-
and ``get`` in applications that write or read at high rates.
196-
Also prefer ``getJson`` and ``setFromJson`` over ``set``
197-
and ``get`` when accessing all or most of the fields of a sample
182+
The typed getters and setters perform better than :meth:`Instance.set`
183+
and :meth:`SampleIterator.get` in applications that write or read at high rates.
184+
Also, prefer :meth:`SampleIterator.getJson` and :meth:`Instance.setFromJson`
185+
over :meth:`Instance.set` and :meth:`SampleIterator.get` when accessing all
186+
or most of the fields of a sample
198187
(see previous section).
199188

200189
.. note::
201190
If a field ``my_string``, defined as a string in the configuration file, contains
202191
a value that can be interpreted as a number, ``sample.get('my_string')`` returns
203192
a number, not a string.
204193

194+
Accessing 64-bit integers
195+
^^^^^^^^^^^^^^^^^^^^^^^^^
196+
Internally, *Connector* relies on a framework that only contains a single number
197+
type, an IEEE-754 floating-point number. Additionally, *Connector* does not use
198+
JavaScript's BigInt representation for numbers, meaning JavaScript has this same limitation.
199+
As a result, not all 64-bit integers can be represented with exact precision using all
200+
APIs.
201+
If your type contains ``uint64`` or ``int64`` members, and you expect them to be larger
202+
than ``Number.MAX_SAFE_INTEGER`` (or smaller than ``Number.MIN_SAFE_INTEGER``),
203+
then you must take the following into account.
204+
205+
64-bit values with an absolute value greater or equal to 2^53 can be set via:
206+
- The type-agnostic setter, :meth:`Instance.set`. The values must be supplied
207+
as strings, e.g., ``the_output.instance.set('my_uint64', '18446744073709551615')``.
208+
- :meth:`Instance.setString`, e.g., ``the_output.instance.setString('my_uint64', '18446744073709551615')``.
209+
- :meth:`Instance.setFromJson`, if the values are provided as strings, e.g., ``the_output.instance.setFromJson({my_uint64: '18446744073709551615'})``.
210+
211+
64-bit values with an absolute value greater than 2^53 can be retrieved via:
212+
- The type-agnostic getter, :meth:`SampleIterator.get`. If the absolute value of
213+
the field is less than ``2^53`` it will be returned as a number; otherwise it will be
214+
returned as a string, e.g., ``sample.get(0).get('my_int64') // '9223372036854775807' OR 1234``.
215+
- Using :meth:`SampleIterator.getString`. The value will be returned as a string,
216+
e.g., ``sample.getString(my_int64') // '9223372036854775807' OR '1234'``.
217+
218+
.. warning::
219+
220+
If :meth:`SampleIterator.getNumber()` is used to retrieve a value > 2^53 it will
221+
throw a :class:`DDSError`.
222+
223+
.. warning::
224+
225+
If :meth:`Instance.setNumber()` is used to set a value >= 2^53 it will throw a
226+
:class:`DDSError`.
227+
228+
.. warning::
229+
230+
The :meth:`SampleIterator.getJson()` method should not be used to retrieve integer
231+
values larger than ``Number.MAX_SAFE_INTEGER`` (or smaller than ``Number.MIN_SAFE_INTEGER``).
232+
The values returned may be corrupted **but no error will be thrown**.
233+
234+
.. note::
235+
236+
The :meth:`Instance.setNumber()` operation can safely handle ``abs(value) < 2^53``,
237+
whereas the :meth:`SampleIterator.getNumber()` operation can safely handle ``abs(value) <= 2^53``.
238+
205239
Accessing structs
206240
^^^^^^^^^^^^^^^^^
207241

rticonnextdds-connector.js

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -321,13 +321,17 @@ function _getAnyValue (getter, connector, inputName, index, fieldName) {
321321
return !!boolVal.deref()
322322
} else if (selection === _AnyValueKind.connector_string) {
323323
const nodeStr = _moveCString(stringVal.deref())
324-
// Try to convert the returned string to a JSON object. We can now return
325-
// one of two things:
324+
// If this is NOT a numeric string, try to convert the returned string to a
325+
// JSON object. We can now return one of two things:
326326
// - An actual string (if the JSON.parse call fails)
327327
// - A JSON object (if the JSON.parse call succeeds)
328-
try {
329-
return JSON.parse(nodeStr)
330-
} catch (err) {
328+
if (isNaN(nodeStr)) {
329+
try {
330+
return JSON.parse(nodeStr)
331+
} catch (err) {
332+
return nodeStr
333+
}
334+
} else {
331335
return nodeStr
332336
}
333337
} else {
@@ -496,6 +500,11 @@ class SampleIterator {
496500
/**
497501
* Gets the value of a numeric field in this sample.
498502
*
503+
* .. note::
504+
* This operation should not be used with values with an aboslute value
505+
* larger than `Number.MAX_SAFE_INTEGER`. See :ref:`Accessing 64-bit integers`
506+
* for more information.
507+
*
499508
* @param {string} fieldName - The name of the field.
500509
* @returns {number} The numeric value of the field.
501510
*/
@@ -1279,6 +1288,11 @@ class Instance {
12791288
/**
12801289
* Sets a numeric field.
12811290
*
1291+
* .. note::
1292+
* This operation should not be used with values with an aboslute value
1293+
* larger than `Number.MAX_SAFE_INTEGER`. See :ref:`Accessing 64-bit integers`
1294+
* for more information.
1295+
*
12821296
* @param {string} fieldName - The name of the field.
12831297
* @param {number} value - A numeric value, or null, to unset an
12841298
* optional member.
@@ -1331,7 +1345,7 @@ class Instance {
13311345
* Sets a string field.
13321346
*
13331347
* @param {string} fieldName - The name of the field.
1334-
* @param {number} value - A string value, or null, to unset an
1348+
* @param {string|null} value - A string value, or null, to unset an
13351349
* optional member.
13361350
*/
13371351
setString (fieldName, value) {
@@ -1420,6 +1434,11 @@ class Instance {
14201434
/**
14211435
* Retrives the value of this instance as a JSON object.
14221436
*
1437+
* .. note::
1438+
* This operation should not be used with values with an aboslute value
1439+
* larger than `Number.MAX_SAFE_INTEGER`. See :ref:`Accessing 64-bit integers`
1440+
* for more information.
1441+
*
14231442
* @returns {JSON} The value of this instance as a JSON object.
14241443
*/
14251444
getJson () {

test/nodejs/test_rticonnextdds_connector.js

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -42,45 +42,46 @@ describe('Connector Tests', function () {
4242
})
4343

4444
it('Connector should get instantiated for valid' +
45-
'xml and participant profile', function () {
45+
'xml and participant profile', async function () {
4646
const participantProfile = 'MyParticipantLibrary::Zero'
4747
const xmlProfile = path.join(__dirname, '/../xml/TestConnector.xml')
4848
const connector = new rti.Connector(participantProfile, xmlProfile)
4949
expect(connector).to.exist
5050
expect(connector).to.be.instanceOf(rti.Connector)
51-
connector.close()
51+
await connector.close()
5252
})
5353

54-
it('Multiple Connector objects can be instantiated', () => {
54+
it('Multiple Connector objects can be instantiated', async () => {
5555
const participantProfile = 'MyParticipantLibrary::Zero'
5656
const xmlProfile = path.join(__dirname, '/../xml/TestConnector.xml')
5757
const connectors = []
5858
for (let i = 0; i < 3; i++) {
5959
connectors.push(new rti.Connector(participantProfile, xmlProfile))
6060
}
61-
connectors.forEach((connector) => {
61+
62+
connectors.forEach(async (connector) => {
6263
expect(connector).to.exist
6364
expect(connector).to.be.instanceOf(rti.Connector)
64-
connector.close()
65+
await connector.close()
6566
})
6667
})
6768

6869
// Test for CON-163
69-
it('Multiple Connector obejcts can be instantiated without participant QoS', () => {
70+
it('Multiple Connector obejcts can be instantiated without participant QoS', async () => {
7071
const participantProfile = 'MyParticipantLibrary::MyParticipant'
7172
const xmlProfile = path.join(__dirname, '/../xml/TestConnector3.xml')
7273
const connectors = []
7374
for (let i = 0; i < 2; i++) {
7475
connectors.push(new rti.Connector(participantProfile, xmlProfile))
7576
}
76-
connectors.forEach((connector) => {
77+
connectors.forEach(async (connector) => {
7778
expect(connector).to.exist
7879
expect(connector).to.be.instanceOf(rti.Connector)
79-
connector.close()
80+
await connector.close()
8081
})
8182
})
8283

83-
it('Load two XML files using the url group syntax', function () {
84+
it('Load two XML files using the url group syntax', async function () {
8485
const xmlProfile1 = path.join(__dirname, '/../xml/TestConnector.xml')
8586
const xmlProfile2 = path.join(__dirname, '/../xml/TestConnector2.xml')
8687
const fullXmlPath = xmlProfile1 + ';' + xmlProfile2
@@ -89,17 +90,18 @@ describe('Connector Tests', function () {
8990
expect(connector).to.be.instanceOf(rti.Connector)
9091
const output = connector.getOutput('MyPublisher2::MySquareWriter2')
9192
expect(output).to.exist
92-
connector.close()
93+
await connector.close()
9394
})
9495

95-
it('Should be possible to create a Connector with participant qos', function () {
96-
const xmlProfile = path.join(__dirname, '/../xml/TestConnector.xml')
97-
const connector = new rti.Connector(
98-
'MyParticipantLibrary::ConnectorWithParticipantQos',
99-
xmlProfile)
100-
expect(connector).to.exist
101-
expect(connector).to.be.instanceOf(rti.Connector)
102-
})
96+
// it('Should be possible to create a Connector with participant qos', async function () {
97+
// const xmlProfile = path.join(__dirname, '/../xml/TestConnector.xml')
98+
// const connector = new rti.Connector(
99+
// 'MyParticipantLibrary::ConnectorWithParticipantQos',
100+
// xmlProfile)
101+
// expect(connector).to.exist
102+
// expect(connector).to.be.instanceOf(rti.Connector)
103+
// await connector.close()
104+
// })
103105

104106
it('is possible to obtain the current version of Connector', function () {
105107
const version = rti.Connector.getVersion()
@@ -133,7 +135,6 @@ describe('Connector Tests', function () {
133135
expect(connector).to.exist
134136
expect(connector).to.be.instanceOf(rti.Connector)
135137
await connector.close()
136-
await connector.close()
137138
})
138139

139140
describe('Connector callback test', function () {

0 commit comments

Comments
 (0)