1#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/examples/shapes/help_main.txt"))]
14#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/examples/shapes/help_pub.txt"))]
23#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/examples/shapes/help_sub.txt"))]
33#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/examples/shapes/Shapes.xml"))]
40#![deny(missing_docs)]
44
45mod publisher;
46mod subscriber;
47
48const PUB_PARTICIPANT_NAME: &str = "ShapeParticipantLibrary::Pub";
49const SUB_PARTICIPANT_NAME: &str = "ShapeParticipantLibrary::Sub";
50const OUTPUT_NAME: &str = "ShapePublisher::ShapeSquareWriter";
51const INPUT_NAME: &str = "ShapeSubscriber::ShapeSquareReader";
52
53use clap::{Parser, Subcommand};
54use serde::{Deserialize, Serialize};
55
56type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
57
58fn validate_samples(s: &str) -> std::result::Result<usize, String> {
59 let value: usize = s
60 .parse()
61 .map_err(|_| format!("`{s}` isn't a valid number"))?;
62 if value == 0 {
63 Err("samples must be greater than 0".to_string())
64 } else {
65 Ok(value)
66 }
67}
68
69#[derive(Debug, Clone, Copy)]
71pub enum TypedMode {
72 Enabled,
74 Disabled,
76}
77
78#[derive(Serialize, Deserialize, Debug, Clone)]
80pub struct ShapeType {
81 pub color: String,
83 pub x: i64,
85 pub y: i64,
87 pub shapesize: i64,
89}
90
91#[derive(Parser)]
93#[command(name = "shapes")]
94#[command(about = "RTI Connector for Rust example for Shape data")]
95struct Args {
96 #[command(subcommand)]
97 command: Commands,
99
100 #[arg(long)]
101 typed: bool,
103}
104
105impl Args {
106 fn typed_mode(&self) -> TypedMode {
107 if self.typed {
108 TypedMode::Enabled
109 } else {
110 TypedMode::Disabled
111 }
112 }
113}
114
115#[derive(Subcommand)]
117enum Commands {
118 Pub {
120 #[arg(short = 's', long, default_value_t = usize::MAX, value_parser = validate_samples)]
121 samples: usize,
123
124 #[arg(short = 'w', long, default_value_t = 200)]
125 wait_ms: u64,
127
128 #[arg(short = 'd', long, default_value_t = 3000)]
129 wait_for_subscriptions_ms: u64,
131 },
132 Sub {
134 #[arg(short = 's', long, default_value_t = usize::MAX, value_parser = validate_samples)]
135 samples: usize,
137
138 #[arg(short = 'w', long, default_value_t = 500)]
139 wait_ms: u64,
141
142 #[arg(short = 'd', long, default_value_t = 3000)]
143 wait_for_publications_ms: u64,
145 },
146}
147
148fn config_path() -> Result<std::path::PathBuf> {
150 use std::{env, fs};
151
152 let contents = include_str!(concat!(
153 env!("CARGO_MANIFEST_DIR"),
154 "/examples/shapes/Shapes.xml"
155 ));
156
157 let temp_dir = env::temp_dir();
160 let temp_path = temp_dir.join("Shapes.xml");
161
162 fs::write(&temp_path, contents)?;
164
165 Ok(temp_path)
166}
167
168fn optional_duration_from_ms(ms: u64) -> Option<std::time::Duration> {
169 if ms == 0 {
170 None
171 } else {
172 Some(std::time::Duration::from_millis(ms))
173 }
174}
175
176fn main() -> Result<()> {
177 run(Args::parse())
178}
179
180fn run(args: Args) -> Result<()> {
181 let typed_mode = args.typed_mode();
182 println!(
183 "Running with typed support: {}",
184 matches!(typed_mode, TypedMode::Enabled)
185 );
186
187 match args.command {
188 Commands::Pub {
189 samples,
190 wait_ms,
191 wait_for_subscriptions_ms,
192 } => publisher::main(typed_mode, samples, wait_ms, wait_for_subscriptions_ms),
193 Commands::Sub {
194 samples,
195 wait_ms,
196 wait_for_publications_ms,
197 } => subscriber::main(typed_mode, samples, wait_ms, wait_for_publications_ms),
198 }
199}
200
201#[cfg(test)]
202mod tests {
203 use super::{Args, Parser, TypedMode, run};
204 use std::thread;
205
206 #[test]
208 fn use_shapes_example() {
209 impl_shapes_example(TypedMode::Disabled);
210 impl_shapes_example(TypedMode::Enabled);
211 }
212
213 fn impl_shapes_example(typed_mode: TypedMode) {
214 let typed_flag = matches!(typed_mode, TypedMode::Enabled);
215
216 let pub_fn = move || {
218 let args = if typed_flag {
219 "pub --samples 10 --typed"
220 } else {
221 "pub --samples 10"
222 };
223 let args = {
224 let program_iter = std::iter::once("shapes");
225 let args_iter = args.split_whitespace();
226 Args::try_parse_from(program_iter.chain(args_iter))
227 }
228 .expect("Failed to parse publisher arguments");
229
230 run(args).expect("Publisher run should succeed");
231 };
232 let sub_fn = move || {
233 let args = if typed_flag {
234 "sub --samples 10 --typed"
235 } else {
236 "sub --samples 10"
237 };
238 let args = {
239 let program_iter = std::iter::once("shapes");
240 let args_iter = args.split_whitespace();
241 Args::try_parse_from(program_iter.chain(args_iter))
242 }
243 .expect("Failed to parse subscriber arguments");
244
245 run(args).expect("Subscriber run should succeed");
246 };
247
248 let pub_thread_handle = thread::spawn(move || pub_fn());
250 let sub_thread_handle = thread::spawn(move || sub_fn());
251
252 pub_thread_handle
254 .join()
255 .expect("Publisher thread has panicked");
256 sub_thread_handle
257 .join()
258 .expect("Subscriber thread has panicked");
259 }
260}