Change how callbacks work
This commit is contained in:
parent
deb1710c3e
commit
20c823eb76
@ -17,7 +17,7 @@ dependencies {
|
||||
|
||||
java {
|
||||
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.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
|
||||
|
||||
@ -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};
|
||||
|
||||
|
||||
@ -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<Result<()>>,
|
||||
context: Arc<Context>,
|
||||
},
|
||||
JavaCallback {
|
||||
instance: Instance,
|
||||
respond_to: oneshot::Sender<Result<()>>,
|
||||
},
|
||||
LoadPlugin {
|
||||
plugin_path: PathBuf,
|
||||
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::{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");
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
pub mod jar;
|
||||
pub mod jvm;
|
||||
pub mod native_callbacks;
|
||||
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