From 8e4b13d42b39ccfed7ae91dae7759d6e1a86b7aa Mon Sep 17 00:00:00 2001 From: Bjorn Beishline <75190918+BjornTheProgrammer@users.noreply.github.com> Date: Tue, 3 Feb 2026 22:50:39 -0700 Subject: [PATCH] Remove last of NativePatchBukkit commands --- .../patchbukkit/bridge/NativePatchBukkit.java | 374 ------------------ .../patchbukkit/entity/PatchBukkitEntity.java | 8 +- .../patchbukkit/entity/PatchBukkitPlayer.java | 75 ++-- .../events/PatchBukkitEventManager.java | 1 - .../registry/PatchBukkitRegistry.java | 63 ++- .../registry/PatchBukkitRegistryAccess.java | 44 +-- .../patchbukkit/world/PatchBukkitWorld.java | 5 +- proto/patchbukkit/bridge.proto | 7 + proto/patchbukkit/registry.proto | 31 ++ proto/patchbukkit/sound.proto | 31 ++ rust/src/java/native_callbacks/location.rs | 4 +- rust/src/java/native_callbacks/memory.rs | 9 - rust/src/java/native_callbacks/mod.rs | 51 +-- rust/src/java/native_callbacks/registry.rs | 74 ++-- rust/src/java/native_callbacks/sound.rs | 154 ++++---- rust/src/java/native_callbacks/world.rs | 39 -- rust/src/proto/mod.rs | 8 + 17 files changed, 274 insertions(+), 704 deletions(-) delete mode 100644 java/patchbukkit/src/main/java/org/patchbukkit/bridge/NativePatchBukkit.java create mode 100644 proto/patchbukkit/registry.proto create mode 100644 proto/patchbukkit/sound.proto delete mode 100644 rust/src/java/native_callbacks/memory.rs delete mode 100644 rust/src/java/native_callbacks/world.rs diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/bridge/NativePatchBukkit.java b/java/patchbukkit/src/main/java/org/patchbukkit/bridge/NativePatchBukkit.java deleted file mode 100644 index d8ff5a9..0000000 --- a/java/patchbukkit/src/main/java/org/patchbukkit/bridge/NativePatchBukkit.java +++ /dev/null @@ -1,374 +0,0 @@ -package org.patchbukkit.bridge; - -import java.lang.foreign.*; -import java.lang.invoke.MethodHandle; -import java.lang.invoke.VarHandle; -import java.util.UUID; - -import org.bukkit.event.Event; -import org.bukkit.event.Listener; -import org.bukkit.plugin.Plugin; -import org.patchbukkit.events.PatchBukkitEventSerializer; - -public class NativePatchBukkit { - - private static final Linker LINKER = Linker.nativeLinker(); - - private static MethodHandle getWorldNative; - private static MethodHandle freeStringNative; - private static MethodHandle getRegistryDataNative; - private static MethodHandle playerEntityPlaySoundNative; - private static MethodHandle playerPlaySoundNative; - - // Struct layout matching Rust's #[repr(C)] AbilitiesFFI - private static final StructLayout ABILITIES_LAYOUT = - MemoryLayout.structLayout( - ValueLayout.JAVA_BOOLEAN.withName("invulnerable"), - ValueLayout.JAVA_BOOLEAN.withName("flying"), - ValueLayout.JAVA_BOOLEAN.withName("allow_flying"), - ValueLayout.JAVA_BOOLEAN.withName("creative"), - ValueLayout.JAVA_BOOLEAN.withName("allow_modify_world"), - MemoryLayout.paddingLayout(3), // Padding to align f32 to 4-byte boundary - ValueLayout.JAVA_FLOAT.withName("fly_speed"), - ValueLayout.JAVA_FLOAT.withName("walk_speed") - ); - - // VarHandles for accessing struct fields - private static final VarHandle INVULNERABLE = ABILITIES_LAYOUT.varHandle( - MemoryLayout.PathElement.groupElement("invulnerable") - ); - private static final VarHandle FLYING = ABILITIES_LAYOUT.varHandle( - MemoryLayout.PathElement.groupElement("flying") - ); - private static final VarHandle ALLOW_FLYING = ABILITIES_LAYOUT.varHandle( - MemoryLayout.PathElement.groupElement("allow_flying") - ); - private static final VarHandle CREATIVE = ABILITIES_LAYOUT.varHandle( - MemoryLayout.PathElement.groupElement("creative") - ); - private static final VarHandle ALLOW_MODIFY_WORLD = - ABILITIES_LAYOUT.varHandle( - MemoryLayout.PathElement.groupElement("allow_modify_world") - ); - private static final VarHandle FLY_SPEED = ABILITIES_LAYOUT.varHandle( - MemoryLayout.PathElement.groupElement("fly_speed") - ); - private static final VarHandle WALK_SPEED = ABILITIES_LAYOUT.varHandle( - MemoryLayout.PathElement.groupElement("walk_speed") - ); - - // Struct layout for Vec3 - private static final StructLayout VEC3_LAYOUT = MemoryLayout.structLayout( - ValueLayout.JAVA_DOUBLE.withName("x"), - ValueLayout.JAVA_DOUBLE.withName("y"), - ValueLayout.JAVA_DOUBLE.withName("z") - ); - - // VarHandles for Vec3 fields - private static final VarHandle VEC3_X = VEC3_LAYOUT.varHandle( - MemoryLayout.PathElement.groupElement("x") - ); - private static final VarHandle VEC3_Y = VEC3_LAYOUT.varHandle( - MemoryLayout.PathElement.groupElement("y") - ); - private static final VarHandle VEC3_Z = VEC3_LAYOUT.varHandle( - MemoryLayout.PathElement.groupElement("z") - ); - - public record Vec3(double x, double y, double z) {} - - /** - * Java record to hold player abilities. - */ - public record Abilities( - boolean invulnerable, - boolean flying, - boolean allowFlying, - boolean creative, - boolean allowModifyWorld, - float flySpeed, - float walkSpeed - ) { - // "Wither" methods - return a copy with one field changed - public Abilities withInvulnerable(boolean value) { - return new Abilities( - value, - flying, - allowFlying, - creative, - allowModifyWorld, - flySpeed, - walkSpeed - ); - } - - public Abilities withFlying(boolean value) { - return new Abilities( - invulnerable, - value, - allowFlying, - creative, - allowModifyWorld, - flySpeed, - walkSpeed - ); - } - - public Abilities withAllowFlying(boolean value) { - return new Abilities( - invulnerable, - flying, - value, - creative, - allowModifyWorld, - flySpeed, - walkSpeed - ); - } - - public Abilities withCreative(boolean value) { - return new Abilities( - invulnerable, - flying, - allowFlying, - value, - allowModifyWorld, - flySpeed, - walkSpeed - ); - } - - public Abilities withAllowModifyWorld(boolean value) { - return new Abilities( - invulnerable, - flying, - allowFlying, - creative, - value, - flySpeed, - walkSpeed - ); - } - - public Abilities withFlySpeed(float value) { - return new Abilities( - invulnerable, - flying, - allowFlying, - creative, - allowModifyWorld, - value, - walkSpeed - ); - } - - public Abilities withWalkSpeed(float value) { - return new Abilities( - invulnerable, - flying, - allowFlying, - creative, - allowModifyWorld, - flySpeed, - value - ); - } - } - - /** - * Called from Rust during initialization to register native function pointers. - */ - public static void initCallbacks( - long freeStringAddr, - long getWorldAddr, - long getRegistryDataAddr, - long playerEntityPlaySoundAddr, - long playerPlaySoundAddr - ) { - - // const char* rust_get_world(const char* uuid) - getWorldNative = LINKER.downcallHandle( - MemorySegment.ofAddress(getWorldAddr), - FunctionDescriptor.of(ValueLayout.ADDRESS, ValueLayout.ADDRESS) - ); - - // void rust_free_string(const char* str) - freeStringNative = LINKER.downcallHandle( - MemorySegment.ofAddress(freeStringAddr), - FunctionDescriptor.ofVoid(ValueLayout.ADDRESS) - ); - - // const char* rust_get_registry_entries(const char* registry_name) - getRegistryDataNative = LINKER.downcallHandle( - MemorySegment.ofAddress(getRegistryDataAddr), - FunctionDescriptor.of(ValueLayout.ADDRESS, ValueLayout.ADDRESS) - ); - - // void rust_player_entity_play_sound(const char* player_uuid, const char* sound_name, - // const char* sound_category, const char* entity_uuid, float volume, float pitch) - playerEntityPlaySoundNative = LINKER.downcallHandle( - MemorySegment.ofAddress(playerEntityPlaySoundAddr), - FunctionDescriptor.ofVoid( - ValueLayout.ADDRESS, // player_uuid - ValueLayout.ADDRESS, // sound_name - ValueLayout.ADDRESS, // sound_category - ValueLayout.ADDRESS, // entity_uuid - ValueLayout.JAVA_FLOAT, // volume - ValueLayout.JAVA_FLOAT // pitch - ) - ); - - // void rust_player_play_sound(const char* player_uuid, const char* sound_name, - // const char* sound_category, double x, double y, double z, float volume, float pitch) - playerPlaySoundNative = LINKER.downcallHandle( - MemorySegment.ofAddress(playerPlaySoundAddr), - FunctionDescriptor.ofVoid( - ValueLayout.ADDRESS, // player_uuid - ValueLayout.ADDRESS, // sound_name - ValueLayout.ADDRESS, // sound_category - ValueLayout.JAVA_DOUBLE, // x - ValueLayout.JAVA_DOUBLE, // y - ValueLayout.JAVA_DOUBLE, // z - ValueLayout.JAVA_FLOAT, // volume - ValueLayout.JAVA_FLOAT // pitch - ) - ); - } - - /** - * Free a string allocated by the Rust side. - */ - private static void freeRustString(MemorySegment ptr) { - try { - freeStringNative.invokeExact(ptr); - } catch (Throwable t) { - throw new RuntimeException("Failed to free Rust string", t); - } - } - - /** - * Get the world UUID for an entity. - */ - public static String getWorld(UUID entityUuid) { - try (Arena arena = Arena.ofConfined()) { - MemorySegment uuidStr = arena.allocateFrom(entityUuid.toString()); - MemorySegment resultPtr = (MemorySegment) getWorldNative.invokeExact(uuidStr); - - if (resultPtr.equals(MemorySegment.NULL)) { - return null; - } - - // Read the string, then free the Rust-allocated memory - try { - String result = resultPtr.reinterpret(Long.MAX_VALUE).getString(0); - return result; - } finally { - freeRustString(resultPtr); - } - } catch (Throwable t) { - throw new RuntimeException("Failed to call native getWorld", t); - } - } - - /** - * Get all data for a registry (entries + tags) as JSON. - * - * @param registryName The registry identifier (e.g. "sound_event", "block") - * @return JSON string with "entries" and "tags" fields, or null if registry unknown - */ - public static String getRegistryData(String registryName) { - try (Arena arena = Arena.ofConfined()) { - MemorySegment nameStr = arena.allocateFrom(registryName); - MemorySegment resultPtr = (MemorySegment) getRegistryDataNative.invokeExact(nameStr); - - if (resultPtr.equals(MemorySegment.NULL)) return null; - - try { - return resultPtr.reinterpret(Long.MAX_VALUE).getString(0); - } finally { - freeRustString(resultPtr); - } - } catch (Throwable t) { - throw new RuntimeException("Failed to get registry data: " + registryName, t); - } - } - - /** - * Play a sound effect attached to an entity, heard by a specific player. - * - * @param playerUuid The player who will hear the sound - * @param soundName Sound identifier (e.g. "block.note_block.chime") - * @param soundCategory Category name (e.g. "master", "music", "blocks") - * @param entityUuid The entity the sound is attached to - * @param volume Volume (1.0 = normal) - * @param pitch Pitch (1.0 = normal) - */ - public static void playerEntityPlaySound( - UUID playerUuid, - String soundName, - String soundCategory, - UUID entityUuid, - float volume, - float pitch - ) { - try (Arena arena = Arena.ofConfined()) { - MemorySegment playerUuidStr = arena.allocateFrom(playerUuid.toString()); - MemorySegment soundNameStr = arena.allocateFrom(soundName); - MemorySegment soundCategoryStr = arena.allocateFrom(soundCategory); - MemorySegment entityUuidStr = arena.allocateFrom(entityUuid.toString()); - - playerEntityPlaySoundNative.invokeExact( - playerUuidStr, - soundNameStr, - soundCategoryStr, - entityUuidStr, - volume, - pitch - ); - } catch (Throwable t) { - throw new RuntimeException("Failed to call native entityPlaySound", t); - } - } - - /** - * Play a sound effect at a location, heard by a specific player. - * - * @param playerUuid The player who will hear the sound - * @param soundName Sound identifier (e.g. "block.note_block.chime") - * @param soundCategory Category name (e.g. "master", "music", "blocks") - * @param x X coordinate - * @param y Y coordinate - * @param z Z coordinate - * @param volume Volume (1.0 = normal) - * @param pitch Pitch (1.0 = normal) - */ - public static void playerPlaySound( - UUID playerUuid, - String soundName, - String soundCategory, - double x, - double y, - double z, - float volume, - float pitch - ) { - try (Arena arena = Arena.ofConfined()) { - MemorySegment playerUuidStr = arena.allocateFrom(playerUuid.toString()); - MemorySegment soundNameStr = arena.allocateFrom(soundName); - MemorySegment soundCategoryStr = arena.allocateFrom(soundCategory); - - playerPlaySoundNative.invokeExact( - playerUuidStr, - soundNameStr, - soundCategoryStr, - x, - y, - z, - volume, - pitch - ); - } catch (Throwable t) { - throw new RuntimeException("Failed to call native playerPlaySound", t); - } - } -} diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/entity/PatchBukkitEntity.java b/java/patchbukkit/src/main/java/org/patchbukkit/entity/PatchBukkitEntity.java index 1a5978f..c950b98 100644 --- a/java/patchbukkit/src/main/java/org/patchbukkit/entity/PatchBukkitEntity.java +++ b/java/patchbukkit/src/main/java/org/patchbukkit/entity/PatchBukkitEntity.java @@ -38,7 +38,6 @@ import org.bukkit.util.Vector; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.patchbukkit.bridge.BridgeUtils; -import org.patchbukkit.bridge.NativePatchBukkit; import org.patchbukkit.world.PatchBukkitWorld; import io.papermc.paper.datacomponent.DataComponentType; @@ -325,11 +324,8 @@ public class PatchBukkitEntity implements Entity { @Override public @NotNull World getWorld() { - var world_uuid_str = NativePatchBukkit.getWorld(this.uuid); - UUID world_uuid = UUID.fromString(world_uuid_str); - var world = PatchBukkitWorld.getOrCreate(world_uuid); - - return world; + var location = NativeBridgeFfi.getLocation(BridgeUtils.convertUuid(this.uuid)); + return PatchBukkitWorld.getOrCreate(BridgeUtils.convertUuid(location.getWorld().getUuid())); } @Override diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/entity/PatchBukkitPlayer.java b/java/patchbukkit/src/main/java/org/patchbukkit/entity/PatchBukkitPlayer.java index 571608c..458bbb9 100644 --- a/java/patchbukkit/src/main/java/org/patchbukkit/entity/PatchBukkitPlayer.java +++ b/java/patchbukkit/src/main/java/org/patchbukkit/entity/PatchBukkitPlayer.java @@ -5,15 +5,7 @@ import java.net.InetAddress; import java.net.InetSocketAddress; import java.time.Duration; import java.time.Instant; -import java.util.Collection; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Set; -import java.util.UUID; +import java.util.*; import java.util.concurrent.CompletableFuture; import org.bukkit.BanEntry; @@ -63,7 +55,6 @@ import org.jetbrains.annotations.Range; import org.jetbrains.annotations.Unmodifiable; import org.jetbrains.annotations.UnmodifiableView; import org.patchbukkit.bridge.BridgeUtils; -import org.patchbukkit.bridge.NativePatchBukkit; import org.patchbukkit.registry.PatchBukkitSound; import com.destroystokyo.paper.ClientOption; @@ -82,6 +73,8 @@ import net.md_5.bungee.api.chat.BaseComponent; import patchbukkit.bridge.NativeBridgeFfi; import patchbukkit.message.SendMessageRequest; import patchbukkit.abilities.SetAbilitiesRequest; +import patchbukkit.sound.PlayerEntityPlaySoundRequest; +import patchbukkit.sound.PlayerPlaySoundRequest; @SuppressWarnings({ "deprecation", "removal" }) public class PatchBukkitPlayer @@ -694,49 +687,87 @@ public class PatchBukkitPlayer @Override public void playSound(Location location, Sound sound, SoundCategory category, float volume, float pitch) { var patchBukkitSound = (PatchBukkitSound) sound; - NativePatchBukkit.playerPlaySound(this.uuid, patchBukkitSound.getOriginalName(), category.name(), location.x(), location.y(), location.z(), volume, pitch); + this.playSound0(location, patchBukkitSound.getOriginalName(), category, volume, pitch, OptionalLong.empty()); } @Override public void playSound(Location location, String sound, SoundCategory category, float volume, float pitch) { - NativePatchBukkit.playerPlaySound(this.uuid, sound, category.name(), location.x(), location.y(), location.z(), volume, pitch); + this.playSound0(location, sound, category, volume, pitch, OptionalLong.empty()); } @Override public void playSound(Location location, Sound sound, SoundCategory category, float volume, float pitch, long seed) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'playSound'"); + var patchBukkitSound = (PatchBukkitSound) sound; + this.playSound0(location, patchBukkitSound.getOriginalName(), category, volume, pitch, OptionalLong.of(seed)); } @Override public void playSound(Location location, String sound, SoundCategory category, float volume, float pitch, long seed) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'playSound'"); + this.playSound0(location, sound, category, volume, pitch, OptionalLong.of(seed)); + } + + private void playSound0(Location location, String sound, SoundCategory category, float volume, float pitch, OptionalLong seed) { + var request = PlayerPlaySoundRequest.newBuilder() + .setPlayerUuid(BridgeUtils.convertUuid(this.uuid)) + .setLocation( + patchbukkit.common.Location.newBuilder() + .setPosition( + patchbukkit.common.Vec3.newBuilder() + .setX(location.x()) + .setY(location.y()) + .setZ(location.z()) + ).setWorld( + patchbukkit.common.World.newBuilder().setUuid(BridgeUtils.convertUuid(location.getWorld().getUID())) + ).setPitch(location.getPitch()) + .setYaw(location.getYaw()) + ).setSound( + patchbukkit.sound.Sound.newBuilder() + .setCategory(category.name()) + .setName(sound) + ).setVolume(volume) + .setPitch(pitch); + + if (seed.isPresent()) request.setSeed(seed.getAsLong()); + NativeBridgeFfi.playerPlaySound(request.build()); } @Override public void playSound(Entity entity, Sound sound, SoundCategory category, float volume, float pitch) { var patchBukkitSound = (PatchBukkitSound) sound; - NativePatchBukkit.playerEntityPlaySound(this.uuid, patchBukkitSound.getOriginalName(), category.name(), entity.getUniqueId(), volume, pitch); + this.playSound0(entity, patchBukkitSound.getOriginalName(), category, volume, pitch, OptionalLong.empty()); } @Override public void playSound(Entity entity, String sound, SoundCategory category, float volume, float pitch) { - NativePatchBukkit.playerEntityPlaySound(this.uuid, sound, category.name(), entity.getUniqueId(), volume, pitch); + this.playSound0(entity, sound, category, volume, pitch, OptionalLong.empty()); } @Override public void playSound(Entity entity, Sound sound, SoundCategory category, float volume, float pitch, long seed) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'playSound'"); + var patchBukkitSound = (PatchBukkitSound) sound; + this.playSound0(entity, patchBukkitSound.getOriginalName(), category, volume, pitch, OptionalLong.of(seed)); } @Override public void playSound(Entity entity, String sound, SoundCategory category, float volume, float pitch, long seed) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'playSound'"); + this.playSound0(entity, sound, category, volume, pitch, OptionalLong.of(seed)); + } + + private void playSound0(Entity entity, String sound, SoundCategory category, float volume, float pitch, OptionalLong seed) { + var request = PlayerEntityPlaySoundRequest.newBuilder() + .setPlayerUuid(BridgeUtils.convertUuid(this.uuid)) + .setEntityUuid(BridgeUtils.convertUuid(entity.getUniqueId())) + .setSound( + patchbukkit.sound.Sound.newBuilder() + .setCategory(category.name()) + .setName(sound) + ).setVolume(volume) + .setPitch(pitch); + + if (seed.isPresent()) request.setSeed(seed.getAsLong()); + NativeBridgeFfi.playerEntityPlaySound(request.build()); } @Override diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/events/PatchBukkitEventManager.java b/java/patchbukkit/src/main/java/org/patchbukkit/events/PatchBukkitEventManager.java index f744a29..4478f2a 100644 --- a/java/patchbukkit/src/main/java/org/patchbukkit/events/PatchBukkitEventManager.java +++ b/java/patchbukkit/src/main/java/org/patchbukkit/events/PatchBukkitEventManager.java @@ -13,7 +13,6 @@ import org.bukkit.plugin.IllegalPluginAccessException; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.RegisteredListener; import org.patchbukkit.bridge.BridgeUtils; -import org.patchbukkit.bridge.NativePatchBukkit; import com.google.common.collect.Sets; diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/registry/PatchBukkitRegistry.java b/java/patchbukkit/src/main/java/org/patchbukkit/registry/PatchBukkitRegistry.java index b734ad2..ee66859 100644 --- a/java/patchbukkit/src/main/java/org/patchbukkit/registry/PatchBukkitRegistry.java +++ b/java/patchbukkit/src/main/java/org/patchbukkit/registry/PatchBukkitRegistry.java @@ -12,57 +12,44 @@ import org.bukkit.NamespacedKey; import org.bukkit.Registry; import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; -import org.patchbukkit.bridge.NativePatchBukkit; +import patchbukkit.bridge.NativeBridgeFfi; +import patchbukkit.registry.GetRegistryDataRequest; +import patchbukkit.registry.GetRegistryDataResponse; +import patchbukkit.registry.RegistryType; import java.util.*; import java.util.function.Function; import java.util.stream.Stream; -public class PatchBukkitRegistry implements Registry { +public class PatchBukkitRegistry implements Registry { private final Map entries = new LinkedHashMap<>(); private final Map> tags = new LinkedHashMap<>(); - /** - * Build a registry by fetching JSON from the native side and parsing - * entries and tags from the combined response. - * - * Expected JSON shape: - * { - * "entries": [{"name": "...", "id": ...}, ...], - * "tags": {"minecraft:logs": ["minecraft:oak_log", ...], ...} - * } - * - * @param registryName The native registry name (e.g. "sound_event") - * @param registryKey The Paper RegistryKey for this registry - * @param factory Converts a single JsonObject into a B instance - */ public PatchBukkitRegistry( - String registryName, - RegistryKey registryKey, - Function factory + RegistryType registryType, + Function> extractor, + Function factory ) { - String json = NativePatchBukkit.getRegistryData(registryName); - if (json == null) return; + if (registryType == null) return; - JsonObject root = JsonParser.parseString(json).getAsJsonObject(); + GetRegistryDataRequest request = GetRegistryDataRequest.newBuilder() + .setRegistry(registryType) + .build(); - JsonArray entriesArray = root.getAsJsonArray("entries"); - if (entriesArray != null) { - for (JsonElement element : entriesArray) { - B value = factory.apply(element.getAsJsonObject()); - if (value != null) { - entries.put(value.getKey(), value); - } + GetRegistryDataResponse response = NativeBridgeFfi.getRegistryData(request); + if (response == null) return; + + List

protoEntries = extractor.apply(response); + for (P protoEntry : protoEntries) { + B value = factory.apply(protoEntry); + if (value != null) { + entries.put(value.getKey(), value); } } - - JsonElement tagsElement = root.get("tags"); - if (tagsElement != null && tagsElement.isJsonObject()) { - loadTags(tagsElement.getAsJsonObject(), registryKey); - } } + private void loadTags(JsonObject tagMap, RegistryKey registryKey) { for (Map.Entry entry : tagMap.entrySet()) { String tagName = entry.getKey(); @@ -116,6 +103,14 @@ public class PatchBukkitRegistry implements Registry { return tag; } + public static PatchBukkitRegistry empty(RegistryKey registryKey) { + return new PatchBukkitRegistry<>( + null, + response -> Collections.emptyList(), + obj -> null + ); + } + @Override public @NonNull Collection> getTags() { return Collections.unmodifiableCollection(tags.values()); diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/registry/PatchBukkitRegistryAccess.java b/java/patchbukkit/src/main/java/org/patchbukkit/registry/PatchBukkitRegistryAccess.java index 9e30a7e..854db6f 100644 --- a/java/patchbukkit/src/main/java/org/patchbukkit/registry/PatchBukkitRegistryAccess.java +++ b/java/patchbukkit/src/main/java/org/patchbukkit/registry/PatchBukkitRegistryAccess.java @@ -6,11 +6,13 @@ import org.bukkit.Keyed; import org.bukkit.Registry; import org.bukkit.Sound; import org.jspecify.annotations.Nullable; -import org.patchbukkit.bridge.NativePatchBukkit; import com.google.gson.JsonObject; +import patchbukkit.registry.GetRegistryDataResponse; +import patchbukkit.registry.RegistryType; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; @@ -22,21 +24,21 @@ public class PatchBukkitRegistryAccess implements RegistryAccess { * Maps a RegistryKey to (native registry name, factory function). * Add an entry here for each registry you support. */ - private static final Map, RegistryFactory> FACTORIES = Map.of( + private static final Map, RegistryFactory> FACTORIES = Map.of( RegistryKey.SOUND_EVENT, new RegistryFactory<>( - "sound_event", - RegistryKey.SOUND_EVENT, - json -> new PatchBukkitSound( - json.get("name").getAsString(), - json.get("id").getAsInt() + RegistryType.SOUND_EVENT, + response -> response.getSoundEvent().getSoundEventsList(), + data -> new PatchBukkitSound( + data.getName(), + data.getId() ) ) ); - private record RegistryFactory( - String nativeRegistryName, - RegistryKey registryKey, - Function factory + private record RegistryFactory( + RegistryType registryType, + Function> extractor, + Function factory ) {} @Override @@ -51,25 +53,21 @@ public class PatchBukkitRegistryAccess implements RegistryAccess { if (registryKey == null) return null; return (Registry) instances.computeIfAbsent(registryKey, key -> { - RegistryFactory factoryEntry = FACTORIES.get(key); + RegistryFactory factoryEntry = FACTORIES.get(key); if (factoryEntry != null) { return buildRegistry(factoryEntry); } - - // We return an empty registry for unsupported types, since if we don't the server will crash immediately when the registry class is first loaded - return new PatchBukkitRegistry<>( - key.key().value(), - (RegistryKey) key, - json -> null - ); + // Return an empty registry for unsupported types + return PatchBukkitRegistry.empty((RegistryKey) key); }); } - private static PatchBukkitRegistry buildRegistry(RegistryFactory factory) { + @SuppressWarnings("unchecked") + private static PatchBukkitRegistry buildRegistry(RegistryFactory factory) { return new PatchBukkitRegistry<>( - factory.nativeRegistryName(), - factory.registryKey(), - factory.factory() + factory.registryType(), + factory.extractor(), + factory.factory() ); } diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/world/PatchBukkitWorld.java b/java/patchbukkit/src/main/java/org/patchbukkit/world/PatchBukkitWorld.java index 8a27e19..e8c2c36 100644 --- a/java/patchbukkit/src/main/java/org/patchbukkit/world/PatchBukkitWorld.java +++ b/java/patchbukkit/src/main/java/org/patchbukkit/world/PatchBukkitWorld.java @@ -1589,10 +1589,7 @@ public class PatchBukkitWorld @Override public @NotNull UUID getUID() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException( - "Unimplemented method 'getUID'" - ); + return this.uuid; } @Override diff --git a/proto/patchbukkit/bridge.proto b/proto/patchbukkit/bridge.proto index a4e7d8b..5338248 100644 --- a/proto/patchbukkit/bridge.proto +++ b/proto/patchbukkit/bridge.proto @@ -7,6 +7,8 @@ import "patchbukkit/abilities.proto"; import "patchbukkit/common/types.proto"; import "patchbukkit/events/event.proto"; import "patchbukkit/message.proto"; +import "patchbukkit/registry.proto"; +import "patchbukkit/sound.proto"; service NativeBridge { rpc GetAbilities(patchbukkit.common.UUID) returns (patchbukkit.abilities.Abilities); @@ -18,4 +20,9 @@ service NativeBridge { rpc SendMessage(patchbukkit.message.SendMessageRequest) returns (google.protobuf.Empty); rpc GetLocation(patchbukkit.common.UUID) returns (patchbukkit.common.Location); + + rpc GetRegistryData(patchbukkit.registry.GetRegistryDataRequest) returns (patchbukkit.registry.GetRegistryDataResponse); + + rpc PlayerEntityPlaySound(patchbukkit.sound.PlayerEntityPlaySoundRequest) returns (google.protobuf.Empty); + rpc PlayerPlaySound(patchbukkit.sound.PlayerPlaySoundRequest) returns (google.protobuf.Empty); } diff --git a/proto/patchbukkit/registry.proto b/proto/patchbukkit/registry.proto new file mode 100644 index 0000000..a7dac34 --- /dev/null +++ b/proto/patchbukkit/registry.proto @@ -0,0 +1,31 @@ +syntax = "proto3"; + +package patchbukkit.registry; + +import "patchbukkit/common/types.proto"; + +option java_multiple_files = true; +option java_package = "patchbukkit.registry"; + +enum RegistryType { + SOUND_EVENT = 0; +} + +message GetRegistryDataRequest { + RegistryType registry = 1; +} + +message GetRegistryDataResponse { + oneof registry { + SoundEventRegistryData sound_event = 1; + } +} + +message SoundEvent { + uint32 id = 1; + string name = 2; +} + +message SoundEventRegistryData { + repeated SoundEvent sound_events = 1; +} diff --git a/proto/patchbukkit/sound.proto b/proto/patchbukkit/sound.proto new file mode 100644 index 0000000..b85b4fc --- /dev/null +++ b/proto/patchbukkit/sound.proto @@ -0,0 +1,31 @@ +syntax = "proto3"; + +package patchbukkit.sound; + +import "patchbukkit/common/types.proto"; + +option java_multiple_files = true; +option java_package = "patchbukkit.sound"; + +message Sound { + string name = 1; + string category = 2; +} + +message PlayerEntityPlaySoundRequest { + patchbukkit.common.UUID player_uuid = 1; + Sound sound = 2; + patchbukkit.common.UUID entity_uuid = 3; + float volume = 4; + float pitch = 5; + optional int64 seed = 6; +} + +message PlayerPlaySoundRequest { + patchbukkit.common.UUID player_uuid = 1; + Sound sound = 2; + patchbukkit.common.Location location = 3; + float volume = 4; + float pitch = 5; + optional int64 seed = 6; +} diff --git a/rust/src/java/native_callbacks/location.rs b/rust/src/java/native_callbacks/location.rs index 9ba6ff2..661ce6b 100644 --- a/rust/src/java/native_callbacks/location.rs +++ b/rust/src/java/native_callbacks/location.rs @@ -1,9 +1,7 @@ -use std::ffi::c_char; - use pumpkin::command::args::entities::{EntitySelectorType, TargetSelector}; use crate::{ - java::native_callbacks::{CALLBACK_CONTEXT, utils::get_string}, + java::native_callbacks::CALLBACK_CONTEXT, proto::patchbukkit::common::{Location, Uuid, Vec3, World}, }; diff --git a/rust/src/java/native_callbacks/memory.rs b/rust/src/java/native_callbacks/memory.rs deleted file mode 100644 index e095ef8..0000000 --- a/rust/src/java/native_callbacks/memory.rs +++ /dev/null @@ -1,9 +0,0 @@ -use std::ffi::{CString, c_char}; - -pub extern "C" fn rust_free_string(ptr: *mut c_char) { - if !ptr.is_null() { - unsafe { - drop(CString::from_raw(ptr)); - } - } -} diff --git a/rust/src/java/native_callbacks/mod.rs b/rust/src/java/native_callbacks/mod.rs index 8057a55..dc65f60 100644 --- a/rust/src/java/native_callbacks/mod.rs +++ b/rust/src/java/native_callbacks/mod.rs @@ -1,7 +1,7 @@ use std::sync::{Arc, OnceLock}; use anyhow::Result; -use j4rs::{InvocationArg, Jvm}; +use j4rs::Jvm; use pumpkin::plugin::Context; use tokio::sync::mpsc; @@ -16,9 +16,6 @@ pub use events::*; pub mod location; pub use location::*; -pub mod memory; -pub use memory::*; - pub mod message; pub use message::*; @@ -28,9 +25,6 @@ pub use registry::*; pub mod sound; pub use sound::*; -pub mod world; -pub use world::*; - pub mod utils; static CALLBACK_CONTEXT: OnceLock = OnceLock::new(); @@ -58,47 +52,6 @@ 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 free_string_addr = memory::rust_free_string as *const () as i64; - let get_world_addr = world::rust_get_world as *const () as i64; - let rust_get_registry_data_addr = registry::rust_get_registry_data as *const () as i64; - let rust_player_entity_play_sound_addr = - sound::rust_player_entity_play_sound as *const () as i64; - let rust_player_play_sound_addr = sound::rust_player_play_sound as *const () as i64; - - jvm.invoke_static( - "org.patchbukkit.bridge.NativePatchBukkit", - "initCallbacks", - &[ - InvocationArg::try_from(free_string_addr)?.into_primitive()?, - InvocationArg::try_from(get_world_addr)?.into_primitive()?, - InvocationArg::try_from(rust_get_registry_data_addr)?.into_primitive()?, - InvocationArg::try_from(rust_player_entity_play_sound_addr)?.into_primitive()?, - InvocationArg::try_from(rust_player_play_sound_addr)?.into_primitive()?, - ], - )?; - - initialize_ffi_callbacks(jvm)?; - - Ok(()) + initialize_ffi_callbacks(jvm) } diff --git a/rust/src/java/native_callbacks/registry.rs b/rust/src/java/native_callbacks/registry.rs index f9a5319..421b46a 100644 --- a/rust/src/java/native_callbacks/registry.rs +++ b/rust/src/java/native_callbacks/registry.rs @@ -1,60 +1,30 @@ -use std::{collections::HashMap, ffi::CString, os::raw::c_char}; +use crate::proto::patchbukkit::registry::{ + GetRegistryDataRequest, GetRegistryDataResponse, RegistryType, SoundEvent, + SoundEventRegistryData, get_registry_data_response::Registry, +}; -use serde::Serialize; -use serde_json::Value; - -use crate::java::native_callbacks::utils::get_string; - -#[derive(Serialize, Debug, Clone)] -struct Registry { - pub entries: Vec, - pub tags: HashMap>, -} - -impl Registry { - pub const fn new(entries: Vec, tags: HashMap>) -> Self { - Self { entries, tags } - } -} - -#[must_use] -pub extern "C" fn rust_get_registry_data(registry_name: *const c_char) -> *const c_char { - let name = get_string(registry_name); - - let registry: Option = match name.as_ref() { - "sound_event" => { - let entries: Vec = pumpkin_data::sound::Sound::slice() +pub fn ffi_native_bridge_get_registry_data_impl( + request: GetRegistryDataRequest, +) -> Option { + let registry = match request.registry { + val if val == RegistryType::SoundEvent as i32 => { + let sounds = pumpkin_data::sound::Sound::slice() .iter() .map(|s| { - serde_json::json!({ - "name": s.to_name(), - "id": *s as u16 - }) + let mut sound_event = SoundEvent::default(); + sound_event.id = *s as u32; + sound_event.name = s.to_name().to_string(); + sound_event }) - .collect(); - Some(Registry::new(entries, HashMap::new())) + .collect::>(); + let mut data = SoundEventRegistryData::default(); + data.sound_events = sounds; + Registry::SoundEvent(data) } - _ => None, + _ => unreachable!(), }; - let registry = match registry { - Some(registry) => registry, - None => return std::ptr::null(), - }; - - let json_str = match serde_json::to_string(®istry) { - Ok(json_str) => json_str, - Err(err) => { - log::error!("Failed to serialize registry: {err}"); - return std::ptr::null(); - } - }; - - match CString::new(json_str) { - Ok(cstring) => cstring.into_raw(), - Err(err) => { - log::error!("Failed to convert registry string to CString: {err}"); - std::ptr::null_mut() - } - } + let mut response = GetRegistryDataResponse::default(); + response.registry = Some(registry); + Some(response) } diff --git a/rust/src/java/native_callbacks/sound.rs b/rust/src/java/native_callbacks/sound.rs index 7b22112..4ee229b 100644 --- a/rust/src/java/native_callbacks/sound.rs +++ b/rust/src/java/native_callbacks/sound.rs @@ -1,111 +1,89 @@ -use std::ffi::{c_char, c_double, c_float}; - use pumpkin::command::args::entities::{EntitySelectorType, TargetSelector}; use pumpkin_data::sound::{Sound, SoundCategory}; use pumpkin_protocol::{IdOr, java::client::play::CEntitySoundEffect}; use pumpkin_util::math::vector3::Vector3; use rand::{RngExt, rng}; -use crate::java::native_callbacks::{CALLBACK_CONTEXT, utils::get_string}; +use crate::{ + java::native_callbacks::CALLBACK_CONTEXT, + proto::patchbukkit::sound::{PlayerEntityPlaySoundRequest, PlayerPlaySoundRequest}, +}; -pub extern "C" fn rust_player_entity_play_sound( - player_uuid_ptr: *const c_char, - sound_name_ptr: *const c_char, - sound_category_ptr: *const c_char, - entity_uuid_ptr: *const c_char, - volume: c_float, - pitch: c_float, -) { - let player_uuid_str = get_string(player_uuid_ptr); - let sound_name = get_string(sound_name_ptr); - let sound_category = get_string(sound_category_ptr); - let entity_uuid_str = get_string(entity_uuid_ptr); +pub fn ffi_native_bridge_player_entity_play_sound_impl( + request: PlayerEntityPlaySoundRequest, +) -> Option<()> { + log::error!("PlayerEntityPlaySoundRequest sent"); + let ctx = CALLBACK_CONTEXT.get()?; + let player_uuid = uuid::Uuid::parse_str(&request.player_uuid?.value).ok()?; + let entity_uuid = uuid::Uuid::parse_str(&request.entity_uuid?.value).ok()?; - if let Some(ctx) = CALLBACK_CONTEXT.get() { - let player_uuid = uuid::Uuid::parse_str(&player_uuid_str).unwrap(); - let entity_uuid = uuid::Uuid::parse_str(&entity_uuid_str).unwrap(); + let player = ctx.plugin_context.server.get_player_by_uuid(player_uuid)?; - ctx.runtime.spawn(async move { - let player = ctx.plugin_context.server.get_player_by_uuid(player_uuid); + let sound = request.sound?; + let pumpkin_sound = Sound::from_name(&sound.name)?; + let category = SoundCategory::from_name(&sound.category.to_lowercase())?; - let entity = ctx.plugin_context.server.select_entities( - &TargetSelector::new(EntitySelectorType::Uuid(entity_uuid)), - None, - ); - if entity.len() != 1 { - return; - } + let seed = rng().random::(); - let entity = entity.first().unwrap().get_entity(); + ctx.runtime.spawn(async move { + let entity = ctx.plugin_context.server.select_entities( + &TargetSelector::new(EntitySelectorType::Uuid(entity_uuid)), + None, + ); - if let Some(player) = player { - let sound = match Sound::from_name(&sound_name) { - Some(sound) => sound, - None => return, - }; + if entity.len() != 1 { + return None; + } - let category = match SoundCategory::from_name(&sound_category) { - Some(category) => category, - None => return, - }; + let entity = entity.first()?.get_entity(); - let seed = rng().random::(); + player + .client + .enqueue_packet(&CEntitySoundEffect::new( + IdOr::Id(pumpkin_sound as u16), + category, + entity.entity_id.into(), + request.volume, + request.pitch, + seed, + )) + .await; - player - .client - .enqueue_packet(&CEntitySoundEffect::new( - IdOr::Id(sound as u16), - category, - entity.entity_id.into(), - volume, - pitch, - seed, - )) - .await; - } - }); - } + Some(()) + }); + + Some(()) } -pub extern "C" fn rust_player_play_sound( - player_uuid_ptr: *const c_char, - sound_name_ptr: *const c_char, - sound_category_ptr: *const c_char, - x: c_double, - y: c_double, - z: c_double, - volume: c_float, - pitch: c_float, -) { - let player_uuid_str = get_string(player_uuid_ptr); - let sound_name = get_string(sound_name_ptr); - let sound_category = get_string(sound_category_ptr); +pub fn ffi_native_bridge_player_play_sound_impl(request: PlayerPlaySoundRequest) -> Option<()> { + let ctx = CALLBACK_CONTEXT.get()?; + let player_uuid = uuid::Uuid::parse_str(&request.player_uuid?.value).ok()?; + let player = ctx.plugin_context.server.get_player_by_uuid(player_uuid)?; - if let Some(ctx) = CALLBACK_CONTEXT.get() { - let player_uuid = uuid::Uuid::parse_str(&player_uuid_str).unwrap(); + let sound = request.sound?; + let pumpkin_sound = Sound::from_name(&sound.name)?; + let category = SoundCategory::from_name(&sound.category.to_lowercase())?; - ctx.runtime.spawn(async move { - let player = ctx.plugin_context.server.get_player_by_uuid(player_uuid); + let seed = match request.seed { + Some(seed) => seed as f64, + None => rng().random::(), + }; - if let Some(player) = player { - let sound = match Sound::from_name(&sound_name) { - Some(sound) => sound, - None => return, - }; + let position = request.location?.position?; + let position: Vector3 = Vector3::new(position.x, position.y, position.z); - let category = match SoundCategory::from_name(&sound_category.to_lowercase()) { - Some(category) => category, - None => return, - }; + ctx.runtime.spawn(async move { + player + .play_sound( + pumpkin_sound as u16, + category, + &position, + request.volume, + request.pitch, + seed, + ) + .await; + }); - let seed = rng().random::(); - - let position: Vector3 = Vector3::new(x, y, z); - - player - .play_sound(sound as u16, category, &position, volume, pitch, seed) - .await; - } - }); - } + Some(()) } diff --git a/rust/src/java/native_callbacks/world.rs b/rust/src/java/native_callbacks/world.rs deleted file mode 100644 index d9403e5..0000000 --- a/rust/src/java/native_callbacks/world.rs +++ /dev/null @@ -1,39 +0,0 @@ -use std::ffi::{CString, c_char}; - -use pumpkin::command::args::entities::{EntitySelectorType, TargetSelector}; - -use crate::java::native_callbacks::{CALLBACK_CONTEXT, utils::get_string}; - -pub extern "C" fn rust_get_world(entity_uuid_ptr: *const c_char) -> *mut c_char { - if entity_uuid_ptr.is_null() { - return std::ptr::null_mut(); - } - - let entity_uuid = get_string(entity_uuid_ptr); - - let Some(ctx) = CALLBACK_CONTEXT.get() else { - return std::ptr::null_mut(); - }; - - let Ok(uuid) = uuid::Uuid::parse_str(&entity_uuid) else { - return std::ptr::null_mut(); - }; - - let entities = ctx - .plugin_context - .server - .select_entities(&TargetSelector::new(EntitySelectorType::Uuid(uuid)), None); - - if entities.len() != 1 { - return std::ptr::null_mut(); - } - - let entity = entities.first().unwrap().get_entity(); - let world = entity.world.load(); - let world_uuid = world.uuid.to_string(); - - match CString::new(world_uuid) { - Ok(cstring) => cstring.into_raw(), // Transfers ownership to caller - Err(_) => std::ptr::null_mut(), - } -} diff --git a/rust/src/proto/mod.rs b/rust/src/proto/mod.rs index caf231c..64a6435 100644 --- a/rust/src/proto/mod.rs +++ b/rust/src/proto/mod.rs @@ -18,6 +18,14 @@ pub mod patchbukkit { pub mod message { include!(concat!(env!("OUT_DIR"), "/patchbukkit.message.rs")); } + + pub mod registry { + include!(concat!(env!("OUT_DIR"), "/patchbukkit.registry.rs")); + } + + pub mod sound { + include!(concat!(env!("OUT_DIR"), "/patchbukkit.sound.rs")); + } } include!(concat!(env!("OUT_DIR"), "/ffi_init.rs"));