Get sendMessage working

This commit is contained in:
Bjorn Beishline 2026-01-30 14:26:39 -07:00
parent d51f952a2f
commit 4816935568
8 changed files with 112 additions and 34 deletions

View File

@ -1,6 +1,8 @@
// No longer used currently, keeping around for reference in the short term // No longer used currently, keeping around for reference in the short term
package org.patchbukkit; package org.patchbukkit;
import java.util.UUID;
import org.astonbitecode.j4rs.api.invocation.NativeCallbackToRustChannelSupport; import org.astonbitecode.j4rs.api.invocation.NativeCallbackToRustChannelSupport;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
@ -37,4 +39,13 @@ public class NativeCallbacks extends NativeCallbackToRustChannelSupport {
) )
); );
} }
public void sendMessage(@NotNull UUID uuid, String message) {
NativeCallbacks.getInstance().doCallback(
new CallbackValue(
"SEND_MESSAGE",
new Object[] { uuid.toString(), message }
)
);
}
} }

View File

@ -103,27 +103,23 @@ public class PatchBukkitEntity implements Entity {
} }
@Override @Override
public void sendMessage(@NotNull String message) { public void sendMessage(String message) {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'sendMessage'");
} }
@Override @Override
public void sendMessage(@NotNull String... messages) { public void sendMessage(String... messages) {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'sendMessage'");
} }
@Override @Override
public void sendMessage(@Nullable UUID sender, @NotNull String message) { public void sendMessage(UUID sender, String message) {
// TODO Auto-generated method stub this.sendMessage(message); // Most entities don't know about senders
throw new UnsupportedOperationException("Unimplemented method 'sendMessage'");
} }
@Override @Override
public void sendMessage(@Nullable UUID sender, @NotNull String... messages) { public void sendMessage(UUID sender, String... messages) {
// TODO Auto-generated method stub this.sendMessage(messages); // Most entities don't know about senders
throw new UnsupportedOperationException("Unimplemented method 'sendMessage'");
} }
@Override @Override

View File

