Skip to main content

rill_patchbay/
module_factory.rs

1//! Module factory — type-registry for modular rack module construction.
2//!
3//! `ModuleFactory` is the single creation point for all rack modules:
4//! Servo, Sensor, Graph, and Custom. Each archetype registers a
5//! [`ModuleConstructor`] that receives a [`ModuleDef`] descriptor
6//! and returns an [`ActorRef<CommandEnum>`] for the rack actor fan-out.
7
8use 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/// Errors returned by module construction via the type-registry.
19#[derive(Debug, Clone)]
20pub enum ModuleError {
21    /// The requested module type name was not registered.
22    UnknownType(String),
23    /// Construction failed with a user-readable reason.
24    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
36/// Constructs rack modules from a [`ModuleDef`] descriptor.
37///
38/// Each constructor receives the full module descriptor plus the list of
39/// automaton definitions needed by `Servo` modules. Custom and registered
40/// constructors return an [`ActorRef<CommandEnum>`] that the rack actor
41/// uses for fan-out.
42pub trait ModuleConstructor: Send + Sync {
43    /// Returns the string key used to register this constructor.
44    fn type_name(&self) -> &'static str;
45
46    /// Build the module and return its actor handle.
47    ///
48    /// `automaton_defs` provides the automaton definitions referenced
49    /// by `ModuleDef::Servo`. Other module types ignore this parameter.
50    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    /// Returns a heap-allocated clone of this constructor.
59    fn clone_box(&self) -> Box<dyn ModuleConstructor>;
60}
61
62/// How the actor's drain loop is spawned.
63#[derive(Debug, Clone, Copy)]
64pub enum Drain {
65    /// OS thread with periodic drain (handler: !Send).
66    OsThread {
67        /// Drain interval in milliseconds.
68        interval_ms: u64,
69    },
70    /// Tokio task with periodic drain (handler: Send).
71    TokioTask {
72        /// Drain interval in milliseconds.
73        interval_ms: u64,
74    },
75    /// I/O callback drain — handler drained inline in the backend callback.
76    /// Factory spawns the I/O thread, construction closures run inside it.
77    IoCallback,
78}
79/// Registry that maps module type names to constructors.
80pub struct ModuleFactory {
81    entries: HashMap<String, Box<dyn ModuleConstructor>>,
82}
83
84impl ModuleFactory {
85    /// Creates an empty module factory.
86    pub fn new() -> Self {
87        Self {
88            entries: HashMap::new(),
89        }
90    }
91    /// Adds a typed constructor to the registry, keyed by its type name.
92    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    /// Register a closure-based constructor (handler: !Send).
98    ///
99    /// `make_handler` receives module params and the graph handle, and returns
100    /// the message handler closure. The factory calls it **inside the drain thread**,
101    /// so the handler does not need `Send`.
102    #[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    /// Register a closure-based constructor (handler: `Send`, for [`Drain::TokioTask`]).
123    ///
124    /// The returned handler must be `Send` so it can be stored in a tokio future.
125    #[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    /// Looks up a type name and constructs the corresponding module actor.
146    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    /// Checks whether a type name is registered.
161    pub fn contains(&self, type_name: &str) -> bool {
162        self.entries.contains_key(type_name)
163    }
164    /// Returns the number of registered module types.
165    pub fn len(&self) -> usize {
166        self.entries.len()
167    }
168    /// Returns true if no module types are registered.
169    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
180// ============================================================================
181// ClosureCtor
182// ============================================================================
183
184type 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(); // Custom modules use type_name as id
272        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, &params, &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, &params, &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, &params, &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}