From 4f52e3054529fd90f53e7423fb0805ad95545198 Mon Sep 17 00:00:00 2001 From: Luuk van Oijen Date: Sun, 12 Nov 2023 21:26:23 +0100 Subject: [PATCH] wip lua event stuff --- Resources/Server/TestPlugin/main.lua | 3 +- src/server/mod.rs | 13 +++++++ src/server/plugins/backend_lua.rs | 57 ++++++++++++++++++++++++++-- src/server/plugins/mod.rs | 45 ++++++++++++++++++++-- 4 files changed, 110 insertions(+), 8 deletions(-) diff --git a/Resources/Server/TestPlugin/main.lua b/Resources/Server/TestPlugin/main.lua index 0f5f28e..0c50d79 100644 --- a/Resources/Server/TestPlugin/main.lua +++ b/Resources/Server/TestPlugin/main.lua @@ -1,7 +1,8 @@ print("test from lua") +print("Player count: " .. MP.GetPlayerCount()) function onPluginLoaded() print("HI!") end -MP:RegisterEventHandler("onPluginLoaded", "onPluginLoaded") +MP.RegisterEventHandler("onPluginLoaded", "onPluginLoaded") diff --git a/src/server/mod.rs b/src/server/mod.rs index becd44b..dbf131e 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -300,6 +300,19 @@ impl Server { self.broadcast(Packet::Raw(RawPacket::from_str(&data)), None).await; } + // Receive plugin events and process them + for plugin in &mut self.plugins { + for event in plugin.get_events() { + debug!("event: {:?}", event); + // TODO: Error handling (?) + match event { + ServerBoundPluginEvent::PluginLoaded => plugin.send_event(PluginBoundPluginEvent::CallEventHandler((ScriptEvent::OnPluginLoaded, Vec::new()))).await, + ServerBoundPluginEvent::RequestPlayerCount(responder) => { let _ = responder.send(PluginBoundPluginEvent::PlayerCount(self.clients.len())); } + _ => {}, + } + } + } + Ok(()) } diff --git a/src/server/plugins/backend_lua.rs b/src/server/plugins/backend_lua.rs index 04123b4..f649834 100644 --- a/src/server/plugins/backend_lua.rs +++ b/src/server/plugins/backend_lua.rs @@ -1,8 +1,18 @@ -use super::{Backend, ServerBoundPluginEvent}; +use super::{ + Backend, + ServerBoundPluginEvent, + PluginBoundPluginEvent, + ScriptEvent, + Argument +}; + use std::sync::Arc; -use tokio::sync::mpsc::Sender; + +use tokio::sync::mpsc::{Sender, Receiver}; +use tokio::sync::oneshot; + use mlua::prelude::*; -use mlua::{UserData, UserDataMethods, Value}; +use mlua::{UserData, UserDataMethods, Value, Function, Variadic}; #[derive(Clone)] struct Context { @@ -28,13 +38,32 @@ impl<'lua> FromLua<'lua> for Context { impl UserData for Context { fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) { - methods.add_method("RegisterEventHandler", |lua, _, (event_name, handler_name): (String, String)| { + // Implement all the API functions here + + methods.add_function("RegisterEventHandler", |lua, (event_name, handler_name): (String, String)| { debug!("Event handler registered: {} (EVENT) = {} (LUA)", event_name, handler_name); let me: Context = lua.globals().get("MP")?; // TODO: Figure out how to handle these errors (?) let _ = me.tx.blocking_send(ServerBoundPluginEvent::RegisterEventHandler((event_name, handler_name))); Ok(()) }); + + methods.add_function("GetPlayerCount", |lua, ()| { + let me: Context = lua.globals().get("MP")?; + let (tx, rx) = oneshot::channel(); + let r = me.tx.blocking_send(ServerBoundPluginEvent::RequestPlayerCount(tx)); + debug!("{:?}", r); + let message = rx.blocking_recv(); + if let Ok(message) = message { + if let PluginBoundPluginEvent::PlayerCount(player_count) = message { + Ok(player_count) + } else { + unreachable!() // This really should never be reachable + } + } else { + todo!("Receiving a response from the server failed! How?") + } + }); } } @@ -79,4 +108,24 @@ impl Backend for BackendLua { Ok(()) } + + fn call_event_handler(&mut self, event: ScriptEvent, args: Vec) { + let event_name = match event { + ScriptEvent::OnPluginLoaded => "onPluginLoaded", + }; + + let func: LuaResult = self.lua.globals().get(event_name); + if let Ok(func) = func { + let mapped_args = args.into_iter().map(|arg| { + match arg { + Argument::String(s) => if let Ok(lua_str) = self.lua.create_string(&s) { Some(Value::String(lua_str)) } else { None }, + Argument::Boolean(b) => Some(Value::Boolean(b)), + Argument::Number(f) => Some(Value::Number(f as f64)), + } + }).filter(|v| v.is_some()); + if let Err(e) = func.call::<_, ()>(Variadic::from_iter(mapped_args)) { + error!("[LUA] {}", e); + } + } + } } diff --git a/src/server/plugins/mod.rs b/src/server/plugins/mod.rs index 4952018..ca7c93c 100644 --- a/src/server/plugins/mod.rs +++ b/src/server/plugins/mod.rs @@ -4,23 +4,36 @@ use std::sync::Arc; use tokio::runtime::Runtime; use tokio::sync::Mutex; 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<()> { Ok(()) } + fn load_api(&mut self, tx: Arc>) -> anyhow::Result<()>; + + fn call_event_handler(&mut self, event: ScriptEvent, args: Vec); } +// 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)] pub enum Argument { + String(String), + Boolean(bool), Number(f32), } +#[derive(Debug)] +pub enum ScriptEvent { + OnPluginLoaded, +} + #[derive(Debug)] pub enum PluginBoundPluginEvent { - CallEventHandler((String, Vec)) + CallEventHandler((ScriptEvent, Vec)), + PlayerCount(usize), } #[derive(Debug)] @@ -29,6 +42,7 @@ pub enum ServerBoundPluginEvent { /// Arguments: (event name, handler function name) RegisterEventHandler((String, String)), + RequestPlayerCount(oneshot::Sender), } pub struct Plugin { @@ -55,8 +69,14 @@ impl Plugin { loop { if let Some(message) = pb_rx.blocking_recv() { - debug!("Received message: {:?}", message); + match message { + PluginBoundPluginEvent::CallEventHandler((event, args)) => { + backend.call_event_handler(event, args); + }, + _ => {}, + } } else { + error!("Event receiver has closed!"); // TODO: We probably want to display the plugin name here too lol return; } } @@ -67,4 +87,23 @@ impl Plugin { rx: sb_rx, }) } + + // 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; + } }