Skip to content

Commit

Permalink
Use reflection to use sun.misc.Unsafe
Browse files Browse the repository at this point in the history
  • Loading branch information
MrMicky-FR committed Jun 11, 2021
1 parent bf99e9f commit b2281e3
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 45 deletions.
35 changes: 15 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -50,17 +50,15 @@ Lightweight packet-based scoreboard API for Bukkit plugins, with 1.7.10 to 1.16
</plugin>
</plugins>
</build>
```
```xml

<dependencies>
<dependency>
<groupId>fr.mrmicky</groupId>
<artifactId>fastboard</artifactId>
<version>1.2.0-SNAPSHOT</version>
</dependency>
</dependencies>
```
```xml

<repositories>
<repository>
<id>sonatype-oss</id>
Expand All @@ -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'
Expand All @@ -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);
Expand All @@ -113,7 +108,7 @@ board.updateTitle(ChatColor.GOLD + "FastBoard");
board.updateLines(
"", // Empty line
"One line",
"", // Empty line
"",
"Second line"
);
```
Expand Down
17 changes: 8 additions & 9 deletions src/main/java/fr/mrmicky/fastboard/FastBoard.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
* <p>
* The project is on <a href="https://github.com/MrMicky-FR/FastBoard">GitHub</a>.
*
Expand All @@ -55,6 +55,9 @@
public class FastBoard {

private static final Map<Class<?>, 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;
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}
}

Expand Down
35 changes: 19 additions & 16 deletions src/main/java/fr/mrmicky/fastboard/FastReflection.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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";

Expand All @@ -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> 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();
Expand Down Expand Up @@ -130,14 +120,27 @@ static Class<?> innerClass(Class<?> parentClass, Predicate<Class<?>> 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
Expand Down

0 comments on commit b2281e3

Please sign in to comment.