1use std::collections::HashMap;
9use std::fmt;
10use std::sync::Arc;
11
12use rill_core::queues::CommandEnum;
13use rill_core::traits::ParamValue;
14use rill_core_actor::{ActorRef, ActorSystem};
15
16use crate::module_def::{AutomatonDef, ModuleDef};
17
18#[derive(Debug, Clone)]
20pub enum ModuleError {
21 UnknownType(String),
23 ConstructionFailed(String),
25}
26
27impl fmt::Display for ModuleError {
28 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29 match self {
30 Self::UnknownType(t) => write!(f, "unknown module type: {t}"),
31 Self::ConstructionFailed(e) => write!(f, "module construction failed: {e}"),
32 }
33 }
34}
35
36pub trait ModuleConstructor: Send + Sync {
43 fn type_name(&self) -> &'static str;
45
46 fn construct(
51 &self,
52 module: &ModuleDef,
53 automaton_defs: &[AutomatonDef],
54 system: &Arc<ActorSystem>,
55 graph_ref: &ActorRef<CommandEnum>,
56 ) -> Result<ActorRef<CommandEnum>, ModuleError>;
57
58 fn clone_box(&self) -> Box<dyn ModuleConstructor>;
60}
61
62#[derive(Debug, Clone, Copy)]
64pub enum Drain {
65 OsThread {
67 interval_ms: u64,
69 },
70 TokioTask {
72 interval_ms: u64,
74 },
75 IoCallback,
78}
79pub struct ModuleFactory {
81 entries: HashMap<String, Box<dyn ModuleConstructor>>,
82}
83
84impl ModuleFactory {
85 pub fn new() -> Self {
87 Self {
88 entries: HashMap::new(),
89 }
90 }
91 pub fn register(&mut self, ctor: impl ModuleConstructor + 'static) {
93 self.entries
94 .insert(ctor.type_name().to_string(), Box::new(ctor));
95 }
96
97 #[allow(dead_code)]
103 pub fn register_fn(
104 &mut self,
105 type_name: impl Into<String>,
106 drain: Drain,
107 make_handler: impl Fn(
108 &str,
109 &HashMap<String, ParamValue>,
110 &ActorRef<CommandEnum>,
111 ) -> Box<dyn FnMut(CommandEnum) + 'static>
112 + Send
113 + Sync
114 + 'static,
115 ) {
116 self.entries.insert(
117 type_name.into(),
118 Box::new(ClosureCtor::new_erased(drain, make_handler)),
119 );
120 }
121
122 #[allow(dead_code)]
126 pub fn register_fn_send(
127 &mut self,
128 type_name: impl Into<String>,
129 drain: Drain,
130 make_handler: impl Fn(
131 &str,
132 &HashMap<String, ParamValue>,
133 &ActorRef<CommandEnum>,
134 ) -> Box<dyn FnMut(CommandEnum) + Send + 'static>
135 + Send
136 + Sync
137 + 'static,
138 ) {
139 self.entries.insert(
140 type_name.into(),
141 Box::new(ClosureCtor::new_send(drain, make_handler)),
142 );
143 }
144
145 pub fn construct(
147 &self,
148 module: &ModuleDef,
149 automaton_defs: &[AutomatonDef],
150 system: &Arc<ActorSystem>,
151 graph_ref: &ActorRef<CommandEnum>,
152 ) -> Result<ActorRef<CommandEnum>, ModuleError> {
153 let type_name = module.type_name();
154 self.entries
155 .get(type_name)
156 .ok_or_else(|| ModuleError::UnknownType(type_name.to_string()))
157 .and_then(|ctor| ctor.construct(module, automaton_defs, system, graph_ref))
158 }
159
160 pub fn contains(&self, type_name: &str) -> bool {
162 self.entries.contains_key(type_name)
163 }
164 pub fn len(&self) -> usize {
166 self.entries.len()
167 }
168 pub fn is_empty(&self) -> bool {
170 self.entries.is_empty()
171 }
172}
173
174impl Default for ModuleFactory {
175 fn default() -> Self {
176 Self::new()
177 }
178}
179
180type ErasedCtorFn = Arc<
185 dyn Fn(
186 &str,
187 &HashMap<String, ParamValue>,
188 &ActorRef<CommandEnum>,
189 ) -> Box<dyn FnMut(CommandEnum) + 'static>
190 + Send
191 + Sync,
192>;
193
194type SendCtorFn = Arc<
195 dyn Fn(
196 &str,
197 &HashMap<String, ParamValue>,
198 &ActorRef<CommandEnum>,
199 ) -> Box<dyn FnMut(CommandEnum) + Send + 'static>
200 + Send
201 + Sync,
202>;
203
204enum ClosureCtorKind {
205 Erased { f: ErasedCtorFn },
206 Send { f: SendCtorFn },
207}
208
209struct ClosureCtor {
210 drain: Drain,
211 kind: ClosureCtorKind,
212}
213
214impl ClosureCtor {
215 fn new_erased(
216 drain: Drain,
217 f: impl Fn(
218 &str,
219 &HashMap<String, ParamValue>,
220 &ActorRef<CommandEnum>,
221 ) -> Box<dyn FnMut(CommandEnum) + 'static>
222 + Send
223 + Sync
224 + 'static,
225 ) -> Self {
226 Self {
227 drain,
228 kind: ClosureCtorKind::Erased { f: Arc::new(f) },
229 }
230 }
231
232 fn new_send(
233 drain: Drain,
234 f: impl Fn(
235 &str,
236 &HashMap<String, ParamValue>,
237 &ActorRef<CommandEnum>,
238 ) -> Box<dyn FnMut(CommandEnum) + Send + 'static>
239 + Send
240 + Sync
241 + 'static,
242 ) -> Self {
243 Self {
244 drain,
245 kind: ClosureCtorKind::Send { f: Arc::new(f) },
246 }
247 }
248}
249
250impl ModuleConstructor for ClosureCtor {
251 fn type_name(&self) -> &'static str {
252 ""
253 }
254 fn construct(
255 &self,
256 module: &ModuleDef,
257 _automaton_defs: &[AutomatonDef],
258 system: &Arc<ActorSystem>,
259 graph_ref: &ActorRef<CommandEnum>,
260 ) -> Result<ActorRef<CommandEnum>, ModuleError> {
261 let ModuleDef::Custom {
262 type_name: _,
263 params,
264 } = module
265 else {
266 return Err(ModuleError::ConstructionFailed(
267 "ClosureCtor only supports Custom modules".into(),
268 ));
269 };
270
271 let id_owned = String::new(); let name = "custom".to_string();
273 let graph_ref = graph_ref.clone();
274 let params = params.clone();
275
276 match (&self.kind, self.drain) {
277 (ClosureCtorKind::Erased { f }, Drain::OsThread { interval_ms }) => {
278 let f = f.clone();
279 let actor_ref = system.spawn_detached(
280 &name,
281 move || f(&id_owned, ¶ms, &graph_ref),
282 interval_ms,
283 );
284 Ok(actor_ref)
285 }
286 (ClosureCtorKind::Send { f }, Drain::OsThread { interval_ms }) => {
287 let f = f.clone();
288 let actor_ref = system.spawn_detached(
289 &name,
290 move || f(&id_owned, ¶ms, &graph_ref),
291 interval_ms,
292 );
293 Ok(actor_ref)
294 }
295 (ClosureCtorKind::Send { f }, Drain::TokioTask { interval_ms }) => {
296 let f = f.clone();
297 let actor_ref = system.spawn_detached_tokio(
298 &name,
299 move || f(&id_owned, ¶ms, &graph_ref),
300 interval_ms,
301 );
302 Ok(actor_ref)
303 }
304 (ClosureCtorKind::Erased { .. }, Drain::TokioTask { .. }) => {
305 Err(ModuleError::ConstructionFailed(
306 "TokioTask drain requires a Send handler; use register_fn_send()".into(),
307 ))
308 }
309 (ClosureCtorKind::Erased { .. }, Drain::IoCallback) => {
310 Err(ModuleError::ConstructionFailed(
311 "IoCallback drain not supported via register_fn(); use Graph constructor directly".into(),
312 ))
313 }
314 (ClosureCtorKind::Send { .. }, Drain::IoCallback) => {
315 Err(ModuleError::ConstructionFailed(
316 "IoCallback drain not supported via register_fn_send(); use Graph constructor directly".into(),
317 ))
318 }
319 }
320 }
321 fn clone_box(&self) -> Box<dyn ModuleConstructor> {
322 match &self.kind {
323 ClosureCtorKind::Erased { f } => Box::new(ClosureCtor {
324 drain: self.drain,
325 kind: ClosureCtorKind::Erased { f: f.clone() },
326 }),
327 ClosureCtorKind::Send { f } => Box::new(ClosureCtor {
328 drain: self.drain,
329 kind: ClosureCtorKind::Send { f: f.clone() },
330 }),
331 }
332 }
333}