mecha10-behavior-runtime 0.8.0

Behavior tree runtime for Mecha10 - unified AI and logic composition system
Documentation
//! Core BehaviorNode trait and types
//!
//! This module defines the fundamental `BehaviorNode` trait that all behaviors must implement.

use crate::NodeStatus;
use async_trait::async_trait;
use mecha10_core::Context;
use std::fmt::Debug;

#[async_trait]
pub trait BehaviorNode: Send + Sync + Debug {
    /// Execute one tick of the behavior. Called repeatedly by the execution engine; should
    /// perform one unit of work, return quickly, and report progress via `NodeStatus`.
    async fn tick(&mut self, ctx: &Context) -> anyhow::Result<NodeStatus>;

    /// Get the name of this behavior node.
    ///
    /// Used for debugging, logging, and monitoring.
    fn name(&self) -> &str {
        std::any::type_name::<Self>()
    }

    /// Return mutable references to this node's child behaviors, in the order
    /// they should receive lifecycle events. Leaf nodes should not override this;
    /// composite nodes override it to expose their children.
    fn children_mut(&mut self) -> Vec<&mut dyn BehaviorNode> {
        Vec::new()
    }

    /// The `capability` this node implies should be published on `/behavior/status` while it's
    /// actively being ticked; `None` for nodes with no such gate (the default). Composite nodes
    /// override this to forward whichever child is currently active.
    fn active_capability(&self) -> Option<&str> {
        None
    }

    /// Reset this node's own local state, ignoring children.
    ///
    /// Called by the default `reset()` before it propagates to children.
    fn reset_self(&mut self) {}

    /// Reset the behavior to its initial state.
    ///
    /// Called when the behavior needs to be restarted (e.g., in a loop or after a failure).
    async fn reset(&mut self) -> anyhow::Result<()> {
        self.reset_self();
        for child in self.children_mut() {
            child.reset().await?;
        }
        Ok(())
    }

    /// Called when the behavior is first initialized.
    ///
    /// Use this for one-time setup like loading models, establishing connections, etc.
    async fn on_init(&mut self, ctx: &Context) -> anyhow::Result<()> {
        for child in self.children_mut() {
            child.on_init(ctx).await?;
        }
        Ok(())
    }

    /// Called when the behavior is being terminated.
    ///
    /// Use this for cleanup like closing connections, saving state, etc.
    async fn on_terminate(&mut self, ctx: &Context) -> anyhow::Result<()> {
        for child in self.children_mut() {
            child.on_terminate(ctx).await?;
        }
        Ok(())
    }
}

pub type BoxedBehavior = Box<dyn BehaviorNode>;

/// Maps an iterator of boxed child behaviors to `BehaviorNode::children_mut`'s trait-object
/// return type.
pub fn boxed_children_mut<'a>(children: impl Iterator<Item = &'a mut BoxedBehavior>) -> Vec<&'a mut dyn BehaviorNode> {
    children.map(|c| c.as_mut() as &mut dyn BehaviorNode).collect()
}

#[macro_export]
macro_rules! children_mut_via_wrapped_behavior {
    ($field:ident, $wrapper:ident) => {
        fn children_mut(&mut self) -> Vec<&mut dyn $crate::BehaviorNode> {
            $crate::boxed_children_mut(self.$field.iter_mut().map(|$wrapper| &mut $wrapper.behavior))
        }
    };
}

#[macro_export]
macro_rules! reset_child_statuses_field {
    ($field:ident) => {
        fn reset_self(&mut self) {
            self.$field
                .iter_mut()
                .for_each(|s| *s = $crate::NodeStatus::Running);
        }
    };
}

#[macro_export]
macro_rules! reset_index_field {
    ($field:ident) => {
        fn reset_self(&mut self) {
            self.$field = 0;
        }
    };
}

#[async_trait]
pub trait BehaviorNodeExt: BehaviorNode {
    /// Run this behavior until it completes (Success or Failure).
    ///
    /// Convenience method for testing and simple execution.
    async fn run_until_complete(&mut self, ctx: &Context) -> anyhow::Result<NodeStatus> {
        loop {
            match self.tick(ctx).await? {
                NodeStatus::Success => return Ok(NodeStatus::Success),
                NodeStatus::Failure => return Ok(NodeStatus::Failure),
                NodeStatus::Running => continue,
            }
        }
    }

    /// Run this behavior with a maximum number of ticks.
    ///
    /// Returns `Err` if the behavior doesn't complete within `max_ticks`.
    async fn run_with_limit(&mut self, ctx: &Context, max_ticks: usize) -> anyhow::Result<NodeStatus> {
        for _tick_count in 0..max_ticks {
            match self.tick(ctx).await? {
                NodeStatus::Success => return Ok(NodeStatus::Success),
                NodeStatus::Failure => return Ok(NodeStatus::Failure),
                NodeStatus::Running => continue,
            }
        }
        anyhow::bail!("Behavior exceeded maximum ticks ({})", max_ticks)
    }
}

impl<T: BehaviorNode + ?Sized> BehaviorNodeExt for T {}