Optimize class lookups

This commit is contained in:
Dan Mulloy 2023-03-25 23:16:04 -05:00
parent df3b68df4c
commit 0c6fa46871
No known key found for this signature in database
GPG Key ID: E3B02DE32FB04AC1
10 changed files with 140 additions and 93 deletions

View File

@ -414,7 +414,7 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
} else if (methods.size() == 1) {
// We're in 1.2.5
alwaysSync = true;
} else if (MinecraftVersion.getCurrentVersion().isAtLeast(MinecraftVersion.BOUNTIFUL_UPDATE)) {
} else if (MinecraftVersion.BOUNTIFUL_UPDATE.atOrAbove()) {
// The centralized async marker was removed in 1.8
// Incoming chat packets can be async
if (event.getPacketType() == PacketType.Play.Client.CHAT) {

View File

@ -137,8 +137,8 @@ public class StructureCache {
Class<?> packetClass = PacketRegistry.getPacketClassFromType(type);
// We need to map the Bundle Delimiter to the synthetic bundle packet which contains a list of all packets in a bundle
if (MinecraftVersion.atOrAbove(MinecraftVersion.FEATURE_PREVIEW_2) && packetClass.equals(MinecraftReflection.getBundleDelimiterClass())) {
packetClass = MinecraftReflection.getPackedBundlePacketClass();
if (MinecraftReflection.isBundleDelimiter(packetClass)) {
packetClass = MinecraftReflection.getPackedBundlePacketClass().get();
}
return new StructureModifier<>(packetClass, MinecraftReflection.getPacketClass(), true);

View File

@ -204,15 +204,10 @@ public class NettyChannelInjector implements Injector {
return false;
}
String anchorHandler = "decoder";
if (MinecraftVersion.FEATURE_PREVIEW_2.atOrAbove()) {
anchorHandler = "unbundler";
}
// inject our handlers
this.wrappedChannel.pipeline().addAfter("encoder", WIRE_PACKET_ENCODER_NAME, WIRE_PACKET_ENCODER);
this.wrappedChannel.pipeline().addAfter(
anchorHandler,
"decoder",
INTERCEPTOR_NAME,
new InboundPacketInterceptor(this, this.channelListener));

View File

@ -36,7 +36,7 @@ final class CachedPackage {
* @param packageName - the name of the current package.
* @param source - the class source.
*/
public CachedPackage(String packageName, ClassSource source) {
CachedPackage(String packageName, ClassSource source) {
this.source = source;
this.packageName = packageName;
this.cache = new ConcurrentHashMap<>();
@ -71,6 +71,16 @@ final class CachedPackage {
}
}
private Optional<Class<?>> resolveClass(String className) {
return source.loadClass(combine(packageName, className));
}
public Class<?> requireClass(String className) throws ClassNotFoundException {
String canonicalName = combine(packageName, className);
return source.loadClass(canonicalName)
.orElseThrow(() -> new ClassNotFoundException(className));
}
/**
* Retrieve the class object of a specific class in the current package.
*
@ -78,13 +88,21 @@ final class CachedPackage {
* @return Class object.
* @throws RuntimeException If we are unable to find the given class.
*/
public Optional<Class<?>> getPackageClass(final String className) {
return this.cache.computeIfAbsent(className, x -> {
try {
return Optional.ofNullable(this.source.loadClass(combine(this.packageName, className)));
} catch (ClassNotFoundException ex) {
return Optional.empty();
public Optional<Class<?>> getPackageClass(String className, String... aliases) {
return cache.computeIfAbsent(className, x -> {
Optional<Class<?>> clazz = resolveClass(className);
if (clazz.isPresent()) {
return clazz;
}
for (String alias : aliases) {
clazz = resolveClass(className);
if (clazz.isPresent()) {
return clazz;
}
}
return Optional.empty();
});
}
}

View File

@ -2,6 +2,8 @@ package com.comphenix.protocol.utility;
import java.util.Collections;
import java.util.Map;
import java.util.Optional;
import java.util.function.Supplier;
/**
* Represents an abstract class loader that can only retrieve classes by their canonical name.
@ -37,27 +39,25 @@ public interface ClassSource {
* @return The corresponding class source.
*/
static ClassSource fromClassLoader(final ClassLoader loader) {
return loader::loadClass;
return canonicalName -> {
try {
return Optional.of(loader.loadClass(canonicalName));
} catch (ClassNotFoundException ignored) {
return Optional.empty();
}
};
}
/**
* Construct a class source from a mapping of canonical names and the corresponding classes. If the map is null, it
* will be interpreted as an empty map. If the map does not contain a Class with the specified name, or that string
* maps to NULL explicitly, a {@link ClassNotFoundException} will be thrown.
* maps to NULL explicitly, an empty optional will be returned
*
* @param map - map of class names and classes.
* @return The class source.
*/
static ClassSource fromMap(final Map<String, Class<?>> map) {
return canonicalName -> {
Class<?> loaded = map == null ? null : map.get(canonicalName);
if (loaded == null) {
// Throw the appropriate exception if we can't load the class
throw new ClassNotFoundException("The specified class could not be found by this ClassLoader.");
}
return loaded;
};
return canonicalName -> Optional.ofNullable(map.get(canonicalName));
}
/**
@ -94,14 +94,8 @@ public interface ClassSource {
* @param other - the other class source.
* @return A new class source.
*/
default ClassSource retry(final ClassSource other) {
return canonicalName -> {
try {
return ClassSource.this.loadClass(canonicalName);
} catch (ClassNotFoundException e) {
return other.loadClass(canonicalName);
}
};
default ClassSource retry(ClassSource other) {
return canonicalName -> Optionals.or(loadClass(canonicalName), () -> other.loadClass(canonicalName));
}
/**
@ -115,12 +109,9 @@ public interface ClassSource {
}
/**
* Retrieve a class by name.
*
* @param canonicalName - the full canonical name of the class.
* @return The corresponding class. If the class is not found, NULL should <b>not</b> be returned, instead a {@code
* ClassNotFoundException} exception should be thrown.
* @throws ClassNotFoundException If the class could not be found.
* Retrieve a class by its canonical name
* @param canonicalName The class's canonical name, i.e. java.lang.Object
* @return Optional that may contain a Class
*/
Class<?> loadClass(String canonicalName) throws ClassNotFoundException;
Optional<Class<?>> loadClass(String canonicalName);
}

View File

@ -25,6 +25,7 @@ import java.lang.reflect.ParameterizedType;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.logging.Level;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -628,16 +629,20 @@ public final class MinecraftReflection {
return getMinecraftClass("network.chat.IChatBaseComponent", "network.chat.IChatbaseComponent", "network.chat.Component", "IChatBaseComponent");
}
public static Class<?> getPackedBundlePacketClass() {
return getMinecraftClass("network.protocol.game.ClientboundBundlePacket", "ClientboundBundlePacket");
public static Optional<Class<?>> getPackedBundlePacketClass() {
return getOptionalNMS("network.protocol.game.ClientboundBundlePacket", "ClientboundBundlePacket");
}
public static boolean isBundlePacket(Class<?> packetClass) {
return MinecraftVersion.FEATURE_PREVIEW_2.atOrAbove() && packetClass.equals(getPackedBundlePacketClass());
return Optionals.Equals(getPackedBundlePacketClass(), packetClass);
}
public static Class<?> getBundleDelimiterClass() {
return getMinecraftClass("network.protocol.BundleDelimiterPacket","BundleDelimiterPacket");
public static boolean isBundleDelimiter(Class<?> packetClass) {
return Optionals.Equals(getBundleDelimiterClass(), packetClass);
}
public static Optional<Class<?>> getBundleDelimiterClass() {
return getOptionalNMS("network.protocol.BundleDelimiterPacket","BundleDelimiterPacket");
}
public static Class<?> getIChatBaseComponentArrayClass() {
@ -1337,11 +1342,8 @@ public final class MinecraftReflection {
* @return The class.
*/
private static Class<?> getClass(String className) {
try {
return getClassSource().loadClass(className);
} catch (ClassNotFoundException e) {
throw new RuntimeException("Cannot find class " + className, e);
}
return getClassSource().loadClass(className)
.orElseThrow(() -> new RuntimeException("Cannot find class " + className));
}
/**
@ -1376,12 +1378,34 @@ public final class MinecraftReflection {
.orElseThrow(() -> new RuntimeException("Failed to find NMS class: " + className));
}
public static Class<?> getNullableNMS(String className, String... aliases) {
try {
return getMinecraftClass(className, aliases);
} catch (RuntimeException ex) {
return null;
/**
* Optionally retrieve the class object of a NMS (net.minecraft.server) class.
* If the class does not exist, the optional will be empty
*
* @param className NMS class name
* @param aliases Potential aliases
* @return Optional that may contain the class
*/
private static Optional<Class<?>> getOptionalNMS(String className, String... aliases) {
if (minecraftPackage == null) {
minecraftPackage = new CachedPackage(getMinecraftPackage(), getClassSource());
}
return minecraftPackage.getPackageClass(className, aliases);
}
/**
* Retrieves a nullable NMS (net.minecraft.server) class. We will attempt to
* look up the class and its aliases, but will return null if none is found.
*
* @deprecated - Use getOptionalNMS where possible
* @param className NMS class name
* @param aliases Potential aliases
* @return The class, or null if not found
*/
@Deprecated
public static Class<?> getNullableNMS(String className, String... aliases) {
return getOptionalNMS(className, aliases).orElse(null);
}
/**
@ -1418,29 +1442,8 @@ public final class MinecraftReflection {
* @throws RuntimeException If we are unable to find any of the given classes.
*/
public static Class<?> getMinecraftClass(String className, String... aliases) {
if (minecraftPackage == null) {
minecraftPackage = new CachedPackage(getMinecraftPackage(), getClassSource());
}
return minecraftPackage.getPackageClass(className).orElseGet(() -> {
Class<?> resolved = null;
for (String alias : aliases) {
// try to resolve the class and stop searching if we found it
resolved = minecraftPackage.getPackageClass(alias).orElse(null);
if (resolved != null) {
break;
}
}
// if we resolved the class cache it and return the result
if (resolved != null) {
minecraftPackage.setPackageClass(className, resolved);
return resolved;
}
// unable to find the class
throw new RuntimeException(String.format("Unable to find %s (%s)", className, String.join(", ", aliases)));
});
return getOptionalNMS(className, aliases)
.orElseThrow(() -> new RuntimeException(String.format("Unable to find %s (%s)", className, String.join(", ", aliases))));
}
/**

View File

@ -273,7 +273,7 @@ public final class MinecraftVersion implements Comparable<MinecraftVersion>, Ser
currentVersion = version;
}
public static boolean atOrAbove(MinecraftVersion version) {
private static boolean atOrAbove(MinecraftVersion version) {
return getCurrentVersion().isAtLeast(version);
}
@ -354,7 +354,7 @@ public final class MinecraftVersion implements Comparable<MinecraftVersion>, Ser
*/
public boolean atOrAbove() {
if (this.atCurrentOrAbove == null) {
this.atCurrentOrAbove = MinecraftVersion.atOrAbove(this);
this.atCurrentOrAbove = atOrAbove(this);
}
return this.atCurrentOrAbove;

View File

@ -0,0 +1,45 @@
package com.comphenix.protocol.utility;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.function.Supplier;
/**
* Utility methods for operating with Optionals
*/
public final class Optionals {
/**
* Chains two optionals together by returning the secondary
* optional if the primary does not contain a value
* @param primary Primary optional
* @param secondary Supplier of secondary optional
* @return The resulting optional
* @param <T> Type
*/
public static <T> Optional<T> or(Optional<T> primary, Supplier<Optional<T>> secondary) {
return primary.isPresent() ? primary : secondary.get();
}
/**
* Evaluates the provided predicate against the optional only if it is present
* @param optional Optional
* @param predicate Test to run against potential value
* @return True if the optional is present and the predicate passes
* @param <T> Type
*/
public static <T> boolean TestIfPresent(Optional<T> optional, Predicate<T> predicate) {
return optional.isPresent() && predicate.test(optional.get());
}
/**
* Check if the optional has a value and its value equals the provided value
* @param optional Optional
* @param contents Contents to test for
* @return True if the optional has a value and that value equals the parameter
* @param <T> Type
*/
public static <T> boolean Equals(Optional<T> optional, Class<?> contents) {
return optional.isPresent() && contents.equals(optional.get());
}
}

View File

@ -4,6 +4,7 @@ import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import javax.annotation.Nonnull;
@ -144,14 +145,8 @@ public class TroveWrapper {
throw new IllegalArgumentException("trove instance cannot be non-null.");
AbstractFuzzyMatcher<Class<?>> match = FuzzyMatchers.matchSuper(trove.getClass());
Class<?> decorators = null;
try {
// Attempt to get decorator class
decorators = getClassSource(trove.getClass()).loadClass("TDecorators");
} catch (ClassNotFoundException e) {
throw new IllegalStateException(e.getMessage(), e);
}
Class<?> decorators = getClassSource(trove.getClass()).loadClass("TDecorator")
.orElseThrow(() -> new IllegalStateException("TDecorators class not found"));
// Find an appropriate wrapper method in TDecorators
for (Method method : decorators.getMethods()) {

View File

@ -22,7 +22,7 @@ import com.google.common.base.Preconditions;
* @author Kristian
*/
public class WrappedAttributeModifier extends AbstractWrapper {
private static final boolean OPERATION_ENUM = MinecraftVersion.atOrAbove(MinecraftVersion.VILLAGE_UPDATE);
private static final boolean OPERATION_ENUM = MinecraftVersion.VILLAGE_UPDATE.atOrAbove();
private static final Class<?> OPERATION_CLASS;
private static final EquivalentConverter<Operation> OPERATION_CONVERTER;