DDS 发送大数据

news2024/11/25 18:52:41

Sending Large Data

This section describes the capabilities offered by Connext DDS—specifically, RTI FlatData language binding and Zero Copy transfer over shared memory—that allow sending and receiving large data samples with minimum latency. In this section, “large data” means samples with a large serialized size, usually on the order of MBs, such as video frame samples.

Note: If you implement FlatData language binding or Zero Copy transfer over shared memory with data smaller than this, you may not see significant difference in latency or even pay a penalty in latency.

The definition of “large data” in this chapter contrasts with other definitions of large data in this manual:

  • In 6.4.1 ASYNCHRONOUS_PUBLISHER QosPolicy (DDS Extension), “large data” is defined as data that cannot be sent as a single packet by a transport. The concept of large data in this section is decoupled from the maximum message size of the underlying transport, although these two things are related: samples with a size in the order of MBs will usually be greater than the underlying transport’s maximum message size.

  • In 21. DDS Sample-Data and Instance-Data Memory Management, “large data” refers to types whose samples have a large maximum serialized size independently of the actual serialized size of the samples sent on the wire. This contrasts with the definition of “large data” in this section, which refers to samples with a large serialized size.

The primary consideration when sending large samples is latency. When using Connext DDS, and in general any connectivity framework, sample latency has three components: middleware, copy, and transport (see Figure 22.1: Basic Components of Latency).

Figure 22.1: Basic Components of Latency

When Connext DDS is used to send small data samples, such as temperature readings, the weight of the copy component in the total sample latency is small. But when samples are large, the weight of the copy component becomes considerable. (See Figure 22.2: Copy Components Compared.)

Figure 22.2: Copy Components Compared

Therefore, reducing the number of copies made by the middleware or network infrastructure when publishing and receiving large samples becomes critical. Two features allow reducing the number of sample copies and consequently the transmission latency: Zero Copy transfer over shared memory and FlatData language binding. These two features can be used individually or in combination.

Use Cases

Zero Copy transfer over shared memory and FlatData language binding are recommended when your strict latency requirements cannot be met by regular C/C++ language binding (which defines the in-memory representation of a type), and the UDP and shared memory transports. For example, video applications such as video conferencing, video surveillance, or computer vision usually have strict latency requirements, especially if the video signal is used to do control. Consider, for instance, a latency requirement of less than 100 milliseconds. This latency must account for different components such as:

  • Video compression

  • Video decoding

  • Transmission

  • Image scaling

  • Application processing logic

To keep latency to a minimum for large data samples, reduce the number of copies made by the middleware or network infrastructure by using FlatData language binding, Zero Copy transfer over shared memory, or both.

Copies in the Middleware Memory Space

Figure 22.3: Number of Copies Out-of-the-Box shows how many times Connext DDS may copy a large sample sent over UDP or shared memory. The diagram assumes that the samples have to be fragmented by the middleware (via DDS fragmentation) because their serialized size is greater than the underlying transport MTU (maximum transmission unit), which can be configured by setting message_size_max in the transport properties (see 15. Transport Plugins). Note that these are copies in the middleware memory space—the operating system network stack may make additional copies.

Figure 22.3: Number of Copies Out-of-the-Box

For both UDP and shared memory (SHMEM), the copies are as follows, out of the box:

  1. Copy 1 is the serialization copy. Connext DDS calls TypePlugin::serialize to convert the in-memory representation of a sample, such as a C++ object, into a data representation, called a wire representation, with a format suitable for storage or transmission.

After a sample is serialized, it is sent to the subscribing application using one or more of the available transports. When the underlying transport maximum message size is smaller than the serialized size of the sample, the sample must be fragmented. The fragmentation process does not require any extra copy. Fragments refer directly to offsets in the serialization buffer.

Note: The transport maximum message size can be configured using the property dds.transport.UDPv4.builtin.parent.message_size_max for UDPv4 and dds.transport.shmem.builtin.parent.message_size_max for SHMEM. There are equivalent properties for other transports, such as TCPv4 and UDPv6.

  1. Copy 2: For SHMEM, the sample fragments that live in the local memory space of the publishing process have to be copied into the shared memory segment from which the subscribing application will read them. For UDP, the call to the socket receive operation copies the fragments.

  1. Copy 3: After they are received, the sample fragments are reassembled into a single buffer.

  1. Copy 4 is the deserialization copy. Connext DDS calls TypePlugin::deserialize to convert the wire memory representation of a sample into its in-memory representation, such as a C++ object.

Figure 22.4: Number of Copies Using FlatData Language Binding

FlatData is a language binding in which the in-memory representation of a sample matches the wire representation. Therefore, the cost of serialization/deserialization is zero. You can directly access the serialized data without deserializing it first. When using FlatData language binding, Copy 1 and Copy 4 in Figure 22.3: Number of Copies Out-of-the-Box are removed for both UDP and SHMEM communications. See Figure 22.3: Number of Copies Out-of-the-Box.

Choosing between FlatData Language Binding and Zero Copy Transfer over Shared Memory

Whether to use Zero Copy transfer over shared memory or FlatData language binding, or both, depends on whether the DataReaders run on the same host as the DataWriters, on different hosts, or a combination of both. It also depends on the definition of the type. Zero Copy transfer over shared memory requires the FlatData language binding when the type is variable-size. The following table summarizes how to choose between these features:

Table 22.1 Zero Copy Transfer Over Shared Memory vs. FlatData Language Binding

Readers and writers run on same host

Readers and writers run on different hosts

Some readers/writers run on same host, some on different hosts

Fixed-size types

Use Zero Copy

Use FlatData

Use both Zero Copy and FlatData

Variable-size types

Use both Zero Copy and FlatData

Use FlatData

Use both Zero Copy and FlatData

In summary, for DataReaders running on the same host as the DataWriter, the DataWriter can take advantage of Zero Copy transfer over shared memory. For DataReaders running on a different host, the DataWriter won’t use Zero Copy transfer over shared memory, but can benefit from FlatData language binding. Therefore, when you have writers and readers running on the same and different hosts, it is recommended to use both Zero Copy transfer over shared memory and FlatData language binding, and let the DataWriter use the correct option for each DataReader.

For more information, see 22.4 FlatData Language Binding and 22.5 Zero Copy Transfer Over Shared Memory.

FlatData Language Binding

FlatData language binding offers the following benefits:

  • Reduced number of copies: from four to two for both SHMEM and UDP transports (see 22.4 FlatData Language Binding), because there is no need to serialize and deserialize a sample.

  • Reduced memory consumption and CPU load, due to reduced data copying.

  • Improved latency for large data samples.

FlatData Representation

When you create a FlatData sample (see 22.4.2.2 Programming with FlatData Language Binding), the in-memory representation for the sample buffer is XCDR encoding version 2 (XCDR2), using the endianness of the host where the sample is created to populate the buffer. The use of the host platform endianness allows fast access to the sample content, because the setters and getters do not have to change the endianness.

If you use a DataReader to read a FlatData sample that was received from a DataWriter running on a platform with a different endianness, however, direct access to the sample content is not possible, making the subscribing application less performant.

Note: Because the in-memory representation of a FlatData sample is XCDR2 and older versions of Connext DDS use encoding version 1 (XCDR), applications using the FlatData language binding will not communicate with older versions of Connext DDS. See "Choosing the Right Data Representation" in the RTI Connext DDS Core Libraries Getting Started Guide Addendum for Extensible Types for more information.

Using FlatData Language Binding

For examples of FlatData language binding and Zero Copy transfer over shared memory, including example code, see https://community.rti.com/kb/flatdata-and-zerocopy-examples.

Selecting FlatData Language Binding

To select FlatData as the language binding of a type, annotate it with @language_binding(FLAT_DATA). (See 3.3.9.9 The @language_binding Annotation.)

For example, consider a surveillance application in which high-definition (HD) video signal is published and subscribed to. The application publishes a Topic of the type CameraImage. This is the IDL:

enum Format {

RGB,

HSV,

YUV

};

@final

@language_binding(FLAT_DATA)

struct Resolution {

long height;

long width;

};

@final

@language_binding(FLAT_DATA)

struct Pixel {

octet red;

octet green;

octet blue;

};

const long MAX_IMAGE_SIZE = 8294400;

@mutable

@language_binding(FLAT_DATA)

struct CameraImage {

string<128> source;

Format format;

Resolution resolution;

sequence<Pixel, MAX_IMAGE_SIZE> pixels;

};

The language binding annotation supports two values: FLAT_DATA and PLAIN (default). PLAIN refers to the regular in-memory representation, where an IDL struct maps to a C++ class or C struct.

There are some restrictions regarding the kinds of structures, value types, and unions to which the FlatData language binding can be applied.

For final types, the FlatData language binding can be applied only to fixed-size types. A fixed-size type is a type whose wire representation always has the same size. This includes primitive types, arrays of fixed-size types, and structs containing only members of fix-size types. Unions are not fixed-size types.1

The FlatData language binding can be applied to any mutable type. This enables support for variable-size types, containing sequences, strings, or optional members. It also allows using unions.

FlatData cannot be applied to extensible types.

Final types provide the best performance, while mutable types are the most flexible. Typically, the best compromise between flexibility and performance comes from a mutable type whose largest member is either a final type or a sequence of final elements. In the CameraImage example, the top-level type is mutable, which allows for type evolution, optional members, and variable-size members (such as the source string member). On the other hand, its member pixels, which contains the bulk of the data, is defined as a sequence of the final type Pixel, which allows for an efficient manipulation.

22.4.2.2 Programming with FlatData Language Binding

When a type is marked with the FlatData language binding, the in-memory representation for samples of this type is equal to the wire representation (according to XCDR version 22). That is, the data sample is in its serialized format at all times. To facilitate accessing and setting the sample content, RTI Code Generator generates helper types that provide the operations to create and access these data samples. These helper types are Samples, Offsets, and Builders.

A FlatData Sample is a buffer holding the wire representation of the data. In the code generated for the previous IDL, a sample of the type CameraImage contains this buffer. This is the top-level object that can be written or read:

typedef rti::flat::Sample<CameraImageOffset> CameraImage;

(Note: These examples show code for the Modern C++ API. See 22.4.2.3 Languages Supported by FlatData Language Binding.)

To access this sample, applications use Offset types. An Offset represents the type of a member and its location in the buffer. An Offset can be described as an “iterator,” a light-weight object that points to the data, but doesn’t own it. Copying an Offset copies the “iterator,” not the data it points to.

class NDDSUSERDllExport CameraImageConstOffset : public rti::flat::MutableOffset {

public:

const rti::flat::StringOffset source() const;

Format format() const;

Resolution::ConstOffset resolution() const;

rti::flat::SequenceOffset<Pixel::ConstOffset> pixels() const;

};

