From 373a553722099a312e90770af3f7ea386ef6f4a0 Mon Sep 17 00:00:00 2001 From: Bjorn Beishline <75190918+BjornTheProgrammer@users.noreply.github.com> Date: Tue, 3 Feb 2026 08:00:08 -0700 Subject: [PATCH] Add the basis of protobufs --- java/patchbukkit/build.gradle.kts | 19 +++ proto/patchbukkit/bridge/native.proto | 11 ++ proto/patchbukkit/common/abilities.proto | 20 +++ proto/patchbukkit/common/types.proto | 7 + proto/patchbukkit/events/event.proto | 14 ++ proto/patchbukkit/events/player_events.proto | 9 ++ rust/Cargo.lock | 89 +++++++++++++ rust/Cargo.toml | 3 + rust/build.rs | 116 ++++++++++++++--- rust/src/java/native_callbacks/abilities.rs | 130 +++++++------------ rust/src/java/native_callbacks/mod.rs | 24 +++- rust/src/lib.rs | 3 + rust/src/proto/mod.rs | 13 ++ 13 files changed, 352 insertions(+), 106 deletions(-) create mode 100644 proto/patchbukkit/bridge/native.proto create mode 100644 proto/patchbukkit/common/abilities.proto create mode 100644 proto/patchbukkit/common/types.proto create mode 100644 proto/patchbukkit/events/event.proto create mode 100644 proto/patchbukkit/events/player_events.proto create mode 100644 rust/src/proto/mod.rs diff --git a/java/patchbukkit/build.gradle.kts b/java/patchbukkit/build.gradle.kts index e6ff59b..8a1e922 100644 --- a/java/patchbukkit/build.gradle.kts +++ b/java/patchbukkit/build.gradle.kts @@ -1,7 +1,25 @@ plugins { `java-library` + id("com.google.protobuf") version "0.9.6" } +val protobufVersion = "4.33.5" + +sourceSets { + main { + proto { + srcDir("../../proto") + } + } +} + +protobuf { + protoc { + artifact = "com.google.protobuf:protoc:$protobufVersion" + } +} + + repositories { maven { name = "papermc" @@ -18,6 +36,7 @@ dependencies { implementation("org.apache.maven.resolver:maven-resolver-connector-basic:1.9.18") implementation("org.apache.maven.resolver:maven-resolver-transport-http:1.9.18") implementation("org.apache.maven.resolver:maven-resolver-util:1.9.18") + implementation("com.google.protobuf:protobuf-java:$protobufVersion") } java { diff --git a/proto/patchbukkit/bridge/native.proto b/proto/patchbukkit/bridge/native.proto new file mode 100644 index 0000000..0a0af57 --- /dev/null +++ b/proto/patchbukkit/bridge/native.proto @@ -0,0 +1,11 @@ +syntax = "proto3"; +package patchbukkit.bridge; + +import "google/protobuf/wrappers.proto"; +import "patchbukkit/common/abilities.proto"; +import "patchbukkit/common/types.proto"; + +service NativeBridge { + rpc GetAbilities(patchbukkit.common.UUID) returns (patchbukkit.common.Abilities); + rpc SetAbilities(patchbukkit.common.SetAbilitiesRequest) returns (google.protobuf.BoolValue); +} diff --git a/proto/patchbukkit/common/abilities.proto b/proto/patchbukkit/common/abilities.proto new file mode 100644 index 0000000..207dfeb --- /dev/null +++ b/proto/patchbukkit/common/abilities.proto @@ -0,0 +1,20 @@ +syntax = "proto3"; + +package patchbukkit.common; + +import "patchbukkit/common/types.proto"; + +message SetAbilitiesRequest { + patchbukkit.common.UUID uuid = 1; + patchbukkit.common.Abilities abilities = 2; +} + +message Abilities { + bool invulnerable = 1; + bool flying = 2; + bool allow_flying = 3; + bool creative = 4; + bool allow_modify_world = 5; + float fly_speed = 6; + float walk_speed = 7; +} diff --git a/proto/patchbukkit/common/types.proto b/proto/patchbukkit/common/types.proto new file mode 100644 index 0000000..b7d9a19 --- /dev/null +++ b/proto/patchbukkit/common/types.proto @@ -0,0 +1,7 @@ +syntax = "proto3"; + +package patchbukkit.common; + +message UUID { + string value = 1; +} diff --git a/proto/patchbukkit/events/event.proto b/proto/patchbukkit/events/event.proto new file mode 100644 index 0000000..f65cbfa --- /dev/null +++ b/proto/patchbukkit/events/event.proto @@ -0,0 +1,14 @@ +syntax = "proto3"; + +package patchbukkit.events; + +import "patchbukkit/events/player_events.proto"; + +message Event { + string event_id = 1; + bool cancellable = 2; + + oneof event_data { + PlayerJoinEvent player_join = 10; + } +} diff --git a/proto/patchbukkit/events/player_events.proto b/proto/patchbukkit/events/player_events.proto new file mode 100644 index 0000000..843fb42 --- /dev/null +++ b/proto/patchbukkit/events/player_events.proto @@ -0,0 +1,9 @@ +syntax = "proto3"; +package patchbukkit.events; + +import "patchbukkit/common/types.proto"; + +message PlayerJoinEvent { + patchbukkit.common.UUID player = 1; + string join_message = 2; +} diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 06944f4..af1e654 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -947,6 +947,12 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + [[package]] name = "flate2" version = "1.1.8" @@ -1733,6 +1739,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "multimap" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" + [[package]] name = "nibble_vec" version = "0.1.0" @@ -1902,6 +1914,8 @@ dependencies = [ "j4rs", "j4rs_derive", "log", + "prost", + "prost-build", "pumpkin", "pumpkin-api-macros", "pumpkin-data", @@ -1995,6 +2009,17 @@ dependencies = [ "sha2 0.10.9", ] +[[package]] +name = "petgraph" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" +dependencies = [ + "fixedbitset", + "hashbrown 0.15.5", + "indexmap", +] + [[package]] name = "phf" version = "0.13.1" @@ -2172,6 +2197,57 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "prost" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ea70524a2f82d518bce41317d0fae74151505651af45faf1ffbd6fd33f0568" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-build" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "343d3bd7056eda839b03204e68deff7d1b13aba7af2b2fd16890697274262ee7" +dependencies = [ + "heck", + "itertools", + "log", + "multimap", + "petgraph", + "prettyplease", + "prost", + "prost-types", + "regex", + "syn", + "tempfile", +] + +[[package]] +name = "prost-derive" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "prost-types" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8991c4cbdb8bc5b11f0b074ffe286c30e523de90fee5ba8132f1399f23cb3dd7" +dependencies = [ + "prost", +] + [[package]] name = "pumpkin" version = "0.1.0-dev+1.21.11" @@ -3076,6 +3152,19 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60" +[[package]] +name = "tempfile" +version = "3.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" +dependencies = [ + "fastrand", + "getrandom 0.3.4", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + [[package]] name = "termcolor" version = "1.4.1" diff --git a/rust/Cargo.toml b/rust/Cargo.toml index b9e6540..beb66bf 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -30,11 +30,14 @@ serde_json = "1.0" serde-saphyr = "0.0.17" rand = { git = "https://github.com/rust-random/rand"} +prost = "0.14" [build-dependencies] j4rs = { version = "0.24", artifact = "cdylib", lib = true } env_logger = "0.11.8" log = "0.4" +prost-build = "0.14" +glob = "0.3" [profile.release] lto = true diff --git a/rust/build.rs b/rust/build.rs index 9c891d6..74b86f2 100644 --- a/rust/build.rs +++ b/rust/build.rs @@ -1,13 +1,102 @@ 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() { +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()); + let resources = base.join("resources"); + let jassets = resources.join("jassets"); + let deps = resources.join("deps"); + + let mut java_path = base.clone(); + java_path.pop(); + let java_path = java_path.join("java"); + + let patchbukkit_jar = java_path + .join("patchbukkit") + .join("build") + .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"), ( @@ -82,11 +171,6 @@ fn main() { ("io.papermc.paper", "paper-api", "1.21.11-R0.1-SNAPSHOT"), ]; - let base = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()); - let resources = base.join("resources"); - let jassets = resources.join("jassets"); - let deps = resources.join("deps"); - let jvm = JvmBuilder::new() .with_maven_settings(MavenSettings::new(vec![MavenArtifactRepo::from( "papermc::https://repo.papermc.io/repository/maven-public/", @@ -131,21 +215,11 @@ fn main() { } } - let mut java_path = base; - java_path.pop(); - let java_path = java_path.join("java"); - - let patchbukkit_jar = java_path - .join("patchbukkit") - .join("build") - .join("libs") - .join("patchbukkit.jar"); - - println!("patchbukkit_jar: {}", patchbukkit_jar.display()); - assert!( - !!&patchbukkit_jar.exists(), - "Failed to find patchbukkit.jar, build the java library first by running `gradle build` in the java directory!" - ); + if !&patchbukkit_jar.exists() { + panic!( + "Failed to find patchbukkit.jar, build the java library first by running `gradle build` in the java directory!" + ); + } jvm.deploy_artifact(&LocalJarArtifact::new(&patchbukkit_jar.to_string_lossy())) .unwrap(); diff --git a/rust/src/java/native_callbacks/abilities.rs b/rust/src/java/native_callbacks/abilities.rs index f9a5eb2..50cf135 100644 --- a/rust/src/java/native_callbacks/abilities.rs +++ b/rust/src/java/native_callbacks/abilities.rs @@ -1,89 +1,53 @@ -use std::ffi::c_char; +use prost::Message; -use pumpkin::entity::player::Abilities; -use tokio::sync::MutexGuard; +use crate::java::native_callbacks::CALLBACK_CONTEXT; +use crate::proto::patchbukkit::common::{Abilities, SetAbilitiesRequest, Uuid}; -use crate::java::native_callbacks::{CALLBACK_CONTEXT, utils::get_string}; +include!(concat!(env!("OUT_DIR"), "/patchbukkit.bridge.rs")); -#[repr(C)] -pub struct AbilitiesFFI { - pub invulnerable: bool, - pub flying: bool, - pub allow_flying: bool, - pub creative: bool, - pub allow_modify_world: bool, - pub fly_speed: f32, - pub walk_speed: f32, +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(); + let player = ctx.plugin_context.server.get_player_by_uuid(player_uuid)?; + let pumpkin_abilities = tokio::task::block_in_place(|| { + ctx.runtime + .block_on(async { player.abilities.lock().await }) + }); + + abilities.invulnerable = pumpkin_abilities.invulnerable; + abilities.flying = pumpkin_abilities.flying; + abilities.allow_flying = pumpkin_abilities.allow_flying; + abilities.creative = pumpkin_abilities.creative; + abilities.allow_modify_world = pumpkin_abilities.allow_modify_world; + abilities.fly_speed = pumpkin_abilities.fly_speed; + abilities.walk_speed = pumpkin_abilities.walk_speed; + + return Some(abilities); } -impl AbilitiesFFI { - fn new(abilities: MutexGuard<'_, Abilities>) -> Self { - Self { - invulnerable: abilities.invulnerable, - flying: abilities.flying, - allow_flying: abilities.allow_flying, - creative: abilities.creative, - allow_modify_world: abilities.allow_modify_world, - fly_speed: abilities.fly_speed, - walk_speed: abilities.walk_speed, - } - } -} - -pub extern "C" fn rust_set_abilities( - uuid_ptr: *const c_char, - abilities: *mut AbilitiesFFI, -) -> bool { - let uuid_str = get_string(uuid_ptr); - if let Some(ctx) = CALLBACK_CONTEXT.get() { - let uuid = uuid::Uuid::parse_str(&uuid_str).unwrap(); - let player = ctx.plugin_context.server.get_player_by_uuid(uuid); - if let Some(player) = player { - tokio::task::block_in_place(|| { - ctx.runtime.block_on(async { - let mut server_abilities = player.abilities.lock().await; - unsafe { - server_abilities.allow_flying = (*abilities).allow_flying; - server_abilities.allow_modify_world = (*abilities).allow_modify_world; - server_abilities.creative = (*abilities).creative; - server_abilities.fly_speed = (*abilities).fly_speed; - server_abilities.flying = (*abilities).flying; - server_abilities.invulnerable = (*abilities).invulnerable; - server_abilities.walk_speed = (*abilities).walk_speed; - } - }); - }); - - let player = player; - ctx.runtime.spawn(async move { - player.send_abilities_update().await; - }); - - return true; - } - } - - false -} - -pub extern "C" fn rust_get_abilities(uuid_ptr: *const c_char, out: *mut AbilitiesFFI) -> bool { - let uuid_str = get_string(uuid_ptr); - if let Some(ctx) = CALLBACK_CONTEXT.get() { - let uuid = uuid::Uuid::parse_str(&uuid_str).unwrap(); - let player = ctx.plugin_context.server.get_player_by_uuid(uuid); - if let Some(player) = player { - let abilities = tokio::task::block_in_place(|| { - ctx.runtime - .block_on(async { AbilitiesFFI::new(player.abilities.lock().await) }) - }); - - unsafe { - *out = abilities; - } - - return true; - } - } - - false +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 = 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 }) + }); + + pumpkin_abilities.invulnerable = abilities.invulnerable; + pumpkin_abilities.flying = abilities.flying; + pumpkin_abilities.allow_flying = abilities.allow_flying; + pumpkin_abilities.creative = abilities.creative; + pumpkin_abilities.allow_modify_world = abilities.allow_modify_world; + pumpkin_abilities.fly_speed = abilities.fly_speed; + pumpkin_abilities.walk_speed = abilities.walk_speed; + + let player = player.clone(); + ctx.runtime.spawn(async move { + player.send_abilities_update().await; + }); + + return Some(true); } diff --git a/rust/src/java/native_callbacks/mod.rs b/rust/src/java/native_callbacks/mod.rs index ae0b3e7..ecb97b2 100644 --- a/rust/src/java/native_callbacks/mod.rs +++ b/rust/src/java/native_callbacks/mod.rs @@ -42,12 +42,32 @@ pub fn init_callback_context( Ok(()) } +pub fn encode_with_length(data: Vec) -> *mut u8 { + let len = data.len() as u32; + let mut result = Vec::with_capacity(4 + data.len()); + result.extend_from_slice(&len.to_le_bytes()); + result.extend(data); + + let ptr = result.as_mut_ptr(); + std::mem::forget(result); + ptr +} + +pub extern "C" fn rust_free_bytes(ptr: *mut u8, len: u32) { + if !ptr.is_null() { + unsafe { + let total_len = 4 + len as usize; + drop(Vec::from_raw_parts(ptr, total_len, total_len)); + } + } +} + 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::rust_get_abilities as *const () as i64; - let set_abilities_addr = abilities::rust_set_abilities 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_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/lib.rs b/rust/src/lib.rs index 0290fb1..415f00c 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -10,6 +10,7 @@ pub mod config; pub mod directories; pub mod events; pub mod java; +pub mod proto; use directories::setup_directories; use java::{ @@ -33,6 +34,8 @@ async fn on_load_inner(plugin: &PatchBukkitPlugin, server: Arc) -> Resu // Setup directories let dirs = setup_directories(&server)?; + proto::patchbukkit::common::Uuid::default(); + // Discover and prepare JAR files let jar_paths = discover_jar_files(&dirs.plugins); for jar_path in jar_paths { diff --git a/rust/src/proto/mod.rs b/rust/src/proto/mod.rs new file mode 100644 index 0000000..a7504f9 --- /dev/null +++ b/rust/src/proto/mod.rs @@ -0,0 +1,13 @@ +pub mod patchbukkit { + // pub mod bridge { + // include!(concat!(env!("OUT_DIR"), "/patchbukkit.bridge.rs")); + // } + + pub mod common { + include!(concat!(env!("OUT_DIR"), "/patchbukkit.common.rs")); + } + + pub mod events { + include!(concat!(env!("OUT_DIR"), "/patchbukkit.events.rs")); + } +}