mecha10-behavior-runtime 0.1.25

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;

/// Core trait that all behavior nodes must implement.
///
/// This is the fundamental building block of the behavior system. Everything - from simple
/// actions to complex AI models to composition primitives - implements this trait.
///
/// # Design Principles
///
/// 1. **Simplicity** - One method: `tick()`
/// 2. **Async-first** - All behaviors are async by default
/// 3. **Composable** - Nodes can be freely combined
/// 4. **Type-safe** - Rust's type system ensures correctness
///
/// # Example
///
/// ```rust
/// use mecha10_behavior_runtime::prelude::*;
///
/// #[derive(Debug)]
/// struct PrintMessage {
///     message: String,
/// }
///
/// #[async_trait]
/// impl BehaviorNode for PrintMessage {
///     async fn tick(&mut self, ctx: &Context) -> anyhow::Result<NodeStatus> {
///         println!("{}", self.message);
///         Ok(NodeStatus::Success)
///     }
///
///     fn name(&self) -> &str {
///         "print_message"
///     }
/// }
/// ```
#[async_trait]
pub trait BehaviorNode: Send + Sync + Debug {
    /// Execute one tick of the behavior.
    ///
    /// This method is called repeatedly by the execution engine. It should:
    /// - Perform one unit of work
    /// - Return quickly (non-blocking)
    /// - Return `NodeStatus` to indicate progress
    ///
    /// # Arguments
    ///
    /// * `ctx` - Execution context with access to messaging, sensors, etc.
    ///
    /// # Returns
    ///
    /// * `NodeStatus::Success` - Behavior completed successfully
    /// * `NodeStatus::Failure` - Behavior failed (irrecoverable error)
    /// * `NodeStatus::Running` - Behavior is still executing (call again)
    ///
    /// # Errors
    ///
    /// Return an error for exceptional conditions that should halt execution.
    /// For recoverable failures, return `Ok(NodeStatus::Failure)` instead.
    async fn tick(&mut self, ctx: &Context) -> anyhow::Result<NodeStatus>;

    /// Get the name of this behavior node.
    ///
    /// Used for debugging, logging, and monitoring. The default implementation
    /// returns the type name.
    fn name(&self) -> &str {
        std::any::type_name::<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). The default implementation does nothing.
    async fn reset(&mut self) -> anyhow::Result<()> {
        Ok(())
    }

    /// Called when the behavior is first initialized.
    ///
    /// Use this for one-time setup like loading models, establishing connections, etc.
    /// The default implementation does nothing.
    async fn on_init(&mut self, _ctx: &Context) -> anyhow::Result<()> {
        Ok(())
    }

    /// Called when the behavior is being terminated.
    ///
    /// Use this for cleanup like closing connections, saving state, etc.
    /// The default implementation does nothing.
    async fn on_terminate(&mut self, _ctx: &Context) -> anyhow::Result<()> {
        Ok(())
    }
}

/// Type alias for boxed behavior nodes.
///
/// This is used for storing heterogeneous collections of behaviors.
pub type BoxedBehavior = Box<dyn BehaviorNode>;

// ============================================================================
// Helper Implementations
// ============================================================================

/// Extension trait for BehaviorNode to provide convenient helpers.
///
/// This trait is automatically implemented for all types that implement `BehaviorNode`.
#[async_trait]
pub trait BehaviorNodeExt: BehaviorNode {
    /// Run this behavior until it completes (Success or Failure).
    ///
    /// This is a convenience method for testing and simple execution.
    ///
    /// # Example
    ///
    /// ```rust
    /// use mecha10_behavior_runtime::prelude::*;
    ///
    /// # async fn example(mut behavior: impl BehaviorNode, ctx: &Context) -> anyhow::Result<()> {
    /// let status = behavior.run_until_complete(ctx).await?;
    /// assert_eq!(status, NodeStatus::Success);
    /// # Ok(())
    /// # }
    /// ```
    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`.
    ///
    /// # Example
    ///
    /// ```rust
    /// use mecha10_behavior_runtime::prelude::*;
    ///
    /// # async fn example(mut behavior: impl BehaviorNode, ctx: &Context) -> anyhow::Result<()> {
    /// let status = behavior.run_with_limit(ctx, 100).await?;
    /// # Ok(())
    /// # }
    /// ```
    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)
    }
}

// Blanket implementation for all BehaviorNode types
impl<T: BehaviorNode + ?Sized> BehaviorNodeExt for T {}