class NDDSUSERDllExport CameraImageOffset : public rti::flat::MutableOffset {

public:

typedef CameraImageConstOffset ConstOffset;

// Const accessors

const rti::flat::StringOffset source() const;

Format format() const;

Resolution::ConstOffset resolution() const;

rti::flat::SequenceOffset<Pixel::ConstOffset> pixels() const;

// Modifiers

rti::flat::StringOffset source();

bool format(Format value);

Resolution::Offset resolution();

rti::flat::SequenceOffset<Pixel::Offset> pixels();

};

There are two kinds of Offset types:

  • Generated, named Offsets, to access a user-defined struct or union type (CameraImageOffset, PixelOffset). They provide accessors to directly get or set primitive members, and one getter for each non-primitive member to retrieve its corresponding Offset.

Each named offset has a corresponding read-only version (CameraImageConstOffset). This is analogous to a read-only iterator (e.g., std::vector<T>::const_iterator and std::vector<T>::iterator).

  • Offsets to arrays, sequences, strings, and other IDL types. They provide access to their elements. Primitive elements can be accessed directly; non-primitive elements are accessed through Offsets for their types.

For details on all the Offset types and their interface, see the API Reference HTML documentation, under RTI Connext DDS API Reference > Topic Module > FlatData Topic-Types.

The function CameraImage::root() provides the Offset to the top-level type (CameraImageOffset). If the sample is const (for example, in a LoanedSamples container), root() returns a read-only offset (CameraImageConstOffset).

To create variable-size (mutable) data-samples, applications use Builders. A Builder type provides the interface to create a mutable sample member by member. Once all the desired members for a sample have been added, the Builder is “finished,” returning the built sample, which can be published.

class NDDSUSERDllExport CameraImageBuilder : public rti::flat::AggregationBuilder {

public:

typedef CameraImageOffset Offset;

Offset finish();

CameraImage * finish_sample();

rti::flat::StringBuilder build_source();

bool add_format(Format value);

Resolution::Offset add_resolution();

rti::flat::FinalSequenceBuilder<Pixel::Offset> build_pixels();

};

Builders provide three kinds of functions:

  • add_<member> functions insert a member of a final type, returning an Offset to it.

  • build_<member> functions provide another Builder to create a member of a mutable type.

  • finish and finish_sample end the construction of a member or a sample, respectively.

Similarly to Offsets, Builders can correspond to user-defined struct and union types, or other IDL types such as sequences, arrays, and strings. For details on all the Builder types see the API Reference HTML documentation.

The following sections summarize how to use FlatData language binding:

  • Creating a FlatData sample

  • Writing a FlatData sample

  • Reading a FlatData sample

  • Working with unmanaged FlatData samples

  • Multi-threading notes

  • Notes on Extensible Types

Creating a FlatData sample

The following sections assume you have created a DataWriter for the type Pixel or CameraImage, following the usual process.

To write FlatData, first create a FlatData sample. The way to create a sample varies depending on whether the type is final or mutable. In both cases, this section shows how to create DataWriter-managed samples. See also Working with unmanaged FlatData samples.

Creating a FlatData sample for a final type

In this section we will create a sample for the final type Pixel. To create a sample for the mutable type CameraImage, see Creating a FlatData sample for a mutable type after this.

Samples for final FlatData types are created directly with a single call to the DataWriter function get_loan. The DataWriter manages this sample and will return it to a pool at some point after the sample is written.

Pixel *pixel_sample = writer.extensions().get_loan();

pixel_sample contains the buffer that can be written. To set its values, first locate the position of the top-level type:

PixelOffset pixel = pixel_sample->root();

The root() function returns PixelOffset, which points to the position where the data begins. To set the values, use the following setters:

pixel.red(10);

pixel.green(20);

pixel.blue(30);

Creating a FlatData sample for a mutable type

Samples for mutable types are created using Builders. To obtain a CameraImageBuilder to build a CameraImage sample, use the function build_data:

CameraImageBuilder image_builder = rti::flat::build_data(writer);

This function loans the memory necessary to create a CameraImage sample from the DataWriter and provides a CameraImageBuilder to populate it. Use the Builder functions to set the sample’s members (in any order). Non-key members can be omitted, even when they are not optional.3 These Builder functions work on a pre-allocated buffer; they do not allocate any additional memory.

First, we add the member format. As a primitive member, the function add_format directly adds the member and sets its value:

image_builder.add_format(Format::RGB);

Next, we add the member resolution. Its type being final, the function add_resolution adds the member and provides the Offset that allows setting its values:

ResolutionOffset resolution = image_builder.add_resolution();

resolution.height(100);

resolution.width(200);

To build the string member source, the function build_source returns a StringBuilder. We use this builder (in this case it’s as simple as calling set_string), and then call finish. The function finish (not to be confused with finish_sample) completes the construction of the member and renders source_builder invalid.

auto source_builder = image_builder.build_source();

source_builder.set_string(“CAM-1”);

source_builder.finish();

Since this builder is so simple, it is possible to simplify the above code:

image_builder.build_source().set_string("CAM-1");

(The Builder destructor takes care of calling finish.)

To create the pixels member, we build a sequence of Pixels:

auto pixels_builder = image_builder.build_pixels();

There are two ways to populate this member.

Method 1: add and initialize each element:

for (int i = 0; i < 20000; i++) {

PixelOffset pixel = pixels_builder.add_next();

pixel.red(i % 256);

pixel.green((i + 1) % 256);

pixel.blue((i + 2) % 256);

}

pixels_builder.finish();

Builders for sequences with elements of a final type provide the function add_next to add the elements. When the element type is mutable, the sequence (and array) Builder provides the function build_next, which provides a Builder for each element. See more details in the API Reference HTML documentation.

Method 2: cast the elements in the sequence to the equivalent C++ plain type. This method only works for types that meet the conditions required by rti::flat::plain_cast, as described in the API Reference HTML documentation. Basically, the in-memory representation must match the XCDR2 serialized representation. Pixel meets these conditions.

Method 2 is more efficient. First, we use the Builder function add_n to add 20000 elements at once, leaving them uninitialized. Then, after finishing the Builder, we obtain the Offset to the member, cast it, and manipulate the data as a plain C++ type:

pixels_builder.add_n(20000);

auto pixels_offset = pixels_builder.finish();

auto plain_pixels = rti::flat::plain_cast(pixels_offset);

for (int i = 0; i < 20000; i++) {

plain_pixels[i].red(i % 256);

plain_pixels[i].green((i + 1) % 256);

plain_pixels[i].blue((i + 2) % 256);

}

The function rti::flat::plain_cast casts the position in memory that pixels_offset points to into a C-style array of PixelPlainHelper, a type with the same IDL definition as Pixel, but with @language_binding(PLAIN).

Finally, call finish_sample to obtain the complete sample. After this, the Builder instance is invalid and cannot be further used.

CameraImage *image_sample = image_builder.finish_sample();

Once the sample has been created, it is still possible to modify its values, as long as these modifications don’t change the size. For example, it is possible to change the value of an existing pixel, but it’s not possible to add a new one:

auto pixels_offset = image_sample->root().pixels();

pixels_offset.get_element(100).blue(0);

The next section shows how to write the sample.

Writing a FlatData sample

When you write a sample using a regular DataWriter (for a type with a plain language binding), the DataWriter copies the sample in its internal queue, so when write() ends, the application still owns the sample. A DataWriter for a FlatData type, however, doesn’t copy the sample; it keeps a reference. You yield ownership of the data sample from the moment you call write().

writer.write(*image_sample);

The DataWriter will decide when to return samples created with get_loan or build_data to a pool, where the sample will be reused.

To write a new sample, don’t use image_sample again, but obtain a new one with get_loan or build a new one with build_data.

If the sample cannot be written, to return it to the DataWriter pool call:

writer.extensions().discard_loan(*image_sample);

Or, if the sample has not been completely built yet, discard the Builder:

rti::flat::discard_builder(writer, image_builder);

Reading a FlatData sample

The method for reading data for a FlatData type is the same regardless of whether the type is final or mutable.

Create a DataReader as you normally would; see 7.3.1 Creating DataReaders.

Read the data samples:

dds::sub::LoanedSamples<CameraImage> samples = camera_reader.take();

Let’s work with the first sample (assuming samples.length() > 0 and samples[0].info().valid()):

const CameraImage& image_sample = samples[0].data();

Using the root Offset and the Offset to the members, the following code prints the sample values. Note that in this example, image_sample is const, so camera_image is a CameraImageConstOffset, which only allows reading the buffer, not modifying it.

auto camera_image = image_sample->root();

std::cout << "Source: " << camera_image.source().get_string() << std::endl;

std::cout << "Timestamp: " << camera_image.timestamp() << std::endl;

std::cout << "Format: " << camera_image.format() << std::endl;

auto resolution = camera_image.resolution();

std::cout << "Resolution (height: " << resolution.height()

<< ", width: " << resolution.width() << ")\n";

To access the sequence of pixels, the same two methods that allowed building it (element by element or plain cast) are available:

Method 1 (access each element offset):

for (auto pixel : camera_image.pixels()) {

std::cout << "Pixel (" << pixel.red() << ", " << pixel.green()

<< ", " << pixel.blue() << ")\n";

}

Method 2 (plain_cast):

auto pixel_count = camera_image.pixels().element_count();

auto plain_pixels = rti::flat::plain_cast(camera_image.pixels());

for (int i = 0; i < pixel_count; i++) {

const auto& pixel = plain_pixels[i];

std::cout << "Pixel (" << pixel.red() << ", " << pixel.green()

<< ", " << pixel.blue() << ")\n";

}

Method 2 is more efficient, provided that the type meets the requirements of plain_cast. Also, the endianness of the publishing application must be the same as the local endianness.

Note that you can directly print the sample:

std::cout << *image_sample << std::endl;

Working with unmanaged FlatData samples

The previous sections describe how to create and write DataWriter-managed samples (via get_loan or build_data). While this is the recommended and easiest way, sometimes applications may need to use unmanaged samples. For example, they may need to reuse the same sample after it is written or to obtain the memory from some other source.

Note that a given DataWriter cannot write both unmanaged and managed samples. The functions get_loan or build_data will fail if an unmanaged sample has been written. Conversely, the DataWriter will fail to write an unmanaged sample if get_loan or build_data have been called.

To create a CameraImage using memory from an arbitrary buffer, my_buffer, with a capacity of my_buffer_size bytes, use the following constructor:

unsigned char *my_buffer = ...;

unsigned int my_buffer_size = ...;

CameraImageBuilder image_builder(my_buffer, my_buffer_size);

// use image_builder...

CameraImage *image_sample = image_builder.finish_sample();

image_builder will fail if it runs out of space. The maximum size of a CameraImage can be obtained from its dynamic type:

unsigned int max_size =

rti::topic::dynamic_type<CameraImage>::get().cdr_serialized_sample_max_size();

