diff --git a/Cargo.toml b/Cargo.toml index d48b915a0..63efc4ef1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,16 +41,18 @@ tokio = { version = "1.43", features = [ "signal", ] } -thiserror = "2" +thiserror = "2.0" bytes = "1.9" # Concurrency/Parallelism and Synchronization -rayon = "1.10.0" -parking_lot = { version = "0.12.3", features = ["send_guard"] } -crossbeam = "0.8.4" +rayon = "1.10" +parking_lot = { version = "0.12", features = ["send_guard"] } +crossbeam = "0.8" -uuid = { version = "1.12.1", features = ["serde", "v3", "v4"] } -derive_more = { version = "1.0.0", features = ["full"] } +uuid = { version = "1.12", features = ["serde", "v3", "v4"] } +derive_more = { version = "1.0", features = ["full"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" + +async-trait = "0.1" diff --git a/pumpkin-api-macros/Cargo.toml b/pumpkin-api-macros/Cargo.toml index 6c0d4bcc8..132835790 100644 --- a/pumpkin-api-macros/Cargo.toml +++ b/pumpkin-api-macros/Cargo.toml @@ -7,6 +7,6 @@ edition.workspace = true proc-macro = true [dependencies] -syn = { version = "2.0.89", features = ["full"] } -quote = "1.0.37" -proc-macro2 = "1.0.92" \ No newline at end of file +syn = { version = "2.0", features = ["full"] } +quote = "1.0" +proc-macro2 = "1.0" \ No newline at end of file diff --git a/pumpkin-api-macros/src/lib.rs b/pumpkin-api-macros/src/lib.rs index 6cd2e0725..e08b06153 100644 --- a/pumpkin-api-macros/src/lib.rs +++ b/pumpkin-api-macros/src/lib.rs @@ -108,7 +108,7 @@ pub fn with_runtime(attr: TokenStream, item: TokenStream) -> TokenStream { method.block = if use_global { parse_quote!({ - GLOBAL_RUNTIME.block_on(async move { + crate::GLOBAL_RUNTIME.block_on(async move { #original_body }) }) diff --git a/pumpkin-config/src/lib.rs b/pumpkin-config/src/lib.rs index 04804ae09..7dd87e161 100644 --- a/pumpkin-config/src/lib.rs +++ b/pumpkin-config/src/lib.rs @@ -86,6 +86,7 @@ pub struct BasicConfiguration { pub encryption: bool, /// The server's description displayed on the status screen. pub motd: String, + /// The server's ticks per second. pub tps: f32, /// The default game mode for players. pub default_gamemode: GameMode, diff --git a/pumpkin-data/Cargo.toml b/pumpkin-data/Cargo.toml index f9c83cccd..b7d1b229c 100644 --- a/pumpkin-data/Cargo.toml +++ b/pumpkin-data/Cargo.toml @@ -13,7 +13,7 @@ pumpkin-util = { path = "../pumpkin-util" } serde.workspace = true serde_json.workspace = true -heck = "0.5.0" +heck = "0.5" proc-macro2 = "1.0" quote = "1.0" syn = "2.0" \ No newline at end of file diff --git a/pumpkin-macros/src/lib.rs b/pumpkin-macros/src/lib.rs index 3df7e9637..6c98350f4 100644 --- a/pumpkin-macros/src/lib.rs +++ b/pumpkin-macros/src/lib.rs @@ -2,20 +2,17 @@ use proc_macro::TokenStream; use quote::quote; use syn::{ parse::{Nothing, Parser}, - parse_macro_input, Field, Fields, ItemStruct, + parse_macro_input, Block, Expr, Field, Fields, ItemStruct, Stmt, }; extern crate proc_macro; -#[proc_macro_attribute] -pub fn event(args: TokenStream, item: TokenStream) -> TokenStream { +#[proc_macro_derive(Event)] +pub fn event(item: TokenStream) -> TokenStream { let input = parse_macro_input!(item as ItemStruct); let name = &input.ident; - let _ = parse_macro_input!(args as Nothing); quote! { - #input - impl crate::plugin::Event for #name { fn get_name_static() -> &'static str { stringify!(#name) @@ -46,7 +43,10 @@ pub fn cancellable(args: TokenStream, input: TokenStream) -> TokenStream { if let Fields::Named(ref mut fields) = item_struct.fields { fields.named.push( Field::parse_named - .parse2(quote! { pub cancelled: bool }) + .parse2(quote! { + /// A boolean indicating cancel state of the event. + pub cancelled: bool + }) .unwrap(), ); } @@ -67,6 +67,89 @@ pub fn cancellable(args: TokenStream, input: TokenStream) -> TokenStream { .into() } +#[proc_macro] +pub fn send_cancellable(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as Block); + + let mut event = None; + let mut after_block = None; + let mut cancelled_block = None; + + for stmt in input.stmts { + if let Stmt::Expr(expr, _) = stmt { + if event.is_none() { + event = Some(expr); + } else if let Expr::Block(b) = expr { + if let Some(ref label) = b.label { + if label.name.ident == "after" { + after_block = Some(b); + } else if label.name.ident == "cancelled" { + cancelled_block = Some(b); + } + } + } + } + } + + if let Some(event) = event { + if let Some(after_block) = after_block { + if let Some(cancelled_block) = cancelled_block { + quote! { + let event = crate::PLUGIN_MANAGER + .lock() + .await + .fire(#event) + .await; + + if !event.cancelled { + #after_block + } else { + #cancelled_block + } + } + .into() + } else { + quote! { + let event = crate::PLUGIN_MANAGER + .lock() + .await + .fire(#event) + .await; + + if !event.cancelled { + #after_block + } + } + .into() + } + } else if let Some(cancelled_block) = cancelled_block { + quote! { + let event = crate::PLUGIN_MANAGER + .lock() + .await + .fire(#event) + .await; + + if event.cancelled { + #cancelled_block + } + } + .into() + } else { + quote! { + let event = crate::PLUGIN_MANAGER + .lock() + .await + .fire(#event) + .await; + } + .into() + } + } else { + panic!("Event must be specified"); + } +} + #[proc_macro_attribute] pub fn client_packet(input: TokenStream, item: TokenStream) -> TokenStream { let ast: syn::DeriveInput = syn::parse(item.clone()).unwrap(); diff --git a/pumpkin-nbt/Cargo.toml b/pumpkin-nbt/Cargo.toml index a25b7a2fa..d9eaa8c7a 100644 --- a/pumpkin-nbt/Cargo.toml +++ b/pumpkin-nbt/Cargo.toml @@ -7,4 +7,5 @@ edition.workspace = true serde.workspace = true thiserror.workspace = true bytes.workspace = true -cesu8 = "1.1.0" + +cesu8 = "1.1" diff --git a/pumpkin-protocol/Cargo.toml b/pumpkin-protocol/Cargo.toml index 8c936c529..74a6606bf 100644 --- a/pumpkin-protocol/Cargo.toml +++ b/pumpkin-protocol/Cargo.toml @@ -25,8 +25,8 @@ tokio.workspace = true bytes.workspace = true # encryption -aes = "0.8.4" -cfb8 = "0.8.1" +aes = "0.8" +cfb8 = "0.8" # compression -libdeflater = "1.23.0" \ No newline at end of file +libdeflater = "1.23" \ No newline at end of file diff --git a/pumpkin-registry/Cargo.toml b/pumpkin-registry/Cargo.toml index ca74857a4..e227698b8 100644 --- a/pumpkin-registry/Cargo.toml +++ b/pumpkin-registry/Cargo.toml @@ -8,7 +8,7 @@ pumpkin-protocol = { path = "../pumpkin-protocol" } pumpkin-nbt = { path = "../pumpkin-nbt" } pumpkin-util = { path = "../pumpkin-util" } -indexmap = { version = "2.7.1", features = ["serde"] } +indexmap = { version = "2.7", features = ["serde"] } serde.workspace = true serde_json.workspace = true diff --git a/pumpkin-util/Cargo.toml b/pumpkin-util/Cargo.toml index 33e88fba8..0cb0be940 100644 --- a/pumpkin-util/Cargo.toml +++ b/pumpkin-util/Cargo.toml @@ -12,5 +12,5 @@ uuid.workspace = true num-traits = "0.2" -colored = "3" -md5 = "0.7.0" +colored = "3.0" +md5 = "0.7" diff --git a/pumpkin-world/Cargo.toml b/pumpkin-world/Cargo.toml index 36750d444..5cc39fcb6 100644 --- a/pumpkin-world/Cargo.toml +++ b/pumpkin-world/Cargo.toml @@ -21,29 +21,28 @@ serde.workspace = true serde_json.workspace = true log.workspace = true -num-derive = "0.4.2" +num-derive = "0.4" -dashmap = "6.1.0" +dashmap = "6.1" num-traits = "0.2" # Compression flate2 = "1.0" -lz4 = "1.28.1" +lz4 = "1.28" -file-guard = "0.2.0" -indexmap = "2.7.1" +file-guard = "0.2" +indexmap = "2.7" -enum_dispatch = "0.3.13" +enum_dispatch = "0.3" fastnbt = { git = "https://github.com/owengage/fastnbt.git" } -noise = "0.9.0" - -rand = "0.8.5" +noise = "0.9" +rand = "0.8" [dev-dependencies] -criterion = { version = "0.5.1", features = ["html_reports"] } +criterion = { version = "0.5", features = ["html_reports"] } [[bench]] name = "chunk_noise" diff --git a/pumpkin-world/src/chunk/mod.rs b/pumpkin-world/src/chunk/mod.rs index 53bce75e0..1da137069 100644 --- a/pumpkin-world/src/chunk/mod.rs +++ b/pumpkin-world/src/chunk/mod.rs @@ -74,6 +74,7 @@ pub enum CompressionError { LZ4Error(std::io::Error), } +#[derive(Clone)] pub struct ChunkData { /// See description in `Subchunks` pub subchunks: Subchunks, @@ -92,7 +93,7 @@ pub struct ChunkData { /// chunk, what filled only air or only water. /// /// Multi means a normal chunk, what contains 24 subchunks. -#[derive(PartialEq, Debug)] +#[derive(PartialEq, Debug, Clone)] pub enum Subchunks { Single(u16), Multi(Box<[Subchunk; SUBCHUNKS_COUNT]>), diff --git a/pumpkin-world/src/level.rs b/pumpkin-world/src/level.rs index d1ee64db7..3c65aacd9 100644 --- a/pumpkin-world/src/level.rs +++ b/pumpkin-world/src/level.rs @@ -235,7 +235,7 @@ impl Level { pub fn fetch_chunks( &self, chunks: &[Vector2], - channel: mpsc::Sender>>, + channel: mpsc::Sender<(Arc>, bool)>, rt: &Handle, ) { chunks.par_iter().for_each(|at| { @@ -246,11 +246,14 @@ impl Level { let level_folder = self.level_folder.clone(); let world_gen = self.world_gen.clone(); let chunk_pos = *at; + let mut first_load = false; let chunk = loaded_chunks .get(&chunk_pos) .map(|entry| entry.value().clone()) .unwrap_or_else(|| { + first_load = true; + let loaded_chunk = match Self::load_chunk_from_save(chunk_reader, &level_folder, chunk_pos) { Ok(chunk) => { @@ -299,7 +302,7 @@ impl Level { rt.spawn(async move { let _ = channel - .send(chunk) + .send((chunk, first_load)) .await .inspect_err(|err| log::error!("unable to send chunk to channel: {}", err)); }); diff --git a/pumpkin/Cargo.toml b/pumpkin/Cargo.toml index 4856e4d1a..07ce10914 100644 --- a/pumpkin/Cargo.toml +++ b/pumpkin/Cargo.toml @@ -33,6 +33,7 @@ uuid.workspace = true tokio.workspace = true rayon.workspace = true thiserror.workspace = true +async-trait.workspace = true # config serde.workspace = true @@ -40,50 +41,48 @@ serde_json.workspace = true bytes.workspace = true -rand = "0.8.5" +rand = "0.8" num-bigint = "0.4" # Console line reading -rustyline = "15.0.0" +rustyline = "15.0" # encryption -rsa = "0.9.7" -rsa-der = "0.3.0" +rsa = "0.9" +rsa-der = "0.3" # authentication -reqwest = { version = "0.12.12", default-features = false, features = [ +reqwest = { version = "0.12", default-features = false, features = [ "http2", "json", "macos-system-configuration", "rustls-tls", ] } -sha1 = "0.10.6" +sha1 = "0.10" # velocity en -hmac = "0.12.1" -sha2 = "0.10.8" +hmac = "0.12" +sha2 = "0.10" -base64 = "0.22.1" +base64 = "0.22" # icon loading -png = "0.17.16" +png = "0.17" # logging -simple_logger = { version = "5.0.0", features = ["threads"] } +simple_logger = { version = "5.0", features = ["threads"] } # Remove time in favor of chrono? -time = "0.3.37" +time = "0.3" -chrono = { version = "0.4.39", features = ["serde"]} - -# commands -async-trait = "0.1.85" +chrono = { version = "0.4", features = ["serde"]} # plugins -libloading = "0.8.6" +libloading = "0.8" + [build-dependencies] -git-version = "0.3.9" +git-version = "0.3" # This makes it so the entire project doesn't recompile on each build on linux. [target.'cfg(target_os = "windows")'.build-dependencies] -tauri-winres= "0.2.0" +tauri-winres= "0.3" diff --git a/pumpkin/src/entity/mod.rs b/pumpkin/src/entity/mod.rs index af1dc6357..55ca6defa 100644 --- a/pumpkin/src/entity/mod.rs +++ b/pumpkin/src/entity/mod.rs @@ -1,5 +1,8 @@ use core::f32; -use std::sync::{atomic::AtomicBool, Arc}; +use std::{ + f32::consts::PI, + sync::{atomic::AtomicBool, Arc}, +}; use async_trait::async_trait; use crossbeam::atomic::AtomicCell; @@ -170,6 +173,20 @@ impl Entity { } } + /// Returns entity rotation as vector + pub fn rotation(&self) -> Vector3 { + // Convert degrees to radians if necessary + let yaw_rad = self.yaw.load() * (PI / 180.0); + let pitch_rad = self.pitch.load() * (PI / 180.0); + + Vector3::new( + yaw_rad.cos() * pitch_rad.cos(), + pitch_rad.sin(), + yaw_rad.sin() * pitch_rad.cos(), + ) + .normalize() + } + /// Changes this entity's pitch and yaw to look at target pub async fn look_at(&self, target: Vector3) { let position = self.pos.load(); diff --git a/pumpkin/src/lib.rs b/pumpkin/src/lib.rs index 0c240396d..e33758b2d 100644 --- a/pumpkin/src/lib.rs +++ b/pumpkin/src/lib.rs @@ -1,9 +1,14 @@ +// Not warn event sending macros +#![allow(unused_labels)] + use crate::net::{lan_broadcast, query, rcon::RCONServer, Client}; use crate::server::{ticker::Ticker, Server}; +use log::{LevelFilter, Log}; use plugin::PluginManager; use pumpkin_config::{ADVANCED_CONFIG, BASIC_CONFIG}; use pumpkin_util::text::TextComponent; use rustyline::DefaultEditor; +use simple_logger::SimpleLogger; use std::{ net::SocketAddr, sync::{Arc, LazyLock}, @@ -29,6 +34,43 @@ const GIT_VERSION: &str = env!("GIT_VERSION"); pub static PLUGIN_MANAGER: LazyLock> = LazyLock::new(|| Mutex::new(PluginManager::new())); +pub static LOGGER_IMPL: LazyLock, LevelFilter)>> = LazyLock::new(|| { + if ADVANCED_CONFIG.logging.enabled { + let mut logger = SimpleLogger::new(); + + if ADVANCED_CONFIG.logging.timestamp { + logger = logger.with_timestamp_format(time::macros::format_description!( + "[year]-[month]-[day] [hour]:[minute]:[second]" + )); + } else { + logger = logger.without_timestamps(); + } + + Some(( + Box::new( + logger + .with_level(LevelFilter::Info) + .with_colors(ADVANCED_CONFIG.logging.color) + .with_threads(ADVANCED_CONFIG.logging.threads) + .env(), + ), + LevelFilter::Info, + )) + } else { + None + } +}); + +#[macro_export] +macro_rules! init_log { + () => { + if let Some((logger_impl, level)) = &*pumpkin::LOGGER_IMPL { + log::set_max_level(*level); + log::set_logger(logger_impl).unwrap(); + } + }; +} + pub struct PumpkinServer { pub server: Arc, pub listener: TcpListener, diff --git a/pumpkin/src/main.rs b/pumpkin/src/main.rs index d81caa37f..5e474bef0 100644 --- a/pumpkin/src/main.rs +++ b/pumpkin/src/main.rs @@ -29,12 +29,12 @@ #![expect(clippy::missing_errors_doc)] #![expect(clippy::module_name_repetitions)] #![expect(clippy::struct_excessive_bools)] +// Not warn event sending macros +#![allow(unused_labels)] #[cfg(target_os = "wasi")] compile_error!("Compiling for WASI targets is not supported!"); -use log::LevelFilter; - use plugin::PluginManager; use std::{ io::{self}, @@ -47,7 +47,7 @@ use tokio::signal::unix::{signal, SignalKind}; use tokio::sync::Mutex; use crate::server::CURRENT_MC_VERSION; -use pumpkin::PumpkinServer; +use pumpkin::{init_log, PumpkinServer}; use pumpkin_protocol::CURRENT_MC_PROTOCOL; use pumpkin_util::text::{color::NamedColor, TextComponent}; use std::time::Instant; @@ -75,7 +75,8 @@ const GIT_VERSION: &str = env!("GIT_VERSION"); #[tokio::main] async fn main() { let time = Instant::now(); - init_logger(); + + init_log!(); let default_panic = std::panic::take_hook(); std::panic::set_hook(Box::new(move |info| { @@ -130,30 +131,6 @@ fn handle_interrupt() { std::process::exit(0); } -fn init_logger() { - use pumpkin_config::ADVANCED_CONFIG; - if ADVANCED_CONFIG.logging.enabled { - let mut logger = simple_logger::SimpleLogger::new(); - logger = logger.with_timestamp_format(time::macros::format_description!( - "[year]-[month]-[day] [hour]:[minute]:[second]" - )); - - if !ADVANCED_CONFIG.logging.timestamp { - logger = logger.without_timestamps(); - } - - // default - logger = logger.with_level(LevelFilter::Info); - - logger = logger.with_colors(ADVANCED_CONFIG.logging.color); - logger = logger.with_threads(ADVANCED_CONFIG.logging.threads); - - logger = logger.env(); - - logger.init().unwrap(); - } -} - // Non-UNIX Ctrl-C handling #[cfg(not(unix))] async fn setup_sighandler() -> io::Result<()> { diff --git a/pumpkin/src/plugin/api/context.rs b/pumpkin/src/plugin/api/context.rs index be6763e37..e66443ab2 100644 --- a/pumpkin/src/plugin/api/context.rs +++ b/pumpkin/src/plugin/api/context.rs @@ -12,12 +12,28 @@ use crate::{ use super::{Event, EventPriority, PluginMetadata}; +/// The `Context` struct represents the context of a plugin, containing metadata, +/// a server reference, and event handlers. +/// +/// # Fields +/// - `metadata`: Metadata of the plugin. +/// - `server`: A reference to the server on which the plugin operates. +/// - `handlers`: A map of event handlers, protected by a read-write lock for safe access across threads. pub struct Context { metadata: PluginMetadata<'static>, pub server: Arc, handlers: Arc>, } impl Context { + /// Creates a new instance of `Context`. + /// + /// # Arguments + /// - `metadata`: The metadata of the plugin. + /// - `server`: A reference to the server. + /// - `handlers`: A collection containing the event handlers. + /// + /// # Returns + /// A new instance of `Context`. #[must_use] pub fn new( metadata: PluginMetadata<'static>, @@ -31,6 +47,10 @@ impl Context { } } + /// Retrieves the data folder path for the plugin, creating it if it does not exist. + /// + /// # Returns + /// A string representing the path to the data folder. #[must_use] pub fn get_data_folder(&self) -> String { let path = format!("./plugins/{}", self.metadata.name); @@ -40,10 +60,22 @@ impl Context { path } + /// Asynchronously retrieves a player by their name. + /// + /// # Arguments + /// - `player_name`: The name of the player to retrieve. + /// + /// # Returns + /// An optional reference to the player if found, or `None` if not. pub async fn get_player_by_name(&self, player_name: String) -> Option> { self.server.get_player_by_name(&player_name).await } + /// Asynchronously registers a command with the server. + /// + /// # Arguments + /// - `tree`: The command tree to register. + /// - `permission`: The permission level required to execute the command. pub async fn register_command( &self, tree: crate::command::tree::CommandTree, @@ -62,6 +94,10 @@ impl Context { } } + /// Asynchronously unregisters a command from the server. + /// + /// # Arguments + /// - `name`: The name of the command to unregister. pub async fn unregister_command(&self, name: &str) { { let mut dispatcher_lock = self.server.command_dispatcher.write().await; @@ -76,9 +112,22 @@ impl Context { } } + /// Asynchronously registers an event handler for a specific event type. + /// + /// # Type Parameters + /// - `E`: The event type that the handler will respond to. + /// - `H`: The type of the event handler. + /// + /// # Arguments + /// - `handler`: A reference to the event handler. + /// - `priority`: The priority of the event handler. + /// - `blocking`: A boolean indicating whether the handler is blocking. + /// + /// # Constraints + /// The handler must implement the `EventHandler` trait. pub async fn register_event( &self, - handler: H, + handler: Arc, priority: EventPriority, blocking: bool, ) where diff --git a/pumpkin/src/plugin/api/events/block.rs b/pumpkin/src/plugin/api/events/block.rs index ea3184b27..2f8187534 100644 --- a/pumpkin/src/plugin/api/events/block.rs +++ b/pumpkin/src/plugin/api/events/block.rs @@ -1,24 +1,51 @@ -use std::sync::Arc; - -use pumpkin_macros::{cancellable, event}; +use pumpkin_macros::{cancellable, Event}; use pumpkin_world::block::registry::Block; +use std::sync::Arc; use crate::entity::player::Player; +/// A trait representing events related to blocks. +/// +/// This trait provides a method to retrieve the block associated with the event. pub trait BlockEvent: Send + Sync { + /// Retrieves a reference to the block associated with the event. + /// + /// # Returns + /// A reference to the `Block` involved in the event. fn get_block(&self) -> &Block; } -#[event] +/// An event that occurs when a block is broken. +/// +/// This event contains information about the player breaking the block, the block itself, +/// the experience gained, and whether the block should drop items. #[cancellable] +#[derive(Event, Clone)] pub struct BlockBreakEvent { + /// The player who is breaking the block, if applicable. pub player: Option>, + + /// The block that is being broken. pub block: Block, + + /// The amount of experience gained from breaking the block. pub exp: u32, + + /// A boolean indicating whether the block should drop items. pub drop: bool, } impl BlockBreakEvent { + /// Creates a new instance of `BlockBreakEvent`. + /// + /// # Arguments + /// - `player`: An optional reference to the player breaking the block. + /// - `block`: The block that is being broken. + /// - `exp`: The amount of experience gained from breaking the block. + /// - `drop`: A boolean indicating whether the block should drop items. + /// + /// # Returns + /// A new instance of `BlockBreakEvent`. #[must_use] pub fn new(player: Option>, block: Block, exp: u32, drop: bool) -> Self { Self { @@ -37,10 +64,16 @@ impl BlockEvent for BlockBreakEvent { } } -#[event] +/// An event that occurs when a block is burned. +/// +/// This event contains information about the block that ignited the fire and the block that is burning. #[cancellable] +#[derive(Event, Clone)] pub struct BlockBurnEvent { + /// The block that is igniting the fire. pub igniting_block: Block, + + /// The block that is burning. pub block: Block, } @@ -50,12 +83,23 @@ impl BlockEvent for BlockBurnEvent { } } -#[event] +/// An event that occurs when a player attempts to build on a block. +/// +/// This event contains information about the block to build, whether building is allowed, +/// the player attempting to build, and the block being built upon. #[cancellable] +#[derive(Event, Clone)] pub struct BlockCanBuildEvent { + /// The block that the player is attempting to build. pub block_to_build: Block, + + /// A boolean indicating whether building is allowed. pub buildable: bool, + + /// The player attempting to build. pub player: Arc, + + /// The block being built upon. pub block: Block, } @@ -65,12 +109,23 @@ impl BlockEvent for BlockCanBuildEvent { } } -#[event] +/// An event that occurs when a block is placed. +/// +/// This event contains information about the player placing the block, the block being placed, +/// the block being placed against, and whether the player can build. #[cancellable] +#[derive(Event, Clone)] pub struct BlockPlaceEvent { + /// The player placing the block. pub player: Arc, + + /// The block that is being placed. pub block_placed: Block, + + /// The block that the new block is being placed against. pub block_placed_against: Block, + + /// A boolean indicating whether the player can build. pub can_build: bool, } diff --git a/pumpkin/src/plugin/api/events/mod.rs b/pumpkin/src/plugin/api/events/mod.rs index d0ab0b921..a0f2bf464 100644 --- a/pumpkin/src/plugin/api/events/mod.rs +++ b/pumpkin/src/plugin/api/events/mod.rs @@ -2,27 +2,77 @@ use std::any::Any; pub mod block; pub mod player; +pub mod world; +/// A trait representing an event in the system. +/// +/// This trait provides methods for retrieving the event's name and for type-safe downcasting. pub trait Event: Send + Sync { + /// Returns the static name of the event type. + /// + /// # Returns + /// A static string slice representing the name of the event type. fn get_name_static() -> &'static str where Self: Sized; + + /// Returns the name of the event instance. + /// + /// # Returns + /// A static string slice representing the name of the event instance. fn get_name(&self) -> &'static str; + + /// Provides a mutable reference to the event as a trait object. + /// + /// This method allows for type-safe downcasting of the event. + /// + /// # Returns + /// A mutable reference to the event as a `dyn Any` trait object. fn as_any_mut(&mut self) -> &mut dyn Any; + + /// Provides an immutable reference to the event as a trait object. + /// + /// This method allows for type-safe downcasting of the event. + /// + /// # Returns + /// An immutable reference to the event as a `dyn Any` trait object. fn as_any(&self) -> &dyn Any; } +/// A trait for cancellable events. +/// +/// This trait provides methods to check and set the cancellation state of an event. pub trait Cancellable: Send + Sync { + /// Checks if the event has been cancelled. + /// + /// # Returns + /// A boolean indicating whether the event is cancelled. fn cancelled(&self) -> bool; + + /// Sets the cancellation state of the event. + /// + /// # Arguments + /// - `cancelled`: A boolean indicating the new cancellation state. fn set_cancelled(&mut self, cancelled: bool); } - -// Lowest priority events are executed first, so that higher priority events can override their changes +/// An enumeration representing the priority levels of events. +/// +/// Events with lower priority values are executed first, allowing higher priority events +/// to override their changes. #[derive(Eq, PartialEq, Ord, PartialOrd, Clone)] pub enum EventPriority { + /// Highest priority level. Highest, + + /// High priority level. High, + + /// Normal priority level. Normal, + + /// Low priority level. Low, + + /// Lowest priority level. Lowest, } diff --git a/pumpkin/src/plugin/api/events/player.rs b/pumpkin/src/plugin/api/events/player.rs index 9e24ea3f6..21ad75967 100644 --- a/pumpkin/src/plugin/api/events/player.rs +++ b/pumpkin/src/plugin/api/events/player.rs @@ -1,22 +1,42 @@ -use std::sync::Arc; - -use pumpkin_macros::{cancellable, event}; +use pumpkin_macros::{cancellable, Event}; use pumpkin_util::text::TextComponent; +use std::sync::Arc; use crate::entity::player::Player; +/// A trait representing events related to players. +/// +/// This trait provides a method to retrieve the player associated with the event. pub trait PlayerEvent: Send + Sync { + /// Retrieves a reference to the player associated with the event. + /// + /// # Returns + /// A reference to the `Arc` involved in the event. fn get_player(&self) -> &Arc; } -#[event] +/// An event that occurs when a player joins the game. +/// +/// This event contains information about the player joining and a message to display upon joining. #[cancellable] +#[derive(Event, Clone)] pub struct PlayerJoinEvent { + /// The player who is joining the game. pub player: Arc, + + /// The message to display when the player joins. pub join_message: TextComponent, } impl PlayerJoinEvent { + /// Creates a new instance of `PlayerJoinEvent`. + /// + /// # Arguments + /// - `player`: A reference to the player joining the game. + /// - `join_message`: The message to display upon joining. + /// + /// # Returns + /// A new instance of `PlayerJoinEvent`. pub fn new(player: Arc, join_message: TextComponent) -> Self { Self { player, @@ -32,14 +52,28 @@ impl PlayerEvent for PlayerJoinEvent { } } -#[event] +/// An event that occurs when a player leaves the game. +/// +/// This event contains information about the player leaving and a message to display upon leaving. #[cancellable] +#[derive(Event, Clone)] pub struct PlayerLeaveEvent { + /// The player who is leaving the game. pub player: Arc, + + /// The message to display when the player leaves. pub leave_message: TextComponent, } impl PlayerLeaveEvent { + /// Creates a new instance of `PlayerLeaveEvent`. + /// + /// # Arguments + /// - `player`: A reference to the player leaving the game. + /// - `leave_message`: The message to display upon leaving. + /// + /// # Returns + /// A new instance of `PlayerLeaveEvent`. pub fn new(player: Arc, leave_message: TextComponent) -> Self { Self { player, diff --git a/pumpkin/src/plugin/api/events/world.rs b/pumpkin/src/plugin/api/events/world.rs new file mode 100644 index 000000000..1789faad2 --- /dev/null +++ b/pumpkin/src/plugin/api/events/world.rs @@ -0,0 +1,44 @@ +use crate::world::World; +use pumpkin_macros::{cancellable, Event}; +use pumpkin_world::chunk::ChunkData; +use std::sync::Arc; +use tokio::sync::RwLock; + +/// An event that occurs when a chunk is loaded in a world. +/// +/// This event contains information about the world and the chunk being loaded. +#[cancellable] +#[derive(Event, Clone)] +pub struct ChunkLoad { + /// The world in which the chunk is being loaded. + pub world: Arc, + + /// The chunk data being loaded, wrapped in a read-write lock for safe concurrent access. + pub chunk: Arc>, +} + +/// An event that occurs when a chunk is sent to a client. +/// +/// This event contains information about the world and the chunk being sent. +#[cancellable] +#[derive(Event, Clone)] +pub struct ChunkSend { + /// The world from which the chunk is being sent. + pub world: Arc, + + /// The chunk data being sent, wrapped in a read-write lock for safe concurrent access. + pub chunk: Arc>, +} + +/// An event that occurs when a chunk is saved in a world. +/// +/// This event contains information about the world and the chunk being saved. +#[cancellable] +#[derive(Event, Clone)] +pub struct ChunkSave { + /// The world in which the chunk is being saved. + pub world: Arc, + + /// The chunk data being saved, wrapped in a read-write lock for safe concurrent access. + pub chunk: Arc>, +} diff --git a/pumpkin/src/plugin/api/mod.rs b/pumpkin/src/plugin/api/mod.rs index 1d924438f..2350506af 100644 --- a/pumpkin/src/plugin/api/mod.rs +++ b/pumpkin/src/plugin/api/mod.rs @@ -5,6 +5,11 @@ use async_trait::async_trait; pub use context::*; pub use events::*; +/// Struct representing metadata for a plugin. +/// +/// This struct contains essential information about a plugin, including its name, +/// version, authors, and a description. It is generic over a lifetime `'s` to allow +/// for string slices that are valid for the lifetime of the plugin metadata. #[derive(Debug, Clone)] pub struct PluginMetadata<'s> { /// The name of the plugin. @@ -17,14 +22,35 @@ pub struct PluginMetadata<'s> { pub description: &'s str, } +/// Trait representing a plugin with asynchronous lifecycle methods. +/// +/// This trait defines the required methods for a plugin, including hooks for when +/// the plugin is loaded and unloaded. It is marked with `async_trait` to allow +/// for asynchronous implementations. #[async_trait] pub trait Plugin: Send + Sync + 'static { - /// Called when the plugin is loaded. + /// Asynchronous method called when the plugin is loaded. + /// + /// This method initializes the plugin within the server context. + /// + /// # Parameters + /// - `_server`: Reference to the server's context. + /// + /// # Returns + /// - `Ok(())` on success, or `Err(String)` on failure. async fn on_load(&mut self, _server: &Context) -> Result<(), String> { Ok(()) } - /// Called when the plugin is unloaded. + /// Asynchronous method called when the plugin is unloaded. + /// + /// This method cleans up resources when the plugin is removed from the server context. + /// + /// # Parameters + /// - `_server`: Reference to the server's context. + /// + /// # Returns + /// - `Ok(())` on success, or `Err(String)` on failure. async 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 a3145a021..c276fc7cd 100644 --- a/pumpkin/src/plugin/mod.rs +++ b/pumpkin/src/plugin/mod.rs @@ -15,30 +15,67 @@ type PluginData = ( bool, ); +/// A trait for handling events dynamically. +/// +/// This trait allows for handling events of any type that implements the `Event` trait. #[async_trait] pub trait DynEventHandler: Send + Sync { + /// Asynchronously handles a dynamic event. + /// + /// # Arguments + /// - `event`: A reference to the event to handle. async fn handle_dyn(&self, event: &(dyn Event + Send + Sync)); + + /// Asynchronously handles a blocking dynamic event. + /// + /// # Arguments + /// - `event`: A mutable reference to the event to handle. async fn handle_blocking_dyn(&self, _event: &mut (dyn Event + Send + Sync)); + + /// Checks if the event handler is blocking. + /// + /// # Returns + /// A boolean indicating whether the handler is blocking. fn is_blocking(&self) -> bool; + + /// Retrieves the priority of the event handler. + /// + /// # Returns + /// The priority of the event handler. fn get_priority(&self) -> EventPriority; } +/// A trait for handling specific events. +/// +/// This trait allows for handling events of a specific type that implements the `Event` trait. #[async_trait] pub trait EventHandler: Send + Sync { + /// Asynchronously handles an event of type `E`. + /// + /// # Arguments + /// - `event`: A reference to the event to handle. async fn handle(&self, _event: &E) { unimplemented!(); } + + /// Asynchronously handles a blocking event of type `E`. + /// + /// # Arguments + /// - `event`: A mutable reference to the event to handle. async fn handle_blocking(&self, _event: &mut E) { unimplemented!(); } } +/// A struct representing a typed event handler. +/// +/// This struct holds a reference to an event handler, its priority, and whether it is blocking. struct TypedEventHandler where E: Event + Send + Sync + 'static, H: EventHandler + Send + Sync, { - handler: H, + handler: Arc, priority: EventPriority, blocking: bool, _phantom: std::marker::PhantomData, @@ -50,12 +87,10 @@ where E: Event + Send + Sync + 'static, H: EventHandler + Send + Sync, { + /// Asynchronously handles a blocking dynamic event. async fn handle_blocking_dyn(&self, event: &mut (dyn Event + Send + Sync)) { - // Check if the event is the same type as E. We can not use the type_id because it is - // different in the plugin and the main program if E::get_name_static() == event.get_name() { - // This is fully safe as long as the event's get_name() and get_name_static() - // functions are correctly implemented and don't conflict with other events + // Safely cast the event to the correct type and handle it. let event = unsafe { &mut *std::ptr::from_mut::(event.as_any_mut()).cast::() }; @@ -63,29 +98,32 @@ where } } + /// Asynchronously handles a dynamic event. async fn handle_dyn(&self, event: &(dyn Event + Send + Sync)) { - // Check if the event is the same type as E. We can not use the type_id because it is - // different in the plugin and the main program if E::get_name_static() == event.get_name() { - // This is fully safe as long as the event's get_name() and get_name_static() - // functions are correctly implemented and don't conflict with other events + // Safely cast the event to the correct type and handle it. let event = unsafe { &*std::ptr::from_ref::(event.as_any()).cast::() }; self.handler.handle(event).await; } } + /// Checks if the handler is blocking. fn is_blocking(&self) -> bool { self.blocking } + /// Retrieves the priority of the handler. fn get_priority(&self) -> EventPriority { self.priority.clone() } } +/// A type alias for a map of event handlers, where the key is a static string +/// and the value is a vector of dynamic event handlers. pub type HandlerMap = HashMap<&'static str, Vec>>; +/// A struct for managing plugins. pub struct PluginManager { plugins: Vec, server: Option>, @@ -93,12 +131,17 @@ pub struct PluginManager { } impl Default for PluginManager { + /// Creates a new instance of `PluginManager` with default values. fn default() -> Self { Self::new() } } impl PluginManager { + /// Creates a new instance of `PluginManager`. + /// + /// # Returns + /// A new instance of `PluginManager`. #[must_use] pub fn new() -> Self { Self { @@ -108,16 +151,24 @@ impl PluginManager { } } + /// Sets the server reference for the plugin manager. + /// + /// # Arguments + /// - `server`: An `Arc` reference to the server to set. pub fn set_server(&mut self, server: Arc) { self.server = Some(server); } + /// Asynchronously loads plugins from the specified plugin directory. + /// + /// # Returns + /// A result indicating success or failure. If it fails, it returns a `PluginsLoadError`. pub async fn load_plugins(&mut self) -> Result<(), PluginsLoadError> { const PLUGIN_DIR: &str = "./plugins"; if !Path::new(PLUGIN_DIR).exists() { fs::create_dir(PLUGIN_DIR).map_err(|_| PluginsLoadError::CreatePluginDir)?; - // Well we just created the dir, it has to be empty so lets return + // If the directory was just created, it should be empty, so we return. return Ok(()); } @@ -137,6 +188,13 @@ impl PluginManager { Ok(()) } + /// Tries to load a plugin from the specified path. + /// + /// # Arguments + /// - `path`: The path to the plugin to load. + /// + /// # Returns + /// A result indicating success or failure. If it fails, it returns a `PluginLoadError`. async fn try_load_plugin(&mut self, path: &Path) -> Result<(), PluginLoadError> { let library = unsafe { libloading::Library::new(path) @@ -154,7 +212,7 @@ impl PluginManager { .map_err(|_| PluginLoadError::GetPluginMeta)? }; - // The chance that this will panic is non-existent, but just in case + // Create a context for the plugin. let context = Context::new( metadata.clone(), self.server.clone().expect("Server not set"), @@ -173,6 +231,13 @@ impl PluginManager { Ok(()) } + /// Checks if a plugin is loaded by its name. + /// + /// # Arguments + /// - `name`: The name of the plugin to check. + /// + /// # Returns + /// A boolean indicating whether the plugin is loaded. #[must_use] pub fn is_plugin_loaded(&self, name: &str) -> bool { self.plugins @@ -180,6 +245,13 @@ impl PluginManager { .any(|(metadata, _, _, loaded)| metadata.name == name && *loaded) } + /// Asynchronously loads a plugin by its name. + /// + /// # Arguments + /// - `name`: The name of the plugin to load. + /// + /// # Returns + /// A result indicating success or failure. If it fails, it returns an error message. pub async fn load_plugin(&mut self, name: &str) -> Result<(), String> { let plugin = self .plugins @@ -205,6 +277,13 @@ impl PluginManager { } } + /// Asynchronously unloads a plugin by its name. + /// + /// # Arguments + /// - `name`: The name of the plugin to unload. + /// + /// # Returns + /// A result indicating success or failure. If it fails, it returns an error message. pub async fn unload_plugin(&mut self, name: &str) -> Result<(), String> { let plugin = self .plugins @@ -226,6 +305,11 @@ impl PluginManager { } } + /// Lists all plugins along with their loaded status. + /// + /// # Returns + /// A vector of tuples containing references to the plugin metadata and a boolean indicating + /// whether each plugin is loaded. #[must_use] pub fn list_plugins(&self) -> Vec<(&PluginMetadata, &bool)> { self.plugins @@ -234,9 +318,22 @@ impl PluginManager { .collect() } + /// Asynchronously registers an event handler for a specific event type. + /// + /// # Type Parameters + /// - `E`: The event type that the handler will respond to. + /// - `H`: The type of the event handler. + /// + /// # Arguments + /// - `handler`: A reference to the event handler. + /// - `priority`: The priority of the event handler. + /// - `blocking`: A boolean indicating whether the handler is blocking. + /// + /// # Constraints + /// The handler must implement the `EventHandler` trait. pub async fn register( &self, - handler: H, + handler: Arc, priority: EventPriority, blocking: bool, ) where @@ -258,6 +355,16 @@ impl PluginManager { handlers_vec.push(Box::new(typed_handler)); } + /// Asynchronously fires an event, invoking all registered handlers for that event type. + /// + /// # Type Parameters + /// - `E`: The event type to fire. + /// + /// # Arguments + /// - `event`: The event to fire. + /// + /// # Returns + /// The event after all handlers have processed it. pub async fn fire(&self, mut event: E) -> E { // Take a snapshot of handlers to avoid lifetime issues let handlers = self.handlers.read().await; @@ -275,6 +382,7 @@ impl PluginManager { .iter() .partition(|handler| handler.is_blocking()); + // Handle blocking handlers first for handler in blocking_handlers { handler.handle_blocking_dyn(&mut event).await; } diff --git a/pumpkin/src/world/mod.rs b/pumpkin/src/world/mod.rs index a281e14b8..9ed76c2f3 100644 --- a/pumpkin/src/world/mod.rs +++ b/pumpkin/src/world/mod.rs @@ -1,4 +1,7 @@ -use std::{collections::HashMap, sync::Arc}; +use std::{ + collections::HashMap, + sync::{atomic::Ordering, Arc}, +}; pub mod level_time; pub mod player_chunker; @@ -10,6 +13,7 @@ use crate::{ plugin::{ block::BlockBreakEvent, player::{PlayerJoinEvent, PlayerLeaveEvent}, + world::{ChunkLoad, ChunkSave, ChunkSend}, }, server::Server, PLUGIN_MANAGER, @@ -21,6 +25,7 @@ use pumpkin_data::{ sound::{Sound, SoundCategory}, world::WorldEvent, }; +use pumpkin_macros::send_cancellable; use pumpkin_protocol::client::play::{CBlockUpdate, CDisguisedChatMessage, CRespawn, CWorldEvent}; use pumpkin_protocol::{client::play::CLevelEvent, codec::identifier::Identifier}; use pumpkin_protocol::{ @@ -594,11 +599,13 @@ impl World { let level = self.level.clone(); tokio::spawn(async move { - while let Some(chunk_data) = receiver.recv().await { - let chunk_data = chunk_data.read().await; - let packet = CChunkData(&chunk_data); + 'main: while let Some((chunk, first_load)) = receiver.recv().await { + let position = chunk.read().await.position; + #[cfg(debug_assertions)] - if chunk_data.position == (0, 0).into() { + if position == (0, 0).into() { + let binding = chunk.read().await; + let packet = CChunkData(&binding); let mut test = bytes::BytesMut::new(); packet.write(&mut test); let len = test.len(); @@ -610,21 +617,60 @@ impl World { ); } - if !level.is_chunk_watched(&chunk_data.position) { - log::trace!( - "Received chunk {:?}, but it is no longer watched... cleaning", - &chunk_data.position - ); - level.clean_chunk(&chunk_data.position).await; - continue; - } - - if !player - .client - .closed - .load(std::sync::atomic::Ordering::Relaxed) - { - player.client.send_packet(&packet).await; + let (world, chunk) = if level.is_chunk_watched(&position) { + (player.world().clone(), chunk) + } else { + send_cancellable! {{ + ChunkSave { + world: player.world().clone(), + chunk, + cancelled: false, + }; + + 'after: { + log::trace!( + "Received chunk {:?}, but it is no longer watched... cleaning", + &position + ); + level.clean_chunk(&position).await; + continue 'main; + } + }}; + (event.world, event.chunk) + }; + + let (world, chunk) = if first_load { + send_cancellable! {{ + ChunkLoad { + world, + chunk, + cancelled: false, + }; + + 'cancelled: { + continue 'main; + } + }} + (event.world, event.chunk) + } else { + (world, chunk) + }; + + if !player.client.closed.load(Ordering::Relaxed) { + send_cancellable! {{ + ChunkSend { + world, + chunk, + cancelled: false, + }; + + 'after: { + player + .client + .send_packet(&CChunkData(&*event.chunk.read().await)) + .await; + } + }}; } } @@ -880,7 +926,7 @@ impl World { // Since we divide by 16 remnant can never exceed u8 let relative = ChunkRelativeBlockCoordinates::from(relative_coordinates); - let chunk = self.receive_chunk(chunk_coordinate).await; + let chunk = self.receive_chunk(chunk_coordinate).await.0; let replaced_block_state_id = chunk.read().await.subchunks.get_block(relative).unwrap(); chunk .write() @@ -900,7 +946,10 @@ impl World { // Stream the chunks (don't collect them and then do stuff with them) /// Important: must be called from an async function (or changed to accept a tokio runtime /// handle) - pub fn receive_chunks(&self, chunks: Vec>) -> Receiver>> { + pub fn receive_chunks( + &self, + chunks: Vec>, + ) -> Receiver<(Arc>, bool)> { let (sender, receive) = mpsc::channel(chunks.len()); // Put this in another thread so we aren't blocking on it let level = self.level.clone(); @@ -911,7 +960,7 @@ impl World { receive } - pub async fn receive_chunk(&self, chunk_pos: Vector2) -> Arc> { + pub async fn receive_chunk(&self, chunk_pos: Vector2) -> (Arc>, bool) { let mut receiver = self.receive_chunks(vec![chunk_pos]); let chunk = receiver .recv() @@ -962,7 +1011,7 @@ impl World { pub async fn get_block_state_id(&self, position: &BlockPos) -> Result { let (chunk, relative) = position.chunk_and_chunk_relative_position(); let relative = ChunkRelativeBlockCoordinates::from(relative); - let chunk = self.receive_chunk(chunk).await; + let chunk = self.receive_chunk(chunk).await.0; let chunk: tokio::sync::RwLockReadGuard = chunk.read().await; let Some(id) = chunk.subchunks.get_block(relative) else {