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}