Add in java event manager

This commit is contained in:
Bjorn Beishline 2026-01-28 04:33:27 -07:00
parent 93dc1adea9
commit 9ee012e2fa
11 changed files with 424 additions and 23 deletions

View File

@ -1,3 +1,4 @@
// No longer used currently, keeping around for reference in the short term
package org.patchbukkit; package org.patchbukkit;
import org.astonbitecode.j4rs.api.invocation.NativeCallbackToRustChannelSupport; import org.astonbitecode.j4rs.api.invocation.NativeCallbackToRustChannelSupport;

View File

@ -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<? extends Event>,
Set<RegisteredListener>
> entry : this.createRegisteredListeners(listener, plugin).entrySet()) {
this.getEventListeners(
this.getRegistrationClass(entry.getKey())
).registerAll(entry.getValue());
}
}
@NotNull
private HandlerList getEventListeners(
@NotNull Class<? extends Event> 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<? extends Event> getRegistrationClass(
@NotNull Class<? extends Event> 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<? extends Event> 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<? extends Event> 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<? extends Event>,
Set<RegisteredListener>
> createRegisteredListeners(
@NotNull Listener listener,
@NotNull final Plugin plugin
) {
Map<Class<? extends Event>, Set<RegisteredListener>> ret =
new HashMap<>();
Set<Method> 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<? extends Event> eventClass = checkClass.asSubclass(
Event.class
);
method.setAccessible(true);
Set<RegisteredListener> 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;
}
}

View File

@ -81,8 +81,8 @@ public class PatchBukkitPluginLoader implements PluginLoader {
Class<? extends Event>, Class<? extends Event>,
Set<RegisteredListener> Set<RegisteredListener>
> createRegisteredListeners(Listener listener, Plugin plugin) { > createRegisteredListeners(Listener listener, Plugin plugin) {
// TODO: Implement event listener registration when event system is needed throw new UnsupportedOperationException(
// For now, return empty map - plugins won't be able to register events "Unimplemented method 'createRegisteredListeners'"
return Map.of(); );
} }
} }

View File

@ -5,6 +5,7 @@ import io.papermc.paper.plugin.configuration.PluginMeta;
import java.io.File; import java.io.File;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import org.bukkit.Server;
import org.bukkit.event.Event; import org.bukkit.event.Event;
import org.bukkit.event.EventPriority; import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
@ -18,6 +19,12 @@ import org.jspecify.annotations.NonNull;
@SuppressWarnings("removal") @SuppressWarnings("removal")
public class PatchBukkitPluginManager implements PluginManager { public class PatchBukkitPluginManager implements PluginManager {
final PatchBukkitEventManager patchBukkitEventManager;
public PatchBukkitPluginManager(Server server) {
this.patchBukkitEventManager = new PatchBukkitEventManager(server);
}
@Override @Override
public void registerInterface(@NotNull Class<? extends PluginLoader> loader) public void registerInterface(@NotNull Class<? extends PluginLoader> loader)
throws IllegalArgumentException {} throws IllegalArgumentException {}
@ -88,9 +95,7 @@ public class PatchBukkitPluginManager implements PluginManager {
@Override @Override
public void callEvent(@NotNull Event event) throws IllegalStateException { public void callEvent(@NotNull Event event) throws IllegalStateException {
throw new UnsupportedOperationException( this.patchBukkitEventManager.callEvent(event);
"Unimplemented method 'callEvent'"
);
} }
@Override @Override
@ -98,7 +103,7 @@ public class PatchBukkitPluginManager implements PluginManager {
@NotNull Listener listener, @NotNull Listener listener,
@NotNull Plugin plugin @NotNull Plugin plugin
) { ) {
NativeCallbacks.getInstance().registerEventCallback(listener, plugin); this.patchBukkitEventManager.registerEvents(listener, plugin);
} }
@Override @Override
@ -109,8 +114,12 @@ public class PatchBukkitPluginManager implements PluginManager {
@NotNull EventExecutor executor, @NotNull EventExecutor executor,
@NotNull Plugin plugin @NotNull Plugin plugin
) { ) {
throw new UnsupportedOperationException( this.patchBukkitEventManager.registerEvent(
"Unimplemented method 'registerEvent'" event,
listener,
priority,
executor,
plugin
); );
} }
@ -123,8 +132,13 @@ public class PatchBukkitPluginManager implements PluginManager {
@NotNull Plugin plugin, @NotNull Plugin plugin,
boolean ignoreCancelled boolean ignoreCancelled
) { ) {
throw new UnsupportedOperationException( this.patchBukkitEventManager.registerEvent(
"Unimplemented method 'registerEvent'" event,
listener,
priority,
executor,
plugin,
ignoreCancelled
); );
} }

View File

