use crate::runtime::{
generate_spec_file, MessageHandler, NodeSpec, RegisteredNodeSpec, Result,
};
use once_cell::sync::Lazy;
use parking_lot::RwLock;
use serde_json::Value;
use std::collections::HashMap;
pub type NodeCreator = Box<dyn Fn() -> Box<dyn MessageHandler> + Send + Sync>;
pub type SpecGenerator = Box<dyn Fn() -> RegisteredNodeSpec + Send + Sync>;
static REGISTRY: Lazy<RwLock<Registry>> = Lazy::new(|| RwLock::new(Registry::new()));
struct Registry {
creators: HashMap<String, NodeCreator>,
specs: HashMap<String, SpecGenerator>,
}
impl Registry {
fn new() -> Self {
Self {
creators: HashMap::new(),
specs: HashMap::new(),
}
}
}
pub fn register_node<N>()
where
N: MessageHandler + NodeSpec + Default + serde::de::DeserializeOwned + 'static,
{
let node_id = N::node_id().to_string();
let creator: NodeCreator = Box::new(|| Box::new(N::default()) as Box<dyn MessageHandler>);
let spec_gen: SpecGenerator = Box::new(|| RegisteredNodeSpec {
id: N::node_id().to_string(),
name: N::node_name().to_string(),
icon: N::node_icon().to_string(),
color: N::node_color().to_string(),
inputs: N::inputs(),
outputs: N::outputs(),
tool: N::tool_spec(),
properties: N::properties(),
});
let mut registry = REGISTRY.write();
registry.creators.insert(node_id.clone(), creator);
registry.specs.insert(node_id, spec_gen);
}
pub fn registered_node_specs() -> Vec<RegisteredNodeSpec> {
let registry = REGISTRY.read();
registry.specs.values().map(|gen| gen()).collect()
}
pub fn get_node_creator(node_id: &str) -> Option<Box<dyn MessageHandler>> {
let registry = REGISTRY.read();
registry.creators.get(node_id).map(|creator| creator())
}
#[macro_export]
macro_rules! register_nodes {
($($node:ty),* $(,)?) => {
$(
$crate::runtime::register_node::<$node>();
)*
};
}
pub use register_nodes;
pub fn read_config_file() -> Value {
let paths = ["config.json", "../config.json"];
for path in &paths {
if let Ok(content) = std::fs::read_to_string(path) {
if let Ok(config) = serde_json::from_str(&content) {
return config;
}
}
}
tracing::error!("Failed to read config.json");
Value::Null
}
pub async fn start() {
let args: Vec<String> = std::env::args().collect();
let config = read_config_file();
if args.len() > 1 {
match args[1].as_str() {
"-s" => {
let name = config["name"].as_str().unwrap_or("unknown");
let version = config["version"].as_str().unwrap_or("0.0.0");
let spec = generate_spec_file(name, version);
println!("{}", spec);
return;
}
"-a" => {
let namespace = config["namespace"].as_str().unwrap_or("");
tracing::info!("Starting in attach mode for namespace: {}", namespace);
if let Err(e) = run_plugin_server(true, namespace).await {
tracing::error!("Plugin server error: {}", e);
}
return;
}
_ => {}
}
}
if let Err(e) = run_plugin_server(false, "").await {
tracing::error!("Plugin server error: {}", e);
}
}
async fn run_plugin_server(attach_mode: bool, namespace: &str) -> Result<()> {
use tokio::signal;
use tokio::sync::oneshot;
tracing_subscriber::fmt()
.with_env_filter("robomotion=info")
.with_writer(std::io::stderr)
.init();
std::env::set_var("robomotion_plugin", "6e80b1a2cf26c5935ed7b6e5be77fe218d5f358d");
let (shutdown_tx, shutdown_rx) = oneshot::channel::<()>();
tokio::spawn(async move {
let _ = signal::ctrl_c().await;
let _ = shutdown_tx.send(());
});
if attach_mode {
tracing::info!("Attach mode started for namespace: {}", namespace);
}
let _ = shutdown_rx.await;
tracing::info!("Shutting down...");
Ok(())
}