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
| State | Meaning |
|---|---|
Alive | Writer is actively publishing |
NotAliveDisposed | Writer called dispose() |
NotAliveNoWriters | All 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
| History | Memory per Instance |
|---|---|
| KeepLast(1) | 1 x sample_size |
| KeepLast(N) | N x sample_size |
| KeepAll | Unbounded (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
- Multi-Topic - Multiple topics in one application
- History QoS - History buffer configuration
- Deadline QoS - Per-instance deadlines