After writing image_sample, the DataWriter takes ownership of it. In order to reuse the sample, the application needs to monitor the on_sample_removed callback in the DataWriter listener, and correlate the cookie it receives with the sample. The following is a simple DataWriterListener implementation that does that:

class FlatDataWriterListener

: public dds::pub::NoOpDataWriterListener<CameraImage> {

public:

void on_sample_removed(

dds::pub::DataWriter<CameraImage>& writer,

const rti::core::Cookie& cookie) override

{

// The cookie identifies the sample being removed

last_removed_sample = cookie.to_pointer<CameraImage>();

}

CameraImage *last_removed_sample = NULL;

};

The application will need to wait until last_removed_sample is equal to image_sample. This indicates that the DataWriter no longer needs to hold ownership of image_sample.

Another way to create an unmanaged sample is CameraImage::create_data() or Pixel::create_data() (the result of CameraImage::create_data() must be passed to the CameraImageBuilder constructor mentioned before). Samples can be copied with the clone() function. These samples need to be released with the respective delete_data() functions. See the API Reference HTML documentation for more information.

Multi-threading notes

  • It’s not safe to use the same Offset object in parallel, even for reading. For efficiency, each offset object contains an internal state that may change when accessing a member.

void my_thread1(CameraImageOffset& camera_image)

{

auto format = camera_image.format();

}

void my_thread2(CameraImageOffset& camera_image)

{

auto resolution = camera_image.resolution();

}

// Unsafe:

auto camera_image = camera_image_sample.root();

std::async(my_thread1, camera_image);

std::async(my_thread2, camera_image);

  • It is safe to use different Offset objects to read the same member in a sample.

// Safe

auto camera_image1 = camera_image_sample.root();

auto camera_image2 = camera_image_sample.root();

std::async(my_thread1, camera_image1);

std::async(my_thread2, camera_image2);

  • It is not safe to build a sample using a Builder in parallel.

Notes on Extensible Types

There are a few differences in how a plain and a FlatData DataReader behave when they receive samples of types that are different but compatible.

Before a DataReader and DataWriter can communicate, their types are inspected to determine if they are compatible. The same is true when using FlatData; however, even after two types have been deemed compatible, there may be specific data samples that are not.

DataReaders for plain types verify sample compatibility during data deserialization, but DataReaders for FlatData types don’t deserialize the data, passing FlatData samples directly to the application. For that reason, there may be situations where a plain DataReader would drop a data-sample, while a DataReader for a FlatData type with the same definition will pass the same sample to the application. Therefore, if you are using FlatData you may need to explicitly check if all the received samples are consistent with your application logic. For more information on the rules that determine the assignability of a sample, see the RTI Connext DDS Core Libraries Getting Started Guide Addendum for Extensible Types (see the section “Verifying Sample Consistency: Sample Assignability”) or the "Extensible and Dynamic Topic Types for DDS" (DDS-XTypes) specification.

For example, a FlatData DataReader won’t drop a sample when a sequence (or a string) member exceeds the bounds in the reader’s type definition, and the application will be able to read this sequence or string. (This can only happen if ignore_sequence_bounds or ignore_string_bounds in TypeConsistencyEnforcement has been set to true; otherwise the DataWriter’s type wouldn’t have matched the DataReader’s.) The @min and @max annotations are another example. FlatData DataReaders will not enforce the @min/@max range set for a member, and applications will be able to access such samples.

Another difference in behavior involves the reception of samples that don’t include some data members. When a regular DataReader for a mutable (plain) type receives a data sample that doesn’t include one of its non-optional members, it automatically assigns a default value during the data deserialization. A FlatData DataReader for a mutable (FlatData) type will not do that. Instead, if the application tries to access that member, the corresponding member getter will return a null Offset. Only if the member is primitive will it return a default value. This means that, for a FlatData DataReader in this case, all non-primitive members will be treated as if they were optional.

22.4.2.3 Languages Supported by FlatData Language Binding

The FlatData language binding is supported in the Modern and Traditional C++ APIs:

  • rtiddsgen -language C++11 or rtiddsgen -language C++03 generates code for the Modern C++ API.

  • rtiddsgen -language C++ generates code for the Traditional C++ API.

The FlatData language binding is basically the same in both APIs, as described in the previous sections, with a few differences:

  • Modern C++ may throw exceptions in Sample, Offset, and Builder operations, such as dds::core::PreconditionNotMetError; Traditional C++ doesn’t throw exceptions and in these cases it would return invalid objects. See the API Reference HTML documentation for each language for details.

  • Modern C++ maps integer types to int32_t, uint16_t, etc; Traditional C++ uses DDS_Long, DDS_UnsignedShort, etc. This is consistent with these languages’ respective plain language bindings.

  • Modern C++ provides an overloaded operator<< to print a sample; Traditional C++ uses FooTypeSupport::print_data. Both provide a function to transform to a string with format options. This behavior is also consistent with the plain binding.

Using FlatData Language Binding

For examples of FlatData language binding and Zero Copy transfer over shared memory, including example code, see https://community.rti.com/kb/flatdata-and-zerocopy-examples.

Selecting FlatData Language Binding

To select FlatData as the language binding of a type, annotate it with @language_binding(FLAT_DATA). (See 3.3.9.9 The @language_binding Annotation.)

For example, consider a surveillance application in which high-definition (HD) video signal is published and subscribed to. The application publishes a Topic of the type CameraImage. This is the IDL:

enum Format {

RGB,

HSV,

YUV

};

@final

@language_binding(FLAT_DATA)

struct Resolution {

long height;

long width;

};

@final

@language_binding(FLAT_DATA)

struct Pixel {

octet red;

octet green;

octet blue;

};

const long MAX_IMAGE_SIZE = 8294400;

@mutable

@language_binding(FLAT_DATA)

struct CameraImage {

string<128> source;

Format format;

Resolution resolution;

sequence<Pixel, MAX_IMAGE_SIZE> pixels;

};

The language binding annotation supports two values: FLAT_DATA and PLAIN (default). PLAIN refers to the regular in-memory representation, where an IDL struct maps to a C++ class or C struct.

There are some restrictions regarding the kinds of structures, value types, and unions to which the FlatData language binding can be applied.

For final types, the FlatData language binding can be applied only to fixed-size types. A fixed-size type is a type whose wire representation always has the same size. This includes primitive types, arrays of fixed-size types, and structs containing only members of fix-size types. Unions are not fixed-size types.1

The FlatData language binding can be applied to any mutable type. This enables support for variable-size types, containing sequences, strings, or optional members. It also allows using unions.

FlatData cannot be applied to extensible types.

Final types provide the best performance, while mutable types are the most flexible. Typically, the best compromise between flexibility and performance comes from a mutable type whose largest member is either a final type or a sequence of final elements. In the CameraImage example, the top-level type is mutable, which allows for type evolution, optional members, and variable-size members (such as the source string member). On the other hand, its member pixels, which contains the bulk of the data, is defined as a sequence of the final type Pixel, which allows for an efficient manipulation.

22.4.2.2 Programming with FlatData Language Binding

When a type is marked with the FlatData language binding, the in-memory representation for samples of this type is equal to the wire representation (according to XCDR version 22). That is, the data sample is in its serialized format at all times. To facilitate accessing and setting the sample content, RTI Code Generator generates helper types that provide the operations to create and access these data samples. These helper types are Samples, Offsets, and Builders.

A FlatData Sample is a buffer holding the wire representation of the data. In the code generated for the previous IDL, a sample of the type CameraImage contains this buffer. This is the top-level object that can be written or read:

typedef rti::flat::Sample<CameraImageOffset> CameraImage;

(Note: These examples show code for the Modern C++ API. See 22.4.2.3 Languages Supported by FlatData Language Binding.)

To access this sample, applications use Offset types. An Offset represents the type of a member and its location in the buffer. An Offset can be described as an “iterator,” a light-weight object that points to the data, but doesn’t own it. Copying an Offset copies the “iterator,” not the data it points to.

class NDDSUSERDllExport CameraImageConstOffset : public rti::flat::MutableOffset {

public:

const rti::flat::StringOffset source() const;

Format format() const;

Resolution::ConstOffset resolution() const;

rti::flat::SequenceOffset<Pixel::ConstOffset> pixels() const;

};

class NDDSUSERDllExport CameraImageOffset : public rti::flat::MutableOffset {

public:

typedef CameraImageConstOffset ConstOffset;

// Const accessors

const rti::flat::StringOffset source() const;

Format format() const;

Resolution::ConstOffset resolution() const;

rti::flat::SequenceOffset<Pixel::ConstOffset> pixels() const;

// Modifiers

rti::flat::StringOffset source();

bool format(Format value);

Resolution::Offset resolution();

rti::flat::SequenceOffset<Pixel::Offset> pixels();

};

There are two kinds of Offset types:

  • Generated, named Offsets, to access a user-defined struct or union type (CameraImageOffset, PixelOffset). They provide accessors to directly get or set primitive members, and one getter for each non-primitive member to retrieve its corresponding Offset.

Each named offset has a corresponding read-only version (CameraImageConstOffset). This is analogous to a read-only iterator (e.g., std::vector<T>::const_iterator and std::vector<T>::iterator).

  • Offsets to arrays, sequences, strings, and other IDL types. They provide access to their elements. Primitive elements can be accessed directly; non-primitive elements are accessed through Offsets for their types.

For details on all the Offset types and their interface, see the API Reference HTML documentation, under RTI Connext DDS API Reference > Topic Module > FlatData Topic-Types.

The function CameraImage::root() provides the Offset to the top-level type (CameraImageOffset). If the sample is const (for example, in a LoanedSamples container), root() returns a read-only offset (CameraImageConstOffset).

To create variable-size (mutable) data-samples, applications use Builders. A Builder type provides the interface to create a mutable sample member by member. Once all the desired members for a sample have been added, the Builder is “finished,” returning the built sample, which can be published.

class NDDSUSERDllExport CameraImageBuilder : public rti::flat::AggregationBuilder {

public:

typedef CameraImageOffset Offset;

Offset finish();

CameraImage * finish_sample();

rti::flat::StringBuilder build_source();

bool add_format(Format value);

Resolution::Offset add_resolution();

rti::flat::FinalSequenceBuilder<Pixel::Offset> build_pixels();

};

Builders provide three kinds of functions:

  • add_<member> functions insert a member of a final type, returning an Offset to it.

  • build_<member> functions provide another Builder to create a member of a mutable type.

  • finish and finish_sample end the construction of a member or a sample, respectively.

Similarly to Offsets, Builders can correspond to user-defined struct and union types, or other IDL types such as sequences, arrays, and strings. For details on all the Builder types see the API Reference HTML documentation.

The following sections summarize how to use FlatData language binding:

  • Creating a FlatData sample

  • Writing a FlatData sample

  • Reading a FlatData sample

  • Working with unmanaged FlatData samples

  • Multi-threading notes

  • Notes on Extensible Types

Creating a FlatData sample

