Plugin event rewrite (#513)

* simplify all

* fmt

* Consider using derive macro

* Add new events

* Add events call

* minor fixes

* minor fixes

* fix

* optimize code

* big changes

* fix skill issue

* fix

* Consider not specify patch version

* make logger shared

* i want test one thing

* Add one useful function

* add little amouth of docs

* use config instead

* revert changes

* fmt

---------

Co-authored-by: Alexander Medvedev <lilalexmed@proton.me>
This commit is contained in:
suprohub
2025-02-01 21:14:28 +04:00
committed by GitHub
parent 466d4380cf
commit 186e846858
25 changed files with 678 additions and 138 deletions

View File

@@ -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"

View File

@@ -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"
syn = { version = "2.0", features = ["full"] }
quote = "1.0"
proc-macro2 = "1.0"

View File

@@ -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
})
})

View File

@@ -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,

View File

@@ -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"

View File

@@ -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();

View File

@@ -7,4 +7,5 @@ edition.workspace = true
serde.workspace = true
thiserror.workspace = true
bytes.workspace = true
cesu8 = "1.1.0"
cesu8 = "1.1"

View File

@@ -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"
libdeflater = "1.23"

View File

@@ -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

View File

@@ -12,5 +12,5 @@ uuid.workspace = true
num-traits = "0.2"
colored = "3"
md5 = "0.7.0"
colored = "3.0"
md5 = "0.7"

View File

@@ -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"

View File

@@ -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]>),

View File

@@ -235,7 +235,7 @@ impl Level {
pub fn fetch_chunks(
&self,
chunks: &[Vector2<i32>],
channel: mpsc::Sender<Arc<RwLock<ChunkData>>>,
channel: mpsc::Sender<(Arc<RwLock<ChunkData>>, 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));
});

View File

@@ -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"

View File

@@ -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<f32> {
// 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<f64>) {
let position = self.pos.load();

View File

@@ -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<Mutex<PluginManager>> =
LazyLock::new(|| Mutex::new(PluginManager::new()));
pub static LOGGER_IMPL: LazyLock<Option<(Box<dyn Log>, 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<Server>,
pub listener: TcpListener,

View File

@@ -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<()> {

View File

@@ -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<Server>,
handlers: Arc<RwLock<HandlerMap>>,
}
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<Arc<Player>> {
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<E>` trait.
pub async fn register_event<E: Event + 'static, H>(
&self,
handler: H,
handler: Arc<H>,
priority: EventPriority,
blocking: bool,
) where

View File

@@ -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<Arc<Player>>,
/// 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<Arc<Player>>, 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<Player>,
/// 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<Player>,
/// 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,
}

View File

@@ -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,
}

View File

@@ -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<Player>` involved in the event.
fn get_player(&self) -> &Arc<Player>;
}
#[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<Player>,
/// 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<Player>, 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<Player>,
/// 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<Player>, leave_message: TextComponent) -> Self {
Self {
player,

View File

@@ -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<World>,
/// The chunk data being loaded, wrapped in a read-write lock for safe concurrent access.
pub chunk: Arc<RwLock<ChunkData>>,
}
/// 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<World>,
/// The chunk data being sent, wrapped in a read-write lock for safe concurrent access.
pub chunk: Arc<RwLock<ChunkData>>,
}
/// 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<World>,
/// The chunk data being saved, wrapped in a read-write lock for safe concurrent access.
pub chunk: Arc<RwLock<ChunkData>>,
}

View File

@@ -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(())
}

View File

@@ -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<E: Event>: 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<E, H>
where
E: Event + Send + Sync + 'static,
H: EventHandler<E> + Send + Sync,
{
handler: H,
handler: Arc<H>,
priority: EventPriority,
blocking: bool,
_phantom: std::marker::PhantomData<E>,
@@ -50,12 +87,10 @@ where
E: Event + Send + Sync + 'static,
H: EventHandler<E> + 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::<dyn std::any::Any>(event.as_any_mut()).cast::<E>()
};
@@ -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::<dyn std::any::Any>(event.as_any()).cast::<E>() };
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<Box<dyn DynEventHandler>>>;
/// A struct for managing plugins.
pub struct PluginManager {
plugins: Vec<PluginData>,
server: Option<Arc<Server>>,
@@ -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<Server>) {
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<E>` trait.
pub async fn register<E: Event + 'static, H>(
&self,
handler: H,
handler: Arc<H>,
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<E: Event + Send + Sync + 'static>(&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;
}

View File

@@ -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;
}
let (world, chunk) = if level.is_chunk_watched(&position) {
(player.world().clone(), chunk)
} else {
send_cancellable! {{
ChunkSave {
world: player.world().clone(),
chunk,
cancelled: false,
};
if !player
.client
.closed
.load(std::sync::atomic::Ordering::Relaxed)
{
player.client.send_packet(&packet).await;
'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<Vector2<i32>>) -> Receiver<Arc<RwLock<ChunkData>>> {
pub fn receive_chunks(
&self,
chunks: Vec<Vector2<i32>>,
) -> Receiver<(Arc<RwLock<ChunkData>>, 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<i32>) -> Arc<RwLock<ChunkData>> {
pub async fn receive_chunk(&self, chunk_pos: Vector2<i32>) -> (Arc<RwLock<ChunkData>>, 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<u16, GetBlockError> {
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<ChunkData> = chunk.read().await;
let Some(id) = chunk.subchunks.get_block(relative) else {