@ -443,7 +443,7 @@ public class PatchBukkitServer implements Server {
@Override @Override
public @NotNull PluginManager getPluginManager() { public @NotNull PluginManager getPluginManager() {
return new PatchBukkitPluginManager(); return new PatchBukkitPluginManager(this);
} }
@Override @Override

View File

@ -5,10 +5,9 @@ use pumpkin::{
server::Server, server::Server,
}; };
use pumpkin_api_macros::with_runtime; use pumpkin_api_macros::with_runtime;
use pumpkin_util::text::{TextComponent, color::NamedColor}; use tokio::sync::{mpsc, oneshot};
use tokio::sync::mpsc;
use crate::java::jvm::commands::JvmCommand; use crate::{events::Event, java::jvm::commands::JvmCommand};
pub struct PatchBukkitJoinHandler { pub struct PatchBukkitJoinHandler {
pub command_tx: mpsc::Sender<JvmCommand>, pub command_tx: mpsc::Sender<JvmCommand>,
@ -21,11 +20,22 @@ impl EventHandler<PlayerJoinEvent> for PatchBukkitJoinHandler {
_server: &Arc<Server>, _server: &Arc<Server>,
event: &'a mut PlayerJoinEvent, event: &'a mut PlayerJoinEvent,
) -> BoxFuture<'a, ()> { ) -> BoxFuture<'a, ()> {
Box::pin(async { let event: &mut PlayerJoinEvent = event;
// for (_plugin_name, plugin) in self.plugins.lock().unwrap().iter_mut() {}
event.join_message = let (tx, rx) = oneshot::channel();
TextComponent::text(format!("Welcome, {}!", event.player.gameprofile.name)) let sent_event = event.clone();
.color_named(NamedColor::Green); 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 {})
} }
} }

View File

@ -1,12 +1,16 @@
use std::sync::Arc; use std::sync::Arc;
use pumpkin::plugin::{Context, EventPriority}; use pumpkin::plugin::{Context, EventPriority, player::player_join::PlayerJoinEvent};
use tokio::sync::mpsc; use tokio::sync::mpsc;
use crate::{events::join::PatchBukkitJoinHandler, java::jvm::commands::JvmCommand}; use crate::{events::join::PatchBukkitJoinHandler, java::jvm::commands::JvmCommand};
pub mod join; pub mod join;
pub enum Event {
PlayerJoinEvent(PlayerJoinEvent),
}
pub async fn register_handlers(command_tx: mpsc::Sender<JvmCommand>, server: &Arc<Context>) { pub async fn register_handlers(command_tx: mpsc::Sender<JvmCommand>, server: &Arc<Context>) {
server server
.register_event( .register_event(

View File

@ -4,6 +4,8 @@ use anyhow::Result;
use j4rs::Instance; use j4rs::Instance;
use tokio::sync::oneshot; use tokio::sync::oneshot;
use crate::events::Event;
pub enum LoadPluginResult { pub enum LoadPluginResult {
SuccessfullyLoadedSpigot, SuccessfullyLoadedSpigot,
SuccessfullyLoadedPaper, SuccessfullyLoadedPaper,
@ -38,4 +40,8 @@ pub enum JvmCommand {
Shutdown { Shutdown {
respond_to: oneshot::Sender<Result<()>>, respond_to: oneshot::Sender<Result<()>>,
}, },
TriggerEvent {
event: Event,
respond_to: oneshot::Sender<Result<Event>>,
},
} }

View File

@ -2,20 +2,23 @@ use std::path::PathBuf;
use anyhow::bail; use anyhow::bail;
use j4rs::{Instance, InvocationArg, Jvm, JvmBuilder}; use j4rs::{Instance, InvocationArg, Jvm, JvmBuilder};
use pumpkin::plugin::player::player_join::PlayerJoinEvent;
use tokio::sync::{mpsc, oneshot}; use tokio::sync::{mpsc, oneshot};
use crate::{ use crate::{
events::Event,
java::{ java::{
jar::read_configs_from_jar, jar::read_configs_from_jar,
jvm::commands::{JvmCommand, LoadPluginResult}, jvm::commands::{JvmCommand, LoadPluginResult},
}, },
plugin::manager::PluginManager, plugin::{event_manager::EventManager, manager::PluginManager},
}; };
pub struct JvmWorker { pub struct JvmWorker {
command_rx: mpsc::Receiver<JvmCommand>, command_rx: mpsc::Receiver<JvmCommand>,
command_tx: mpsc::Sender<JvmCommand>, command_tx: mpsc::Sender<JvmCommand>,
pub plugin_manager: PluginManager, pub plugin_manager: PluginManager,
pub event_manager: EventManager,
jvm: Option<j4rs::Jvm>, jvm: Option<j4rs::Jvm>,
} }
@ -28,6 +31,7 @@ impl JvmWorker {
command_rx, command_rx,
command_tx, command_tx,
plugin_manager: PluginManager::new(), plugin_manager: PluginManager::new(),
event_manager: EventManager::new(),
jvm: None, jvm: None,
} }
} }
@ -117,6 +121,19 @@ impl JvmWorker {
let _ = respond_to.send(self.plugin_manager.unload_all_plugins()); let _ = respond_to.send(self.plugin_manager.unload_all_plugins());
break; 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)
}
}
}
} }
} }

View File

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

View File

@ -1 +1,2 @@
pub mod event_manager;
pub mod manager; pub mod manager;