The following sections assume you have created a DataWriter for the type Pixel or CameraImage, following the usual process.

To write FlatData, first create a FlatData sample. The way to create a sample varies depending on whether the type is final or mutable. In both cases, this section shows how to create DataWriter-managed samples. See also Working with unmanaged FlatData samples.

Creating a FlatData sample for a final type

In this section we will create a sample for the final type Pixel. To create a sample for the mutable type CameraImage, see Creating a FlatData sample for a mutable type after this.

Samples for final FlatData types are created directly with a single call to the DataWriter function get_loan. The DataWriter manages this sample and will return it to a pool at some point after the sample is written.

Pixel *pixel_sample = writer.extensions().get_loan();

pixel_sample contains the buffer that can be written. To set its values, first locate the position of the top-level type:

PixelOffset pixel = pixel_sample->root();

The root() function returns PixelOffset, which points to the position where the data begins. To set the values, use the following setters:

pixel.red(10);

pixel.green(20);

pixel.blue(30);

Creating a FlatData sample for a mutable type

Samples for mutable types are created using Builders. To obtain a CameraImageBuilder to build a CameraImage sample, use the function build_data:

CameraImageBuilder image_builder = rti::flat::build_data(writer);

This function loans the memory necessary to create a CameraImage sample from the DataWriter and provides a CameraImageBuilder to populate it. Use the Builder functions to set the sample’s members (in any order). Non-key members can be omitted, even when they are not optional.3 These Builder functions work on a pre-allocated buffer; they do not allocate any additional memory.

First, we add the member format. As a primitive member, the function add_format directly adds the member and sets its value:

image_builder.add_format(Format::RGB);

Next, we add the member resolution. Its type being final, the function add_resolution adds the member and provides the Offset that allows setting its values:

ResolutionOffset resolution = image_builder.add_resolution();

resolution.height(100);

resolution.width(200);

To build the string member source, the function build_source returns a StringBuilder. We use this builder (in this case it’s as simple as calling set_string), and then call finish. The function finish (not to be confused with finish_sample) completes the construction of the member and renders source_builder invalid.

auto source_builder = image_builder.build_source();

source_builder.set_string(“CAM-1”);

source_builder.finish();

Since this builder is so simple, it is possible to simplify the above code:

image_builder.build_source().set_string("CAM-1");

(The Builder destructor takes care of calling finish.)

To create the pixels member, we build a sequence of Pixels:

auto pixels_builder = image_builder.build_pixels();

There are two ways to populate this member.

Method 1: add and initialize each element:

for (int i = 0; i < 20000; i++) {

PixelOffset pixel = pixels_builder.add_next();

pixel.red(i % 256);

pixel.green((i + 1) % 256);

pixel.blue((i + 2) % 256);

}

pixels_builder.finish();

Builders for sequences with elements of a final type provide the function add_next to add the elements. When the element type is mutable, the sequence (and array) Builder provides the function build_next, which provides a Builder for each element. See more details in the API Reference HTML documentation.

Method 2: cast the elements in the sequence to the equivalent C++ plain type. This method only works for types that meet the conditions required by rti::flat::plain_cast, as described in the API Reference HTML documentation. Basically, the in-memory representation must match the XCDR2 serialized representation. Pixel meets these conditions.

Method 2 is more efficient. First, we use the Builder function add_n to add 20000 elements at once, leaving them uninitialized. Then, after finishing the Builder, we obtain the Offset to the member, cast it, and manipulate the data as a plain C++ type:

pixels_builder.add_n(20000);

auto pixels_offset = pixels_builder.finish();

auto plain_pixels = rti::flat::plain_cast(pixels_offset);

for (int i = 0; i < 20000; i++) {

plain_pixels[i].red(i % 256);

plain_pixels[i].green((i + 1) % 256);

plain_pixels[i].blue((i + 2) % 256);

}

The function rti::flat::plain_cast casts the position in memory that pixels_offset points to into a C-style array of PixelPlainHelper, a type with the same IDL definition as Pixel, but with @language_binding(PLAIN).

Finally, call finish_sample to obtain the complete sample. After this, the Builder instance is invalid and cannot be further used.

CameraImage *image_sample = image_builder.finish_sample();

Once the sample has been created, it is still possible to modify its values, as long as these modifications don’t change the size. For example, it is possible to change the value of an existing pixel, but it’s not possible to add a new one:

auto pixels_offset = image_sample->root().pixels();

pixels_offset.get_element(100).blue(0);

The next section shows how to write the sample.

Writing a FlatData sample

When you write a sample using a regular DataWriter (for a type with a plain language binding), the DataWriter copies the sample in its internal queue, so when write() ends, the application still owns the sample. A DataWriter for a FlatData type, however, doesn’t copy the sample; it keeps a reference. You yield ownership of the data sample from the moment you call write().

writer.write(*image_sample);

The DataWriter will decide when to return samples created with get_loan or build_data to a pool, where the sample will be reused.

To write a new sample, don’t use image_sample again, but obtain a new one with get_loan or build a new one with build_data.

If the sample cannot be written, to return it to the DataWriter pool call:

writer.extensions().discard_loan(*image_sample);

Or, if the sample has not been completely built yet, discard the Builder:

rti::flat::discard_builder(writer, image_builder);

Reading a FlatData sample

The method for reading data for a FlatData type is the same regardless of whether the type is final or mutable.

Create a DataReader as you normally would; see 7.3.1 Creating DataReaders.

Read the data samples:

dds::sub::LoanedSamples<CameraImage> samples = camera_reader.take();

Let’s work with the first sample (assuming samples.length() > 0 and samples[0].info().valid()):

const CameraImage& image_sample = samples[0].data();

Using the root Offset and the Offset to the members, the following code prints the sample values. Note that in this example, image_sample is const, so camera_image is a CameraImageConstOffset, which only allows reading the buffer, not modifying it.

auto camera_image = image_sample->root();

std::cout << "Source: " << camera_image.source().get_string() << std::endl;

std::cout << "Timestamp: " << camera_image.timestamp() << std::endl;

std::cout << "Format: " << camera_image.format() << std::endl;

auto resolution = camera_image.resolution();

std::cout << "Resolution (height: " << resolution.height()

<< ", width: " << resolution.width() << ")\n";

To access the sequence of pixels, the same two methods that allowed building it (element by element or plain cast) are available:

Method 1 (access each element offset):

for (auto pixel : camera_image.pixels()) {

std::cout << "Pixel (" << pixel.red() << ", " << pixel.green()

<< ", " << pixel.blue() << ")\n";

}

Method 2 (plain_cast):

auto pixel_count = camera_image.pixels().element_count();

auto plain_pixels = rti::flat::plain_cast(camera_image.pixels());

for (int i = 0; i < pixel_count; i++) {

const auto& pixel = plain_pixels[i];

std::cout << "Pixel (" << pixel.red() << ", " << pixel.green()

<< ", " << pixel.blue() << ")\n";

}

Method 2 is more efficient, provided that the type meets the requirements of plain_cast. Also, the endianness of the publishing application must be the same as the local endianness.

Note that you can directly print the sample:

std::cout << *image_sample << std::endl;

Working with unmanaged FlatData samples

The previous sections describe how to create and write DataWriter-managed samples (via get_loan or build_data). While this is the recommended and easiest way, sometimes applications may need to use unmanaged samples. For example, they may need to reuse the same sample after it is written or to obtain the memory from some other source.

Note that a given DataWriter cannot write both unmanaged and managed samples. The functions get_loan or build_data will fail if an unmanaged sample has been written. Conversely, the DataWriter will fail to write an unmanaged sample if get_loan or build_data have been called.

To create a CameraImage using memory from an arbitrary buffer, my_buffer, with a capacity of my_buffer_size bytes, use the following constructor:

unsigned char *my_buffer = ...;

unsigned int my_buffer_size = ...;

CameraImageBuilder image_builder(my_buffer, my_buffer_size);

// use image_builder...

CameraImage *image_sample = image_builder.finish_sample();

image_builder will fail if it runs out of space. The maximum size of a CameraImage can be obtained from its dynamic type:

unsigned int max_size =

rti::topic::dynamic_type<CameraImage>::get().cdr_serialized_sample_max_size();

After writing image_sample, the DataWriter takes ownership of it. In order to reuse the sample, the application needs to monitor the on_sample_removed callback in the DataWriter listener, and correlate the cookie it receives with the sample. The following is a simple DataWriterListener implementation that does that:

class FlatDataWriterListener

: public dds::pub::NoOpDataWriterListener<CameraImage> {

public:

void on_sample_removed(

dds::pub::DataWriter<CameraImage>& writer,

const rti::core::Cookie& cookie) override

{

// The cookie identifies the sample being removed

last_removed_sample = cookie.to_pointer<CameraImage>();

}

CameraImage *last_removed_sample = NULL;

};

The application will need to wait until last_removed_sample is equal to image_sample. This indicates that the DataWriter no longer needs to hold ownership of image_sample.

Another way to create an unmanaged sample is CameraImage::create_data() or Pixel::create_data() (the result of CameraImage::create_data() must be passed to the CameraImageBuilder constructor mentioned before). Samples can be copied with the clone() function. These samples need to be released with the respective delete_data() functions. See the API Reference HTML documentation for more information.

Multi-threading notes

  • It’s not safe to use the same Offset object in parallel, even for reading. For efficiency, each offset object contains an internal state that may change when accessing a member.

void my_thread1(CameraImageOffset& camera_image)

{

auto format = camera_image.format();

}

void my_thread2(CameraImageOffset& camera_image)

{

auto resolution = camera_image.resolution();

}

// Unsafe:

auto camera_image = camera_image_sample.root();

std::async(my_thread1, camera_image);

std::async(my_thread2, camera_image);

  • It is safe to use different Offset objects to read the same member in a sample.

// Safe

auto camera_image1 = camera_image_sample.root();

auto camera_image2 = camera_image_sample.root();

std::async(my_thread1, camera_image1);

std::async(my_thread2, camera_image2);

  • It is not safe to build a sample using a Builder in parallel.

Notes on Extensible Types

There are a few differences in how a plain and a FlatData DataReader behave when they receive samples of types that are different but compatible.

Before a DataReader and DataWriter can communicate, their types are inspected to determine if they are compatible. The same is true when using FlatData; however, even after two types have been deemed compatible, there may be specific data samples that are not.

DataReaders for plain types verify sample compatibility during data deserialization, but DataReaders for FlatData types don’t deserialize the data, passing FlatData samples directly to the application. For that reason, there may be situations where a plain DataReader would drop a data-sample, while a DataReader for a FlatData type with the same definition will pass the same sample to the application. Therefore, if you are using FlatData you may need to explicitly check if all the received samples are consistent with your application logic. For more information on the rules that determine the assignability of a sample, see the RTI Connext DDS Core Libraries Getting Started Guide Addendum for Extensible Types (see the section “Verifying Sample Consistency: Sample Assignability”) or the "Extensible and Dynamic Topic Types for DDS" (DDS-XTypes) specification.

