# `#[derive(Node)]` Macro

The `#[derive(Node)]` macro eliminates boilerplate code when creating Mecha10 nodes by automatically generating the `run()` function.

## Overview

Every Mecha10 node requires a `run()` function that:
1. Loads configuration (from files, environment variables, or defaults)
2. Creates a Context with the node name
3. Optionally handles multi-instance deployment
4. Calls `run_node()` with health reporting configuration

This involves 15-20 lines of repetitive code. The `#[derive(Node)]` macro automates this entirely.

## Before and After

### Before (Manual Boilerplate)

```rust
pub async fn run() -> Result<()> {
    init_logging();
    info!("Starting camera driver");

    let config = load_config_with_fallback(
        "CAMERA_CONFIG",
        "config/drivers/camera.yaml",
        "CAMERA",
        Some(CameraConfig::default())
    )?;

    let mut ctx = Context::new("camera_fake").await?;

    if let Ok(instance) = std::env::var("CAMERA_INSTANCE") {
        ctx = ctx.with_instance(&instance);
        info!("Running node with instance '{}'", instance);
    }

    run_node::<CameraFakeNode>(
        config,
        ctx,
        HealthReportingConfig::default()
    ).await
}
```

### After (Using #[derive(Node)])

```rust
#[derive(Debug, Node)]
#[node(
    name = "camera_fake",
    instance_env = "CAMERA_INSTANCE"
)]
pub struct CameraFakeNode {
    config: CameraConfig,
}
```

That's it! The entire `run()` function is auto-generated.

## Usage

### Simple Node (Single Instance)

```rust
use mecha10::prelude::*;

#[derive(Debug, Node)]
#[node(
    name = "my_sensor",
    config_env = "SENSOR_CONFIG",
    config_path = "config/sensor.yaml",
    env_prefix = "SENSOR"
)]
pub struct MySensorNode {
    config: SensorConfig,
}

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct SensorConfig {
    pub rate_hz: f32,
}

#[async_trait]
impl NodeImpl for MySensorNode {
    type Config = SensorConfig;

    async fn init(config: Self::Config) -> Result<Self> {
        Ok(Self { config })
    }

    async fn run(&mut self, ctx: &Context) -> Result<()> {
        // Your node logic here
        Ok(())
    }
}
```

### Multi-Instance Node

Enable multi-instance support by adding `instance_env`:

```rust
#[derive(Debug, Node)]
#[node(
    name = "camera",
    config_env = "CAMERA_CONFIG",
    config_path = "config/camera.yaml",
    env_prefix = "CAMERA",
    instance_env = "CAMERA_INSTANCE"  // Enable multi-instance
)]
pub struct CameraNode {
    config: CameraConfig,
}
```

Now you can run multiple instances:

```bash
# Run left camera
CAMERA_INSTANCE=left ./camera &

# Run right camera
CAMERA_INSTANCE=right ./camera &

# Topics are automatically scoped:
# - /camera/rgb/left
# - /camera/rgb/right
```

### Minimal Configuration

Only the `name` attribute is required. The macro will generate sensible defaults:

```rust
#[derive(Debug, Node)]
#[node(name = "lidar")]  // Minimal configuration
pub struct LidarNode {
    config: LidarConfig,
}
```

**Generated defaults:**
- `config_env`: `LIDAR_CONFIG` (name + "_CONFIG")
- `config_path`: `config/lidar.yaml` ("config/" + name + ".yaml")
- `env_prefix`: `LIDAR` (name in uppercase)

## Attributes

### Required

- `name` - Node name (string)

### Optional

- `config_env` - Environment variable for config file path override
  - Default: `{NAME}_CONFIG`
  - Example: `CAMERA_CONFIG`

- `config_path` - Default config file path
  - Default: `config/{name}.yaml`
  - Example: `config/camera.yaml`

- `env_prefix` - Prefix for environment variable config overrides
  - Default: `{NAME}` in uppercase
  - Example: `CAMERA_FPS`, `CAMERA_WIDTH`

- `instance_env` - Environment variable for instance name (enables multi-instance)
  - Default: None (single instance)
  - Example: `CAMERA_INSTANCE`

## Configuration Loading

The generated `run()` function uses a **fallback chain** for configuration:

1. **File from environment variable**: `$config_env`
   ```bash
   CAMERA_CONFIG=custom.yaml ./camera
   ```

2. **Default file path**: `config_path`
   ```bash
   ./camera  # Uses config/camera.yaml
   ```

3. **Environment variables**: `${env_prefix}_*`
   ```bash
   CAMERA_FPS=60 CAMERA_WIDTH=1920 ./camera
   ```

4. **Default configuration**: `Config::default()`
   ```bash
   ./camera  # Uses CameraConfig::default()
   ```

## Multi-Instance Support

When `instance_env` is specified, the macro:

1. Checks for the environment variable
2. If set, calls `ctx.with_instance(&instance_name)`
3. Logs which instance is running
4. Topics are automatically scoped

