From b2281e36bda3d9cee316f87247ccee7933f64c61 Mon Sep 17 00:00:00 2001 From: MrMicky Date: Fri, 11 Jun 2021 23:21:37 +0200 Subject: [PATCH] Use reflection to use `sun.misc.Unsafe` --- README.md | 35 ++++++++----------- .../java/fr/mrmicky/fastboard/FastBoard.java | 17 +++++---- .../fr/mrmicky/fastboard/FastReflection.java | 35 ++++++++++--------- 3 files changed, 42 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index 47bae99..351befa 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,22 @@ # FastBoard [![Java CI](https://github.com/MrMicky-FR/FastBoard/actions/workflows/build.yml/badge.svg)](https://github.com/MrMicky-FR/FastBoard/actions/workflows/build.yml) -[![Sonatype Snapshots](https://img.shields.io/nexus/s/fr.mrmicky/fastboard?label=sonatype%20snapshots&server=https%3A%2F%2Fs01.oss.sonatype.org)](pom.xml) -[![Discord](https://img.shields.io/discord/390919659874156560.svg?colorB=7289da&label=discord&logo=discord&logoColor=white)](https://discord.gg/q9UwaBT) +[![Sonatype Snapshots](https://img.shields.io/nexus/s/fr.mrmicky/fastboard?label=Sonatype%20snapshots&server=https%3A%2F%2Fs01.oss.sonatype.org)](pom.xml) +[![Discord](https://img.shields.io/discord/390919659874156560.svg?colorB=5865f2&label=Discord&logo=discord&logoColor=white)](https://discord.gg/q9UwaBT) -Lightweight packet-based scoreboard API for Bukkit plugins, with 1.7.10 to 1.16 support. +Lightweight packet-based scoreboard API for Bukkit plugins, with 1.7.10 to 1.17 support. ⚠️ To use FastBoard on a 1.8 server, the server must be on 1.8.8. ## Features * No flickering (without using a buffer) -* Works with all versions from 1.7.10 to 1.16 -* Very small (around 550 lines of code with the JavaDoc) and no dependencies +* Works with all versions from 1.7.10 to 1.17 +* Very small (around 600 lines of code with the JavaDoc) and no dependencies * Easy to use * Dynamic scoreboard size: you don't need to add/remove lines, you can just give a string list (or array) to change all the lines * Everything is at packet level, so it works with other plugins using scoreboard and/or teams -* Can be used in an async thread -* Supports up to 30 characters per line on 1.7-1.12 +* Can be used asynchronously +* Supports up to 30 characters per line on 1.12.2 and below * No character limit on 1.13 and higher * Supports hex colors on 1.16 and higher @@ -50,8 +50,7 @@ Lightweight packet-based scoreboard API for Bukkit plugins, with 1.7.10 to 1.16 -``` -```xml + fr.mrmicky @@ -59,8 +58,7 @@ Lightweight packet-based scoreboard API for Bukkit plugins, with 1.7.10 to 1.16 1.2.0-SNAPSHOT -``` -```xml + sonatype-oss @@ -75,18 +73,15 @@ Lightweight packet-based scoreboard API for Bukkit plugins, with 1.7.10 to 1.16 plugins { id 'com.github.johnrengelman.shadow' version '6.1.0' } -``` -```groovy + repositories { maven { url 'https://s01.oss.sonatype.org/content/repositories/snapshots/' } } -``` -```groovy + dependencies { implementation 'fr.mrmicky:fastboard:1.2.0-SNAPSHOT' } -``` -```groovy + shadowJar { // Replace 'com.yourpackage' with the package of your plugin relocate 'fr.mrmicky.fastboard', 'com.yourpackage.fastboard' @@ -95,13 +90,13 @@ shadowJar { ### Manual -Copy `FastBoard.java` and `FastReflection.java` in your plugin +Copy `FastBoard.java` and `FastReflection.java` in your plugin. ## Usage ### Creating a scoreboard -Just create a new `FastBoard` and update the title and the lines +Just create a new `FastBoard` and update the title and the lines: ```java FastBoard board = new FastBoard(player); @@ -113,7 +108,7 @@ board.updateTitle(ChatColor.GOLD + "FastBoard"); board.updateLines( "", // Empty line "One line", - "", // Empty line + "", "Second line" ); ``` diff --git a/src/main/java/fr/mrmicky/fastboard/FastBoard.java b/src/main/java/fr/mrmicky/fastboard/FastBoard.java index 8c72b70..df0fab8 100644 --- a/src/main/java/fr/mrmicky/fastboard/FastBoard.java +++ b/src/main/java/fr/mrmicky/fastboard/FastBoard.java @@ -45,7 +45,7 @@ /** * Lightweight packet-based scoreboard API for Bukkit plugins. - * It can be used safely in an async thread as everything is at packet level. + * It can be safely used asynchronously as everything is at packet level. *

