367 lines
14 KiB
Rust
367 lines
14 KiB
Rust
use std::{path::PathBuf, str::FromStr, 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;
|
|
use tokio::sync::{mpsc, oneshot};
|
|
|
|
use crate::{
|
|
events::Event,
|
|
java::{
|
|
jar::read_configs_from_jar,
|
|
jvm::commands::{JvmCommand, LoadPluginResult},
|
|
},
|
|
plugin::{event_manager::EventManager, manager::PluginManager},
|
|
};
|
|
|
|
pub struct JvmWorker {
|
|
command_rx: mpsc::Receiver<JvmCommand>,
|
|
command_tx: mpsc::Sender<JvmCommand>,
|
|
pub plugin_manager: PluginManager,
|
|
pub event_manager: EventManager,
|
|
jvm: Option<j4rs::Jvm>,
|
|
context: Option<Arc<Context>>,
|
|
}
|
|
|
|
impl JvmWorker {
|
|
pub fn new(
|
|
command_tx: mpsc::Sender<JvmCommand>,
|
|
command_rx: mpsc::Receiver<JvmCommand>,
|
|
) -> Self {
|
|
Self {
|
|
command_rx,
|
|
command_tx,
|
|
plugin_manager: PluginManager::new(),
|
|
event_manager: EventManager::new(),
|
|
jvm: None,
|
|
context: None,
|
|
}
|
|
}
|
|
|
|
pub async fn attach_thread(mut self) {
|
|
log::info!("JVM worker thread started");
|
|
|
|
while let Some(command) = self.command_rx.recv().await {
|
|
match command {
|
|
JvmCommand::Initialize {
|
|
j4rs_path,
|
|
respond_to,
|
|
context,
|
|
} => {
|
|
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,
|
|
} => {
|
|
let _ = match read_configs_from_jar(&plugin_path) {
|
|
Ok(configs) => match configs {
|
|
(Some(paper_plugin_config), spigot @ _) => {
|
|
match self.plugin_manager.load_paper_plugin(
|
|
&plugin_path,
|
|
&paper_plugin_config,
|
|
&spigot,
|
|
) {
|
|
Ok(_) => {
|
|
respond_to.send(LoadPluginResult::SuccessfullyLoadedPaper)
|
|
}
|
|
Err(err) => respond_to
|
|
.send(LoadPluginResult::FailedToLoadPaperPlugin(err)),
|
|
}
|
|
}
|
|
(None, Some(spigot)) => {
|
|
match self
|
|
.plugin_manager
|
|
.load_spigot_plugin(&plugin_path, &spigot)
|
|
{
|
|
Ok(_) => {
|
|
respond_to.send(LoadPluginResult::SuccessfullyLoadedSpigot)
|
|
}
|
|
Err(err) => respond_to
|
|
.send(LoadPluginResult::FailedToLoadSpigotPlugin(err)),
|
|
}
|
|
}
|
|
(None, None) => respond_to.send(LoadPluginResult::NoConfigurationFile),
|
|
},
|
|
Err(err) => {
|
|
respond_to.send(LoadPluginResult::FailedToReadConfigurationFile(err))
|
|
}
|
|
};
|
|
}
|
|
JvmCommand::InstantiateAllPlugins {
|
|
respond_to,
|
|
server,
|
|
command_tx,
|
|
} => {
|
|
let jvm = match self.jvm {
|
|
Some(ref jvm) => jvm,
|
|
None => &Jvm::attach_thread().unwrap(),
|
|
};
|
|
|
|
let _ = respond_to.send(
|
|
self.plugin_manager
|
|
.instantiate_all_plugins(jvm, &server, command_tx),
|
|
);
|
|
}
|
|
JvmCommand::EnableAllPlugins { respond_to } => {
|
|
let jvm = match self.jvm {
|
|
Some(ref jvm) => jvm,
|
|
None => &Jvm::attach_thread().unwrap(),
|
|
};
|
|
|
|
let _ = respond_to.send(self.plugin_manager.enable_all_plugins(jvm));
|
|
}
|
|
JvmCommand::DisableAllPlugins { respond_to } => {
|
|
let jvm = match self.jvm {
|
|
Some(ref jvm) => jvm,
|
|
None => &Jvm::attach_thread().unwrap(),
|
|
};
|
|
|
|
let _ = respond_to.send(self.plugin_manager.disable_all_plugins(jvm));
|
|
}
|
|
JvmCommand::Shutdown { respond_to } => {
|
|
let _ = respond_to.send(self.plugin_manager.unload_all_plugins());
|
|
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 server_instance = jvm
|
|
.invoke_static(
|
|
"org.bukkit.Bukkit",
|
|
"getServer",
|
|
InvocationArg::empty(),
|
|
)
|
|
.map_err(|e| format!("Failed to get server: {}", e))
|
|
.unwrap();
|
|
|
|
// let cloned_server_instance =
|
|
// jvm.clone_instance(&server_instance).unwrap();
|
|
|
|
// let j_entity = jvm
|
|
// .create_instance(
|
|
// "org.patchbukkit.entity.PatchBukkitEntity",
|
|
// &[
|
|
// InvocationArg::from(cloned_server_instance),
|
|
// // InvocationArg::from(InvocationArg::empty()), // Placeholder for the actual NMS entity
|
|
// ],
|
|
// )
|
|
// .unwrap();
|
|
// let cloned_server_instance =
|
|
// jvm.clone_instance(&server_instance).unwrap();
|
|
|
|
let player = player_join_event.player;
|
|
|
|
let j_uuid = jvm
|
|
.invoke_static(
|
|
"java.util.UUID",
|
|
"fromString",
|
|
&[InvocationArg::try_from(player.gameprofile.id.to_string())
|
|
.unwrap()],
|
|
)
|
|
.map_err(|e| format!("Failed to create Java UUID: {}", e))
|
|
.unwrap();
|
|
|
|
let j_player = jvm
|
|
.create_instance(
|
|
"org.patchbukkit.entity.PatchBukkitPlayer",
|
|
&[
|
|
InvocationArg::from(j_uuid),
|
|
InvocationArg::try_from(player.gameprofile.name.clone())
|
|
.unwrap(),
|
|
],
|
|
)
|
|
.map_err(|e| format!("Failed to create player instance: {}", e))
|
|
.unwrap();
|
|
let patch_server = jvm
|
|
.cast(&server_instance, "org.patchbukkit.PatchBukkitServer")
|
|
.unwrap();
|
|
|
|
jvm.invoke(
|
|
&patch_server,
|
|
"registerPlayer",
|
|
&[InvocationArg::from(j_player)],
|
|
)
|
|
.unwrap();
|
|
// self.event_manager.call_event(&jvm, event)
|
|
}
|
|
}
|
|
}
|
|
JvmCommand::TriggerCommand {
|
|
cmd_name,
|
|
command_sender,
|
|
respond_to,
|
|
command,
|
|
} => {
|
|
let jvm = match self.jvm {
|
|
Some(ref jvm) => jvm,
|
|
None => &Jvm::attach_thread().unwrap(),
|
|
};
|
|
self.plugin_manager
|
|
.trigger_command(
|
|
jvm,
|
|
&cmd_name,
|
|
command,
|
|
command_sender,
|
|
vec![cmd_name.clone()],
|
|
)
|
|
.unwrap();
|
|
}
|
|
}
|
|
}
|
|
|
|
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::text(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()?;
|
|
|
|
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");
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
pub fn setup_patchbukkit_server(jvm: &Jvm) -> anyhow::Result<()> {
|
|
let patchbukkit_server =
|
|
jvm.create_instance("org.patchbukkit.PatchBukkitServer", InvocationArg::empty())?;
|
|
|
|
jvm.invoke_static(
|
|
"org.bukkit.Bukkit",
|
|
"setServer",
|
|
&[InvocationArg::from(patchbukkit_server)],
|
|
)?;
|
|
|
|
Ok(())
|
|
}
|