3.5.1. Topics, keys and instances

By definition, a Topic is linked to a single data type, so each data sample related to a Topic could be understood as an update on the information described by the data type. However, it is possible to include a logical separation and have, within the same Topic, several instances referring to the same data type. Thus, the received data sample will be an update for a specific instance of that Topic. Therefore, a Topic identifies data of a single type, ranging from one single instance to a whole collection of instances of that given type, as shown in the figure below.

../../../_images/instances.png

The different instances gathered under the same topic are distinguishable by means of one or more data fields that form the key to that data set. The key description has to be indicated to the middleware. The rule is simple: different data values with the same key value represent successive data samples for the same instance, while different data values with different keys represent different topic instances. If no key is provided, the data set associated with the Topic is restricted to a single instance. Please refer to Data types with a key for more information about how to set the key in eProsima Fast DDS.

3.5.1.1. Instance advantages

The advantage of using instances instead of creating a new DataWriter, DataReader, and Topic is that the corresponding entity is already created and discovered. Consequently, there is less memory usage, and no new discovery (with the related metatraffic involved as explained in Discovery) is necessary. Another advantage is that several QoS are applied per topic instance; e.g. the HistoryQosPolicy is kept for each instance in the DataWriter. Thus, instances could be tuned to a wide range of applications.

3.5.1.2. Instance lifecycle

When reading or taking data from the DataReader (as explained in Accessing received data), a SampleInfo is also returned. This SampleInfo provides additional information about the instance lifecycle, specifically with the view_state, instance_state, disposed_generation_count, and no_writers_generation_count. The diagram below shows the statechart of instance_state and view_state for a single instance.

../../../_images/instance-lifecycle.png

3.5.1.3. Practical applications

This section provides a couple of examples to help clarify the use of DDS instances.

3.5.1.3.1. Commercial flights tracking

Airspace and the air traffic going through it are typically managed by the air traffic controllers that are in charge of organizing the air traffic, preventing collisions, and providing information. In this scenario, each air traffic control center takes responsibility for a specific flight area and delivers the data to the airspace traffic management system, which unifies the flight information.

Any time an air traffic control center discovers a plane coming into its controlled flight zone, tracking information about that specific flight is notified to the airspace traffic management center. Such a flow of information could be implemented by means of DDS by creating a specific Topic where the information related to the flight location is published. In that case, the management center would be required to create, if not existing previously, the corresponding Topic and DataReader to have access to the flight information, with the corresponding memory consumption and discovery metatraffic required. On the other hand, a cleverer implementation could leverage topic instances to relay the information from the local air traffic control centers to the airspace traffic management center. The topic instances might be identified using the airline name and the flight number (i.e. IBERIA 1234) as Topic instance key. The sample data being relayed would be the location of each flight being tracked at any given time. The following IDL defines the data described model:

struct FlightPosition
{
    // Unique ID: airline name
    @key string<256> airline_name;

    // Unique ID: flight number
    @key short flight_number;

    // Coordinates
    double latitude;
    double longitude;
    double altitude;
};

Once a new flight is discovered by a control center, the corresponding instance is registered into the system:

// Create data sample
FlightPosition first_flight_position;

// Specify the flight instance
first_flight_position.airline_name("IBERIA");
first_flight_position.flight_number(1234);

// Register instance
eprosima::fastdds::rtps::InstanceHandle_t first_flight_handle =
        data_writer->register_instance(&first_flight_position);

register_instance() returns an InstanceHandle_t which can be used to efficiently call the next operations (i.e. write(), dispose(), or unregister_instance()) over the instance. The returned InstanceHandle_t contains the instance keyhash so it does not have to be recalculated again from the data sample. In case of following this approach, the application must take charge of mapping the instance handles to the corresponding instances.

// Update position value received from the plane
first_flight_position.latitude(39.08);
first_flight_position.longitude(-84.21);
first_flight_position.altitude(1500);

// Write sample to the instance
data_writer->write(&first_flight_position, first_flight_handle);

On the other hand, the user application could directly call the DataWriter instance operations with a NIL instance handle. In this case, the instance handle would be calculated every time an operation is done over the instance, which can be time consuming depending on the specific data type being used.