@ -60,11 +60,13 @@ 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 com.destroystokyo.paper.ClientOption; import com.destroystokyo.paper.ClientOption;
import com.destroystokyo.paper.Title; import com.destroystokyo.paper.Title;
import com.destroystokyo.paper.profile.PlayerProfile; import com.destroystokyo.paper.profile.PlayerProfile;
import com.google.common.base.Preconditions;
import io.papermc.paper.connection.PlayerGameConnection; import io.papermc.paper.connection.PlayerGameConnection;
import io.papermc.paper.entity.LookAnchor; import io.papermc.paper.entity.LookAnchor;
@ -84,6 +86,44 @@ public class PatchBukkitPlayer
super(uuid, name); super(uuid, name);
} }
@Override
public void sendRawMessage(String message) {
this.sendRawMessage(null, message);
}
@Override
public void sendRawMessage(UUID sender, String message) {
if (sender == null) {
sender = this.getUniqueId();
}
NativeCallbacks.getInstance().sendMessage(sender, message);
}
@Override
public void sendMessage(String message) {
this.sendRawMessage(message);
}
@Override
public void sendMessage(String... messages) {
for (String message : messages) {
this.sendMessage(message);
}
}
@Override
public void sendMessage(UUID sender, String message) {
this.sendRawMessage(sender, message);
}
@Override
public void sendMessage(UUID sender, String... messages) {
for (String message : messages) {
this.sendMessage(sender, message);
}
}
@Override @Override
public Player.Spigot spigot() { public Player.Spigot spigot() {
throw new UnsupportedOperationException("Unimplemented method 'spigot'"); throw new UnsupportedOperationException("Unimplemented method 'spigot'");
@ -119,12 +159,6 @@ public class PatchBukkitPlayer
throw new UnsupportedOperationException("Unimplemented method 'abandonConversation'"); throw new UnsupportedOperationException("Unimplemented method 'abandonConversation'");
} }
@Override
public void sendRawMessage(@Nullable UUID sender, @NotNull String message) {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'sendRawMessage'");
}
@Override @Override
public boolean isOnline() { public boolean isOnline() {
// TODO Auto-generated method stub // TODO Auto-generated method stub
@ -517,12 +551,6 @@ public class PatchBukkitPlayer
throw new UnsupportedOperationException("Unimplemented method 'transfer'"); throw new UnsupportedOperationException("Unimplemented method 'transfer'");
} }
@Override
public void sendRawMessage(String message) {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'sendRawMessage'");
}
@Override @Override
public void kickPlayer(@org.jspecify.annotations.Nullable String message) { public void kickPlayer(@org.jspecify.annotations.Nullable String message) {
// TODO Auto-generated method stub // TODO Auto-generated method stub

1
rust/Cargo.lock generated
View File

@ -1701,6 +1701,7 @@ dependencies = [
"serde", "serde",
"serde-saphyr", "serde-saphyr",
"tokio", "tokio",
"uuid",
"walkdir", "walkdir",
"zip", "zip",
] ]

View File

@ -25,6 +25,7 @@ serde-saphyr = "0.0.16"
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
anyhow = "1.0" anyhow = "1.0"
uuid = "1.20.0"
[build-dependencies] [build-dependencies]
j4rs = { version = "0.24", artifact = "cdylib", lib = true } j4rs = { version = "0.24", artifact = "cdylib", lib = true }

View File

@ -23,6 +23,7 @@ pub enum JvmCommand {
Initialize { Initialize {
j4rs_path: PathBuf, j4rs_path: PathBuf,
respond_to: oneshot::Sender<Result<()>>, respond_to: oneshot::Sender<Result<()>>,
context: Arc<Context>,
}, },
JavaCallback { JavaCallback {
instance: Instance, instance: Instance,

View File

@ -1,8 +1,9 @@
use std::{path::PathBuf, sync::Arc}; use std::{path::PathBuf, str::FromStr, sync::Arc};
use anyhow::bail; use anyhow::bail;
use j4rs::{Instance, InvocationArg, Jvm, JvmBuilder}; use j4rs::{Instance, InvocationArg, Jvm, JvmBuilder};
use pumpkin::{net::bedrock::play, server::Server}; use pumpkin::{net::bedrock::play, plugin::Context, server::Server};
use pumpkin_util::text::TextComponent;
use tokio::sync::{mpsc, oneshot}; use tokio::sync::{mpsc, oneshot};
use crate::{ use crate::{
@ -20,6 +21,7 @@ pub struct JvmWorker {
pub plugin_manager: PluginManager, pub plugin_manager: PluginManager,
pub event_manager: EventManager, pub event_manager: EventManager,
jvm: Option<j4rs::Jvm>, jvm: Option<j4rs::Jvm>,
context: Option<Arc<Context>>,
} }
impl JvmWorker { impl JvmWorker {
@ -33,18 +35,21 @@ impl JvmWorker {
plugin_manager: PluginManager::new(), plugin_manager: PluginManager::new(),
event_manager: EventManager::new(), event_manager: EventManager::new(),
jvm: None, jvm: None,
context: None,
} }
} }
pub fn attach_thread(mut self) { pub async fn attach_thread(mut self) {
log::info!("JVM worker thread started"); log::info!("JVM worker thread started");
while let Some(command) = self.command_rx.blocking_recv() { while let Some(command) = self.command_rx.recv().await {
match command { match command {
JvmCommand::Initialize { JvmCommand::Initialize {
j4rs_path, j4rs_path,
respond_to, respond_to,
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);
} }
@ -52,7 +57,7 @@ impl JvmWorker {
instance, instance,
respond_to, respond_to,
} => { } => {
let result = self.process_java_callback(instance); let result = self.process_java_callback(instance).await;
let _ = respond_to.send(result); let _ = respond_to.send(result);
} }
JvmCommand::LoadPlugin { JvmCommand::LoadPlugin {
@ -223,7 +228,7 @@ impl JvmWorker {
log::info!("JVM worker thread exited"); log::info!("JVM worker thread exited");
} }
fn process_java_callback(&mut self, instance: Instance) -> anyhow::Result<()> { async fn process_java_callback(&mut self, instance: Instance) -> anyhow::Result<()> {
let jvm = match self.jvm { let jvm = match self.jvm {
Some(ref jvm) => jvm, Some(ref jvm) => jvm,
None => bail!("JVM not initialized"), None => bail!("JVM not initialized"),
@ -256,6 +261,29 @@ impl JvmWorker {
None => todo!(), 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::text(message))
.await;
}
}
_ => log::warn!("Received unknown callback: {:?}", callback_name), _ => log::warn!("Received unknown callback: {:?}", callback_name),
} }
@ -302,8 +330,10 @@ impl JvmWorker {
break; break;
} }
if let Err(e) = receiver.await { match receiver.await {
log::error!("Callback failed: {}", e); Ok(Ok(_)) => (),
Ok(Err(e)) => log::error!("{}", e),
Err(e) => log::error!("Callback failed: {}", e),
} }
} }
Err(e) => { Err(e) => {

View File

@ -14,7 +14,10 @@ use java::{
jar::discover_jar_files, jar::discover_jar_files,
resources::{cleanup_stale_files, sync_embedded_resources}, resources::{cleanup_stale_files, sync_embedded_resources},
}; };
use tokio::sync::{mpsc, oneshot}; use tokio::sync::{
mpsc::{self, Receiver, Sender},
oneshot,
};
use crate::{ use crate::{
events::register_handlers, events::register_handlers,
@ -108,6 +111,7 @@ async fn on_load_inner(plugin: &mut PatchBukkitPlugin, server: Arc<Context>) ->
.send(JvmCommand::Initialize { .send(JvmCommand::Initialize {
j4rs_path: dirs.j4rs, j4rs_path: dirs.j4rs,
respond_to: tx, respond_to: tx,
context: server.clone(),
}) })
.await .await
.map_err(|e| format!("Failed to send command to initialize J4RS: {}", e))?; .map_err(|e| format!("Failed to send command to initialize J4RS: {}", e))?;
@ -197,10 +201,16 @@ impl PatchBukkitPlugin {
pub fn new() -> Self { pub fn new() -> Self {
let (tx, rx) = mpsc::channel(100); let (tx, rx) = mpsc::channel(100);
let command_tx = tx.clone(); let command_tx = tx.clone();
#[tokio::main]
pub async fn jvm_thread_task(command_tx: Sender<JvmCommand>, rx: Receiver<JvmCommand>) {
JvmWorker::new(command_tx, rx).attach_thread().await;
}
std::thread::Builder::new() std::thread::Builder::new()
.name("patchbukkit-jvm-worker".to_string()) .name("patchbukkit-jvm-worker".to_string())
.spawn(move || { .spawn(move || {
JvmWorker::new(command_tx.clone(), rx).attach_thread(); jvm_thread_task(command_tx.clone(), rx);
}) })
.unwrap(); .unwrap();
PatchBukkitPlugin { command_tx: tx } PatchBukkitPlugin { command_tx: tx }