Example:

```bash
CAMERA_INSTANCE=front_left ./camera
```

**Generated behavior:**
```rust
if let Ok(instance) = std::env::var("CAMERA_INSTANCE") {
    ctx = ctx.with_instance(&instance);
    info!("Running node 'camera' with instance 'front_left'");
}
```

**Topic scoping:**
```rust
// Publishing to "/camera/rgb" becomes "/camera/rgb/front_left"
ctx.publish_to_scoped(topics::RGB, &image).await?;
```

## Generated Code

The macro generates a public `run()` function with this signature:

```rust
pub async fn run() -> ::mecha10::Result<()>
```

### Single-Instance Version

```rust
pub async fn run() -> ::mecha10::Result<()> {
    let config = ::mecha10::config::load_config_with_fallback(
        "CAMERA_CONFIG",
        "config/camera.yaml",
        "CAMERA",
        Some(CameraConfig::default()),
    )?;

    let ctx = ::mecha10::context::Context::new("camera").await?;

    ::mecha10::node::run_node::<CameraNode>(
        config,
        ctx,
        ::mecha10::node::HealthReportingConfig::default()
    ).await
}
```

### Multi-Instance Version

```rust
pub async fn run() -> ::mecha10::Result<()> {
    let config = ::mecha10::config::load_config_with_fallback(
        "CAMERA_CONFIG",
        "config/camera.yaml",
        "CAMERA",
        Some(CameraConfig::default()),
    )?;

    let mut ctx = ::mecha10::context::Context::new("camera").await?;

    if let Ok(instance) = std::env::var("CAMERA_INSTANCE") {
        ctx = ctx.with_instance(&instance);
        ::mecha10::prelude::info!(
            "Running node 'camera' with instance '{}'",
            instance
        );
    } else {
        ::mecha10::prelude::info!("Running node 'camera' (no instance)");
    }

    ::mecha10::node::run_node::<CameraNode>(
        config,
        ctx,
        ::mecha10::node::HealthReportingConfig::default()
    ).await
}
```

## Benefits

✅ **Less Boilerplate**: Eliminates 15-20 lines per node
✅ **Standardization**: Consistent node setup across codebase
✅ **No Copy-Paste Errors**: Reduces human error
✅ **Multi-Instance Made Easy**: Add one attribute to enable
✅ **Config Flexibility**: Automatic fallback chain
✅ **Type Safety**: Compile-time guarantees
✅ **Documentation**: Generated functions include docs

## Complete Example

```rust
// lib.rs
use mecha10::prelude::*;
use mecha10::sensor::RgbImage;

#[derive(Debug, Node)]
#[node(
    name = "camera_driver",
    config_env = "CAMERA_CONFIG",
    config_path = "config/drivers/camera.yaml",
    env_prefix = "CAMERA",
    instance_env = "CAMERA_INSTANCE"
)]
pub struct CameraDriverNode {
    config: CameraConfig,
    frame_counter: u64,
}

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct CameraConfig {
    pub fps: f32,
    pub width: u32,
    pub height: u32,
}

#[async_trait]
impl NodeImpl for CameraDriverNode {
    type Config = CameraConfig;

    async fn init(config: Self::Config) -> Result<Self> {
        info!("Camera initialized: {}x{} @ {} fps",
              config.width, config.height, config.fps);
        Ok(Self {
            config,
            frame_counter: 0,
        })
    }

    async fn run(&mut self, ctx: &Context) -> Result<()> {
        let mut interval = interval_at_hz(self.config.fps);

        loop {
            interval.tick().await;

            let image = self.capture_image();
            ctx.publish(&image).await?;

            self.frame_counter += 1;
        }
    }
}

impl CameraDriverNode {
    fn capture_image(&self) -> RgbImage {
        // Capture logic
        todo!()
    }
}

// main.rs
#[tokio::main]
async fn main() -> anyhow::Result<()> {
    camera_driver::run().await
}
```

**Run it:**

```bash
# Single instance
./camera_driver

# Multi-instance deployment
CAMERA_INSTANCE=left ./camera_driver &
CAMERA_INSTANCE=right ./camera_driver &
CAMERA_INSTANCE=top ./camera_driver &

# Custom config
CAMERA_CONFIG=high_res.yaml CAMERA_INSTANCE=front ./camera_driver

# Environment variable overrides
CAMERA_FPS=120 CAMERA_INSTANCE=highspeed ./camera_driver
```

## See Also

- [`#[derive(Config)]`](./README_CONFIG_MACRO.md) - Automatic config loading methods
- [`run_node_simple!`](../core/src/macros.rs) - Legacy macro (prefer `#[derive(Node)]`)
- [`run_node_with_instance!`](../core/src/macros.rs) - Legacy macro (prefer `#[derive(Node)]`)
- [Examples](./examples/node_macro_usage.rs) - More usage examples