* The project is on GitHub. * @@ -55,6 +55,9 @@ public class FastBoard { private static final Map, Field[]> PACKETS = new HashMap<>(8); + private static final String[] COLOR_CODES = Arrays.stream(ChatColor.values()) + .map(Object::toString) + .toArray(String[]::new); private static final VersionType VERSION_TYPE; // Packets and components private static final Class CHAT_COMPONENT_CLASS; @@ -433,15 +436,11 @@ private void checkLineNumber(int line, boolean checkInRange, boolean checkMax) { throw new IllegalArgumentException("Line number must be under " + this.lines.size()); } - if (checkMax && line >= ChatColor.values().length - 1) { + if (checkMax && line >= COLOR_CODES.length - 1) { throw new IllegalArgumentException("Line number is too high: " + this.lines.size()); } } - private String getColorCode(int score) { - return ChatColor.values()[score].toString(); - } - private int getScoreByLine(int line) { return this.lines.size() - line - 1; } @@ -485,7 +484,7 @@ private void sendDisplayObjectivePacket() throws Throwable { private void sendScorePacket(int score, ScoreboardAction action) throws Throwable { Object packet = PACKET_SB_SCORE.invoke(); - setField(packet, String.class, getColorCode(score), 0); // Player Name + setField(packet, String.class, COLOR_CODES[score], 0); // Player Name if (VersionType.V1_8.isHigherOrEqual()) { setField(packet, ENUM_SB_ACTION, action == ScoreboardAction.REMOVE ? ENUM_SB_ACTION_REMOVE : ENUM_SB_ACTION_CHANGE); @@ -518,7 +517,7 @@ private void sendTeamPacket(int score, TeamMode mode) throws Throwable { String suffix = null; if (line == null || line.isEmpty()) { - prefix = getColorCode(score) + ChatColor.RESET; + prefix = COLOR_CODES[score] + ChatColor.RESET; } else if (line.length() <= maxLength) { prefix = line; } else { @@ -562,7 +561,7 @@ private void sendTeamPacket(int score, TeamMode mode) throws Throwable { } if (mode == TeamMode.CREATE) { - setField(packet, Collection.class, Collections.singletonList(getColorCode(score))); // Players in the team + setField(packet, Collection.class, Collections.singletonList(COLOR_CODES[score])); // Players in the team } } diff --git a/src/main/java/fr/mrmicky/fastboard/FastReflection.java b/src/main/java/fr/mrmicky/fastboard/FastReflection.java index 0c7cc12..69a9b6f 100644 --- a/src/main/java/fr/mrmicky/fastboard/FastReflection.java +++ b/src/main/java/fr/mrmicky/fastboard/FastReflection.java @@ -23,17 +23,15 @@ */ package fr.mrmicky.fastboard; -import com.google.common.base.Suppliers; import org.bukkit.Bukkit; -import sun.misc.Unsafe; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.lang.reflect.Field; +import java.lang.reflect.Method; import java.util.Optional; import java.util.function.Predicate; -import java.util.function.Supplier; /** * Small reflection utility class to use CraftBukkit and NMS. @@ -42,7 +40,7 @@ */ public final class FastReflection { - public static final String NM_PACKAGE = "net.minecraft"; + private static final String NM_PACKAGE = "net.minecraft"; public static final String OBC_PACKAGE = "org.bukkit.craftbukkit"; public static final String NMS_PACKAGE = NM_PACKAGE + ".server"; @@ -51,15 +49,7 @@ public final class FastReflection { private static final MethodType VOID_METHOD_TYPE = MethodType.methodType(void.class); private static final boolean NMS_REPACKAGED = optionalClass(NM_PACKAGE + ".network.protocol.Packet").isPresent(); - private static final Supplier UNSAFE = Suppliers.memoize(() -> { - try { - Field f = Unsafe.class.getDeclaredField("theUnsafe"); - f.setAccessible(true); - return (Unsafe) f.get(null); - } catch (NoSuchFieldException | IllegalAccessException e) { - throw new RuntimeException(e); - } - }); + private static volatile Object theUnsafe; private FastReflection() { throw new UnsupportedOperationException(); @@ -130,14 +120,27 @@ static Class innerClass(Class parentClass, Predicate> classPredic throw new ClassNotFoundException("No class in " + parentClass.getCanonicalName() + " matches the predicate."); } - public static PacketConstructor findPacketConstructor(Class packetClass, MethodHandles.Lookup lookup) { + public static PacketConstructor findPacketConstructor(Class packetClass, MethodHandles.Lookup lookup) throws Exception { try { MethodHandle constructor = lookup.findConstructor(packetClass, VOID_METHOD_TYPE); return constructor::invoke; } catch (NoSuchMethodException | IllegalAccessException e) { - Unsafe unsafe = UNSAFE.get(); - return () -> unsafe.allocateInstance(packetClass); + // try below with Unsafe + } + + if (theUnsafe == null) { + synchronized (FastReflection.class) { + if (theUnsafe == null) { + Class unsafeClass = Class.forName("sun.misc.Unsafe"); + Field theUnsafeField = unsafeClass.getDeclaredField("theUnsafe"); + theUnsafeField.setAccessible(true); + theUnsafe = theUnsafeField.get(null); + } + } } + + Method allocateMethod = theUnsafe.getClass().getMethod("allocateInstance", Class.class); + return () -> allocateMethod.invoke(theUnsafe, packetClass); } @FunctionalInterface