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
12/// Type alias for node factory functions.
13///
14/// A factory function takes a JSON configuration value and returns a boxed behavior node.
15/// The configuration value can be used to parameterize the behavior at construction time.
16///
17/// # Example
18///
19/// ```rust
20/// use mecha10_behavior_runtime::prelude::*;
21/// use serde_json::Value;
22///
23/// fn create_my_behavior(config: Value) -> anyhow::Result<BoxedBehavior> {
24///     // Parse configuration
25///     let speed = config.get("speed")
26///         .and_then(|v| v.as_f64())
27///         .unwrap_or(1.0);
28///
29///     // Create and return behavior
30///     // Ok(Box::new(MyBehavior::new(speed)))
31///     # Ok(Box::new(mecha10_behavior_runtime::SequenceNode::new(vec![])))
32/// }
33/// ```
34pub type NodeFactory = Arc<dyn Fn(Value) -> Result<BoxedBehavior> + Send + Sync>;
35
36/// Registry for behavior node types.
37///
38/// The `NodeRegistry` provides a factory pattern for creating behavior nodes from
39/// string identifiers and JSON configuration. This enables loading behavior trees
40/// from JSON files without hardcoding node types.
41///
42/// # Thread Safety
43///
44/// The registry is thread-safe and can be shared across multiple threads using `Arc`.
45/// Internally it uses `RwLock` to allow concurrent reads while serializing writes.
46///
47/// # Example
48///
49/// ```rust
50/// use mecha10_behavior_runtime::prelude::*;
51/// use mecha10_behavior_runtime::NodeRegistry;
52/// use serde_json::json;
53///
54/// # #[derive(Debug)]
55/// # struct MyBehavior;
56/// # #[async_trait::async_trait]
57/// # impl BehaviorNode for MyBehavior {
58/// #     async fn tick(&mut self, _ctx: &Context) -> anyhow::Result<NodeStatus> {
59/// #         Ok(NodeStatus::Success)
60/// #     }
61/// # }
62///
63/// // Create a registry
64/// let mut registry = NodeRegistry::new();
65///
66/// // Register a node type
67/// registry.register("my_behavior", |config| {
68///     Ok(Box::new(MyBehavior))
69/// });
70///
71/// // Create an instance from the registry
72/// let node = registry.create("my_behavior", json!({})).unwrap();
73/// ```
74#[derive(Clone)]
75pub struct NodeRegistry {
76    factories: Arc<RwLock<HashMap<String, NodeFactory>>>,
77}
78
79impl NodeRegistry {
80    /// Create a new empty node registry.
81    pub fn new() -> Self {
82        Self {
83            factories: Arc::new(RwLock::new(HashMap::new())),
84        }
85    }
86
87    /// Register a node type with a factory function.
88    ///
89    /// The factory function will be called each time a node of this type is created.
90    /// It receives the JSON configuration and should return a boxed behavior node.
91    ///
92    /// # Arguments
93    ///
94    /// * `node_type` - String identifier for this node type (e.g., "move_to_goal")
95    /// * `factory` - Function that creates instances of this node type
96    ///
97    /// # Example
98    ///
99    /// ```rust
100    /// use mecha10_behavior_runtime::NodeRegistry;
101    /// use serde_json::json;
102    ///
103    /// # #[derive(Debug)]
104    /// # struct MoveBehavior { speed: f64 }
105    /// # #[async_trait::async_trait]
106    /// # impl mecha10_behavior_runtime::BehaviorNode for MoveBehavior {
107    /// #     async fn tick(&mut self, _ctx: &mecha10_behavior_runtime::Context) -> anyhow::Result<mecha10_behavior_runtime::NodeStatus> {
108    /// #         Ok(mecha10_behavior_runtime::NodeStatus::Success)
109    /// #     }
110    /// # }
111    ///
112    /// let mut registry = NodeRegistry::new();
113    ///
114    /// registry.register("move", |config| {
115    ///     let speed = config.get("speed")
116    ///         .and_then(|v| v.as_f64())
117    ///         .unwrap_or(1.0);
118    ///     Ok(Box::new(MoveBehavior { speed }))
119    /// });
120    /// ```
121    pub fn register<F>(&mut self, node_type: impl Into<String>, factory: F)
122    where
123        F: Fn(Value) -> Result<BoxedBehavior> + Send + Sync + 'static,
124    {
125        let mut factories = self.factories.write().unwrap();
126        factories.insert(node_type.into(), Arc::new(factory));
127    }
128
129    /// Create a node instance from a registered type.
130    ///
131    /// This looks up the factory function for the given node type and calls it
132    /// with the provided configuration to create a new instance.
133    ///
134    /// # Arguments
135    ///
136    /// * `node_type` - The type identifier that was used during registration
137    /// * `config` - JSON configuration to pass to the factory function
138    ///
139    /// # Returns
140    ///
141    /// A boxed behavior node instance, or an error if:
142    /// - The node type is not registered
143    /// - The factory function returns an error
144    ///
145    /// # Example
146    ///
147    /// ```rust
148    /// use mecha10_behavior_runtime::NodeRegistry;
149    /// use serde_json::json;
150    ///
151    /// # let mut registry = NodeRegistry::new();
152    /// # registry.register("move", |_| Ok(Box::new(mecha10_behavior_runtime::SequenceNode::new(vec![]))));
153    ///
154    /// let config = json!({ "speed": 2.0 });
155    /// let node = registry.create("move", config).unwrap();
156    /// ```
157    pub fn create(&self, node_type: &str, config: Value) -> Result<BoxedBehavior> {
158        let factories = self.factories.read().unwrap();
159
160        let factory = factories
161            .get(node_type)
162            .ok_or_else(|| anyhow!("Node type '{}' not registered", node_type))?;
163
164        factory(config).with_context(|| format!("Failed to create node of type '{}'", node_type))
165    }
166
167    /// Check if a node type is registered.
168    ///
169    /// # Arguments
170    ///
171    /// * `node_type` - The type identifier to check
172    ///
173    /// # Returns
174    ///
175    /// `true` if the node type is registered, `false` otherwise
176    ///
177    /// # Example
178    ///
179    /// ```rust
180    /// use mecha10_behavior_runtime::NodeRegistry;
181    ///
182    /// let mut registry = NodeRegistry::new();
183    /// registry.register("move", |_| Ok(Box::new(mecha10_behavior_runtime::SequenceNode::new(vec![]))));
184    ///
185    /// assert!(registry.has_node("move"));
186    /// assert!(!registry.has_node("unknown"));
187    /// ```
188    pub fn has_node(&self, node_type: &str) -> bool {
189        let factories = self.factories.read().unwrap();
190        factories.contains_key(node_type)
191    }
192
193    /// Check if a node type is registered.
194    ///
195    /// # Example
196    ///
197    /// ```rust
198    /// use mecha10_behavior_runtime::NodeRegistry;
199    ///
200    /// let mut registry = NodeRegistry::new();
201    /// registry.register("move", |_| Ok(Box::new(mecha10_behavior_runtime::SequenceNode::new(vec![]))));
202    ///
203    /// assert!(registry.contains("move"));
204    /// assert!(!registry.contains("unknown"));
205    /// ```
206    pub fn contains(&self, node_type: &str) -> bool {
207        let factories = self.factories.read().unwrap();
208        factories.contains_key(node_type)
209    }
210
211    /// Get a list of all registered node types.
212    ///
213    /// # Example
214    ///
215    /// ```rust
216    /// use mecha10_behavior_runtime::NodeRegistry;
217    ///
218    /// let mut registry = NodeRegistry::new();
219    /// registry.register("move", |_| Ok(Box::new(mecha10_behavior_runtime::SequenceNode::new(vec![]))));
220    /// registry.register("rotate", |_| Ok(Box::new(mecha10_behavior_runtime::SequenceNode::new(vec![]))));
221    ///
222    /// let types = registry.registered_types();
223    /// assert!(types.contains(&"move".to_string()));
224    /// assert!(types.contains(&"rotate".to_string()));
225    /// ```
226    pub fn registered_types(&self) -> Vec<String> {
227        let factories = self.factories.read().unwrap();
228        factories.keys().cloned().collect()
229    }
230
231    /// Get the number of registered node types.
232    ///
233    /// # Example
234    ///
235    /// ```rust
236    /// use mecha10_behavior_runtime::NodeRegistry;
237    ///
238    /// let mut registry = NodeRegistry::new();
239    /// assert_eq!(registry.len(), 0);
240    ///
241    /// registry.register("move", |_| Ok(Box::new(mecha10_behavior_runtime::SequenceNode::new(vec![]))));
242    /// assert_eq!(registry.len(), 1);
243    /// ```
244    pub fn len(&self) -> usize {
245        let factories = self.factories.read().unwrap();
246        factories.len()
247    }
248
249    /// Check if the registry is empty.
250    ///
251    /// # Example
252    ///
253    /// ```rust
254    /// use mecha10_behavior_runtime::NodeRegistry;
255    ///
256    /// let registry = NodeRegistry::new();
257    /// assert!(registry.is_empty());
258    /// ```
259    pub fn is_empty(&self) -> bool {
260        self.len() == 0
261    }
262}
263
264impl Default for NodeRegistry {
265    fn default() -> Self {
266        Self::new()
267    }
268}
269
270impl std::fmt::Debug for NodeRegistry {
271    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
272        let factories = self.factories.read().unwrap();
273        f.debug_struct("NodeRegistry")
274            .field("registered_types", &factories.keys().collect::<Vec<_>>())
275            .finish()
276    }
277}