For example, a FlatData DataReader won’t drop a sample when a sequence (or a string) member exceeds the bounds in the reader’s type definition, and the application will be able to read this sequence or string. (This can only happen if ignore_sequence_bounds or ignore_string_bounds in TypeConsistencyEnforcement has been set to true; otherwise the DataWriter’s type wouldn’t have matched the DataReader’s.) The @min and @max annotations are another example. FlatData DataReaders will not enforce the @min/@max range set for a member, and applications will be able to access such samples.

Another difference in behavior involves the reception of samples that don’t include some data members. When a regular DataReader for a mutable (plain) type receives a data sample that doesn’t include one of its non-optional members, it automatically assigns a default value during the data deserialization. A FlatData DataReader for a mutable (FlatData) type will not do that. Instead, if the application tries to access that member, the corresponding member getter will return a null Offset. Only if the member is primitive will it return a default value. This means that, for a FlatData DataReader in this case, all non-primitive members will be treated as if they were optional.

22.4.2.3 Languages Supported by FlatData Language Binding

The FlatData language binding is supported in the Modern and Traditional C++ APIs:

  • rtiddsgen -language C++11 or rtiddsgen -language C++03 generates code for the Modern C++ API.

  • rtiddsgen -language C++ generates code for the Traditional C++ API.

The FlatData language binding is basically the same in both APIs, as described in the previous sections, with a few differences:

  • Modern C++ may throw exceptions in Sample, Offset, and Builder operations, such as dds::core::PreconditionNotMetError; Traditional C++ doesn’t throw exceptions and in these cases it would return invalid objects. See the API Reference HTML documentation for each language for details.

  • Modern C++ maps integer types to int32_t, uint16_t, etc; Traditional C++ uses DDS_Long, DDS_UnsignedShort, etc. This is consistent with these languages’ respective plain language bindings.

  • Modern C++ provides an overloaded operator<< to print a sample; Traditional C++ uses FooTypeSupport::print_data. Both provide a function to transform to a string with format options. This behavior is also consistent with the plain binding

Programming with FlatData Language Binding

When a type is marked with the FlatData language binding, the in-memory representation for samples of this type is equal to the wire representation (according to XCDR version 22). That is, the data sample is in its serialized format at all times. To facilitate accessing and setting the sample content, RTI Code Generator generates helper types that provide the operations to create and access these data samples. These helper types are Samples, Offsets, and Builders.

A FlatData Sample is a buffer holding the wire representation of the data. In the code generated for the previous IDL, a sample of the type CameraImage contains this buffer. This is the top-level object that can be written or read:

typedef rti::flat::Sample<CameraImageOffset> CameraImage;

(Note: These examples show code for the Modern C++ API. See 22.4.2.3 Languages Supported by FlatData Language Binding.)

To access this sample, applications use Offset types. An Offset represents the type of a member and its location in the buffer. An Offset can be described as an “iterator,” a light-weight object that points to the data, but doesn’t own it. Copying an Offset copies the “iterator,” not the data it points to.

class NDDSUSERDllExport CameraImageConstOffset : public rti::flat::MutableOffset {

public:

const rti::flat::StringOffset source() const;

Format format() const;

Resolution::ConstOffset resolution() const;

rti::flat::SequenceOffset<Pixel::ConstOffset> pixels() const;

};

class NDDSUSERDllExport CameraImageOffset : public rti::flat::MutableOffset {

public:

typedef CameraImageConstOffset ConstOffset;

// Const accessors

const rti::flat::StringOffset source() const;

Format format() const;

Resolution::ConstOffset resolution() const;

rti::flat::SequenceOffset<Pixel::ConstOffset> pixels() const;

// Modifiers

rti::flat::StringOffset source();

bool format(Format value);

Resolution::Offset resolution();

rti::flat::SequenceOffset<Pixel::Offset> pixels();

};

There are two kinds of Offset types:

  • Generated, named Offsets, to access a user-defined struct or union type (CameraImageOffset, PixelOffset). They provide accessors to directly get or set primitive members, and one getter for each non-primitive member to retrieve its corresponding Offset.

Each named offset has a corresponding read-only version (CameraImageConstOffset). This is analogous to a read-only iterator (e.g., std::vector<T>::const_iterator and std::vector<T>::iterator).

  • Offsets to arrays, sequences, strings, and other IDL types. They provide access to their elements. Primitive elements can be accessed directly; non-primitive elements are accessed through Offsets for their types.

For details on all the Offset types and their interface, see the API Reference HTML documentation, under RTI Connext DDS API Reference > Topic Module > FlatData Topic-Types.

The function CameraImage::root() provides the Offset to the top-level type (CameraImageOffset). If the sample is const (for example, in a LoanedSamples container), root() returns a read-only offset (CameraImageConstOffset).

To create variable-size (mutable) data-samples, applications use Builders. A Builder type provides the interface to create a mutable sample member by member. Once all the desired members for a sample have been added, the Builder is “finished,” returning the built sample, which can be published.

class NDDSUSERDllExport CameraImageBuilder : public rti::flat::AggregationBuilder {

public:

typedef CameraImageOffset Offset;

Offset finish();

CameraImage * finish_sample();

rti::flat::StringBuilder build_source();

bool add_format(Format value);

Resolution::Offset add_resolution();

rti::flat::FinalSequenceBuilder<Pixel::Offset> build_pixels();

};

Builders provide three kinds of functions:

  • add_<member> functions insert a member of a final type, returning an Offset to it.

  • build_<member> functions provide another Builder to create a member of a mutable type.

  • finish and finish_sample end the construction of a member or a sample, respectively.

Similarly to Offsets, Builders can correspond to user-defined struct and union types, or other IDL types such as sequences, arrays, and strings. For details on all the Builder types see the API Reference HTML documentation.

The following sections summarize how to use FlatData language binding:

  • Creating a FlatData sample

  • Writing a FlatData sample

  • Reading a FlatData sample

  • Working with unmanaged FlatData samples

  • Multi-threading notes

  • Notes on Extensible Types

Creating a FlatData sample

The following sections assume you have created a DataWriter for the type Pixel or CameraImage, following the usual process.

To write FlatData, first create a FlatData sample. The way to create a sample varies depending on whether the type is final or mutable. In both cases, this section shows how to create DataWriter-managed samples. See also Working with unmanaged FlatData samples.

Creating a FlatData sample for a final type

In this section we will create a sample for the final type Pixel. To create a sample for the mutable type CameraImage, see Creating a FlatData sample for a mutable type after this.

Samples for final FlatData types are created directly with a single call to the DataWriter function get_loan. The DataWriter manages this sample and will return it to a pool at some point after the sample is written.

Pixel *pixel_sample = writer.extensions().get_loan();

pixel_sample contains the buffer that can be written. To set its values, first locate the position of the top-level type:

PixelOffset pixel = pixel_sample->root();

The root() function returns PixelOffset, which points to the position where the data begins. To set the values, use the following setters:

pixel.red(10);

pixel.green(20);

pixel.blue(30);

Creating a FlatData sample for a mutable type

Samples for mutable types are created using Builders. To obtain a CameraImageBuilder to build a CameraImage sample, use the function build_data:

CameraImageBuilder image_builder = rti::flat::build_data(writer);

This function loans the memory necessary to create a CameraImage sample from the DataWriter and provides a CameraImageBuilder to populate it. Use the Builder functions to set the sample’s members (in any order). Non-key members can be omitted, even when they are not optional.3 These Builder functions work on a pre-allocated buffer; they do not allocate any additional memory.

First, we add the member format. As a primitive member, the function add_format directly adds the member and sets its value:

image_builder.add_format(Format::RGB);

Next, we add the member resolution. Its type being final, the function add_resolution adds the member and provides the Offset that allows setting its values:

ResolutionOffset resolution = image_builder.add_resolution();

resolution.height(100);

resolution.width(200);

To build the string member source, the function build_source returns a StringBuilder. We use this builder (in this case it’s as simple as calling set_string), and then call finish. The function finish (not to be confused with finish_sample) completes the construction of the member and renders source_builder invalid.

auto source_builder = image_builder.build_source();

source_builder.set_string(“CAM-1”);

source_builder.finish();

Since this builder is so simple, it is possible to simplify the above code:

image_builder.build_source().set_string("CAM-1");

(The Builder destructor takes care of calling finish.)

To create the pixels member, we build a sequence of Pixels:

auto pixels_builder = image_builder.build_pixels();

There are two ways to populate this member.

Method 1: add and initialize each element:

for (int i = 0; i < 20000; i++) {

PixelOffset pixel = pixels_builder.add_next();

pixel.red(i % 256);

pixel.green((i + 1) % 256);

pixel.blue((i + 2) % 256);

}

pixels_builder.finish();

Builders for sequences with elements of a final type provide the function add_next to add the elements. When the element type is mutable, the sequence (and array) Builder provides the function build_next, which provides a Builder for each element. See more details in the API Reference HTML documentation.

Method 2: cast the elements in the sequence to the equivalent C++ plain type. This method only works for types that meet the conditions required by rti::flat::plain_cast, as described in the API Reference HTML documentation. Basically, the in-memory representation must match the XCDR2 serialized representation. Pixel meets these conditions.

Method 2 is more efficient. First, we use the Builder function add_n to add 20000 elements at once, leaving them uninitialized. Then, after finishing the Builder, we obtain the Offset to the member, cast it, and manipulate the data as a plain C++ type:

pixels_builder.add_n(20000);

auto pixels_offset = pixels_builder.finish();

auto plain_pixels = rti::flat::plain_cast(pixels_offset);

for (int i = 0; i < 20000; i++) {

plain_pixels[i].red(i % 256);

plain_pixels[i].green((i + 1) % 256);

plain_pixels[i].blue((i + 2) % 256);

}

The function rti::flat::plain_cast casts the position in memory that pixels_offset points to into a C-style array of PixelPlainHelper, a type with the same IDL definition as Pixel, but with @language_binding(PLAIN).

Finally, call finish_sample to obtain the complete sample. After this, the Builder instance is invalid and cannot be further used.

CameraImage *image_sample = image_builder.finish_sample();

Once the sample has been created, it is still possible to modify its values, as long as these modifications don’t change the size. For example, it is possible to change the value of an existing pixel, but it’s not possible to add a new one:

auto pixels_offset = image_sample->root().pixels();

pixels_offset.get_element(100).blue(0);

The next section shows how to write the sample.

Writing a FlatData sample

When you write a sample using a regular DataWriter (for a type with a plain language binding), the DataWriter copies the sample in its internal queue, so when write() ends, the application still owns the sample. A DataWriter for a FlatData type, however, doesn’t copy the sample; it keeps a reference. You yield ownership of the data sample from the moment you call write().

writer.write(*image_sample);

The DataWriter will decide when to return samples created with get_loan or build_data to a pool, where the sample will be reused.

