From 656b916af19b07c656890784e5578aaa60e535da Mon Sep 17 00:00:00 2001 From: vyPal Date: Sun, 24 Nov 2024 19:56:03 +0100 Subject: [PATCH 01/62] Add pumpkin-api crate for API definitions --- pumpkin-api/Cargo.toml | 6 ++++++ pumpkin-api/src/lib.rs | 45 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 pumpkin-api/Cargo.toml create mode 100644 pumpkin-api/src/lib.rs diff --git a/pumpkin-api/Cargo.toml b/pumpkin-api/Cargo.toml new file mode 100644 index 000000000..97ae22386 --- /dev/null +++ b/pumpkin-api/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "pumpkin-api" +version.workspace = true +edition.workspace = true + +[dependencies] \ No newline at end of file diff --git a/pumpkin-api/src/lib.rs b/pumpkin-api/src/lib.rs new file mode 100644 index 000000000..be1d681ad --- /dev/null +++ b/pumpkin-api/src/lib.rs @@ -0,0 +1,45 @@ +#[derive(Debug, Clone)] +pub struct PluginMetadata<'s> { + /// The name of the plugin. + pub name: &'s str, + /// The unique identifier of the plugin. + pub id: &'s str, + /// The version of the plugin. + pub version: &'s str, + /// The authors of the plugin. + pub authors: &'s[&'s str], + /// A description of the plugin. + pub description: Option<&'s str>, +} + +pub trait Plugin: Send + Sync + 'static { + /// Called when the plugin is loaded. + fn on_load(&mut self, server: &dyn PluginContext) -> Result<(), String>; + + /// Called when the plugin is unloaded. + fn on_unload(&mut self, server: &dyn PluginContext) -> Result<(), String>; +} + +pub trait PluginContext { + fn get_logger(&self) -> Box; +} + +pub trait Logger { + fn info(&self, message: &str); + fn warn(&self, message: &str); + fn error(&self, message: &str); +} + +#[macro_export] +macro_rules! plugin_metadata { + ($name:expr, $id:expr, $version:expr, $authors:expr, $description:expr) => { + #[no_mangle] + pub static METADATA: PluginMetadata = PluginMetadata { + name: $name, + id: $id, + version: $version, + authors: $authors, + description: Some($description), + }; + }; +} \ No newline at end of file From 3e1f84af35c6b730c0fb2b042572da1b69a6e78b Mon Sep 17 00:00:00 2001 From: vyPal Date: Sun, 24 Nov 2024 19:56:36 +0100 Subject: [PATCH 02/62] Add proc-macro definitions for pumpkin-api --- pumpkin-api-macros/Cargo.toml | 14 +++++++ pumpkin-api-macros/src/lib.rs | 77 +++++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+) create mode 100644 pumpkin-api-macros/Cargo.toml create mode 100644 pumpkin-api-macros/src/lib.rs diff --git a/pumpkin-api-macros/Cargo.toml b/pumpkin-api-macros/Cargo.toml new file mode 100644 index 000000000..69c16a7c8 --- /dev/null +++ b/pumpkin-api-macros/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "pumpkin-api-macros" +version.workspace = true +edition.workspace = true + +[lib] +proc-macro = true + +[dependencies] +syn = { version = "2.0.89", features = ["full"] } +quote = "1.0.37" +proc-macro2 = "1.0.92" +once_cell = "1.20.2" +pumpkin-api = { path = "../pumpkin-api" } \ No newline at end of file diff --git a/pumpkin-api-macros/src/lib.rs b/pumpkin-api-macros/src/lib.rs new file mode 100644 index 000000000..fb49ca69b --- /dev/null +++ b/pumpkin-api-macros/src/lib.rs @@ -0,0 +1,77 @@ +use proc_macro::TokenStream; +use quote::quote; +use syn::{parse_macro_input, ItemFn, ItemStruct}; +use std::collections::HashMap; +use once_cell::sync::Lazy; +use std::sync::Mutex; + +static PLUGIN_METHODS: Lazy>>> = + Lazy::new(|| Mutex::new(HashMap::new())); + +#[proc_macro_attribute] +pub fn plugin_method(attr: TokenStream, item: TokenStream) -> TokenStream { + let input_fn = parse_macro_input!(item as ItemFn); + let fn_name = &input_fn.sig.ident; + let fn_inputs = &input_fn.sig.inputs; + let fn_output = &input_fn.sig.output; + let fn_body = &input_fn.block; + + let struct_name = if attr.is_empty() { + "MyPlugin".to_string() + } else { + attr.to_string().trim().to_string() + }; + + let method = quote! { + #[allow(unused_mut)] + fn #fn_name(#fn_inputs) #fn_output { + #fn_body + } + }.to_string(); + + PLUGIN_METHODS.lock().unwrap() + .entry(struct_name) + .or_default() + .push(method); + + TokenStream::new() +} + +#[proc_macro_attribute] +pub fn plugin_impl(attr: TokenStream, item: TokenStream) -> TokenStream { + // Parse the input struct + let input_struct = parse_macro_input!(item as ItemStruct); + let struct_ident = &input_struct.ident; + + // Get the custom name from attribute or use the struct's name + let struct_name = if attr.is_empty() { + struct_ident.clone() + } else { + let attr_str = attr.to_string(); + quote::format_ident!("{}", attr_str.trim()) + }; + + let methods = PLUGIN_METHODS.lock().unwrap() + .remove(&struct_name.to_string()) + .unwrap_or_default(); + + let methods: Vec = methods.iter() + .filter_map(|method_str| method_str.parse().ok()) + .collect(); + + // Combine the original struct definition with the impl block and plugin() function + let expanded = quote! { + #input_struct + + impl pumpkin_api::Plugin for #struct_ident { + #(#methods)* + } + + #[no_mangle] + pub fn plugin() -> Box { + Box::new(#struct_ident {}) + } + }; + + TokenStream::from(expanded) +} \ No newline at end of file From 9b8d2091836a9385be789432678bfd7e2214b990 Mon Sep 17 00:00:00 2001 From: vyPal Date: Sun, 24 Nov 2024 19:57:01 +0100 Subject: [PATCH 03/62] Add pumpkin-api and pumpkin-api-macros to workspace --- Cargo.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 4f449a64e..97bb453e2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,8 @@ [workspace] resolver = "2" members = [ + "pumpkin-api", + "pumpkin-api-macros", "pumpkin-config", "pumpkin-core", "pumpkin-entity", From 274fab68f844d6bed8121c83bbe33c6e966783be Mon Sep 17 00:00:00 2001 From: vyPal Date: Sun, 24 Nov 2024 19:57:33 +0100 Subject: [PATCH 04/62] Add basic PluginManager implementation plugin loading --- pumpkin/Cargo.toml | 4 ++ pumpkin/src/main.rs | 7 ++++ pumpkin/src/plugin/mod.rs | 87 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 98 insertions(+) create mode 100644 pumpkin/src/plugin/mod.rs diff --git a/pumpkin/Cargo.toml b/pumpkin/Cargo.toml index d3f0df38a..0bc69132a 100644 --- a/pumpkin/Cargo.toml +++ b/pumpkin/Cargo.toml @@ -12,6 +12,7 @@ LegalCopyright = "Copyright © 2024 Aleksander Medvedev" [dependencies] # pumpkin +pumpkin-api = { path = "../pumpkin-api" } pumpkin-core = { path = "../pumpkin-core" } pumpkin-config = { path = "../pumpkin-config" } pumpkin-inventory = { path = "../pumpkin-inventory" } @@ -73,6 +74,9 @@ sysinfo = "0.32.0" # commands async-trait = "0.1.83" + +# plugins +libloading = "0.8.5" [build-dependencies] git-version = "0.3.9" # This makes it so the entire project doesn't recompile on each build on linux. diff --git a/pumpkin/src/main.rs b/pumpkin/src/main.rs index a936f2e2c..209ad0337 100644 --- a/pumpkin/src/main.rs +++ b/pumpkin/src/main.rs @@ -49,6 +49,7 @@ pub mod query; pub mod rcon; pub mod server; pub mod world; +pub mod plugin; fn scrub_address(ip: &str) -> String { use pumpkin_config::BASIC_CONFIG; @@ -233,6 +234,12 @@ async fn main() -> io::Result<()> { let server = Arc::new(Server::new()); let mut ticker = Ticker::new(BASIC_CONFIG.tps); + // Plugins + let mut plugin_manager = plugin::PluginManager::new(); + plugin_manager.load_plugins().expect("Failed to load plugins"); + + plugin_manager.list_plugins(); + log::info!("Started Server took {}ms", time.elapsed().as_millis()); log::info!("You now can connect to the server, Listening on {}", addr); diff --git a/pumpkin/src/plugin/mod.rs b/pumpkin/src/plugin/mod.rs new file mode 100644 index 000000000..0f73d9f85 --- /dev/null +++ b/pumpkin/src/plugin/mod.rs @@ -0,0 +1,87 @@ +use std::{ + collections::HashMap, fs, io, path::Path +}; +use pumpkin_api::{Plugin, PluginMetadata}; + +pub struct PluginManager<'s> { + plugins: HashMap, Box, libloading::Library)>, +} + +impl PluginManager<'_> { + pub fn new() -> Self { + PluginManager { + plugins: HashMap::new(), + } + } + + pub fn load_plugins(&mut self) -> Result<(), String> { + const PLUGIN_DIR: &str = "./plugins"; + + let dir_entires = fs::read_dir(PLUGIN_DIR); + + for entry in dir_entires.unwrap() { + if !entry.as_ref().unwrap().path().is_file() { + continue; + } + let err = self.try_load_plugin(entry.unwrap().path().as_path()); + if let Err(err) = err { + return Err(format!("Failed to load plugin: {}", err)); + } + } + + Ok(()) + } + + fn try_load_plugin(&mut self, path: &Path) -> Result<(), io::Error> { + let library = unsafe { libloading::Library::new(path).unwrap() }; + + let plugin_fn = unsafe { library.get:: Box>(b"plugin").unwrap() }; + let metadata: &PluginMetadata = unsafe { &**library.get::<*const PluginMetadata>(b"METADATA").unwrap() }; + + struct Logger { + plugin_name: String, + } + + impl pumpkin_api::Logger for Logger { + fn info(&self, message: &str) { + log::info!("[{}] {}", self.plugin_name, message); + } + + fn warn(&self, message: &str) { + log::warn!("[{}] {}", self.plugin_name, message); + } + + fn error(&self, message: &str) { + log::error!("[{}] {}", self.plugin_name, message); + } + } + + struct Context<'a> { + metadata: &'a PluginMetadata<'a>, + } + impl pumpkin_api::PluginContext for Context<'_> { + fn get_logger(&self) -> Box { + Box::new(Logger { + plugin_name: self.metadata.name.to_string(), + }) + } + } + + let context = Context { metadata }; + let _ = plugin_fn().on_load(&context); + + self.plugins.insert(metadata.name.to_string(), (metadata.clone(), plugin_fn(), library)); + + Ok(()) + } + + pub fn get_plugin(&self, name: &str) -> Option<&(PluginMetadata, Box, libloading::Library)> { + self.plugins.get(name) + } + + pub fn list_plugins(&self) { + for (_, (metadata, _, _)) in &self.plugins { + println!("{}: {} v{} by {}", metadata.id, metadata.name, metadata.version, metadata.authors.join(", ")); + } + } +} From cd38d4059e949c065c9e5b718144f432352584a6 Mon Sep 17 00:00:00 2001 From: vyPal Date: Sun, 24 Nov 2024 19:58:42 +0100 Subject: [PATCH 05/62] Update .gitignore to include specific plugin file types --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index eb5de605a..4c357a827 100644 --- a/.gitignore +++ b/.gitignore @@ -103,7 +103,9 @@ Cargo.lock #.idea/ # === PROJECT SPECIFIC === -plugins/* +plugins/**/*.so +plugins/**/*.dylib +plugins/**/*.dll world/* # docker-compose From dfc869b91708003aa79c420c72f3a3823feb842b Mon Sep 17 00:00:00 2001 From: vyPal Date: Sun, 24 Nov 2024 19:58:55 +0100 Subject: [PATCH 06/62] Add example plugin --- plugins/hello-plugin-source/Cargo.toml | 13 +++++++++++++ plugins/hello-plugin-source/src/lib.rs | 19 +++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 plugins/hello-plugin-source/Cargo.toml create mode 100644 plugins/hello-plugin-source/src/lib.rs diff --git a/plugins/hello-plugin-source/Cargo.toml b/plugins/hello-plugin-source/Cargo.toml new file mode 100644 index 000000000..58496e6e9 --- /dev/null +++ b/plugins/hello-plugin-source/Cargo.toml @@ -0,0 +1,13 @@ +[workspace] + +[package] +name = "hello-plugin-source" +edition = "2021" +version = "0.1.0" + +[lib] +crate-type = ["dylib"] + +[dependencies] +pumpkin-api = { path = "../../pumpkin-api" } +pumpkin-api-macros = { path = "../../pumpkin-api-macros" } \ No newline at end of file diff --git a/plugins/hello-plugin-source/src/lib.rs b/plugins/hello-plugin-source/src/lib.rs new file mode 100644 index 000000000..2b82ea78e --- /dev/null +++ b/plugins/hello-plugin-source/src/lib.rs @@ -0,0 +1,19 @@ +use pumpkin_api::*; +use pumpkin_api_macros::{plugin_method, plugin_impl}; + +plugin_metadata!("Plugin name", "plugin-id", "1.0.0", &["Author Name"], "Description"); + +#[plugin_method] +fn on_load(&mut self, server: &dyn PluginContext) -> Result<(), String> { + server.get_logger().info("Plugin loaded!"); + Ok(()) +} + +#[plugin_method] +fn on_unload(&mut self, server: &dyn PluginContext) -> Result<(), String> { + server.get_logger().info("Plugin unloaded!"); + Ok(()) +} + +#[plugin_impl] +pub struct MyPlugin {} \ No newline at end of file From 6f2e361a6af3875a3eb613fe15f08a1b48ecf889 Mon Sep 17 00:00:00 2001 From: vyPal Date: Sun, 24 Nov 2024 20:01:38 +0100 Subject: [PATCH 07/62] Cargo fmt --- plugins/hello-plugin-source/src/lib.rs | 12 ++++++-- pumpkin-api-macros/src/lib.rs | 40 +++++++++++++++----------- pumpkin-api/src/lib.rs | 4 +-- pumpkin/src/main.rs | 6 ++-- pumpkin/src/plugin/mod.rs | 25 +++++++++++----- 5 files changed, 56 insertions(+), 31 deletions(-) diff --git a/plugins/hello-plugin-source/src/lib.rs b/plugins/hello-plugin-source/src/lib.rs index 2b82ea78e..e82677a24 100644 --- a/plugins/hello-plugin-source/src/lib.rs +++ b/plugins/hello-plugin-source/src/lib.rs @@ -1,7 +1,13 @@ use pumpkin_api::*; -use pumpkin_api_macros::{plugin_method, plugin_impl}; +use pumpkin_api_macros::{plugin_impl, plugin_method}; -plugin_metadata!("Plugin name", "plugin-id", "1.0.0", &["Author Name"], "Description"); +plugin_metadata!( + "Plugin name", + "plugin-id", + "1.0.0", + &["Author Name"], + "Description" +); #[plugin_method] fn on_load(&mut self, server: &dyn PluginContext) -> Result<(), String> { @@ -16,4 +22,4 @@ fn on_unload(&mut self, server: &dyn PluginContext) -> Result<(), String> { } #[plugin_impl] -pub struct MyPlugin {} \ No newline at end of file +pub struct MyPlugin {} diff --git a/pumpkin-api-macros/src/lib.rs b/pumpkin-api-macros/src/lib.rs index fb49ca69b..e841a5762 100644 --- a/pumpkin-api-macros/src/lib.rs +++ b/pumpkin-api-macros/src/lib.rs @@ -1,11 +1,11 @@ +use once_cell::sync::Lazy; use proc_macro::TokenStream; use quote::quote; -use syn::{parse_macro_input, ItemFn, ItemStruct}; use std::collections::HashMap; -use once_cell::sync::Lazy; use std::sync::Mutex; +use syn::{parse_macro_input, ItemFn, ItemStruct}; -static PLUGIN_METHODS: Lazy>>> = +static PLUGIN_METHODS: Lazy>>> = Lazy::new(|| Mutex::new(HashMap::new())); #[proc_macro_attribute] @@ -15,25 +15,28 @@ pub fn plugin_method(attr: TokenStream, item: TokenStream) -> TokenStream { let fn_inputs = &input_fn.sig.inputs; let fn_output = &input_fn.sig.output; let fn_body = &input_fn.block; - + let struct_name = if attr.is_empty() { "MyPlugin".to_string() } else { attr.to_string().trim().to_string() }; - + let method = quote! { #[allow(unused_mut)] fn #fn_name(#fn_inputs) #fn_output { #fn_body } - }.to_string(); - - PLUGIN_METHODS.lock().unwrap() + } + .to_string(); + + PLUGIN_METHODS + .lock() + .unwrap() .entry(struct_name) .or_default() .push(method); - + TokenStream::new() } @@ -42,7 +45,7 @@ pub fn plugin_impl(attr: TokenStream, item: TokenStream) -> TokenStream { // Parse the input struct let input_struct = parse_macro_input!(item as ItemStruct); let struct_ident = &input_struct.ident; - + // Get the custom name from attribute or use the struct's name let struct_name = if attr.is_empty() { struct_ident.clone() @@ -50,15 +53,18 @@ pub fn plugin_impl(attr: TokenStream, item: TokenStream) -> TokenStream { let attr_str = attr.to_string(); quote::format_ident!("{}", attr_str.trim()) }; - - let methods = PLUGIN_METHODS.lock().unwrap() + + let methods = PLUGIN_METHODS + .lock() + .unwrap() .remove(&struct_name.to_string()) .unwrap_or_default(); - - let methods: Vec = methods.iter() + + let methods: Vec = methods + .iter() .filter_map(|method_str| method_str.parse().ok()) .collect(); - + // Combine the original struct definition with the impl block and plugin() function let expanded = quote! { #input_struct @@ -72,6 +78,6 @@ pub fn plugin_impl(attr: TokenStream, item: TokenStream) -> TokenStream { Box::new(#struct_ident {}) } }; - + TokenStream::from(expanded) -} \ No newline at end of file +} diff --git a/pumpkin-api/src/lib.rs b/pumpkin-api/src/lib.rs index be1d681ad..0fd20e4e7 100644 --- a/pumpkin-api/src/lib.rs +++ b/pumpkin-api/src/lib.rs @@ -7,7 +7,7 @@ pub struct PluginMetadata<'s> { /// The version of the plugin. pub version: &'s str, /// The authors of the plugin. - pub authors: &'s[&'s str], + pub authors: &'s [&'s str], /// A description of the plugin. pub description: Option<&'s str>, } @@ -42,4 +42,4 @@ macro_rules! plugin_metadata { description: Some($description), }; }; -} \ No newline at end of file +} diff --git a/pumpkin/src/main.rs b/pumpkin/src/main.rs index 209ad0337..047bf26c9 100644 --- a/pumpkin/src/main.rs +++ b/pumpkin/src/main.rs @@ -44,12 +44,12 @@ pub mod command; pub mod entity; pub mod error; pub mod lan_broadcast; +pub mod plugin; pub mod proxy; pub mod query; pub mod rcon; pub mod server; pub mod world; -pub mod plugin; fn scrub_address(ip: &str) -> String { use pumpkin_config::BASIC_CONFIG; @@ -236,7 +236,9 @@ async fn main() -> io::Result<()> { // Plugins let mut plugin_manager = plugin::PluginManager::new(); - plugin_manager.load_plugins().expect("Failed to load plugins"); + plugin_manager + .load_plugins() + .expect("Failed to load plugins"); plugin_manager.list_plugins(); diff --git a/pumpkin/src/plugin/mod.rs b/pumpkin/src/plugin/mod.rs index 0f73d9f85..aa8189f30 100644 --- a/pumpkin/src/plugin/mod.rs +++ b/pumpkin/src/plugin/mod.rs @@ -1,7 +1,5 @@ -use std::{ - collections::HashMap, fs, io, path::Path -}; use pumpkin_api::{Plugin, PluginMetadata}; +use std::{collections::HashMap, fs, io, path::Path}; pub struct PluginManager<'s> { plugins: HashMap, Box, libloading::Library)>, @@ -36,7 +34,8 @@ impl PluginManager<'_> { let library = unsafe { libloading::Library::new(path).unwrap() }; let plugin_fn = unsafe { library.get:: Box>(b"plugin").unwrap() }; - let metadata: &PluginMetadata = unsafe { &**library.get::<*const PluginMetadata>(b"METADATA").unwrap() }; + let metadata: &PluginMetadata = + unsafe { &**library.get::<*const PluginMetadata>(b"METADATA").unwrap() }; struct Logger { plugin_name: String, @@ -70,18 +69,30 @@ impl PluginManager<'_> { let context = Context { metadata }; let _ = plugin_fn().on_load(&context); - self.plugins.insert(metadata.name.to_string(), (metadata.clone(), plugin_fn(), library)); + self.plugins.insert( + metadata.name.to_string(), + (metadata.clone(), plugin_fn(), library), + ); Ok(()) } - pub fn get_plugin(&self, name: &str) -> Option<&(PluginMetadata, Box, libloading::Library)> { + pub fn get_plugin( + &self, + name: &str, + ) -> Option<&(PluginMetadata, Box, libloading::Library)> { self.plugins.get(name) } pub fn list_plugins(&self) { for (_, (metadata, _, _)) in &self.plugins { - println!("{}: {} v{} by {}", metadata.id, metadata.name, metadata.version, metadata.authors.join(", ")); + println!( + "{}: {} v{} by {}", + metadata.id, + metadata.name, + metadata.version, + metadata.authors.join(", ") + ); } } } From 66c6e3f27aa14a14fc2b32b77ce3d7809dd77f9d Mon Sep 17 00:00:00 2001 From: vyPal Date: Sun, 24 Nov 2024 20:09:57 +0100 Subject: [PATCH 08/62] Fix clippy issues --- pumpkin/src/plugin/mod.rs | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/pumpkin/src/plugin/mod.rs b/pumpkin/src/plugin/mod.rs index aa8189f30..ad9783d09 100644 --- a/pumpkin/src/plugin/mod.rs +++ b/pumpkin/src/plugin/mod.rs @@ -1,11 +1,18 @@ use pumpkin_api::{Plugin, PluginMetadata}; -use std::{collections::HashMap, fs, io, path::Path}; +use std::{collections::HashMap, fs, path::Path}; pub struct PluginManager<'s> { plugins: HashMap, Box, libloading::Library)>, } +impl Default for PluginManager<'_> { + fn default() -> Self { + Self::new() + } +} + impl PluginManager<'_> { + #[must_use] pub fn new() -> Self { PluginManager { plugins: HashMap::new(), @@ -21,22 +28,13 @@ impl PluginManager<'_> { if !entry.as_ref().unwrap().path().is_file() { continue; } - let err = self.try_load_plugin(entry.unwrap().path().as_path()); - if let Err(err) = err { - return Err(format!("Failed to load plugin: {}", err)); - } + self.try_load_plugin(entry.unwrap().path().as_path()); } Ok(()) } - fn try_load_plugin(&mut self, path: &Path) -> Result<(), io::Error> { - let library = unsafe { libloading::Library::new(path).unwrap() }; - - let plugin_fn = unsafe { library.get:: Box>(b"plugin").unwrap() }; - let metadata: &PluginMetadata = - unsafe { &**library.get::<*const PluginMetadata>(b"METADATA").unwrap() }; - + fn try_load_plugin(&mut self, path: &Path) { struct Logger { plugin_name: String, } @@ -66,6 +64,12 @@ impl PluginManager<'_> { } } + let library = unsafe { libloading::Library::new(path).unwrap() }; + + let plugin_fn = unsafe { library.get:: Box>(b"plugin").unwrap() }; + let metadata: &PluginMetadata = + unsafe { &**library.get::<*const PluginMetadata>(b"METADATA").unwrap() }; + let context = Context { metadata }; let _ = plugin_fn().on_load(&context); @@ -73,10 +77,9 @@ impl PluginManager<'_> { metadata.name.to_string(), (metadata.clone(), plugin_fn(), library), ); - - Ok(()) } + #[must_use] pub fn get_plugin( &self, name: &str, @@ -85,7 +88,7 @@ impl PluginManager<'_> { } pub fn list_plugins(&self) { - for (_, (metadata, _, _)) in &self.plugins { + for (metadata, _, _) in self.plugins.values() { println!( "{}: {} v{} by {}", metadata.id, From b486bce0e6dac2129899d99e91a887ae25894853 Mon Sep 17 00:00:00 2001 From: vyPal Date: Mon, 25 Nov 2024 16:37:01 +0100 Subject: [PATCH 09/62] Refactoring to prevent a circular import --- Cargo.toml | 1 - plugins/hello-plugin-source/Cargo.toml | 2 +- plugins/hello-plugin-source/src/lib.rs | 3 +- pumpkin-api-macros/Cargo.toml | 2 +- pumpkin-api-macros/src/lib.rs | 4 +- pumpkin-api/Cargo.toml | 6 -- pumpkin/Cargo.toml | 4 +- pumpkin/src/lib.rs | 13 +++ pumpkin/src/plugin/api/context.rs | 9 ++ .../lib.rs => pumpkin/src/plugin/api/mod.rs | 18 ++-- pumpkin/src/plugin/mod.rs | 98 ++++++++++++------- 11 files changed, 102 insertions(+), 58 deletions(-) delete mode 100644 pumpkin-api/Cargo.toml create mode 100644 pumpkin/src/lib.rs create mode 100644 pumpkin/src/plugin/api/context.rs rename pumpkin-api/src/lib.rs => pumpkin/src/plugin/api/mod.rs (79%) diff --git a/Cargo.toml b/Cargo.toml index 97bb453e2..c8a5f2bbb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,6 @@ [workspace] resolver = "2" members = [ - "pumpkin-api", "pumpkin-api-macros", "pumpkin-config", "pumpkin-core", diff --git a/plugins/hello-plugin-source/Cargo.toml b/plugins/hello-plugin-source/Cargo.toml index 58496e6e9..bb13de1c1 100644 --- a/plugins/hello-plugin-source/Cargo.toml +++ b/plugins/hello-plugin-source/Cargo.toml @@ -9,5 +9,5 @@ version = "0.1.0" crate-type = ["dylib"] [dependencies] -pumpkin-api = { path = "../../pumpkin-api" } +pumpkin = { path = "../../pumpkin" } pumpkin-api-macros = { path = "../../pumpkin-api-macros" } \ No newline at end of file diff --git a/plugins/hello-plugin-source/src/lib.rs b/plugins/hello-plugin-source/src/lib.rs index e82677a24..06634342f 100644 --- a/plugins/hello-plugin-source/src/lib.rs +++ b/plugins/hello-plugin-source/src/lib.rs @@ -1,4 +1,5 @@ -use pumpkin_api::*; +use pumpkin::plugin::*; +use pumpkin::plugin_metadata; use pumpkin_api_macros::{plugin_impl, plugin_method}; plugin_metadata!( diff --git a/pumpkin-api-macros/Cargo.toml b/pumpkin-api-macros/Cargo.toml index 69c16a7c8..ebfc7939d 100644 --- a/pumpkin-api-macros/Cargo.toml +++ b/pumpkin-api-macros/Cargo.toml @@ -11,4 +11,4 @@ syn = { version = "2.0.89", features = ["full"] } quote = "1.0.37" proc-macro2 = "1.0.92" once_cell = "1.20.2" -pumpkin-api = { path = "../pumpkin-api" } \ No newline at end of file +pumpkin = { path = "../pumpkin" } \ No newline at end of file diff --git a/pumpkin-api-macros/src/lib.rs b/pumpkin-api-macros/src/lib.rs index e841a5762..2fd6730e4 100644 --- a/pumpkin-api-macros/src/lib.rs +++ b/pumpkin-api-macros/src/lib.rs @@ -69,12 +69,12 @@ pub fn plugin_impl(attr: TokenStream, item: TokenStream) -> TokenStream { let expanded = quote! { #input_struct - impl pumpkin_api::Plugin for #struct_ident { + impl pumpkin::plugin::Plugin for #struct_ident { #(#methods)* } #[no_mangle] - pub fn plugin() -> Box { + pub fn plugin() -> Box { Box::new(#struct_ident {}) } }; diff --git a/pumpkin-api/Cargo.toml b/pumpkin-api/Cargo.toml deleted file mode 100644 index 97ae22386..000000000 --- a/pumpkin-api/Cargo.toml +++ /dev/null @@ -1,6 +0,0 @@ -[package] -name = "pumpkin-api" -version.workspace = true -edition.workspace = true - -[dependencies] \ No newline at end of file diff --git a/pumpkin/Cargo.toml b/pumpkin/Cargo.toml index 0bc69132a..783cf241d 100644 --- a/pumpkin/Cargo.toml +++ b/pumpkin/Cargo.toml @@ -10,9 +10,11 @@ FileDescription = "Pumpkin" OriginalFilename = "pumpkin.exe" LegalCopyright = "Copyright © 2024 Aleksander Medvedev" +# Required to expose pumpkin plugin API +[lib] + [dependencies] # pumpkin -pumpkin-api = { path = "../pumpkin-api" } pumpkin-core = { path = "../pumpkin-core" } pumpkin-config = { path = "../pumpkin-config" } pumpkin-inventory = { path = "../pumpkin-inventory" } diff --git a/pumpkin/src/lib.rs b/pumpkin/src/lib.rs new file mode 100644 index 000000000..4ff442c3d --- /dev/null +++ b/pumpkin/src/lib.rs @@ -0,0 +1,13 @@ +use client::Client; +use pumpkin_core::text::TextComponent; + +pub mod client; +pub mod command; +pub mod entity; +pub mod error; +pub mod plugin; +pub mod proxy; +pub mod server; +pub mod world; + +const GIT_VERSION: &str = env!("GIT_VERSION"); \ No newline at end of file diff --git a/pumpkin/src/plugin/api/context.rs b/pumpkin/src/plugin/api/context.rs new file mode 100644 index 000000000..ea6cfb0c9 --- /dev/null +++ b/pumpkin/src/plugin/api/context.rs @@ -0,0 +1,9 @@ +pub trait PluginContext { + fn get_logger(&self) -> Box; +} + +pub trait Logger { + fn info(&self, message: &str); + fn warn(&self, message: &str); + fn error(&self, message: &str); +} \ No newline at end of file diff --git a/pumpkin-api/src/lib.rs b/pumpkin/src/plugin/api/mod.rs similarity index 79% rename from pumpkin-api/src/lib.rs rename to pumpkin/src/plugin/api/mod.rs index 0fd20e4e7..6efa16b60 100644 --- a/pumpkin-api/src/lib.rs +++ b/pumpkin/src/plugin/api/mod.rs @@ -1,3 +1,9 @@ +pub mod context; +pub mod events; + +pub use context::*; +pub use events::*; + #[derive(Debug, Clone)] pub struct PluginMetadata<'s> { /// The name of the plugin. @@ -20,21 +26,11 @@ pub trait Plugin: Send + Sync + 'static { fn on_unload(&mut self, server: &dyn PluginContext) -> Result<(), String>; } -pub trait PluginContext { - fn get_logger(&self) -> Box; -} - -pub trait Logger { - fn info(&self, message: &str); - fn warn(&self, message: &str); - fn error(&self, message: &str); -} - #[macro_export] macro_rules! plugin_metadata { ($name:expr, $id:expr, $version:expr, $authors:expr, $description:expr) => { #[no_mangle] - pub static METADATA: PluginMetadata = PluginMetadata { + pub static METADATA: pumpkin::plugin::PluginMetadata = pumpkin::plugin::PluginMetadata { name: $name, id: $id, version: $version, diff --git a/pumpkin/src/plugin/mod.rs b/pumpkin/src/plugin/mod.rs index ad9783d09..be44f603c 100644 --- a/pumpkin/src/plugin/mod.rs +++ b/pumpkin/src/plugin/mod.rs @@ -1,8 +1,12 @@ -use pumpkin_api::{Plugin, PluginMetadata}; +pub mod api; + +pub use api::*; use std::{collections::HashMap, fs, path::Path}; +use crate::{entity::player::Player, world::World}; + pub struct PluginManager<'s> { - plugins: HashMap, Box, libloading::Library)>, + plugins: HashMap, Box, Box, libloading::Library)>, } impl Default for PluginManager<'_> { @@ -11,6 +15,50 @@ impl Default for PluginManager<'_> { } } +struct PluginLogger { + plugin_name: String, +} + +impl Logger for PluginLogger { + fn info(&self, message: &str) { + log::info!("[{}] {}", self.plugin_name, message); + } + + fn warn(&self, message: &str) { + log::warn!("[{}] {}", self.plugin_name, message); + } + + fn error(&self, message: &str) { + log::error!("[{}] {}", self.plugin_name, message); + } +} + +struct Context<'a> { + metadata: &'a PluginMetadata<'a>, +} +impl PluginContext for Context<'_> { + fn get_logger(&self) -> Box { + Box::new(PluginLogger { + plugin_name: self.metadata.name.to_string(), + }) + } +} + +struct Event<'a> { + player: &'a Player, + world: &'a World, +} + +impl PlayerConnectionEvent for Event<'_> { + fn get_player(&self) -> &Player { + self.player + } + + fn get_world(&self) -> &World { + self.world + } +} + impl PluginManager<'_> { #[must_use] pub fn new() -> Self { @@ -35,38 +83,10 @@ impl PluginManager<'_> { } fn try_load_plugin(&mut self, path: &Path) { - struct Logger { - plugin_name: String, - } - - impl pumpkin_api::Logger for Logger { - fn info(&self, message: &str) { - log::info!("[{}] {}", self.plugin_name, message); - } - - fn warn(&self, message: &str) { - log::warn!("[{}] {}", self.plugin_name, message); - } - - fn error(&self, message: &str) { - log::error!("[{}] {}", self.plugin_name, message); - } - } - - struct Context<'a> { - metadata: &'a PluginMetadata<'a>, - } - impl pumpkin_api::PluginContext for Context<'_> { - fn get_logger(&self) -> Box { - Box::new(Logger { - plugin_name: self.metadata.name.to_string(), - }) - } - } - let library = unsafe { libloading::Library::new(path).unwrap() }; let plugin_fn = unsafe { library.get:: Box>(b"plugin").unwrap() }; + let hooks_fn = unsafe { library.get:: Box>(b"hooks").unwrap() }; let metadata: &PluginMetadata = unsafe { &**library.get::<*const PluginMetadata>(b"METADATA").unwrap() }; @@ -75,7 +95,7 @@ impl PluginManager<'_> { self.plugins.insert( metadata.name.to_string(), - (metadata.clone(), plugin_fn(), library), + (metadata.clone(), plugin_fn(), hooks_fn(), library), ); } @@ -83,12 +103,12 @@ impl PluginManager<'_> { pub fn get_plugin( &self, name: &str, - ) -> Option<&(PluginMetadata, Box, libloading::Library)> { + ) -> Option<&(PluginMetadata, Box, Box, libloading::Library)> { self.plugins.get(name) } pub fn list_plugins(&self) { - for (metadata, _, _) in self.plugins.values() { + for (metadata, _, _, _) in self.plugins.values() { println!( "{}: {} v{} by {}", metadata.id, @@ -98,4 +118,14 @@ impl PluginManager<'_> { ); } } + + pub fn emit_player_join(&mut self, player: &Player, world: &World) { + for (metadata, _, hooks, _) in self.plugins.values_mut() { + if hooks.registered_events().unwrap().contains(&"player_join") { + let context = Context { metadata }; + let event = Event { player, world }; + let _ = hooks.as_mut().on_player_join(&context, &event); + } + } + } } From 3e25550f18946f046590160bcff99d19cc4e414c Mon Sep 17 00:00:00 2001 From: vyPal Date: Mon, 25 Nov 2024 16:37:13 +0100 Subject: [PATCH 10/62] Add base impl for plugin hooks --- pumpkin/src/plugin/api/events.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 pumpkin/src/plugin/api/events.rs diff --git a/pumpkin/src/plugin/api/events.rs b/pumpkin/src/plugin/api/events.rs new file mode 100644 index 000000000..56ed41d02 --- /dev/null +++ b/pumpkin/src/plugin/api/events.rs @@ -0,0 +1,19 @@ +use crate::{entity::player::Player, world::World}; + +use super::PluginContext; + +pub trait Hooks: Send + Sync + 'static { + /// Returns an array of events that the + fn registered_events(&self) -> Result<&'static[&'static str], String>; + + /// Called when the plugin is loaded. + fn on_player_join(&mut self, server: &dyn PluginContext, event: &dyn PlayerConnectionEvent) -> Result<(), String>; + + /// Called when the plugin is unloaded. + fn on_player_leave(&mut self, server: &dyn PluginContext, event: &dyn PlayerConnectionEvent) -> Result<(), String>; +} + +pub trait PlayerConnectionEvent { + fn get_player(&self) -> &Player; + fn get_world(&self) -> &World; +} \ No newline at end of file From b7dc70bf380071b1cc1be44060f9780fab6351f9 Mon Sep 17 00:00:00 2001 From: vyPal Date: Mon, 25 Nov 2024 18:28:36 +0100 Subject: [PATCH 11/62] Move plugin manager to server --- pumpkin/src/main.rs | 8 -------- pumpkin/src/server/mod.rs | 15 +++++++++++++++ 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/pumpkin/src/main.rs b/pumpkin/src/main.rs index 047bf26c9..9ff778ef7 100644 --- a/pumpkin/src/main.rs +++ b/pumpkin/src/main.rs @@ -234,14 +234,6 @@ async fn main() -> io::Result<()> { let server = Arc::new(Server::new()); let mut ticker = Ticker::new(BASIC_CONFIG.tps); - // Plugins - let mut plugin_manager = plugin::PluginManager::new(); - plugin_manager - .load_plugins() - .expect("Failed to load plugins"); - - plugin_manager.list_plugins(); - log::info!("Started Server took {}ms", time.elapsed().as_millis()); log::info!("You now can connect to the server, Listening on {}", addr); diff --git a/pumpkin/src/server/mod.rs b/pumpkin/src/server/mod.rs index 184547a5d..ccf903407 100644 --- a/pumpkin/src/server/mod.rs +++ b/pumpkin/src/server/mod.rs @@ -21,6 +21,7 @@ use std::{ use tokio::sync::{Mutex, RwLock}; use crate::client::EncryptionError; +use crate::plugin::PluginManager; use crate::{ client::Client, command::{default_dispatcher, dispatcher::CommandDispatcher}, @@ -57,6 +58,8 @@ pub struct Server { entity_id: AtomicI32, /// Manages authentication with a authentication server, if enabled. pub auth_client: Option, + /// Plugin manager + pub plugin_manager: Mutex, } impl Server { @@ -86,6 +89,15 @@ impl Server { ), DimensionType::Overworld, ); + + // Plugins + let mut plugin_manager = PluginManager::new(); + plugin_manager + .load_plugins() + .expect("Failed to load plugins"); + + plugin_manager.list_plugins(); + Self { cached_registry: Registry::get_synced(), open_containers: RwLock::new(HashMap::new()), @@ -104,6 +116,7 @@ impl Server { key_store: KeyStore::new(), server_listing: Mutex::new(CachedStatus::new()), server_branding: CachedBranding::new(), + plugin_manager: Mutex::new(plugin_manager), } } @@ -154,6 +167,8 @@ impl Server { } } + self.plugin_manager.lock().await.emit_player_join(&player, world); + (player, world.clone()) } From 55ad63900d64fdb821bf78697f873e5270bfec99 Mon Sep 17 00:00:00 2001 From: vyPal Date: Mon, 25 Nov 2024 18:29:02 +0100 Subject: [PATCH 12/62] Make metadata have static lifetime --- pumpkin/src/plugin/mod.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pumpkin/src/plugin/mod.rs b/pumpkin/src/plugin/mod.rs index be44f603c..bab76146a 100644 --- a/pumpkin/src/plugin/mod.rs +++ b/pumpkin/src/plugin/mod.rs @@ -5,11 +5,11 @@ use std::{collections::HashMap, fs, path::Path}; use crate::{entity::player::Player, world::World}; -pub struct PluginManager<'s> { - plugins: HashMap, Box, Box, libloading::Library)>, +pub struct PluginManager { + plugins: HashMap, Box, Box, libloading::Library)>, } -impl Default for PluginManager<'_> { +impl Default for PluginManager { fn default() -> Self { Self::new() } @@ -59,7 +59,7 @@ impl PlayerConnectionEvent for Event<'_> { } } -impl PluginManager<'_> { +impl PluginManager { #[must_use] pub fn new() -> Self { PluginManager { @@ -120,10 +120,10 @@ impl PluginManager<'_> { } pub fn emit_player_join(&mut self, player: &Player, world: &World) { + let event = Event { player, world }; for (metadata, _, hooks, _) in self.plugins.values_mut() { if hooks.registered_events().unwrap().contains(&"player_join") { let context = Context { metadata }; - let event = Event { player, world }; let _ = hooks.as_mut().on_player_join(&context, &event); } } From 906e1e017fdf4b1c9280ccd2e406aec8929dc3da Mon Sep 17 00:00:00 2001 From: vyPal Date: Mon, 25 Nov 2024 18:29:22 +0100 Subject: [PATCH 13/62] Add default implementations to events --- pumpkin/src/plugin/api/events.rs | 12 +++++++++--- pumpkin/src/plugin/api/mod.rs | 8 ++++++-- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/pumpkin/src/plugin/api/events.rs b/pumpkin/src/plugin/api/events.rs index 56ed41d02..06536619d 100644 --- a/pumpkin/src/plugin/api/events.rs +++ b/pumpkin/src/plugin/api/events.rs @@ -4,13 +4,19 @@ use super::PluginContext; pub trait Hooks: Send + Sync + 'static { /// Returns an array of events that the - fn registered_events(&self) -> Result<&'static[&'static str], String>; + fn registered_events(&self) -> Result<&'static[&'static str], String> { + Ok(&[]) + } /// Called when the plugin is loaded. - fn on_player_join(&mut self, server: &dyn PluginContext, event: &dyn PlayerConnectionEvent) -> Result<(), String>; + fn on_player_join(&mut self, _server: &dyn PluginContext, _event: &dyn PlayerConnectionEvent) -> Result<(), String> { + Ok(()) + } /// Called when the plugin is unloaded. - fn on_player_leave(&mut self, server: &dyn PluginContext, event: &dyn PlayerConnectionEvent) -> Result<(), String>; + fn on_player_leave(&mut self, _server: &dyn PluginContext, _event: &dyn PlayerConnectionEvent) -> Result<(), String> { + Ok(()) + } } pub trait PlayerConnectionEvent { diff --git a/pumpkin/src/plugin/api/mod.rs b/pumpkin/src/plugin/api/mod.rs index 6efa16b60..c960aef28 100644 --- a/pumpkin/src/plugin/api/mod.rs +++ b/pumpkin/src/plugin/api/mod.rs @@ -20,10 +20,14 @@ pub struct PluginMetadata<'s> { pub trait Plugin: Send + Sync + 'static { /// Called when the plugin is loaded. - fn on_load(&mut self, server: &dyn PluginContext) -> Result<(), String>; + fn on_load(&mut self, _server: &dyn PluginContext) -> Result<(), String> { + Ok(()) + } /// Called when the plugin is unloaded. - fn on_unload(&mut self, server: &dyn PluginContext) -> Result<(), String>; + fn on_unload(&mut self, _server: &dyn PluginContext) -> Result<(), String> { + Ok(()) + } } #[macro_export] From 50bdf0d1cc69bbd4f4ff9d667f9b830bc67bf14f Mon Sep 17 00:00:00 2001 From: vyPal Date: Mon, 25 Nov 2024 18:29:33 +0100 Subject: [PATCH 14/62] Add hooks to proc macro --- pumpkin-api-macros/src/lib.rs | 81 +++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/pumpkin-api-macros/src/lib.rs b/pumpkin-api-macros/src/lib.rs index 2fd6730e4..f0615c464 100644 --- a/pumpkin-api-macros/src/lib.rs +++ b/pumpkin-api-macros/src/lib.rs @@ -7,6 +7,10 @@ use syn::{parse_macro_input, ItemFn, ItemStruct}; static PLUGIN_METHODS: Lazy>>> = Lazy::new(|| Mutex::new(HashMap::new())); +static PLUGIN_HOOKS: Lazy>>> = + Lazy::new(|| Mutex::new(HashMap::new())); +static PLUGIN_EVENT_NAMES: Lazy>>> = + Lazy::new(|| Mutex::new(HashMap::new())); #[proc_macro_attribute] pub fn plugin_method(attr: TokenStream, item: TokenStream) -> TokenStream { @@ -40,6 +44,45 @@ pub fn plugin_method(attr: TokenStream, item: TokenStream) -> TokenStream { TokenStream::new() } +#[proc_macro_attribute] +pub fn plugin_event(attr: TokenStream, item: TokenStream) -> TokenStream { + let input_fn = parse_macro_input!(item as ItemFn); + let fn_name = &input_fn.sig.ident; + let fn_inputs = &input_fn.sig.inputs; + let fn_output = &input_fn.sig.output; + let fn_body = &input_fn.block; + + let struct_name = if attr.is_empty() { + "MyPlugin".to_string() + } else { + attr.to_string().trim().to_string() + }; + + let method = quote! { + #[allow(unused_mut)] + fn #fn_name(#fn_inputs) #fn_output { + #fn_body + } + } + .to_string(); + + PLUGIN_HOOKS + .lock() + .unwrap() + .entry(struct_name.clone()) + .or_default() + .push(method); + + PLUGIN_EVENT_NAMES + .lock() + .unwrap() + .entry(struct_name) + .or_default() + .push("\"".to_owned() + &fn_name.to_string().trim_start_matches("on_").to_string() + "\""); + + TokenStream::new() +} + #[proc_macro_attribute] pub fn plugin_impl(attr: TokenStream, item: TokenStream) -> TokenStream { // Parse the input struct @@ -65,6 +108,30 @@ pub fn plugin_impl(attr: TokenStream, item: TokenStream) -> TokenStream { .filter_map(|method_str| method_str.parse().ok()) .collect(); + let hooks = PLUGIN_HOOKS + .lock() + .unwrap() + .remove(&struct_name.to_string()) + .unwrap_or_default(); + + let hooks: Vec = hooks + .iter() + .filter_map(|method_str| method_str.parse().ok()) + .collect(); + + let events = PLUGIN_EVENT_NAMES + .lock() + .unwrap() + .remove(&struct_name.to_string()) + .unwrap_or_default(); + + let events: Vec = events + .iter() + .filter_map(|method_str| method_str.parse().ok()) + .collect(); + + let event_names = events.iter().map(|event| quote::quote!(#event)).collect::>(); + // Combine the original struct definition with the impl block and plugin() function let expanded = quote! { #input_struct @@ -73,10 +140,24 @@ pub fn plugin_impl(attr: TokenStream, item: TokenStream) -> TokenStream { #(#methods)* } + impl pumpkin::plugin::Hooks for #struct_ident { + fn registered_events(&self) -> Result<&'static [&'static str], String> { + static EVENTS: &[&str] = &[#(#event_names),*]; + Ok(EVENTS) + } + + #(#hooks)* + } + #[no_mangle] pub fn plugin() -> Box { Box::new(#struct_ident {}) } + + #[no_mangle] + pub fn hooks() -> Box { + Box::new(#struct_ident {}) + } }; TokenStream::from(expanded) From 3388734b7b591c63facefb0cfda6393b1d12ad9c Mon Sep 17 00:00:00 2001 From: vyPal Date: Mon, 25 Nov 2024 18:29:40 +0100 Subject: [PATCH 15/62] Update example --- plugins/hello-plugin-source/src/lib.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/plugins/hello-plugin-source/src/lib.rs b/plugins/hello-plugin-source/src/lib.rs index 06634342f..bb395de4d 100644 --- a/plugins/hello-plugin-source/src/lib.rs +++ b/plugins/hello-plugin-source/src/lib.rs @@ -1,6 +1,6 @@ use pumpkin::plugin::*; use pumpkin::plugin_metadata; -use pumpkin_api_macros::{plugin_impl, plugin_method}; +use pumpkin_api_macros::{plugin_impl, plugin_method, plugin_event}; plugin_metadata!( "Plugin name", @@ -22,5 +22,11 @@ fn on_unload(&mut self, server: &dyn PluginContext) -> Result<(), String> { Ok(()) } +#[plugin_event] +fn on_player_join(&mut self, server: &dyn PluginContext, event: &dyn PlayerConnectionEvent) -> Result<(), String> { + server.get_logger().info(format!("Player {} joined the game", event.get_player().gameprofile.name).as_str()); + Ok(()) +} + #[plugin_impl] pub struct MyPlugin {} From 8f7dc0430af7a2c886fc4d33abc80ddf35529867 Mon Sep 17 00:00:00 2001 From: vyPal Date: Mon, 25 Nov 2024 18:30:36 +0100 Subject: [PATCH 16/62] Fix formatting --- pumpkin-api-macros/src/lib.rs | 5 ++++- pumpkin/src/lib.rs | 2 +- pumpkin/src/plugin/api/context.rs | 2 +- pumpkin/src/plugin/api/events.rs | 18 +++++++++++++----- pumpkin/src/plugin/mod.rs | 17 +++++++++++++++-- pumpkin/src/server/mod.rs | 5 ++++- 6 files changed, 38 insertions(+), 11 deletions(-) diff --git a/pumpkin-api-macros/src/lib.rs b/pumpkin-api-macros/src/lib.rs index f0615c464..cc9f0feb9 100644 --- a/pumpkin-api-macros/src/lib.rs +++ b/pumpkin-api-macros/src/lib.rs @@ -130,7 +130,10 @@ pub fn plugin_impl(attr: TokenStream, item: TokenStream) -> TokenStream { .filter_map(|method_str| method_str.parse().ok()) .collect(); - let event_names = events.iter().map(|event| quote::quote!(#event)).collect::>(); + let event_names = events + .iter() + .map(|event| quote::quote!(#event)) + .collect::>(); // Combine the original struct definition with the impl block and plugin() function let expanded = quote! { diff --git a/pumpkin/src/lib.rs b/pumpkin/src/lib.rs index 4ff442c3d..ba15a692b 100644 --- a/pumpkin/src/lib.rs +++ b/pumpkin/src/lib.rs @@ -10,4 +10,4 @@ pub mod proxy; pub mod server; pub mod world; -const GIT_VERSION: &str = env!("GIT_VERSION"); \ No newline at end of file +const GIT_VERSION: &str = env!("GIT_VERSION"); diff --git a/pumpkin/src/plugin/api/context.rs b/pumpkin/src/plugin/api/context.rs index ea6cfb0c9..929f81141 100644 --- a/pumpkin/src/plugin/api/context.rs +++ b/pumpkin/src/plugin/api/context.rs @@ -6,4 +6,4 @@ pub trait Logger { fn info(&self, message: &str); fn warn(&self, message: &str); fn error(&self, message: &str); -} \ No newline at end of file +} diff --git a/pumpkin/src/plugin/api/events.rs b/pumpkin/src/plugin/api/events.rs index 06536619d..ec2466cd9 100644 --- a/pumpkin/src/plugin/api/events.rs +++ b/pumpkin/src/plugin/api/events.rs @@ -3,18 +3,26 @@ use crate::{entity::player::Player, world::World}; use super::PluginContext; pub trait Hooks: Send + Sync + 'static { - /// Returns an array of events that the - fn registered_events(&self) -> Result<&'static[&'static str], String> { + /// Returns an array of events that the + fn registered_events(&self) -> Result<&'static [&'static str], String> { Ok(&[]) } /// Called when the plugin is loaded. - fn on_player_join(&mut self, _server: &dyn PluginContext, _event: &dyn PlayerConnectionEvent) -> Result<(), String> { + fn on_player_join( + &mut self, + _server: &dyn PluginContext, + _event: &dyn PlayerConnectionEvent, + ) -> Result<(), String> { Ok(()) } /// Called when the plugin is unloaded. - fn on_player_leave(&mut self, _server: &dyn PluginContext, _event: &dyn PlayerConnectionEvent) -> Result<(), String> { + fn on_player_leave( + &mut self, + _server: &dyn PluginContext, + _event: &dyn PlayerConnectionEvent, + ) -> Result<(), String> { Ok(()) } } @@ -22,4 +30,4 @@ pub trait Hooks: Send + Sync + 'static { pub trait PlayerConnectionEvent { fn get_player(&self) -> &Player; fn get_world(&self) -> &World; -} \ No newline at end of file +} diff --git a/pumpkin/src/plugin/mod.rs b/pumpkin/src/plugin/mod.rs index bab76146a..1a3f9cb93 100644 --- a/pumpkin/src/plugin/mod.rs +++ b/pumpkin/src/plugin/mod.rs @@ -6,7 +6,15 @@ use std::{collections::HashMap, fs, path::Path}; use crate::{entity::player::Player, world::World}; pub struct PluginManager { - plugins: HashMap, Box, Box, libloading::Library)>, + plugins: HashMap< + String, + ( + PluginMetadata<'static>, + Box, + Box, + libloading::Library, + ), + >, } impl Default for PluginManager { @@ -103,7 +111,12 @@ impl PluginManager { pub fn get_plugin( &self, name: &str, - ) -> Option<&(PluginMetadata, Box, Box, libloading::Library)> { + ) -> Option<&( + PluginMetadata, + Box, + Box, + libloading::Library, + )> { self.plugins.get(name) } diff --git a/pumpkin/src/server/mod.rs b/pumpkin/src/server/mod.rs index ccf903407..13fd09e2c 100644 --- a/pumpkin/src/server/mod.rs +++ b/pumpkin/src/server/mod.rs @@ -167,7 +167,10 @@ impl Server { } } - self.plugin_manager.lock().await.emit_player_join(&player, world); + self.plugin_manager + .lock() + .await + .emit_player_join(&player, world); (player, world.clone()) } From c3b7d15eb89cb2729d1ba52115edf03f08edb836 Mon Sep 17 00:00:00 2001 From: vyPal Date: Mon, 25 Nov 2024 18:35:02 +0100 Subject: [PATCH 17/62] Fix clippy warnings --- pumpkin-api-macros/src/lib.rs | 2 +- pumpkin/src/plugin/mod.rs | 27 +++++++++------------------ 2 files changed, 10 insertions(+), 19 deletions(-) diff --git a/pumpkin-api-macros/src/lib.rs b/pumpkin-api-macros/src/lib.rs index cc9f0feb9..ecf790a30 100644 --- a/pumpkin-api-macros/src/lib.rs +++ b/pumpkin-api-macros/src/lib.rs @@ -78,7 +78,7 @@ pub fn plugin_event(attr: TokenStream, item: TokenStream) -> TokenStream { .unwrap() .entry(struct_name) .or_default() - .push("\"".to_owned() + &fn_name.to_string().trim_start_matches("on_").to_string() + "\""); + .push("\"".to_owned() + fn_name.to_string().trim_start_matches("on_") + "\""); TokenStream::new() } diff --git a/pumpkin/src/plugin/mod.rs b/pumpkin/src/plugin/mod.rs index 1a3f9cb93..3a09e5a58 100644 --- a/pumpkin/src/plugin/mod.rs +++ b/pumpkin/src/plugin/mod.rs @@ -5,16 +5,15 @@ use std::{collections::HashMap, fs, path::Path}; use crate::{entity::player::Player, world::World}; +type PluginData = ( + PluginMetadata<'static>, + Box, + Box, + libloading::Library, +); + pub struct PluginManager { - plugins: HashMap< - String, - ( - PluginMetadata<'static>, - Box, - Box, - libloading::Library, - ), - >, + plugins: HashMap, } impl Default for PluginManager { @@ -108,15 +107,7 @@ impl PluginManager { } #[must_use] - pub fn get_plugin( - &self, - name: &str, - ) -> Option<&( - PluginMetadata, - Box, - Box, - libloading::Library, - )> { + pub fn get_plugin(&self, name: &str) -> Option<&PluginData> { self.plugins.get(name) } From b60a2cc762c32b19a0d536469cca9533fc60c1ab Mon Sep 17 00:00:00 2001 From: vyPal Date: Tue, 26 Nov 2024 10:44:05 +0100 Subject: [PATCH 18/62] Load metadata from Cargo.toml --- plugins/hello-plugin-source/src/lib.rs | 11 +---------- pumpkin-api-macros/src/lib.rs | 27 ++++++++++++++++++-------- pumpkin/src/plugin/api/mod.rs | 20 ++----------------- 3 files changed, 22 insertions(+), 36 deletions(-) diff --git a/plugins/hello-plugin-source/src/lib.rs b/plugins/hello-plugin-source/src/lib.rs index bb395de4d..1becea1ea 100644 --- a/plugins/hello-plugin-source/src/lib.rs +++ b/plugins/hello-plugin-source/src/lib.rs @@ -1,15 +1,6 @@ use pumpkin::plugin::*; -use pumpkin::plugin_metadata; use pumpkin_api_macros::{plugin_impl, plugin_method, plugin_event}; -plugin_metadata!( - "Plugin name", - "plugin-id", - "1.0.0", - &["Author Name"], - "Description" -); - #[plugin_method] fn on_load(&mut self, server: &dyn PluginContext) -> Result<(), String> { server.get_logger().info("Plugin loaded!"); @@ -23,7 +14,7 @@ fn on_unload(&mut self, server: &dyn PluginContext) -> Result<(), String> { } #[plugin_event] -fn on_player_join(&mut self, server: &dyn PluginContext, event: &dyn PlayerConnectionEvent) -> Result<(), String> { +fn on_player_join(&mut self, server: &dyn PluginContext, event: &mut dyn PlayerConnectionEvent) -> Result<(), String> { server.get_logger().info(format!("Player {} joined the game", event.get_player().gameprofile.name).as_str()); Ok(()) } diff --git a/pumpkin-api-macros/src/lib.rs b/pumpkin-api-macros/src/lib.rs index ecf790a30..738c6d75a 100644 --- a/pumpkin-api-macros/src/lib.rs +++ b/pumpkin-api-macros/src/lib.rs @@ -66,6 +66,14 @@ pub fn plugin_event(attr: TokenStream, item: TokenStream) -> TokenStream { } .to_string(); + let event = quote! { + pumpkin::plugin::EventDescriptor { + name: "#fn_name", + priority: pumpkin::plugin::EventPriority::Normal, + blocking: false, + } + }.to_string(); + PLUGIN_HOOKS .lock() .unwrap() @@ -78,7 +86,7 @@ pub fn plugin_event(attr: TokenStream, item: TokenStream) -> TokenStream { .unwrap() .entry(struct_name) .or_default() - .push("\"".to_owned() + fn_name.to_string().trim_start_matches("on_") + "\""); + .push(event); TokenStream::new() } @@ -130,13 +138,16 @@ pub fn plugin_impl(attr: TokenStream, item: TokenStream) -> TokenStream { .filter_map(|method_str| method_str.parse().ok()) .collect(); - let event_names = events - .iter() - .map(|event| quote::quote!(#event)) - .collect::>(); - // Combine the original struct definition with the impl block and plugin() function let expanded = quote! { + #[no_mangle] + pub static METADATA: pumpkin::plugin::PluginMetadata = pumpkin::plugin::PluginMetadata { + name: env!("CARGO_PKG_NAME"), + version: env!("CARGO_PKG_VERSION"), + authors: env!("CARGO_PKG_AUTHORS"), + description: env!("CARGO_PKG_DESCRIPTION"), + }; + #input_struct impl pumpkin::plugin::Plugin for #struct_ident { @@ -144,8 +155,8 @@ pub fn plugin_impl(attr: TokenStream, item: TokenStream) -> TokenStream { } impl pumpkin::plugin::Hooks for #struct_ident { - fn registered_events(&self) -> Result<&'static [&'static str], String> { - static EVENTS: &[&str] = &[#(#event_names),*]; + fn registered_events(&self) -> Result<&'static [pumpkin::plugin::EventDescriptor], String> { + static EVENTS: &[EventDescriptor] = &[#(#events),*]; Ok(EVENTS) } diff --git a/pumpkin/src/plugin/api/mod.rs b/pumpkin/src/plugin/api/mod.rs index c960aef28..90120b1b4 100644 --- a/pumpkin/src/plugin/api/mod.rs +++ b/pumpkin/src/plugin/api/mod.rs @@ -8,14 +8,12 @@ pub use events::*; pub struct PluginMetadata<'s> { /// The name of the plugin. pub name: &'s str, - /// The unique identifier of the plugin. - pub id: &'s str, /// The version of the plugin. pub version: &'s str, /// The authors of the plugin. - pub authors: &'s [&'s str], + pub authors: &'s str, /// A description of the plugin. - pub description: Option<&'s str>, + pub description: &'s str, } pub trait Plugin: Send + Sync + 'static { @@ -29,17 +27,3 @@ pub trait Plugin: Send + Sync + 'static { Ok(()) } } - -#[macro_export] -macro_rules! plugin_metadata { - ($name:expr, $id:expr, $version:expr, $authors:expr, $description:expr) => { - #[no_mangle] - pub static METADATA: pumpkin::plugin::PluginMetadata = pumpkin::plugin::PluginMetadata { - name: $name, - id: $id, - version: $version, - authors: $authors, - description: Some($description), - }; - }; -} From 6ab3d182a1894cd3a54137b7be1cdea56f16df87 Mon Sep 17 00:00:00 2001 From: vyPal Date: Tue, 26 Nov 2024 17:32:10 +0100 Subject: [PATCH 19/62] Mark plugins as an implemented feature :D --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 46fb97616..70ada55c2 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,7 @@ and customizable experience. It prioritizes performance and player enjoyment whi - [ ] Minecart - [ ] Boss - Server - - [ ] Plugins + - [x] Plugins - [x] Query - [x] RCON - [x] Inventories From c8ba17eb3b0cec42b3c730c0ab7745b22635694f Mon Sep 17 00:00:00 2001 From: vyPal Date: Tue, 26 Nov 2024 19:01:24 +0100 Subject: [PATCH 20/62] Implement new event handling --- pumpkin/src/plugin/api/events.rs | 67 +++++++++++++++++++- pumpkin/src/plugin/mod.rs | 102 ++++++++++++++++++++++--------- 2 files changed, 138 insertions(+), 31 deletions(-) diff --git a/pumpkin/src/plugin/api/events.rs b/pumpkin/src/plugin/api/events.rs index ec2466cd9..e05bde37f 100644 --- a/pumpkin/src/plugin/api/events.rs +++ b/pumpkin/src/plugin/api/events.rs @@ -2,9 +2,70 @@ use crate::{entity::player::Player, world::World}; use super::PluginContext; +#[derive(Eq, PartialEq, Ord, PartialOrd)] +pub enum EventPriority { + Highest, + High, + Normal, + Low, + Lowest, +} + +pub struct EventDescriptor { + pub name: &'static str, + pub priority: EventPriority, + pub blocking: bool, +} + +pub trait Event { + fn handle(&mut self, server: &dyn PluginContext, hooks: &mut dyn Hooks) -> Result<(), String>; + fn is_cancelled(&self) -> bool; + fn cancel(&mut self) -> Result<(), String>; +} + +pub struct PlayerConnection<'a> { + pub player: &'a Player, + pub world: &'a World, + pub is_cancelled: bool, + pub is_join: bool, +} + +impl PlayerConnectionEvent for PlayerConnection<'_> { + fn get_player(&self) -> &Player { + self.player + } + + fn get_world(&self) -> &World { + self.world + } +} + +impl Event for PlayerConnection<'_> { + fn handle(&mut self, server: &dyn PluginContext, hooks: &mut dyn Hooks) -> Result<(), String> { + if self.is_cancelled { + return Ok(()); + } + + if self.is_join { + hooks.on_player_join(server, self) + } else { + hooks.on_player_leave(server, self) + } + } + + fn is_cancelled(&self) -> bool { + self.is_cancelled + } + + fn cancel(&mut self) -> Result<(), String> { + self.is_cancelled = true; + Ok(()) + } +} + pub trait Hooks: Send + Sync + 'static { /// Returns an array of events that the - fn registered_events(&self) -> Result<&'static [&'static str], String> { + fn registered_events(&self) -> Result<&'static [EventDescriptor], String> { Ok(&[]) } @@ -12,7 +73,7 @@ pub trait Hooks: Send + Sync + 'static { fn on_player_join( &mut self, _server: &dyn PluginContext, - _event: &dyn PlayerConnectionEvent, + _event: &mut PlayerConnection, ) -> Result<(), String> { Ok(()) } @@ -21,7 +82,7 @@ pub trait Hooks: Send + Sync + 'static { fn on_player_leave( &mut self, _server: &dyn PluginContext, - _event: &dyn PlayerConnectionEvent, + _event: &mut PlayerConnection, ) -> Result<(), String> { Ok(()) } diff --git a/pumpkin/src/plugin/mod.rs b/pumpkin/src/plugin/mod.rs index 3a09e5a58..a56afedc5 100644 --- a/pumpkin/src/plugin/mod.rs +++ b/pumpkin/src/plugin/mod.rs @@ -3,12 +3,10 @@ pub mod api; pub use api::*; use std::{collections::HashMap, fs, path::Path}; -use crate::{entity::player::Player, world::World}; - type PluginData = ( PluginMetadata<'static>, Box, - Box, + Box, libloading::Library, ); @@ -51,21 +49,6 @@ impl PluginContext for Context<'_> { } } -struct Event<'a> { - player: &'a Player, - world: &'a World, -} - -impl PlayerConnectionEvent for Event<'_> { - fn get_player(&self) -> &Player { - self.player - } - - fn get_world(&self) -> &World { - self.world - } -} - impl PluginManager { #[must_use] pub fn new() -> Self { @@ -93,7 +76,11 @@ impl PluginManager { let library = unsafe { libloading::Library::new(path).unwrap() }; let plugin_fn = unsafe { library.get:: Box>(b"plugin").unwrap() }; - let hooks_fn = unsafe { library.get:: Box>(b"hooks").unwrap() }; + let hooks_fn = unsafe { + library + .get:: Box>(b"hooks") + .unwrap() + }; let metadata: &PluginMetadata = unsafe { &**library.get::<*const PluginMetadata>(b"METADATA").unwrap() }; @@ -114,22 +101,81 @@ impl PluginManager { pub fn list_plugins(&self) { for (metadata, _, _, _) in self.plugins.values() { println!( - "{}: {} v{} by {}", - metadata.id, - metadata.name, - metadata.version, - metadata.authors.join(", ") + "{} v{} by {}", + metadata.name, metadata.version, metadata.authors ); } } - pub fn emit_player_join(&mut self, player: &Player, world: &World) { - let event = Event { player, world }; + pub fn emit(&mut self, event_name: &str, event_data: &mut T) -> bool { + let mut blocking_hooks = Vec::new(); + let mut non_blocking_hooks = Vec::new(); + for (metadata, _, hooks, _) in self.plugins.values_mut() { - if hooks.registered_events().unwrap().contains(&"player_join") { + if hooks + .registered_events() + .unwrap() + .iter() + .any(|e| e.name == event_name) + { let context = Context { metadata }; - let _ = hooks.as_mut().on_player_join(&context, &event); + if hooks + .registered_events() + .unwrap() + .iter() + .any(|e| e.name == event_name && e.blocking) + { + blocking_hooks.push((context, hooks)); + } else { + non_blocking_hooks.push((context, hooks)); + } + } + } + + blocking_hooks.sort_by(|a, b| { + b.1.registered_events() + .unwrap() + .iter() + .find(|e| e.name == event_name) + .unwrap() + .priority + .cmp( + &a.1.registered_events() + .unwrap() + .iter() + .find(|e| e.name == event_name) + .unwrap() + .priority, + ) + }); + non_blocking_hooks.sort_by(|a, b| { + b.1.registered_events() + .unwrap() + .iter() + .find(|e| e.name == event_name) + .unwrap() + .priority + .cmp( + &a.1.registered_events() + .unwrap() + .iter() + .find(|e| e.name == event_name) + .unwrap() + .priority, + ) + }); + + for (context, hooks) in blocking_hooks { + let _ = event_data.handle(&context, hooks.as_mut()); + if event_data.is_cancelled() { + return true; } } + + for (context, hooks) in non_blocking_hooks { + let _ = event_data.handle(&context, hooks.as_mut()); + } + + false } } From fd2fbdbf3245264b185a0a088e69a9a22e6b9cd2 Mon Sep 17 00:00:00 2001 From: vyPal Date: Tue, 26 Nov 2024 19:01:57 +0100 Subject: [PATCH 21/62] Create a static global reference to the plugin manager --- pumpkin/src/lib.rs | 6 ++++++ pumpkin/src/main.rs | 11 ++++++++++- pumpkin/src/server/mod.rs | 17 ----------------- 3 files changed, 16 insertions(+), 18 deletions(-) diff --git a/pumpkin/src/lib.rs b/pumpkin/src/lib.rs index ba15a692b..5006dc141 100644 --- a/pumpkin/src/lib.rs +++ b/pumpkin/src/lib.rs @@ -1,4 +1,7 @@ +use std::sync::{LazyLock, Mutex}; + use client::Client; +use plugin::PluginManager; use pumpkin_core::text::TextComponent; pub mod client; @@ -11,3 +14,6 @@ pub mod server; pub mod world; const GIT_VERSION: &str = env!("GIT_VERSION"); + +pub static PLUGIN_MANAGER: LazyLock> = + LazyLock::new(|| Mutex::new(PluginManager::new())); diff --git a/pumpkin/src/main.rs b/pumpkin/src/main.rs index 9ff778ef7..1dc35b6d8 100644 --- a/pumpkin/src/main.rs +++ b/pumpkin/src/main.rs @@ -20,8 +20,12 @@ compile_error!("Compiling for WASI targets is not supported!"); use log::LevelFilter; use client::Client; +use plugin::PluginManager; use server::{ticker::Ticker, Server}; -use std::io::{self}; +use std::{ + io::{self}, + sync::{LazyLock, Mutex}, +}; use tokio::io::{AsyncBufReadExt, BufReader}; #[cfg(not(unix))] use tokio::signal::ctrl_c; @@ -51,6 +55,9 @@ pub mod rcon; pub mod server; pub mod world; +pub static PLUGIN_MANAGER: LazyLock> = + LazyLock::new(|| Mutex::new(PluginManager::new())); + fn scrub_address(ip: &str) -> String { use pumpkin_config::BASIC_CONFIG; if BASIC_CONFIG.scrub_ips { @@ -234,6 +241,8 @@ async fn main() -> io::Result<()> { let server = Arc::new(Server::new()); let mut ticker = Ticker::new(BASIC_CONFIG.tps); + PLUGIN_MANAGER.lock().unwrap().load_plugins().unwrap(); + log::info!("Started Server took {}ms", time.elapsed().as_millis()); log::info!("You now can connect to the server, Listening on {}", addr); diff --git a/pumpkin/src/server/mod.rs b/pumpkin/src/server/mod.rs index 13fd09e2c..7205d9a6c 100644 --- a/pumpkin/src/server/mod.rs +++ b/pumpkin/src/server/mod.rs @@ -21,7 +21,6 @@ use std::{ use tokio::sync::{Mutex, RwLock}; use crate::client::EncryptionError; -use crate::plugin::PluginManager; use crate::{ client::Client, command::{default_dispatcher, dispatcher::CommandDispatcher}, @@ -58,8 +57,6 @@ pub struct Server { entity_id: AtomicI32, /// Manages authentication with a authentication server, if enabled. pub auth_client: Option, - /// Plugin manager - pub plugin_manager: Mutex, } impl Server { @@ -90,14 +87,6 @@ impl Server { DimensionType::Overworld, ); - // Plugins - let mut plugin_manager = PluginManager::new(); - plugin_manager - .load_plugins() - .expect("Failed to load plugins"); - - plugin_manager.list_plugins(); - Self { cached_registry: Registry::get_synced(), open_containers: RwLock::new(HashMap::new()), @@ -116,7 +105,6 @@ impl Server { key_store: KeyStore::new(), server_listing: Mutex::new(CachedStatus::new()), server_branding: CachedBranding::new(), - plugin_manager: Mutex::new(plugin_manager), } } @@ -167,11 +155,6 @@ impl Server { } } - self.plugin_manager - .lock() - .await - .emit_player_join(&player, world); - (player, world.clone()) } From 8848e9bc14978b7ff63c44ecddd540bd2447c20c Mon Sep 17 00:00:00 2001 From: vyPal Date: Tue, 26 Nov 2024 19:02:16 +0100 Subject: [PATCH 22/62] Emit player join and leave events --- pumpkin/src/world/mod.rs | 58 +++++++++++++++++++++++++++++----------- 1 file changed, 43 insertions(+), 15 deletions(-) diff --git a/pumpkin/src/world/mod.rs b/pumpkin/src/world/mod.rs index a6e326f90..c3cfb9d24 100644 --- a/pumpkin/src/world/mod.rs +++ b/pumpkin/src/world/mod.rs @@ -12,7 +12,9 @@ use crate::{ Entity, }, error::PumpkinError, + plugin::api::events::PlayerConnection, server::Server, + PLUGIN_MANAGER, }; use itertools::Itertools; use pumpkin_config::BasicConfiguration; @@ -685,14 +687,27 @@ impl World { let mut current_players = self.current_players.lock().await; current_players.insert(uuid, player.clone()); - // Handle join message - // TODO: Config - let msg_txt = format!("{} joined the game.", player.gameprofile.name.as_str()); - let msg_comp = TextComponent::text(msg_txt.as_str()).color_named(NamedColor::Yellow); - for player in current_players.values() { - player.send_system_message(&msg_comp).await; + let mut event_data = PlayerConnection { + player: &player.clone(), + world: self, + is_cancelled: false, + is_join: true, + }; + + if !PLUGIN_MANAGER + .lock() + .unwrap() + .emit::("player_join", &mut event_data) + { + // Handle join message + // TODO: Config + let msg_txt = format!("{} joined the game.", player.gameprofile.name.as_str()); + let msg_comp = TextComponent::text(msg_txt.as_str()).color_named(NamedColor::Yellow); + for player in current_players.values() { + player.send_system_message(&msg_comp).await; + } + log::info!("{}", msg_comp.to_pretty_console()); } - log::info!("{}", msg_comp.to_pretty_console()); } /// Removes a player from the world and broadcasts a disconnect message if enabled. @@ -727,15 +742,28 @@ impl World { .await; self.remove_entity(&player.living_entity.entity).await; - // Send disconnect message / quit message to players in the same world - // TODO: Config - let disconn_msg_txt = format!("{} left the game.", player.gameprofile.name.as_str()); - let disconn_msg_cmp = - TextComponent::text(disconn_msg_txt.as_str()).color_named(NamedColor::Yellow); - for player in self.current_players.lock().await.values() { - player.send_system_message(&disconn_msg_cmp).await; + let mut event_data = PlayerConnection { + player: &player, + world: self, + is_cancelled: false, + is_join: false, + }; + + if !PLUGIN_MANAGER + .lock() + .unwrap() + .emit::("player_leave", &mut event_data) + { + // Send disconnect message / quit message to players in the same world + // TODO: Config + let disconn_msg_txt = format!("{} left the game.", player.gameprofile.name.as_str()); + let disconn_msg_cmp = + TextComponent::text(disconn_msg_txt.as_str()).color_named(NamedColor::Yellow); + for player in self.current_players.lock().await.values() { + player.send_system_message(&disconn_msg_cmp).await; + } + log::info!("{}", disconn_msg_cmp.to_pretty_console()); } - log::info!("{}", disconn_msg_cmp.to_pretty_console()); } pub async fn remove_entity(&self, entity: &Entity) { From 32f340e1b96243e289616db5f2e70609bae7fed2 Mon Sep 17 00:00:00 2001 From: vyPal Date: Tue, 26 Nov 2024 19:02:25 +0100 Subject: [PATCH 23/62] Update macro generation --- pumpkin-api-macros/src/lib.rs | 54 +++++++++++++++++++++++++++++------ 1 file changed, 45 insertions(+), 9 deletions(-) diff --git a/pumpkin-api-macros/src/lib.rs b/pumpkin-api-macros/src/lib.rs index 738c6d75a..435c42513 100644 --- a/pumpkin-api-macros/src/lib.rs +++ b/pumpkin-api-macros/src/lib.rs @@ -52,11 +52,43 @@ pub fn plugin_event(attr: TokenStream, item: TokenStream) -> TokenStream { let fn_output = &input_fn.sig.output; let fn_body = &input_fn.block; - let struct_name = if attr.is_empty() { - "MyPlugin".to_string() - } else { - attr.to_string().trim().to_string() - }; + let mut struct_name = "MyPlugin".to_string(); + let mut priority = quote! { pumpkin::plugin::EventPriority::Normal }; + let mut blocking = quote! { false }; + + if !attr.is_empty() { + let attr_string = attr.to_string(); + for pair in attr_string.split(',').map(str::trim) { + if let Some((key, value)) = pair.split_once('=') { + let key = key.trim(); + let value = value.trim().trim_matches('"'); + + match key { + "struct_name" => { + struct_name = value.to_string(); + } + "priority" => { + priority = match value { + "Highest" => quote! { pumpkin::plugin::EventPriority::Highest }, + "High" => quote! { pumpkin::plugin::EventPriority::High }, + "Normal" => quote! { pumpkin::plugin::EventPriority::Normal }, + "Low" => quote! { pumpkin::plugin::EventPriority::Low }, + "Lowest" => quote! { pumpkin::plugin::EventPriority::Lowest }, + _ => priority + }; + } + "blocking" => { + blocking = match value { + "true" => quote! { true }, + "false" => quote! { false }, + _ => blocking, + }; + } + _ => {} + } + } + } + } let method = quote! { #[allow(unused_mut)] @@ -66,13 +98,17 @@ pub fn plugin_event(attr: TokenStream, item: TokenStream) -> TokenStream { } .to_string(); + let binding = fn_name.to_string().to_owned(); + let fn_name_quoted = binding.trim_start_matches("on_"); + let event = quote! { pumpkin::plugin::EventDescriptor { - name: "#fn_name", - priority: pumpkin::plugin::EventPriority::Normal, - blocking: false, + name: #fn_name_quoted, + priority: #priority, + blocking: #blocking, } - }.to_string(); + } + .to_string(); PLUGIN_HOOKS .lock() From 4eb45b54d4e5493956368323cee351ac2dc32990 Mon Sep 17 00:00:00 2001 From: vyPal Date: Tue, 26 Nov 2024 19:02:31 +0100 Subject: [PATCH 24/62] Update example --- plugins/hello-plugin-source/src/lib.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/plugins/hello-plugin-source/src/lib.rs b/plugins/hello-plugin-source/src/lib.rs index 1becea1ea..28f5ec673 100644 --- a/plugins/hello-plugin-source/src/lib.rs +++ b/plugins/hello-plugin-source/src/lib.rs @@ -13,11 +13,17 @@ fn on_unload(&mut self, server: &dyn PluginContext) -> Result<(), String> { Ok(()) } -#[plugin_event] -fn on_player_join(&mut self, server: &dyn PluginContext, event: &mut dyn PlayerConnectionEvent) -> Result<(), String> { +#[plugin_event(blocking = true, priority = Highest)] +fn on_player_join(&mut self, server: &dyn PluginContext, event: &mut PlayerConnection) -> Result<(), String> { server.get_logger().info(format!("Player {} joined the game", event.get_player().gameprofile.name).as_str()); Ok(()) } +#[plugin_event] +fn on_player_leave(&mut self, server: &dyn PluginContext, event: &mut PlayerConnection) -> Result<(), String> { + server.get_logger().info(format!("Player {} left the game", event.get_player().gameprofile.name).as_str()); + Ok(()) +} + #[plugin_impl] pub struct MyPlugin {} From 8f3cc7868c5a91b7bac2784485bd184a40c2e1fb Mon Sep 17 00:00:00 2001 From: vyPal Date: Tue, 26 Nov 2024 19:02:58 +0100 Subject: [PATCH 25/62] Fix formatting --- pumpkin-api-macros/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pumpkin-api-macros/src/lib.rs b/pumpkin-api-macros/src/lib.rs index 435c42513..492110d87 100644 --- a/pumpkin-api-macros/src/lib.rs +++ b/pumpkin-api-macros/src/lib.rs @@ -74,7 +74,7 @@ pub fn plugin_event(attr: TokenStream, item: TokenStream) -> TokenStream { "Normal" => quote! { pumpkin::plugin::EventPriority::Normal }, "Low" => quote! { pumpkin::plugin::EventPriority::Low }, "Lowest" => quote! { pumpkin::plugin::EventPriority::Lowest }, - _ => priority + _ => priority, }; } "blocking" => { From c25d9ebf7c5ee9ff2cf24afcabf0c5ce0c7d3e29 Mon Sep 17 00:00:00 2001 From: vyPal Date: Tue, 26 Nov 2024 19:04:02 +0100 Subject: [PATCH 26/62] Fix clippy issue --- pumpkin/src/world/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pumpkin/src/world/mod.rs b/pumpkin/src/world/mod.rs index c3cfb9d24..e98e85c1f 100644 --- a/pumpkin/src/world/mod.rs +++ b/pumpkin/src/world/mod.rs @@ -743,7 +743,7 @@ impl World { self.remove_entity(&player.living_entity.entity).await; let mut event_data = PlayerConnection { - player: &player, + player, world: self, is_cancelled: false, is_join: false, From be6468f8ecd598ac964dc86fed58fc8f5b477838 Mon Sep 17 00:00:00 2001 From: vyPal Date: Wed, 27 Nov 2024 11:15:01 +0100 Subject: [PATCH 27/62] Simplify event handling --- plugins/hello-plugin-source/src/lib.rs | 14 +++--- pumpkin/src/plugin/api/events.rs | 65 +++----------------------- pumpkin/src/plugin/mod.rs | 57 +++++++++++++++++++--- pumpkin/src/world/mod.rs | 19 +------- 4 files changed, 68 insertions(+), 87 deletions(-) diff --git a/plugins/hello-plugin-source/src/lib.rs b/plugins/hello-plugin-source/src/lib.rs index 28f5ec673..86ebb4aa6 100644 --- a/plugins/hello-plugin-source/src/lib.rs +++ b/plugins/hello-plugin-source/src/lib.rs @@ -1,5 +1,6 @@ use pumpkin::plugin::*; use pumpkin_api_macros::{plugin_impl, plugin_method, plugin_event}; +use pumpkin::entity::player::Player; #[plugin_method] fn on_load(&mut self, server: &dyn PluginContext) -> Result<(), String> { @@ -14,15 +15,16 @@ fn on_unload(&mut self, server: &dyn PluginContext) -> Result<(), String> { } #[plugin_event(blocking = true, priority = Highest)] -fn on_player_join(&mut self, server: &dyn PluginContext, event: &mut PlayerConnection) -> Result<(), String> { - server.get_logger().info(format!("Player {} joined the game", event.get_player().gameprofile.name).as_str()); - Ok(()) +fn on_player_join(&mut self, server: &dyn PluginContext, player: &Player) -> Result { + server.get_logger().info(format!("Player {} joined the game", player.gameprofile.name).as_str()); + // Returning true will block any other plugins from receiving this event + Ok(true) } #[plugin_event] -fn on_player_leave(&mut self, server: &dyn PluginContext, event: &mut PlayerConnection) -> Result<(), String> { - server.get_logger().info(format!("Player {} left the game", event.get_player().gameprofile.name).as_str()); - Ok(()) +fn on_player_leave(&mut self, server: &dyn PluginContext, player: &Player) -> Result { + server.get_logger().info(format!("Player {} left the game", player.gameprofile.name).as_str()); + Ok(false) } #[plugin_impl] diff --git a/pumpkin/src/plugin/api/events.rs b/pumpkin/src/plugin/api/events.rs index e05bde37f..a1b14829a 100644 --- a/pumpkin/src/plugin/api/events.rs +++ b/pumpkin/src/plugin/api/events.rs @@ -1,4 +1,4 @@ -use crate::{entity::player::Player, world::World}; +use crate::entity::player::Player; use super::PluginContext; @@ -17,52 +17,6 @@ pub struct EventDescriptor { pub blocking: bool, } -pub trait Event { - fn handle(&mut self, server: &dyn PluginContext, hooks: &mut dyn Hooks) -> Result<(), String>; - fn is_cancelled(&self) -> bool; - fn cancel(&mut self) -> Result<(), String>; -} - -pub struct PlayerConnection<'a> { - pub player: &'a Player, - pub world: &'a World, - pub is_cancelled: bool, - pub is_join: bool, -} - -impl PlayerConnectionEvent for PlayerConnection<'_> { - fn get_player(&self) -> &Player { - self.player - } - - fn get_world(&self) -> &World { - self.world - } -} - -impl Event for PlayerConnection<'_> { - fn handle(&mut self, server: &dyn PluginContext, hooks: &mut dyn Hooks) -> Result<(), String> { - if self.is_cancelled { - return Ok(()); - } - - if self.is_join { - hooks.on_player_join(server, self) - } else { - hooks.on_player_leave(server, self) - } - } - - fn is_cancelled(&self) -> bool { - self.is_cancelled - } - - fn cancel(&mut self) -> Result<(), String> { - self.is_cancelled = true; - Ok(()) - } -} - pub trait Hooks: Send + Sync + 'static { /// Returns an array of events that the fn registered_events(&self) -> Result<&'static [EventDescriptor], String> { @@ -73,22 +27,17 @@ pub trait Hooks: Send + Sync + 'static { fn on_player_join( &mut self, _server: &dyn PluginContext, - _event: &mut PlayerConnection, - ) -> Result<(), String> { - Ok(()) + _event: &Player, + ) -> Result { + Ok(false) } /// Called when the plugin is unloaded. fn on_player_leave( &mut self, _server: &dyn PluginContext, - _event: &mut PlayerConnection, - ) -> Result<(), String> { - Ok(()) + _event: &Player, + ) -> Result { + Ok(false) } } - -pub trait PlayerConnectionEvent { - fn get_player(&self) -> &Player; - fn get_world(&self) -> &World; -} diff --git a/pumpkin/src/plugin/mod.rs b/pumpkin/src/plugin/mod.rs index a56afedc5..7b3217519 100644 --- a/pumpkin/src/plugin/mod.rs +++ b/pumpkin/src/plugin/mod.rs @@ -1,7 +1,7 @@ pub mod api; pub use api::*; -use std::{collections::HashMap, fs, path::Path}; +use std::{any::Any, collections::HashMap, fs, path::Path}; type PluginData = ( PluginMetadata<'static>, @@ -107,7 +107,7 @@ impl PluginManager { } } - pub fn emit(&mut self, event_name: &str, event_data: &mut T) -> bool { + pub fn emit(&mut self, event_name: &str, event: &T) -> bool { let mut blocking_hooks = Vec::new(); let mut non_blocking_hooks = Vec::new(); @@ -165,15 +165,60 @@ impl PluginManager { ) }); + let event = event as &dyn Any; + for (context, hooks) in blocking_hooks { - let _ = event_data.handle(&context, hooks.as_mut()); - if event_data.is_cancelled() { - return true; + let r = match event_name { + "player_join" => { + if let Some(event) = event.downcast_ref::() { + hooks.on_player_join(&context, event) + } else { + Ok(false) + } + } + "player_leave" => { + if let Some(event) = event.downcast_ref::() { + hooks.on_player_leave(&context, event) + } else { + Ok(false) + } + } + _ => Ok(false), + }; + match r { + Ok(true) => return true, + Err(e) => { + log::error!("Error in plugin: {}", e); + } + _ => {} } } for (context, hooks) in non_blocking_hooks { - let _ = event_data.handle(&context, hooks.as_mut()); + let r = match event_name { + "player_join" => { + if let Some(event) = event.downcast_ref::() { + hooks.on_player_join(&context, event) + } else { + Ok(false) + } + } + "player_leave" => { + if let Some(event) = event.downcast_ref::() { + hooks.on_player_leave(&context, event) + } else { + Ok(false) + } + } + _ => Ok(false), + }; + match r { + Ok(true) => continue, + Err(e) => { + log::error!("Error in plugin: {}", e); + } + _ => {} + } } false diff --git a/pumpkin/src/world/mod.rs b/pumpkin/src/world/mod.rs index e98e85c1f..0ef0f911c 100644 --- a/pumpkin/src/world/mod.rs +++ b/pumpkin/src/world/mod.rs @@ -12,7 +12,6 @@ use crate::{ Entity, }, error::PumpkinError, - plugin::api::events::PlayerConnection, server::Server, PLUGIN_MANAGER, }; @@ -687,17 +686,10 @@ impl World { let mut current_players = self.current_players.lock().await; current_players.insert(uuid, player.clone()); - let mut event_data = PlayerConnection { - player: &player.clone(), - world: self, - is_cancelled: false, - is_join: true, - }; - if !PLUGIN_MANAGER .lock() .unwrap() - .emit::("player_join", &mut event_data) + .emit::("player_join", &player.clone()) { // Handle join message // TODO: Config @@ -742,17 +734,10 @@ impl World { .await; self.remove_entity(&player.living_entity.entity).await; - let mut event_data = PlayerConnection { - player, - world: self, - is_cancelled: false, - is_join: false, - }; - if !PLUGIN_MANAGER .lock() .unwrap() - .emit::("player_leave", &mut event_data) + .emit::("player_leave", &player) { // Send disconnect message / quit message to players in the same world // TODO: Config From dd6a2b9bf024ddfdcb3a8050256cb40390159471 Mon Sep 17 00:00:00 2001 From: vyPal Date: Thu, 28 Nov 2024 09:14:00 +0100 Subject: [PATCH 28/62] Add plugin command to list plugins --- pumpkin/src/command/commands/cmd_plugins.rs | 50 +++++++++++++++++++++ pumpkin/src/command/commands/mod.rs | 1 + pumpkin/src/command/mod.rs | 3 +- pumpkin/src/lib.rs | 3 +- pumpkin/src/main.rs | 5 ++- pumpkin/src/plugin/mod.rs | 38 ++++++++-------- pumpkin/src/world/mod.rs | 4 +- 7 files changed, 78 insertions(+), 26 deletions(-) create mode 100644 pumpkin/src/command/commands/cmd_plugins.rs diff --git a/pumpkin/src/command/commands/cmd_plugins.rs b/pumpkin/src/command/commands/cmd_plugins.rs new file mode 100644 index 000000000..c77783910 --- /dev/null +++ b/pumpkin/src/command/commands/cmd_plugins.rs @@ -0,0 +1,50 @@ +use async_trait::async_trait; +use itertools::Itertools; +use pumpkin_core::text::TextComponent; + +use crate::{ + command::{ + args::ConsumedArgs, tree::CommandTree, CommandError, CommandExecutor, CommandSender, + }, + PLUGIN_MANAGER, +}; + +const NAMES: [&str; 1] = ["plugins"]; + +const DESCRIPTION: &str = "List all available plugins."; + +struct ListExecutor; + +#[async_trait] +impl CommandExecutor for ListExecutor { + async fn execute<'a>( + &self, + sender: &mut CommandSender<'a>, + _server: &crate::server::Server, + _args: &ConsumedArgs<'a>, + ) -> Result<(), CommandError> { + let plugin_manager = PLUGIN_MANAGER.lock().await; + let plugins = plugin_manager.list_plugins(); + + let message = if plugins.is_empty() { + "There are no loaded plugins." + } else { + &format!( + "There are {} plugins loaded: {}", + plugins.len(), + plugins + .iter() + .map(|(plugin, _)| plugin.name) + .join(", ") + ) + }; + + sender.send_message(TextComponent::text(message)).await; + + Ok(()) + } +} + +pub fn init_command_tree<'a>() -> CommandTree<'a> { + CommandTree::new(NAMES, DESCRIPTION).execute(&ListExecutor) +} diff --git a/pumpkin/src/command/commands/mod.rs b/pumpkin/src/command/commands/mod.rs index 84dae01c1..d754443ff 100644 --- a/pumpkin/src/command/commands/mod.rs +++ b/pumpkin/src/command/commands/mod.rs @@ -8,6 +8,7 @@ pub mod cmd_help; pub mod cmd_kick; pub mod cmd_kill; pub mod cmd_list; +pub mod cmd_plugins; pub mod cmd_pumpkin; pub mod cmd_say; pub mod cmd_seed; diff --git a/pumpkin/src/command/mod.rs b/pumpkin/src/command/mod.rs index 56201946d..ff07909f3 100644 --- a/pumpkin/src/command/mod.rs +++ b/pumpkin/src/command/mod.rs @@ -11,7 +11,7 @@ use args::ConsumedArgs; use async_trait::async_trait; use commands::{ cmd_clear, cmd_craft, cmd_echest, cmd_fill, cmd_gamemode, cmd_give, cmd_help, cmd_kick, - cmd_kill, cmd_list, cmd_pumpkin, cmd_say, cmd_setblock, cmd_stop, cmd_teleport, + cmd_kill, cmd_list, cmd_plugins, cmd_pumpkin, cmd_say, cmd_setblock, cmd_stop, cmd_teleport, cmd_worldborder, }; use dispatcher::CommandError; @@ -120,6 +120,7 @@ pub fn default_dispatcher<'a>() -> Arc> { dispatcher.register(cmd_craft::init_command_tree()); dispatcher.register(cmd_kill::init_command_tree()); dispatcher.register(cmd_kick::init_command_tree()); + dispatcher.register(cmd_plugins::init_command_tree()); dispatcher.register(cmd_worldborder::init_command_tree()); dispatcher.register(cmd_teleport::init_command_tree()); dispatcher.register(cmd_give::init_command_tree()); diff --git a/pumpkin/src/lib.rs b/pumpkin/src/lib.rs index 5006dc141..f37151266 100644 --- a/pumpkin/src/lib.rs +++ b/pumpkin/src/lib.rs @@ -1,8 +1,9 @@ -use std::sync::{LazyLock, Mutex}; +use std::sync::LazyLock; use client::Client; use plugin::PluginManager; use pumpkin_core::text::TextComponent; +use tokio::sync::Mutex; pub mod client; pub mod command; diff --git a/pumpkin/src/main.rs b/pumpkin/src/main.rs index 1dc35b6d8..c6c193ea3 100644 --- a/pumpkin/src/main.rs +++ b/pumpkin/src/main.rs @@ -24,9 +24,10 @@ use plugin::PluginManager; use server::{ticker::Ticker, Server}; use std::{ io::{self}, - sync::{LazyLock, Mutex}, + sync::LazyLock, }; use tokio::io::{AsyncBufReadExt, BufReader}; +use tokio::sync::Mutex; #[cfg(not(unix))] use tokio::signal::ctrl_c; #[cfg(unix)] @@ -241,7 +242,7 @@ async fn main() -> io::Result<()> { let server = Arc::new(Server::new()); let mut ticker = Ticker::new(BASIC_CONFIG.tps); - PLUGIN_MANAGER.lock().unwrap().load_plugins().unwrap(); + PLUGIN_MANAGER.lock().await.load_plugins().unwrap(); log::info!("Started Server took {}ms", time.elapsed().as_millis()); log::info!("You now can connect to the server, Listening on {}", addr); diff --git a/pumpkin/src/plugin/mod.rs b/pumpkin/src/plugin/mod.rs index 7b3217519..2dbe24e59 100644 --- a/pumpkin/src/plugin/mod.rs +++ b/pumpkin/src/plugin/mod.rs @@ -1,17 +1,18 @@ pub mod api; pub use api::*; -use std::{any::Any, collections::HashMap, fs, path::Path}; +use std::{any::Any, fs, path::Path}; type PluginData = ( PluginMetadata<'static>, Box, Box, libloading::Library, + bool, ); pub struct PluginManager { - plugins: HashMap, + plugins: Vec, } impl Default for PluginManager { @@ -53,7 +54,7 @@ impl PluginManager { #[must_use] pub fn new() -> Self { PluginManager { - plugins: HashMap::new(), + plugins: vec![], } } @@ -85,33 +86,30 @@ impl PluginManager { unsafe { &**library.get::<*const PluginMetadata>(b"METADATA").unwrap() }; let context = Context { metadata }; - let _ = plugin_fn().on_load(&context); + let res = plugin_fn().on_load(&context); + let mut loaded = true; + if let Err(e) = res { + log::error!("Error loading plugin: {}", e); + loaded = false; + } - self.plugins.insert( - metadata.name.to_string(), - (metadata.clone(), plugin_fn(), hooks_fn(), library), + self.plugins.push( + (metadata.clone(), plugin_fn(), hooks_fn(), library, loaded), ); } - #[must_use] - pub fn get_plugin(&self, name: &str) -> Option<&PluginData> { - self.plugins.get(name) - } - - pub fn list_plugins(&self) { - for (metadata, _, _, _) in self.plugins.values() { - println!( - "{} v{} by {}", - metadata.name, metadata.version, metadata.authors - ); - } + pub fn list_plugins(&self) -> Vec<(&PluginMetadata, &bool)> { + return self.plugins.iter().map(|(metadata, _, _, _, loaded)| (metadata, loaded)).collect(); } pub fn emit(&mut self, event_name: &str, event: &T) -> bool { let mut blocking_hooks = Vec::new(); let mut non_blocking_hooks = Vec::new(); - for (metadata, _, hooks, _) in self.plugins.values_mut() { + for (metadata, _, hooks, _, loaded) in self.plugins.iter_mut() { + if !*loaded { + continue; + } if hooks .registered_events() .unwrap() diff --git a/pumpkin/src/world/mod.rs b/pumpkin/src/world/mod.rs index 0ef0f911c..10b2eb351 100644 --- a/pumpkin/src/world/mod.rs +++ b/pumpkin/src/world/mod.rs @@ -688,7 +688,7 @@ impl World { if !PLUGIN_MANAGER .lock() - .unwrap() + .await .emit::("player_join", &player.clone()) { // Handle join message @@ -736,7 +736,7 @@ impl World { if !PLUGIN_MANAGER .lock() - .unwrap() + .await .emit::("player_leave", &player) { // Send disconnect message / quit message to players in the same world From 02139756a06cc7c491d8e550a54d62a4d53b5572 Mon Sep 17 00:00:00 2001 From: vyPal Date: Fri, 29 Nov 2024 14:45:56 +0100 Subject: [PATCH 29/62] Make handlers async --- pumpkin/src/command/commands/cmd_plugins.rs | 7 +--- pumpkin/src/main.rs | 4 +- pumpkin/src/plugin/api/context.rs | 2 +- pumpkin/src/plugin/api/events.rs | 15 ++++--- pumpkin/src/plugin/mod.rs | 43 +++++++++++---------- pumpkin/src/world/mod.rs | 33 +++++++++------- 6 files changed, 56 insertions(+), 48 deletions(-) diff --git a/pumpkin/src/command/commands/cmd_plugins.rs b/pumpkin/src/command/commands/cmd_plugins.rs index c77783910..b328d1f6c 100644 --- a/pumpkin/src/command/commands/cmd_plugins.rs +++ b/pumpkin/src/command/commands/cmd_plugins.rs @@ -24,7 +24,7 @@ impl CommandExecutor for ListExecutor { _args: &ConsumedArgs<'a>, ) -> Result<(), CommandError> { let plugin_manager = PLUGIN_MANAGER.lock().await; - let plugins = plugin_manager.list_plugins(); + let plugins = plugin_manager.list_plugins().await; let message = if plugins.is_empty() { "There are no loaded plugins." @@ -32,10 +32,7 @@ impl CommandExecutor for ListExecutor { &format!( "There are {} plugins loaded: {}", plugins.len(), - plugins - .iter() - .map(|(plugin, _)| plugin.name) - .join(", ") + plugins.iter().map(|(plugin, _)| plugin.name).join(", ") ) }; diff --git a/pumpkin/src/main.rs b/pumpkin/src/main.rs index c6c193ea3..c82432982 100644 --- a/pumpkin/src/main.rs +++ b/pumpkin/src/main.rs @@ -27,11 +27,11 @@ use std::{ sync::LazyLock, }; use tokio::io::{AsyncBufReadExt, BufReader}; -use tokio::sync::Mutex; #[cfg(not(unix))] use tokio::signal::ctrl_c; #[cfg(unix)] use tokio::signal::unix::{signal, SignalKind}; +use tokio::sync::Mutex; use std::sync::Arc; @@ -242,7 +242,7 @@ async fn main() -> io::Result<()> { let server = Arc::new(Server::new()); let mut ticker = Ticker::new(BASIC_CONFIG.tps); - PLUGIN_MANAGER.lock().await.load_plugins().unwrap(); + PLUGIN_MANAGER.lock().await.load_plugins().await.unwrap(); log::info!("Started Server took {}ms", time.elapsed().as_millis()); log::info!("You now can connect to the server, Listening on {}", addr); diff --git a/pumpkin/src/plugin/api/context.rs b/pumpkin/src/plugin/api/context.rs index 929f81141..7a318d2ea 100644 --- a/pumpkin/src/plugin/api/context.rs +++ b/pumpkin/src/plugin/api/context.rs @@ -1,4 +1,4 @@ -pub trait PluginContext { +pub trait PluginContext: Send + Sync { fn get_logger(&self) -> Box; } diff --git a/pumpkin/src/plugin/api/events.rs b/pumpkin/src/plugin/api/events.rs index a1b14829a..656cdc943 100644 --- a/pumpkin/src/plugin/api/events.rs +++ b/pumpkin/src/plugin/api/events.rs @@ -1,3 +1,5 @@ +use async_trait::async_trait; + use crate::entity::player::Player; use super::PluginContext; @@ -17,26 +19,27 @@ pub struct EventDescriptor { pub blocking: bool, } +#[async_trait] pub trait Hooks: Send + Sync + 'static { /// Returns an array of events that the fn registered_events(&self) -> Result<&'static [EventDescriptor], String> { Ok(&[]) } - /// Called when the plugin is loaded. - fn on_player_join( + /// Called when a player joins the server. + async fn on_player_join( &mut self, _server: &dyn PluginContext, - _event: &Player, + _player: &Player, ) -> Result { Ok(false) } - /// Called when the plugin is unloaded. - fn on_player_leave( + /// Called when a player leaves the server. + async fn on_player_leave( &mut self, _server: &dyn PluginContext, - _event: &Player, + _player: &Player, ) -> Result { Ok(false) } diff --git a/pumpkin/src/plugin/mod.rs b/pumpkin/src/plugin/mod.rs index 2dbe24e59..11624c5a5 100644 --- a/pumpkin/src/plugin/mod.rs +++ b/pumpkin/src/plugin/mod.rs @@ -53,12 +53,10 @@ impl PluginContext for Context<'_> { impl PluginManager { #[must_use] pub fn new() -> Self { - PluginManager { - plugins: vec![], - } + PluginManager { plugins: vec![] } } - pub fn load_plugins(&mut self) -> Result<(), String> { + pub async fn load_plugins(&mut self) -> Result<(), String> { const PLUGIN_DIR: &str = "./plugins"; let dir_entires = fs::read_dir(PLUGIN_DIR); @@ -67,13 +65,13 @@ impl PluginManager { if !entry.as_ref().unwrap().path().is_file() { continue; } - self.try_load_plugin(entry.unwrap().path().as_path()); + self.try_load_plugin(entry.unwrap().path().as_path()).await; } Ok(()) } - fn try_load_plugin(&mut self, path: &Path) { + async fn try_load_plugin(&mut self, path: &Path) { let library = unsafe { libloading::Library::new(path).unwrap() }; let plugin_fn = unsafe { library.get:: Box>(b"plugin").unwrap() }; @@ -93,16 +91,19 @@ impl PluginManager { loaded = false; } - self.plugins.push( - (metadata.clone(), plugin_fn(), hooks_fn(), library, loaded), - ); + self.plugins + .push((metadata.clone(), plugin_fn(), hooks_fn(), library, loaded)); } - pub fn list_plugins(&self) -> Vec<(&PluginMetadata, &bool)> { - return self.plugins.iter().map(|(metadata, _, _, _, loaded)| (metadata, loaded)).collect(); + pub async fn list_plugins(&self) -> Vec<(&PluginMetadata, &bool)> { + return self + .plugins + .iter() + .map(|(metadata, _, _, _, loaded)| (metadata, loaded)) + .collect(); } - pub fn emit(&mut self, event_name: &str, event: &T) -> bool { + pub async fn emit(&mut self, event_name: &str, event: &T) -> bool { let mut blocking_hooks = Vec::new(); let mut non_blocking_hooks = Vec::new(); @@ -163,7 +164,7 @@ impl PluginManager { ) }); - let event = event as &dyn Any; + let event = event as &(dyn Any + Sync + Send); for (context, hooks) in blocking_hooks { let r = match event_name { @@ -171,19 +172,19 @@ impl PluginManager { if let Some(event) = event.downcast_ref::() { hooks.on_player_join(&context, event) } else { - Ok(false) + Box::pin(async { Ok(false) }) } } "player_leave" => { if let Some(event) = event.downcast_ref::() { hooks.on_player_leave(&context, event) } else { - Ok(false) + Box::pin(async { Ok(false) }) } } - _ => Ok(false), + _ => Box::pin(async { Ok(false) }), }; - match r { + match r.await { Ok(true) => return true, Err(e) => { log::error!("Error in plugin: {}", e); @@ -198,19 +199,19 @@ impl PluginManager { if let Some(event) = event.downcast_ref::() { hooks.on_player_join(&context, event) } else { - Ok(false) + Box::pin(async { Ok(false) }) } } "player_leave" => { if let Some(event) = event.downcast_ref::() { hooks.on_player_leave(&context, event) } else { - Ok(false) + Box::pin(async { Ok(false) }) } } - _ => Ok(false), + _ => Box::pin(async { Ok(false) }), }; - match r { + match r.await { Ok(true) => continue, Err(e) => { log::error!("Error in plugin: {}", e); diff --git a/pumpkin/src/world/mod.rs b/pumpkin/src/world/mod.rs index 10b2eb351..a02d09615 100644 --- a/pumpkin/src/world/mod.rs +++ b/pumpkin/src/world/mod.rs @@ -686,20 +686,26 @@ impl World { let mut current_players = self.current_players.lock().await; current_players.insert(uuid, player.clone()); - if !PLUGIN_MANAGER - .lock() - .await - .emit::("player_join", &player.clone()) - { - // Handle join message - // TODO: Config - let msg_txt = format!("{} joined the game.", player.gameprofile.name.as_str()); - let msg_comp = TextComponent::text(msg_txt.as_str()).color_named(NamedColor::Yellow); - for player in current_players.values() { - player.send_system_message(&msg_comp).await; + let current_players = self.current_players.clone(); + tokio::spawn(async move { + if !PLUGIN_MANAGER + .lock() + .await + .emit::("player_join", &player.clone()) + .await + { + // Handle join message + // TODO: Config + let msg_txt = format!("{} joined the game.", player.gameprofile.name.as_str()); + let msg_comp = + TextComponent::text(msg_txt.as_str()).color_named(NamedColor::Yellow); + let players = current_players.lock().await; + for player in players.values() { + player.send_system_message(&msg_comp).await; + } + log::info!("{}", msg_comp.to_pretty_console()); } - log::info!("{}", msg_comp.to_pretty_console()); - } + }); } /// Removes a player from the world and broadcasts a disconnect message if enabled. @@ -738,6 +744,7 @@ impl World { .lock() .await .emit::("player_leave", &player) + .await { // Send disconnect message / quit message to players in the same world // TODO: Config From 07ba3bb4882cdcbc4b8f60695f6cb81b69c285e8 Mon Sep 17 00:00:00 2001 From: vyPal Date: Fri, 29 Nov 2024 14:46:05 +0100 Subject: [PATCH 30/62] Update macros --- pumpkin-api-macros/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pumpkin-api-macros/src/lib.rs b/pumpkin-api-macros/src/lib.rs index 492110d87..48375d216 100644 --- a/pumpkin-api-macros/src/lib.rs +++ b/pumpkin-api-macros/src/lib.rs @@ -92,7 +92,7 @@ pub fn plugin_event(attr: TokenStream, item: TokenStream) -> TokenStream { let method = quote! { #[allow(unused_mut)] - fn #fn_name(#fn_inputs) #fn_output { + async fn #fn_name(#fn_inputs) #fn_output { #fn_body } } @@ -190,6 +190,7 @@ pub fn plugin_impl(attr: TokenStream, item: TokenStream) -> TokenStream { #(#methods)* } + #[async_trait::async_trait] impl pumpkin::plugin::Hooks for #struct_ident { fn registered_events(&self) -> Result<&'static [pumpkin::plugin::EventDescriptor], String> { static EVENTS: &[EventDescriptor] = &[#(#events),*]; From 69c932eaf19339578aba1164cd68716920f81110 Mon Sep 17 00:00:00 2001 From: vyPal Date: Fri, 29 Nov 2024 14:46:13 +0100 Subject: [PATCH 31/62] Update example --- plugins/hello-plugin-source/Cargo.toml | 4 +++- plugins/hello-plugin-source/src/lib.rs | 30 ++++++++++++++++++++------ 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/plugins/hello-plugin-source/Cargo.toml b/plugins/hello-plugin-source/Cargo.toml index bb13de1c1..68345d7b6 100644 --- a/plugins/hello-plugin-source/Cargo.toml +++ b/plugins/hello-plugin-source/Cargo.toml @@ -10,4 +10,6 @@ crate-type = ["dylib"] [dependencies] pumpkin = { path = "../../pumpkin" } -pumpkin-api-macros = { path = "../../pumpkin-api-macros" } \ No newline at end of file +pumpkin-core = { path = "../../pumpkin-core" } +pumpkin-api-macros = { path = "../../pumpkin-api-macros" } +async-trait = "0.1.83" \ No newline at end of file diff --git a/plugins/hello-plugin-source/src/lib.rs b/plugins/hello-plugin-source/src/lib.rs index 86ebb4aa6..eb327884b 100644 --- a/plugins/hello-plugin-source/src/lib.rs +++ b/plugins/hello-plugin-source/src/lib.rs @@ -1,6 +1,8 @@ -use pumpkin::plugin::*; -use pumpkin_api_macros::{plugin_impl, plugin_method, plugin_event}; use pumpkin::entity::player::Player; +use pumpkin::plugin::*; +use pumpkin_api_macros::{plugin_event, plugin_impl, plugin_method}; +use pumpkin_core::text::TextComponent; +use pumpkin_core::GameMode; #[plugin_method] fn on_load(&mut self, server: &dyn PluginContext) -> Result<(), String> { @@ -15,15 +17,31 @@ fn on_unload(&mut self, server: &dyn PluginContext) -> Result<(), String> { } #[plugin_event(blocking = true, priority = Highest)] -fn on_player_join(&mut self, server: &dyn PluginContext, player: &Player) -> Result { - server.get_logger().info(format!("Player {} joined the game", player.gameprofile.name).as_str()); +async fn on_player_join( + &mut self, + server: &dyn PluginContext, + player: &Player, +) -> Result { + server + .get_logger() + .info(format!("Player {} joined the game", player.gameprofile.name).as_str()); + /// TODO: Calling any method that involves sending packets to the player will cause the client to crash + //let _ = player.send_system_message(&TextComponent::text("Hello, world!")).await; + //player.set_gamemode(GameMode::Creative).await; + //player.kill().await; // Returning true will block any other plugins from receiving this event Ok(true) } #[plugin_event] -fn on_player_leave(&mut self, server: &dyn PluginContext, player: &Player) -> Result { - server.get_logger().info(format!("Player {} left the game", player.gameprofile.name).as_str()); +async fn on_player_leave( + &mut self, + server: &dyn PluginContext, + player: &Player, +) -> Result { + server + .get_logger() + .info(format!("Player {} left the game", player.gameprofile.name).as_str()); Ok(false) } From 5681fa4377a1608a80ae806665a40584dc799296 Mon Sep 17 00:00:00 2001 From: vyPal Date: Mon, 2 Dec 2024 11:28:43 +0100 Subject: [PATCH 32/62] Fix formatting and clippy issues --- pumpkin/src/command/commands/cmd_plugins.rs | 2 +- pumpkin/src/command/mod.rs | 4 +-- pumpkin/src/main.rs | 2 +- pumpkin/src/plugin/mod.rs | 34 +++++++-------------- pumpkin/src/world/mod.rs | 2 +- 5 files changed, 16 insertions(+), 28 deletions(-) diff --git a/pumpkin/src/command/commands/cmd_plugins.rs b/pumpkin/src/command/commands/cmd_plugins.rs index b328d1f6c..41a1a53b7 100644 --- a/pumpkin/src/command/commands/cmd_plugins.rs +++ b/pumpkin/src/command/commands/cmd_plugins.rs @@ -24,7 +24,7 @@ impl CommandExecutor for ListExecutor { _args: &ConsumedArgs<'a>, ) -> Result<(), CommandError> { let plugin_manager = PLUGIN_MANAGER.lock().await; - let plugins = plugin_manager.list_plugins().await; + let plugins = plugin_manager.list_plugins(); let message = if plugins.is_empty() { "There are no loaded plugins." diff --git a/pumpkin/src/command/mod.rs b/pumpkin/src/command/mod.rs index 85d3c42ad..273a86164 100644 --- a/pumpkin/src/command/mod.rs +++ b/pumpkin/src/command/mod.rs @@ -11,8 +11,8 @@ use args::ConsumedArgs; use async_trait::async_trait; use commands::{ cmd_clear, cmd_craft, cmd_echest, cmd_fill, cmd_gamemode, cmd_give, cmd_help, cmd_kick, - cmd_kill, cmd_list, cmd_plugins, cmd_pumpkin, cmd_say, cmd_setblock, cmd_stop, cmd_teleport, cmd_time, - cmd_worldborder, + cmd_kill, cmd_list, cmd_plugins, cmd_pumpkin, cmd_say, cmd_setblock, cmd_stop, cmd_teleport, + cmd_time, cmd_worldborder, }; use dispatcher::CommandError; use pumpkin_core::math::vector3::Vector3; diff --git a/pumpkin/src/main.rs b/pumpkin/src/main.rs index cd6747899..a1fb5443b 100644 --- a/pumpkin/src/main.rs +++ b/pumpkin/src/main.rs @@ -167,7 +167,7 @@ async fn main() -> io::Result<()> { let server = Arc::new(Server::new()); let mut ticker = Ticker::new(BASIC_CONFIG.tps); - PLUGIN_MANAGER.lock().await.load_plugins().await.unwrap(); + PLUGIN_MANAGER.lock().await.load_plugins().unwrap(); log::info!("Started Server took {}ms", time.elapsed().as_millis()); log::info!("You now can connect to the server, Listening on {}", addr); diff --git a/pumpkin/src/plugin/mod.rs b/pumpkin/src/plugin/mod.rs index 11624c5a5..4c5502ea1 100644 --- a/pumpkin/src/plugin/mod.rs +++ b/pumpkin/src/plugin/mod.rs @@ -56,7 +56,7 @@ impl PluginManager { PluginManager { plugins: vec![] } } - pub async fn load_plugins(&mut self) -> Result<(), String> { + pub fn load_plugins(&mut self) -> Result<(), String> { const PLUGIN_DIR: &str = "./plugins"; let dir_entires = fs::read_dir(PLUGIN_DIR); @@ -65,13 +65,13 @@ impl PluginManager { if !entry.as_ref().unwrap().path().is_file() { continue; } - self.try_load_plugin(entry.unwrap().path().as_path()).await; + self.try_load_plugin(entry.unwrap().path().as_path()); } Ok(()) } - async fn try_load_plugin(&mut self, path: &Path) { + fn try_load_plugin(&mut self, path: &Path) { let library = unsafe { libloading::Library::new(path).unwrap() }; let plugin_fn = unsafe { library.get:: Box>(b"plugin").unwrap() }; @@ -95,7 +95,8 @@ impl PluginManager { .push((metadata.clone(), plugin_fn(), hooks_fn(), library, loaded)); } - pub async fn list_plugins(&self) -> Vec<(&PluginMetadata, &bool)> { + #[must_use] + pub fn list_plugins(&self) -> Vec<(&PluginMetadata, &bool)> { return self .plugins .iter() @@ -107,7 +108,7 @@ impl PluginManager { let mut blocking_hooks = Vec::new(); let mut non_blocking_hooks = Vec::new(); - for (metadata, _, hooks, _, loaded) in self.plugins.iter_mut() { + for (metadata, _, hooks, _, loaded) in &mut self.plugins { if !*loaded { continue; } @@ -131,7 +132,7 @@ impl PluginManager { } } - blocking_hooks.sort_by(|a, b| { + let event_sort = |a: &(_, &mut Box), b: &(_, &mut Box)| { b.1.registered_events() .unwrap() .iter() @@ -146,23 +147,10 @@ impl PluginManager { .unwrap() .priority, ) - }); - non_blocking_hooks.sort_by(|a, b| { - b.1.registered_events() - .unwrap() - .iter() - .find(|e| e.name == event_name) - .unwrap() - .priority - .cmp( - &a.1.registered_events() - .unwrap() - .iter() - .find(|e| e.name == event_name) - .unwrap() - .priority, - ) - }); + }; + + blocking_hooks.sort_by(event_sort); + non_blocking_hooks.sort_by(event_sort); let event = event as &(dyn Any + Sync + Send); diff --git a/pumpkin/src/world/mod.rs b/pumpkin/src/world/mod.rs index 2b550715d..df0980cf1 100644 --- a/pumpkin/src/world/mod.rs +++ b/pumpkin/src/world/mod.rs @@ -764,7 +764,7 @@ impl World { if !PLUGIN_MANAGER .lock() .await - .emit::("player_leave", &player) + .emit::("player_leave", player) .await { // Send disconnect message / quit message to players in the same world From 5c7568c9e12972d0669c8fa4eda8d26a0ee0e23b Mon Sep 17 00:00:00 2001 From: vyPal Date: Mon, 2 Dec 2024 13:48:44 +0100 Subject: [PATCH 33/62] Better styling on plugins command --- plugins/hello-plugin-source/Cargo.toml | 2 ++ pumpkin/src/command/commands/cmd_plugins.rs | 38 ++++++++++++++++----- 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/plugins/hello-plugin-source/Cargo.toml b/plugins/hello-plugin-source/Cargo.toml index 68345d7b6..52ba6677c 100644 --- a/plugins/hello-plugin-source/Cargo.toml +++ b/plugins/hello-plugin-source/Cargo.toml @@ -4,6 +4,8 @@ name = "hello-plugin-source" edition = "2021" version = "0.1.0" +authors = ["vyPal"] +description = "An example plugin for Pumpkin" [lib] crate-type = ["dylib"] diff --git a/pumpkin/src/command/commands/cmd_plugins.rs b/pumpkin/src/command/commands/cmd_plugins.rs index 41a1a53b7..7ea5e4456 100644 --- a/pumpkin/src/command/commands/cmd_plugins.rs +++ b/pumpkin/src/command/commands/cmd_plugins.rs @@ -1,6 +1,5 @@ use async_trait::async_trait; -use itertools::Itertools; -use pumpkin_core::text::TextComponent; +use pumpkin_core::text::{color::NamedColor, hover::HoverEvent, TextComponent}; use crate::{ command::{ @@ -26,17 +25,38 @@ impl CommandExecutor for ListExecutor { let plugin_manager = PLUGIN_MANAGER.lock().await; let plugins = plugin_manager.list_plugins(); - let message = if plugins.is_empty() { + let message_text = if plugins.is_empty() { "There are no loaded plugins." + } else if plugins.len() == 1 { + "There is 1 plugin loaded:\n" } else { - &format!( - "There are {} plugins loaded: {}", - plugins.len(), - plugins.iter().map(|(plugin, _)| plugin.name).join(", ") - ) + &format!("There are {} plugins loaded:\n", plugins.len(),) }; + let mut message = TextComponent::text(message_text); - sender.send_message(TextComponent::text(message)).await; + for (i, (metadata, loaded)) in plugins.clone().into_iter().enumerate() { + let fmt = if i == plugins.len() - 1 { + metadata.name.to_string() + } else { + format!("{}, ", metadata.name) + }; + let hover_text = format!( + "Version: {}\nAuthors: {}\nDescription: {}", + metadata.version, metadata.authors, metadata.description + ); + let component = if *loaded { + TextComponent::text_string(fmt) + .color_named(NamedColor::Green) + .hover_event(HoverEvent::ShowText(hover_text.into())) + } else { + TextComponent::text_string(fmt) + .color_named(NamedColor::Red) + .hover_event(HoverEvent::ShowText(hover_text.into())) + }; + message = message.add_child(component); + } + + sender.send_message(message).await; Ok(()) } From b4088d91c051fda62d3c591d34c5c54967ba13f3 Mon Sep 17 00:00:00 2001 From: vyPal Date: Mon, 2 Dec 2024 14:04:56 +0100 Subject: [PATCH 34/62] Fix clippy issues --- pumpkin/src/plugin/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pumpkin/src/plugin/mod.rs b/pumpkin/src/plugin/mod.rs index 4c5502ea1..ef6d71389 100644 --- a/pumpkin/src/plugin/mod.rs +++ b/pumpkin/src/plugin/mod.rs @@ -97,11 +97,11 @@ impl PluginManager { #[must_use] pub fn list_plugins(&self) -> Vec<(&PluginMetadata, &bool)> { - return self + self .plugins .iter() .map(|(metadata, _, _, _, loaded)| (metadata, loaded)) - .collect(); + .collect() } pub async fn emit(&mut self, event_name: &str, event: &T) -> bool { From bf9af2f22f54e2c44670a441185735b72e78199a Mon Sep 17 00:00:00 2001 From: vyPal Date: Mon, 2 Dec 2024 14:06:00 +0100 Subject: [PATCH 35/62] Cargo fmt --- pumpkin/src/plugin/mod.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pumpkin/src/plugin/mod.rs b/pumpkin/src/plugin/mod.rs index ef6d71389..742c174ee 100644 --- a/pumpkin/src/plugin/mod.rs +++ b/pumpkin/src/plugin/mod.rs @@ -97,8 +97,7 @@ impl PluginManager { #[must_use] pub fn list_plugins(&self) -> Vec<(&PluginMetadata, &bool)> { - self - .plugins + self.plugins .iter() .map(|(metadata, _, _, _, loaded)| (metadata, loaded)) .collect() From 363e30bcc6ec112a3c926094d828187fb08deb00 Mon Sep 17 00:00:00 2001 From: vyPal Date: Mon, 2 Dec 2024 14:15:36 +0100 Subject: [PATCH 36/62] Disable doctest for lib target on pumpkin crate --- pumpkin/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pumpkin/Cargo.toml b/pumpkin/Cargo.toml index 8185e0139..351442214 100644 --- a/pumpkin/Cargo.toml +++ b/pumpkin/Cargo.toml @@ -12,6 +12,7 @@ LegalCopyright = "Copyright © 2024 Aleksander Medvedev" # Required to expose pumpkin plugin API [lib] +doctest = false [dependencies] # pumpkin From fa911fe516296f23d863dc1277c0bb8c0d377da9 Mon Sep 17 00:00:00 2001 From: vyPal Date: Thu, 5 Dec 2024 12:38:38 +0100 Subject: [PATCH 37/62] New API for plugins --- pumpkin/Cargo.toml | 1 + pumpkin/src/plugin/api/context.rs | 1 + pumpkin/src/plugin/api/events.rs | 8 +- pumpkin/src/plugin/api/mod.rs | 5 +- pumpkin/src/plugin/api/types/mod.rs | 1 + pumpkin/src/plugin/api/types/player.rs | 75 ++++++++++++++ pumpkin/src/plugin/mod.rs | 33 ++++--- pumpkin/src/world/mod.rs | 131 ++++++++++++++++++++++++- 8 files changed, 231 insertions(+), 24 deletions(-) create mode 100644 pumpkin/src/plugin/api/types/mod.rs create mode 100644 pumpkin/src/plugin/api/types/player.rs diff --git a/pumpkin/Cargo.toml b/pumpkin/Cargo.toml index 351442214..83620fe26 100644 --- a/pumpkin/Cargo.toml +++ b/pumpkin/Cargo.toml @@ -79,6 +79,7 @@ async-trait = "0.1.83" # plugins libloading = "0.8.5" +oneshot = "0.1.8" [build-dependencies] git-version = "0.3.9" # This makes it so the entire project doesn't recompile on each build on linux. diff --git a/pumpkin/src/plugin/api/context.rs b/pumpkin/src/plugin/api/context.rs index 7a318d2ea..75ea81e4e 100644 --- a/pumpkin/src/plugin/api/context.rs +++ b/pumpkin/src/plugin/api/context.rs @@ -1,5 +1,6 @@ pub trait PluginContext: Send + Sync { fn get_logger(&self) -> Box; + fn get_data_folder(&self) -> String; } pub trait Logger { diff --git a/pumpkin/src/plugin/api/events.rs b/pumpkin/src/plugin/api/events.rs index 656cdc943..2dd116f21 100644 --- a/pumpkin/src/plugin/api/events.rs +++ b/pumpkin/src/plugin/api/events.rs @@ -1,8 +1,6 @@ use async_trait::async_trait; -use crate::entity::player::Player; - -use super::PluginContext; +use super::{types::player::PlayerEvent, PluginContext}; #[derive(Eq, PartialEq, Ord, PartialOrd)] pub enum EventPriority { @@ -30,7 +28,7 @@ pub trait Hooks: Send + Sync + 'static { async fn on_player_join( &mut self, _server: &dyn PluginContext, - _player: &Player, + _player: &PlayerEvent, ) -> Result { Ok(false) } @@ -39,7 +37,7 @@ pub trait Hooks: Send + Sync + 'static { async fn on_player_leave( &mut self, _server: &dyn PluginContext, - _player: &Player, + _player: &PlayerEvent, ) -> Result { Ok(false) } diff --git a/pumpkin/src/plugin/api/mod.rs b/pumpkin/src/plugin/api/mod.rs index 90120b1b4..d61e9dddb 100644 --- a/pumpkin/src/plugin/api/mod.rs +++ b/pumpkin/src/plugin/api/mod.rs @@ -1,5 +1,6 @@ pub mod context; pub mod events; +pub mod types; pub use context::*; pub use events::*; @@ -16,7 +17,7 @@ pub struct PluginMetadata<'s> { pub description: &'s str, } -pub trait Plugin: Send + Sync + 'static { +pub trait PluginMethods: Send + Sync + 'static { /// Called when the plugin is loaded. fn on_load(&mut self, _server: &dyn PluginContext) -> Result<(), String> { Ok(()) @@ -27,3 +28,5 @@ pub trait Plugin: Send + Sync + 'static { Ok(()) } } + +pub trait Plugin: PluginMethods + Hooks {} diff --git a/pumpkin/src/plugin/api/types/mod.rs b/pumpkin/src/plugin/api/types/mod.rs new file mode 100644 index 000000000..f28d7c205 --- /dev/null +++ b/pumpkin/src/plugin/api/types/mod.rs @@ -0,0 +1 @@ +pub mod player; diff --git a/pumpkin/src/plugin/api/types/player.rs b/pumpkin/src/plugin/api/types/player.rs new file mode 100644 index 000000000..5a2b8145f --- /dev/null +++ b/pumpkin/src/plugin/api/types/player.rs @@ -0,0 +1,75 @@ +use pumpkin_core::text::TextComponent; +use tokio::sync::mpsc; +use uuid::Uuid; + +pub enum PlayerEventAction<'a> { + SendMessage { + message: TextComponent<'a>, + player_id: Uuid, + response: oneshot::Sender<()>, + }, + Kick { + reason: TextComponent<'a>, + player_id: Uuid, + response: oneshot::Sender<()>, + }, + SetHealth { + health: f32, + food: i32, + saturation: f32, + player_id: Uuid, + response: oneshot::Sender<()>, + }, + Kill { + player_id: Uuid, + response: oneshot::Sender<()>, + }, + SetGameMode { + game_mode: pumpkin_core::GameMode, + player_id: Uuid, + response: oneshot::Sender<()>, + }, +} + +pub struct PlayerEvent<'a> { + pub name: String, + pub uuid: Uuid, + channel: mpsc::Sender>, +} + +impl<'a> PlayerEvent<'a> { + #[must_use] + pub fn new(name: String, uuid: Uuid, channel: mpsc::Sender>) -> Self { + Self { + name, + uuid, + channel, + } + } + + pub async fn send_message(&self, message: TextComponent<'a>) { + let (tx, rx) = oneshot::channel(); + self.channel + .send(PlayerEventAction::SendMessage { + message, + player_id: self.uuid, + response: tx, + }) + .await + .unwrap(); + rx.await.unwrap(); + } + + pub async fn kick(&self, reason: TextComponent<'a>) { + let (tx, rx) = oneshot::channel(); + self.channel + .send(PlayerEventAction::Kick { + reason, + player_id: self.uuid, + response: tx, + }) + .await + .unwrap(); + rx.await.unwrap(); + } +} diff --git a/pumpkin/src/plugin/mod.rs b/pumpkin/src/plugin/mod.rs index 742c174ee..94a1c31ad 100644 --- a/pumpkin/src/plugin/mod.rs +++ b/pumpkin/src/plugin/mod.rs @@ -6,7 +6,6 @@ use std::{any::Any, fs, path::Path}; type PluginData = ( PluginMetadata<'static>, Box, - Box, libloading::Library, bool, ); @@ -48,6 +47,14 @@ impl PluginContext for Context<'_> { plugin_name: self.metadata.name.to_string(), }) } + + fn get_data_folder(&self) -> String { + let path = format!("./plugins/{}", self.metadata.name); + if !Path::new(&path).exists() { + fs::create_dir_all(&path).unwrap(); + } + path + } } impl PluginManager { @@ -75,16 +82,12 @@ impl PluginManager { let library = unsafe { libloading::Library::new(path).unwrap() }; let plugin_fn = unsafe { library.get:: Box>(b"plugin").unwrap() }; - let hooks_fn = unsafe { - library - .get:: Box>(b"hooks") - .unwrap() - }; let metadata: &PluginMetadata = unsafe { &**library.get::<*const PluginMetadata>(b"METADATA").unwrap() }; let context = Context { metadata }; - let res = plugin_fn().on_load(&context); + let mut plugin_box = plugin_fn(); + let res = plugin_box.on_load(&context); let mut loaded = true; if let Err(e) = res { log::error!("Error loading plugin: {}", e); @@ -92,14 +95,14 @@ impl PluginManager { } self.plugins - .push((metadata.clone(), plugin_fn(), hooks_fn(), library, loaded)); + .push((metadata.clone(), plugin_box, library, loaded)); } #[must_use] pub fn list_plugins(&self) -> Vec<(&PluginMetadata, &bool)> { self.plugins .iter() - .map(|(metadata, _, _, _, loaded)| (metadata, loaded)) + .map(|(metadata, _, _, loaded)| (metadata, loaded)) .collect() } @@ -107,7 +110,7 @@ impl PluginManager { let mut blocking_hooks = Vec::new(); let mut non_blocking_hooks = Vec::new(); - for (metadata, _, hooks, _, loaded) in &mut self.plugins { + for (metadata, hooks, _, loaded) in &mut self.plugins { if !*loaded { continue; } @@ -131,7 +134,7 @@ impl PluginManager { } } - let event_sort = |a: &(_, &mut Box), b: &(_, &mut Box)| { + let event_sort = |a: &(_, &mut Box), b: &(_, &mut Box)| { b.1.registered_events() .unwrap() .iter() @@ -156,14 +159,14 @@ impl PluginManager { for (context, hooks) in blocking_hooks { let r = match event_name { "player_join" => { - if let Some(event) = event.downcast_ref::() { + if let Some(event) = event.downcast_ref::() { hooks.on_player_join(&context, event) } else { Box::pin(async { Ok(false) }) } } "player_leave" => { - if let Some(event) = event.downcast_ref::() { + if let Some(event) = event.downcast_ref::() { hooks.on_player_leave(&context, event) } else { Box::pin(async { Ok(false) }) @@ -183,14 +186,14 @@ impl PluginManager { for (context, hooks) in non_blocking_hooks { let r = match event_name { "player_join" => { - if let Some(event) = event.downcast_ref::() { + if let Some(event) = event.downcast_ref::() { hooks.on_player_join(&context, event) } else { Box::pin(async { Ok(false) }) } } "player_leave" => { - if let Some(event) = event.downcast_ref::() { + if let Some(event) = event.downcast_ref::() { hooks.on_player_leave(&context, event) } else { Box::pin(async { Ok(false) }) diff --git a/pumpkin/src/world/mod.rs b/pumpkin/src/world/mod.rs index df0980cf1..21d809a2f 100644 --- a/pumpkin/src/world/mod.rs +++ b/pumpkin/src/world/mod.rs @@ -13,6 +13,7 @@ use crate::{ Entity, }, error::PumpkinError, + plugin::types::player::{PlayerEvent, PlayerEventAction}, server::Server, PLUGIN_MANAGER, }; @@ -709,10 +710,74 @@ impl World { let current_players = self.current_players.clone(); tokio::spawn(async move { + let (send, mut recv) = mpsc::channel(1); + let player_event = + PlayerEvent::new(player.gameprofile.name.clone(), player.gameprofile.id, send); + let players_copy = current_players.lock().await.clone(); + tokio::spawn(async move { + while let Some(action) = recv.recv().await { + match action { + PlayerEventAction::SendMessage { + message, + player_id, + response, + } => { + if let Some(player) = players_copy.get(&player_id) { + player.send_system_message(&message).await; + } + response.send(()).unwrap(); + } + PlayerEventAction::Kick { + reason, + player_id, + response, + } => { + if let Some(player) = players_copy.get(&player_id) { + player.kick(reason).await; + } + response.send(()).unwrap(); + } + PlayerEventAction::SetHealth { + health, + food, + saturation, + player_id, + response, + } => { + if let Some(player) = players_copy.get(&player_id) { + player.set_health(health, food, saturation).await; + } + response.send(()).unwrap(); + } + PlayerEventAction::Kill { + player_id, + response, + } => { + if let Some(player) = players_copy.get(&player_id) { + player.kill().await; + } + response.send(()).unwrap(); + } + PlayerEventAction::SetGameMode { + game_mode, + player_id, + response, + } => { + if let Some(player) = players_copy.get(&player_id) { + player.set_gamemode(game_mode).await; + } + response.send(()).unwrap(); + } + } + } + }); if !PLUGIN_MANAGER .lock() .await - .emit::("player_join", &player.clone()) + .emit::( + "player_join", + &player_event, + ) .await { // Handle join message @@ -760,11 +825,71 @@ impl World { ) .await; self.remove_entity(&player.living_entity.entity).await; - + let (send, mut recv) = mpsc::channel(1); + let player_event = + PlayerEvent::new(player.gameprofile.name.clone(), player.gameprofile.id, send); + let players_copy = self.current_players.lock().await.clone(); + tokio::spawn(async move { + while let Some(action) = recv.recv().await { + match action { + PlayerEventAction::SendMessage { + message, + player_id, + response, + } => { + if let Some(player) = players_copy.get(&player_id) { + player.send_system_message(&message).await; + } + response.send(()).unwrap(); + } + PlayerEventAction::Kick { + reason, + player_id, + response, + } => { + if let Some(player) = players_copy.get(&player_id) { + player.kick(reason).await; + } + response.send(()).unwrap(); + } + PlayerEventAction::SetHealth { + health, + food, + saturation, + player_id, + response, + } => { + if let Some(player) = players_copy.get(&player_id) { + player.set_health(health, food, saturation).await; + } + response.send(()).unwrap(); + } + PlayerEventAction::Kill { + player_id, + response, + } => { + if let Some(player) = players_copy.get(&player_id) { + player.kill().await; + } + response.send(()).unwrap(); + } + PlayerEventAction::SetGameMode { + game_mode, + player_id, + response, + } => { + if let Some(player) = players_copy.get(&player_id) { + player.set_gamemode(game_mode).await; + } + response.send(()).unwrap(); + } + } + } + }); if !PLUGIN_MANAGER .lock() .await - .emit::("player_leave", player) + .emit::("player_leave", &player_event) .await { // Send disconnect message / quit message to players in the same world From c00039ae0805c91e64de4f347956107e92221bd3 Mon Sep 17 00:00:00 2001 From: vyPal Date: Thu, 5 Dec 2024 12:38:48 +0100 Subject: [PATCH 38/62] Update api macros --- pumpkin-api-macros/src/lib.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/pumpkin-api-macros/src/lib.rs b/pumpkin-api-macros/src/lib.rs index 48375d216..b1af63151 100644 --- a/pumpkin-api-macros/src/lib.rs +++ b/pumpkin-api-macros/src/lib.rs @@ -186,7 +186,9 @@ pub fn plugin_impl(attr: TokenStream, item: TokenStream) -> TokenStream { #input_struct - impl pumpkin::plugin::Plugin for #struct_ident { + impl pumpkin::plugin::Plugin for #struct_ident {} + + impl pumpkin::plugin::PluginMethods for #struct_ident { #(#methods)* } @@ -202,12 +204,7 @@ pub fn plugin_impl(attr: TokenStream, item: TokenStream) -> TokenStream { #[no_mangle] pub fn plugin() -> Box { - Box::new(#struct_ident {}) - } - - #[no_mangle] - pub fn hooks() -> Box { - Box::new(#struct_ident {}) + Box::new(#struct_ident::new()) } }; From ce6ad0e4032112c9b397d375255ec3491c60afda Mon Sep 17 00:00:00 2001 From: vyPal Date: Thu, 5 Dec 2024 12:39:01 +0100 Subject: [PATCH 39/62] Update plugin example --- plugins/hello-plugin-source/Cargo.toml | 2 + plugins/hello-plugin-source/data.toml | 2 + plugins/hello-plugin-source/src/lib.rs | 67 +++++++++++++++++++++----- 3 files changed, 59 insertions(+), 12 deletions(-) create mode 100644 plugins/hello-plugin-source/data.toml diff --git a/plugins/hello-plugin-source/Cargo.toml b/plugins/hello-plugin-source/Cargo.toml index 52ba6677c..77178452d 100644 --- a/plugins/hello-plugin-source/Cargo.toml +++ b/plugins/hello-plugin-source/Cargo.toml @@ -14,4 +14,6 @@ crate-type = ["dylib"] pumpkin = { path = "../../pumpkin" } pumpkin-core = { path = "../../pumpkin-core" } pumpkin-api-macros = { path = "../../pumpkin-api-macros" } +serde = { version = "1.0", features = ["derive"] } +toml = "0.8.19" async-trait = "0.1.83" \ No newline at end of file diff --git a/plugins/hello-plugin-source/data.toml b/plugins/hello-plugin-source/data.toml new file mode 100644 index 000000000..f82aff0da --- /dev/null +++ b/plugins/hello-plugin-source/data.toml @@ -0,0 +1,2 @@ +[bans] +players = [] diff --git a/plugins/hello-plugin-source/src/lib.rs b/plugins/hello-plugin-source/src/lib.rs index eb327884b..14a8c439d 100644 --- a/plugins/hello-plugin-source/src/lib.rs +++ b/plugins/hello-plugin-source/src/lib.rs @@ -1,17 +1,44 @@ -use pumpkin::entity::player::Player; use pumpkin::plugin::*; use pumpkin_api_macros::{plugin_event, plugin_impl, plugin_method}; use pumpkin_core::text::TextComponent; -use pumpkin_core::GameMode; +use pumpkin_core::text::color::NamedColor; +use pumpkin::plugin::api::types::player::PlayerEvent; +use std::fs; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug)] +struct Config { + bans: Bans, +} + +#[derive(Serialize, Deserialize, Debug)] +struct Bans { + players: Vec, +} #[plugin_method] fn on_load(&mut self, server: &dyn PluginContext) -> Result<(), String> { + let data_folder = server.get_data_folder(); + if !fs::exists(format!("{}/data.toml", data_folder)).unwrap() { + let cfg = toml::to_string(&self.config).unwrap(); + fs::write(format!("{}/data.toml", data_folder), cfg).unwrap(); + server.get_logger().info(format!("Created config in {} with {:#?}", data_folder, self.config).as_str()); + } else { + let data = fs::read_to_string(format!("{}/data.toml", data_folder)).unwrap(); + self.config = toml::from_str(data.as_str()).unwrap(); + server.get_logger().info(format!("Loaded config from {} with {:#?}", data_folder, self.config).as_str()); + } + server.get_logger().info("Plugin loaded!"); Ok(()) } #[plugin_method] fn on_unload(&mut self, server: &dyn PluginContext) -> Result<(), String> { + let data_folder = server.get_data_folder(); + let cfg = toml::to_string(&self.config).unwrap(); + fs::write(format!("{}/data.toml", data_folder), cfg).unwrap(); + server.get_logger().info("Plugin unloaded!"); Ok(()) } @@ -20,16 +47,18 @@ fn on_unload(&mut self, server: &dyn PluginContext) -> Result<(), String> { async fn on_player_join( &mut self, server: &dyn PluginContext, - player: &Player, + player: &PlayerEvent, ) -> Result { server .get_logger() - .info(format!("Player {} joined the game", player.gameprofile.name).as_str()); - /// TODO: Calling any method that involves sending packets to the player will cause the client to crash - //let _ = player.send_system_message(&TextComponent::text("Hello, world!")).await; - //player.set_gamemode(GameMode::Creative).await; - //player.kill().await; - // Returning true will block any other plugins from receiving this event + .info(format!("Player {} joined the game. Config is {:#?}", player.name, self.config).as_str()); + + if self.config.bans.players.contains(&player.name) { + let _ = player.kick(TextComponent::text("You are banned from the server")).await; + return Ok(true); + } + + let _ = player.send_message(TextComponent::text_string(format!("Hello {}, welocme to the server", player.name)).color_named(NamedColor::Green)).await; Ok(true) } @@ -37,13 +66,27 @@ async fn on_player_join( async fn on_player_leave( &mut self, server: &dyn PluginContext, - player: &Player, + player: &PlayerEvent, ) -> Result { server .get_logger() - .info(format!("Player {} left the game", player.gameprofile.name).as_str()); + .info(format!("Player {} left the game", player.name).as_str()); Ok(false) } #[plugin_impl] -pub struct MyPlugin {} +pub struct MyPlugin { + config: Config, +} + +impl MyPlugin { + pub fn new() -> Self { + MyPlugin { + config: Config { + bans: Bans { + players: vec![], + }, + }, + } + } +} From ca30edf140c50af635e47e42e668e91e252ba0a9 Mon Sep 17 00:00:00 2001 From: vyPal Date: Fri, 6 Dec 2024 10:35:49 +0100 Subject: [PATCH 40/62] A bit of clean up --- plugins/hello-plugin-source/src/lib.rs | 46 +++++++++++++++++--------- pumpkin/src/entity/player.rs | 4 +-- pumpkin/src/plugin/api/types/player.rs | 27 +++++++++------ pumpkin/src/world/mod.rs | 8 ++--- 4 files changed, 53 insertions(+), 32 deletions(-) diff --git a/plugins/hello-plugin-source/src/lib.rs b/plugins/hello-plugin-source/src/lib.rs index 14a8c439d..3a4549d76 100644 --- a/plugins/hello-plugin-source/src/lib.rs +++ b/plugins/hello-plugin-source/src/lib.rs @@ -1,10 +1,10 @@ +use pumpkin::plugin::api::types::player::PlayerEvent; use pumpkin::plugin::*; use pumpkin_api_macros::{plugin_event, plugin_impl, plugin_method}; -use pumpkin_core::text::TextComponent; use pumpkin_core::text::color::NamedColor; -use pumpkin::plugin::api::types::player::PlayerEvent; -use std::fs; +use pumpkin_core::text::TextComponent; use serde::{Deserialize, Serialize}; +use std::fs; #[derive(Serialize, Deserialize, Debug)] struct Config { @@ -22,11 +22,15 @@ fn on_load(&mut self, server: &dyn PluginContext) -> Result<(), String> { if !fs::exists(format!("{}/data.toml", data_folder)).unwrap() { let cfg = toml::to_string(&self.config).unwrap(); fs::write(format!("{}/data.toml", data_folder), cfg).unwrap(); - server.get_logger().info(format!("Created config in {} with {:#?}", data_folder, self.config).as_str()); + server + .get_logger() + .info(format!("Created config in {} with {:#?}", data_folder, self.config).as_str()); } else { let data = fs::read_to_string(format!("{}/data.toml", data_folder)).unwrap(); self.config = toml::from_str(data.as_str()).unwrap(); - server.get_logger().info(format!("Loaded config from {} with {:#?}", data_folder, self.config).as_str()); + server + .get_logger() + .info(format!("Loaded config from {} with {:#?}", data_folder, self.config).as_str()); } server.get_logger().info("Plugin loaded!"); @@ -49,16 +53,30 @@ async fn on_player_join( server: &dyn PluginContext, player: &PlayerEvent, ) -> Result { - server - .get_logger() - .info(format!("Player {} joined the game. Config is {:#?}", player.name, self.config).as_str()); + server.get_logger().info( + format!( + "Player {} joined the game. Config is {:#?}", + player.gameprofile.name, self.config + ) + .as_str(), + ); - if self.config.bans.players.contains(&player.name) { - let _ = player.kick(TextComponent::text("You are banned from the server")).await; + if self.config.bans.players.contains(&player.gameprofile.name) { + let _ = player + .kick(TextComponent::text("You are banned from the server")) + .await; return Ok(true); } - let _ = player.send_message(TextComponent::text_string(format!("Hello {}, welocme to the server", player.name)).color_named(NamedColor::Green)).await; + let _ = player + .send_message( + TextComponent::text_string(format!( + "Hello {}, welocme to the server", + player.gameprofile.name + )) + .color_named(NamedColor::Green), + ) + .await; Ok(true) } @@ -70,7 +88,7 @@ async fn on_player_leave( ) -> Result { server .get_logger() - .info(format!("Player {} left the game", player.name).as_str()); + .info(format!("Player {} left the game", player.gameprofile.name).as_str()); Ok(false) } @@ -83,9 +101,7 @@ impl MyPlugin { pub fn new() -> Self { MyPlugin { config: Config { - bans: Bans { - players: vec![], - }, + bans: Bans { players: vec![] }, }, } } diff --git a/pumpkin/src/entity/player.rs b/pumpkin/src/entity/player.rs index 9b9c52a86..2a5c468ab 100644 --- a/pumpkin/src/entity/player.rs +++ b/pumpkin/src/entity/player.rs @@ -232,14 +232,14 @@ impl Player { /// Removes the Player out of the current World #[allow(unused_variables)] - pub async fn remove(&self) { + pub async fn remove(self: Arc) { let world = self.world(); // Abort pending chunks here too because we might clean up before chunk tasks are done self.abort_chunks("closed"); self.cancel_tasks.notify_waiters(); - world.remove_player(self).await; + world.remove_player(self.clone()).await; let cylindrical = self.watched_section.load(); diff --git a/pumpkin/src/plugin/api/types/player.rs b/pumpkin/src/plugin/api/types/player.rs index 5a2b8145f..7452ec2d0 100644 --- a/pumpkin/src/plugin/api/types/player.rs +++ b/pumpkin/src/plugin/api/types/player.rs @@ -1,7 +1,11 @@ +use std::{ops::Deref, sync::Arc}; + use pumpkin_core::text::TextComponent; use tokio::sync::mpsc; use uuid::Uuid; +use crate::entity::player::Player; + pub enum PlayerEventAction<'a> { SendMessage { message: TextComponent<'a>, @@ -32,19 +36,22 @@ pub enum PlayerEventAction<'a> { } pub struct PlayerEvent<'a> { - pub name: String, - pub uuid: Uuid, + pub player: Arc, channel: mpsc::Sender>, } +impl Deref for PlayerEvent<'_> { + type Target = Player; + + fn deref(&self) -> &Self::Target { + &self.player + } +} + impl<'a> PlayerEvent<'a> { #[must_use] - pub fn new(name: String, uuid: Uuid, channel: mpsc::Sender>) -> Self { - Self { - name, - uuid, - channel, - } + pub fn new(player: Arc, channel: mpsc::Sender>) -> Self { + Self { player, channel } } pub async fn send_message(&self, message: TextComponent<'a>) { @@ -52,7 +59,7 @@ impl<'a> PlayerEvent<'a> { self.channel .send(PlayerEventAction::SendMessage { message, - player_id: self.uuid, + player_id: self.player.gameprofile.id, response: tx, }) .await @@ -65,7 +72,7 @@ impl<'a> PlayerEvent<'a> { self.channel .send(PlayerEventAction::Kick { reason, - player_id: self.uuid, + player_id: self.player.gameprofile.id, response: tx, }) .await diff --git a/pumpkin/src/world/mod.rs b/pumpkin/src/world/mod.rs index 21d809a2f..4f80bf5cb 100644 --- a/pumpkin/src/world/mod.rs +++ b/pumpkin/src/world/mod.rs @@ -711,8 +711,7 @@ impl World { let current_players = self.current_players.clone(); tokio::spawn(async move { let (send, mut recv) = mpsc::channel(1); - let player_event = - PlayerEvent::new(player.gameprofile.name.clone(), player.gameprofile.id, send); + let player_event = PlayerEvent::new(player.clone(), send); let players_copy = current_players.lock().await.clone(); tokio::spawn(async move { while let Some(action) = recv.recv().await { @@ -812,7 +811,7 @@ impl World { /// /// - This function assumes `broadcast_packet_expect` and `remove_entity` are defined elsewhere. /// - The disconnect message sending is currently optional. Consider making it a configurable option. - pub async fn remove_player(&self, player: &Player) { + pub async fn remove_player(&self, player: Arc) { self.current_players .lock() .await @@ -826,8 +825,7 @@ impl World { .await; self.remove_entity(&player.living_entity.entity).await; let (send, mut recv) = mpsc::channel(1); - let player_event = - PlayerEvent::new(player.gameprofile.name.clone(), player.gameprofile.id, send); + let player_event = PlayerEvent::new(player.clone(), send); let players_copy = self.current_players.lock().await.clone(); tokio::spawn(async move { while let Some(action) = recv.recv().await { From d279df6562ed393a4aa55f38ec34380ebac997f0 Mon Sep 17 00:00:00 2001 From: vyPal Date: Mon, 16 Dec 2024 18:54:13 +0100 Subject: [PATCH 41/62] Some QoL (and performance) improvements --- plugins/hello-plugin-source/src/lib.rs | 8 +-- pumpkin/src/plugin/api/context.rs | 73 ++++++++++++++++++++--- pumpkin/src/plugin/api/events.rs | 7 ++- pumpkin/src/plugin/api/mod.rs | 4 +- pumpkin/src/plugin/mod.rs | 80 ++++++++++---------------- 5 files changed, 105 insertions(+), 67 deletions(-) diff --git a/plugins/hello-plugin-source/src/lib.rs b/plugins/hello-plugin-source/src/lib.rs index 3a4549d76..316c6f016 100644 --- a/plugins/hello-plugin-source/src/lib.rs +++ b/plugins/hello-plugin-source/src/lib.rs @@ -17,7 +17,7 @@ struct Bans { } #[plugin_method] -fn on_load(&mut self, server: &dyn PluginContext) -> Result<(), String> { +fn on_load(&mut self, server: &Context) -> Result<(), String> { let data_folder = server.get_data_folder(); if !fs::exists(format!("{}/data.toml", data_folder)).unwrap() { let cfg = toml::to_string(&self.config).unwrap(); @@ -38,7 +38,7 @@ fn on_load(&mut self, server: &dyn PluginContext) -> Result<(), String> { } #[plugin_method] -fn on_unload(&mut self, server: &dyn PluginContext) -> Result<(), String> { +fn on_unload(&mut self, server: &Context) -> Result<(), String> { let data_folder = server.get_data_folder(); let cfg = toml::to_string(&self.config).unwrap(); fs::write(format!("{}/data.toml", data_folder), cfg).unwrap(); @@ -50,7 +50,7 @@ fn on_unload(&mut self, server: &dyn PluginContext) -> Result<(), String> { #[plugin_event(blocking = true, priority = Highest)] async fn on_player_join( &mut self, - server: &dyn PluginContext, + server: &Context, player: &PlayerEvent, ) -> Result { server.get_logger().info( @@ -83,7 +83,7 @@ async fn on_player_join( #[plugin_event] async fn on_player_leave( &mut self, - server: &dyn PluginContext, + server: &Context, player: &PlayerEvent, ) -> Result { server diff --git a/pumpkin/src/plugin/api/context.rs b/pumpkin/src/plugin/api/context.rs index 75ea81e4e..58264bab6 100644 --- a/pumpkin/src/plugin/api/context.rs +++ b/pumpkin/src/plugin/api/context.rs @@ -1,10 +1,69 @@ -pub trait PluginContext: Send + Sync { - fn get_logger(&self) -> Box; - fn get_data_folder(&self) -> String; +use std::{fs, path::Path}; + +use tokio::sync::mpsc::{self, Sender}; + +use super::PluginMetadata; + +pub struct Context { + metadata: PluginMetadata<'static>, + _channel: Sender, +} +impl Context { + pub fn new(metadata: PluginMetadata<'static>, channel: Sender) -> Context { + Context { metadata, _channel: channel } + } + + pub fn get_logger(&self) -> Logger { + Logger { + plugin_name: self.metadata.name.to_string(), + } + } + + pub fn get_data_folder(&self) -> String { + let path = format!("./plugins/{}", self.metadata.name); + if !Path::new(&path).exists() { + fs::create_dir_all(&path).unwrap(); + } + path + } +/* TODO: Implement when dispatcher is mutable + pub async fn register_command(&self, tree: crate::command::tree::CommandTree<'static>) { + self.channel.send(ContextAction::RegisterCommand(tree)).await; + } */ +} + +pub enum ContextAction { + // TODO: Implement when dispatcher is mutable +} + +pub fn handle_context(metadata: PluginMetadata<'static>/* , dispatcher: Arc> */) -> Context { + let (send, mut recv) = mpsc::channel(1); + tokio::spawn(async move { + while let Some(action) = recv.recv().await { + match action { + /* ContextAction::RegisterCommand(_tree) => { + // TODO: Implement when dispatcher is mutable + } */ + } + } + }); + Context::new(metadata, send) } -pub trait Logger { - fn info(&self, message: &str); - fn warn(&self, message: &str); - fn error(&self, message: &str); +pub struct Logger { + plugin_name: String, +} + +impl Logger { + pub fn info(&self, message: &str) { + log::info!("[{}] {}", self.plugin_name, message); + } + + pub fn warn(&self, message: &str) { + log::warn!("[{}] {}", self.plugin_name, message); + } + + pub fn error(&self, message: &str) { + log::error!("[{}] {}", self.plugin_name, message); + } } diff --git a/pumpkin/src/plugin/api/events.rs b/pumpkin/src/plugin/api/events.rs index 2dd116f21..a73e72217 100644 --- a/pumpkin/src/plugin/api/events.rs +++ b/pumpkin/src/plugin/api/events.rs @@ -1,6 +1,7 @@ use async_trait::async_trait; -use super::{types::player::PlayerEvent, PluginContext}; +use super::types::player::PlayerEvent; +use super::context::Context; #[derive(Eq, PartialEq, Ord, PartialOrd)] pub enum EventPriority { @@ -27,7 +28,7 @@ pub trait Hooks: Send + Sync + 'static { /// Called when a player joins the server. async fn on_player_join( &mut self, - _server: &dyn PluginContext, + _server: &Context, _player: &PlayerEvent, ) -> Result { Ok(false) @@ -36,7 +37,7 @@ pub trait Hooks: Send + Sync + 'static { /// Called when a player leaves the server. async fn on_player_leave( &mut self, - _server: &dyn PluginContext, + _server: &Context, _player: &PlayerEvent, ) -> Result { Ok(false) diff --git a/pumpkin/src/plugin/api/mod.rs b/pumpkin/src/plugin/api/mod.rs index d61e9dddb..29cb354f0 100644 --- a/pumpkin/src/plugin/api/mod.rs +++ b/pumpkin/src/plugin/api/mod.rs @@ -19,12 +19,12 @@ pub struct PluginMetadata<'s> { pub trait PluginMethods: Send + Sync + 'static { /// Called when the plugin is loaded. - fn on_load(&mut self, _server: &dyn PluginContext) -> Result<(), String> { + fn on_load(&mut self, _server: &Context) -> Result<(), String> { Ok(()) } /// Called when the plugin is unloaded. - fn on_unload(&mut self, _server: &dyn PluginContext) -> Result<(), String> { + fn on_unload(&mut self, _server: &Context) -> Result<(), String> { Ok(()) } } diff --git a/pumpkin/src/plugin/mod.rs b/pumpkin/src/plugin/mod.rs index 94a1c31ad..5d743b68a 100644 --- a/pumpkin/src/plugin/mod.rs +++ b/pumpkin/src/plugin/mod.rs @@ -1,7 +1,9 @@ pub mod api; pub use api::*; -use std::{any::Any, fs, path::Path}; +use std::{any::Any, fs, path::Path, sync::Arc}; + +use crate::command::dispatcher::CommandDispatcher; type PluginData = ( PluginMetadata<'static>, @@ -12,6 +14,7 @@ type PluginData = ( pub struct PluginManager { plugins: Vec, + command_dispatcher: Option>> } impl Default for PluginManager { @@ -20,47 +23,14 @@ impl Default for PluginManager { } } -struct PluginLogger { - plugin_name: String, -} - -impl Logger for PluginLogger { - fn info(&self, message: &str) { - log::info!("[{}] {}", self.plugin_name, message); - } - - fn warn(&self, message: &str) { - log::warn!("[{}] {}", self.plugin_name, message); - } - - fn error(&self, message: &str) { - log::error!("[{}] {}", self.plugin_name, message); - } -} - -struct Context<'a> { - metadata: &'a PluginMetadata<'a>, -} -impl PluginContext for Context<'_> { - fn get_logger(&self) -> Box { - Box::new(PluginLogger { - plugin_name: self.metadata.name.to_string(), - }) - } - - fn get_data_folder(&self) -> String { - let path = format!("./plugins/{}", self.metadata.name); - if !Path::new(&path).exists() { - fs::create_dir_all(&path).unwrap(); - } - path - } -} - impl PluginManager { #[must_use] pub fn new() -> Self { - PluginManager { plugins: vec![] } + PluginManager { plugins: vec![], command_dispatcher: None } + } + + pub fn set_command_dispatcher(&mut self, dispatcher: Arc>) { + self.command_dispatcher = Some(dispatcher); } pub fn load_plugins(&mut self) -> Result<(), String> { @@ -85,7 +55,8 @@ impl PluginManager { let metadata: &PluginMetadata = unsafe { &**library.get::<*const PluginMetadata>(b"METADATA").unwrap() }; - let context = Context { metadata }; + // let dispatcher = self.command_dispatcher.clone().expect("Command dispatcher not set").clone(); + let context = handle_context(metadata.clone()/* , dispatcher */); let mut plugin_box = plugin_fn(); let res = plugin_box.on_load(&context); let mut loaded = true; @@ -110,23 +81,30 @@ impl PluginManager { let mut blocking_hooks = Vec::new(); let mut non_blocking_hooks = Vec::new(); + /* let dispatcher = self.command_dispatcher + .clone() + .expect("Command dispatcher not set"); // This should not happen */ + for (metadata, hooks, _, loaded) in &mut self.plugins { if !*loaded { continue; } - if hooks - .registered_events() - .unwrap() + + let registered_events = match hooks.registered_events() { + Ok(events) => events, + Err(e) => { + log::error!("Failed to get registered events: {}", e); + continue; + } + }; + + if let Some(matching_event) = registered_events .iter() - .any(|e| e.name == event_name) + .find(|e| e.name == event_name) { - let context = Context { metadata }; - if hooks - .registered_events() - .unwrap() - .iter() - .any(|e| e.name == event_name && e.blocking) - { + let context = handle_context(metadata.clone()/* , dispatcher.clone() */); + + if matching_event.blocking { blocking_hooks.push((context, hooks)); } else { non_blocking_hooks.push((context, hooks)); From 237c2808923ebc4aa0fcfd426d92bc42fa26d4bb Mon Sep 17 00:00:00 2001 From: vyPal Date: Fri, 20 Dec 2024 11:33:15 +0100 Subject: [PATCH 42/62] Cargo fmt and clippy fixes --- pumpkin/src/lib.rs | 1 + pumpkin/src/plugin/api/context.rs | 11 ++++++++--- pumpkin/src/plugin/api/events.rs | 2 +- pumpkin/src/plugin/mod.rs | 22 +++++++++++----------- 4 files changed, 21 insertions(+), 15 deletions(-) diff --git a/pumpkin/src/lib.rs b/pumpkin/src/lib.rs index f37151266..1c00f2272 100644 --- a/pumpkin/src/lib.rs +++ b/pumpkin/src/lib.rs @@ -5,6 +5,7 @@ use plugin::PluginManager; use pumpkin_core::text::TextComponent; use tokio::sync::Mutex; +pub mod block; pub mod client; pub mod command; pub mod entity; diff --git a/pumpkin/src/plugin/api/context.rs b/pumpkin/src/plugin/api/context.rs index 58264bab6..66c44f6d1 100644 --- a/pumpkin/src/plugin/api/context.rs +++ b/pumpkin/src/plugin/api/context.rs @@ -10,7 +10,10 @@ pub struct Context { } impl Context { pub fn new(metadata: PluginMetadata<'static>, channel: Sender) -> Context { - Context { metadata, _channel: channel } + Context { + metadata, + _channel: channel, + } } pub fn get_logger(&self) -> Logger { @@ -26,7 +29,7 @@ impl Context { } path } -/* TODO: Implement when dispatcher is mutable + /* TODO: Implement when dispatcher is mutable pub async fn register_command(&self, tree: crate::command::tree::CommandTree<'static>) { self.channel.send(ContextAction::RegisterCommand(tree)).await; } */ @@ -36,7 +39,9 @@ pub enum ContextAction { // TODO: Implement when dispatcher is mutable } -pub fn handle_context(metadata: PluginMetadata<'static>/* , dispatcher: Arc> */) -> Context { +pub fn handle_context( + metadata: PluginMetadata<'static>, /* , dispatcher: Arc> */ +) -> Context { let (send, mut recv) = mpsc::channel(1); tokio::spawn(async move { while let Some(action) = recv.recv().await { diff --git a/pumpkin/src/plugin/api/events.rs b/pumpkin/src/plugin/api/events.rs index a73e72217..e4a845eea 100644 --- a/pumpkin/src/plugin/api/events.rs +++ b/pumpkin/src/plugin/api/events.rs @@ -1,7 +1,7 @@ use async_trait::async_trait; -use super::types::player::PlayerEvent; use super::context::Context; +use super::types::player::PlayerEvent; #[derive(Eq, PartialEq, Ord, PartialOrd)] pub enum EventPriority { diff --git a/pumpkin/src/plugin/mod.rs b/pumpkin/src/plugin/mod.rs index 5d743b68a..b4786b562 100644 --- a/pumpkin/src/plugin/mod.rs +++ b/pumpkin/src/plugin/mod.rs @@ -14,7 +14,7 @@ type PluginData = ( pub struct PluginManager { plugins: Vec, - command_dispatcher: Option>> + command_dispatcher: Option>>, } impl Default for PluginManager { @@ -26,7 +26,10 @@ impl Default for PluginManager { impl PluginManager { #[must_use] pub fn new() -> Self { - PluginManager { plugins: vec![], command_dispatcher: None } + PluginManager { + plugins: vec![], + command_dispatcher: None, + } } pub fn set_command_dispatcher(&mut self, dispatcher: Arc>) { @@ -56,7 +59,7 @@ impl PluginManager { unsafe { &**library.get::<*const PluginMetadata>(b"METADATA").unwrap() }; // let dispatcher = self.command_dispatcher.clone().expect("Command dispatcher not set").clone(); - let context = handle_context(metadata.clone()/* , dispatcher */); + let context = handle_context(metadata.clone() /* , dispatcher */); let mut plugin_box = plugin_fn(); let res = plugin_box.on_load(&context); let mut loaded = true; @@ -82,8 +85,8 @@ impl PluginManager { let mut non_blocking_hooks = Vec::new(); /* let dispatcher = self.command_dispatcher - .clone() - .expect("Command dispatcher not set"); // This should not happen */ + .clone() + .expect("Command dispatcher not set"); // This should not happen */ for (metadata, hooks, _, loaded) in &mut self.plugins { if !*loaded { @@ -98,12 +101,9 @@ impl PluginManager { } }; - if let Some(matching_event) = registered_events - .iter() - .find(|e| e.name == event_name) - { - let context = handle_context(metadata.clone()/* , dispatcher.clone() */); - + if let Some(matching_event) = registered_events.iter().find(|e| e.name == event_name) { + let context = handle_context(metadata.clone() /* , dispatcher.clone() */); + if matching_event.blocking { blocking_hooks.push((context, hooks)); } else { From bdb684bb42fc4b434a10b36da5285bba2846fa39 Mon Sep 17 00:00:00 2001 From: vyPal Date: Sun, 22 Dec 2024 18:57:11 +0100 Subject: [PATCH 43/62] refactoring, better event handling, new context functions --- pumpkin/src/main.rs | 4 +- pumpkin/src/plugin/api/context.rs | 57 +++++++++++-- pumpkin/src/plugin/api/types/player.rs | 79 ++++++++++++++++- pumpkin/src/plugin/mod.rs | 114 ++++++++++++++----------- 4 files changed, 192 insertions(+), 62 deletions(-) diff --git a/pumpkin/src/main.rs b/pumpkin/src/main.rs index 830fff216..3d6663555 100644 --- a/pumpkin/src/main.rs +++ b/pumpkin/src/main.rs @@ -179,7 +179,9 @@ async fn main() { let server = Arc::new(Server::new()); let mut ticker = Ticker::new(BASIC_CONFIG.tps); - PLUGIN_MANAGER.lock().await.load_plugins().unwrap(); + let mut loader_lock = PLUGIN_MANAGER.lock().await; + loader_lock.set_server(server.clone()); + loader_lock.load_plugins().unwrap(); log::info!("Started Server took {}ms", time.elapsed().as_millis()); log::info!("You now can connect to the server, Listening on {}", addr); diff --git a/pumpkin/src/plugin/api/context.rs b/pumpkin/src/plugin/api/context.rs index 66c44f6d1..a575cdd1e 100644 --- a/pumpkin/src/plugin/api/context.rs +++ b/pumpkin/src/plugin/api/context.rs @@ -1,27 +1,32 @@ -use std::{fs, path::Path}; +use std::{fs, path::Path, sync::Arc}; use tokio::sync::mpsc::{self, Sender}; -use super::PluginMetadata; +use crate::server::Server; + +use super::{ + types::player::{player_event_handler, PlayerEvent}, + PluginMetadata, +}; pub struct Context { metadata: PluginMetadata<'static>, - _channel: Sender, + channel: Sender, } impl Context { - pub fn new(metadata: PluginMetadata<'static>, channel: Sender) -> Context { - Context { - metadata, - _channel: channel, - } + #[must_use] + pub fn new(metadata: PluginMetadata<'static>, channel: Sender) -> Self { + Self { metadata, channel } } + #[must_use] pub fn get_logger(&self) -> Logger { Logger { plugin_name: self.metadata.name.to_string(), } } + #[must_use] pub fn get_data_folder(&self) -> String { let path = format!("./plugins/{}", self.metadata.name); if !Path::new(&path).exists() { @@ -29,6 +34,22 @@ impl Context { } path } + + pub async fn get_player_by_name( + &self, + player_name: String, + ) -> Result, String> { + let (send, recv) = oneshot::channel(); + let _ = self + .channel + .send(ContextAction::GetPlayerByName { + player_name, + response: send, + }) + .await; + recv.await.unwrap() + } + /* TODO: Implement when dispatcher is mutable pub async fn register_command(&self, tree: crate::command::tree::CommandTree<'static>) { self.channel.send(ContextAction::RegisterCommand(tree)).await; @@ -37,10 +58,15 @@ impl Context { pub enum ContextAction { // TODO: Implement when dispatcher is mutable + GetPlayerByName { + player_name: String, + response: oneshot::Sender, String>>, + }, } pub fn handle_context( metadata: PluginMetadata<'static>, /* , dispatcher: Arc> */ + server: Arc, ) -> Context { let (send, mut recv) = mpsc::channel(1); tokio::spawn(async move { @@ -49,6 +75,21 @@ pub fn handle_context( /* ContextAction::RegisterCommand(_tree) => { // TODO: Implement when dispatcher is mutable } */ + ContextAction::GetPlayerByName { + player_name, + response, + } => { + let player = server.get_player_by_name(&player_name).await; + if let Some(player) = player { + response + .send(Ok( + player_event_handler(server.clone(), player.clone()).await + )) + .unwrap(); + } else { + response.send(Err("Player not found".to_string())).unwrap(); + } + } } } }); diff --git a/pumpkin/src/plugin/api/types/player.rs b/pumpkin/src/plugin/api/types/player.rs index 7452ec2d0..dd430f70c 100644 --- a/pumpkin/src/plugin/api/types/player.rs +++ b/pumpkin/src/plugin/api/types/player.rs @@ -4,7 +4,7 @@ use pumpkin_core::text::TextComponent; use tokio::sync::mpsc; use uuid::Uuid; -use crate::entity::player::Player; +use crate::{entity::player::Player, server::Server}; pub enum PlayerEventAction<'a> { SendMessage { @@ -80,3 +80,80 @@ impl<'a> PlayerEvent<'a> { rx.await.unwrap(); } } + +pub async fn player_event_handler( + server: Arc, + player: Arc, +) -> PlayerEvent<'static> { + let (send, mut recv) = mpsc::channel(1); + let player_event = PlayerEvent::new(player.clone(), send); + let players_copy = server.get_all_players().await; + tokio::spawn(async move { + while let Some(action) = recv.recv().await { + match action { + PlayerEventAction::SendMessage { + message, + player_id, + response, + } => { + if let Some(player) = + players_copy.iter().find(|p| p.gameprofile.id == player_id) + { + player.send_system_message(&message).await; + } + response.send(()).unwrap(); + } + PlayerEventAction::Kick { + reason, + player_id, + response, + } => { + if let Some(player) = + players_copy.iter().find(|p| p.gameprofile.id == player_id) + { + player.kick(reason).await; + } + response.send(()).unwrap(); + } + PlayerEventAction::SetHealth { + health, + food, + saturation, + player_id, + response, + } => { + if let Some(player) = + players_copy.iter().find(|p| p.gameprofile.id == player_id) + { + player.set_health(health, food, saturation).await; + } + response.send(()).unwrap(); + } + PlayerEventAction::Kill { + player_id, + response, + } => { + if let Some(player) = + players_copy.iter().find(|p| p.gameprofile.id == player_id) + { + player.kill().await; + } + response.send(()).unwrap(); + } + PlayerEventAction::SetGameMode { + game_mode, + player_id, + response, + } => { + if let Some(player) = + players_copy.iter().find(|p| p.gameprofile.id == player_id) + { + player.set_gamemode(game_mode).await; + } + response.send(()).unwrap(); + } + } + } + }); + player_event +} diff --git a/pumpkin/src/plugin/mod.rs b/pumpkin/src/plugin/mod.rs index b4786b562..f53b5a1f1 100644 --- a/pumpkin/src/plugin/mod.rs +++ b/pumpkin/src/plugin/mod.rs @@ -1,9 +1,9 @@ pub mod api; pub use api::*; -use std::{any::Any, fs, path::Path, sync::Arc}; +use std::{any::Any, fs, future::Future, path::Path, pin::Pin, sync::Arc}; -use crate::command::dispatcher::CommandDispatcher; +use crate::server::Server; type PluginData = ( PluginMetadata<'static>, @@ -14,7 +14,7 @@ type PluginData = ( pub struct PluginManager { plugins: Vec, - command_dispatcher: Option>>, + server: Option>, } impl Default for PluginManager { @@ -23,17 +23,59 @@ impl Default for PluginManager { } } +const EVENT_PLAYER_JOIN: &str = "player_join"; +const EVENT_PLAYER_LEAVE: &str = "player_leave"; + +type EventResult = Result; +type EventFuture<'a> = Pin + Send + 'a>>; + +fn create_default_handler() -> EventFuture<'static> { + Box::pin(async { Ok(false) }) +} + +fn handle_player_event<'a>( + hooks: &'a mut Box, + context: &'a Context, + event: &'a (dyn Any + Send + Sync), + handler: impl Fn( + &'a mut Box, + &'a Context, + &'a types::player::PlayerEvent, + ) -> EventFuture<'a>, +) -> EventFuture<'a> { + event + .downcast_ref::() + .map_or_else(|| create_default_handler(), |e| handler(hooks, context, e)) +} + +fn match_event<'a>( + event_name: &str, + hooks: &'a mut Box, + context: &'a Context, + event: &'a (dyn Any + Send + Sync), +) -> EventFuture<'a> { + match event_name { + EVENT_PLAYER_JOIN => { + handle_player_event(hooks, context, event, |h, c, e| h.on_player_join(c, e)) + } + EVENT_PLAYER_LEAVE => { + handle_player_event(hooks, context, event, |h, c, e| h.on_player_leave(c, e)) + } + _ => create_default_handler(), + } +} + impl PluginManager { #[must_use] pub fn new() -> Self { - PluginManager { + Self { plugins: vec![], - command_dispatcher: None, + server: None, } } - pub fn set_command_dispatcher(&mut self, dispatcher: Arc>) { - self.command_dispatcher = Some(dispatcher); + pub fn set_server(&mut self, server: Arc) { + self.server = Some(server); } pub fn load_plugins(&mut self) -> Result<(), String> { @@ -58,8 +100,11 @@ impl PluginManager { let metadata: &PluginMetadata = unsafe { &**library.get::<*const PluginMetadata>(b"METADATA").unwrap() }; - // let dispatcher = self.command_dispatcher.clone().expect("Command dispatcher not set").clone(); - let context = handle_context(metadata.clone() /* , dispatcher */); + // The chance that this will panic is non-existent, but just in case + let context = handle_context( + metadata.clone(), /* , dispatcher */ + self.server.clone().expect("Server not set"), + ); let mut plugin_box = plugin_fn(); let res = plugin_box.on_load(&context); let mut loaded = true; @@ -102,7 +147,10 @@ impl PluginManager { }; if let Some(matching_event) = registered_events.iter().find(|e| e.name == event_name) { - let context = handle_context(metadata.clone() /* , dispatcher.clone() */); + let context = handle_context( + metadata.clone(), /* , dispatcher.clone() */ + self.server.clone().expect("Server not set"), + ); if matching_event.blocking { blocking_hooks.push((context, hooks)); @@ -135,55 +183,17 @@ impl PluginManager { let event = event as &(dyn Any + Sync + Send); for (context, hooks) in blocking_hooks { - let r = match event_name { - "player_join" => { - if let Some(event) = event.downcast_ref::() { - hooks.on_player_join(&context, event) - } else { - Box::pin(async { Ok(false) }) - } - } - "player_leave" => { - if let Some(event) = event.downcast_ref::() { - hooks.on_player_leave(&context, event) - } else { - Box::pin(async { Ok(false) }) - } - } - _ => Box::pin(async { Ok(false) }), - }; - match r.await { + match match_event(event_name, hooks, &context, event).await { Ok(true) => return true, - Err(e) => { - log::error!("Error in plugin: {}", e); - } + Err(e) => log::error!("Error in plugin: {}", e), _ => {} } } for (context, hooks) in non_blocking_hooks { - let r = match event_name { - "player_join" => { - if let Some(event) = event.downcast_ref::() { - hooks.on_player_join(&context, event) - } else { - Box::pin(async { Ok(false) }) - } - } - "player_leave" => { - if let Some(event) = event.downcast_ref::() { - hooks.on_player_leave(&context, event) - } else { - Box::pin(async { Ok(false) }) - } - } - _ => Box::pin(async { Ok(false) }), - }; - match r.await { + match match_event(event_name, hooks, &context, event).await { Ok(true) => continue, - Err(e) => { - log::error!("Error in plugin: {}", e); - } + Err(e) => log::error!("Error in plugin: {}", e), _ => {} } } From 2f71fd7c16159c40f635a0b8627799d0d5855bd8 Mon Sep 17 00:00:00 2001 From: vyPal Date: Mon, 23 Dec 2024 13:03:35 +0100 Subject: [PATCH 44/62] Async on_load and on_unload --- plugins/hello-plugin-source/src/lib.rs | 4 ++-- pumpkin-api-macros/src/lib.rs | 3 ++- pumpkin/src/plugin/api/mod.rs | 6 ++++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/plugins/hello-plugin-source/src/lib.rs b/plugins/hello-plugin-source/src/lib.rs index 316c6f016..f5d4ce751 100644 --- a/plugins/hello-plugin-source/src/lib.rs +++ b/plugins/hello-plugin-source/src/lib.rs @@ -17,7 +17,7 @@ struct Bans { } #[plugin_method] -fn on_load(&mut self, server: &Context) -> Result<(), String> { +async fn on_load(&mut self, server: &Context) -> Result<(), String> { let data_folder = server.get_data_folder(); if !fs::exists(format!("{}/data.toml", data_folder)).unwrap() { let cfg = toml::to_string(&self.config).unwrap(); @@ -38,7 +38,7 @@ fn on_load(&mut self, server: &Context) -> Result<(), String> { } #[plugin_method] -fn on_unload(&mut self, server: &Context) -> Result<(), String> { +async fn on_unload(&mut self, server: &Context) -> Result<(), String> { let data_folder = server.get_data_folder(); let cfg = toml::to_string(&self.config).unwrap(); fs::write(format!("{}/data.toml", data_folder), cfg).unwrap(); diff --git a/pumpkin-api-macros/src/lib.rs b/pumpkin-api-macros/src/lib.rs index b1af63151..1a8ff75e3 100644 --- a/pumpkin-api-macros/src/lib.rs +++ b/pumpkin-api-macros/src/lib.rs @@ -28,7 +28,7 @@ pub fn plugin_method(attr: TokenStream, item: TokenStream) -> TokenStream { let method = quote! { #[allow(unused_mut)] - fn #fn_name(#fn_inputs) #fn_output { + async fn #fn_name(#fn_inputs) #fn_output { #fn_body } } @@ -188,6 +188,7 @@ pub fn plugin_impl(attr: TokenStream, item: TokenStream) -> TokenStream { impl pumpkin::plugin::Plugin for #struct_ident {} + #[async_trait::async_trait] impl pumpkin::plugin::PluginMethods for #struct_ident { #(#methods)* } diff --git a/pumpkin/src/plugin/api/mod.rs b/pumpkin/src/plugin/api/mod.rs index 29cb354f0..e4eff68da 100644 --- a/pumpkin/src/plugin/api/mod.rs +++ b/pumpkin/src/plugin/api/mod.rs @@ -2,6 +2,7 @@ pub mod context; pub mod events; pub mod types; +use async_trait::async_trait; pub use context::*; pub use events::*; @@ -17,14 +18,15 @@ pub struct PluginMetadata<'s> { pub description: &'s str, } +#[async_trait] pub trait PluginMethods: Send + Sync + 'static { /// Called when the plugin is loaded. - fn on_load(&mut self, _server: &Context) -> Result<(), String> { + async fn on_load(&mut self, _server: &Context) -> Result<(), String> { Ok(()) } /// Called when the plugin is unloaded. - fn on_unload(&mut self, _server: &Context) -> Result<(), String> { + async fn on_unload(&mut self, _server: &Context) -> Result<(), String> { Ok(()) } } From 9bd82c8d52d064c72c6e194c52e8da8351e3f578 Mon Sep 17 00:00:00 2001 From: vyPal Date: Mon, 23 Dec 2024 13:04:03 +0100 Subject: [PATCH 45/62] Fix mutex lock never going out of scope --- pumpkin/src/main.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pumpkin/src/main.rs b/pumpkin/src/main.rs index 3d6663555..59466078c 100644 --- a/pumpkin/src/main.rs +++ b/pumpkin/src/main.rs @@ -179,9 +179,11 @@ async fn main() { let server = Arc::new(Server::new()); let mut ticker = Ticker::new(BASIC_CONFIG.tps); - let mut loader_lock = PLUGIN_MANAGER.lock().await; - loader_lock.set_server(server.clone()); - loader_lock.load_plugins().unwrap(); + { + let mut loader_lock = PLUGIN_MANAGER.lock().await; + loader_lock.set_server(server.clone()); + loader_lock.load_plugins().await.unwrap(); + } log::info!("Started Server took {}ms", time.elapsed().as_millis()); log::info!("You now can connect to the server, Listening on {}", addr); From fed1364478e81059086860624df6c96dd4fc2caf Mon Sep 17 00:00:00 2001 From: vyPal Date: Mon, 23 Dec 2024 13:04:18 +0100 Subject: [PATCH 46/62] Async plugin loading --- pumpkin/src/plugin/mod.rs | 62 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 58 insertions(+), 4 deletions(-) diff --git a/pumpkin/src/plugin/mod.rs b/pumpkin/src/plugin/mod.rs index f53b5a1f1..d265ba4ca 100644 --- a/pumpkin/src/plugin/mod.rs +++ b/pumpkin/src/plugin/mod.rs @@ -78,7 +78,7 @@ impl PluginManager { self.server = Some(server); } - pub fn load_plugins(&mut self) -> Result<(), String> { + pub async fn load_plugins(&mut self) -> Result<(), String> { const PLUGIN_DIR: &str = "./plugins"; let dir_entires = fs::read_dir(PLUGIN_DIR); @@ -87,13 +87,13 @@ impl PluginManager { if !entry.as_ref().unwrap().path().is_file() { continue; } - self.try_load_plugin(entry.unwrap().path().as_path()); + self.try_load_plugin(entry.unwrap().path().as_path()).await; } Ok(()) } - fn try_load_plugin(&mut self, path: &Path) { + async fn try_load_plugin(&mut self, path: &Path) { let library = unsafe { libloading::Library::new(path).unwrap() }; let plugin_fn = unsafe { library.get:: Box>(b"plugin").unwrap() }; @@ -106,7 +106,7 @@ impl PluginManager { self.server.clone().expect("Server not set"), ); let mut plugin_box = plugin_fn(); - let res = plugin_box.on_load(&context); + let res = plugin_box.on_load(&context).await; let mut loaded = true; if let Err(e) = res { log::error!("Error loading plugin: {}", e); @@ -117,6 +117,60 @@ impl PluginManager { .push((metadata.clone(), plugin_box, library, loaded)); } + pub fn is_plugin_loaded(&self, name: &str) -> bool { + self.plugins + .iter() + .any(|(metadata, _, _, loaded)| metadata.name == name && *loaded) + } + + pub async fn load_plugin(&mut self, name: &str) -> Result<(), String> { + let plugin = self + .plugins + .iter_mut() + .find(|(metadata, _, _, _)| metadata.name == name); + + if let Some((metadata, plugin, _, loaded)) = plugin { + if *loaded { + return Err(format!("Plugin {} is already loaded", name)); + } + + let context = handle_context( + metadata.clone(), /* , dispatcher */ + self.server.clone().expect("Server not set"), + ); + let res = plugin.on_load(&context).await; + if let Err(e) = res { + return Err(e); + } + *loaded = true; + Ok(()) + } else { + Err(format!("Plugin {} not found", name)) + } + } + + pub async fn unload_plugin(&mut self, name: &str) -> Result<(), String> { + let plugin = self + .plugins + .iter_mut() + .find(|(metadata, _, _, _)| metadata.name == name); + + if let Some((metadata, plugin, _, loaded)) = plugin { + let context = handle_context( + metadata.clone(), /* , dispatcher */ + self.server.clone().expect("Server not set"), + ); + let res = plugin.on_unload(&context).await; + if let Err(e) = res { + return Err(e); + } + *loaded = false; + Ok(()) + } else { + Err(format!("Plugin {} not found", name)) + } + } + #[must_use] pub fn list_plugins(&self) -> Vec<(&PluginMetadata, &bool)> { self.plugins From bb8f0baa0f00114061a5c9669929b2b798d22941 Mon Sep 17 00:00:00 2001 From: vyPal Date: Mon, 23 Dec 2024 13:04:27 +0100 Subject: [PATCH 47/62] Add plugin management command --- pumpkin/src/command/commands/cmd_plugin.rs | 198 +++++++++++++++++++++ pumpkin/src/command/commands/mod.rs | 1 + pumpkin/src/command/mod.rs | 3 +- 3 files changed, 201 insertions(+), 1 deletion(-) create mode 100644 pumpkin/src/command/commands/cmd_plugin.rs diff --git a/pumpkin/src/command/commands/cmd_plugin.rs b/pumpkin/src/command/commands/cmd_plugin.rs new file mode 100644 index 000000000..039bed8c5 --- /dev/null +++ b/pumpkin/src/command/commands/cmd_plugin.rs @@ -0,0 +1,198 @@ +use async_trait::async_trait; +use pumpkin_core::text::{color::NamedColor, hover::HoverEvent, TextComponent}; + +use crate::{ + command::{ + args::{arg_simple::SimpleArgConsumer, Arg, ConsumedArgs}, + tree::CommandTree, + tree_builder::{argument, literal, require}, + CommandError, CommandExecutor, CommandSender, + }, + entity::player::PermissionLvl, + PLUGIN_MANAGER, +}; + +use crate::command::CommandError::InvalidConsumption; + +const NAMES: [&str; 1] = ["plugin"]; + +const DESCRIPTION: &str = "Manage plugins."; + +const PLUGIN_NAME: &str = "plugin_name"; + +struct ListExecutor; + +#[async_trait] +impl CommandExecutor for ListExecutor { + async fn execute<'a>( + &self, + sender: &mut CommandSender<'a>, + _server: &crate::server::Server, + _args: &ConsumedArgs<'a>, + ) -> Result<(), CommandError> { + let plugin_manager = PLUGIN_MANAGER.lock().await; + let plugins = plugin_manager.list_plugins(); + + let message_text = if plugins.is_empty() { + "There are no loaded plugins." + } else if plugins.len() == 1 { + "There is 1 plugin loaded:\n" + } else { + &format!("There are {} plugins loaded:\n", plugins.len(),) + }; + let mut message = TextComponent::text(message_text); + + for (i, (metadata, loaded)) in plugins.clone().into_iter().enumerate() { + let fmt = if i == plugins.len() - 1 { + metadata.name.to_string() + } else { + format!("{}, ", metadata.name) + }; + let hover_text = format!( + "Version: {}\nAuthors: {}\nDescription: {}", + metadata.version, metadata.authors, metadata.description + ); + let component = if *loaded { + TextComponent::text_string(fmt) + .color_named(NamedColor::Green) + .hover_event(HoverEvent::ShowText(hover_text.into())) + } else { + TextComponent::text_string(fmt) + .color_named(NamedColor::Red) + .hover_event(HoverEvent::ShowText(hover_text.into())) + }; + message = message.add_child(component); + } + + sender.send_message(message).await; + + Ok(()) + } +} + +struct LoadExecutor; + +#[async_trait] +impl CommandExecutor for LoadExecutor { + async fn execute<'a>( + &self, + sender: &mut CommandSender<'a>, + _server: &crate::server::Server, + args: &ConsumedArgs<'a>, + ) -> Result<(), CommandError> { + let Some(Arg::Simple(plugin_name)) = args.get(PLUGIN_NAME) else { + return Err(InvalidConsumption(Some(PLUGIN_NAME.into()))); + }; + let mut plugin_manager = PLUGIN_MANAGER.lock().await; + + if plugin_manager.is_plugin_loaded(plugin_name) { + sender + .send_message( + TextComponent::text_string(format!("Plugin {} is already loaded", plugin_name)) + .color_named(NamedColor::Red), + ) + .await; + return Ok(()); + } + + let result = plugin_manager.load_plugin(plugin_name).await; + + match result { + Ok(_) => { + sender + .send_message( + TextComponent::text_string(format!( + "Plugin {} loaded successfully", + plugin_name + )) + .color_named(NamedColor::Green), + ) + .await; + } + Err(e) => { + sender + .send_message( + TextComponent::text_string(format!( + "Failed to load plugin {}: {}", + plugin_name, e + )) + .color_named(NamedColor::Red), + ) + .await; + } + } + + Ok(()) + } +} + +struct UnloadExecutor; + +#[async_trait] +impl CommandExecutor for UnloadExecutor { + async fn execute<'a>( + &self, + sender: &mut CommandSender<'a>, + _server: &crate::server::Server, + args: &ConsumedArgs<'a>, + ) -> Result<(), CommandError> { + let Some(Arg::Simple(plugin_name)) = args.get(PLUGIN_NAME) else { + return Err(InvalidConsumption(Some(PLUGIN_NAME.into()))); + }; + let mut plugin_manager = PLUGIN_MANAGER.lock().await; + + if !plugin_manager.is_plugin_loaded(plugin_name) { + sender + .send_message( + TextComponent::text_string(format!("Plugin {} is not loaded", plugin_name)) + .color_named(NamedColor::Red), + ) + .await; + return Ok(()); + } + + let result = plugin_manager.unload_plugin(plugin_name).await; + + match result { + Ok(_) => { + sender + .send_message( + TextComponent::text_string(format!( + "Plugin {} unloaded successfully", + plugin_name + )) + .color_named(NamedColor::Green), + ) + .await; + } + Err(e) => { + sender + .send_message( + TextComponent::text_string(format!( + "Failed to unload plugin {}: {}", + plugin_name, e + )) + .color_named(NamedColor::Red), + ) + .await; + } + } + + Ok(()) + } +} + +pub fn init_command_tree<'a>() -> CommandTree<'a> { + CommandTree::new(NAMES, DESCRIPTION).with_child( + require(&|sender| sender.has_permission_lvl(PermissionLvl::Three)) + .with_child( + literal("load") + .with_child(argument(PLUGIN_NAME, &SimpleArgConsumer).execute(&LoadExecutor)), + ) + .with_child( + literal("unload") + .with_child(argument(PLUGIN_NAME, &SimpleArgConsumer).execute(&UnloadExecutor)), + ) + .with_child(literal("list").execute(&ListExecutor)), + ) +} diff --git a/pumpkin/src/command/commands/mod.rs b/pumpkin/src/command/commands/mod.rs index 78e9d32fc..182845acc 100644 --- a/pumpkin/src/command/commands/mod.rs +++ b/pumpkin/src/command/commands/mod.rs @@ -7,6 +7,7 @@ pub mod cmd_help; pub mod cmd_kick; pub mod cmd_kill; pub mod cmd_list; +pub mod cmd_plugin; pub mod cmd_plugins; pub mod cmd_pumpkin; pub mod cmd_say; diff --git a/pumpkin/src/command/mod.rs b/pumpkin/src/command/mod.rs index 74f7d6700..9d6547e5c 100644 --- a/pumpkin/src/command/mod.rs +++ b/pumpkin/src/command/mod.rs @@ -11,7 +11,7 @@ use args::ConsumedArgs; use async_trait::async_trait; use commands::{ cmd_clear, cmd_fill, cmd_gamemode, cmd_give, cmd_help, cmd_kick, cmd_kill, cmd_list, - cmd_plugins, cmd_pumpkin, cmd_say, cmd_setblock, cmd_stop, cmd_teleport, cmd_time, + cmd_plugin, cmd_plugins, cmd_pumpkin, cmd_say, cmd_setblock, cmd_stop, cmd_teleport, cmd_time, cmd_worldborder, }; use dispatcher::CommandError; @@ -119,6 +119,7 @@ pub fn default_dispatcher<'a>() -> Arc> { dispatcher.register(cmd_help::init_command_tree()); dispatcher.register(cmd_kill::init_command_tree()); dispatcher.register(cmd_kick::init_command_tree()); + dispatcher.register(cmd_plugin::init_command_tree()); dispatcher.register(cmd_plugins::init_command_tree()); dispatcher.register(cmd_worldborder::init_command_tree()); dispatcher.register(cmd_teleport::init_command_tree()); From 6d7ec38e8d393c0746b8a9258e71f47bf7ba39ed Mon Sep 17 00:00:00 2001 From: vyPal Date: Mon, 23 Dec 2024 13:19:10 +0100 Subject: [PATCH 48/62] Fix clippy issues --- pumpkin/src/command/commands/cmd_plugin.rs | 20 ++++++++------------ pumpkin/src/main.rs | 2 +- pumpkin/src/plugin/mod.rs | 15 ++++++--------- 3 files changed, 15 insertions(+), 22 deletions(-) diff --git a/pumpkin/src/command/commands/cmd_plugin.rs b/pumpkin/src/command/commands/cmd_plugin.rs index 039bed8c5..5d779e681 100644 --- a/pumpkin/src/command/commands/cmd_plugin.rs +++ b/pumpkin/src/command/commands/cmd_plugin.rs @@ -88,7 +88,7 @@ impl CommandExecutor for LoadExecutor { if plugin_manager.is_plugin_loaded(plugin_name) { sender .send_message( - TextComponent::text_string(format!("Plugin {} is already loaded", plugin_name)) + TextComponent::text_string(format!("Plugin {plugin_name} is already loaded")) .color_named(NamedColor::Red), ) .await; @@ -98,12 +98,11 @@ impl CommandExecutor for LoadExecutor { let result = plugin_manager.load_plugin(plugin_name).await; match result { - Ok(_) => { + Ok(()) => { sender .send_message( TextComponent::text_string(format!( - "Plugin {} loaded successfully", - plugin_name + "Plugin {plugin_name} loaded successfully" )) .color_named(NamedColor::Green), ) @@ -113,8 +112,7 @@ impl CommandExecutor for LoadExecutor { sender .send_message( TextComponent::text_string(format!( - "Failed to load plugin {}: {}", - plugin_name, e + "Failed to load plugin {plugin_name}: {e}" )) .color_named(NamedColor::Red), ) @@ -144,7 +142,7 @@ impl CommandExecutor for UnloadExecutor { if !plugin_manager.is_plugin_loaded(plugin_name) { sender .send_message( - TextComponent::text_string(format!("Plugin {} is not loaded", plugin_name)) + TextComponent::text_string(format!("Plugin {plugin_name} is not loaded")) .color_named(NamedColor::Red), ) .await; @@ -154,12 +152,11 @@ impl CommandExecutor for UnloadExecutor { let result = plugin_manager.unload_plugin(plugin_name).await; match result { - Ok(_) => { + Ok(()) => { sender .send_message( TextComponent::text_string(format!( - "Plugin {} unloaded successfully", - plugin_name + "Plugin {plugin_name} unloaded successfully", )) .color_named(NamedColor::Green), ) @@ -169,8 +166,7 @@ impl CommandExecutor for UnloadExecutor { sender .send_message( TextComponent::text_string(format!( - "Failed to unload plugin {}: {}", - plugin_name, e + "Failed to unload plugin {plugin_name}: {e}" )) .color_named(NamedColor::Red), ) diff --git a/pumpkin/src/main.rs b/pumpkin/src/main.rs index 59466078c..853595085 100644 --- a/pumpkin/src/main.rs +++ b/pumpkin/src/main.rs @@ -183,7 +183,7 @@ async fn main() { let mut loader_lock = PLUGIN_MANAGER.lock().await; loader_lock.set_server(server.clone()); loader_lock.load_plugins().await.unwrap(); - } + }; log::info!("Started Server took {}ms", time.elapsed().as_millis()); log::info!("You now can connect to the server, Listening on {}", addr); diff --git a/pumpkin/src/plugin/mod.rs b/pumpkin/src/plugin/mod.rs index d265ba4ca..d301e48c0 100644 --- a/pumpkin/src/plugin/mod.rs +++ b/pumpkin/src/plugin/mod.rs @@ -117,6 +117,7 @@ impl PluginManager { .push((metadata.clone(), plugin_box, library, loaded)); } + #[must_use] pub fn is_plugin_loaded(&self, name: &str) -> bool { self.plugins .iter() @@ -131,7 +132,7 @@ impl PluginManager { if let Some((metadata, plugin, _, loaded)) = plugin { if *loaded { - return Err(format!("Plugin {} is already loaded", name)); + return Err(format!("Plugin {name} is already loaded")); } let context = handle_context( @@ -139,13 +140,11 @@ impl PluginManager { self.server.clone().expect("Server not set"), ); let res = plugin.on_load(&context).await; - if let Err(e) = res { - return Err(e); - } + res?; *loaded = true; Ok(()) } else { - Err(format!("Plugin {} not found", name)) + Err(format!("Plugin {name} not found")) } } @@ -161,13 +160,11 @@ impl PluginManager { self.server.clone().expect("Server not set"), ); let res = plugin.on_unload(&context).await; - if let Err(e) = res { - return Err(e); - } + res?; *loaded = false; Ok(()) } else { - Err(format!("Plugin {} not found", name)) + Err(format!("Plugin {name} not found")) } } From e04dda16f360c9015cf604b02756dfeba7b3218e Mon Sep 17 00:00:00 2001 From: vyPal Date: Thu, 26 Dec 2024 10:35:52 +0100 Subject: [PATCH 49/62] Fix import issues --- pumpkin/src/lib.rs | 4 +--- pumpkin/src/main.rs | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/pumpkin/src/lib.rs b/pumpkin/src/lib.rs index 1c00f2272..6fe9304ab 100644 --- a/pumpkin/src/lib.rs +++ b/pumpkin/src/lib.rs @@ -1,17 +1,15 @@ use std::sync::LazyLock; -use client::Client; use plugin::PluginManager; use pumpkin_core::text::TextComponent; use tokio::sync::Mutex; pub mod block; -pub mod client; pub mod command; pub mod entity; pub mod error; +pub mod net; pub mod plugin; -pub mod proxy; pub mod server; pub mod world; diff --git a/pumpkin/src/main.rs b/pumpkin/src/main.rs index d8f5d63f8..1237ce3e8 100644 --- a/pumpkin/src/main.rs +++ b/pumpkin/src/main.rs @@ -35,8 +35,8 @@ compile_error!("Compiling for WASI targets is not supported!"); use log::LevelFilter; -use plugin::PluginManager; use net::{lan_broadcast, query, rcon::RCONServer, Client}; +use plugin::PluginManager; use server::{ticker::Ticker, Server}; use std::{ io::{self}, @@ -62,8 +62,8 @@ pub mod block; pub mod command; pub mod entity; pub mod error; -pub mod plugin; pub mod net; +pub mod plugin; pub mod server; pub mod world; From 94cb711bbf92760ba45995cfc02dd0666f3641ae Mon Sep 17 00:00:00 2001 From: vyPal Date: Thu, 26 Dec 2024 13:04:09 +0100 Subject: [PATCH 50/62] Move TcpConnection out of client --- pumpkin/src/main.rs | 77 +++++++++++++++++++++++++++++++++++++++--- pumpkin/src/net/mod.rs | 34 ++++++++++--------- 2 files changed, 90 insertions(+), 21 deletions(-) diff --git a/pumpkin/src/main.rs b/pumpkin/src/main.rs index 1237ce3e8..801475c70 100644 --- a/pumpkin/src/main.rs +++ b/pumpkin/src/main.rs @@ -42,7 +42,7 @@ use std::{ io::{self}, sync::LazyLock, }; -use tokio::io::{AsyncBufReadExt, BufReader}; +use tokio::{io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader}, net::tcp::OwnedReadHalf}; #[cfg(not(unix))] use tokio::signal::ctrl_c; #[cfg(unix)] @@ -54,7 +54,7 @@ use std::sync::Arc; use crate::server::CURRENT_MC_VERSION; use pumpkin_config::{ADVANCED_CONFIG, BASIC_CONFIG}; use pumpkin_core::text::{color::NamedColor, TextComponent}; -use pumpkin_protocol::CURRENT_MC_PROTOCOL; +use pumpkin_protocol::{client, CURRENT_MC_PROTOCOL}; use std::time::Instant; // Setup some tokens to allow us to identify which event is for which socket. @@ -228,7 +228,27 @@ async fn main() { id ); - let client = Arc::new(Client::new(connection, addr, id)); + let (tx, mut rx) = tokio::sync::mpsc::channel(16); + let (connection_reader, connection_writer) = connection.into_split(); + let connection_reader = Arc::new(Mutex::new(connection_reader)); + let connection_writer = Arc::new(Mutex::new(connection_writer)); + + let client = Arc::new(Client::new(tx, addr, id)); + + let client_clone = client.clone(); + tokio::spawn(async move { + while let Some(packet) = rx.recv().await { + let mut writer = connection_writer.lock().await; + match writer.write_all(&packet).await { + Ok(_) => (), + Err(e) => { + log::warn!("Failed to write packet to client: {e}"); + client_clone.close(); + break; + } + } + } + }); let server = server.clone(); tokio::spawn(async move { @@ -237,7 +257,7 @@ async fn main() { .make_player .load(std::sync::atomic::Ordering::Relaxed) { - let open = client.poll().await; + let open = poll(&client, connection_reader.clone()).await; if open { client.process_packets(&server).await; }; @@ -257,7 +277,7 @@ async fn main() { .closed .load(core::sync::atomic::Ordering::Relaxed) { - let open = player.client.poll().await; + let open = poll(&player.client, connection_reader.clone()).await; if open { player.process_packets(&server).await; }; @@ -270,6 +290,53 @@ async fn main() { } } +async fn poll(client: &Client, connection_reader: Arc>) -> bool { + loop { + if client.closed.load(std::sync::atomic::Ordering::Relaxed) { + // If we manually close (like a kick) we dont want to keep reading bytes + return false; + } + + let mut dec = client.dec.lock().await; + + match dec.decode() { + Ok(Some(packet)) => { + client.add_packet(packet).await; + return true; + } + Ok(None) => (), //log::debug!("Waiting for more data to complete packet..."), + Err(err) => { + log::warn!("Failed to decode packet for: {}", err.to_string()); + client.close(); + return false; // return to avoid reserving additional bytes + } + } + + dec.reserve(4096); + let mut buf = dec.take_capacity(); + + let bytes_read = connection_reader.lock().await.read_buf(&mut buf).await; + match bytes_read { + Ok(cnt) => { + //log::debug!("Read {} bytes", cnt); + if cnt == 0 { + client.close(); + return false; + } + } + Err(error) => { + log::error!("Error while reading incoming packet {}", error); + client.close(); + return false; + } + }; + + // This should always be an O(1) unsplit because we reserved space earlier and + // the call to `read_buf` shouldn't have grown the allocation. + dec.queue_bytes(buf); + } +} + fn handle_interrupt() { log::warn!( "{}", diff --git a/pumpkin/src/net/mod.rs b/pumpkin/src/net/mod.rs index 0e5f9e9c2..48314831c 100644 --- a/pumpkin/src/net/mod.rs +++ b/pumpkin/src/net/mod.rs @@ -13,6 +13,7 @@ use crate::{ server::Server, }; +use bytes::BytesMut; use crossbeam::atomic::AtomicCell; use pumpkin_config::compression::CompressionInfo; use pumpkin_core::{text::TextComponent, ProfileAction}; @@ -38,7 +39,7 @@ use pumpkin_protocol::{ use serde::Deserialize; use sha1::Digest; use sha2::Sha256; -use tokio::io::{AsyncReadExt, AsyncWriteExt}; +use tokio::sync::mpsc; use tokio::sync::Mutex; use thiserror::Error; @@ -127,15 +128,14 @@ pub struct Client { pub connection_state: AtomicCell, /// Indicates if the client connection is closed. pub closed: AtomicBool, - /// The underlying TCP connection to the client. - pub connection_reader: Arc>, - pub connection_writer: Arc>, /// The client's IP address. pub address: Mutex, /// The packet encoder for outgoing packets. - enc: Arc>, + pub enc: Arc>, /// The packet decoder for incoming packets. - dec: Arc>, + pub dec: Arc>, + /// A channel for sending packets to the client. + pub server_packets_channel: mpsc::Sender, /// A queue of raw packets received from the client, waiting to be processed. pub client_packets_queue: Arc>>, /// Indicates whether the client should be converted into a player. @@ -144,8 +144,7 @@ pub struct Client { impl Client { #[must_use] - pub fn new(connection: tokio::net::TcpStream, address: SocketAddr, id: u16) -> Self { - let (connection_reader, connection_writer) = connection.into_split(); + pub fn new(server_packets_channel: mpsc::Sender, address: SocketAddr, id: u16) -> Self { Self { id, protocol_version: AtomicI32::new(0), @@ -155,11 +154,10 @@ impl Client { server_address: Mutex::new(String::new()), address: Mutex::new(address), connection_state: AtomicCell::new(ConnectionState::HandShake), - connection_reader: Arc::new(Mutex::new(connection_reader)), - connection_writer: Arc::new(Mutex::new(connection_writer)), enc: Arc::new(Mutex::new(PacketEncoder::default())), dec: Arc::new(Mutex::new(PacketDecoder::default())), closed: AtomicBool::new(false), + server_packets_channel, client_packets_queue: Arc::new(Mutex::new(VecDeque::new())), make_player: AtomicBool::new(false), } @@ -245,10 +243,12 @@ impl Client { return; } - let mut writer = self.connection_writer.lock().await; + let _ = self.server_packets_channel.send(enc.take()).await; + + /* let mut writer = self.connection_writer.lock().await; if let Err(error) = writer.write_all(&enc.take()).await { log::debug!("Unable to write to connection: {}", error.to_string()); - } + } */ /* else if let Err(error) = writer.flush().await { @@ -290,8 +290,10 @@ impl Client { let mut enc = self.enc.lock().await; enc.append_packet(packet)?; - let mut writer = self.connection_writer.lock().await; - let _ = writer.write_all(&enc.take()).await; + let _ = self.server_packets_channel.send(enc.take()).await; + + /* let mut writer = self.connection_writer.lock().await; + let _ = writer.write_all(&enc.take()).await; */ /* writer @@ -503,7 +505,7 @@ impl Client { /// Reads the connection until our buffer of len 4096 is full, then decode /// Close connection when an error occurs or when the Client closed the connection /// Returns if connection is still open - pub async fn poll(&self) -> bool { + /* pub async fn poll(&self) -> bool { loop { if self.closed.load(std::sync::atomic::Ordering::Relaxed) { // If we manually close (like a kick) we dont want to keep reading bytes @@ -548,7 +550,7 @@ impl Client { // the call to `read_buf` shouldn't have grown the allocation. dec.queue_bytes(buf); } - } + } */ /// Disconnects a client from the server with a specified reason. /// From 499df22d0fef4ebf010260200595939f4e3ee465 Mon Sep 17 00:00:00 2001 From: vyPal Date: Thu, 26 Dec 2024 13:44:59 +0100 Subject: [PATCH 51/62] Move packet encoding out of client --- pumpkin/src/main.rs | 22 +++++++++++----------- pumpkin/src/net/mod.rs | 9 ++++----- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/pumpkin/src/main.rs b/pumpkin/src/main.rs index 801475c70..e15e8fd69 100644 --- a/pumpkin/src/main.rs +++ b/pumpkin/src/main.rs @@ -42,19 +42,22 @@ use std::{ io::{self}, sync::LazyLock, }; -use tokio::{io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader}, net::tcp::OwnedReadHalf}; #[cfg(not(unix))] use tokio::signal::ctrl_c; #[cfg(unix)] use tokio::signal::unix::{signal, SignalKind}; use tokio::sync::Mutex; +use tokio::{ + io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader}, + net::tcp::OwnedReadHalf, +}; use std::sync::Arc; use crate::server::CURRENT_MC_VERSION; use pumpkin_config::{ADVANCED_CONFIG, BASIC_CONFIG}; use pumpkin_core::text::{color::NamedColor, TextComponent}; -use pumpkin_protocol::{client, CURRENT_MC_PROTOCOL}; +use pumpkin_protocol::CURRENT_MC_PROTOCOL; use std::time::Instant; // Setup some tokens to allow us to identify which event is for which socket. @@ -237,15 +240,12 @@ async fn main() { let client_clone = client.clone(); tokio::spawn(async move { - while let Some(packet) = rx.recv().await { - let mut writer = connection_writer.lock().await; - match writer.write_all(&packet).await { - Ok(_) => (), - Err(e) => { - log::warn!("Failed to write packet to client: {e}"); - client_clone.close(); - break; - } + while let Some(_) = rx.recv().await { + let mut enc = client_clone.enc.lock().await; + let buf = enc.take(); + if let Err(e) = connection_writer.lock().await.write_all(&buf).await { + log::warn!("Failed to write packet to client: {e}"); + client_clone.close(); } } }); diff --git a/pumpkin/src/net/mod.rs b/pumpkin/src/net/mod.rs index 48314831c..3d2fb2ac2 100644 --- a/pumpkin/src/net/mod.rs +++ b/pumpkin/src/net/mod.rs @@ -13,7 +13,6 @@ use crate::{ server::Server, }; -use bytes::BytesMut; use crossbeam::atomic::AtomicCell; use pumpkin_config::compression::CompressionInfo; use pumpkin_core::{text::TextComponent, ProfileAction}; @@ -135,7 +134,7 @@ pub struct Client { /// The packet decoder for incoming packets. pub dec: Arc>, /// A channel for sending packets to the client. - pub server_packets_channel: mpsc::Sender, + pub server_packets_channel: mpsc::Sender<()>, /// A queue of raw packets received from the client, waiting to be processed. pub client_packets_queue: Arc>>, /// Indicates whether the client should be converted into a player. @@ -144,7 +143,7 @@ pub struct Client { impl Client { #[must_use] - pub fn new(server_packets_channel: mpsc::Sender, address: SocketAddr, id: u16) -> Self { + pub fn new(server_packets_channel: mpsc::Sender<()>, address: SocketAddr, id: u16) -> Self { Self { id, protocol_version: AtomicI32::new(0), @@ -243,7 +242,7 @@ impl Client { return; } - let _ = self.server_packets_channel.send(enc.take()).await; + let _ = self.server_packets_channel.send(()).await; /* let mut writer = self.connection_writer.lock().await; if let Err(error) = writer.write_all(&enc.take()).await { @@ -290,7 +289,7 @@ impl Client { let mut enc = self.enc.lock().await; enc.append_packet(packet)?; - let _ = self.server_packets_channel.send(enc.take()).await; + let _ = self.server_packets_channel.send(()).await; /* let mut writer = self.connection_writer.lock().await; let _ = writer.write_all(&enc.take()).await; */ From 87f23ec996420fb75adb4f0ebe654b2db776b750 Mon Sep 17 00:00:00 2001 From: vyPal Date: Thu, 26 Dec 2024 13:45:14 +0100 Subject: [PATCH 52/62] Allow plugins to register commands --- plugins/hello-plugin-source/Cargo.toml | 1 + plugins/hello-plugin-source/src/lib.rs | 32 ++++++++++++++ pumpkin/src/command/args/arg_bounded_num.rs | 4 +- pumpkin/src/command/args/mod.rs | 46 ++++++++++----------- pumpkin/src/command/dispatcher.rs | 2 +- pumpkin/src/command/mod.rs | 4 +- pumpkin/src/plugin/api/context.rs | 14 +++++-- 7 files changed, 72 insertions(+), 31 deletions(-) diff --git a/plugins/hello-plugin-source/Cargo.toml b/plugins/hello-plugin-source/Cargo.toml index 77178452d..b31089f67 100644 --- a/plugins/hello-plugin-source/Cargo.toml +++ b/plugins/hello-plugin-source/Cargo.toml @@ -13,6 +13,7 @@ crate-type = ["dylib"] [dependencies] pumpkin = { path = "../../pumpkin" } pumpkin-core = { path = "../../pumpkin-core" } +pumpkin-protocol = { path = "../../pumpkin-protocol" } pumpkin-api-macros = { path = "../../pumpkin-api-macros" } serde = { version = "1.0", features = ["derive"] } toml = "0.8.19" diff --git a/plugins/hello-plugin-source/src/lib.rs b/plugins/hello-plugin-source/src/lib.rs index f5d4ce751..e006a4d0f 100644 --- a/plugins/hello-plugin-source/src/lib.rs +++ b/plugins/hello-plugin-source/src/lib.rs @@ -5,6 +5,14 @@ use pumpkin_core::text::color::NamedColor; use pumpkin_core::text::TextComponent; use serde::{Deserialize, Serialize}; use std::fs; +use pumpkin::command::tree::CommandTree; +use pumpkin::command::dispatcher::CommandError; +use pumpkin::command::args::ConsumedArgs; +use pumpkin::server::Server; +use pumpkin::command::CommandSender; +use pumpkin::command::CommandExecutor; +use async_trait::async_trait; +use pumpkin_protocol::client::play::CSystemChatMessage; #[derive(Serialize, Deserialize, Debug)] struct Config { @@ -16,8 +24,32 @@ struct Bans { players: Vec, } +const NAMES: [&str; 1] = ["pcmdtest"]; + +const DESCRIPTION: &str = "Testing the ability of plugins to add commands"; + +struct SayExecutor; + +#[async_trait] +impl CommandExecutor for SayExecutor { + async fn execute<'a>( + &self, + sender: &mut CommandSender<'a>, + _server: &Server, + _args: &ConsumedArgs<'a>, + ) -> Result<(), CommandError> { + sender.send_message(TextComponent::text("Hello, world! This was sent from a plugin as a response to using a command registered by a plugin!")).await; + Ok(()) + } +} + +pub fn init_command_tree<'a>() -> CommandTree<'a> { + CommandTree::new(NAMES, DESCRIPTION).execute(&SayExecutor) +} + #[plugin_method] async fn on_load(&mut self, server: &Context) -> Result<(), String> { + server.register_command(init_command_tree()).await; let data_folder = server.get_data_folder(); if !fs::exists(format!("{}/data.toml", data_folder)).unwrap() { let cfg = toml::to_string(&self.config).unwrap(); diff --git a/pumpkin/src/command/args/arg_bounded_num.rs b/pumpkin/src/command/args/arg_bounded_num.rs index 41410367d..afaabb1e0 100644 --- a/pumpkin/src/command/args/arg_bounded_num.rs +++ b/pumpkin/src/command/args/arg_bounded_num.rs @@ -80,10 +80,10 @@ impl FindArg<'_> for BoundedNumArgumentConsumer { } } -pub(crate) type NotInBounds = (); +pub type NotInBounds = (); #[derive(Clone, Copy)] -pub(crate) enum Number { +pub enum Number { F64(f64), F32(f32), I32(i32), diff --git a/pumpkin/src/command/args/mod.rs b/pumpkin/src/command/args/mod.rs index 8342386bd..f5e358184 100644 --- a/pumpkin/src/command/args/mod.rs +++ b/pumpkin/src/command/args/mod.rs @@ -18,29 +18,29 @@ use super::{ use crate::world::bossbar::{BossbarColor, BossbarDivisions}; use crate::{entity::player::Player, server::Server}; -pub(crate) mod arg_block; -pub(crate) mod arg_bool; -pub(crate) mod arg_bossbar_color; -pub(crate) mod arg_bossbar_style; -pub(crate) mod arg_bounded_num; -pub(crate) mod arg_command; -pub(crate) mod arg_entities; -pub(crate) mod arg_entity; -pub(crate) mod arg_gamemode; -pub(crate) mod arg_item; -pub(crate) mod arg_message; -pub(crate) mod arg_players; -pub(crate) mod arg_position_2d; -pub(crate) mod arg_position_3d; -pub(crate) mod arg_position_block; -pub(crate) mod arg_resource_location; -pub(crate) mod arg_rotation; -pub(crate) mod arg_simple; +pub mod arg_block; +pub mod arg_bool; +pub mod arg_bossbar_color; +pub mod arg_bossbar_style; +pub mod arg_bounded_num; +pub mod arg_command; +pub mod arg_entities; +pub mod arg_entity; +pub mod arg_gamemode; +pub mod arg_item; +pub mod arg_message; +pub mod arg_players; +pub mod arg_position_2d; +pub mod arg_position_3d; +pub mod arg_position_block; +pub mod arg_resource_location; +pub mod arg_rotation; +pub mod arg_simple; mod coordinate; /// see [`crate::commands::tree_builder::argument`] #[async_trait] -pub(crate) trait ArgumentConsumer: Sync + GetClientSideArgParser { +pub trait ArgumentConsumer: Sync + GetClientSideArgParser { async fn consume<'a>( &self, sender: &CommandSender<'a>, @@ -59,14 +59,14 @@ pub(crate) trait ArgumentConsumer: Sync + GetClientSideArgParser { ) -> Result>>, CommandError>; } -pub(crate) trait GetClientSideArgParser { +pub trait GetClientSideArgParser { /// Return the parser the client should use while typing a command in chat. fn get_client_side_parser(&self) -> ProtoCmdArgParser; /// Usually this should return None. This can be used to force suggestions to be processed on serverside. fn get_client_side_suggestion_type_override(&self) -> Option; } -pub(crate) trait DefaultNameArgConsumer: ArgumentConsumer { +pub trait DefaultNameArgConsumer: ArgumentConsumer { fn default_name(&self) -> &'static str; /// needed because trait upcasting is not stable @@ -74,7 +74,7 @@ pub(crate) trait DefaultNameArgConsumer: ArgumentConsumer { } #[derive(Clone)] -pub(crate) enum Arg<'a> { +pub enum Arg<'a> { Entities(Vec>), Entity(Arc), Players(Vec>), @@ -97,7 +97,7 @@ pub(crate) enum Arg<'a> { } /// see [`crate::commands::tree_builder::argument`] and [`CommandTree::execute`]/[`crate::commands::tree_builder::NonLeafNodeBuilder::execute`] -pub(crate) type ConsumedArgs<'a> = HashMap<&'a str, Arg<'a>>; +pub type ConsumedArgs<'a> = HashMap<&'a str, Arg<'a>>; pub(crate) trait GetCloned { fn get_cloned(&self, key: &K) -> Option; diff --git a/pumpkin/src/command/dispatcher.rs b/pumpkin/src/command/dispatcher.rs index b2dc27ae1..8c3392880 100644 --- a/pumpkin/src/command/dispatcher.rs +++ b/pumpkin/src/command/dispatcher.rs @@ -14,7 +14,7 @@ use pumpkin_core::text::color::{Color, NamedColor}; use std::collections::{HashMap, HashSet}; #[derive(Debug)] -pub(crate) enum CommandError { +pub enum CommandError { /// This error means that there was an error while parsing a previously consumed argument. /// That only happens when consumption is wrongly implemented, as it should ensure parsing may /// never fail. diff --git a/pumpkin/src/command/mod.rs b/pumpkin/src/command/mod.rs index 6d525e6d1..ed8fd1254 100644 --- a/pumpkin/src/command/mod.rs +++ b/pumpkin/src/command/mod.rs @@ -22,7 +22,7 @@ pub mod args; pub mod client_cmd_suggestions; mod commands; pub mod dispatcher; -mod tree; +pub mod tree; mod tree_builder; mod tree_format; @@ -136,7 +136,7 @@ pub fn default_dispatcher<'a>() -> CommandDispatcher<'a> { } #[async_trait] -pub(crate) trait CommandExecutor: Sync { +pub trait CommandExecutor: Sync { async fn execute<'a>( &self, sender: &mut CommandSender<'a>, diff --git a/pumpkin/src/plugin/api/context.rs b/pumpkin/src/plugin/api/context.rs index a575cdd1e..0e3cd8f0e 100644 --- a/pumpkin/src/plugin/api/context.rs +++ b/pumpkin/src/plugin/api/context.rs @@ -50,10 +50,12 @@ impl Context { recv.await.unwrap() } - /* TODO: Implement when dispatcher is mutable pub async fn register_command(&self, tree: crate::command::tree::CommandTree<'static>) { - self.channel.send(ContextAction::RegisterCommand(tree)).await; - } */ + let _ = self + .channel + .send(ContextAction::RegisterCommand(tree)) + .await; + } } pub enum ContextAction { @@ -62,6 +64,7 @@ pub enum ContextAction { player_name: String, response: oneshot::Sender, String>>, }, + RegisterCommand(crate::command::tree::CommandTree<'static>), } pub fn handle_context( @@ -69,6 +72,7 @@ pub fn handle_context( server: Arc, ) -> Context { let (send, mut recv) = mpsc::channel(1); + let server = server.clone(); tokio::spawn(async move { while let Some(action) = recv.recv().await { match action { @@ -90,6 +94,10 @@ pub fn handle_context( response.send(Err("Player not found".to_string())).unwrap(); } } + ContextAction::RegisterCommand(tree) => { + let mut dispatcher_lock = server.command_dispatcher.write().await; + dispatcher_lock.register(tree); + } } } }); From 5354835d2827d916e5613002ceaab3449e33807b Mon Sep 17 00:00:00 2001 From: vyPal Date: Thu, 26 Dec 2024 15:09:19 +0100 Subject: [PATCH 53/62] Fix fmt and clippy --- plugins/hello-plugin-source/src/lib.rs | 27 +++++++------- pumpkin/src/command/tree_builder.rs | 3 ++ pumpkin/src/main.rs | 2 +- pumpkin/src/net/mod.rs | 50 -------------------------- pumpkin/src/plugin/api/context.rs | 2 +- pumpkin/src/plugin/mod.rs | 8 ++--- 6 files changed, 23 insertions(+), 69 deletions(-) diff --git a/plugins/hello-plugin-source/src/lib.rs b/plugins/hello-plugin-source/src/lib.rs index e006a4d0f..72ad6a008 100644 --- a/plugins/hello-plugin-source/src/lib.rs +++ b/plugins/hello-plugin-source/src/lib.rs @@ -1,18 +1,17 @@ +use async_trait::async_trait; +use pumpkin::command::args::ConsumedArgs; +use pumpkin::command::dispatcher::CommandError; +use pumpkin::command::tree::CommandTree; +use pumpkin::command::CommandExecutor; +use pumpkin::command::CommandSender; use pumpkin::plugin::api::types::player::PlayerEvent; use pumpkin::plugin::*; +use pumpkin::server::Server; use pumpkin_api_macros::{plugin_event, plugin_impl, plugin_method}; use pumpkin_core::text::color::NamedColor; use pumpkin_core::text::TextComponent; use serde::{Deserialize, Serialize}; use std::fs; -use pumpkin::command::tree::CommandTree; -use pumpkin::command::dispatcher::CommandError; -use pumpkin::command::args::ConsumedArgs; -use pumpkin::server::Server; -use pumpkin::command::CommandSender; -use pumpkin::command::CommandExecutor; -use async_trait::async_trait; -use pumpkin_protocol::client::play::CSystemChatMessage; #[derive(Serialize, Deserialize, Debug)] struct Config { @@ -80,11 +79,7 @@ async fn on_unload(&mut self, server: &Context) -> Result<(), String> { } #[plugin_event(blocking = true, priority = Highest)] -async fn on_player_join( - &mut self, - server: &Context, - player: &PlayerEvent, -) -> Result { +async fn on_player_join(&mut self, server: &Context, player: &PlayerEvent) -> Result { server.get_logger().info( format!( "Player {} joined the game. Config is {:#?}", @@ -138,3 +133,9 @@ impl MyPlugin { } } } + +impl Default for MyPlugin { + fn default() -> Self { + Self::new() + } +} diff --git a/pumpkin/src/command/tree_builder.rs b/pumpkin/src/command/tree_builder.rs index e750ea0bb..30945eb56 100644 --- a/pumpkin/src/command/tree_builder.rs +++ b/pumpkin/src/command/tree_builder.rs @@ -7,6 +7,7 @@ use super::CommandExecutor; impl<'a> CommandTree<'a> { /// Add a child [Node] to the root of this [`CommandTree`]. + #[must_use] pub fn with_child(mut self, child: impl NodeBuilder<'a>) -> Self { let node = child.build(&mut self); self.children.push(self.nodes.len()); @@ -15,6 +16,7 @@ impl<'a> CommandTree<'a> { } /// provide at least one name + #[must_use] pub fn new( names: [&'a str; NAME_COUNT], description: &'a str, @@ -42,6 +44,7 @@ impl<'a> CommandTree<'a> { /// desired type. /// /// Also see [`NonLeafNodeBuilder::execute`]. + #[must_use] pub fn execute(mut self, executor: &'a dyn CommandExecutor) -> Self { let node = Node { node_type: NodeType::ExecuteLeaf { executor }, diff --git a/pumpkin/src/main.rs b/pumpkin/src/main.rs index e15e8fd69..ebdf116e3 100644 --- a/pumpkin/src/main.rs +++ b/pumpkin/src/main.rs @@ -240,7 +240,7 @@ async fn main() { let client_clone = client.clone(); tokio::spawn(async move { - while let Some(_) = rx.recv().await { + while (rx.recv().await).is_some() { let mut enc = client_clone.enc.lock().await; let buf = enc.take(); if let Err(e) = connection_writer.lock().await.write_all(&buf).await { diff --git a/pumpkin/src/net/mod.rs b/pumpkin/src/net/mod.rs index 3d2fb2ac2..d36671766 100644 --- a/pumpkin/src/net/mod.rs +++ b/pumpkin/src/net/mod.rs @@ -501,56 +501,6 @@ impl Client { Ok(()) } - /// Reads the connection until our buffer of len 4096 is full, then decode - /// Close connection when an error occurs or when the Client closed the connection - /// Returns if connection is still open - /* pub async fn poll(&self) -> bool { - loop { - if self.closed.load(std::sync::atomic::Ordering::Relaxed) { - // If we manually close (like a kick) we dont want to keep reading bytes - return false; - } - - let mut dec = self.dec.lock().await; - - match dec.decode() { - Ok(Some(packet)) => { - self.add_packet(packet).await; - return true; - } - Ok(None) => (), //log::debug!("Waiting for more data to complete packet..."), - Err(err) => { - log::warn!("Failed to decode packet for: {}", err.to_string()); - self.close(); - return false; // return to avoid reserving additional bytes - } - } - - dec.reserve(4096); - let mut buf = dec.take_capacity(); - - let bytes_read = self.connection_reader.lock().await.read_buf(&mut buf).await; - match bytes_read { - Ok(cnt) => { - //log::debug!("Read {} bytes", cnt); - if cnt == 0 { - self.close(); - return false; - } - } - Err(error) => { - log::error!("Error while reading incoming packet {}", error); - self.close(); - return false; - } - }; - - // This should always be an O(1) unsplit because we reserved space earlier and - // the call to `read_buf` shouldn't have grown the allocation. - dec.queue_bytes(buf); - } - } */ - /// Disconnects a client from the server with a specified reason. /// /// This function kicks a client identified by its ID from the server. The appropriate disconnect packet is sent based on the client's current connection state. diff --git a/pumpkin/src/plugin/api/context.rs b/pumpkin/src/plugin/api/context.rs index 0e3cd8f0e..aa0e330cf 100644 --- a/pumpkin/src/plugin/api/context.rs +++ b/pumpkin/src/plugin/api/context.rs @@ -69,7 +69,7 @@ pub enum ContextAction { pub fn handle_context( metadata: PluginMetadata<'static>, /* , dispatcher: Arc> */ - server: Arc, + server: &Arc, ) -> Context { let (send, mut recv) = mpsc::channel(1); let server = server.clone(); diff --git a/pumpkin/src/plugin/mod.rs b/pumpkin/src/plugin/mod.rs index d301e48c0..35dda2138 100644 --- a/pumpkin/src/plugin/mod.rs +++ b/pumpkin/src/plugin/mod.rs @@ -103,7 +103,7 @@ impl PluginManager { // The chance that this will panic is non-existent, but just in case let context = handle_context( metadata.clone(), /* , dispatcher */ - self.server.clone().expect("Server not set"), + &self.server.clone().expect("Server not set"), ); let mut plugin_box = plugin_fn(); let res = plugin_box.on_load(&context).await; @@ -137,7 +137,7 @@ impl PluginManager { let context = handle_context( metadata.clone(), /* , dispatcher */ - self.server.clone().expect("Server not set"), + &self.server.clone().expect("Server not set"), ); let res = plugin.on_load(&context).await; res?; @@ -157,7 +157,7 @@ impl PluginManager { if let Some((metadata, plugin, _, loaded)) = plugin { let context = handle_context( metadata.clone(), /* , dispatcher */ - self.server.clone().expect("Server not set"), + &self.server.clone().expect("Server not set"), ); let res = plugin.on_unload(&context).await; res?; @@ -200,7 +200,7 @@ impl PluginManager { if let Some(matching_event) = registered_events.iter().find(|e| e.name == event_name) { let context = handle_context( metadata.clone(), /* , dispatcher.clone() */ - self.server.clone().expect("Server not set"), + &self.server.clone().expect("Server not set"), ); if matching_event.blocking { From 2a3a6ac6a916d648d5eacf226b877701880d865d Mon Sep 17 00:00:00 2001 From: vyPal Date: Thu, 26 Dec 2024 15:55:54 +0100 Subject: [PATCH 54/62] Implement plugin list in query --- pumpkin/src/net/query.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/pumpkin/src/net/query.rs b/pumpkin/src/net/query.rs index 0968c58cd..099eb4cd9 100644 --- a/pumpkin/src/net/query.rs +++ b/pumpkin/src/net/query.rs @@ -133,11 +133,19 @@ async fn handle_packet( } } + let plugin_manager = crate::PLUGIN_MANAGER.lock().await; + let plugins = plugin_manager + .list_plugins() + .iter() + .map(|(meta, _)| meta.name.to_string()) + .reduce(|acc, name| format!("{acc}, {name}")) + .unwrap_or_default(); + let response = CFullStatus { session_id: packet.session_id, hostname: CString::new(BASIC_CONFIG.motd.as_str())?, version: CString::new(CURRENT_MC_VERSION)?, - plugins: CString::new("Pumpkin on 1.21.4")?, // TODO: Fill this with plugins when plugins are working + plugins: CString::new(plugins)?, map: CString::new("world")?, // TODO: Get actual world name num_players: server.get_player_count().await, max_players: BASIC_CONFIG.max_players as usize, From 7402f28e5340ce2bd73acd28920be82878cff9c2 Mon Sep 17 00:00:00 2001 From: vyPal Date: Fri, 27 Dec 2024 09:25:45 +0100 Subject: [PATCH 55/62] Make arguments public so that plugins can use them --- pumpkin/src/command/args/arg_block.rs | 2 +- pumpkin/src/command/args/arg_bool.rs | 2 +- pumpkin/src/command/args/arg_bossbar_color.rs | 2 +- pumpkin/src/command/args/arg_bossbar_style.rs | 2 +- pumpkin/src/command/args/arg_command.rs | 2 +- pumpkin/src/command/args/arg_entities.rs | 2 +- pumpkin/src/command/args/arg_entity.rs | 2 +- pumpkin/src/command/args/arg_gamemode.rs | 2 +- pumpkin/src/command/args/arg_item.rs | 2 +- pumpkin/src/command/args/arg_message.rs | 2 +- pumpkin/src/command/args/arg_players.rs | 2 +- pumpkin/src/command/args/arg_position_2d.rs | 2 +- pumpkin/src/command/args/arg_position_3d.rs | 2 +- pumpkin/src/command/args/arg_position_block.rs | 2 +- pumpkin/src/command/args/arg_rotation.rs | 2 +- pumpkin/src/command/args/arg_simple.rs | 2 +- 16 files changed, 16 insertions(+), 16 deletions(-) diff --git a/pumpkin/src/command/args/arg_block.rs b/pumpkin/src/command/args/arg_block.rs index fe11228f5..a60237cfd 100644 --- a/pumpkin/src/command/args/arg_block.rs +++ b/pumpkin/src/command/args/arg_block.rs @@ -14,7 +14,7 @@ use super::{ Arg, DefaultNameArgConsumer, FindArg, GetClientSideArgParser, }; -pub(crate) struct BlockArgumentConsumer; +pub struct BlockArgumentConsumer; impl GetClientSideArgParser for BlockArgumentConsumer { fn get_client_side_parser(&self) -> ProtoCmdArgParser { diff --git a/pumpkin/src/command/args/arg_bool.rs b/pumpkin/src/command/args/arg_bool.rs index fc47f590d..55aa61047 100644 --- a/pumpkin/src/command/args/arg_bool.rs +++ b/pumpkin/src/command/args/arg_bool.rs @@ -8,7 +8,7 @@ use pumpkin_protocol::client::play::{ CommandSuggestion, ProtoCmdArgParser, ProtoCmdArgSuggestionType, }; -pub(crate) struct BoolArgConsumer; +pub struct BoolArgConsumer; impl GetClientSideArgParser for BoolArgConsumer { fn get_client_side_parser(&self) -> ProtoCmdArgParser { diff --git a/pumpkin/src/command/args/arg_bossbar_color.rs b/pumpkin/src/command/args/arg_bossbar_color.rs index 94df5a4c8..1d0dbd3dd 100644 --- a/pumpkin/src/command/args/arg_bossbar_color.rs +++ b/pumpkin/src/command/args/arg_bossbar_color.rs @@ -11,7 +11,7 @@ use pumpkin_protocol::client::play::{ CommandSuggestion, ProtoCmdArgParser, ProtoCmdArgSuggestionType, }; -pub(crate) struct BossbarColorArgumentConsumer; +pub struct BossbarColorArgumentConsumer; impl GetClientSideArgParser for BossbarColorArgumentConsumer { fn get_client_side_parser(&self) -> ProtoCmdArgParser { diff --git a/pumpkin/src/command/args/arg_bossbar_style.rs b/pumpkin/src/command/args/arg_bossbar_style.rs index 6111688be..a3371e21f 100644 --- a/pumpkin/src/command/args/arg_bossbar_style.rs +++ b/pumpkin/src/command/args/arg_bossbar_style.rs @@ -11,7 +11,7 @@ use pumpkin_protocol::client::play::{ CommandSuggestion, ProtoCmdArgParser, ProtoCmdArgSuggestionType, }; -pub(crate) struct BossbarStyleArgumentConsumer; +pub struct BossbarStyleArgumentConsumer; impl GetClientSideArgParser for BossbarStyleArgumentConsumer { fn get_client_side_parser(&self) -> ProtoCmdArgParser { diff --git a/pumpkin/src/command/args/arg_command.rs b/pumpkin/src/command/args/arg_command.rs index 9055fde10..cbe12bf46 100644 --- a/pumpkin/src/command/args/arg_command.rs +++ b/pumpkin/src/command/args/arg_command.rs @@ -15,7 +15,7 @@ use crate::{ use super::{Arg, ArgumentConsumer, DefaultNameArgConsumer, FindArg, GetClientSideArgParser}; -pub(crate) struct CommandTreeArgumentConsumer; +pub struct CommandTreeArgumentConsumer; impl GetClientSideArgParser for CommandTreeArgumentConsumer { fn get_client_side_parser(&self) -> ProtoCmdArgParser { diff --git a/pumpkin/src/command/args/arg_entities.rs b/pumpkin/src/command/args/arg_entities.rs index 102a177b5..9d5a5990f 100644 --- a/pumpkin/src/command/args/arg_entities.rs +++ b/pumpkin/src/command/args/arg_entities.rs @@ -18,7 +18,7 @@ use super::{Arg, DefaultNameArgConsumer, FindArg, GetClientSideArgParser}; /// todo: implement (currently just calls [`super::arg_player::PlayerArgumentConsumer`]) /// /// For selecting zero, one or multiple entities, eg. using @s, a player name, @a or @e -pub(crate) struct EntitiesArgumentConsumer; +pub struct EntitiesArgumentConsumer; impl GetClientSideArgParser for EntitiesArgumentConsumer { fn get_client_side_parser(&self) -> ProtoCmdArgParser { diff --git a/pumpkin/src/command/args/arg_entity.rs b/pumpkin/src/command/args/arg_entity.rs index 37e4dac09..8d14464bd 100644 --- a/pumpkin/src/command/args/arg_entity.rs +++ b/pumpkin/src/command/args/arg_entity.rs @@ -19,7 +19,7 @@ use super::{Arg, DefaultNameArgConsumer, FindArg, GetClientSideArgParser}; /// For selecting a single entity, eg. using @s, a player name or entity uuid. /// /// Use [`super::arg_entities::EntitiesArgumentConsumer`] when there may be multiple targets. -pub(crate) struct EntityArgumentConsumer; +pub struct EntityArgumentConsumer; impl GetClientSideArgParser for EntityArgumentConsumer { fn get_client_side_parser(&self) -> ProtoCmdArgParser { diff --git a/pumpkin/src/command/args/arg_gamemode.rs b/pumpkin/src/command/args/arg_gamemode.rs index d520b25cb..6b19ee574 100644 --- a/pumpkin/src/command/args/arg_gamemode.rs +++ b/pumpkin/src/command/args/arg_gamemode.rs @@ -14,7 +14,7 @@ use crate::{ use super::{Arg, ArgumentConsumer, DefaultNameArgConsumer, FindArg, GetClientSideArgParser}; -pub(crate) struct GamemodeArgumentConsumer; +pub struct GamemodeArgumentConsumer; impl GetClientSideArgParser for GamemodeArgumentConsumer { fn get_client_side_parser(&self) -> ProtoCmdArgParser { diff --git a/pumpkin/src/command/args/arg_item.rs b/pumpkin/src/command/args/arg_item.rs index 481feb7d5..e4b9b6821 100644 --- a/pumpkin/src/command/args/arg_item.rs +++ b/pumpkin/src/command/args/arg_item.rs @@ -14,7 +14,7 @@ use super::{ Arg, DefaultNameArgConsumer, FindArg, GetClientSideArgParser, }; -pub(crate) struct ItemArgumentConsumer; +pub struct ItemArgumentConsumer; impl GetClientSideArgParser for ItemArgumentConsumer { fn get_client_side_parser(&self) -> ProtoCmdArgParser { diff --git a/pumpkin/src/command/args/arg_message.rs b/pumpkin/src/command/args/arg_message.rs index 00af2d873..ccf89d074 100644 --- a/pumpkin/src/command/args/arg_message.rs +++ b/pumpkin/src/command/args/arg_message.rs @@ -14,7 +14,7 @@ use super::{ }; /// Consumes all remaining words/args. Does not consume if there is no word. -pub(crate) struct MsgArgConsumer; +pub struct MsgArgConsumer; impl GetClientSideArgParser for MsgArgConsumer { fn get_client_side_parser(&self) -> ProtoCmdArgParser { diff --git a/pumpkin/src/command/args/arg_players.rs b/pumpkin/src/command/args/arg_players.rs index 0e8876a74..2509e5dbe 100644 --- a/pumpkin/src/command/args/arg_players.rs +++ b/pumpkin/src/command/args/arg_players.rs @@ -15,7 +15,7 @@ use super::super::args::ArgumentConsumer; use super::{Arg, DefaultNameArgConsumer, FindArg, GetClientSideArgParser}; /// Select zero, one or multiple players -pub(crate) struct PlayersArgumentConsumer; +pub struct PlayersArgumentConsumer; impl GetClientSideArgParser for PlayersArgumentConsumer { fn get_client_side_parser(&self) -> ProtoCmdArgParser { diff --git a/pumpkin/src/command/args/arg_position_2d.rs b/pumpkin/src/command/args/arg_position_2d.rs index 107ea4327..77d7053f3 100644 --- a/pumpkin/src/command/args/arg_position_2d.rs +++ b/pumpkin/src/command/args/arg_position_2d.rs @@ -17,7 +17,7 @@ use super::{Arg, DefaultNameArgConsumer, FindArg, GetClientSideArgParser}; /// x and z coordinates only /// /// todo: implememnt ~ ^ notations -pub(crate) struct Position2DArgumentConsumer; +pub struct Position2DArgumentConsumer; impl GetClientSideArgParser for Position2DArgumentConsumer { fn get_client_side_parser(&self) -> ProtoCmdArgParser { diff --git a/pumpkin/src/command/args/arg_position_3d.rs b/pumpkin/src/command/args/arg_position_3d.rs index 1e58ad81d..d345e9706 100644 --- a/pumpkin/src/command/args/arg_position_3d.rs +++ b/pumpkin/src/command/args/arg_position_3d.rs @@ -14,7 +14,7 @@ use super::coordinate::MaybeRelativeCoordinate; use super::{Arg, DefaultNameArgConsumer, FindArg, GetClientSideArgParser}; /// x, y and z coordinates -pub(crate) struct Position3DArgumentConsumer; +pub struct Position3DArgumentConsumer; impl GetClientSideArgParser for Position3DArgumentConsumer { fn get_client_side_parser(&self) -> ProtoCmdArgParser { diff --git a/pumpkin/src/command/args/arg_position_block.rs b/pumpkin/src/command/args/arg_position_block.rs index 861eec12b..671424539 100644 --- a/pumpkin/src/command/args/arg_position_block.rs +++ b/pumpkin/src/command/args/arg_position_block.rs @@ -15,7 +15,7 @@ use super::coordinate::MaybeRelativeBlockCoordinate; use super::{Arg, DefaultNameArgConsumer, FindArg, GetClientSideArgParser}; /// x, y and z coordinates -pub(crate) struct BlockPosArgumentConsumer; +pub struct BlockPosArgumentConsumer; impl GetClientSideArgParser for BlockPosArgumentConsumer { fn get_client_side_parser(&self) -> ProtoCmdArgParser { diff --git a/pumpkin/src/command/args/arg_rotation.rs b/pumpkin/src/command/args/arg_rotation.rs index 6e53f2f97..dc08b0b5f 100644 --- a/pumpkin/src/command/args/arg_rotation.rs +++ b/pumpkin/src/command/args/arg_rotation.rs @@ -12,7 +12,7 @@ use super::super::args::ArgumentConsumer; use super::{Arg, DefaultNameArgConsumer, FindArg, GetClientSideArgParser}; /// yaw and pitch -pub(crate) struct RotationArgumentConsumer; +pub struct RotationArgumentConsumer; impl GetClientSideArgParser for RotationArgumentConsumer { fn get_client_side_parser(&self) -> ProtoCmdArgParser { diff --git a/pumpkin/src/command/args/arg_simple.rs b/pumpkin/src/command/args/arg_simple.rs index 1412d63ed..d71e290c1 100644 --- a/pumpkin/src/command/args/arg_simple.rs +++ b/pumpkin/src/command/args/arg_simple.rs @@ -15,7 +15,7 @@ use super::{ /// Should never be a permanent solution #[allow(unused)] -pub(crate) struct SimpleArgConsumer; +pub struct SimpleArgConsumer; impl GetClientSideArgParser for SimpleArgConsumer { fn get_client_side_parser(&self) -> ProtoCmdArgParser { From 96b302ced916504569ef214115089a8df5fca8a6 Mon Sep 17 00:00:00 2001 From: vyPal Date: Fri, 27 Dec 2024 09:28:13 +0100 Subject: [PATCH 56/62] Update proc_macros to handle runtime --- pumpkin-api-macros/src/lib.rs | 48 ++++++++++++++++++++++++++++++++--- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/pumpkin-api-macros/src/lib.rs b/pumpkin-api-macros/src/lib.rs index 1a8ff75e3..5b86168db 100644 --- a/pumpkin-api-macros/src/lib.rs +++ b/pumpkin-api-macros/src/lib.rs @@ -3,7 +3,7 @@ use proc_macro::TokenStream; use quote::quote; use std::collections::HashMap; use std::sync::Mutex; -use syn::{parse_macro_input, ItemFn, ItemStruct}; +use syn::{parse_macro_input, parse_quote, ImplItem, ItemFn, ItemImpl, ItemStruct}; static PLUGIN_METHODS: Lazy>>> = Lazy::new(|| Mutex::new(HashMap::new())); @@ -29,7 +29,9 @@ pub fn plugin_method(attr: TokenStream, item: TokenStream) -> TokenStream { let method = quote! { #[allow(unused_mut)] async fn #fn_name(#fn_inputs) #fn_output { - #fn_body + crate::GLOBAL_RUNTIME.block_on(async move { + #fn_body + }) } } .to_string(); @@ -93,7 +95,9 @@ pub fn plugin_event(attr: TokenStream, item: TokenStream) -> TokenStream { let method = quote! { #[allow(unused_mut)] async fn #fn_name(#fn_inputs) #fn_output { - #fn_body + crate::GLOBAL_RUNTIME.block_on(async move { + #fn_body + }) } } .to_string(); @@ -176,6 +180,9 @@ pub fn plugin_impl(attr: TokenStream, item: TokenStream) -> TokenStream { // Combine the original struct definition with the impl block and plugin() function let expanded = quote! { + pub static GLOBAL_RUNTIME: std::sync::LazyLock> = + std::sync::LazyLock::new(|| std::sync::Arc::new(tokio::runtime::Runtime::new().unwrap())); + #[no_mangle] pub static METADATA: pumpkin::plugin::PluginMetadata = pumpkin::plugin::PluginMetadata { name: env!("CARGO_PKG_NAME"), @@ -211,3 +218,38 @@ pub fn plugin_impl(attr: TokenStream, item: TokenStream) -> TokenStream { TokenStream::from(expanded) } + +#[proc_macro_attribute] +pub fn with_runtime(attr: TokenStream, item: TokenStream) -> TokenStream { + let mut input = parse_macro_input!(item as ItemImpl); + + let use_global = attr.to_string() == "global"; + + for item in &mut input.items { + if let ImplItem::Fn(method) = item { + if method.sig.asyncness.is_some() { + let original_body = &method.block; + + method.block = if use_global { + parse_quote!({ + crate::GLOBAL_RUNTIME.block_on(async move { + #original_body + }) + }) + } else { + parse_quote!({ + tokio::runtime::Runtime::new() + .unwrap() + .block_on(async move { + #original_body + }) + }) + }; + + method.sig.asyncness = None; + } + } + } + + TokenStream::from(quote!(#input)) +} \ No newline at end of file From 5927ce6a797089c74ce8cf0036b17ae1fd9e3d52 Mon Sep 17 00:00:00 2001 From: vyPal Date: Fri, 27 Dec 2024 09:29:01 +0100 Subject: [PATCH 57/62] Make tree_builder public for use in plugins --- pumpkin/src/command/mod.rs | 2 +- pumpkin/src/command/tree_builder.rs | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/pumpkin/src/command/mod.rs b/pumpkin/src/command/mod.rs index ed8fd1254..55071a976 100644 --- a/pumpkin/src/command/mod.rs +++ b/pumpkin/src/command/mod.rs @@ -23,7 +23,7 @@ pub mod client_cmd_suggestions; mod commands; pub mod dispatcher; pub mod tree; -mod tree_builder; +pub mod tree_builder; mod tree_format; pub enum CommandSender<'a> { diff --git a/pumpkin/src/command/tree_builder.rs b/pumpkin/src/command/tree_builder.rs index 30945eb56..72a997858 100644 --- a/pumpkin/src/command/tree_builder.rs +++ b/pumpkin/src/command/tree_builder.rs @@ -106,6 +106,7 @@ impl<'a> NodeBuilder<'a> for NonLeafNodeBuilder<'a> { impl<'a> NonLeafNodeBuilder<'a> { /// Add a child [Node] to this one. + #[must_use] pub fn with_child(mut self, child: Self) -> Self { self.child_nodes.push(child); self @@ -118,6 +119,7 @@ impl<'a> NonLeafNodeBuilder<'a> { /// desired type. /// /// Also see [`CommandTree::execute`]. + #[must_use] pub fn execute(mut self, executor: &'a dyn CommandExecutor) -> Self { self.leaf_nodes.push(LeafNodeBuilder { node_type: NodeType::ExecuteLeaf { executor }, @@ -128,6 +130,7 @@ impl<'a> NonLeafNodeBuilder<'a> { } /// Matches a sting literal. +#[must_use] pub const fn literal(string: &str) -> NonLeafNodeBuilder { NonLeafNodeBuilder { node_type: NodeType::Literal { string }, From 888276c9ac8519afa8c37e488cdc3c709bc93943 Mon Sep 17 00:00:00 2001 From: vyPal Date: Fri, 27 Dec 2024 09:29:23 +0100 Subject: [PATCH 58/62] Make FindArg trait public for use in plugins --- pumpkin/src/command/args/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pumpkin/src/command/args/mod.rs b/pumpkin/src/command/args/mod.rs index f5e358184..bb0dd8f61 100644 --- a/pumpkin/src/command/args/mod.rs +++ b/pumpkin/src/command/args/mod.rs @@ -109,7 +109,7 @@ impl GetCloned for HashMap { } } -pub(crate) trait FindArg<'a> { +pub trait FindArg<'a> { type Data; fn find_arg(args: &'a ConsumedArgs, name: &'a str) -> Result; From 8ed5ffce386860e8ca363b0b088075f8608a6496 Mon Sep 17 00:00:00 2001 From: vyPal Date: Fri, 27 Dec 2024 09:29:35 +0100 Subject: [PATCH 59/62] Update example plugin --- plugins/hello-plugin-source/Cargo.toml | 14 +++- plugins/hello-plugin-source/src/lib.rs | 90 +++++++++++++++++++++++--- 2 files changed, 95 insertions(+), 9 deletions(-) diff --git a/plugins/hello-plugin-source/Cargo.toml b/plugins/hello-plugin-source/Cargo.toml index b31089f67..88b2dba94 100644 --- a/plugins/hello-plugin-source/Cargo.toml +++ b/plugins/hello-plugin-source/Cargo.toml @@ -17,4 +17,16 @@ pumpkin-protocol = { path = "../../pumpkin-protocol" } pumpkin-api-macros = { path = "../../pumpkin-api-macros" } serde = { version = "1.0", features = ["derive"] } toml = "0.8.19" -async-trait = "0.1.83" \ No newline at end of file +async-trait = "0.1.83" +tokio = { version = "1.42", features = [ + "fs", + "io-util", + "macros", + "net", + "rt-multi-thread", + "sync", + "io-std", + "signal", +] } +env_logger = "0.11.6" +log = "0.4.22" \ No newline at end of file diff --git a/plugins/hello-plugin-source/src/lib.rs b/plugins/hello-plugin-source/src/lib.rs index 72ad6a008..19a80ed4d 100644 --- a/plugins/hello-plugin-source/src/lib.rs +++ b/plugins/hello-plugin-source/src/lib.rs @@ -1,17 +1,25 @@ use async_trait::async_trait; +use pumpkin::command::args::arg_block::BlockArgumentConsumer; +use pumpkin::command::args::arg_position_block::BlockPosArgumentConsumer; use pumpkin::command::args::ConsumedArgs; +use pumpkin::command::args::FindArg; use pumpkin::command::dispatcher::CommandError; use pumpkin::command::tree::CommandTree; +use pumpkin::command::tree_builder::argument; +use pumpkin::command::tree_builder::literal; +use pumpkin::command::tree_builder::require; use pumpkin::command::CommandExecutor; use pumpkin::command::CommandSender; +use pumpkin::entity::player::PermissionLvl; use pumpkin::plugin::api::types::player::PlayerEvent; use pumpkin::plugin::*; use pumpkin::server::Server; -use pumpkin_api_macros::{plugin_event, plugin_impl, plugin_method}; +use pumpkin_api_macros::{plugin_event, plugin_impl, plugin_method, with_runtime}; use pumpkin_core::text::color::NamedColor; use pumpkin_core::text::TextComponent; use serde::{Deserialize, Serialize}; use std::fs; +use tokio::runtime::Handle; #[derive(Serialize, Deserialize, Debug)] struct Config { @@ -23,31 +31,97 @@ struct Bans { players: Vec, } -const NAMES: [&str; 1] = ["pcmdtest"]; +const NAMES: [&str; 1] = ["setblock2"]; -const DESCRIPTION: &str = "Testing the ability of plugins to add commands"; +const DESCRIPTION: &str = "Place a block."; -struct SayExecutor; +const ARG_BLOCK: &str = "block"; +const ARG_BLOCK_POS: &str = "position"; + +#[derive(Clone, Copy)] +enum Mode { + /// with particles + item drops + Destroy, + + /// only replaces air + Keep, + + /// default; without particles + Replace, +} + +struct SetblockExecutor(Mode); #[async_trait] -impl CommandExecutor for SayExecutor { +#[with_runtime(global)] +impl CommandExecutor for SetblockExecutor { async fn execute<'a>( &self, sender: &mut CommandSender<'a>, _server: &Server, - _args: &ConsumedArgs<'a>, + args: &ConsumedArgs<'a>, ) -> Result<(), CommandError> { - sender.send_message(TextComponent::text("Hello, world! This was sent from a plugin as a response to using a command registered by a plugin!")).await; + log::error!("Runtime is {:?}", Handle::current()); + let block = BlockArgumentConsumer::find_arg(args, ARG_BLOCK)?; + let block_state_id = block.default_state_id; + let pos = BlockPosArgumentConsumer::find_arg(args, ARG_BLOCK_POS)?; + let mode = self.0; + // TODO: allow console to use the command (seed sender.world) + let world = sender.world().ok_or(CommandError::InvalidRequirement)?; + + let success = match mode { + Mode::Destroy => { + world.break_block(pos, None).await; + world.set_block_state(pos, block_state_id).await; + true + } + Mode::Replace => { + world.set_block_state(pos, block_state_id).await; + true + } + Mode::Keep => match world.get_block_state(pos).await { + Ok(old_state) if old_state.air => { + world.set_block_state(pos, block_state_id).await; + true + } + Ok(_) => false, + Err(e) => return Err(CommandError::OtherPumpkin(e.into())), + }, + }; + + sender + .send_message(if success { + TextComponent::text_string(format!("Placed block {} at {pos}", block.name,)) + } else { + TextComponent::text_string(format!("Kept block at {pos}")) + .color_named(NamedColor::Red) + }) + .await; + Ok(()) } } pub fn init_command_tree<'a>() -> CommandTree<'a> { - CommandTree::new(NAMES, DESCRIPTION).execute(&SayExecutor) + CommandTree::new(NAMES, DESCRIPTION).with_child( + require(&|sender| { + sender.has_permission_lvl(PermissionLvl::Two) && sender.world().is_some() + }) + .with_child( + argument(ARG_BLOCK_POS, &BlockPosArgumentConsumer).with_child( + argument(ARG_BLOCK, &BlockArgumentConsumer) + .with_child(literal("replace").execute(&SetblockExecutor(Mode::Replace))) + .with_child(literal("destroy").execute(&SetblockExecutor(Mode::Destroy))) + .with_child(literal("keep").execute(&SetblockExecutor(Mode::Keep))) + .execute(&SetblockExecutor(Mode::Replace)), + ), + ), + ) } #[plugin_method] async fn on_load(&mut self, server: &Context) -> Result<(), String> { + env_logger::init(); server.register_command(init_command_tree()).await; let data_folder = server.get_data_folder(); if !fs::exists(format!("{}/data.toml", data_folder)).unwrap() { From 4b31468a59e61d5b3ba8a30ee8d912ab3fdc4b8b Mon Sep 17 00:00:00 2001 From: vyPal Date: Fri, 27 Dec 2024 09:51:13 +0100 Subject: [PATCH 60/62] Fix merge related issues --- plugins/hello-plugin-source/src/lib.rs | 18 ++++++++---------- pumpkin/src/command/commands/cmd_plugin.rs | 10 +++++----- pumpkin/src/command/commands/cmd_plugins.rs | 4 ++-- pumpkin/src/plugin/api/context.rs | 4 ++-- 4 files changed, 17 insertions(+), 19 deletions(-) diff --git a/plugins/hello-plugin-source/src/lib.rs b/plugins/hello-plugin-source/src/lib.rs index 19a80ed4d..acd83b6d0 100644 --- a/plugins/hello-plugin-source/src/lib.rs +++ b/plugins/hello-plugin-source/src/lib.rs @@ -19,7 +19,6 @@ use pumpkin_core::text::color::NamedColor; use pumpkin_core::text::TextComponent; use serde::{Deserialize, Serialize}; use std::fs; -use tokio::runtime::Handle; #[derive(Serialize, Deserialize, Debug)] struct Config { @@ -61,7 +60,6 @@ impl CommandExecutor for SetblockExecutor { _server: &Server, args: &ConsumedArgs<'a>, ) -> Result<(), CommandError> { - log::error!("Runtime is {:?}", Handle::current()); let block = BlockArgumentConsumer::find_arg(args, ARG_BLOCK)?; let block_state_id = block.default_state_id; let pos = BlockPosArgumentConsumer::find_arg(args, ARG_BLOCK_POS)?; @@ -102,18 +100,18 @@ impl CommandExecutor for SetblockExecutor { } } -pub fn init_command_tree<'a>() -> CommandTree<'a> { +pub fn init_command_tree() -> CommandTree { CommandTree::new(NAMES, DESCRIPTION).with_child( - require(&|sender| { + require(|sender| { sender.has_permission_lvl(PermissionLvl::Two) && sender.world().is_some() }) .with_child( - argument(ARG_BLOCK_POS, &BlockPosArgumentConsumer).with_child( - argument(ARG_BLOCK, &BlockArgumentConsumer) - .with_child(literal("replace").execute(&SetblockExecutor(Mode::Replace))) - .with_child(literal("destroy").execute(&SetblockExecutor(Mode::Destroy))) - .with_child(literal("keep").execute(&SetblockExecutor(Mode::Keep))) - .execute(&SetblockExecutor(Mode::Replace)), + argument(ARG_BLOCK_POS, BlockPosArgumentConsumer).with_child( + argument(ARG_BLOCK, BlockArgumentConsumer) + .with_child(literal("replace").execute(SetblockExecutor(Mode::Replace))) + .with_child(literal("destroy").execute(SetblockExecutor(Mode::Destroy))) + .with_child(literal("keep").execute(SetblockExecutor(Mode::Keep))) + .execute(SetblockExecutor(Mode::Replace)), ), ), ) diff --git a/pumpkin/src/command/commands/cmd_plugin.rs b/pumpkin/src/command/commands/cmd_plugin.rs index 5d779e681..43ccd69a2 100644 --- a/pumpkin/src/command/commands/cmd_plugin.rs +++ b/pumpkin/src/command/commands/cmd_plugin.rs @@ -178,17 +178,17 @@ impl CommandExecutor for UnloadExecutor { } } -pub fn init_command_tree<'a>() -> CommandTree<'a> { +pub fn init_command_tree() -> CommandTree { CommandTree::new(NAMES, DESCRIPTION).with_child( - require(&|sender| sender.has_permission_lvl(PermissionLvl::Three)) + require(|sender| sender.has_permission_lvl(PermissionLvl::Three)) .with_child( literal("load") - .with_child(argument(PLUGIN_NAME, &SimpleArgConsumer).execute(&LoadExecutor)), + .with_child(argument(PLUGIN_NAME, SimpleArgConsumer).execute(LoadExecutor)), ) .with_child( literal("unload") - .with_child(argument(PLUGIN_NAME, &SimpleArgConsumer).execute(&UnloadExecutor)), + .with_child(argument(PLUGIN_NAME, SimpleArgConsumer).execute(UnloadExecutor)), ) - .with_child(literal("list").execute(&ListExecutor)), + .with_child(literal("list").execute(ListExecutor)), ) } diff --git a/pumpkin/src/command/commands/cmd_plugins.rs b/pumpkin/src/command/commands/cmd_plugins.rs index 7ea5e4456..6ebf9febc 100644 --- a/pumpkin/src/command/commands/cmd_plugins.rs +++ b/pumpkin/src/command/commands/cmd_plugins.rs @@ -62,6 +62,6 @@ impl CommandExecutor for ListExecutor { } } -pub fn init_command_tree<'a>() -> CommandTree<'a> { - CommandTree::new(NAMES, DESCRIPTION).execute(&ListExecutor) +pub fn init_command_tree() -> CommandTree { + CommandTree::new(NAMES, DESCRIPTION).execute(ListExecutor) } diff --git a/pumpkin/src/plugin/api/context.rs b/pumpkin/src/plugin/api/context.rs index aa0e330cf..f3062b734 100644 --- a/pumpkin/src/plugin/api/context.rs +++ b/pumpkin/src/plugin/api/context.rs @@ -50,7 +50,7 @@ impl Context { recv.await.unwrap() } - pub async fn register_command(&self, tree: crate::command::tree::CommandTree<'static>) { + pub async fn register_command(&self, tree: crate::command::tree::CommandTree) { let _ = self .channel .send(ContextAction::RegisterCommand(tree)) @@ -64,7 +64,7 @@ pub enum ContextAction { player_name: String, response: oneshot::Sender, String>>, }, - RegisterCommand(crate::command::tree::CommandTree<'static>), + RegisterCommand(crate::command::tree::CommandTree), } pub fn handle_context( From 80b1d62cf4ae92d335e8edafd98b2b00de02cfe3 Mon Sep 17 00:00:00 2001 From: vyPal Date: Fri, 27 Dec 2024 09:52:55 +0100 Subject: [PATCH 61/62] Fix cargo fmt --- plugins/hello-plugin-source/src/lib.rs | 20 +++++++++----------- pumpkin-api-macros/src/lib.rs | 12 ++++++------ pumpkin-nbt/src/lib.rs | 5 +---- pumpkin/src/command/args/mod.rs | 1 - 4 files changed, 16 insertions(+), 22 deletions(-) diff --git a/plugins/hello-plugin-source/src/lib.rs b/plugins/hello-plugin-source/src/lib.rs index acd83b6d0..99c30aae4 100644 --- a/plugins/hello-plugin-source/src/lib.rs +++ b/plugins/hello-plugin-source/src/lib.rs @@ -102,18 +102,16 @@ impl CommandExecutor for SetblockExecutor { pub fn init_command_tree() -> CommandTree { CommandTree::new(NAMES, DESCRIPTION).with_child( - require(|sender| { - sender.has_permission_lvl(PermissionLvl::Two) && sender.world().is_some() - }) - .with_child( - argument(ARG_BLOCK_POS, BlockPosArgumentConsumer).with_child( - argument(ARG_BLOCK, BlockArgumentConsumer) - .with_child(literal("replace").execute(SetblockExecutor(Mode::Replace))) - .with_child(literal("destroy").execute(SetblockExecutor(Mode::Destroy))) - .with_child(literal("keep").execute(SetblockExecutor(Mode::Keep))) - .execute(SetblockExecutor(Mode::Replace)), + require(|sender| sender.has_permission_lvl(PermissionLvl::Two) && sender.world().is_some()) + .with_child( + argument(ARG_BLOCK_POS, BlockPosArgumentConsumer).with_child( + argument(ARG_BLOCK, BlockArgumentConsumer) + .with_child(literal("replace").execute(SetblockExecutor(Mode::Replace))) + .with_child(literal("destroy").execute(SetblockExecutor(Mode::Destroy))) + .with_child(literal("keep").execute(SetblockExecutor(Mode::Keep))) + .execute(SetblockExecutor(Mode::Replace)), + ), ), - ), ) } diff --git a/pumpkin-api-macros/src/lib.rs b/pumpkin-api-macros/src/lib.rs index 5b86168db..17f83dc9c 100644 --- a/pumpkin-api-macros/src/lib.rs +++ b/pumpkin-api-macros/src/lib.rs @@ -222,14 +222,14 @@ pub fn plugin_impl(attr: TokenStream, item: TokenStream) -> TokenStream { #[proc_macro_attribute] pub fn with_runtime(attr: TokenStream, item: TokenStream) -> TokenStream { let mut input = parse_macro_input!(item as ItemImpl); - + let use_global = attr.to_string() == "global"; - + for item in &mut input.items { if let ImplItem::Fn(method) = item { if method.sig.asyncness.is_some() { let original_body = &method.block; - + method.block = if use_global { parse_quote!({ crate::GLOBAL_RUNTIME.block_on(async move { @@ -245,11 +245,11 @@ pub fn with_runtime(attr: TokenStream, item: TokenStream) -> TokenStream { }) }) }; - + method.sig.asyncness = None; } } } - + TokenStream::from(quote!(#input)) -} \ No newline at end of file +} diff --git a/pumpkin-nbt/src/lib.rs b/pumpkin-nbt/src/lib.rs index acee723fe..5ed48da9b 100644 --- a/pumpkin-nbt/src/lib.rs +++ b/pumpkin-nbt/src/lib.rs @@ -208,10 +208,7 @@ mod test { use crate::BytesArray; use crate::IntArray; use crate::LongArray; - use crate::{ - deserializer::from_bytes_unnamed, - serializer::to_bytes_unnamed, - }; + use crate::{deserializer::from_bytes_unnamed, serializer::to_bytes_unnamed}; #[derive(Serialize, Deserialize, PartialEq, Debug)] struct Test { diff --git a/pumpkin/src/command/args/mod.rs b/pumpkin/src/command/args/mod.rs index 7a63af8f7..16a1dd806 100644 --- a/pumpkin/src/command/args/mod.rs +++ b/pumpkin/src/command/args/mod.rs @@ -66,7 +66,6 @@ pub trait GetClientSideArgParser { fn get_client_side_suggestion_type_override(&self) -> Option; } - pub trait DefaultNameArgConsumer: ArgumentConsumer { fn default_name(&self) -> String; } From b5615bb437249b18697d2e7a7fc9988cb5c4e5e2 Mon Sep 17 00:00:00 2001 From: vyPal Date: Mon, 30 Dec 2024 19:54:19 +0100 Subject: [PATCH 62/62] Post-merge fixes (also 69th commit, nice) --- plugins/hello-plugin-source/src/lib.rs | 17 ++++---- pumpkin-api-macros/src/lib.rs | 32 +++++++-------- pumpkin/src/command/commands/cmd_plugin.rs | 44 +++++++++------------ pumpkin/src/command/commands/cmd_plugins.rs | 10 ++--- pumpkin/src/command/commands/mod.rs | 2 +- pumpkin/src/lib.rs | 1 + pumpkin/src/plugin/api/context.rs | 25 +++++++----- pumpkin/src/plugin/api/types/player.rs | 25 ++++++------ pumpkin/src/world/mod.rs | 3 +- 9 files changed, 77 insertions(+), 82 deletions(-) diff --git a/plugins/hello-plugin-source/src/lib.rs b/plugins/hello-plugin-source/src/lib.rs index 99c30aae4..004fae30c 100644 --- a/plugins/hello-plugin-source/src/lib.rs +++ b/plugins/hello-plugin-source/src/lib.rs @@ -10,13 +10,13 @@ use pumpkin::command::tree_builder::literal; use pumpkin::command::tree_builder::require; use pumpkin::command::CommandExecutor; use pumpkin::command::CommandSender; -use pumpkin::entity::player::PermissionLvl; use pumpkin::plugin::api::types::player::PlayerEvent; use pumpkin::plugin::*; use pumpkin::server::Server; use pumpkin_api_macros::{plugin_event, plugin_impl, plugin_method, with_runtime}; use pumpkin_core::text::color::NamedColor; use pumpkin_core::text::TextComponent; +use pumpkin_core::PermissionLvl; use serde::{Deserialize, Serialize}; use std::fs; @@ -51,8 +51,10 @@ enum Mode { struct SetblockExecutor(Mode); -#[async_trait] +// IMPORTANT: If using something that requires a tokio runtime, the #[with_runtime] attribute must be used. +// EVEN MORE IMPORTANT: The #[with_runtime] attribute must be used **BRFORE** the #[async_trait] attribute. #[with_runtime(global)] +#[async_trait] impl CommandExecutor for SetblockExecutor { async fn execute<'a>( &self, @@ -89,10 +91,9 @@ impl CommandExecutor for SetblockExecutor { sender .send_message(if success { - TextComponent::text_string(format!("Placed block {} at {pos}", block.name,)) + TextComponent::text(format!("Placed block {} at {pos}", block.name,)) } else { - TextComponent::text_string(format!("Kept block at {pos}")) - .color_named(NamedColor::Red) + TextComponent::text(format!("Kept block at {pos}")).color_named(NamedColor::Red) }) .await; @@ -118,7 +119,9 @@ pub fn init_command_tree() -> CommandTree { #[plugin_method] async fn on_load(&mut self, server: &Context) -> Result<(), String> { env_logger::init(); - server.register_command(init_command_tree()).await; + server + .register_command(init_command_tree(), PermissionLvl::Two) + .await; let data_folder = server.get_data_folder(); if !fs::exists(format!("{}/data.toml", data_folder)).unwrap() { let cfg = toml::to_string(&self.config).unwrap(); @@ -167,7 +170,7 @@ async fn on_player_join(&mut self, server: &Context, player: &PlayerEvent) -> Re let _ = player .send_message( - TextComponent::text_string(format!( + TextComponent::text(format!( "Hello {}, welocme to the server", player.gameprofile.name )) diff --git a/pumpkin-api-macros/src/lib.rs b/pumpkin-api-macros/src/lib.rs index 17f83dc9c..894fd4f99 100644 --- a/pumpkin-api-macros/src/lib.rs +++ b/pumpkin-api-macros/src/lib.rs @@ -227,27 +227,23 @@ pub fn with_runtime(attr: TokenStream, item: TokenStream) -> TokenStream { for item in &mut input.items { if let ImplItem::Fn(method) = item { - if method.sig.asyncness.is_some() { - let original_body = &method.block; + let original_body = &method.block; - method.block = if use_global { - parse_quote!({ - crate::GLOBAL_RUNTIME.block_on(async move { + method.block = if use_global { + parse_quote!({ + GLOBAL_RUNTIME.block_on(async move { + #original_body + }) + }) + } else { + parse_quote!({ + tokio::runtime::Runtime::new() + .unwrap() + .block_on(async move { #original_body }) - }) - } else { - parse_quote!({ - tokio::runtime::Runtime::new() - .unwrap() - .block_on(async move { - #original_body - }) - }) - }; - - method.sig.asyncness = None; - } + }) + }; } } diff --git a/pumpkin/src/command/commands/cmd_plugin.rs b/pumpkin/src/command/commands/cmd_plugin.rs index 43ccd69a2..40f1550ad 100644 --- a/pumpkin/src/command/commands/cmd_plugin.rs +++ b/pumpkin/src/command/commands/cmd_plugin.rs @@ -1,5 +1,8 @@ use async_trait::async_trait; -use pumpkin_core::text::{color::NamedColor, hover::HoverEvent, TextComponent}; +use pumpkin_core::{ + text::{color::NamedColor, hover::HoverEvent, TextComponent}, + PermissionLvl, +}; use crate::{ command::{ @@ -8,7 +11,6 @@ use crate::{ tree_builder::{argument, literal, require}, CommandError, CommandExecutor, CommandSender, }, - entity::player::PermissionLvl, PLUGIN_MANAGER, }; @@ -34,11 +36,11 @@ impl CommandExecutor for ListExecutor { let plugins = plugin_manager.list_plugins(); let message_text = if plugins.is_empty() { - "There are no loaded plugins." + "There are no loaded plugins.".to_string() } else if plugins.len() == 1 { - "There is 1 plugin loaded:\n" + "There is 1 plugin loaded:\n".to_string() } else { - &format!("There are {} plugins loaded:\n", plugins.len(),) + format!("There are {} plugins loaded:\n", plugins.len()) }; let mut message = TextComponent::text(message_text); @@ -53,11 +55,11 @@ impl CommandExecutor for ListExecutor { metadata.version, metadata.authors, metadata.description ); let component = if *loaded { - TextComponent::text_string(fmt) + TextComponent::text(fmt) .color_named(NamedColor::Green) .hover_event(HoverEvent::ShowText(hover_text.into())) } else { - TextComponent::text_string(fmt) + TextComponent::text(fmt) .color_named(NamedColor::Red) .hover_event(HoverEvent::ShowText(hover_text.into())) }; @@ -88,7 +90,7 @@ impl CommandExecutor for LoadExecutor { if plugin_manager.is_plugin_loaded(plugin_name) { sender .send_message( - TextComponent::text_string(format!("Plugin {plugin_name} is already loaded")) + TextComponent::text(format!("Plugin {plugin_name} is already loaded")) .color_named(NamedColor::Red), ) .await; @@ -101,20 +103,16 @@ impl CommandExecutor for LoadExecutor { Ok(()) => { sender .send_message( - TextComponent::text_string(format!( - "Plugin {plugin_name} loaded successfully" - )) - .color_named(NamedColor::Green), + TextComponent::text(format!("Plugin {plugin_name} loaded successfully")) + .color_named(NamedColor::Green), ) .await; } Err(e) => { sender .send_message( - TextComponent::text_string(format!( - "Failed to load plugin {plugin_name}: {e}" - )) - .color_named(NamedColor::Red), + TextComponent::text(format!("Failed to load plugin {plugin_name}: {e}")) + .color_named(NamedColor::Red), ) .await; } @@ -142,7 +140,7 @@ impl CommandExecutor for UnloadExecutor { if !plugin_manager.is_plugin_loaded(plugin_name) { sender .send_message( - TextComponent::text_string(format!("Plugin {plugin_name} is not loaded")) + TextComponent::text(format!("Plugin {plugin_name} is not loaded")) .color_named(NamedColor::Red), ) .await; @@ -155,20 +153,16 @@ impl CommandExecutor for UnloadExecutor { Ok(()) => { sender .send_message( - TextComponent::text_string(format!( - "Plugin {plugin_name} unloaded successfully", - )) - .color_named(NamedColor::Green), + TextComponent::text(format!("Plugin {plugin_name} unloaded successfully",)) + .color_named(NamedColor::Green), ) .await; } Err(e) => { sender .send_message( - TextComponent::text_string(format!( - "Failed to unload plugin {plugin_name}: {e}" - )) - .color_named(NamedColor::Red), + TextComponent::text(format!("Failed to unload plugin {plugin_name}: {e}")) + .color_named(NamedColor::Red), ) .await; } diff --git a/pumpkin/src/command/commands/cmd_plugins.rs b/pumpkin/src/command/commands/cmd_plugins.rs index 6ebf9febc..a066d9a1f 100644 --- a/pumpkin/src/command/commands/cmd_plugins.rs +++ b/pumpkin/src/command/commands/cmd_plugins.rs @@ -26,11 +26,11 @@ impl CommandExecutor for ListExecutor { let plugins = plugin_manager.list_plugins(); let message_text = if plugins.is_empty() { - "There are no loaded plugins." + "There are no loaded plugins.".to_string() } else if plugins.len() == 1 { - "There is 1 plugin loaded:\n" + "There is 1 plugin loaded:\n".to_string() } else { - &format!("There are {} plugins loaded:\n", plugins.len(),) + format!("There are {} plugins loaded:\n", plugins.len()) }; let mut message = TextComponent::text(message_text); @@ -45,11 +45,11 @@ impl CommandExecutor for ListExecutor { metadata.version, metadata.authors, metadata.description ); let component = if *loaded { - TextComponent::text_string(fmt) + TextComponent::text(fmt) .color_named(NamedColor::Green) .hover_event(HoverEvent::ShowText(hover_text.into())) } else { - TextComponent::text_string(fmt) + TextComponent::text(fmt) .color_named(NamedColor::Red) .hover_event(HoverEvent::ShowText(hover_text.into())) }; diff --git a/pumpkin/src/command/commands/mod.rs b/pumpkin/src/command/commands/mod.rs index f4f8cd06a..5871da383 100644 --- a/pumpkin/src/command/commands/mod.rs +++ b/pumpkin/src/command/commands/mod.rs @@ -7,9 +7,9 @@ pub mod cmd_help; pub mod cmd_kick; pub mod cmd_kill; pub mod cmd_list; +pub mod cmd_op; pub mod cmd_plugin; pub mod cmd_plugins; -pub mod cmd_op; pub mod cmd_pumpkin; pub mod cmd_say; pub mod cmd_seed; diff --git a/pumpkin/src/lib.rs b/pumpkin/src/lib.rs index 6fe9304ab..e43c54f76 100644 --- a/pumpkin/src/lib.rs +++ b/pumpkin/src/lib.rs @@ -6,6 +6,7 @@ use tokio::sync::Mutex; pub mod block; pub mod command; +pub mod data; pub mod entity; pub mod error; pub mod net; diff --git a/pumpkin/src/plugin/api/context.rs b/pumpkin/src/plugin/api/context.rs index f3062b734..2df657526 100644 --- a/pumpkin/src/plugin/api/context.rs +++ b/pumpkin/src/plugin/api/context.rs @@ -1,5 +1,6 @@ use std::{fs, path::Path, sync::Arc}; +use pumpkin_core::PermissionLvl; use tokio::sync::mpsc::{self, Sender}; use crate::server::Server; @@ -35,10 +36,7 @@ impl Context { path } - pub async fn get_player_by_name( - &self, - player_name: String, - ) -> Result, String> { + pub async fn get_player_by_name(&self, player_name: String) -> Result { let (send, recv) = oneshot::channel(); let _ = self .channel @@ -50,10 +48,14 @@ impl Context { recv.await.unwrap() } - pub async fn register_command(&self, tree: crate::command::tree::CommandTree) { + pub async fn register_command( + &self, + tree: crate::command::tree::CommandTree, + permission: PermissionLvl, + ) { let _ = self .channel - .send(ContextAction::RegisterCommand(tree)) + .send(ContextAction::RegisterCommand(tree, permission)) .await; } } @@ -62,9 +64,12 @@ pub enum ContextAction { // TODO: Implement when dispatcher is mutable GetPlayerByName { player_name: String, - response: oneshot::Sender, String>>, + response: oneshot::Sender>, }, - RegisterCommand(crate::command::tree::CommandTree), + RegisterCommand( + crate::command::tree::CommandTree, + pumpkin_core::PermissionLvl, + ), } pub fn handle_context( @@ -94,9 +99,9 @@ pub fn handle_context( response.send(Err("Player not found".to_string())).unwrap(); } } - ContextAction::RegisterCommand(tree) => { + ContextAction::RegisterCommand(tree, permission) => { let mut dispatcher_lock = server.command_dispatcher.write().await; - dispatcher_lock.register(tree); + dispatcher_lock.register(tree, permission); } } } diff --git a/pumpkin/src/plugin/api/types/player.rs b/pumpkin/src/plugin/api/types/player.rs index dd430f70c..ef0a719c1 100644 --- a/pumpkin/src/plugin/api/types/player.rs +++ b/pumpkin/src/plugin/api/types/player.rs @@ -6,14 +6,14 @@ use uuid::Uuid; use crate::{entity::player::Player, server::Server}; -pub enum PlayerEventAction<'a> { +pub enum PlayerEventAction { SendMessage { - message: TextComponent<'a>, + message: TextComponent, player_id: Uuid, response: oneshot::Sender<()>, }, Kick { - reason: TextComponent<'a>, + reason: TextComponent, player_id: Uuid, response: oneshot::Sender<()>, }, @@ -35,12 +35,12 @@ pub enum PlayerEventAction<'a> { }, } -pub struct PlayerEvent<'a> { +pub struct PlayerEvent { pub player: Arc, - channel: mpsc::Sender>, + channel: mpsc::Sender, } -impl Deref for PlayerEvent<'_> { +impl Deref for PlayerEvent { type Target = Player; fn deref(&self) -> &Self::Target { @@ -48,13 +48,13 @@ impl Deref for PlayerEvent<'_> { } } -impl<'a> PlayerEvent<'a> { +impl PlayerEvent { #[must_use] - pub fn new(player: Arc, channel: mpsc::Sender>) -> Self { + pub fn new(player: Arc, channel: mpsc::Sender) -> Self { Self { player, channel } } - pub async fn send_message(&self, message: TextComponent<'a>) { + pub async fn send_message(&self, message: TextComponent) { let (tx, rx) = oneshot::channel(); self.channel .send(PlayerEventAction::SendMessage { @@ -67,7 +67,7 @@ impl<'a> PlayerEvent<'a> { rx.await.unwrap(); } - pub async fn kick(&self, reason: TextComponent<'a>) { + pub async fn kick(&self, reason: TextComponent) { let (tx, rx) = oneshot::channel(); self.channel .send(PlayerEventAction::Kick { @@ -81,10 +81,7 @@ impl<'a> PlayerEvent<'a> { } } -pub async fn player_event_handler( - server: Arc, - player: Arc, -) -> PlayerEvent<'static> { +pub async fn player_event_handler(server: Arc, player: Arc) -> PlayerEvent { let (send, mut recv) = mpsc::channel(1); let player_event = PlayerEvent::new(player.clone(), send); let players_copy = server.get_all_players().await; diff --git a/pumpkin/src/world/mod.rs b/pumpkin/src/world/mod.rs index e9d793eb5..00131e5a2 100644 --- a/pumpkin/src/world/mod.rs +++ b/pumpkin/src/world/mod.rs @@ -795,8 +795,7 @@ impl World { // Handle join message // TODO: Config let msg_txt = format!("{} joined the game.", player.gameprofile.name.as_str()); - let msg_comp = - TextComponent::text(msg_txt).color_named(NamedColor::Yellow); + let msg_comp = TextComponent::text(msg_txt).color_named(NamedColor::Yellow); let players = current_players.lock().await; for player in players.values() { player.send_system_message(&msg_comp).await;