From 20c823eb76ccd390dbca158ca95b600594695f40 Mon Sep 17 00:00:00 2001 From: Bjorn Beishline <75190918+BjornTheProgrammer@users.noreply.github.com> Date: Sat, 31 Jan 2026 04:47:13 -0700 Subject: [PATCH] Change how callbacks work --- java/patchbukkit/build.gradle.kts | 2 +- .../java/org/patchbukkit/CallbackValue.java | 24 ---- .../java/org/patchbukkit/NativeCallbacks.java | 51 ------- .../patchbukkit/bridge/NativePatchBukkit.java | 55 ++++++++ .../patchbukkit/entity/PatchBukkitPlayer.java | 4 +- rust/src/java/jvm/command_executor.rs | 2 +- rust/src/java/jvm/commands.rs | 6 +- rust/src/java/jvm/worker.rs | 132 ++---------------- rust/src/java/mod.rs | 1 + rust/src/java/native_callbacks/mod.rs | 75 ++++++++++ 10 files changed, 145 insertions(+), 207 deletions(-) delete mode 100644 java/patchbukkit/src/main/java/org/patchbukkit/CallbackValue.java delete mode 100644 java/patchbukkit/src/main/java/org/patchbukkit/NativeCallbacks.java create mode 100644 java/patchbukkit/src/main/java/org/patchbukkit/bridge/NativePatchBukkit.java create mode 100644 rust/src/java/native_callbacks/mod.rs diff --git a/java/patchbukkit/build.gradle.kts b/java/patchbukkit/build.gradle.kts index 2986be2..3887e8a 100644 --- a/java/patchbukkit/build.gradle.kts +++ b/java/patchbukkit/build.gradle.kts @@ -17,7 +17,7 @@ dependencies { java { toolchain { - languageVersion = JavaLanguageVersion.of(21) + languageVersion = JavaLanguageVersion.of(25) } } diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/CallbackValue.java b/java/patchbukkit/src/main/java/org/patchbukkit/CallbackValue.java deleted file mode 100644 index 7f61ba6..0000000 --- a/java/patchbukkit/src/main/java/org/patchbukkit/CallbackValue.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.patchbukkit; - -public class CallbackValue { - - public String callbackName; - public Object[] args; - - public CallbackValue(String callbackName, Object[] args) { - this.callbackName = callbackName; - this.args = args; - } - - public Object getArg(Integer arg) { - return args[arg]; - } - - public Object argLength() { - return args.length; - } - - public String getCallbackName() { - return callbackName; - } -} diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/NativeCallbacks.java b/java/patchbukkit/src/main/java/org/patchbukkit/NativeCallbacks.java deleted file mode 100644 index 5ece48e..0000000 --- a/java/patchbukkit/src/main/java/org/patchbukkit/NativeCallbacks.java +++ /dev/null @@ -1,51 +0,0 @@ -// No longer used currently, keeping around for reference in the short term -package org.patchbukkit; - -import java.util.UUID; - -import org.astonbitecode.j4rs.api.invocation.NativeCallbackToRustChannelSupport; -import org.bukkit.event.Listener; -import org.bukkit.plugin.Plugin; -import org.jetbrains.annotations.NotNull; - -public class NativeCallbacks extends NativeCallbackToRustChannelSupport { - - private static NativeCallbacks instance; - - public NativeCallbacks() { - NativeCallbacks.setInstance(this); - } - - private static synchronized void setInstance( - NativeCallbacks nativeCallbacks - ) { - if (instance == null) { - instance = nativeCallbacks; - } - } - - public static NativeCallbacks getInstance() { - return instance; - } - - public void registerEventCallback( - @NotNull Listener listener, - @NotNull Plugin plugin - ) { - doCallback( - new CallbackValue( - "REGISTER_EVENT_CALLBACK", - new Object[] { listener, plugin } - ) - ); - } - - public void sendMessage(@NotNull UUID uuid, String message) { - NativeCallbacks.getInstance().doCallback( - new CallbackValue( - "SEND_MESSAGE", - new Object[] { uuid.toString(), message } - ) - ); - } -} diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/bridge/NativePatchBukkit.java b/java/patchbukkit/src/main/java/org/patchbukkit/bridge/NativePatchBukkit.java new file mode 100644 index 0000000..b788126 --- /dev/null +++ b/java/patchbukkit/src/main/java/org/patchbukkit/bridge/NativePatchBukkit.java @@ -0,0 +1,55 @@ +package org.patchbukkit.bridge; + +import java.lang.foreign.*; +import java.lang.invoke.MethodHandle; +import java.util.UUID; +import org.bukkit.event.Listener; +import org.bukkit.plugin.Plugin; + +public class NativePatchBukkit { + private static final Linker LINKER = Linker.nativeLinker(); + + private static MethodHandle sendMessageNative; + private static MethodHandle registerEventNative; + + // Called from Rust during initialization + public static void initCallbacks(long sendMessageAddr, long registerEventAddr) { + // void rust_send_message(const char* uuid, const char* message) + sendMessageNative = LINKER.downcallHandle( + MemorySegment.ofAddress(sendMessageAddr), + FunctionDescriptor.ofVoid(ValueLayout.ADDRESS, ValueLayout.ADDRESS) + ); + + // void rust_register_event(void* listener, void* plugin) + registerEventNative = LINKER.downcallHandle( + MemorySegment.ofAddress(registerEventAddr), + FunctionDescriptor.ofVoid(ValueLayout.ADDRESS, ValueLayout.ADDRESS) + ); + } + + public static void sendMessage(UUID uuid, String message) { + try (Arena arena = Arena.ofConfined()) { + MemorySegment uuidStr = arena.allocateFrom(uuid.toString()); + MemorySegment msgStr = arena.allocateFrom(message); + + sendMessageNative.invokeExact(uuidStr, msgStr); + } catch (Throwable t) { + throw new RuntimeException("Failed to call native sendMessage", t); + } + } + + public static void registerEvent(Listener listener, Plugin plugin) { + // For objects, you have options: + // 1. Pass identifying info (strings, IDs) instead of object refs + // 2. Store objects in a Java-side registry and pass an ID + // 3. Use JNI NewGlobalRef from Rust side if you need to hold refs + + // Simple approach - pass plugin name, handle lookup in Rust + try (Arena arena = Arena.ofConfined()) { + MemorySegment pluginName = arena.allocateFrom(plugin.getName()); + // ... + } catch (Throwable t) { + throw new RuntimeException("Failed to register event", t); + } + } +} diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/entity/PatchBukkitPlayer.java b/java/patchbukkit/src/main/java/org/patchbukkit/entity/PatchBukkitPlayer.java index 46441db..477642b 100644 --- a/java/patchbukkit/src/main/java/org/patchbukkit/entity/PatchBukkitPlayer.java +++ b/java/patchbukkit/src/main/java/org/patchbukkit/entity/PatchBukkitPlayer.java @@ -60,8 +60,8 @@ import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Range; import org.jetbrains.annotations.Unmodifiable; import org.jetbrains.annotations.UnmodifiableView; -import org.patchbukkit.NativeCallbacks; import org.patchbukkit.PatchBukkitServer; +import org.patchbukkit.bridge.NativePatchBukkit; import com.destroystokyo.paper.ClientOption; import com.destroystokyo.paper.Title; @@ -97,7 +97,7 @@ public class PatchBukkitPlayer sender = this.getUniqueId(); } - NativeCallbacks.getInstance().sendMessage(sender, message); + NativePatchBukkit.sendMessage(uuid, message); } @Override diff --git a/rust/src/java/jvm/command_executor.rs b/rust/src/java/jvm/command_executor.rs index 8471a97..dacf99d 100644 --- a/rust/src/java/jvm/command_executor.rs +++ b/rust/src/java/jvm/command_executor.rs @@ -1,6 +1,6 @@ use std::sync::{Arc, Mutex}; -use j4rs::{Instance, InvocationArg, Jvm}; +use j4rs::Instance; use pumpkin::command::CommandExecutor; use tokio::sync::{mpsc, oneshot}; diff --git a/rust/src/java/jvm/commands.rs b/rust/src/java/jvm/commands.rs index 6041bda..b5a1a21 100644 --- a/rust/src/java/jvm/commands.rs +++ b/rust/src/java/jvm/commands.rs @@ -5,7 +5,7 @@ use std::{ use anyhow::Result; use j4rs::Instance; -use pumpkin::{command::CommandSender, plugin::Context, server::Server}; +use pumpkin::plugin::Context; use tokio::sync::{mpsc, oneshot}; use crate::{events::Event, java::jvm::command_executor::SimpleCommandSender}; @@ -25,10 +25,6 @@ pub enum JvmCommand { respond_to: oneshot::Sender>, context: Arc, }, - JavaCallback { - instance: Instance, - respond_to: oneshot::Sender>, - }, LoadPlugin { plugin_path: PathBuf, respond_to: oneshot::Sender, diff --git a/rust/src/java/jvm/worker.rs b/rust/src/java/jvm/worker.rs index 1de2bae..fc15909 100644 --- a/rust/src/java/jvm/worker.rs +++ b/rust/src/java/jvm/worker.rs @@ -1,16 +1,15 @@ -use std::{path::PathBuf, str::FromStr, sync::Arc}; +use std::{path::PathBuf, sync::Arc}; -use anyhow::bail; -use j4rs::{Instance, InvocationArg, Jvm, JvmBuilder}; -use pumpkin::{net::bedrock::play, plugin::Context, server::Server}; -use pumpkin_util::text::{TextComponent, color::NamedColor}; -use tokio::sync::{mpsc, oneshot}; +use j4rs::{InvocationArg, Jvm, JvmBuilder}; +use pumpkin::plugin::Context; +use tokio::sync::mpsc; use crate::{ events::Event, java::{ jar::read_configs_from_jar, jvm::commands::{JvmCommand, LoadPluginResult}, + native_callbacks::{init_callback_context, initialize_callbacks}, }, plugin::{event_manager::EventManager, manager::PluginManager}, }; @@ -49,17 +48,12 @@ impl JvmWorker { respond_to, context, } => { + init_callback_context(context.clone(), tokio::runtime::Handle::current()) + .unwrap(); self.context = Some(context); let result = self.initialize_jvm(&j4rs_path); let _ = respond_to.send(result); } - JvmCommand::JavaCallback { - instance, - respond_to, - } => { - let result = self.process_java_callback(instance).await; - let _ = respond_to.send(result); - } JvmCommand::LoadPlugin { plugin_path, respond_to, @@ -253,123 +247,15 @@ impl JvmWorker { log::info!("JVM worker thread exited"); } - async fn process_java_callback(&mut self, instance: Instance) -> anyhow::Result<()> { - let jvm = match self.jvm { - Some(ref jvm) => jvm, - None => bail!("JVM not initialized"), - }; - - let result = jvm.invoke(&instance, "getCallbackName", InvocationArg::empty())?; - - let callback_name: String = jvm.to_rust(result)?; - - match callback_name.as_str() { - "REGISTER_EVENT_CALLBACK" => { - let listener = jvm.cast( - &jvm.invoke(&instance, "getArg", &[&InvocationArg::try_from(0_i32)?])?, - "org.bukkit.event.Listener", - )?; - let plugin_instance = jvm.cast( - &jvm.invoke(&instance, "getArg", &[&InvocationArg::try_from(1_i32)?])?, - "org.bukkit.plugin.Plugin", - )?; - let plugin_name: String = jvm.to_rust(jvm.invoke( - &plugin_instance, - "getName", - InvocationArg::empty(), - )?)?; - - match self.plugin_manager.plugins.get_mut(&plugin_name) { - Some(rust_plugin) => rust_plugin - .listeners - .insert("listener_name".to_string(), listener), - None => todo!(), - }; - } - "SEND_MESSAGE" => { - let uuid: String = jvm.to_rust(jvm.cast( - &jvm.invoke(&instance, "getArg", &[&InvocationArg::try_from(0_i32)?])?, - "java.lang.String", - )?)?; - - let message: String = jvm.to_rust(jvm.cast( - &jvm.invoke(&instance, "getArg", &[&InvocationArg::try_from(1_i32)?])?, - "java.lang.String", - )?)?; - - if let Some(player) = self - .context - .as_ref() - .unwrap() - .server - .get_player_by_uuid(uuid::Uuid::from_str(&uuid).unwrap()) - { - player - .send_system_message(&TextComponent::from_legacy_string(&message)) - .await; - } - } - _ => log::warn!("Received unknown callback: {:?}", callback_name), - } - - Ok(()) - } - fn initialize_jvm(&mut self, j4rs_path: &PathBuf) -> anyhow::Result<()> { log::info!("Initializing JVM with path: {:?}", j4rs_path); let jvm = JvmBuilder::new().with_base_path(j4rs_path).build()?; + initialize_callbacks(&jvm)?; + setup_patchbukkit_server(&jvm)?; - let callback_instance = - jvm.create_instance("org.patchbukkit.NativeCallbacks", InvocationArg::empty())?; - - let callback_receiver = jvm.init_callback_channel(&callback_instance)?; - - let command_tx = self.command_tx.clone(); - std::thread::Builder::new() - .name("patchbukkit-jvm-callbacks".to_string()) - .spawn(move || { - let thread_runtime = tokio::runtime::Builder::new_current_thread() - .enable_all() - .build() - .unwrap(); - - thread_runtime.block_on(async { - loop { - match callback_receiver.rx().recv() { - Ok(instance) => { - let (sender, receiver) = oneshot::channel(); - if command_tx - .send(JvmCommand::JavaCallback { - instance, - respond_to: sender, - }) - .await - .is_err() - { - log::info!( - "Command channel closed, stopping callback forwarder" - ); - break; - } - - match receiver.await { - Ok(Ok(_)) => (), - Ok(Err(e)) => log::error!("{}", e), - Err(e) => log::error!("Callback failed: {}", e), - } - } - Err(e) => { - log::error!("Java callback channel closed: {}", e); - break; - } - } - } - }); - })?; - self.jvm = Some(jvm); log::info!("JVM initialized successfully"); diff --git a/rust/src/java/mod.rs b/rust/src/java/mod.rs index 6682467..f0d8b02 100644 --- a/rust/src/java/mod.rs +++ b/rust/src/java/mod.rs @@ -1,3 +1,4 @@ pub mod jar; pub mod jvm; +pub mod native_callbacks; pub mod resources; diff --git a/rust/src/java/native_callbacks/mod.rs b/rust/src/java/native_callbacks/mod.rs new file mode 100644 index 0000000..9f9e84b --- /dev/null +++ b/rust/src/java/native_callbacks/mod.rs @@ -0,0 +1,75 @@ +use std::{ + ffi::{CStr, c_char, c_void}, + sync::{Arc, OnceLock}, +}; + +use anyhow::Result; +use j4rs::{InvocationArg, Jvm}; +use pumpkin::plugin::Context; +use pumpkin_util::text::TextComponent; + +static CALLBACK_CONTEXT: OnceLock = OnceLock::new(); + +struct CallbackContext { + pub plugin_context: Arc, + pub runtime: tokio::runtime::Handle, +} + +pub fn init_callback_context( + plugin_context: Arc, + runtime: tokio::runtime::Handle, +) -> Result<()> { + let context = CallbackContext { + plugin_context, + runtime, + }; + + CALLBACK_CONTEXT + .set(context) + .map_err(|_| anyhow::anyhow!("Failed to set callback context"))?; + Ok(()) +} + +pub fn initialize_callbacks(jvm: &Jvm) -> Result<()> { + let send_message_addr = rust_send_message as *const () as i64; + let register_event_addr = rust_register_event as *const () as i64; + + jvm.invoke_static( + "org.patchbukkit.bridge.NativePatchBukkit", + "initCallbacks", + &[ + InvocationArg::try_from(send_message_addr)?.into_primitive()?, + InvocationArg::try_from(register_event_addr)?.into_primitive()?, + ], + )?; + + Ok(()) +} + +#[unsafe(no_mangle)] +pub extern "C" fn rust_send_message(uuid_ptr: *const c_char, message_ptr: *const c_char) { + // This into_owned might not seem to make any sense, but it's necessary to ensure that the string is owned by the Rust code and can move into the runtime. + let uuid_str = unsafe { CStr::from_ptr(uuid_ptr).to_string_lossy().into_owned() }; + let message = unsafe { CStr::from_ptr(message_ptr).to_string_lossy().into_owned() }; + + if let Some(ctx) = CALLBACK_CONTEXT.get() { + let uuid = uuid::Uuid::parse_str(&uuid_str).unwrap(); + + ctx.runtime.spawn(async move { + let player = ctx.plugin_context.server.get_player_by_uuid(uuid); + if let Some(player) = player { + player + .send_system_message(&TextComponent::from_legacy_string(&message)) + .await; + } + }); + } +} + +#[unsafe(no_mangle)] +pub extern "C" fn rust_register_event( + listener_ptr: *const c_void, // opaque pointer to Java object + plugin_ptr: *const c_void, +) { + // Handle event registration +}