To write a new sample, don’t use image_sample again, but obtain a new one with get_loan or build a new one with build_data.

If the sample cannot be written, to return it to the DataWriter pool call:

writer.extensions().discard_loan(*image_sample);

Or, if the sample has not been completely built yet, discard the Builder:

rti::flat::discard_builder(writer, image_builder);

Reading a FlatData sample

The method for reading data for a FlatData type is the same regardless of whether the type is final or mutable.

Create a DataReader as you normally would; see 7.3.1 Creating DataReaders.

Read the data samples:

dds::sub::LoanedSamples<CameraImage> samples = camera_reader.take();

Let’s work with the first sample (assuming samples.length() > 0 and samples[0].info().valid()):

const CameraImage& image_sample = samples[0].data();

Using the root Offset and the Offset to the members, the following code prints the sample values. Note that in this example, image_sample is const, so camera_image is a CameraImageConstOffset, which only allows reading the buffer, not modifying it.

auto camera_image = image_sample->root();

std::cout << "Source: " << camera_image.source().get_string() << std::endl;

std::cout << "Timestamp: " << camera_image.timestamp() << std::endl;

std::cout << "Format: " << camera_image.format() << std::endl;

auto resolution = camera_image.resolution();

std::cout << "Resolution (height: " << resolution.height()

<< ", width: " << resolution.width() << ")\n";

To access the sequence of pixels, the same two methods that allowed building it (element by element or plain cast) are available:

Method 1 (access each element offset):

for (auto pixel : camera_image.pixels()) {

std::cout << "Pixel (" << pixel.red() << ", " << pixel.green()

<< ", " << pixel.blue() << ")\n";

}

Method 2 (plain_cast):

auto pixel_count = camera_image.pixels().element_count();

auto plain_pixels = rti::flat::plain_cast(camera_image.pixels());

for (int i = 0; i < pixel_count; i++) {

const auto& pixel = plain_pixels[i];

std::cout << "Pixel (" << pixel.red() << ", " << pixel.green()

<< ", " << pixel.blue() << ")\n";

}

Method 2 is more efficient, provided that the type meets the requirements of plain_cast. Also, the endianness of the publishing application must be the same as the local endianness.

Note that you can directly print the sample:

std::cout << *image_sample << std::endl;

Working with unmanaged FlatData samples

The previous sections describe how to create and write DataWriter-managed samples (via get_loan or build_data). While this is the recommended and easiest way, sometimes applications may need to use unmanaged samples. For example, they may need to reuse the same sample after it is written or to obtain the memory from some other source.

Note that a given DataWriter cannot write both unmanaged and managed samples. The functions get_loan or build_data will fail if an unmanaged sample has been written. Conversely, the DataWriter will fail to write an unmanaged sample if get_loan or build_data have been called.

To create a CameraImage using memory from an arbitrary buffer, my_buffer, with a capacity of my_buffer_size bytes, use the following constructor:

unsigned char *my_buffer = ...;

unsigned int my_buffer_size = ...;

CameraImageBuilder image_builder(my_buffer, my_buffer_size);

// use image_builder...

CameraImage *image_sample = image_builder.finish_sample();

image_builder will fail if it runs out of space. The maximum size of a CameraImage can be obtained from its dynamic type:

unsigned int max_size =

rti::topic::dynamic_type<CameraImage>::get().cdr_serialized_sample_max_size();

After writing image_sample, the DataWriter takes ownership of it. In order to reuse the sample, the application needs to monitor the on_sample_removed callback in the DataWriter listener, and correlate the cookie it receives with the sample. The following is a simple DataWriterListener implementation that does that:

class FlatDataWriterListener

: public dds::pub::NoOpDataWriterListener<CameraImage> {

public:

void on_sample_removed(

dds::pub::DataWriter<CameraImage>& writer,

const rti::core::Cookie& cookie) override

{

// The cookie identifies the sample being removed

last_removed_sample = cookie.to_pointer<CameraImage>();

}

CameraImage *last_removed_sample = NULL;

};

The application will need to wait until last_removed_sample is equal to image_sample. This indicates that the DataWriter no longer needs to hold ownership of image_sample.

Another way to create an unmanaged sample is CameraImage::create_data() or Pixel::create_data() (the result of CameraImage::create_data() must be passed to the CameraImageBuilder constructor mentioned before). Samples can be copied with the clone() function. These samples need to be released with the respective delete_data() functions. See the API Reference HTML documentation for more information.

Multi-threading notes

  • It’s not safe to use the same Offset object in parallel, even for reading. For efficiency, each offset object contains an internal state that may change when accessing a member.

void my_thread1(CameraImageOffset& camera_image)

{

auto format = camera_image.format();

}

void my_thread2(CameraImageOffset& camera_image)

{

auto resolution = camera_image.resolution();

}

// Unsafe:

auto camera_image = camera_image_sample.root();

std::async(my_thread1, camera_image);

std::async(my_thread2, camera_image);

  • It is safe to use different Offset objects to read the same member in a sample.

// Safe

auto camera_image1 = camera_image_sample.root();

auto camera_image2 = camera_image_sample.root();

std::async(my_thread1, camera_image1);

std::async(my_thread2, camera_image2);

  • It is not safe to build a sample using a Builder in parallel.

Notes on Extensible Types

There are a few differences in how a plain and a FlatData DataReader behave when they receive samples of types that are different but compatible.

Before a DataReader and DataWriter can communicate, their types are inspected to determine if they are compatible. The same is true when using FlatData; however, even after two types have been deemed compatible, there may be specific data samples that are not.

DataReaders for plain types verify sample compatibility during data deserialization, but DataReaders for FlatData types don’t deserialize the data, passing FlatData samples directly to the application. For that reason, there may be situations where a plain DataReader would drop a data-sample, while a DataReader for a FlatData type with the same definition will pass the same sample to the application. Therefore, if you are using FlatData you may need to explicitly check if all the received samples are consistent with your application logic. For more information on the rules that determine the assignability of a sample, see the RTI Connext DDS Core Libraries Getting Started Guide Addendum for Extensible Types (see the section “Verifying Sample Consistency: Sample Assignability”) or the "Extensible and Dynamic Topic Types for DDS" (DDS-XTypes) specification.

For example, a FlatData DataReader won’t drop a sample when a sequence (or a string) member exceeds the bounds in the reader’s type definition, and the application will be able to read this sequence or string. (This can only happen if ignore_sequence_bounds or ignore_string_bounds in TypeConsistencyEnforcement has been set to true; otherwise the DataWriter’s type wouldn’t have matched the DataReader’s.) The @min and @max annotations are another example. FlatData DataReaders will not enforce the @min/@max range set for a member, and applications will be able to access such samples.

Another difference in behavior involves the reception of samples that don’t include some data members. When a regular DataReader for a mutable (plain) type receives a data sample that doesn’t include one of its non-optional members, it automatically assigns a default value during the data deserialization. A FlatData DataReader for a mutable (FlatData) type will not do that. Instead, if the application tries to access that member, the corresponding member getter will return a null Offset. Only if the member is primitive will it return a default value. This means that, for a FlatData DataReader in this case, all non-primitive members will be treated as if they were optional.

22.4.2.3 Languages Supported by FlatData Language Binding

The FlatData language binding is supported in the Modern and Traditional C++ APIs:

  • rtiddsgen -language C++11 or rtiddsgen -language C++03 generates code for the Modern C++ API.

  • rtiddsgen -language C++ generates code for the Traditional C++ API.

The FlatData language binding is basically the same in both APIs, as described in the previous sections, with a few differences:

  • Modern C++ may throw exceptions in Sample, Offset, and Builder operations, such as dds::core::PreconditionNotMetError; Traditional C++ doesn’t throw exceptions and in these cases it would return invalid objects. See the API Reference HTML documentation for each language for details.

  • Modern C++ maps integer types to int32_t, uint16_t, etc; Traditional C++ uses DDS_Long, DDS_UnsignedShort, etc. This is consistent with these languages’ respective plain language bindings.

  • Modern C++ provides an overloaded operator<< to print a sample; Traditional C++ uses FooTypeSupport::print_data. Both provide a function to transform to a string with format options. This behavior is also consistent with the plain binding.

Languages Supported by FlatData Language Binding

The FlatData language binding is supported in the Modern and Traditional C++ APIs:

  • rtiddsgen -language C++11 or rtiddsgen -language C++03 generates code for the Modern C++ API.

  • rtiddsgen -language C++ generates code for the Traditional C++ API.

The FlatData language binding is basically the same in both APIs, as described in the previous sections, with a few differences:

  • Modern C++ may throw exceptions in Sample, Offset, and Builder operations, such as dds::core::PreconditionNotMetError; Traditional C++ doesn’t throw exceptions and in these cases it would return invalid objects. See the API Reference HTML documentation for each language for details.

  • Modern C++ maps integer types to int32_t, uint16_t, etc; Traditional C++ uses DDS_Long, DDS_UnsignedShort, etc. This is consistent with these languages’ respective plain language bindings.

  • Modern C++ provides an overloaded operator<< to print a sample; Traditional C++ uses FooTypeSupport::print_data. Both provide a function to transform to a string with format options. This behavior is also consistent with the plain binding.

Zero Copy Transfer Over Shared Memory

For communication within the same node using the built-in shared memory transport, by default Connext DDS copies a sample four times (see Figure 22.3: Number of Copies Out-of-the-Box). FlatData language binding reduces the number of copies to two (see 22.4 FlatData Language Binding): the copy of the sample into the shared memory segment in the publishing application and the copy to reassemble the sample in the subscribing application. Two copies, however, may still be too many depending on the sample size and system requirements.

Zero Copy transfer over shared memory, provided as a separate library called nddsmetp, allows reducing the number of copies to zero for communications within the same host. The nddsmetp library can be linked with Connext DDS C or C++ libraries.This feature accomplishes zero copies by using the shared memory (SHMEM) built-in transport to send 16-byte references to samples within a SHMEM segment owned by the DataWriter, instead of using the SHMEM built-in transport to send the serialized sample content by making a copy. See Figure 22.5: Zero Copy Transfer Over Shared Memory.

With Zero Copy transfer over shared memory, there is no need for the DataWriter to serialize a sample, and there is no need for the DataReader to deserialize an incoming sample since the sample is accessed directly on the SHMEM segment created by the DataWriter.

Figure 22.5: Zero Copy Transfer Over Shared Memory

This feature offers the following benefits:

  • Number of copies is reduced from four to zero (see SHMEM in Figure 22.3: Number of Copies Out-of-the-Box). Instead of transferring the entire sample by making multiple copies, only the location in shared memory is distributed to DataReaders (see Figure 22.5: Zero Copy Transfer Over Shared Memory).

  • Because of this reduced data copying, memory consumption and CPU load are also reduced.

  • Latency is independent of the size of the sample.

  • Fragmentation is not required when using Zero Copy transfer over shared memory because the DataWriter exchanges SHMEM references (only 16-bytes) with DataReaders and not the full sample.

  • Data can still be sent off-board, simplifying application deployment and configuration. When the data is sent off-board, the middleware is still making the same copies described in Figure 22.3: Number of Copies Out-of-the-Box. To reduce the number of copies for sending off-board, use FlatData language binding in conjunction with Zero Copy transfer over shared memory.

