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#[cfg(feature = "debug")]
19use crate::debug::PatchbayInspector;
20
21#[derive(Debug, Clone)]
23pub enum ModuleError {
24 UnknownType(String),
26 ConstructionFailed(String),
28}
29
30impl fmt::Display for ModuleError {
31 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32 match self {
33 Self::UnknownType(t) => write!(f, "unknown module type: {t}"),
34 Self::ConstructionFailed(e) => write!(f, "module construction failed: {e}"),
35 }
36 }
37}
38
39pub trait ModuleConstructor: Send + Sync {
46 fn type_name(&self) -> &'static str;
48
49 #[cfg_attr(feature = "debug", allow(unused_variables))]
54 fn construct(
55 &self,
56 module: &ModuleDef,
57 automaton_defs: &[AutomatonDef],
58 system: &Arc<ActorSystem>,
59 graph_ref: &ActorRef<CommandEnum>,
60 #[cfg(feature = "debug")] inspector: Option<&PatchbayInspector>,
61 ) -> Result<ActorRef<CommandEnum>, ModuleError>;
62
63 fn clone_box(&self) -> Box<dyn ModuleConstructor>;
65}
66
67#[derive(Debug, Clone, Copy)]
69pub enum Drain {
70 OsThread {
72 interval_ms: u64,
74 },
75 TokioTask {
77 interval_ms: u64,
79 },
80 IoCallback,
83}
84pub struct ModuleFactory {
86 entries: HashMap<String, Box<dyn ModuleConstructor>>,
87}
88
89impl ModuleFactory {
90 pub fn new() -> Self {
92 Self {
93 entries: HashMap::new(),
94 }
95 }
96 pub fn register(&mut self, ctor: impl ModuleConstructor + 'static) {
98 self.entries
99 .insert(ctor.type_name().to_string(), Box::new(ctor));
100 }
101
102 #[allow(dead_code)]
108 pub fn register_fn(
109 &mut self,
110 type_name: impl Into<String>,
111 drain: Drain,
112 make_handler: impl Fn(
113 &str,
114 &HashMap<String, ParamValue>,
115 &ActorRef<CommandEnum>,
116 ) -> Box<dyn FnMut(CommandEnum) + 'static>
117 + Send
118 + Sync
119 + 'static,
120 ) {
121 self.entries.insert(
122 type_name.into(),
123 Box::new(ClosureCtor::new_erased(drain, make_handler)),
124 );
125 }
126
127 #[allow(dead_code)]
131 pub fn register_fn_send(
132 &mut self,
133 type_name: impl Into<String>,
134 drain: Drain,
135 make_handler: impl Fn(
136 &str,
137 &HashMap<String, ParamValue>,
138 &ActorRef<CommandEnum>,
139 ) -> Box<dyn FnMut(CommandEnum) + Send + 'static>
140 + Send
141 + Sync
142 + 'static,
143 ) {
144 self.entries.insert(
145 type_name.into(),
146 Box::new(ClosureCtor::new_send(drain, make_handler)),
147 );
148 }
149
150 pub fn construct(
152 &self,
153 module: &ModuleDef,
154 automaton_defs: &[AutomatonDef],
155 system: &Arc<ActorSystem>,
156 graph_ref: &ActorRef<CommandEnum>,
157 #[cfg(feature = "debug")] inspector: Option<&PatchbayInspector>,
158 ) -> Result<ActorRef<CommandEnum>, ModuleError> {
159 let type_name = module.type_name();
160 self.entries
161 .get(type_name)
162 .ok_or_else(|| ModuleError::UnknownType(type_name.to_string()))
163 .and_then(|ctor| {
164 ctor.construct(
165 module,
166 automaton_defs,
167 system,
168 graph_ref,
169 #[cfg(feature = "debug")]
170 inspector,
171 )
172 })
173 }
174
175 pub fn contains(&self, type_name: &str) -> bool {
177 self.entries.contains_key(type_name)
178 }
179 pub fn len(&self) -> usize {
181 self.entries.len()
182 }
183 pub fn is_empty(&self) -> bool {
185 self.entries.is_empty()
186 }
187}
188
189impl Default for ModuleFactory {
190 fn default() -> Self {
191 Self::new()
192 }
193}
194
195type ErasedCtorFn = Arc<
200 dyn Fn(
201 &str,
202 &HashMap<String, ParamValue>,
203 &ActorRef<CommandEnum>,
204 ) -> Box<dyn FnMut(CommandEnum) + 'static>
205 + Send
206 + Sync,
207>;
208
209type SendCtorFn = Arc<
210 dyn Fn(
211 &str,
212 &HashMap<String, ParamValue>,
213 &ActorRef<CommandEnum>,
214 ) -> Box<dyn FnMut(CommandEnum) + Send + 'static>
215 + Send
216 + Sync,
217>;
218
219enum ClosureCtorKind {
220 Erased { f: ErasedCtorFn },
221 Send { f: SendCtorFn },
222}
223
224struct ClosureCtor {
225 drain: Drain,
226 kind: ClosureCtorKind,
227}
228
229impl ClosureCtor {
230 fn new_erased(
231 drain: Drain,
232 f: impl Fn(
233 &str,
234 &HashMap<String, ParamValue>,
235 &ActorRef<CommandEnum>,
236 ) -> Box<dyn FnMut(CommandEnum) + 'static>
237 + Send
238 + Sync
239 + 'static,
240 ) -> Self {
241 Self {
242 drain,
243 kind: ClosureCtorKind::Erased { f: Arc::new(f) },
244 }
245 }
246
247 fn new_send(
248 drain: Drain,
249 f: impl Fn(
250 &str,
251 &HashMap<String, ParamValue>,
252 &ActorRef<CommandEnum>,
253 ) -> Box<dyn FnMut(CommandEnum) + Send + 'static>
254 + Send
255 + Sync
256 + 'static,
257 ) -> Self {
258 Self {
259 drain,
260 kind: ClosureCtorKind::Send { f: Arc::new(f) },
261 }
262 }
263}
264
265impl ModuleConstructor for ClosureCtor {
266 fn type_name(&self) -> &'static str {
267 ""
268 }
269 fn construct(
270 &self,
271 module: &ModuleDef,
272 _automaton_defs: &[AutomatonDef],
273 system: &Arc<ActorSystem>,
274 graph_ref: &ActorRef<CommandEnum>,
275 #[cfg(feature = "debug")] _inspector: Option<&PatchbayInspector>,
276 ) -> Result<ActorRef<CommandEnum>, ModuleError> {
277 let ModuleDef::Custom {
278 type_name: _,
279 params,
280 } = module
281 else {
282 return Err(ModuleError::ConstructionFailed(
283 "ClosureCtor only supports Custom modules".into(),
284 ));
285 };
286
287 let id_owned = String::new(); let name = "custom".to_string();
289 let graph_ref = graph_ref.clone();
290 let params = params.clone();
291
292 match (&self.kind, self.drain) {
293 (ClosureCtorKind::Erased { f }, Drain::OsThread { interval_ms }) => {
294 let f = f.clone();
295 let actor_ref = system.spawn_detached(
296 &name,
297 move || f(&id_owned, ¶ms, &graph_ref),
298 interval_ms,
299 );
300 Ok(actor_ref)
301 }
302 (ClosureCtorKind::Send { f }, Drain::OsThread { interval_ms }) => {
303 let f = f.clone();
304 let actor_ref = system.spawn_detached(
305 &name,
306 move || f(&id_owned, ¶ms, &graph_ref),
307 interval_ms,
308 );
309 Ok(actor_ref)
310 }
311 (ClosureCtorKind::Send { f }, Drain::TokioTask { interval_ms }) => {
312 let f = f.clone();
313 let actor_ref = system.spawn_detached_tokio(
314 &name,
315 move || f(&id_owned, ¶ms, &graph_ref),
316 interval_ms,
317 );
318 Ok(actor_ref)
319 }
320 (ClosureCtorKind::Erased { .. }, Drain::TokioTask { .. }) => {
321 Err(ModuleError::ConstructionFailed(
322 "TokioTask drain requires a Send handler; use register_fn_send()".into(),
323 ))
324 }
325 (ClosureCtorKind::Erased { .. }, Drain::IoCallback) => {
326 Err(ModuleError::ConstructionFailed(
327 "IoCallback drain not supported via register_fn(); use Graph constructor directly".into(),
328 ))
329 }
330 (ClosureCtorKind::Send { .. }, Drain::IoCallback) => {
331 Err(ModuleError::ConstructionFailed(
332 "IoCallback drain not supported via register_fn_send(); use Graph constructor directly".into(),
333 ))
334 }
335 }
336 }
337 fn clone_box(&self) -> Box<dyn ModuleConstructor> {
338 match &self.kind {
339 ClosureCtorKind::Erased { f } => Box::new(ClosureCtor {
340 drain: self.drain,
341 kind: ClosureCtorKind::Erased { f: f.clone() },
342 }),
343 ClosureCtorKind::Send { f } => Box::new(ClosureCtor {
344 drain: self.drain,
345 kind: ClosureCtorKind::Send { f: f.clone() },
346 }),
347 }
348 }
349}