// New data sample
FlightPosition second_flight_position;

// New instance
second_flight_position.airline_name("RYANAIR");
second_flight_position.flight_number(4321);

// Update plane location
second_flight_position.latitude(40.02);
second_flight_position.longitude(-84.32);
second_flight_position.altitude(5000);

// Write sample directly without registering the instance
data_writer->write(&second_flight_position);

Warning

The correct management of the instance handles in the user application is paramount. Otherwise, a sample corresponding to a different instance could wrongly update the instance which handle the user has passed to the operation (if a non NIL instance is provided, the instance handle is not recalculated, trusting that the one passed by the user is the correct one). The following code updates the first instance of this example with the information coming from the second instance.

data_writer->write(&second_flight_position, first_flight_handle);

Once the plane leaves the controlled area, the air traffic control center may unregister the instance. Unregistering implies that the DataWriter for this specific center has no more information about the unregistered instance, and in this way the matched DataReaders in the management center are notified. The flight is still in the air but out of scope of this particular DataWriter. The instance is alive but no longer tracked by this center.

data_writer->unregister_instance(&first_flight_position, first_flight_handle);
data_writer->unregister_instance(&second_flight_position, HANDLE_NIL);

Finally, when the flight lands, the instance may be disposed. This means, in this specific example, that as far as the DataWriter knows, the instance no longer exists and should be considered not alive. With this operation, the DataWriter conveys this information to the matched DataReaders.

data_writer->dispose(&first_flight_position, first_flight_handle);
data_writer->dispose(&second_flight_position, HANDLE_NIL);

From the management center point of view, the samples are read using the same DataReader subscribed to the Topic where the instances are being published. However, valid_data must be checked to ensure that the sample received contains a data sample. Otherwise, a change of the instance state is being notified. Instance lifecycle contains a diagram showing the instance statechart.

if (RETCODE_OK == data_reader->take_next_sample(&data, &info))
{
    if (info.valid_data)
    {
        // Data sample has been received
    }
    else if (info.instance_state == NOT_ALIVE_DISPOSED_INSTANCE_STATE)
    {
        // A remote DataWriter has disposed the instance
    }
    else if (info.instance_state == NOT_ALIVE_NO_WRITERS_INSTANCE_STATE)
    {
        // None of the matched DataWriters are writing in the instance.
        // The instance can be safely disposed.
    }
}

3.5.1.3.2. Relational databases

Consider now that the air traffic management center wants to keep a database with the flights being tracked. Using DDS instances, maintaining a relational database is almost direct. The instance key (unique identifier of the instance) is analogous to the primary key of the database. Thus, the airspace traffic management center can keep the latest update for each instance in a table like the one below:

Instance handle [PK]

Data

1

Position1

2

Position2

3

Position3

4

Position4

5

Position5

In this case, every time a new sample is received, the corresponding instance entry in the database will be updated with the latest known location. Disposing the instance may translate in erasing the corresponding data from the database. In this scenario, registering and unregistering the instances does not reflect in the database, although if the instance_state and view_state are also persisted, then the instance lifecycle could be tracked as well. A DataWriter communicating that it is going to be publishing data about a specific instance is of no interest to the database until a new data is received and then an insert is directly done with the new discovered instance.

Historical data can also be stored in the relational database, even though depending on the use case, a time series database might be considered to improve efficiency. In the scenario being considered, the sample timestamp could be used, besides the instance handle, as primary key to be able to access the historical tracking data of an specific flight.

Instance handle [PK]

Source Timestamp [PK]

Data

1

1

Position1

2

1

Position2

1

2

Position3

1

3

Position4

2

2

Position5

In this case, looking for a specific instance handle would return the flight tracking information:

Instance handle [Fixed]

Source Timestamp

Data

1

1

Position1

1

2

Position3

1

3

Position4

Whereas looking for a specific timestamp would allow to have a picture of the different flight locations at a specific time:

Instance handle

Source Timestamp [Fixed]

Data

1

2

Position3

2

2

Position5