Note: A Zero Copy DataWriter is defined as any DataWriter with the ability to send a sample reference. You can have a DataWriter that does both: sends sample references to Zero Copy DataReaders, and sends serialized samples to non-Zero Copy DataReaders. In this case, the DataWriter is still considered a Zero Copy DataWriter in this documentation.

Using Zero Copy Transfer Over Shared Memory

To use Zero Copy transfer over shared memory, perform the following basic steps:

  • Identify types that require Zero Copy transfer over shared memory and annotate them with @transfer_mode(SHMEM_REF) in the IDL files. (See: 3.3.9.8 The @transfer_mode annotation.)

Note: Zero Copy transfer over shared memory requires the FlatData language binding when the type is variable-size.

  • Use the DataWriter’s get_loan() API to get a loaned sample for writing with Zero Copy. (You would use this API to create the sample rather than creating the sample using the TypeSupport. See the example in the following sections and the API Reference HTML documentation for more information on get_loan().)

  • Link the publisher and subscriber application with the additional Zero Copy library, nddsmetp. (RTI Code Generator (rtiddsgen) generates examples that link nddsmetp for you automatically. If you are using a custom build system, make sure you link with nddsmetp.)

RTI Code Generator generates additional TypePlugin code when a type is annotated with @transfer_mode(SHMEM_REF) in the IDL files. This code allows a DataWriter and a DataReader to communicate using a reference to the sample in shared memory (see Figure 22.5: Zero Copy Transfer Over Shared Memory). In addition to sending a sample reference, the DataWriter can also send the serialized sample to a DataReader that doesn’t support Zero Copy transfer over shared memory.

The following sections contain more information about using Zero Copy transfer over shared memory:

  • 22.5.1.1 Sending data with Zero Copy transfer over shared memory

  • 22.5.1.2 Receiving data with Zero Copy transfer over shared memory

  • 22.5.1.3 Checking data consistency with Zero Copy transfer over shared memory

  • 22.5.1.4 Languages Supported by Zero Copy Transfer Over Shared Memory

For examples of FlatData language binding and Zero Copy transfer over shared memory, including example code, see https://community.rti.com/kb/flatdata-and-zerocopy-examples.

Notes:

  • Batching: A Zero Copy DataWriter (any DataWriter that sends sample references) cannot batch samples. That is, Connext DDS will not let you set up a Zero Copy DataWriter to use batching. The reader side is unchanged. Even a Zero Copy DataReader can receive batched samples from a non-Zero Copy DataWriter.

  • Security: While sending a sample via Zero Copy transfer over shared memory, a reference to the sample is sent and not the serialized sample itself. Therefore, encryption or cryptographic signatures cannot be applied to samples written with Zero Copy transfer over shared memory. If you are using RTI Security Plugins, setting the cryptography attribute ProtectionKind to SIGNED or ENCRYPT will generate a warning message indicating that the reference to the sample will be encrypted and not the serialized sample itself.

22.5.1.1 Sending data with Zero Copy transfer over shared memory

The following example shows how to use Zero Copy transfer mode for a surveillance application in which high-definition (HD) video signal is published and subscribed to. The application publishes a Topic of the type CameraImage. This is the IDL:

enum Format {

RGB,

HSV,

YUV

};

struct Resolution {

long height;

long width;

};

const long IMAGE_SIZE = 8294400 * 3;

@transfer_mode(SHMEM_REF)

struct CameraImage {

long long timestamp;

Format format;

Resolution resolution;

octet data[IMAGE_SIZE];

};

The CameraImage type is annotated with @transfer_mode(SHMEM_REF) to allow Zero Copy communication. Note that it is sufficient to annotate only top-level types with this annotation.

Any final or appendable type annotated with @transfer_mode(SHMEM_REF) should be a fixed-size type. This means the type can include primitive members, arrays of fixed-size types, and structs containing only members of fixed-size types. To use a variable-sized type, the type should be annotated with @language_binding(FLAT_DATA) and @mutable in combination with @transfer_mode(SHMEM_REF).

With Zero Copy transfer mode, an application writes samples coming from a shared memory sample pool created by a Zero Copy DataWriter. Therefore, create a DataWriter before creating a sample. The steps for creating a Zero Copy DataWriter are the same as for a regular DataWriter.

const int MY_DOMAIN_ID = 0;

dds::domain::DomainParticipant participant(MY_DOMAIN_ID);

dds::topic::Topic<CameraImage> camera_topic(participant, "Camera");

dds::pub::DataWriter<CameraImage> camera_writer(

rti::pub::implicit_publisher(participant),

camera_topic);

To get a sample from shared memory, use the DataWriter’s get_loan() API:

CameraImage *camera_image = camera_writer->get_loan();

The sample returned by get_loan() is uninitialized by default (the members are not set to default values). If you would like to allow the DataWriter to return an initialized sample from get_loan(), set initialize_writer_loaned_sample to true in the 6.5.6 DATA_WRITER_RESOURCE_LIMITS QosPolicy (DDS Extension).

Populate the fields of the sample as you would a regular sample:

camera_image->timestamp(12345678);

camera_image->format(Format::HSV);

camera_image->resolution().height(1024);

camera_image->resolution().width(2048);

// populate the image data

The example above, showing the population of the fields, assumes regular PLAIN language binding. Zero Copy transfer over shared memory also works with types using FLAT_DATA language binding. In this case, you must use the FlatData API described in 22.4 FlatData Language Binding to populate the sample.

The number of samples in the shared memory sample pool created by the DataWriter can be configured using the writer_loaned_sample_allocation settings in the 6.5.6 DATA_WRITER_RESOURCE_LIMITS QosPolicy (DDS Extension).

Initially all the samples are in a free state. When you call the DataWriter’s get_loan(), the DataWriter provides a sample from this pool, and its state changes to allocated. The samples are provided using an LRU (Least Recently Used) policy.

Write the sample with the regular write operation:

camera_writer.write(*camera_image);

When a sample is written, its state transitions from allocated to enqueued, and the DataWriter takes responsibility for returning the sample back to the shared memory pool. The sample remains in the enqueued state until it is removed from the DataWriter queue. When this happens, the sample is put back into the shared memory sample pool, and its state transitions from enqueued to removed. At this time, a new call to the DataWriter’s get_loan() may return the same sample.

You should not try to reuse a sample that has been written with a DataWriter to publish a new value. Instead, get a new sample using the DataWriter’s get_loan() and populate its content with the new value.

A sample that has not been written can be returned to the shared memory pool by using the DataWriter’s discard_loan():

camera_writer->discard_loan(camera_image)

The shared memory sample pool is destroyed when the DataWriter is deleted.

22.5.1.2 Receiving data with Zero Copy transfer over shared memory

Create a DataReader as you normally would; see 7.3.1 Creating DataReaders.

Read the data samples:

dds::sub::LoanedSamples<CameraImage> samples = camera_reader.take();

Let’s work with the first sample (assuming samples.length() > 0 and samples[0].info().valid()):

const CameraImage& camera_image_sample = samples[0].data();

// Process the sample

process_data(camera_image_sample);

if (!camera_reader->is_data_consistent(camera_image_sample)) {

// Sample was overwritten, ignore this sample

rollback(camera_image_sample);

}

For more information on the DataReader’s is_data_consistent() API, see 22.5.1.3 Checking data consistency with Zero Copy transfer over shared memory.

22.5.1.3 Checking data consistency with Zero Copy transfer over shared memory

Zero Copy transfer over shared memory makes no copies. This means the sample being processed in the subscribing application actually resides in the DataWriter's send queue. The DataWriter in the publishing application can decide to reuse this memory to send a different sample before or while the original sample is being processed by a DataReader, which can lead to data consistency problems. There are several ways to prevent and detect these inconsistencies.

A reliable DataWriter will not attempt to reuse sample memory if the sample has not been acknowledged. With reliable communication and application-level acknowledgments (see 6.3.12 Application Acknowledgment), the subscribing application can prevent the writer from reusing the sample by delaying the acknowledgment until after the sample has been processed.

Note: Application Acknowledgment is not available with RTI Connext DDS Micro.

Without application-level acknowledgments, when the application's DataWriter and DataReader are not synchronized, the subscribing application can use the DataReader's is_data_consistent() API to detect data inconsistencies. For is_data_consistent() to work, configure the DataWriter’s 6.5.25 TRANSFER_MODE QosPolicy setting writer_qos.transfer_mode.shmem_ref_settings.enable_data_consistency_check to true (the default). A DataWriter with this setting sends a special sequence number associated with each sample as an inline QoS (metadata), which can be used to check the sample's validity at the DataReader with the DataReader’s is_data_consistent() API. Simply, the API checks if the shared memory space has been reused for that sample. If it has, the data is inconsistent.

If data consistency checks are disabled, is_data_consistent() will return a PRECONDITION_NOT_MET error.

The is_data_consistent() API helps detect a data inconsistency, not prevent it. Therefore, the recommended way of using the API is to follow this general scheme:

if (sample_info.valid_data) {

process(data);

if (! reader.is_data_consistent(data, sample_info))

discard(processed_data);

}

When is_data_consistent() returns true after the sample has been processed, subscribers can be sure processed data was not inconsistent and can be trusted (e.g., by committing it to a database). When is_data_consistent() returns false, processed data should be discarded. If is_data_consistent() is only called before processing data, it could return true at that point but the sample could be modified while being processed, leading to a race condition. Therefore, if you want to call is_data_consistent() before processing the data (for instance, because the processing is expensive), that is fine, but be sure to also call it after processing the data.

If the publisher sends data in best-effort mode and the expected send frequency is known in advance, the DataWriter's resource limits can be configured with an appropriate writer_loaned_sample_allocation max count (see the API Reference HTML documentation) to minimize the chances of sample reuse and of is_data_consistent() returning false.

Applications can also use other, custom, application-level mechanisms to guarantee data consistency between the publisher and the subscriber.

22.5.1.4 Languages Supported by Zero Copy Transfer Over Shared Memory

Zero Copy transfer over shared memory is supported in the C, Modern C++, and Traditional C++ APIs.

Other Considerations

22.5.2.1 Type Matching for Zero Copy Transfer Over Shared Memory

The default value for TypeConsistencyEnforcementQosPolicy kind is AUTO_TYPE_COERCION.

