mecha10-behavior-runtime 0.1.25

Behavior tree runtime for Mecha10 - unified AI and logic composition system
Documentation
//! Node registry for dynamic behavior instantiation
//!
//! This module provides the `NodeRegistry` for registering and creating behavior nodes
//! from string identifiers and JSON configuration.

use crate::BoxedBehavior;
use anyhow::{anyhow, Context as AnyhowContext, Result};
use serde_json::Value;
use std::collections::HashMap;
use std::sync::{Arc, RwLock};

/// Type alias for node factory functions.
///
/// A factory function takes a JSON configuration value and returns a boxed behavior node.
/// The configuration value can be used to parameterize the behavior at construction time.
///
/// # Example
///
/// ```rust
/// use mecha10_behavior_runtime::prelude::*;
/// use serde_json::Value;
///
/// fn create_my_behavior(config: Value) -> anyhow::Result<BoxedBehavior> {
///     // Parse configuration
///     let speed = config.get("speed")
///         .and_then(|v| v.as_f64())
///         .unwrap_or(1.0);
///
///     // Create and return behavior
///     // Ok(Box::new(MyBehavior::new(speed)))
///     # Ok(Box::new(mecha10_behavior_runtime::SequenceNode::new(vec![])))
/// }
/// ```
pub type NodeFactory = Arc<dyn Fn(Value) -> Result<BoxedBehavior> + Send + Sync>;

/// Registry for behavior node types.
///
/// The `NodeRegistry` provides a factory pattern for creating behavior nodes from
/// string identifiers and JSON configuration. This enables loading behavior trees
/// from JSON files without hardcoding node types.
///
/// # Thread Safety
///
/// The registry is thread-safe and can be shared across multiple threads using `Arc`.
/// Internally it uses `RwLock` to allow concurrent reads while serializing writes.
///
/// # Example
///
/// ```rust
/// use mecha10_behavior_runtime::prelude::*;
/// use mecha10_behavior_runtime::NodeRegistry;
/// use serde_json::json;
///
/// # #[derive(Debug)]
/// # struct MyBehavior;
/// # #[async_trait::async_trait]
/// # impl BehaviorNode for MyBehavior {
/// #     async fn tick(&mut self, _ctx: &Context) -> anyhow::Result<NodeStatus> {
/// #         Ok(NodeStatus::Success)
/// #     }
/// # }
///
/// // Create a registry
/// let mut registry = NodeRegistry::new();
///
/// // Register a node type
/// registry.register("my_behavior", |config| {
///     Ok(Box::new(MyBehavior))
/// });
///
/// // Create an instance from the registry
/// let node = registry.create("my_behavior", json!({})).unwrap();
/// ```
#[derive(Clone)]
pub struct NodeRegistry {
    factories: Arc<RwLock<HashMap<String, NodeFactory>>>,
}

impl NodeRegistry {
    /// Create a new empty node registry.
    pub fn new() -> Self {
        Self {
            factories: Arc::new(RwLock::new(HashMap::new())),
        }
    }

    /// Register a node type with a factory function.
    ///
    /// The factory function will be called each time a node of this type is created.
    /// It receives the JSON configuration and should return a boxed behavior node.
    ///
    /// # Arguments
    ///
    /// * `node_type` - String identifier for this node type (e.g., "move_to_goal")
    /// * `factory` - Function that creates instances of this node type
    ///
    /// # Example
    ///
    /// ```rust
    /// use mecha10_behavior_runtime::NodeRegistry;
    /// use serde_json::json;
    ///
    /// # #[derive(Debug)]
    /// # struct MoveBehavior { speed: f64 }
    /// # #[async_trait::async_trait]
    /// # impl mecha10_behavior_runtime::BehaviorNode for MoveBehavior {
    /// #     async fn tick(&mut self, _ctx: &mecha10_behavior_runtime::Context) -> anyhow::Result<mecha10_behavior_runtime::NodeStatus> {
    /// #         Ok(mecha10_behavior_runtime::NodeStatus::Success)
    /// #     }
    /// # }
    ///
    /// let mut registry = NodeRegistry::new();
    ///
    /// registry.register("move", |config| {
    ///     let speed = config.get("speed")
    ///         .and_then(|v| v.as_f64())
    ///         .unwrap_or(1.0);
    ///     Ok(Box::new(MoveBehavior { speed }))
    /// });
    /// ```
    pub fn register<F>(&mut self, node_type: impl Into<String>, factory: F)
    where
        F: Fn(Value) -> Result<BoxedBehavior> + Send + Sync + 'static,
    {
        let mut factories = self.factories.write().unwrap();
        factories.insert(node_type.into(), Arc::new(factory));
    }

