mecha10_core/macros.rs
1//! Mecha10 Framework Macros
2//!
3//! This module provides convenience macros to reduce boilerplate in node and driver implementations.
4
5/// Run a node with automatic config loading and context creation
6///
7/// This macro eliminates boilerplate by automatically handling:
8/// - Config loading with fallback (from env var, file, or default)
9/// - Context creation with node name
10/// - Calling run_node with proper types
11///
12/// # Arguments
13///
14/// * `$node_type` - The NodeImpl type (e.g., CameraFakeNode)
15/// * `$node_name` - The node name string (e.g., "camera_fake")
16/// * `$env_var` - Environment variable for config file path (e.g., "CAMERA_CONFIG")
17/// * `$config_file` - Default config file path (e.g., "config/drivers/realsense_d435.json")
18/// * `$env_prefix` - Environment variable prefix for config overrides (e.g., "CAMERA")
19///
20/// # Example
21///
22/// ```rust
23/// use mecha10::prelude::*;
24///
25/// #[derive(Debug)]
26/// struct CameraFakeNode {
27/// config: CameraConfig,
28/// }
29///
30/// #[derive(Debug, Clone, Deserialize, Serialize, Default)]
31/// struct CameraConfig {
32/// fps: f32,
33/// }
34///
35/// #[async_trait]
36/// impl NodeImpl for CameraFakeNode {
37/// type Config = CameraConfig;
38///
39/// async fn init(config: Self::Config) -> Result<Self> {
40/// Ok(Self { config })
41/// }
42///
43/// async fn run(&mut self, ctx: &Context) -> Result<()> {
44/// // Node logic here
45/// Ok(())
46/// }
47/// }
48///
49/// // Instead of 13 lines:
50/// // pub async fn run() -> Result<()> {
51/// // info!("Starting fake camera driver");
52/// // let config = load_config_with_fallback(
53/// // "CAMERA_CONFIG",
54/// // "config/drivers/realsense_d435.json",
55/// // "CAMERA",
56/// // Some(CameraConfig::default())
57/// // )?;
58/// // let ctx = Context::new("camera_fake");
59/// // run_node::<CameraFakeNode>(config, ctx).await
60/// // }
61///
62/// // Simply use 1 line:
63/// run_node_simple!(
64/// CameraFakeNode,
65/// "camera_fake",
66/// "CAMERA_CONFIG",
67/// "config/drivers/realsense_d435.json",
68/// "CAMERA"
69/// );
70/// ```
71#[macro_export]
72macro_rules! run_node_simple {
73 ($node_type:ty, $node_name:expr, $env_var:expr, $config_file:expr, $env_prefix:expr) => {
74 pub async fn run() -> $crate::error::Result<()> {
75 // Load config with fallback
76 let config = $crate::config::load_config_with_fallback(
77 $env_var,
78 $config_file,
79 $env_prefix,
80 Some(<$node_type as $crate::node::NodeImpl>::Config::default()),
81 )?;
82
83 // Create context
84 let ctx = $crate::context::Context::new($node_name);
85
86 // Run the node with default health reporting
87 $crate::node::run_node::<$node_type>(config, ctx, $crate::node::HealthReportingConfig::default()).await
88 }
89 };
90}
91
92/// Run a node with instance support for multi-instance deployments
93///
94/// This macro extends `run_node_simple!` with support for instance names,
95/// allowing the same node implementation to run multiple instances with
96/// different names and configurations.
97///
98/// # Arguments
99///
100/// * `$node_type` - The NodeImpl type (e.g., CameraFakeNode)
101/// * `$node_name` - The base node name string (e.g., "camera_fake")
102/// * `$instance_env` - Environment variable for instance name (e.g., "CAMERA_INSTANCE")
103/// * `$env_var` - Environment variable for config file path (e.g., "CAMERA_CONFIG")
104/// * `$config_file` - Default config file path (e.g., "config/drivers/camera/default.json")
105/// * `$env_prefix` - Environment variable prefix for config overrides (e.g., "CAMERA")
106///
107/// # Example
108///
109/// ```rust
110/// use mecha10::prelude::*;
111///
112/// #[derive(Debug)]
113/// struct CameraFakeNode {
114/// config: CameraConfig,
115/// }
116///
117/// #[derive(Debug, Clone, Deserialize, Serialize, Default)]
118/// struct CameraConfig {
119/// fps: f32,
120/// }
121///
122/// #[async_trait]
123/// impl NodeImpl for CameraFakeNode {
124/// type Config = CameraConfig;
125///
126/// async fn init(config: Self::Config) -> Result<Self> {
127/// Ok(Self { config })
128/// }
129///
130/// async fn run(&mut self, ctx: &Context) -> Result<()> {
131/// // Node logic here - publishes to instance-scoped topics
132/// Ok(())
133/// }
134/// }
135///
136/// run_node_with_instance!(
137/// CameraFakeNode,
138/// "camera_fake",
139/// "CAMERA_INSTANCE", // Set to "left" or "right" etc.
140/// "CAMERA_CONFIG",
141/// "config/drivers/camera/default.json",
142/// "CAMERA"
143/// );
144/// ```
145///
146/// # Environment Variables
147///
148/// - `$instance_env`: Instance name (e.g., "left", "right", "front_left")
149/// - `$env_var`: Config file path
150/// - `${env_prefix}_*`: Config overrides
151///
152/// # Instance Naming
153///
154/// If `$instance_env` is set to "left":
155/// - Node ID: "camera_fake/left"
156/// - Topics published via `publish_to_scoped()`: "/sensor/camera/rgb/left"
157#[macro_export]
158macro_rules! run_node_with_instance {
159 ($node_type:ty, $node_name:expr, $instance_env:expr, $env_var:expr, $config_file:expr, $env_prefix:expr) => {
160 pub async fn run() -> $crate::error::Result<()> {
161 // Load config with fallback
162 let config = $crate::config::load_config_with_fallback(
163 $env_var,
164 $config_file,
165 $env_prefix,
166 Some(<$node_type as $crate::node::NodeImpl>::Config::default()),
167 )?;
168
169 // Create context
170 let mut ctx = $crate::context::Context::new($node_name).await?;
171
172 // Apply instance name if set
173 if let Ok(instance) = std::env::var($instance_env) {
174 ctx = ctx.with_instance(&instance);
175 $crate::prelude::info!("Running node '{}' with instance '{}'", $node_name, instance);
176 } else {
177 $crate::prelude::info!("Running node '{}' (no instance)", $node_name);
178 }
179
180 // Run the node with default health reporting
181 $crate::node::run_node::<$node_type>(config, ctx, $crate::node::HealthReportingConfig::default()).await
182 }
183 };
184}