Add the basis of protobufs

This commit is contained in:
Bjorn Beishline 2026-02-03 08:00:08 -07:00
parent faf3ac680f
commit 373a553722
13 changed files with 352 additions and 106 deletions

View File

@ -1,7 +1,25 @@
plugins { plugins {
`java-library` `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 { repositories {
maven { maven {
name = "papermc" 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-connector-basic:1.9.18")
implementation("org.apache.maven.resolver:maven-resolver-transport-http: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("org.apache.maven.resolver:maven-resolver-util:1.9.18")
implementation("com.google.protobuf:protobuf-java:$protobufVersion")
} }
java { java {

View File

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

View File

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

View File

@ -0,0 +1,7 @@
syntax = "proto3";
package patchbukkit.common;
message UUID {
string value = 1;
}

View File

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

View File

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

89
rust/Cargo.lock generated
View File

@ -947,6 +947,12 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
[[package]]
name = "fixedbitset"
version = "0.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99"
[[package]] [[package]]
name = "flate2" name = "flate2"
version = "1.1.8" version = "1.1.8"
@ -1733,6 +1739,12 @@ dependencies = [
"windows-sys 0.61.2", "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]] [[package]]
name = "nibble_vec" name = "nibble_vec"
version = "0.1.0" version = "0.1.0"
@ -1902,6 +1914,8 @@ dependencies = [
"j4rs", "j4rs",
"j4rs_derive", "j4rs_derive",
"log", "log",
"prost",
"prost-build",
"pumpkin", "pumpkin",
"pumpkin-api-macros", "pumpkin-api-macros",
"pumpkin-data", "pumpkin-data",
@ -1995,6 +2009,17 @@ dependencies = [
"sha2 0.10.9", "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]] [[package]]
name = "phf" name = "phf"
version = "0.13.1" version = "0.13.1"
@ -2172,6 +2197,57 @@ dependencies = [
"unicode-ident", "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]] [[package]]
name = "pumpkin" name = "pumpkin"
version = "0.1.0-dev+1.21.11" 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" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60" 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]] [[package]]
name = "termcolor" name = "termcolor"
version = "1.4.1" version = "1.4.1"

View File

@ -30,11 +30,14 @@ serde_json = "1.0"
serde-saphyr = "0.0.17" serde-saphyr = "0.0.17"
rand = { git = "https://github.com/rust-random/rand"} rand = { git = "https://github.com/rust-random/rand"}
prost = "0.14"
[build-dependencies] [build-dependencies]
j4rs = { version = "0.24", artifact = "cdylib", lib = true } j4rs = { version = "0.24", artifact = "cdylib", lib = true }
env_logger = "0.11.8" env_logger = "0.11.8"
log = "0.4" log = "0.4"
prost-build = "0.14"
glob = "0.3"
[profile.release] [profile.release]
lto = true lto = true

View File

@ -1,13 +1,102 @@
use std::{collections::HashSet, fs, path::PathBuf}; use std::{collections::HashSet, fs, path::PathBuf};
use glob::glob;
use j4rs::{JvmBuilder, LocalJarArtifact, MavenArtifact, MavenArtifactRepo, MavenSettings}; 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)] #[expect(clippy::too_many_lines)]
fn main() { fn main() -> Result<(), Box<dyn Error>> {
println!("cargo::rerun-if-changed=rust/src/build.rs"); println!("cargo::rerun-if-changed=rust/src/build.rs");
println!("cargo::rerun-if-changed=java/build/libs/"); println!("cargo::rerun-if-changed=java/build/libs/");
env_logger::init(); 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::<Vec<_>>();
let mut config = Config::new();
config.service_generator(Box::new(FfiServiceGenerator));
config.compile_protos(paths.as_slice(), &[proto_path])?;
let dependencies = [ let dependencies = [
("com.google.guava", "guava", "33.3.1-jre"), ("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"), ("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() let jvm = JvmBuilder::new()
.with_maven_settings(MavenSettings::new(vec![MavenArtifactRepo::from( .with_maven_settings(MavenSettings::new(vec![MavenArtifactRepo::from(
"papermc::https://repo.papermc.io/repository/maven-public/", "papermc::https://repo.papermc.io/repository/maven-public/",
@ -131,21 +215,11 @@ fn main() {
} }
} }
let mut java_path = base; if !&patchbukkit_jar.exists() {
java_path.pop(); panic!(
let java_path = java_path.join("java"); "Failed to find patchbukkit.jar, build the java library first by running `gradle build` in the java directory!"
);
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!"
);
jvm.deploy_artifact(&LocalJarArtifact::new(&patchbukkit_jar.to_string_lossy())) jvm.deploy_artifact(&LocalJarArtifact::new(&patchbukkit_jar.to_string_lossy()))
.unwrap(); .unwrap();

View File

@ -1,89 +1,53 @@
use std::ffi::c_char; use prost::Message;
use pumpkin::entity::player::Abilities; use crate::java::native_callbacks::CALLBACK_CONTEXT;
use tokio::sync::MutexGuard; 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)] fn ffi_native_bridge_get_abilities_impl(request: Uuid) -> Option<Abilities> {
pub struct AbilitiesFFI { let ctx = CALLBACK_CONTEXT.get()?;
pub invulnerable: bool, let player_uuid = uuid::Uuid::parse_str(&request.value).unwrap();
pub flying: bool, let mut abilities = Abilities::default();
pub allow_flying: bool, let player = ctx.plugin_context.server.get_player_by_uuid(player_uuid)?;
pub creative: bool, let pumpkin_abilities = tokio::task::block_in_place(|| {
pub allow_modify_world: bool, ctx.runtime
pub fly_speed: f32, .block_on(async { player.abilities.lock().await })
pub walk_speed: f32, });
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 ffi_native_bridge_set_abilities_impl(request: SetAbilitiesRequest) -> Option<bool> {
fn new(abilities: MutexGuard<'_, Abilities>) -> Self { let ctx = CALLBACK_CONTEXT.get()?;
Self { let player_uuid = uuid::Uuid::parse_str(&request.uuid?.value).unwrap();
invulnerable: abilities.invulnerable, let abilities = Abilities::default();
flying: abilities.flying, let player = ctx.plugin_context.server.get_player_by_uuid(player_uuid)?;
allow_flying: abilities.allow_flying, let mut pumpkin_abilities = tokio::task::block_in_place(|| {
creative: abilities.creative, ctx.runtime
allow_modify_world: abilities.allow_modify_world, .block_on(async { player.abilities.lock().await })
fly_speed: abilities.fly_speed, });
walk_speed: abilities.walk_speed,
} pumpkin_abilities.invulnerable = abilities.invulnerable;
} pumpkin_abilities.flying = abilities.flying;
} pumpkin_abilities.allow_flying = abilities.allow_flying;
pumpkin_abilities.creative = abilities.creative;
pub extern "C" fn rust_set_abilities( pumpkin_abilities.allow_modify_world = abilities.allow_modify_world;
uuid_ptr: *const c_char, pumpkin_abilities.fly_speed = abilities.fly_speed;
abilities: *mut AbilitiesFFI, pumpkin_abilities.walk_speed = abilities.walk_speed;
) -> bool {
let uuid_str = get_string(uuid_ptr); let player = player.clone();
if let Some(ctx) = CALLBACK_CONTEXT.get() { ctx.runtime.spawn(async move {
let uuid = uuid::Uuid::parse_str(&uuid_str).unwrap(); player.send_abilities_update().await;
let player = ctx.plugin_context.server.get_player_by_uuid(uuid); });
if let Some(player) = player {
tokio::task::block_in_place(|| { return Some(true);
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
} }

View File

@ -42,12 +42,32 @@ pub fn init_callback_context(
Ok(()) Ok(())
} }
pub fn encode_with_length(data: Vec<u8>) -> *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<()> { pub fn initialize_callbacks(jvm: &Jvm) -> Result<()> {
let send_message_addr = message::rust_send_message as *const () as i64; 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 register_event_addr = events::rust_register_event as *const () as i64;
let call_event_addr = events::rust_call_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 get_abilities_addr = abilities::ffi_native_bridge_set_abilities as *const () as i64;
let set_abilities_addr = abilities::rust_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 get_location_addr = location::rust_get_location as *const () as i64;
let free_string_addr = memory::rust_free_string 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; let get_world_addr = world::rust_get_world as *const () as i64;

View File

@ -10,6 +10,7 @@ pub mod config;
pub mod directories; pub mod directories;
pub mod events; pub mod events;
pub mod java; pub mod java;
pub mod proto;
use directories::setup_directories; use directories::setup_directories;
use java::{ use java::{
@ -33,6 +34,8 @@ async fn on_load_inner(plugin: &PatchBukkitPlugin, server: Arc<Context>) -> Resu
// Setup directories // Setup directories
let dirs = setup_directories(&server)?; let dirs = setup_directories(&server)?;
proto::patchbukkit::common::Uuid::default();
// Discover and prepare JAR files // Discover and prepare JAR files
let jar_paths = discover_jar_files(&dirs.plugins); let jar_paths = discover_jar_files(&dirs.plugins);
for jar_path in jar_paths { for jar_path in jar_paths {

13
rust/src/proto/mod.rs Normal file
View File

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