mirror of
https://github.com/BeamMP/BeamMP-Server.git
synced 2026-04-05 07:16:18 +00:00
144 lines
4.6 KiB
Rust
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;
|
|
}
|
|
}
|