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}