use std::collections::HashMap;
use std::fmt;
use std::sync::Arc;
use rill_core::queues::CommandEnum;
use rill_core::traits::ParamValue;
use rill_core_actor::{ActorRef, ActorSystem};
use crate::module_def::{AutomatonDef, ModuleDef};
#[derive(Debug, Clone)]
pub enum ModuleError {
UnknownType(String),
ConstructionFailed(String),
}
impl fmt::Display for ModuleError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::UnknownType(t) => write!(f, "unknown module type: {t}"),
Self::ConstructionFailed(e) => write!(f, "module construction failed: {e}"),
}
}
}
pub trait ModuleConstructor: Send + Sync {
fn type_name(&self) -> &'static str;
fn construct(
&self,
module: &ModuleDef,
automaton_defs: &[AutomatonDef],
system: &Arc<ActorSystem>,
graph_ref: &ActorRef<CommandEnum>,
) -> Result<ActorRef<CommandEnum>, ModuleError>;
fn clone_box(&self) -> Box<dyn ModuleConstructor>;
}
#[derive(Debug, Clone, Copy)]
pub enum Drain {
OsThread {
interval_ms: u64,
},
TokioTask {
interval_ms: u64,
},
IoCallback,
}
pub struct ModuleFactory {
entries: HashMap<String, Box<dyn ModuleConstructor>>,
}
impl ModuleFactory {
pub fn new() -> Self {
Self {
entries: HashMap::new(),
}
}
pub fn register(&mut self, ctor: impl ModuleConstructor + 'static) {
self.entries
.insert(ctor.type_name().to_string(), Box::new(ctor));
}
#[allow(dead_code)]
pub fn register_fn(
&mut self,
type_name: impl Into<String>,
drain: Drain,
make_handler: impl Fn(
&str,
&HashMap<String, ParamValue>,
&ActorRef<CommandEnum>,
) -> Box<dyn FnMut(CommandEnum) + 'static>
+ Send
+ Sync
+ 'static,
) {
self.entries.insert(
type_name.into(),
Box::new(ClosureCtor::new_erased(drain, make_handler)),
);
}
#[allow(dead_code)]
pub fn register_fn_send(
&mut self,
type_name: impl Into<String>,
drain: Drain,
make_handler: impl Fn(
&str,
&HashMap<String, ParamValue>,
&ActorRef<CommandEnum>,
) -> Box<dyn FnMut(CommandEnum) + Send + 'static>
+ Send
+ Sync
+ 'static,
) {
self.entries.insert(
type_name.into(),
Box::new(ClosureCtor::new_send(drain, make_handler)),
);
}
pub fn construct(
&self,
module: &ModuleDef,
automaton_defs: &[AutomatonDef],
system: &Arc<ActorSystem>,
graph_ref: &ActorRef<CommandEnum>,
) -> Result<ActorRef<CommandEnum>, ModuleError> {
let type_name = module.type_name();
self.entries
.get(type_name)
.ok_or_else(|| ModuleError::UnknownType(type_name.to_string()))
.and_then(|ctor| ctor.construct(module, automaton_defs, system, graph_ref))
}
pub fn contains(&self, type_name: &str) -> bool {
self.entries.contains_key(type_name)
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
}
impl Default for ModuleFactory {
fn default() -> Self {
Self::new()
}
}
type ErasedCtorFn = Arc<
dyn Fn(
&str,
&HashMap<String, ParamValue>,
&ActorRef<CommandEnum>,
) -> Box<dyn FnMut(CommandEnum) + 'static>
+ Send
+ Sync,
>;
type SendCtorFn = Arc<
dyn Fn(
&str,
&HashMap<String, ParamValue>,
&ActorRef<CommandEnum>,
) -> Box<dyn FnMut(CommandEnum) + Send + 'static>
+ Send
+ Sync,
>;
enum ClosureCtorKind {
Erased { f: ErasedCtorFn },
Send { f: SendCtorFn },
}
struct ClosureCtor {
drain: Drain,
kind: ClosureCtorKind,
}
impl ClosureCtor {
fn new_erased(
drain: Drain,
f: impl Fn(
&str,
&HashMap<String, ParamValue>,
&ActorRef<CommandEnum>,
) -> Box<dyn FnMut(CommandEnum) + 'static>
+ Send
+ Sync
+ 'static,
) -> Self {
Self {
drain,
kind: ClosureCtorKind::Erased { f: Arc::new(f) },
}
}
fn new_send(
drain: Drain,
f: impl Fn(
&str,
&HashMap<String, ParamValue>,
&ActorRef<CommandEnum>,
) -> Box<dyn FnMut(CommandEnum) + Send + 'static>
+ Send
+ Sync
+ 'static,
) -> Self {
Self {
drain,
kind: ClosureCtorKind::Send { f: Arc::new(f) },
}
}
}
impl ModuleConstructor for ClosureCtor {
fn type_name(&self) -> &'static str {
""
}
fn construct(
&self,
module: &ModuleDef,
_automaton_defs: &[AutomatonDef],
system: &Arc<ActorSystem>,
graph_ref: &ActorRef<CommandEnum>,
) -> Result<ActorRef<CommandEnum>, ModuleError> {
let ModuleDef::Custom {
type_name: _,
params,
} = module
else {
return Err(ModuleError::ConstructionFailed(
"ClosureCtor only supports Custom modules".into(),
));
};
let id_owned = String::new(); let name = "custom".to_string();
let graph_ref = graph_ref.clone();
let params = params.clone();
match (&self.kind, self.drain) {
(ClosureCtorKind::Erased { f }, Drain::OsThread { interval_ms }) => {
let f = f.clone();
let actor_ref = system.spawn_detached(
&name,
move || f(&id_owned, ¶ms, &graph_ref),
interval_ms,
);
Ok(actor_ref)
}
(ClosureCtorKind::Send { f }, Drain::OsThread { interval_ms }) => {
let f = f.clone();
let actor_ref = system.spawn_detached(
&name,
move || f(&id_owned, ¶ms, &graph_ref),
interval_ms,
);
Ok(actor_ref)
}
(ClosureCtorKind::Send { f }, Drain::TokioTask { interval_ms }) => {
let f = f.clone();
let actor_ref = system.spawn_detached_tokio(
&name,
move || f(&id_owned, ¶ms, &graph_ref),
interval_ms,
);
Ok(actor_ref)
}
(ClosureCtorKind::Erased { .. }, Drain::TokioTask { .. }) => {
Err(ModuleError::ConstructionFailed(
"TokioTask drain requires a Send handler; use register_fn_send()".into(),
))
}
(ClosureCtorKind::Erased { .. }, Drain::IoCallback) => {
Err(ModuleError::ConstructionFailed(
"IoCallback drain not supported via register_fn(); use Graph constructor directly".into(),
))
}
(ClosureCtorKind::Send { .. }, Drain::IoCallback) => {
Err(ModuleError::ConstructionFailed(
"IoCallback drain not supported via register_fn_send(); use Graph constructor directly".into(),
))
}
}
}
fn clone_box(&self) -> Box<dyn ModuleConstructor> {
match &self.kind {
ClosureCtorKind::Erased { f } => Box::new(ClosureCtor {
drain: self.drain,
kind: ClosureCtorKind::Erased { f: f.clone() },
}),
ClosureCtorKind::Send { f } => Box::new(ClosureCtor {
drain: self.drain,
kind: ClosureCtorKind::Send { f: f.clone() },
}),
}
}
}