Skip to content
68 changes: 51 additions & 17 deletions docs/data.rst
Original file line number Diff line number Diff line change
Expand Up @@ -129,18 +129,6 @@ To set any numeric type, including enumerations:
output.instance.setNumber('my_double', 2.14)
output.instance.setNumber('my_enum', 2)

.. warning::
The range of values for a numeric field is determined by the type
used to define that field in the configuration file. However,
``setNumber`` and ``getNumber`` can't handle 64-bit integers
(*int64* and *uint64*) whose absolute values are larger than 2^53.
This is a *Connector* limitation due to the use of *double* as an
intermediate representation.

When ``setNumber`` or ``getNumber`` detect this situation, they will raise
an :class:`DDSError`. ``getJson`` and ``setJson`` do not have this
limitation and can handle any 64-bit integer.

To set booleans:

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

.. code-block::

Expand All @@ -191,17 +179,63 @@ getter: :meth:`SampleIterator.getNumber()`, :meth:`SampleIterator.getBoolean()`,


.. note::
The typed getters and setters perform better than ``set``
and ``get`` in applications that write or read at high rates.
Also prefer ``getJson`` and ``setFromJson`` over ``set``
and ``get`` when accessing all or most of the fields of a sample
The typed getters and setters perform better than :meth:`Instance.set`
and :meth:`SampleIterator.get` in applications that write or read at high rates.
Also, prefer :meth:`SampleIterator.getJson` and :meth:`Instance.setFromJson`
over :meth:`Instance.set` and :meth:`SampleIterator.get` when accessing all
or most of the fields of a sample
(see previous section).

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

Accessing 64-bit integers
^^^^^^^^^^^^^^^^^^^^^^^^^
Internally, *Connector* relies on a framework that only contains a single number
type, an IEEE-754 floating-point number. Additionally, *Connector* does not use
JavaScript's BigInt representation for numbers, meaning JavaScript has this same limitation.
As a result, not all 64-bit integers can be represented with exact precision using all
APIs.
If your type contains ``uint64`` or ``int64`` members, and you expect them to be larger
than ``Number.MAX_SAFE_INTEGER`` (or smaller than ``Number.MIN_SAFE_INTEGER``),
then you must take the following into account.

64-bit values with an absolute value greater or equal to 2^53 can be set via:
- The type-agnostic setter, :meth:`Instance.set`. The values must be supplied
as strings, e.g., ``the_output.instance.set('my_uint64', '18446744073709551615')``.
- :meth:`Instance.setString`, e.g., ``the_output.instance.setString('my_uint64', '18446744073709551615')``.
- :meth:`Instance.setFromJson`, if the values are provided as strings, e.g., ``the_output.instance.setFromJson({my_uint64: '18446744073709551615'})``.

