Change how callbacks work
This commit is contained in:
parent
deb1710c3e
commit
20c823eb76
@ -17,7 +17,7 @@ dependencies {
|
|||||||
|
|
||||||
java {
|
java {
|
||||||
toolchain {
|
toolchain {
|
||||||
languageVersion = JavaLanguageVersion.of(21)
|
languageVersion = JavaLanguageVersion.of(25)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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 }
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -60,8 +60,8 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
import org.jetbrains.annotations.Range;
|
import org.jetbrains.annotations.Range;
|
||||||
import org.jetbrains.annotations.Unmodifiable;
|
import org.jetbrains.annotations.Unmodifiable;
|
||||||
import org.jetbrains.annotations.UnmodifiableView;
|
import org.jetbrains.annotations.UnmodifiableView;
|
||||||
import org.patchbukkit.NativeCallbacks;
|
|
||||||
import org.patchbukkit.PatchBukkitServer;
|
import org.patchbukkit.PatchBukkitServer;
|
||||||
|
import org.patchbukkit.bridge.NativePatchBukkit;
|
||||||
|
|
||||||
import com.destroystokyo.paper.ClientOption;
|
import com.destroystokyo.paper.ClientOption;
|
||||||
import com.destroystokyo.paper.Title;
|
import com.destroystokyo.paper.Title;
|
||||||
@ -97,7 +97,7 @@ public class PatchBukkitPlayer
|
|||||||
sender = this.getUniqueId();
|
sender = this.getUniqueId();
|
||||||
}
|
}
|
||||||
|
|
||||||
NativeCallbacks.getInstance().sendMessage(sender, message);
|
NativePatchBukkit.sendMessage(uuid, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use j4rs::{Instance, InvocationArg, Jvm};
|
use j4rs::Instance;
|
||||||
use pumpkin::command::CommandExecutor;
|
use pumpkin::command::CommandExecutor;
|
||||||
use tokio::sync::{mpsc, oneshot};
|
use tokio::sync::{mpsc, oneshot};
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,7 @@ use std::{
|
|||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use j4rs::Instance;
|
use j4rs::Instance;
|
||||||
use pumpkin::{command::CommandSender, plugin::Context, server::Server};
|
use pumpkin::plugin::Context;
|
||||||
use tokio::sync::{mpsc, oneshot};
|
use tokio::sync::{mpsc, oneshot};
|
||||||
|
|
||||||
use crate::{events::Event, java::jvm::command_executor::SimpleCommandSender};
|
use crate::{events::Event, java::jvm::command_executor::SimpleCommandSender};
|
||||||
@ -25,10 +25,6 @@ pub enum JvmCommand {
|
|||||||
respond_to: oneshot::Sender<Result<()>>,
|
respond_to: oneshot::Sender<Result<()>>,
|
||||||
context: Arc<Context>,
|
context: Arc<Context>,
|
||||||
},
|
},
|
||||||
JavaCallback {
|
|
||||||
instance: Instance,
|
|
||||||
respond_to: oneshot::Sender<Result<()>>,
|
|
||||||
},
|
|
||||||
LoadPlugin {
|
LoadPlugin {
|
||||||
plugin_path: PathBuf,
|
plugin_path: PathBuf,
|
||||||
respond_to: oneshot::Sender<LoadPluginResult>,
|
respond_to: oneshot::Sender<LoadPluginResult>,
|
||||||
|
|||||||
@ -1,16 +1,15 @@
|
|||||||
use std::{path::PathBuf, str::FromStr, sync::Arc};
|
use std::{path::PathBuf, sync::Arc};
|
||||||
|
|
||||||
use anyhow::bail;
|
use j4rs::{InvocationArg, Jvm, JvmBuilder};
|
||||||
use j4rs::{Instance, InvocationArg, Jvm, JvmBuilder};
|
use pumpkin::plugin::Context;
|
||||||
use pumpkin::{net::bedrock::play, plugin::Context, server::Server};
|
use tokio::sync::mpsc;
|
||||||
use pumpkin_util::text::{TextComponent, color::NamedColor};
|
|
||||||
use tokio::sync::{mpsc, oneshot};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
events::Event,
|
events::Event,
|
||||||
java::{
|
java::{
|
||||||
jar::read_configs_from_jar,
|
jar::read_configs_from_jar,
|
||||||
jvm::commands::{JvmCommand, LoadPluginResult},
|
jvm::commands::{JvmCommand, LoadPluginResult},
|
||||||
|
native_callbacks::{init_callback_context, initialize_callbacks},
|
||||||
},
|
},
|
||||||
plugin::{event_manager::EventManager, manager::PluginManager},
|
plugin::{event_manager::EventManager, manager::PluginManager},
|
||||||
};
|
};
|
||||||
@ -49,17 +48,12 @@ impl JvmWorker {
|
|||||||
respond_to,
|
respond_to,
|
||||||
context,
|
context,
|
||||||
} => {
|
} => {
|
||||||
|
init_callback_context(context.clone(), tokio::runtime::Handle::current())
|
||||||
|
.unwrap();
|
||||||
self.context = Some(context);
|
self.context = Some(context);
|
||||||
let result = self.initialize_jvm(&j4rs_path);
|
let result = self.initialize_jvm(&j4rs_path);
|
||||||
let _ = respond_to.send(result);
|
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 {
|
JvmCommand::LoadPlugin {
|
||||||
plugin_path,
|
plugin_path,
|
||||||
respond_to,
|
respond_to,
|
||||||
@ -253,123 +247,15 @@ impl JvmWorker {
|
|||||||
log::info!("JVM worker thread exited");
|
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<()> {
|
fn initialize_jvm(&mut self, j4rs_path: &PathBuf) -> anyhow::Result<()> {
|
||||||
log::info!("Initializing JVM with path: {:?}", j4rs_path);
|
log::info!("Initializing JVM with path: {:?}", j4rs_path);
|
||||||
|
|
||||||
let jvm = JvmBuilder::new().with_base_path(j4rs_path).build()?;
|
let jvm = JvmBuilder::new().with_base_path(j4rs_path).build()?;
|
||||||
|
|
||||||
|
initialize_callbacks(&jvm)?;
|
||||||
|
|
||||||
setup_patchbukkit_server(&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);
|
self.jvm = Some(jvm);
|
||||||
|
|
||||||
log::info!("JVM initialized successfully");
|
log::info!("JVM initialized successfully");
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
pub mod jar;
|
pub mod jar;
|
||||||
pub mod jvm;
|
pub mod jvm;
|
||||||
|
pub mod native_callbacks;
|
||||||
pub mod resources;
|
pub mod resources;
|
||||||
|
|||||||
75
rust/src/java/native_callbacks/mod.rs
Normal file
75
rust/src/java/native_callbacks/mod.rs
Normal file
@ -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<CallbackContext> = OnceLock::new();
|
||||||
|
|
||||||
|
struct CallbackContext {
|
||||||
|
pub plugin_context: Arc<Context>,
|
||||||
|
pub runtime: tokio::runtime::Handle,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init_callback_context(
|
||||||
|
plugin_context: Arc<Context>,
|
||||||
|
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
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user