mecha10-behavior-runtime 0.8.1

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

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

pub type NodeFactory = Arc<dyn Fn(Value) -> Result<BoxedBehavior> + Send + Sync>;

#[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 (e.g. "move_to_goal") with a factory function.
    ///
    /// The factory is called each time a node of this type is created, receiving the JSON
    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, looking up its factory and calling it
    /// with `config`. Errors if the node type isn't registered or the factory fails.
    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.
    pub fn has_node(&self, node_type: &str) -> bool {
        self.contains(node_type)
    }

    /// Check if a node type is registered.
    ///
    /// Same check as [`has_node`](Self::has_node) - both names are kept since callers use
    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.
    pub fn registered_types(&self) -> Vec<String> {
        let factories = self.factories.read().unwrap();
        factories.keys().cloned().collect()
    }

    /// Get the number of registered node types.
    pub fn len(&self) -> usize {
        let factories = self.factories.read().unwrap();
        factories.len()
    }

    /// Check if the 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()
    }
}