Files
BeamMP-Server/src/server/plugins/mod.rs
2023-11-22 11:02:53 +01:00

144 lines
4.6 KiB
Rust

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<Sender<ServerBoundPluginEvent>>) -> anyhow::Result<()>;
fn call_event_handler(&mut self, event: ScriptEvent, resp: Option<oneshot::Sender<Argument>>);
}
// 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<String, Argument>),
}
#[derive(Debug)]
pub struct PlayerIdentifiers {
pub ip: String,
pub beammp_id: String,
}
impl PlayerIdentifiers {
pub fn to_map(&self) -> HashMap<String, Argument> {
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<oneshot::Sender<Argument>>)),
PlayerCount(usize),
Players(HashMap<u8, String>),
PlayerIdentifiers(PlayerIdentifiers),
}
#[derive(Debug)]
pub enum ServerBoundPluginEvent {
PluginLoaded,
RequestPlayerCount(oneshot::Sender<PluginBoundPluginEvent>),
RequestPlayers(oneshot::Sender<PluginBoundPluginEvent>),
RequestPlayerIdentifiers((u8, oneshot::Sender<PluginBoundPluginEvent>)),
}
pub struct Plugin {
runtime: Runtime,
tx: Sender<PluginBoundPluginEvent>,
rx: Receiver<ServerBoundPluginEvent>,
}
impl Plugin {
pub fn new(mut backend: Box<dyn Backend>, src: String) -> anyhow::Result<Self> {
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<ServerBoundPluginEvent> {
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;
}
}