From 5d47520071250c3f4a4e6a9198233b1c002854ef Mon Sep 17 00:00:00 2001 From: sdburns1998 <49877861+sdburns1998@users.noreply.github.com> Date: Fri, 28 Jun 2019 09:36:55 +0200 Subject: [PATCH] Add caching to config invocation handler (#788) --- .../config/ConfigInvocationHandler.java | 108 +++++++++++------- .../runelite/client/config/ConfigManager.java | 23 +++- 2 files changed, 91 insertions(+), 40 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/config/ConfigInvocationHandler.java b/runelite-client/src/main/java/net/runelite/client/config/ConfigInvocationHandler.java index 62cd38a8d1..330551c37f 100644 --- a/runelite-client/src/main/java/net/runelite/client/config/ConfigInvocationHandler.java +++ b/runelite-client/src/main/java/net/runelite/client/config/ConfigInvocationHandler.java @@ -28,6 +28,8 @@ import java.lang.invoke.MethodHandles; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; import java.util.Objects; import lombok.extern.slf4j.Slf4j; @@ -36,26 +38,49 @@ class ConfigInvocationHandler implements InvocationHandler { private final ConfigManager manager; + // Caches for annotation values + private static final Map, String> groupValueCache = new HashMap<>(); + private static final Map methodKeyNameCache = new HashMap<>(); + public ConfigInvocationHandler(ConfigManager manager) { this.manager = manager; } + private static String groupValueFromProxy(Class proxyClass) + { + Class iface = proxyClass.getInterfaces()[0]; + ConfigGroup group = iface.getAnnotation(ConfigGroup.class); + + return group == null ? null : group.value(); + } + + private static String keyNameFromMethod(Method method) + { + ConfigItem item = method.getAnnotation(ConfigItem.class); + + return item == null ? null : item.keyName(); + } + @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - Class iface = proxy.getClass().getInterfaces()[0]; - - ConfigGroup group = iface.getAnnotation(ConfigGroup.class); - ConfigItem item = method.getAnnotation(ConfigItem.class); - - if (group == null) + String itemKeyName, groupValue; + try + { + groupValue = groupValueCache.computeIfAbsent(proxy.getClass(), ConfigInvocationHandler::groupValueFromProxy); + } + catch (NullPointerException e) { log.warn("Configuration proxy class {} has no @ConfigGroup!", proxy.getClass()); return null; } - if (item == null) + try + { + itemKeyName = methodKeyNameCache.computeIfAbsent(method, ConfigInvocationHandler::keyNameFromMethod); + } + catch (NullPointerException e) { log.warn("Configuration method {} has no @ConfigItem!", method); return null; @@ -64,38 +89,43 @@ class ConfigInvocationHandler implements InvocationHandler if (args == null) { // Getting configuration item - String value = manager.getConfiguration(group.value(), item.keyName()); - - if (value == null) + return manager.getConfigObjectFromCacheOrElse(groupValue, itemKeyName, (value) -> { - if (method.isDefault()) + try { - return callDefaultMethod(proxy, method, null); + value = manager.getConfiguration(value); + if (value == null) + { + if (method.isDefault()) + { + return callDefaultMethod(proxy, method, null); + } + return null; + } + + // Convert value to return type + Class returnType = method.getReturnType(); + + try + { + return ConfigManager.stringToObject(value, returnType); + } + catch (Exception e) + { + log.warn("Unable to unmarshal {}.{} ", groupValue, itemKeyName, e); + if (method.isDefault()) + { + return callDefaultMethod(proxy, method, null); + } + return null; + } } - - return null; - } - - // Convert value to return type - Class returnType = method.getReturnType(); - - try - { - return ConfigManager.stringToObject(value, returnType); - } - catch (Exception e) - { - log.warn("Unable to unmarshal {}.{} ", group.value(), item.keyName(), e); - if (method.isDefault()) + catch (Throwable throwable) { - Object defaultValue = callDefaultMethod(proxy, method, null); - - manager.setConfiguration(group.value(), item.keyName(), defaultValue); - - return defaultValue; + log.error("Unable to resolve configuration value {}.{}", groupValue, itemKeyName, throwable); + return null; } - return null; - } + }); } else { @@ -103,13 +133,13 @@ class ConfigInvocationHandler implements InvocationHandler if (args.length != 1) { - throw new RuntimeException("Invalid number of arguents to configuration method"); + throw new RuntimeException("Invalid number of arguments to configuration method"); } Object newValue = args[0]; Class type = method.getParameterTypes()[0]; - Object oldValue = manager.getConfiguration(group.value(), item.keyName(), type); + Object oldValue = manager.getConfiguration(groupValue, itemKeyName, type); if (Objects.equals(oldValue, newValue)) { @@ -124,19 +154,19 @@ class ConfigInvocationHandler implements InvocationHandler if (Objects.equals(newValue, defaultValue)) { // Just unset if it goes back to the default - manager.unsetConfiguration(group.value(), item.keyName()); + manager.unsetConfiguration(groupValue, itemKeyName); return null; } } if (newValue == null) { - manager.unsetConfiguration(group.value(), item.keyName()); + manager.unsetConfiguration(groupValue, itemKeyName); } else { String newValueStr = ConfigManager.objectToString(newValue); - manager.setConfiguration(group.value(), item.keyName(), newValueStr); + manager.setConfiguration(groupValue, itemKeyName, newValueStr); } return null; } diff --git a/runelite-client/src/main/java/net/runelite/client/config/ConfigManager.java b/runelite-client/src/main/java/net/runelite/client/config/ConfigManager.java index 8bb63b5a4f..c81c25bd0e 100644 --- a/runelite-client/src/main/java/net/runelite/client/config/ConfigManager.java +++ b/runelite-client/src/main/java/net/runelite/client/config/ConfigManager.java @@ -55,6 +55,7 @@ import java.util.Objects; import java.util.Properties; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; +import java.util.function.Function; import java.util.stream.Collectors; import javax.inject.Inject; import javax.inject.Singleton; @@ -81,6 +82,7 @@ public class ConfigManager private final ScheduledExecutorService executor; private final ConfigInvocationHandler handler = new ConfigInvocationHandler(this); private final Properties properties = new Properties(); + private final Map configObjectCache = new HashMap<>(); private final Map pendingChanges = new HashMap<>(); @Inject @@ -220,6 +222,20 @@ public class ConfigManager } } + // Attempts to fetch the config value from the cache if present. Otherwise it calls the get value function and caches the result + Object getConfigObjectFromCacheOrElse(String groupName, String key, Function getValue) + { + String configItemKey = groupName + "." + key; + return configObjectCache.computeIfAbsent(configItemKey, getValue); + } + + // Posts the configchanged event to the event bus and remove the changed key from the cache + private void postConfigChanged(ConfigChanged configChanged) + { + configObjectCache.remove(configChanged.getGroup() + "." + configChanged.getKey()); + eventBus.post(configChanged); + } + public T getConfig(Class clazz) { if (!Modifier.isPublic(clazz.getModifiers())) @@ -245,6 +261,11 @@ public class ConfigManager return properties.getProperty(groupName + "." + key); } + public String getConfiguration(String propertyKey) + { + return properties.getProperty(propertyKey); + } + public T getConfiguration(String groupName, String key, Class clazz) { String value = getConfiguration(groupName, key); @@ -284,7 +305,7 @@ public class ConfigManager configChanged.setOldValue(oldValue); configChanged.setNewValue(value); - eventBus.post(configChanged); + postConfigChanged(configChanged); } public void setConfiguration(String groupName, String key, Object value)