64-bit values with an absolute value greater than 2^53 can be retrieved via:
Comment thread
samuelraeburn marked this conversation as resolved.
- The type-agnostic getter, :meth:`SampleIterator.get`. If the absolute value of
the field is less than ``2^53`` it will be returned as a number; otherwise it will be
returned as a string, e.g., ``sample.get(0).get('my_int64') // '9223372036854775807' OR 1234``.
- Using :meth:`SampleIterator.getString`. The value will be returned as a string,
e.g., ``sample.getString(my_int64') // '9223372036854775807' OR '1234'``.

.. warning::

If :meth:`SampleIterator.getNumber()` is used to retrieve a value > 2^53 it will
throw a :class:`DDSError`.

.. warning::

If :meth:`Instance.setNumber()` is used to set a value >= 2^53 it will throw a
:class:`DDSError`.

.. warning::

The :meth:`SampleIterator.getJson()` method should not be used to retrieve integer
values larger than ``Number.MAX_SAFE_INTEGER`` (or smaller than ``Number.MIN_SAFE_INTEGER``).
The values returned may be corrupted **but no error will be thrown**.

.. note::

The :meth:`Instance.setNumber()` operation can safely handle ``abs(value) < 2^53``,
whereas the :meth:`SampleIterator.getNumber()` operation can safely handle ``abs(value) <= 2^53``.

Accessing structs
^^^^^^^^^^^^^^^^^

Expand Down
31 changes: 25 additions & 6 deletions rticonnextdds-connector.js
Original file line number Diff line number Diff line change
Expand Up @@ -321,13 +321,17 @@ function _getAnyValue (getter, connector, inputName, index, fieldName) {
return !!boolVal.deref()
} else if (selection === _AnyValueKind.connector_string) {
const nodeStr = _moveCString(stringVal.deref())
// Try to convert the returned string to a JSON object. We can now return
// one of two things:
// If this is NOT a numeric string, try to convert the returned string to a
// JSON object. We can now return one of two things:
// - An actual string (if the JSON.parse call fails)
// - A JSON object (if the JSON.parse call succeeds)
try {
return JSON.parse(nodeStr)
} catch (err) {
if (isNaN(nodeStr)) {
try {
return JSON.parse(nodeStr)
} catch (err) {
return nodeStr
}
} else {
return nodeStr
}
} else {
Expand Down Expand Up @@ -496,6 +500,11 @@ class SampleIterator {
/**
* Gets the value of a numeric field in this sample.
*
* .. note::
* This operation should not be used with values with an aboslute value
* larger than `Number.MAX_SAFE_INTEGER`. See :ref:`Accessing 64-bit integers`
* for more information.
*
* @param {string} fieldName - The name of the field.
* @returns {number} The numeric value of the field.
*/
Expand Down Expand Up @@ -1279,6 +1288,11 @@ class Instance {
/**
* Sets a numeric field.
*
* .. note::
* This operation should not be used with values with an aboslute value
* larger than `Number.MAX_SAFE_INTEGER`. See :ref:`Accessing 64-bit integers`
* for more information.
*
* @param {string} fieldName - The name of the field.
* @param {number} value - A numeric value, or null, to unset an
* optional member.
Expand Down Expand Up @@ -1331,7 +1345,7 @@ class Instance {
* Sets a string field.
*
* @param {string} fieldName - The name of the field.
* @param {number} value - A string value, or null, to unset an
* @param {string|null} value - A string value, or null, to unset an
* optional member.
*/
setString (fieldName, value) {
Expand Down Expand Up @@ -1420,6 +1434,11 @@ class Instance {
/**
* Retrives the value of this instance as a JSON object.
*
* .. note::
* This operation should not be used with values with an aboslute value
* larger than `Number.MAX_SAFE_INTEGER`. See :ref:`Accessing 64-bit integers`
* for more information.
*
* @returns {JSON} The value of this instance as a JSON object.
*/
getJson () {
Expand Down
39 changes: 20 additions & 19 deletions test/nodejs/test_rticonnextdds_connector.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,45 +42,46 @@ describe('Connector Tests', function () {
})

it('Connector should get instantiated for valid' +
'xml and participant profile', function () {
'xml and participant profile', async function () {
const participantProfile = 'MyParticipantLibrary::Zero'
const xmlProfile = path.join(__dirname, '/../xml/TestConnector.xml')
const connector = new rti.Connector(participantProfile, xmlProfile)
expect(connector).to.exist
expect(connector).to.be.instanceOf(rti.Connector)
connector.close()
await connector.close()
})

it('Multiple Connector objects can be instantiated', () => {
it('Multiple Connector objects can be instantiated', async () => {
const participantProfile = 'MyParticipantLibrary::Zero'
const xmlProfile = path.join(__dirname, '/../xml/TestConnector.xml')
const connectors = []
for (let i = 0; i < 3; i++) {
connectors.push(new rti.Connector(participantProfile, xmlProfile))
}
connectors.forEach((connector) => {

connectors.forEach(async (connector) => {
expect(connector).to.exist
expect(connector).to.be.instanceOf(rti.Connector)
connector.close()
await connector.close()
})
})

// Test for CON-163
it('Multiple Connector obejcts can be instantiated without participant QoS', () => {
it('Multiple Connector obejcts can be instantiated without participant QoS', async () => {
const participantProfile = 'MyParticipantLibrary::MyParticipant'
const xmlProfile = path.join(__dirname, '/../xml/TestConnector3.xml')
const connectors = []
for (let i = 0; i < 2; i++) {
connectors.push(new rti.Connector(participantProfile, xmlProfile))
}
connectors.forEach((connector) => {
connectors.forEach(async (connector) => {
expect(connector).to.exist
expect(connector).to.be.instanceOf(rti.Connector)
connector.close()
await connector.close()
})
})

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

it('Should be possible to create a Connector with participant qos', function () {
const xmlProfile = path.join(__dirname, '/../xml/TestConnector.xml')
const connector = new rti.Connector(
'MyParticipantLibrary::ConnectorWithParticipantQos',
xmlProfile)
expect(connector).to.exist
expect(connector).to.be.instanceOf(rti.Connector)
})
// it('Should be possible to create a Connector with participant qos', async function () {
// const xmlProfile = path.join(__dirname, '/../xml/TestConnector.xml')
// const connector = new rti.Connector(
// 'MyParticipantLibrary::ConnectorWithParticipantQos',
// xmlProfile)
// expect(connector).to.exist
// expect(connector).to.be.instanceOf(rti.Connector)
// await connector.close()
// })

it('is possible to obtain the current version of Connector', function () {
const version = rti.Connector.getVersion()
Expand Down Expand Up @@ -133,7 +135,6 @@ describe('Connector Tests', function () {
expect(connector).to.exist
expect(connector).to.be.instanceOf(rti.Connector)
await connector.close()
await connector.close()
})

describe('Connector callback test', function () {
Expand Down
Loading