    /// Create a node instance from a registered type.
    ///
    /// This looks up the factory function for the given node type and calls it
    /// with the provided configuration to create a new instance.
    ///
    /// # Arguments
    ///
    /// * `node_type` - The type identifier that was used during registration
    /// * `config` - JSON configuration to pass to the factory function
    ///
    /// # Returns
    ///
    /// A boxed behavior node instance, or an error if:
    /// - The node type is not registered
    /// - The factory function returns an error
    ///
    /// # Example
    ///
    /// ```rust
    /// use mecha10_behavior_runtime::NodeRegistry;
    /// use serde_json::json;
    ///
    /// # let mut registry = NodeRegistry::new();
    /// # registry.register("move", |_| Ok(Box::new(mecha10_behavior_runtime::SequenceNode::new(vec![]))));
    ///
    /// let config = json!({ "speed": 2.0 });
    /// let node = registry.create("move", config).unwrap();
    /// ```
    pub fn create(&self, node_type: &str, config: Value) -> Result<BoxedBehavior> {
        let factories = self.factories.read().unwrap();

        let factory = factories
            .get(node_type)
            .ok_or_else(|| anyhow!("Node type '{}' not registered", node_type))?;

        factory(config).with_context(|| format!("Failed to create node of type '{}'", node_type))
    }

    /// Check if a node type is registered.
    ///
    /// # Arguments
    ///
    /// * `node_type` - The type identifier to check
    ///
    /// # Returns
    ///
    /// `true` if the node type is registered, `false` otherwise
    ///
    /// # Example
    ///
    /// ```rust
    /// use mecha10_behavior_runtime::NodeRegistry;
    ///
    /// let mut registry = NodeRegistry::new();
    /// registry.register("move", |_| Ok(Box::new(mecha10_behavior_runtime::SequenceNode::new(vec![]))));
    ///
    /// assert!(registry.has_node("move"));
    /// assert!(!registry.has_node("unknown"));
    /// ```
    pub fn has_node(&self, node_type: &str) -> bool {
        let factories = self.factories.read().unwrap();
        factories.contains_key(node_type)
    }

    /// Check if a node type is registered.
    ///
    /// # Example
    ///
    /// ```rust
    /// use mecha10_behavior_runtime::NodeRegistry;
    ///
    /// let mut registry = NodeRegistry::new();
    /// registry.register("move", |_| Ok(Box::new(mecha10_behavior_runtime::SequenceNode::new(vec![]))));
    ///
    /// assert!(registry.contains("move"));
    /// assert!(!registry.contains("unknown"));
    /// ```
    pub fn contains(&self, node_type: &str) -> bool {
        let factories = self.factories.read().unwrap();
        factories.contains_key(node_type)
    }

    /// Get a list of all registered node types.
    ///
    /// # Example
    ///
    /// ```rust
    /// use mecha10_behavior_runtime::NodeRegistry;
    ///
    /// let mut registry = NodeRegistry::new();
    /// registry.register("move", |_| Ok(Box::new(mecha10_behavior_runtime::SequenceNode::new(vec![]))));
    /// registry.register("rotate", |_| Ok(Box::new(mecha10_behavior_runtime::SequenceNode::new(vec![]))));
    ///
    /// let types = registry.registered_types();
    /// assert!(types.contains(&"move".to_string()));
    /// assert!(types.contains(&"rotate".to_string()));
    /// ```
    pub fn registered_types(&self) -> Vec<String> {
        let factories = self.factories.read().unwrap();
        factories.keys().cloned().collect()
    }

    /// Get the number of registered node types.
    ///
    /// # Example
    ///
    /// ```rust
    /// use mecha10_behavior_runtime::NodeRegistry;
    ///
    /// let mut registry = NodeRegistry::new();
    /// assert_eq!(registry.len(), 0);
    ///
    /// registry.register("move", |_| Ok(Box::new(mecha10_behavior_runtime::SequenceNode::new(vec![]))));
    /// assert_eq!(registry.len(), 1);
    /// ```
    pub fn len(&self) -> usize {
        let factories = self.factories.read().unwrap();
        factories.len()
    }

    /// Check if the registry is empty.
    ///
    /// # Example
    ///
    /// ```rust
    /// use mecha10_behavior_runtime::NodeRegistry;
    ///
    /// let registry = NodeRegistry::new();
    /// assert!(registry.is_empty());
    /// ```
    pub fn is_empty(&self) -> bool {
        self.len() == 0
    }
}

impl Default for NodeRegistry {
    fn default() -> Self {
        Self::new()
    }
}

impl std::fmt::Debug for NodeRegistry {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let factories = self.factories.read().unwrap();
        f.debug_struct("NodeRegistry")
            .field("registered_types", &factories.keys().collect::<Vec<_>>())
            .finish()
    }
}