mecha10-behavior-runtime 0.6.3

Behavior tree runtime for Mecha10 - unified AI and logic composition system
Documentation
//! Action nodes that gate on elapsed time, or do nothing.

use crate::{BehaviorNode, Context, NodeStatus};
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::time::{Duration, Instant};

/// TimerNode - Waits for a specified duration
///
/// Returns Running until the duration elapses, then returns Success.
///
/// # Configuration
///
/// ```json
/// {
///   "type": "timer",
///   "config": {
///     "duration_secs": 5.0
///   }
/// }
/// ```
#[derive(Debug)]
pub struct TimerNode {
    duration: Duration,
    start_time: Option<Instant>,
}

#[derive(Debug, Serialize, Deserialize)]
struct TimerConfig {
    duration_secs: f64,
}

impl TimerNode {
    /// Create a new TimerNode from JSON configuration
    pub fn from_json(config: Value) -> anyhow::Result<Self> {
        let cfg: TimerConfig = serde_json::from_value(config)?;
        Ok(Self {
            duration: Duration::from_secs_f64(cfg.duration_secs),
            start_time: None,
        })
    }
}

#[async_trait]
impl BehaviorNode for TimerNode {
    async fn tick(&mut self, _ctx: &Context) -> anyhow::Result<NodeStatus> {
        let now = Instant::now();

        // Initialize start time on first tick
        if self.start_time.is_none() {
            self.start_time = Some(now);
        }

        // Check if duration has elapsed
        if now.duration_since(self.start_time.unwrap()) >= self.duration {
            Ok(NodeStatus::Success)
        } else {
            Ok(NodeStatus::Running)
        }
    }

    async fn reset(&mut self) -> anyhow::Result<()> {
        self.start_time = None;
        Ok(())
    }

    fn name(&self) -> &str {
        "TimerNode"
    }
}

/// IdleNode - Returns Success immediately
///
/// This node does nothing and returns Success on every tick.
/// Useful for testing and as a placeholder in behavior trees.
///
/// # Configuration
///
/// ```json
/// {
///   "type": "idle",
///   "config": {}
/// }
/// ```
#[derive(Debug)]
pub struct IdleNode;

impl IdleNode {
    /// Create a new IdleNode from JSON configuration
    pub fn from_json(_config: Value) -> anyhow::Result<Self> {
        Ok(Self)
    }
}

#[async_trait]
impl BehaviorNode for IdleNode {
    async fn tick(&mut self, _ctx: &Context) -> anyhow::Result<NodeStatus> {
        Ok(NodeStatus::Success)
    }

    fn name(&self) -> &str {
        "IdleNode"
    }
}

/// PauseNode - Pauses execution for a specified duration
///
/// Similar to TimerNode but semantically represents a deliberate pause in behavior.
/// Returns Running until the duration elapses, then returns Success.
///
/// # Configuration
///
/// ```json
/// {
///   "type": "pause",
///   "config": {
///     "duration_secs": 2.0
///   }
/// }
/// ```
#[derive(Debug)]
pub struct PauseNode {
    duration: Duration,
    start_time: Option<Instant>,
}

#[derive(Debug, Serialize, Deserialize)]
struct PauseConfig {
    duration_secs: f64,
}

impl PauseNode {
    /// Create a new PauseNode from JSON configuration
    pub fn from_json(config: Value) -> anyhow::Result<Self> {
        let cfg: PauseConfig = serde_json::from_value(config)?;
        Ok(Self {
            duration: Duration::from_secs_f64(cfg.duration_secs),
            start_time: None,
        })
    }
}

#[async_trait]
impl BehaviorNode for PauseNode {
    async fn tick(&mut self, _ctx: &Context) -> anyhow::Result<NodeStatus> {
        let now = Instant::now();

        // Initialize start time on first tick
        if self.start_time.is_none() {
            self.start_time = Some(now);
            tracing::debug!("PauseNode: starting pause for {:?}", self.duration);
        }

        // Check if duration has elapsed
        if now.duration_since(self.start_time.unwrap()) >= self.duration {
            tracing::debug!("PauseNode: pause complete");
            Ok(NodeStatus::Success)
        } else {
            Ok(NodeStatus::Running)
        }
    }

    async fn reset(&mut self) -> anyhow::Result<()> {
        self.start_time = None;
        Ok(())
    }

    fn name(&self) -> &str {
        "PauseNode"
    }
}