mecha10-behavior-runtime 0.7.0

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

use super::elapsed_timer::{reset_timer_field, ElapsedTimer};
use crate::{BehaviorNode, Context, NodeStatus};
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::time::Duration;

macro_rules! from_json_with_duration {
    ($config:ident) => {
        /// Create a new node from JSON configuration
        pub fn from_json(config: Value) -> anyhow::Result<Self> {
            let cfg: $config = serde_json::from_value(config)?;
            Ok(Self {
                duration: Duration::from_secs_f64(cfg.duration_secs),
                timer: ElapsedTimer::default(),
            })
        }
    };
}

#[derive(Debug, Default)]
pub struct TimerNode {
    duration: Duration,
    timer: ElapsedTimer,
}

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

impl TimerNode {
    from_json_with_duration!(TimerConfig);
}

#[async_trait]
impl BehaviorNode for TimerNode {
    async fn tick(&mut self, _ctx: &Context) -> anyhow::Result<NodeStatus> {
        if self.timer.elapsed_since_start(self.duration) {
            Ok(NodeStatus::Success)
        } else {
            Ok(NodeStatus::Running)
        }
    }

    reset_timer_field!(timer);

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

#[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"
    }
}

#[derive(Debug, Default)]
pub struct PauseNode {
    duration: Duration,
    timer: ElapsedTimer,
}

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

impl PauseNode {
    from_json_with_duration!(PauseConfig);
}

#[async_trait]
impl BehaviorNode for PauseNode {
    async fn tick(&mut self, _ctx: &Context) -> anyhow::Result<NodeStatus> {
        if !self.timer.is_running() {
            tracing::debug!("PauseNode: starting pause for {:?}", self.duration);
        }

        if self.timer.elapsed_since_start(self.duration) {
            tracing::debug!("PauseNode: pause complete");
            Ok(NodeStatus::Success)
        } else {
            Ok(NodeStatus::Running)
        }
    }

    reset_timer_field!(timer);

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