Skip to content

Commit b036eb4

Browse files
committed
optimized memory usage for packetwrapper and reflection
1 parent 3b0ee54 commit b036eb4

2 files changed

Lines changed: 250 additions & 219 deletions

File tree

src/dev/_2lstudios/hamsterapi/utils/Reflection.java

Lines changed: 70 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -8,44 +8,42 @@
88

99
public class Reflection {
1010
private final String version;
11+
// Cache for Class.forName(String) lookups
1112
private final Map<String, Class<?>> classes = new HashMap<>();
13+
// Cache for resolved NMS/OBC classes to avoid repeated string manipulation and lookups
14+
private final Map<String, Class<?>> minecraftClassCache = new HashMap<>();
15+
private final Map<String, Class<?>> craftBukkitClassCache = new HashMap<>();
16+
// Cache for reflected fields
1217
private final Map<Class<?>, Map<Class<?>, Map<Integer, Field>>> classFields = new HashMap<>();
1318

1419
public Reflection(final String version) {
1520
this.version = version;
1621
}
1722

23+
// This method is fine, it already caches its results.
1824
public Class<?> getClass(final String className) {
19-
if (this.classes.containsKey(className)) {
20-
return this.classes.get(className);
21-
}
22-
23-
Class<?> obtainedClass = null;
24-
25-
try {
26-
obtainedClass = Class.forName(className);
27-
} catch (final ClassNotFoundException e) {
28-
// Executed when class is not found
29-
} finally {
30-
this.classes.put(className, obtainedClass);
31-
}
32-
33-
return obtainedClass;
25+
// Use computeIfAbsent for a more concise and thread-safe-friendly approach
26+
return this.classes.computeIfAbsent(className, key -> {
27+
try {
28+
return Class.forName(key);
29+
} catch (final ClassNotFoundException e) {
30+
// Return null if not found, which will be cached.
31+
return null;
32+
}
33+
});
3434
}
3535

3636
private Object getValue(final Field field, final Object object)
3737
throws IllegalArgumentException, IllegalAccessException {
3838
final boolean accessible = field.isAccessible();
3939

4040
field.setAccessible(true);
41-
4241
final Object value = field.get(object);
43-
4442
field.setAccessible(accessible);
4543

4644
return value;
4745
}
48-
46+
4947
/**
5048
* Converts a string with legacy color codes (&) into an IChatBaseComponent.
5149
* This method is version-independent, trying the modern method first and
@@ -55,41 +53,33 @@ private Object getValue(final Field field, final Object object)
5553
* @return The IChatBaseComponent object, or null if conversion fails.
5654
*/
5755
public Object toChatBaseComponent(String text) {
56+
// This method seems complex but is likely not the memory bottleneck unless called
57+
// extremely frequently with unique strings. The main issue is the class lookups.
58+
// No changes needed here based on the profiler.
5859
if (text == null) {
5960
return null;
6061
}
6162

6263
String coloredText = ChatColor.translateAlternateColorCodes('&', text);
6364

6465
try {
65-
// --- MODERN METHOD (1.19+ and some recent versions) ---
66-
// CraftChatMessage.fromString(String) is the Spigot-level API to do this.
67-
// It returns IChatBaseComponent[] on modern versions.
6866
Class<?> craftChatMessageClass = Class.forName("org.bukkit.craftbukkit.util.CraftChatMessage");
6967
java.lang.reflect.Method fromStringMethod = craftChatMessageClass.getMethod("fromString", String.class);
7068
Object result = fromStringMethod.invoke(null, coloredText);
7169

72-
// On modern versions, it returns an array. We usually just need the first
73-
// component.
7470
if (result.getClass().isArray()) {
7571
Object[] components = (Object[]) result;
7672
return (components.length > 0) ? components[0] : null;
7773
} else {
78-
// Older versions might return a single component.
7974
return result;
8075
}
8176

8277
} catch (Exception e) {
83-
// --- LEGACY FALLBACK METHOD (pre-1.17, roughly) ---
8478
try {
85-
// This is the old way:
86-
// IChatBaseComponent.ChatSerializer.a("{\"text\":\"...\"}")
8779
Class<?> iChatBaseComponentClass = getIChatBaseComponent();
88-
if (iChatBaseComponentClass == null)
89-
return null;
80+
if (iChatBaseComponentClass == null) return null;
9081

9182
Class<?> chatSerializerClass = null;
92-
// Find the nested ChatSerializer class
9383
for (Class<?> nestedClass : iChatBaseComponentClass.getDeclaredClasses()) {
9484
if (nestedClass.getSimpleName().equals("ChatSerializer")) {
9585
chatSerializerClass = nestedClass;
@@ -98,16 +88,11 @@ public Object toChatBaseComponent(String text) {
9888
}
9989

10090
if (chatSerializerClass == null) {
101-
// If even the legacy method fails, we might be on a very new version
102-
// where the modern API is the ONLY way and the first try failed for another
103-
// reason.
104-
// For now, we'll assume failure.
10591
System.err.println("[HamsterAPI] CRIT Failed to find ChatSerializer, cannot create components.");
106-
e.printStackTrace(); // Print the original error for debugging.
92+
e.printStackTrace();
10793
return null;
10894
}
10995

110-
// Manually create the JSON string. This is what the old fromString did.
11196
String json = "{\"text\":\"" + net.md_5.bungee.api.chat.TextComponent.toLegacyText(
11297
net.md_5.bungee.api.chat.TextComponent.fromLegacyText(coloredText)).replace("\"", "\\\"")
11398
+ "\"}";
@@ -116,16 +101,16 @@ public Object toChatBaseComponent(String text) {
116101
return serializerMethod.invoke(null, json);
117102

118103
} catch (Exception e2) {
119-
// Both methods failed. Log a critical error.
120104
System.err.println(
121105
"[HamsterAPI] CRIT All methods to create IChatBaseComponent have failed. Kick messages will not work.");
122-
e.printStackTrace(); // Print first error
123-
e2.printStackTrace(); // Print second error
106+
e.printStackTrace();
107+
e2.printStackTrace();
124108
return null;
125109
}
126110
}
127111
}
128-
112+
113+
// This method's caching logic is already implemented correctly. No changes needed.
129114
public Object getField(final Object object, final Class<?> fieldType, final int number)
130115
throws IllegalAccessException {
131116
if (object == null) {
@@ -136,71 +121,81 @@ public Object getField(final Object object, final Class<?> fieldType, final int
136121
}
137122

138123
final Class<?> objectClass = object.getClass();
139-
// Caching logic remains the same
140-
final Map<Class<?>, Map<Integer, Field>> typeFields = classFields.getOrDefault(objectClass, new HashMap<>());
141-
final Map<Integer, Field> fields = typeFields.getOrDefault(fieldType, new HashMap<>());
142-
143-
classFields.put(objectClass, typeFields);
144-
typeFields.put(fieldType, fields);
145-
146-
if (!fields.isEmpty() && fields.containsKey(number)) {
124+
final Map<Class<?>, Map<Integer, Field>> typeFields = classFields.computeIfAbsent(objectClass, k -> new HashMap<>());
125+
final Map<Integer, Field> fields = typeFields.computeIfAbsent(fieldType, k -> new HashMap<>());
126+
127+
if (fields.containsKey(number)) {
147128
return getValue(fields.get(number), object);
148129
}
149130

150131
int index = 0;
151-
152-
// Start with the object's actual class
153132
Class<?> currentClass = objectClass;
154-
// Loop through the class and all its superclasses
155133
while (currentClass != null) {
156-
// Use getDeclaredFields() to get ALL fields (public, private, protected)
157134
for (final Field field : currentClass.getDeclaredFields()) {
158-
// isAssignableFrom is more robust than == for checking types
159135
if (fieldType.isAssignableFrom(field.getType())) {
160136
if (index == number) {
161137
final Object value = getValue(field, object);
162-
fields.put(number, field); // Cache the found field
138+
fields.put(number, field);
163139
return value;
164140
}
165141
index++;
166142
}
167143
}
168-
// Move up to the superclass for the next iteration
169144
currentClass = currentClass.getSuperclass();
170145
}
171146

172-
// Return null only after checking the entire class hierarchy
173147
return null;
174148
}
175149

176150
public Object getField(final Object object, final Class<?> fieldType) throws IllegalAccessException {
177151
return getField(object, fieldType, 0);
178152
}
179153

154+
// --- OPTIMIZED METHODS ---
155+
180156
private Class<?> getMinecraftClass(String key) {
181-
final int lastDot = key.lastIndexOf(".");
182-
final String lastKey = key.substring(lastDot > 0 ? lastDot + 1 : 0, key.length());
183-
// 1.8
184-
final Class<?> legacyClass = getClass("net.minecraft.server." + this.version + "." + lastKey);
185-
// 1.17
186-
final Class<?> newClass = getClass("net.minecraft." + key);
187-
188-
return legacyClass != null ? legacyClass : newClass;
157+
// Use computeIfAbsent to check cache, compute and store if absent, all in one go.
158+
return minecraftClassCache.computeIfAbsent(key, k -> {
159+
final int lastDot = k.lastIndexOf(".");
160+
final String lastKey = k.substring(lastDot > 0 ? lastDot + 1 : 0);
161+
162+
// Try modern (1.17+) package structure first
163+
Class<?> newClass = getClass("net.minecraft." + k);
164+
if (newClass != null) {
165+
return newClass;
166+
}
167+
168+
// Fallback to legacy (pre-1.17) versioned package structure
169+
return getClass("net.minecraft.server." + this.version + "." + lastKey);
170+
});
189171
}
190172

191173
private Class<?> getCraftBukkitClass(String key) {
192-
final int lastDot = key.lastIndexOf(".");
193-
final String lastKey = key.substring(lastDot > 0 ? lastDot + 1 : 0, key.length());
194-
// 1.8
195-
final Class<?> legacyClass = getClass("org.bukkit.craftbukkit." + this.version + "." + lastKey);
196-
// 1.17
197-
final Class<?> newClass = getClass("org.bukkit.craftbukkit." + this.version + "." + key);
198-
// 1.20.6
199-
final Class<?> newestClass = getClass("org.bukkit.craftbukkit." + key);
200-
201-
return legacyClass != null ? legacyClass : newClass != null ? newClass : newestClass;
174+
// Same optimization pattern for CraftBukkit classes
175+
return craftBukkitClassCache.computeIfAbsent(key, k -> {
176+
final int lastDot = k.lastIndexOf(".");
177+
final String lastKey = k.substring(lastDot > 0 ? lastDot + 1 : 0);
178+
179+
// Try newest package structure (e.g., 1.20.6+)
180+
Class<?> newestClass = getClass("org.bukkit.craftbukkit." + k);
181+
if (newestClass != null) {
182+
return newestClass;
183+
}
184+
185+
// Try new-ish package structure (e.g., 1.17+)
186+
Class<?> newClass = getClass("org.bukkit.craftbukkit." + this.version + "." + k);
187+
if (newClass != null) {
188+
return newClass;
189+
}
190+
191+
// Fallback to legacy versioned package structure
192+
return getClass("org.bukkit.craftbukkit." + this.version + "." + lastKey);
193+
});
202194
}
203195

196+
// All the following methods will now be extremely fast and allocate no new objects
197+
// after the first call, thanks to the caching in the methods they call.
198+
204199
public Class<?> getItemStack() {
205200
return getMinecraftClass("world.item.ItemStack");
206201
}
@@ -268,4 +263,4 @@ public Class<?> getChatMessageType() {
268263
public Class<?> getCraftItemStack() {
269264
return getCraftBukkitClass("inventory.CraftItemStack");
270265
}
271-
}
266+
}

0 commit comments

Comments
 (0)