pub mod backend_lua; use std::sync::Arc; use std::collections::HashMap; use tokio::runtime::Runtime; use tokio::sync::mpsc::{self, Sender, Receiver}; use tokio::sync::oneshot; /// NOTE: Send is required as the backend is constructed on the main thread and sent over. /// Even if we construct it inside the runtime however, because of tokio, we would // still have to require Send as the runtime might run on different threads (?) pub trait Backend: Send { fn load(&mut self, code: String) -> anyhow::Result<()>; fn load_api(&mut self, tx: Arc>) -> anyhow::Result<()>; fn call_event_handler(&mut self, event: ScriptEvent, resp: Option>); } // TODO: This is quite focused on Lua right now, perhaps in the future we want to modify this list // to be more versatile? #[derive(Debug, Clone)] pub enum Argument { String(String), Boolean(bool), Number(f32), Integer(i64), Table(HashMap), } #[derive(Debug)] pub struct PlayerIdentifiers { pub ip: String, pub beammp_id: String, } impl PlayerIdentifiers { pub fn to_map(&self) -> HashMap { let mut m = HashMap::new(); m.insert(String::from("ip"), Argument::String(self.ip.clone())); m.insert(String::from("beammp"), Argument::String(self.beammp_id.clone())); m } } #[derive(Debug)] pub enum ScriptEvent { OnPluginLoaded, OnShutdown, OnPlayerAuthenticated { name: String, role: String, is_guest: bool, identifiers: PlayerIdentifiers }, OnPlayerDisconnect { pid: u8, name: String }, } #[derive(Debug)] pub enum PluginBoundPluginEvent { None, CallEventHandler((ScriptEvent, Option>)), PlayerCount(usize), Players(HashMap), PlayerIdentifiers(PlayerIdentifiers), } #[derive(Debug)] pub enum ServerBoundPluginEvent { PluginLoaded, RequestPlayerCount(oneshot::Sender), RequestPlayers(oneshot::Sender), RequestPlayerIdentifiers((u8, oneshot::Sender)), } pub struct Plugin { runtime: Runtime, tx: Sender, rx: Receiver, } impl Plugin { pub fn new(mut backend: Box, src: String) -> anyhow::Result { let runtime = Runtime::new().expect("Failed to create a tokio Runtime!"); let (pb_tx, mut pb_rx) = mpsc::channel(1_000); let (sb_tx, sb_rx) = mpsc::channel(1_000); let sb_tx = Arc::new(sb_tx); runtime.spawn_blocking(move || { if backend.load_api(sb_tx.clone()).is_ok() { if backend.load(src).is_ok() { if sb_tx.blocking_send(ServerBoundPluginEvent::PluginLoaded).is_err() { error!("Plugin communication channels somehow already closed!"); return; } } } loop { if let Some(message) = pb_rx.blocking_recv() { match message { PluginBoundPluginEvent::CallEventHandler((event, resp)) => { backend.call_event_handler(event, resp); }, _ => {}, } } else { error!("Event receiver has closed!"); // TODO: We probably want to display the plugin name here too lol return; } } }); Ok(Self { runtime, tx: pb_tx, rx: sb_rx, }) } pub async fn close(mut self) { let (tx, mut rx) = oneshot::channel(); self.send_event(PluginBoundPluginEvent::CallEventHandler((ScriptEvent::OnShutdown, Some(tx)))).await; let _ = rx.await; // We just wait for it to finish shutting down self.runtime.shutdown_background(); } // TODO: For performance I think we can turn this into an iterator instead of first allocating // a full vector? pub fn get_events(&mut self) -> Vec { let mut events = Vec::new(); loop { match self.rx.try_recv() { Ok(event) => events.push(event), Err(mpsc::error::TryRecvError::Disconnected) => break, // TODO: This means the runtime is dead!!! Handle this!!! Err(_) => break, } } events } // TODO: Handle error when connection is closed, as it means the runtime is down pub async fn send_event(&self, event: PluginBoundPluginEvent) { let _ = self.tx.send(event).await; } }