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}