Skip to content

Commit fbef18d

Browse files
committed
Improve reflection performances
1 parent 180fff0 commit fbef18d

2 files changed

Lines changed: 71 additions & 57 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# FastBoard
22
[![Java CI](https://github.com/MrMicky-FR/FastBoard/actions/workflows/build.yml/badge.svg)](https://github.com/MrMicky-FR/FastBoard/actions/workflows/build.yml)
3-
[![Sonatype Snapshots](https://img.shields.io/nexus/s/fr.mrmicky/fastboard?label=sonatype%20snapshots&server=https%3A%2F%2Fs01.oss.sonatype.org)]()
3+
[![Sonatype Snapshots](https://img.shields.io/nexus/s/fr.mrmicky/fastboard?label=sonatype%20snapshots&server=https%3A%2F%2Fs01.oss.sonatype.org)](pom.xml)
44
[![Discord](https://img.shields.io/discord/390919659874156560.svg?colorB=7289da&label=discord&logo=discord&logoColor=white)](https://discord.gg/q9UwaBT)
55

66
Lightweight packet-based scoreboard API for Bukkit plugins, with 1.7.10 to 1.16 support.

src/main/java/fr/mrmicky/fastboard/FastBoard.java

Lines changed: 70 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -26,16 +26,20 @@
2626
import org.bukkit.ChatColor;
2727
import org.bukkit.entity.Player;
2828

29+
import java.lang.invoke.MethodHandle;
30+
import java.lang.invoke.MethodHandles;
31+
import java.lang.invoke.MethodType;
2932
import java.lang.reflect.Array;
30-
import java.lang.reflect.Constructor;
3133
import java.lang.reflect.Field;
32-
import java.lang.reflect.Method;
3334
import java.util.ArrayList;
3435
import java.util.Arrays;
3536
import java.util.Collection;
3637
import java.util.Collections;
38+
import java.util.HashMap;
3739
import java.util.List;
40+
import java.util.Map;
3841
import java.util.Objects;
42+
import java.util.concurrent.ThreadLocalRandom;
3943

4044
/**
4145
* Lightweight packet-based scoreboard API for Bukkit plugins.
@@ -48,19 +52,19 @@
4852
*/
4953
public class FastBoard {
5054

55+
private static final Map<Class<?>, List<Field>> PACKETS = new HashMap<>(8);
5156
private static final VersionType VERSION_TYPE;
52-
// Packets sending
53-
private static final Field PLAYER_CONNECTION;
54-
private static final Method SEND_PACKET;
55-
private static final Method PLAYER_GET_HANDLE;
56-
// Chat components
57+
// Packets and components
5758
private static final Class<?> CHAT_COMPONENT_CLASS;
58-
private static final Method MESSAGE_FROM_STRING;
59+
private static final MethodHandle MESSAGE_FROM_STRING;
60+
private static final MethodHandle PLAYER_CONNECTION;
61+
private static final MethodHandle SEND_PACKET;
62+
private static final MethodHandle PLAYER_GET_HANDLE;
5963
// Scoreboard packets
60-
private static final Constructor<?> PACKET_SB_OBJ;
61-
private static final Constructor<?> PACKET_SB_DISPLAY_OBJ;
62-
private static final Constructor<?> PACKET_SB_SCORE;
63-
private static final Constructor<?> PACKET_SB_TEAM;
64+
private static final MethodHandle PACKET_SB_OBJ;
65+
private static final MethodHandle PACKET_SB_DISPLAY_OBJ;
66+
private static final MethodHandle PACKET_SB_SCORE;
67+
private static final MethodHandle PACKET_SB_TEAM;
6468
// Scoreboard enums
6569
private static final Class<?> ENUM_SB_HEALTH_DISPLAY;
6670
private static final Class<?> ENUM_SB_ACTION;
@@ -70,6 +74,9 @@ public class FastBoard {
7074

7175
static {
7276
try {
77+
MethodHandles.Lookup lookup = MethodHandles.lookup();
78+
MethodType voidType = MethodType.methodType(void.class);
79+
7380
if (FastReflection.nmsOptionalClass("ScoreboardServer$Action").isPresent()) {
7481
VERSION_TYPE = VersionType.V1_13;
7582
} else if (FastReflection.nmsOptionalClass("IScoreboardCriteria$EnumScoreboardHealthDisplay").isPresent()) {
@@ -78,22 +85,31 @@ public class FastBoard {
7885
VERSION_TYPE = VersionType.V1_7;
7986
}
8087

88+
Class<?> craftPlayerClass = FastReflection.obcClass("entity.CraftPlayer");
8189
Class<?> craftChatMessageClass = FastReflection.obcClass("util.CraftChatMessage");
8290
Class<?> entityPlayerClass = FastReflection.nmsClass("EntityPlayer");
8391
Class<?> playerConnectionClass = FastReflection.nmsClass("PlayerConnection");
84-
Class<?> craftPlayerClass = FastReflection.obcClass("entity.CraftPlayer");
92+
Class<?> packetClass = FastReflection.nmsClass("Packet");
93+
Class<?> packetSbObjClass = FastReflection.nmsClass("PacketPlayOutScoreboardObjective");
94+
Class<?> packetSbDisplayObjClass = FastReflection.nmsClass("PacketPlayOutScoreboardDisplayObjective");
95+
Class<?> packetSbScoreClass = FastReflection.nmsClass("PacketPlayOutScoreboardScore");
96+
Class<?> packetSbTeamClass = FastReflection.nmsClass("PacketPlayOutScoreboardTeam");
8597

86-
MESSAGE_FROM_STRING = craftChatMessageClass.getDeclaredMethod("fromString", String.class);
98+
MESSAGE_FROM_STRING = lookup.unreflect(craftChatMessageClass.getMethod("fromString", String.class));
8799
CHAT_COMPONENT_CLASS = FastReflection.nmsClass("IChatBaseComponent");
88-
89-
PLAYER_GET_HANDLE = craftPlayerClass.getDeclaredMethod("getHandle");
90-
PLAYER_CONNECTION = entityPlayerClass.getDeclaredField("playerConnection");
91-
SEND_PACKET = playerConnectionClass.getDeclaredMethod("sendPacket", FastReflection.nmsClass("Packet"));
92-
93-
PACKET_SB_OBJ = FastReflection.nmsClass("PacketPlayOutScoreboardObjective").getConstructor();
94-
PACKET_SB_DISPLAY_OBJ = FastReflection.nmsClass("PacketPlayOutScoreboardDisplayObjective").getConstructor();
95-
PACKET_SB_SCORE = FastReflection.nmsClass("PacketPlayOutScoreboardScore").getConstructor();
96-
PACKET_SB_TEAM = FastReflection.nmsClass("PacketPlayOutScoreboardTeam").getConstructor();
100+
PLAYER_GET_HANDLE = lookup.findVirtual(craftPlayerClass, "getHandle", MethodType.methodType(entityPlayerClass));
101+
PLAYER_CONNECTION = lookup.findGetter(entityPlayerClass, "playerConnection", playerConnectionClass);
102+
SEND_PACKET = lookup.findVirtual(playerConnectionClass, "sendPacket", MethodType.methodType(void.class, packetClass));
103+
PACKET_SB_OBJ = lookup.findConstructor(packetSbObjClass, voidType);
104+
PACKET_SB_DISPLAY_OBJ = lookup.findConstructor(packetSbDisplayObjClass, voidType);
105+
PACKET_SB_SCORE = lookup.findConstructor(packetSbScoreClass, voidType);
106+
PACKET_SB_TEAM = lookup.findConstructor(packetSbTeamClass, voidType);
107+
108+
for (Class<?> clazz : Arrays.asList(packetSbObjClass, packetSbDisplayObjClass, packetSbScoreClass, packetSbTeamClass)) {
109+
List<Field> fields = Arrays.asList(clazz.getDeclaredFields());
110+
fields.forEach(field -> field.setAccessible(true));
111+
PACKETS.put(clazz, fields);
112+
}
97113

98114
if (VersionType.V1_8.isHigherOrEqual()) {
99115
String enumSbActionClass = VersionType.V1_13.isHigherOrEqual()
@@ -131,13 +147,13 @@ public class FastBoard {
131147
*/
132148
public FastBoard(Player player) {
133149
this.player = Objects.requireNonNull(player, "player");
134-
this.id = "fb-" + Double.toString(Math.random()).substring(2, 10);
150+
this.id = "fb-" + Integer.toHexString(ThreadLocalRandom.current().nextInt());
135151

136152
try {
137153
sendObjectivePacket(ObjectiveMode.CREATE);
138154
sendDisplayObjectivePacket();
139-
} catch (ReflectiveOperationException e) {
140-
throw new RuntimeException(e);
155+
} catch (Throwable t) {
156+
throw new RuntimeException("Unable to create scoreboard", t);
141157
}
142158
}
143159

@@ -170,8 +186,8 @@ public void updateTitle(String title) {
170186

171187
try {
172188
sendObjectivePacket(ObjectiveMode.UPDATE);
173-
} catch (ReflectiveOperationException e) {
174-
throw new RuntimeException(e);
189+
} catch (Throwable t) {
190+
throw new RuntimeException("Unable to update scoreboard title", t);
175191
}
176192
}
177193

@@ -226,8 +242,8 @@ public synchronized void updateLine(int line, String text) {
226242
newLines.add(text);
227243

228244
updateLines(newLines);
229-
} catch (ReflectiveOperationException e) {
230-
throw new RuntimeException(e);
245+
} catch (Throwable t) {
246+
throw new RuntimeException("Unable to update scoreboard lines", t);
231247
}
232248
}
233249

@@ -312,8 +328,8 @@ public synchronized void updateLines(Collection<String> lines) {
312328
sendTeamPacket(i, TeamMode.UPDATE);
313329
}
314330
}
315-
} catch (ReflectiveOperationException e) {
316-
throw new RuntimeException(e);
331+
} catch (Throwable t) {
332+
throw new RuntimeException("Unable to update scoreboard lines", t);
317333
}
318334
}
319335

@@ -366,8 +382,8 @@ public void delete() {
366382
}
367383

368384
sendObjectivePacket(ObjectiveMode.REMOVE);
369-
} catch (ReflectiveOperationException e) {
370-
throw new RuntimeException(e);
385+
} catch (Throwable t) {
386+
throw new RuntimeException("Unable to delete scoreboard", t);
371387
}
372388

373389
this.deleted = true;
@@ -384,16 +400,16 @@ protected boolean hasLinesMaxLength() {
384400
return !VersionType.V1_13.isHigherOrEqual();
385401
}
386402

387-
private void checkLineNumber(int line, boolean checkMax, boolean checkValid) {
403+
private void checkLineNumber(int line, boolean checkInRange, boolean checkMax) {
388404
if (line < 0) {
389405
throw new IllegalArgumentException("Line number must be positive");
390406
}
391407

392-
if (checkMax && line >= this.lines.size()) {
408+
if (checkInRange && line >= this.lines.size()) {
393409
throw new IllegalArgumentException("Line number must be under " + this.lines.size());
394410
}
395411

396-
if (checkValid && line >= ChatColor.values().length - 1) {
412+
if (checkMax && line >= ChatColor.values().length - 1) {
397413
throw new IllegalArgumentException("Line number is too high: " + this.lines.size());
398414
}
399415
}
@@ -414,8 +430,8 @@ private String getLineByScore(List<String> lines, int score) {
414430
return lines.get(lines.size() - score - 1);
415431
}
416432

417-
private void sendObjectivePacket(ObjectiveMode mode) throws ReflectiveOperationException {
418-
Object packet = PACKET_SB_OBJ.newInstance();
433+
private void sendObjectivePacket(ObjectiveMode mode) throws Throwable {
434+
Object packet = PACKET_SB_OBJ.invoke();
419435

420436
setField(packet, String.class, this.id);
421437
setField(packet, int.class, mode.ordinal());
@@ -433,17 +449,17 @@ private void sendObjectivePacket(ObjectiveMode mode) throws ReflectiveOperationE
433449
sendPacket(packet);
434450
}
435451

436-
private void sendDisplayObjectivePacket() throws ReflectiveOperationException {
437-
Object packet = PACKET_SB_DISPLAY_OBJ.newInstance();
452+
private void sendDisplayObjectivePacket() throws Throwable {
453+
Object packet = PACKET_SB_DISPLAY_OBJ.invoke();
438454

439455
setField(packet, int.class, 1); // Position (1: sidebar)
440456
setField(packet, String.class, this.id); // Score Name
441457

442458
sendPacket(packet);
443459
}
444460

445-
private void sendScorePacket(int score, ScoreboardAction action) throws ReflectiveOperationException {
446-
Object packet = PACKET_SB_SCORE.newInstance();
461+
private void sendScorePacket(int score, ScoreboardAction action) throws Throwable {
462+
Object packet = PACKET_SB_SCORE.invoke();
447463

448464
setField(packet, String.class, getColorCode(score), 0); // Player Name
449465

@@ -461,13 +477,13 @@ private void sendScorePacket(int score, ScoreboardAction action) throws Reflecti
461477
sendPacket(packet);
462478
}
463479

464-
private void sendTeamPacket(int score, TeamMode mode) throws ReflectiveOperationException {
480+
private void sendTeamPacket(int score, TeamMode mode) throws Throwable {
465481
if (mode == TeamMode.ADD_PLAYERS || mode == TeamMode.REMOVE_PLAYERS) {
466482
throw new UnsupportedOperationException();
467483
}
468484

469485
int maxLength = hasLinesMaxLength() ? 16 : 1024;
470-
Object packet = PACKET_SB_TEAM.newInstance();
486+
Object packet = PACKET_SB_TEAM.invoke();
471487

472488
setField(packet, String.class, this.id + ':' + score); // Team name
473489
setField(packet, int.class, mode.ordinal(), VERSION_TYPE == VersionType.V1_8 ? 1 : 0); // Update mode
@@ -517,14 +533,14 @@ private void sendTeamPacket(int score, TeamMode mode) throws ReflectiveOperation
517533
sendPacket(packet);
518534
}
519535

520-
private void sendPacket(Object packet) throws ReflectiveOperationException {
536+
private void sendPacket(Object packet) throws Throwable {
521537
if (this.deleted) {
522538
throw new IllegalStateException("This FastBoard is deleted");
523539
}
524540

525541
if (this.player.isOnline()) {
526542
Object entityPlayer = PLAYER_GET_HANDLE.invoke(this.player);
527-
Object playerConnection = PLAYER_CONNECTION.get(entityPlayer);
543+
Object playerConnection = PLAYER_CONNECTION.invoke(entityPlayer);
528544
SEND_PACKET.invoke(playerConnection, packet);
529545
}
530546
}
@@ -533,27 +549,25 @@ private void setField(Object object, Class<?> fieldType, Object value) throws Re
533549
setField(object, fieldType, value, 0);
534550
}
535551

536-
private void setField(Object object, Class<?> fieldType, Object value, int count) throws ReflectiveOperationException {
552+
private void setField(Object packet, Class<?> fieldType, Object value, int count) throws ReflectiveOperationException {
537553
int i = 0;
538-
for (Field field : object.getClass().getDeclaredFields()) {
554+
for (Field field : PACKETS.get(packet.getClass())) {
539555
if (field.getType() == fieldType && count == i++) {
540-
field.setAccessible(true);
541-
field.set(object, value);
556+
field.set(packet, value);
542557
}
543558
}
544559
}
545560

546-
private void setComponentField(Object object, String value, int count) throws ReflectiveOperationException {
561+
private void setComponentField(Object packet, String value, int count) throws Throwable {
547562
if (VERSION_TYPE != VersionType.V1_13) {
548-
setField(object, String.class, value, count);
563+
setField(packet, String.class, value, count);
549564
return;
550565
}
551566

552567
int i = 0;
553-
for (Field field : object.getClass().getDeclaredFields()) {
568+
for (Field field : PACKETS.get(packet.getClass())) {
554569
if ((field.getType() == String.class || field.getType() == CHAT_COMPONENT_CLASS) && count == i++) {
555-
field.setAccessible(true);
556-
field.set(object, Array.get(MESSAGE_FROM_STRING.invoke(null, value), 0));
570+
field.set(packet, Array.get(MESSAGE_FROM_STRING.invoke(value), 0));
557571
}
558572
}
559573
}

0 commit comments

Comments
 (0)