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, command_tx: mpsc::Sender, pub plugin_manager: PluginManager, pub event_manager: EventManager, jvm: Option, context: Option>, } impl JvmWorker { pub fn new( command_tx: mpsc::Sender, command_rx: mpsc::Receiver, ) -> 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(()) }