From a31da8c568260fbca868040acfa578d209fd4d7a Mon Sep 17 00:00:00 2001 From: Bjorn Beishline <75190918+BjornTheProgrammer@users.noreply.github.com> Date: Tue, 3 Feb 2026 14:03:03 -0700 Subject: [PATCH] Setup rust passing of ffm --- .gitignore | 1 + rust/Cargo.toml | 2 + rust/{build.rs => build/java.rs} | 82 +------------- rust/build/main.rs | 17 +++ rust/build/protobufs.rs | 113 ++++++++++++++++++++ rust/src/java/native_callbacks/abilities.rs | 15 ++- rust/src/java/native_callbacks/mod.rs | 12 ++- rust/src/proto/mod.rs | 6 +- 8 files changed, 151 insertions(+), 97 deletions(-) rename rust/{build.rs => build/java.rs} (72%) create mode 100644 rust/build/main.rs create mode 100644 rust/build/protobufs.rs diff --git a/.gitignore b/.gitignore index 3c61b4f..3249b3a 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,7 @@ build/ java/build/ java/patchbukkit/build/ java/patchbukkit/bin/ +!rust/build/ # Examples examples/*/bin diff --git a/rust/Cargo.toml b/rust/Cargo.toml index beb66bf..65178bf 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -3,6 +3,8 @@ name = "patchbukkit" version = "0.1.0" edition = "2024" +build = "build/main.rs" + [lib] crate-type = ["cdylib"] diff --git a/rust/build.rs b/rust/build/java.rs similarity index 72% rename from rust/build.rs rename to rust/build/java.rs index 74b86f2..8ce78ad 100644 --- a/rust/build.rs +++ b/rust/build/java.rs @@ -1,75 +1,8 @@ use std::{collections::HashSet, fs, path::PathBuf}; -use glob::glob; use j4rs::{JvmBuilder, LocalJarArtifact, MavenArtifact, MavenArtifactRepo, MavenSettings}; -use prost_build::{Config, Service, ServiceGenerator}; - -struct FfiServiceGenerator; - -impl ServiceGenerator for FfiServiceGenerator { - fn generate(&mut self, service: Service, buf: &mut String) { - for method in &service.methods { - let fn_name = format!("ffi_{}_{}", to_snake_case(&service.name), &method.name); - let input_type = &method.input_type; - let output_type = &method.output_type; - - buf.push_str(&format!( - r#" -#[no_mangle] -pub unsafe extern "C" fn {fn_name}( - input_ptr: *const u8, - input_len: usize, - output_len: *mut usize, -) -> *mut u8 {{ - use prost::Message; - - let input_slice = std::slice::from_raw_parts(input_ptr, input_len); - let request = match {input_type}::decode(input_slice) {{ - Ok(req) => req, - Err(_) => {{ - *output_len = 0; - return std::ptr::null_mut(); - }} - }}; - - let response = {fn_name}_impl(request); - - let encoded = response.encode_to_vec(); - *output_len = encoded.len(); - - let ptr = encoded.as_ptr() as *mut u8; - std::mem::forget(encoded); - ptr -}} -"# - )); - } - } -} - -fn to_snake_case(s: &str) -> String { - let mut result = String::new(); - for (i, c) in s.chars().enumerate() { - if c.is_uppercase() { - if i > 0 { - result.push('_'); - } - result.push(c.to_ascii_lowercase()); - } else { - result.push(c); - } - } - result -} - -#[expect(clippy::too_many_lines)] -fn main() -> Result<(), Box> { - println!("cargo::rerun-if-changed=rust/src/build.rs"); - println!("cargo::rerun-if-changed=java/build/libs/"); - env_logger::init(); - - let base = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()); +pub fn setup_java(base: PathBuf) { let resources = base.join("resources"); let jassets = resources.join("jassets"); let deps = resources.join("deps"); @@ -84,19 +17,6 @@ fn main() -> Result<(), Box> { .join("libs") .join("patchbukkit.jar"); - let mut proto_path = base.clone(); - proto_path.pop(); - let proto_path = proto_path.join("proto"); - - let paths = glob(&format!("{}/**/*.proto", proto_path.display())) - .expect("Failed to read glob pattern") - .filter_map(|p| p.ok()) - .collect::>(); - - let mut config = Config::new(); - config.service_generator(Box::new(FfiServiceGenerator)); - config.compile_protos(paths.as_slice(), &[proto_path])?; - let dependencies = [ ("com.google.guava", "guava", "33.3.1-jre"), ( diff --git a/rust/build/main.rs b/rust/build/main.rs new file mode 100644 index 0000000..09f8db3 --- /dev/null +++ b/rust/build/main.rs @@ -0,0 +1,17 @@ +use crate::{java::setup_java, protobufs::setup_protobufs}; +use std::{error::Error, path::PathBuf}; + +pub mod java; +pub mod protobufs; + +fn main() -> Result<(), Box> { + println!("cargo::rerun-if-changed=rust/src/build.rs"); + println!("cargo::rerun-if-changed=java/build/libs/"); + env_logger::init(); + + let base = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()); + + setup_protobufs(base.clone()); + setup_java(base); + Ok(()) +} diff --git a/rust/build/protobufs.rs b/rust/build/protobufs.rs new file mode 100644 index 0000000..9d7f257 --- /dev/null +++ b/rust/build/protobufs.rs @@ -0,0 +1,113 @@ +use glob::glob; +use prost_build::{Config, Service, ServiceGenerator}; +use std::path::PathBuf; + +pub struct FfiServiceGenerator { + impl_module: String, + proto_module: String, +} + +impl FfiServiceGenerator { + pub fn new(impl_module: impl Into, proto_module: impl Into) -> Self { + Self { + impl_module: impl_module.into(), + proto_module: proto_module.into(), + } + } + + fn proto_type_to_rust(&self, proto_type: &str) -> String { + let trimmed = proto_type.trim_start_matches('.'); + + if let Some(type_name) = trimmed.strip_prefix("google.protobuf.") { + return format!("::prost_types::{type_name}"); + } + + let parts: Vec<&str> = trimmed.split('.').collect(); + let (modules, type_name) = parts.split_at(parts.len() - 1); + + let module_path = modules + .iter() + .map(|s| to_snake_case(s)) + .collect::>() + .join("::"); + let type_name = to_pascal_case(type_name[0]); + + format!("{}::{module_path}::{type_name}", self.proto_module) + } +} + +impl ServiceGenerator for FfiServiceGenerator { + fn generate(&mut self, service: Service, buf: &mut String) { + for method in &service.methods { + let fn_name = format!("ffi_{}_{}", to_snake_case(&service.name), &method.name); + let input_type = self.proto_type_to_rust(&method.input_proto_type); + + buf.push_str(&format!( + r#" +#[unsafe(no_mangle)] +pub unsafe extern "C" fn {fn_name}( + input_ptr: *const u8, + input_len: usize, + output_len: *mut usize, +) -> *mut u8 {{ + use prost::Message; + let input_slice = unsafe {{ std::slice::from_raw_parts(input_ptr, input_len) }}; + let Ok(request) = {input_type}::decode(input_slice) else {{ + unsafe {{ *output_len = 0 }}; + return std::ptr::null_mut(); + }}; + let Some(response) = {0}::{fn_name}_impl(request) else {{ + unsafe {{ *output_len = 0 }}; + return std::ptr::null_mut(); + }}; + let encoded = response.encode_to_vec(); + unsafe {{ *output_len = encoded.len() }}; + let ptr = encoded.as_ptr() as *mut u8; + std::mem::forget(encoded); + ptr +}} +"#, + self.impl_module + )); + } + } +} + +fn to_pascal_case(s: &str) -> String { + if s.chars().all(|c| c.is_uppercase() || c.is_numeric()) { + let mut c = s.chars(); + c.next() + .map(|f| f.to_string() + &c.as_str().to_lowercase()) + .unwrap_or_default() + } else { + s.to_string() + } +} + +fn to_snake_case(s: &str) -> String { + s.chars() + .enumerate() + .flat_map(|(i, c)| { + if c.is_uppercase() && i > 0 { + vec!['_', c.to_ascii_lowercase()] + } else { + vec![c.to_ascii_lowercase()] + } + }) + .collect() +} + +pub fn setup_protobufs(base: PathBuf) { + let proto_path = base.parent().unwrap().join("proto"); + let paths: Vec<_> = glob(&format!("{}/**/*.proto", proto_path.display())) + .expect("Failed to read glob pattern") + .filter_map(Result::ok) + .collect(); + + let mut config = Config::new(); + config.service_generator(Box::new(FfiServiceGenerator::new( + "crate::java::native_callbacks", + "crate::proto", + ))); + config.compile_protos(&paths, &[proto_path]).unwrap(); +} diff --git a/rust/src/java/native_callbacks/abilities.rs b/rust/src/java/native_callbacks/abilities.rs index 50cf135..4f42396 100644 --- a/rust/src/java/native_callbacks/abilities.rs +++ b/rust/src/java/native_callbacks/abilities.rs @@ -1,11 +1,7 @@ -use prost::Message; - use crate::java::native_callbacks::CALLBACK_CONTEXT; use crate::proto::patchbukkit::common::{Abilities, SetAbilitiesRequest, Uuid}; -include!(concat!(env!("OUT_DIR"), "/patchbukkit.bridge.rs")); - -fn ffi_native_bridge_get_abilities_impl(request: Uuid) -> Option { +pub fn ffi_native_bridge_get_abilities_impl(request: Uuid) -> Option { let ctx = CALLBACK_CONTEXT.get()?; let player_uuid = uuid::Uuid::parse_str(&request.value).unwrap(); let mut abilities = Abilities::default(); @@ -26,11 +22,12 @@ fn ffi_native_bridge_get_abilities_impl(request: Uuid) -> Option { return Some(abilities); } -fn ffi_native_bridge_set_abilities_impl(request: SetAbilitiesRequest) -> Option { +pub fn ffi_native_bridge_set_abilities_impl(request: SetAbilitiesRequest) -> Option { let ctx = CALLBACK_CONTEXT.get()?; - let player_uuid = uuid::Uuid::parse_str(&request.uuid?.value).unwrap(); - let abilities = Abilities::default(); + let player_uuid = uuid::Uuid::parse_str(&request.uuid?.value).ok()?; + let abilities = request.abilities?; let player = ctx.plugin_context.server.get_player_by_uuid(player_uuid)?; + let mut pumpkin_abilities = tokio::task::block_in_place(|| { ctx.runtime .block_on(async { player.abilities.lock().await }) @@ -49,5 +46,5 @@ fn ffi_native_bridge_set_abilities_impl(request: SetAbilitiesRequest) -> Option< player.send_abilities_update().await; }); - return Some(true); + Some(true) } diff --git a/rust/src/java/native_callbacks/mod.rs b/rust/src/java/native_callbacks/mod.rs index ecb97b2..3008b03 100644 --- a/rust/src/java/native_callbacks/mod.rs +++ b/rust/src/java/native_callbacks/mod.rs @@ -5,9 +5,11 @@ use j4rs::{InvocationArg, Jvm}; use pumpkin::plugin::Context; use tokio::sync::mpsc; -use crate::java::jvm::commands::JvmCommand; +use crate::{java::jvm::commands::JvmCommand, proto}; + +mod abilities; +pub use abilities::*; -pub mod abilities; pub mod events; pub mod location; pub mod memory; @@ -66,8 +68,10 @@ pub fn initialize_callbacks(jvm: &Jvm) -> Result<()> { let send_message_addr = message::rust_send_message as *const () as i64; let register_event_addr = events::rust_register_event as *const () as i64; let call_event_addr = events::rust_call_event as *const () as i64; - let get_abilities_addr = abilities::ffi_native_bridge_set_abilities as *const () as i64; - let set_abilities_addr = abilities::ffi_native_bridge_set_abilities as *const () as i64; + let get_abilities_addr = + proto::patchbukkit::bridge::ffi_native_bridge_get_abilities as *const () as i64; + let set_abilities_addr = + proto::patchbukkit::bridge::ffi_native_bridge_set_abilities as *const () as i64; let get_location_addr = location::rust_get_location as *const () as i64; let free_string_addr = memory::rust_free_string as *const () as i64; let get_world_addr = world::rust_get_world as *const () as i64; diff --git a/rust/src/proto/mod.rs b/rust/src/proto/mod.rs index a7504f9..455ea9c 100644 --- a/rust/src/proto/mod.rs +++ b/rust/src/proto/mod.rs @@ -1,7 +1,7 @@ pub mod patchbukkit { - // pub mod bridge { - // include!(concat!(env!("OUT_DIR"), "/patchbukkit.bridge.rs")); - // } + pub mod bridge { + include!(concat!(env!("OUT_DIR"), "/patchbukkit.bridge.rs")); + } pub mod common { include!(concat!(env!("OUT_DIR"), "/patchbukkit.common.rs"));