Skip to main content

rill_patchbay/automaton/
factory.rs

1//! Automaton factory — type-registry for automaton construction.
2
3use std::collections::HashMap;
4use std::fmt;
5use std::sync::Arc;
6
7use rill_core::queues::CommandEnum;
8use rill_core::traits::{NodeId, ParamValue, Params};
9use rill_core_actor::ActorRef;
10
11use crate::engine::{BoxedModule, ParameterMapping};
12/// Errors returned by automaton construction via the type-registry.
13#[derive(Debug, Clone)]
14pub enum FactoryError {
15    /// The requested automaton type name was not registered.
16    UnknownType(String),
17}
18
19impl fmt::Display for FactoryError {
20    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
21        match self {
22            Self::UnknownType(t) => write!(f, "unknown automaton type: {t}"),
23        }
24    }
25}
26/// Destination descriptor for a servo-driven parameter automation.
27#[derive(Debug, Clone)]
28pub struct ServoTarget {
29    /// The graph node that receives the parameter value.
30    pub target_node: NodeId,
31    /// The parameter name on the target node to update.
32    pub target_param: String,
33    /// How the automaton output maps to the parameter.
34    pub mapping: ParameterMapping,
35    /// Minimum output clamp value.
36    pub min: f64,
37    /// Maximum output clamp value.
38    pub max: f64,
39    /// Optional value table for index-based automata.
40    pub table: Option<Vec<ParamValue>>,
41}
42/// Constructs automaton modules from type name, params, and target.
43pub trait AutomatonConstructor: Send + Sync {
44    /// Returns the string key used to register this constructor.
45    fn type_name(&self) -> &'static str;
46    /// Builds a boxed module from the given id, params, and servo target.
47    fn construct(&self, id: &str, params: &Params, target: &ServoTarget) -> BoxedModule;
48    /// Optionally spawns an async green-thread for automata that run independently of the graph clock.
49    fn spawn_async(
50        &self,
51        _id: &str,
52        _params: &Params,
53        _target: &ServoTarget,
54        _interval_ms: f64,
55        _command_queue: ActorRef<CommandEnum>,
56    ) -> Option<tokio::task::JoinHandle<()>> {
57        None
58    }
59    /// Returns a heap-allocated clone of this constructor.
60    fn clone_box(&self) -> Box<dyn AutomatonConstructor>;
61}
62/// Registry that maps automaton type names to constructors.
63pub struct AutomatonFactory {
64    entries: HashMap<&'static str, Box<dyn AutomatonConstructor>>,
65}
66
67impl AutomatonFactory {
68    /// Creates an empty automaton factory.
69    pub fn new() -> Self {
70        Self {
71            entries: HashMap::new(),
72        }
73    }
74    /// Adds a typed constructor to the registry, keyed by its type name.
75    pub fn register(&mut self, ctor: impl AutomatonConstructor + 'static) {
76        self.entries.insert(ctor.type_name(), Box::new(ctor));
77    }
78    /// Registers a closure-based constructor under the given type name.
79    pub fn register_fn(
80        &mut self,
81        type_name: &'static str,
82        f: impl Fn(&str, &Params, &ServoTarget) -> BoxedModule + Send + Sync + 'static,
83    ) {
84        self.entries
85            .insert(type_name, Box::new(ClosureCtor::new(type_name, f)));
86    }
87    /// Looks up a type name and constructs the corresponding automaton module.
88    pub fn construct(
89        &self,
90        type_name: &str,
91        id: &str,
92        params: &Params,
93        target: &ServoTarget,
94    ) -> Result<BoxedModule, FactoryError> {
95        self.entries
96            .get(type_name)
97            .ok_or_else(|| FactoryError::UnknownType(type_name.to_string()))
98            .map(|ctor| ctor.construct(id, params, target))
99    }
100    /// Looks up a type name and spawns its async green-thread if supported.
101    pub fn spawn_async(
102        &self,
103        type_name: &str,
104        id: &str,
105        params: &Params,
106        target: &ServoTarget,
107        interval_ms: f64,
108        command_queue: ActorRef<CommandEnum>,
109    ) -> Result<Option<tokio::task::JoinHandle<()>>, FactoryError> {
110        self.entries
111            .get(type_name)
112            .ok_or_else(|| FactoryError::UnknownType(type_name.to_string()))
113            .map(|ctor| ctor.spawn_async(id, params, target, interval_ms, command_queue))
114    }
115    /// Checks whether a type name is registered.
116    pub fn contains(&self, type_name: &str) -> bool {
117        self.entries.contains_key(type_name)
118    }
119    /// Lists all registered automaton type names.
120    pub fn list_types(&self) -> Vec<&'static str> {
121        self.entries.keys().copied().collect()
122    }
123    /// Returns the number of registered automaton types.
124    pub fn len(&self) -> usize {
125        self.entries.len()
126    }
127    /// Returns true if no automaton types are registered.
128    pub fn is_empty(&self) -> bool {
129        self.entries.is_empty()
130    }
131}
132
133impl Default for AutomatonFactory {
134    fn default() -> Self {
135        Self::new()
136    }
137}
138
139type ClosureCtorFn = Arc<dyn Fn(&str, &Params, &ServoTarget) -> BoxedModule + Send + Sync>;
140
141struct ClosureCtor {
142    type_name: &'static str,
143    f: ClosureCtorFn,
144}
145
146impl ClosureCtor {
147    fn new(
148        type_name: &'static str,
149        f: impl Fn(&str, &Params, &ServoTarget) -> BoxedModule + Send + Sync + 'static,
150    ) -> Self {
151        Self {
152            type_name,
153            f: Arc::new(f),
154        }
155    }
156}
157
158impl AutomatonConstructor for ClosureCtor {
159    fn type_name(&self) -> &'static str {
160        self.type_name
161    }
162    fn construct(&self, id: &str, params: &Params, target: &ServoTarget) -> BoxedModule {
163        (self.f)(id, params, target)
164    }
165    fn clone_box(&self) -> Box<dyn AutomatonConstructor> {
166        Box::new(ClosureCtor {
167            type_name: self.type_name,
168            f: self.f.clone(),
169        })
170    }
171}