For a regular DataReader, AUTO_TYPE_COERCION is translated to ALLOW_TYPE_COERCION. A Zero Copy DataReader, however, should use a topic type that is identical to its matched Zero Copy DataWriter’s topic type, because it accesses the sample directly in the DataWriter queue. Therefore, AUTO_TYPE_COERCION for a Zero Copy DataReader is translated to DISALLOW_TYPE_COERCION. The creation of a Zero Copy DataReader with ALLOW_TYPE_COERCION will return an error.

See 7.6.6 TYPE_CONSISTENCY_ENFORCEMENT QosPolicy.

22.5.2.2 Resource Limits Related to Zero Copy Transfer Over Shared Memory

There are resource limits on the DataWriter, DataReader, and DomainParticipant that configure different aspects of Zero Copy transfer over shared memory.

DataWriter Resource Limits

The writer_loaned_sample_allocation setting configures the initial and maximum number of loaned samples managed by the DataWriter. It also configures the growth policy.

By default this setting is derived from the DDS_ResourceLimitsQosPolicy: the initial and maximum counts are equal to initial_samples + 1 and max_samples + 1. The incremental_count defaults to initial_count if the initial_count is not the same as max_count. If these are the same, then incremental_count defaults to 0.

If you want to extend the time to reuse a sample, use a large sample pool by increasing the initial_count of the writer_loaned_sample_allocation.

See 6.5.6 DATA_WRITER_RESOURCE_LIMITS QosPolicy (DDS Extension).

DataReader Resource Limits

The shmem_ref_transfer_mode_attached_segment_allocation setting configures the initial and maximum shared memory segments to which a DataReader can attach.

By default this setting is derived from other fields in the DDS_DataReaderResourceLimitsQosPolicy: the initial and maximum counts of shared memory segments are equal to initial_remote_writers and max_remote_writers. The incremental_count defaults to -1 (doubling of resources) if the initial_count is not the same as max_count. If these are the same, then incremental_count defaults to 0.

The max_count controls the maximum number of shared memory segments that a DataReader can attach at a time. Once this limit is hit, if there is a need to attach to a new segment, the DataReader will try to detach from a segment that doesn’t contain any loaned samples and attach to the new segment.

If there are samples loaned in all the attached segments, then the new segment will not be attached and this will result in losing the sample.

See 7.6.2 DATA_READER_RESOURCE_LIMITS QosPolicy (DDS Extension).

DomainParticipant Resource Limits

The shmem_ref_transfer_mode_max_segments setting sets the maximum number of shared memory segments that can be created by all DataWriters belonging to the participant. The default value of this setting is 500. The maximum value of this setting will be limited by the operating system setting that controls the system wide maximum number of shared memory segments.

See 8.5.4 DOMAIN_PARTICIPANT_RESOURCE_LIMITS QosPolicy (DDS Extension).

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/143829.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Oracle 19c - 手动升级到 Non-CDB Oracle Database 19c 的完整核对清单 (Doc ID 2577572.1)

Oracle 19c - 手动升级到 Non-CDB Oracle Database 19c 的完整核对清单 (Doc ID 2577572.1)正在上传…重新上传取消To Bottom 文档内容 用途适用范围详细信息关于新的 Autoupgrade utility步骤 1: 升级到数据库 19c 的升级路径能够直接升级到 Oracle 19c 的数据库最小版本以下…

mongodb-18.聚合查询练习1

文章目录bulk writeaddFields增加field嵌套增加field覆盖显示用变量替换向数组中增加元素分组 bucket并行执行多个bucket$bucketAuto$count$document$facet1.使用Aggregation对象实现2.使用Aggregates实现$graphLookup 文档递归查询跨多文档递归$graphLookupbulk write db.piz…

第四十二讲:神州防火墙路由模式的初始配置

防火墙作为局域网的智能网关&#xff0c;处于内网和外网之间&#xff0c;必须工作在路由模式。路由模式下&#xff0c;防火墙上添加默认路由&#xff0c;配置SNAT转换&#xff0c;隐藏私有地址&#xff0c;内部用户正常访问外网。从安全考虑&#xff0c;内网处于trust区域&…

《2022年度ASA广告表现报告》生成,探索买量新高度!

回首 2022 年&#xff0c;ASA 广告的历程可以用“变化莫测”来形容&#xff0c;CPP 取代创意集、更新《广告指南》、上线新广告位等等&#xff0c;而这一系列改变&#xff0c;都在一定程度上影响着 ASA 广告的投放。一起来看看 2022 年度全球 ASA 广告的投放情况吧&#xff01;…

日常使用的WhatsApp如何防止被封?

最近好多做外贸的朋友反映&#xff0c;自己手机号码注册的WhatsApp账号被封了&#xff0c;该如何将解封。首先我们先要了解为什么会被封&#xff1f;被封肯定是因为违反了WhatsApp条款和条件&#xff0c;但是具体如何违反的呢&#xff1f;我们一起来看看你没有这样做过&#xf…

【express】中间件

中间件&#xff08;Middleware&#xff09;&#xff0c;特指业务流程的中间处理环节 1、调用流程 当一个请求到达Express的服务器之后&#xff0c;可以连续调用多个中间件&#xff0c;从而对这次请求进行预处理。 2、格式 Express的中间件&#xff0c;本质上就是一个functio…

repeat语句 及 赋值语句说明---verilog HDL

参考&#xff1a;verilog数字系统设计教程【第四版】夏宇闻 repeat语句用阻塞赋值语句&#xff0c;与用非阻塞语句产生的结果差别非常大&#xff0c;所以将二者放在同一篇文章中。 1、赋值语句 2、repeat 语句介绍   2.1、用法要点   2.2、代码举例    代码1&#xff1a;…

2023年北向L2接口的发展会怎么样?

众所周知北向L2接口的逐笔成交功能可以精确查看每笔成交&#xff0c;跟踪北向资金动向&#xff0c;那么由于北向资金动向是股市行情的晴雨表&#xff0c;因此股民做股票投资是要时刻关注着北向资金流动方向的&#xff0c;那么北向L2接口作为帮助头者提供跟踪资金动向的服务软件…

浅谈撮合引擎

浅谈撮合引擎设计撮合引擎简介撮合引擎的发展币安中小型交易所小型交易所业务交易流程竞价方式交易所常用指令开发简易架构设计撮数据结构设计交易委托账本限价委托单其它委托单关键代码实现1.创建一个ringbuffer2. 设置事件监听4.订单撮合主逻辑撮合分支processMath函数逻辑PS…

uniapp实现iOS支付苹果内购支付踩过的坑以及具体操作步骤

由于我们app会员属于虚拟产品&#xff0c;所以苹果商店要求我们必须选择苹果内购&#xff0c;否则就勒令下架。 无奈&#xff0c;于是就又开始了踩坑之旅~ uniapp可以直接使用uni-pay的插件去进行苹果内购。 但是&#xff0c;在对接自己的项目之前&#xff0c;建议先跑通示例项…

JavaEE-Spring(Spring中的五大类注解,@Bean注解,对象装配(@Autowired,@Resource),Bean对象在Spring中的作用域)

文章目录1. 配置扫描路径2. Spring五大类注解3. Spring Bean注解对象装配4. Bean对象在Spring中的作用域5. Bean生命周期1. 配置扫描路径 只有设置了扫描路径&#xff0c;其他的路径下注解不会被Spring扫描 这里设置路径为com.beans下 <?xml version"1.0" enc…

(七)devops持续集成开发——jenkins流水线发布一个node环境下的前端vue项目

前言 在前面的章节中已经介绍了jenkins集成前端流水化部署环境的内容&#xff0c;本节内容是关于前端项目的流水化部署发布&#xff0c;通过实操发布一个前端项目&#xff0c;从而完成前端项目的流水化发布。前端项目主要是静态资源的发布&#xff0c;这里我们以一个vue项目为…

智慧物流信息化供应链管理体系转型发展现状

现如今&#xff0c;伴随着时代的迅速发展和高新科技水准的持续提升&#xff0c;人们慢慢进入了信息时代。在其中&#xff0c;物流制造行业也从以往20年前的粗放型管理机制慢慢变化为信息化、智慧化的管理机制。 5G、云计算技术、AI、物联网等新技术的出现加快了各个领域经营方法…

k线图中的三条线是什么?

新手投资朋友可能会在行情软件中发现&#xff0c;图表中除了K线以外&#xff0c;其下方还有三条颜色不一样的曲线&#xff0c;到底这三条线有什么功能呢&#xff1f;它们的使用方法又是怎样的呢&#xff1f; 其实&#xff0c;这三条线分别是短、中、长周期移动平均线&#xff0…

界面控件DevExpress WinForm——属于WinForm组件的MVVM框架

DevExpress WinForm拥有180组件和UI库&#xff0c;能为Windows Forms平台创建具有影响力的业务解决方案。DevExpress WinForm能完美构建流畅、美观且易于使用的应用程序&#xff0c;无论是Office风格的界面&#xff0c;还是分析处理大批量的业务数据&#xff0c;它都能轻松胜任…

谷粒学院——Day17【数据同步工具、SpringCloud【GateWay网关】、权限管理功能(接口)】

❤ 作者主页&#xff1a;Java技术一点通的博客 ❀ 个人介绍&#xff1a;大家好&#xff0c;我是Java技术一点通&#xff01;(&#xffe3;▽&#xffe3;)~* &#x1f34a; 记得关注、点赞、收藏、评论⭐️⭐️⭐️ &#x1f4e3; 认真学习&#xff0c;共同进步&#xff01;&am…

sql调优

一、MySQL架构总览&#xff1a; 三、SQL解析顺序 SELECT DISTINCT< select_list > FROM< left_table > < join_type > JOIN < right_table > ON < join_condition > WHERE< where_condition > GROUP BY< group_by_list > HAVING<…

振弦采集模块的频率值与温度值的修正

振弦采集模块的频率值与温度值的修正 此功能在 SF3.51 版本时增加。 固件版本 V3.51 修改固件版本号为 V3.51_2200827。 增加了频率和温度的多项式修正参数和对应指令。 $STFP、 $GTFP、 $STTP、 $GTTP 增加了 FFT 频幅数据输出功能。设置 ATSD_SEL.[5]为 1。 修正了 VM608 采集…

java实现的非关系型数据库:nosqldb

nosqldb一、nosqldb介绍二、nosqldb功能介绍三、数据存储结构介绍1. 数据文件存储结构(data.nosqldb)2.索引文件存储结构(index.mbdb)三、优化点1. 不支持连表查询2. 不支持分片存储3. 碎片整理一、nosqldb介绍 github地址 https://github.com/MaBo2420935619/nosqldb nosqld…

2022年终总结2023年计划

目录 前言&#xff1a; 2022年总结&#xff1a; 工作上&#xff1a; 生活上&#xff1a; 2023年规划&#xff1a; 工作上&#xff1a; 生活上&#xff1a; 前言&#xff1a; 嗨&#xff0c;不知不觉一年又过去了&#xff0c;2022已经结束了&#xff0c;我们迎来了2023。…