Setup rust passing of ffm

This commit is contained in:
Bjorn Beishline 2026-02-03 14:03:03 -07:00
parent 373a553722
commit a31da8c568
8 changed files with 151 additions and 97 deletions

1
.gitignore vendored
View File

@ -17,6 +17,7 @@ build/
java/build/
java/patchbukkit/build/
java/patchbukkit/bin/
!rust/build/
# Examples
examples/*/bin

View File

@ -3,6 +3,8 @@ name = "patchbukkit"
version = "0.1.0"
edition = "2024"
build = "build/main.rs"
[lib]
crate-type = ["cdylib"]

View File

@ -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<dyn Error>> {
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<dyn Error>> {
.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::<Vec<_>>();
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"),
(

17
rust/build/main.rs Normal file
View File

@ -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<dyn Error>> {
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(())
}

113
rust/build/protobufs.rs Normal file
View File

@ -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<String>, proto_module: impl Into<String>) -> 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::<Vec<_>>()
.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();
}

View File

@ -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<Abilities> {
pub fn ffi_native_bridge_get_abilities_impl(request: Uuid) -> Option<Abilities> {
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<Abilities> {
return Some(abilities);
}
fn ffi_native_bridge_set_abilities_impl(request: SetAbilitiesRequest) -> Option<bool> {
pub fn ffi_native_bridge_set_abilities_impl(request: SetAbilitiesRequest) -> Option<bool> {
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)
}

View File

@ -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;

View File

@ -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"));