diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/NativeCallbacks.java b/java/patchbukkit/src/main/java/org/patchbukkit/NativeCallbacks.java index 003f117..aeb9887 100644 --- a/java/patchbukkit/src/main/java/org/patchbukkit/NativeCallbacks.java +++ b/java/patchbukkit/src/main/java/org/patchbukkit/NativeCallbacks.java @@ -1,3 +1,4 @@ +// No longer used currently, keeping around for reference in the short term package org.patchbukkit; import org.astonbitecode.j4rs.api.invocation.NativeCallbackToRustChannelSupport; diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitEventManager.java b/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitEventManager.java new file mode 100644 index 0000000..074b51f --- /dev/null +++ b/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitEventManager.java @@ -0,0 +1,327 @@ +// This file was mostly taken from https://github.com/PaperMC/Paper/blob/main/paper-server/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java +package org.patchbukkit; + +import co.aikar.timings.TimedEventExecutor; +import com.destroystokyo.paper.event.server.ServerExceptionEvent; +import com.destroystokyo.paper.exception.ServerEventException; +import com.google.common.collect.Sets; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; +import org.bukkit.Server; +import org.bukkit.Warning; +import org.bukkit.event.Event; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; +import org.bukkit.plugin.AuthorNagException; +import org.bukkit.plugin.EventExecutor; +import org.bukkit.plugin.IllegalPluginAccessException; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.RegisteredListener; +import org.jetbrains.annotations.NotNull; + +public class PatchBukkitEventManager { + + private final Server server; + + public PatchBukkitEventManager(Server server) { + this.server = server; + } + + public void callEvent(@NotNull Event event) { + if (event.isAsynchronous() && this.server.isPrimaryThread()) { + throw new IllegalStateException( + event.getEventName() + " may only be triggered asynchronously." + ); + } else if ( + !event.isAsynchronous() && + !this.server.isPrimaryThread() && + !this.server.isStopping() + ) { + throw new IllegalStateException( + event.getEventName() + " may only be triggered synchronously." + ); + } + + HandlerList handlers = event.getHandlers(); + RegisteredListener[] listeners = handlers.getRegisteredListeners(); + + for (RegisteredListener registration : listeners) { + if (!registration.getPlugin().isEnabled()) { + continue; + } + + try { + registration.callEvent(event); + } catch (AuthorNagException ex) { + Plugin plugin = registration.getPlugin(); + + if (plugin.isNaggable()) { + plugin.setNaggable(false); + + this.server.getLogger().log( + Level.SEVERE, + String.format( + "Nag author(s): '%s' of '%s' about the following: %s", + plugin.getPluginMeta().getAuthors(), + plugin.getPluginMeta().getDisplayName(), + ex.getMessage() + ) + ); + } + } catch (Throwable ex) { + String msg = + "Could not pass event " + + event.getEventName() + + " to " + + registration.getPlugin().getPluginMeta().getDisplayName(); + this.server.getLogger().log(Level.SEVERE, msg, ex); + if (!(event instanceof ServerExceptionEvent)) { + // We don't want to cause an endless event loop + this.callEvent( + new ServerExceptionEvent( + new ServerEventException( + msg, + ex, + registration.getPlugin(), + registration.getListener(), + event + ) + ) + ); + } + } + } + } + + public void registerEvents( + @NotNull Listener listener, + @NotNull Plugin plugin + ) { + if (!plugin.isEnabled()) { + throw new IllegalPluginAccessException( + "Plugin attempted to register " + + listener + + " while not enabled" + ); + } + + for (Map.Entry< + Class, + Set + > entry : this.createRegisteredListeners(listener, plugin).entrySet()) { + this.getEventListeners( + this.getRegistrationClass(entry.getKey()) + ).registerAll(entry.getValue()); + } + } + + @NotNull + private HandlerList getEventListeners( + @NotNull Class type + ) { + try { + Method method = this.getRegistrationClass(type).getDeclaredMethod( + "getHandlerList" + ); + method.setAccessible(true); + return (HandlerList) method.invoke(null); + } catch (Exception e) { + throw new IllegalPluginAccessException(e.toString()); + } + } + + @NotNull + private Class getRegistrationClass( + @NotNull Class clazz + ) { + try { + clazz.getDeclaredMethod("getHandlerList"); + return clazz; + } catch (NoSuchMethodException e) { + if ( + clazz.getSuperclass() != null && + !clazz.getSuperclass().equals(Event.class) && + Event.class.isAssignableFrom(clazz.getSuperclass()) + ) { + return this.getRegistrationClass( + clazz.getSuperclass().asSubclass(Event.class) + ); + } else { + throw new IllegalPluginAccessException( + "Unable to find handler list for event " + + clazz.getName() + + ". Static getHandlerList method required!" + ); + } + } + } + + public void registerEvent( + @NotNull Class event, + @NotNull Listener listener, + @NotNull EventPriority priority, + @NotNull EventExecutor executor, + @NotNull Plugin plugin + ) { + this.registerEvent(event, listener, priority, executor, plugin, false); + } + + public void registerEvent( + @NotNull Class event, + @NotNull Listener listener, + @NotNull EventPriority priority, + @NotNull EventExecutor executor, + @NotNull Plugin plugin, + boolean ignoreCancelled + ) { + if (!plugin.isEnabled()) { + throw new IllegalPluginAccessException( + "Plugin attempted to register " + event + " while not enabled" + ); + } + + executor = new TimedEventExecutor(executor, plugin, null, event); + this.getEventListeners(event).register( + new RegisteredListener( + listener, + executor, + priority, + plugin, + ignoreCancelled + ) + ); + } + + @NotNull + public Map< + Class, + Set + > createRegisteredListeners( + @NotNull Listener listener, + @NotNull final Plugin plugin + ) { + Map, Set> ret = + new HashMap<>(); + + Set methods; + try { + Class listenerClazz = listener.getClass(); + methods = Sets.union( + Set.of(listenerClazz.getMethods()), + Set.of(listenerClazz.getDeclaredMethods()) + ); + } catch (NoClassDefFoundError e) { + plugin + .getLogger() + .severe( + "Failed to register events for " + + listener.getClass() + + " because " + + e.getMessage() + + " does not exist." + ); + return ret; + } + + for (final Method method : methods) { + final EventHandler eh = method.getAnnotation(EventHandler.class); + if (eh == null) continue; + // Do not register bridge or synthetic methods to avoid event duplication + // Fixes SPIGOT-893 + if (method.isBridge() || method.isSynthetic()) { + continue; + } + final Class checkClass; + if ( + method.getParameterTypes().length != 1 || + !Event.class.isAssignableFrom( + checkClass = method.getParameterTypes()[0] + ) + ) { + plugin + .getLogger() + .severe( + plugin.getPluginMeta().getDisplayName() + + " attempted to register an invalid EventHandler method signature \"" + + method.toGenericString() + + "\" in " + + listener.getClass() + ); + continue; + } + final Class eventClass = checkClass.asSubclass( + Event.class + ); + method.setAccessible(true); + Set eventSet = ret.computeIfAbsent( + eventClass, + k -> new HashSet<>() + ); + + for ( + Class clazz = eventClass; + Event.class.isAssignableFrom(clazz); + clazz = clazz.getSuperclass() + ) { + // This loop checks for extending deprecated events + if (clazz.getAnnotation(Deprecated.class) != null) { + Warning warning = clazz.getAnnotation(Warning.class); + Warning.WarningState warningState = + this.server.getWarningState(); + if (!warningState.printFor(warning)) { + break; + } + plugin + .getLogger() + .log( + Level.WARNING, + String.format( + "\"%s\" has registered a listener for %s on method \"%s\", but the event is Deprecated. \"%s\"; please notify the authors %s.", + plugin.getPluginMeta().getDisplayName(), + clazz.getName(), + method.toGenericString(), + (warning != null && + warning.reason().length() != 0) + ? warning.reason() + : "Server performance will be affected", + Arrays.toString( + plugin + .getPluginMeta() + .getAuthors() + .toArray() + ) + ), + warningState == Warning.WarningState.ON + ? new AuthorNagException(null) + : null + ); + break; + } + } + + EventExecutor executor = new TimedEventExecutor( + EventExecutor.create(method, eventClass), + plugin, + method, + eventClass + ); + eventSet.add( + new RegisteredListener( + listener, + executor, + eh.priority(), + plugin, + eh.ignoreCancelled() + ) + ); + } + return ret; + } +} diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitPluginLoader.java b/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitPluginLoader.java index 595f105..8a33072 100644 --- a/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitPluginLoader.java +++ b/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitPluginLoader.java @@ -81,8 +81,8 @@ public class PatchBukkitPluginLoader implements PluginLoader { Class, Set > createRegisteredListeners(Listener listener, Plugin plugin) { - // TODO: Implement event listener registration when event system is needed - // For now, return empty map - plugins won't be able to register events - return Map.of(); + throw new UnsupportedOperationException( + "Unimplemented method 'createRegisteredListeners'" + ); } } diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitPluginManager.java b/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitPluginManager.java index aedf611..02e9878 100644 --- a/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitPluginManager.java +++ b/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitPluginManager.java @@ -5,6 +5,7 @@ import io.papermc.paper.plugin.configuration.PluginMeta; import java.io.File; import java.util.List; import java.util.Set; +import org.bukkit.Server; import org.bukkit.event.Event; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; @@ -18,6 +19,12 @@ import org.jspecify.annotations.NonNull; @SuppressWarnings("removal") public class PatchBukkitPluginManager implements PluginManager { + final PatchBukkitEventManager patchBukkitEventManager; + + public PatchBukkitPluginManager(Server server) { + this.patchBukkitEventManager = new PatchBukkitEventManager(server); + } + @Override public void registerInterface(@NotNull Class loader) throws IllegalArgumentException {} @@ -88,9 +95,7 @@ public class PatchBukkitPluginManager implements PluginManager { @Override public void callEvent(@NotNull Event event) throws IllegalStateException { - throw new UnsupportedOperationException( - "Unimplemented method 'callEvent'" - ); + this.patchBukkitEventManager.callEvent(event); } @Override @@ -98,7 +103,7 @@ public class PatchBukkitPluginManager implements PluginManager { @NotNull Listener listener, @NotNull Plugin plugin ) { - NativeCallbacks.getInstance().registerEventCallback(listener, plugin); + this.patchBukkitEventManager.registerEvents(listener, plugin); } @Override @@ -109,8 +114,12 @@ public class PatchBukkitPluginManager implements PluginManager { @NotNull EventExecutor executor, @NotNull Plugin plugin ) { - throw new UnsupportedOperationException( - "Unimplemented method 'registerEvent'" + this.patchBukkitEventManager.registerEvent( + event, + listener, + priority, + executor, + plugin ); } @@ -123,8 +132,13 @@ public class PatchBukkitPluginManager implements PluginManager { @NotNull Plugin plugin, boolean ignoreCancelled ) { - throw new UnsupportedOperationException( - "Unimplemented method 'registerEvent'" + this.patchBukkitEventManager.registerEvent( + event, + listener, + priority, + executor, + plugin, + ignoreCancelled ); } diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitServer.java b/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitServer.java index c90fde6..35c2f2d 100644 --- a/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitServer.java +++ b/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitServer.java @@ -443,7 +443,7 @@ public class PatchBukkitServer implements Server { @Override public @NotNull PluginManager getPluginManager() { - return new PatchBukkitPluginManager(); + return new PatchBukkitPluginManager(this); } @Override diff --git a/rust/src/events/join.rs b/rust/src/events/join.rs index 82bb330..09e8f2f 100644 --- a/rust/src/events/join.rs +++ b/rust/src/events/join.rs @@ -5,10 +5,9 @@ use pumpkin::{ server::Server, }; use pumpkin_api_macros::with_runtime; -use pumpkin_util::text::{TextComponent, color::NamedColor}; -use tokio::sync::mpsc; +use tokio::sync::{mpsc, oneshot}; -use crate::java::jvm::commands::JvmCommand; +use crate::{events::Event, java::jvm::commands::JvmCommand}; pub struct PatchBukkitJoinHandler { pub command_tx: mpsc::Sender, @@ -21,11 +20,22 @@ impl EventHandler for PatchBukkitJoinHandler { _server: &Arc, event: &'a mut PlayerJoinEvent, ) -> BoxFuture<'a, ()> { - Box::pin(async { - // for (_plugin_name, plugin) in self.plugins.lock().unwrap().iter_mut() {} - event.join_message = - TextComponent::text(format!("Welcome, {}!", event.player.gameprofile.name)) - .color_named(NamedColor::Green); - }) + let event: &mut PlayerJoinEvent = event; + + let (tx, rx) = oneshot::channel(); + let sent_event = event.clone(); + self.command_tx + .blocking_send(JvmCommand::TriggerEvent { + event: Event::PlayerJoinEvent(sent_event), + respond_to: tx, + }) + .unwrap(); + + match rx.await.unwrap().unwrap() { + Event::PlayerJoinEvent(player_join_event) => *event = player_join_event, + _ => unreachable!(), + }; + + Box::pin(async {}) } } diff --git a/rust/src/events/mod.rs b/rust/src/events/mod.rs index d05b16e..a660124 100644 --- a/rust/src/events/mod.rs +++ b/rust/src/events/mod.rs @@ -1,12 +1,16 @@ use std::sync::Arc; -use pumpkin::plugin::{Context, EventPriority}; +use pumpkin::plugin::{Context, EventPriority, player::player_join::PlayerJoinEvent}; use tokio::sync::mpsc; use crate::{events::join::PatchBukkitJoinHandler, java::jvm::commands::JvmCommand}; pub mod join; +pub enum Event { + PlayerJoinEvent(PlayerJoinEvent), +} + pub async fn register_handlers(command_tx: mpsc::Sender, server: &Arc) { server .register_event( diff --git a/rust/src/java/jvm/commands.rs b/rust/src/java/jvm/commands.rs index 295bf08..1e8be84 100644 --- a/rust/src/java/jvm/commands.rs +++ b/rust/src/java/jvm/commands.rs @@ -4,6 +4,8 @@ use anyhow::Result; use j4rs::Instance; use tokio::sync::oneshot; +use crate::events::Event; + pub enum LoadPluginResult { SuccessfullyLoadedSpigot, SuccessfullyLoadedPaper, @@ -38,4 +40,8 @@ pub enum JvmCommand { Shutdown { respond_to: oneshot::Sender>, }, + TriggerEvent { + event: Event, + respond_to: oneshot::Sender>, + }, } diff --git a/rust/src/java/jvm/worker.rs b/rust/src/java/jvm/worker.rs index 52d1fa2..984fb1f 100644 --- a/rust/src/java/jvm/worker.rs +++ b/rust/src/java/jvm/worker.rs @@ -2,20 +2,23 @@ use std::path::PathBuf; use anyhow::bail; use j4rs::{Instance, InvocationArg, Jvm, JvmBuilder}; +use pumpkin::plugin::player::player_join::PlayerJoinEvent; use tokio::sync::{mpsc, oneshot}; use crate::{ + events::Event, java::{ jar::read_configs_from_jar, jvm::commands::{JvmCommand, LoadPluginResult}, }, - plugin::manager::PluginManager, + plugin::{event_manager::EventManager, manager::PluginManager}, }; pub struct JvmWorker { command_rx: mpsc::Receiver, command_tx: mpsc::Sender, pub plugin_manager: PluginManager, + pub event_manager: EventManager, jvm: Option, } @@ -28,6 +31,7 @@ impl JvmWorker { command_rx, command_tx, plugin_manager: PluginManager::new(), + event_manager: EventManager::new(), jvm: None, } } @@ -117,6 +121,19 @@ impl JvmWorker { let _ = respond_to.send(self.plugin_manager.unload_all_plugins()); break; } + JvmCommand::TriggerEvent { event, respond_to } => { + let jvm = match self.jvm { + Some(ref jvm) => jvm, + None => &Jvm::attach_thread().unwrap(), + }; + + match event { + Event::PlayerJoinEvent(player_join_event) => { + // let player = jvm.create_instance("org.bukkit.entity.Player", &[]); + // self.event_manager.call_event(&jvm, event) + } + } + } } } diff --git a/rust/src/plugin/event_manager.rs b/rust/src/plugin/event_manager.rs new file mode 100644 index 0000000..1699290 --- /dev/null +++ b/rust/src/plugin/event_manager.rs @@ -0,0 +1,21 @@ +use anyhow::Result; +use j4rs::{Instance, InvocationArg, Jvm}; + +pub struct EventManager {} + +impl EventManager { + pub fn new() -> Self { + Self {} + } + + pub fn call_event(&self, jvm: &Jvm, event: Instance) -> Result<()> { + let server = jvm.invoke_static("org.bukkit.Bukkit", "getServer", InvocationArg::empty())?; + let plugin_manager = jvm.invoke(&server, "getPluginManager", InvocationArg::empty())?; + jvm.invoke( + &plugin_manager, + "callEvent", + &[InvocationArg::try_from(event)?], + )?; + Ok(()) + } +} diff --git a/rust/src/plugin/mod.rs b/rust/src/plugin/mod.rs index ff8de9e..c6a7ad5 100644 --- a/rust/src/plugin/mod.rs +++ b/rust/src/plugin/mod.rs @@ -1 +1,2 @@ +pub mod event_manager; pub mod manager;