Skip to main content

mecha10_behavior_runtime/
registry.rs

1//! Node registry for dynamic behavior instantiation
2//!
3//! This module provides the `NodeRegistry` for registering and creating behavior nodes
4//! from string identifiers and JSON configuration.
5
6use crate::BoxedBehavior;
7use anyhow::{anyhow, Context as AnyhowContext, Result};
8use serde_json::Value;
9use std::collections::HashMap;
10use std::sync::{Arc, RwLock};
11
12pub type NodeFactory = Arc<dyn Fn(Value) -> Result<BoxedBehavior> + Send + Sync>;
13
14#[derive(Clone)]
15pub struct NodeRegistry {
16    factories: Arc<RwLock<HashMap<String, NodeFactory>>>,
17}
18
19impl NodeRegistry {
20    /// Create a new empty node registry.
21    pub fn new() -> Self {
22        Self {
23            factories: Arc::new(RwLock::new(HashMap::new())),
24        }
25    }
26
27    /// Register a node type with a factory function.
28    ///
29    /// The factory function will be called each time a node of this type is created.
30    /// It receives the JSON configuration and should return a boxed behavior node.
31    ///
32    /// # Arguments
33    ///
34    /// * `node_type` - String identifier for this node type (e.g., "move_to_goal")
35    /// * `factory` - Function that creates instances of this node type
36    ///
37    /// # Example
38    ///
39    /// ```rust
40    /// use mecha10_behavior_runtime::NodeRegistry;
41    /// use serde_json::json;
42    ///
43    /// # #[derive(Debug)]
44    /// # struct MoveBehavior { speed: f64 }
45    /// # #[async_trait::async_trait]
46    /// # impl mecha10_behavior_runtime::BehaviorNode for MoveBehavior {
47    /// #     async fn tick(&mut self, _ctx: &mecha10_behavior_runtime::Context) -> anyhow::Result<mecha10_behavior_runtime::NodeStatus> {
48    /// #         Ok(mecha10_behavior_runtime::NodeStatus::Success)
49    /// #     }
50    /// # }
51    ///
52    /// let mut registry = NodeRegistry::new();
53    ///
54    /// registry.register("move", |config| {
55    ///     let speed = config.get("speed")
56    ///         .and_then(|v| v.as_f64())
57    ///         .unwrap_or(1.0);
58    ///     Ok(Box::new(MoveBehavior { speed }))
59    /// });
60    /// ```
61    pub fn register<F>(&mut self, node_type: impl Into<String>, factory: F)
62    where
63        F: Fn(Value) -> Result<BoxedBehavior> + Send + Sync + 'static,
64    {
65        let mut factories = self.factories.write().unwrap();
66        factories.insert(node_type.into(), Arc::new(factory));
67    }
68
69    /// Create a node instance from a registered type.
70    ///
71    /// This looks up the factory function for the given node type and calls it
72    /// with the provided configuration to create a new instance.
73    ///
74    /// # Arguments
75    ///
76    /// * `node_type` - The type identifier that was used during registration
77    /// * `config` - JSON configuration to pass to the factory function
78    ///
79    /// # Returns
80    ///
81    /// A boxed behavior node instance, or an error if:
82    /// - The node type is not registered
83    /// - The factory function returns an error
84    ///
85    /// # Example
86    ///
87    /// ```rust
88    /// use mecha10_behavior_runtime::NodeRegistry;
89    /// use serde_json::json;
90    ///
91    /// # let mut registry = NodeRegistry::new();
92    /// # registry.register("move", |_| Ok(Box::new(mecha10_behavior_runtime::SequenceNode::new(vec![]))));
93    ///
94    /// let config = json!({ "speed": 2.0 });
95    /// let node = registry.create("move", config).unwrap();
96    /// ```
97    pub fn create(&self, node_type: &str, config: Value) -> Result<BoxedBehavior> {
98        let factories = self.factories.read().unwrap();
99
100        let factory = factories
101            .get(node_type)
102            .ok_or_else(|| anyhow!("Node type '{}' not registered", node_type))?;
103
104        factory(config).with_context(|| format!("Failed to create node of type '{}'", node_type))
105    }
106
107    /// Check if a node type is registered.
108    ///
109    /// # Arguments
110    ///
111    /// * `node_type` - The type identifier to check
112    ///
113    /// # Returns
114    ///
115    /// `true` if the node type is registered, `false` otherwise
116    ///
117    /// # Example
118    ///
119    /// ```rust
120    /// use mecha10_behavior_runtime::NodeRegistry;
121    ///
122    /// let mut registry = NodeRegistry::new();
123    /// registry.register("move", |_| Ok(Box::new(mecha10_behavior_runtime::SequenceNode::new(vec![]))));
124    ///
125    /// assert!(registry.has_node("move"));
126    /// assert!(!registry.has_node("unknown"));
127    /// ```
128    pub fn has_node(&self, node_type: &str) -> bool {
129        self.contains(node_type)
130    }
131
132    /// Check if a node type is registered.
133    ///
134    /// Same check as [`has_node`](Self::has_node) - both names are kept since callers use
135    /// either spelling, but the lookup itself lives here to avoid duplicating the `RwLock` read.
136    ///
137    /// # Example
138    ///
139    /// ```rust
140    /// use mecha10_behavior_runtime::NodeRegistry;
141    ///
142    /// let mut registry = NodeRegistry::new();
143    /// registry.register("move", |_| Ok(Box::new(mecha10_behavior_runtime::SequenceNode::new(vec![]))));
144    ///
145    /// assert!(registry.contains("move"));
146    /// assert!(!registry.contains("unknown"));
147    /// ```
148    pub fn contains(&self, node_type: &str) -> bool {
149        let factories = self.factories.read().unwrap();
150        factories.contains_key(node_type)
151    }
152
153    /// Get a list of all registered node types.
154    ///
155    /// # Example
156    ///
157    /// ```rust
158    /// use mecha10_behavior_runtime::NodeRegistry;
159    ///
160    /// let mut registry = NodeRegistry::new();
161    /// registry.register("move", |_| Ok(Box::new(mecha10_behavior_runtime::SequenceNode::new(vec![]))));
162    /// registry.register("rotate", |_| Ok(Box::new(mecha10_behavior_runtime::SequenceNode::new(vec![]))));
163    ///
164    /// let types = registry.registered_types();
165    /// assert!(types.contains(&"move".to_string()));
166    /// assert!(types.contains(&"rotate".to_string()));
167    /// ```
168    pub fn registered_types(&self) -> Vec<String> {
169        let factories = self.factories.read().unwrap();
170        factories.keys().cloned().collect()
171    }
172
173    /// Get the number of registered node types.
174    ///
175    /// # Example
176    ///
177    /// ```rust
178    /// use mecha10_behavior_runtime::NodeRegistry;
179    ///
180    /// let mut registry = NodeRegistry::new();
181    /// assert_eq!(registry.len(), 0);
182    ///
183    /// registry.register("move", |_| Ok(Box::new(mecha10_behavior_runtime::SequenceNode::new(vec![]))));
184    /// assert_eq!(registry.len(), 1);
185    /// ```
186    pub fn len(&self) -> usize {
187        let factories = self.factories.read().unwrap();
188        factories.len()
189    }
190
191    /// Check if the registry is empty.
192    ///
193    /// # Example
194    ///
195    /// ```rust
196    /// use mecha10_behavior_runtime::NodeRegistry;
197    ///
198    /// let registry = NodeRegistry::new();
199    /// assert!(registry.is_empty());
200    /// ```
201    pub fn is_empty(&self) -> bool {
202        self.len() == 0
203    }
204}
205
206impl Default for NodeRegistry {
207    fn default() -> Self {
208        Self::new()
209    }
210}
211
212impl std::fmt::Debug for NodeRegistry {
213    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
214        let factories = self.factories.read().unwrap();
215        f.debug_struct("NodeRegistry")
216            .field("registered_types", &factories.keys().collect::<Vec<_>>())
217            .finish()
218    }
219}