Skip to main content

Key Instance Example

Demonstrates using @key fields to create multi-instance topics.

Overview

In DDS, a key identifies unique instances within a topic. Each instance:

  • Has independent lifecycle (register, write, dispose, unregister)
  • Maintains separate QoS tracking (deadline, liveliness)
  • Can have different history buffers

IDL Definition

Robot.idl
module fleet {
enum RobotState {
IDLE,
MOVING,
CHARGING,
ERROR
};

@topic
struct RobotStatus {
@key string<32> robot_id; // Primary key
@key uint32 zone_id; // Composite key part
RobotState state;
float battery_percent;
float position_x;
float position_y;
uint64 timestamp;
};
};

The combination of robot_id + zone_id forms the instance key.

Publisher

src/robot_publisher.rs
use hdds::prelude::*;
use fleet::{RobotStatus, RobotState};

fn main() -> Result<(), Box<dyn std::error::Error>> {
let participant = DomainParticipant::new(0)?;
let topic = participant.create_topic::<RobotStatus>("RobotStatusTopic")?;

let qos = DataWriterQos::default()
.reliability(Reliability::Reliable {
max_blocking_time: std::time::Duration::from_secs(1),
})
.history(History::KeepLast { depth: 10 });

let publisher = participant.create_publisher()?;
let writer = publisher.create_writer_with_qos(&topic, qos)?;

// Simulate 3 robots in 2 zones
let robots = vec![
("robot_001", 1),
("robot_002", 1),
("robot_003", 2),
];

for tick in 0..100 {
for (robot_id, zone_id) in &robots {
let status = RobotStatus {
robot_id: robot_id.to_string(),
zone_id: *zone_id,
state: RobotState::Moving,
battery_percent: 100.0 - (tick as f32 * 0.5),
position_x: tick as f32 * 0.1,
position_y: tick as f32 * 0.05,
timestamp: std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)?
.as_nanos() as u64,
};

writer.write(&status)?;
}
std::thread::sleep(std::time::Duration::from_millis(100));
}

Ok(())
}

Subscriber with Instance Filtering

src/robot_subscriber.rs
use hdds::prelude::*;
use fleet::RobotStatus;
use std::collections::HashMap;

fn main() -> Result<(), Box<dyn std::error::Error>> {
let participant = DomainParticipant::new(0)?;
let topic = participant.create_topic::<RobotStatus>("RobotStatusTopic")?;

let qos = DataReaderQos::default()
.reliability(Reliability::Reliable)
.history(History::KeepLast { depth: 1 }); // Latest state per robot

let subscriber = participant.create_subscriber()?;
let reader = subscriber.create_reader_with_qos(&topic, qos)?;

// Track latest state per robot
let mut robot_states: HashMap<(String, u32), RobotStatus> = HashMap::new();

loop {
match reader.take() {
Ok(samples) => {
for sample in samples {
let key = (sample.robot_id.clone(), sample.zone_id);

println!(
"[{}:zone{}] {} - battery: {:.1}%, pos: ({:.2}, {:.2})",
sample.robot_id,
sample.zone_id,
format!("{:?}", sample.state),
sample.battery_percent,
sample.position_x,
sample.position_y
);

robot_states.insert(key, sample);
}
}
Err(HddsError::NoData) => {
std::thread::sleep(std::time::Duration::from_millis(10));
}
Err(e) => return Err(e.into()),
}
}
}

Instance Lifecycle

Register Instance

// Pre-register instance for faster first write
let handle = writer.register_instance(&RobotStatus {
robot_id: "robot_001".to_string(),
zone_id: 1,
..Default::default()
})?;

// Write using handle (faster than key lookup)
writer.write_w_handle(&status, handle)?;

Dispose Instance

// Mark instance as "no longer available"
writer.dispose(&RobotStatus {
robot_id: "robot_001".to_string(),
zone_id: 1,
..Default::default()
})?;

Subscribers receive dispose notification:

match reader.take_with_info() {
Ok(samples) => {
for (sample, info) in samples {
if info.instance_state == InstanceState::NotAliveDisposed {
println!("Robot {} left zone {}", sample.robot_id, sample.zone_id);
}
}
}
// ...
}

Unregister Instance

// Writer no longer responsible for instance
writer.unregister_instance(&status)?;

Reading Specific Instances

// Read only data for a specific robot
let key = RobotStatus {
robot_id: "robot_001".to_string(),
zone_id: 1,
..Default::default()
};

let handle = reader.lookup_instance(&key)?;
let samples = reader.take_instance(handle)?;

Instance States

StateMeaning
AliveWriter is actively publishing
NotAliveDisposedWriter called dispose()
NotAliveNoWritersAll writers unregistered or lost liveliness

Per-Instance QoS

Deadline and liveliness are tracked per instance:

let qos = DataReaderQos::default()
.deadline(Deadline::new(Duration::from_millis(500)));

// Each robot instance has independent deadline
// robot_001 can miss deadline while robot_002 is fine

Listener callback includes instance info:

impl DataReaderListener for MyListener {
fn on_requested_deadline_missed(
&mut self,
reader: &DataReader<RobotStatus>,
status: RequestedDeadlineMissedStatus,
) {
// status.last_instance_handle identifies which robot
println!(
"Deadline missed for instance {:?}",
status.last_instance_handle
);
}
}

Key Design Patterns

Single Key

@topic
struct SensorReading {
@key uint32 sensor_id;
float value;
};

Composite Key

@topic
struct FlightData {
@key string<8> airline_code;
@key uint16 flight_number;
@key uint32 date; // YYYYMMDD
// ... data
};

No Key (Singleton)

@topic
struct SystemConfig {
// No @key - single instance per topic
string config_data;
};

Memory Considerations

HistoryMemory per Instance
KeepLast(1)1 x sample_size
KeepLast(N)N x sample_size
KeepAllUnbounded (use ResourceLimits)

Total memory = instances x history_depth x sample_size

let qos = DataReaderQos::default()
.history(History::KeepLast { depth: 10 })
.resource_limits(ResourceLimits {
max_instances: 1000,
max_samples_per_instance: 10,
max_samples: 10000,
});

Next Steps