From 8724311d206e7165876bee3ed67effcdeae53fd2 Mon Sep 17 00:00:00 2001 From: Lucwousin Date: Sun, 15 Dec 2019 01:26:19 +0100 Subject: [PATCH] Use LambdaMetaFactory to generate accessors for @Subscribe annotations --- .../net/runelite/api/config/Constants.java | 105 ------------------ .../client/eventbus/AccessorGenerator.java | 85 ++++++++++++++ .../client/eventbus/Subscription.java | 20 ++++ .../net/runelite/client/plugins/Plugin.java | 66 ++++------- .../client/plugins/PluginManager.java | 11 +- 5 files changed, 136 insertions(+), 151 deletions(-) delete mode 100644 runelite-api/src/main/java/net/runelite/api/config/Constants.java create mode 100644 runelite-client/src/main/java/net/runelite/client/eventbus/AccessorGenerator.java create mode 100644 runelite-client/src/main/java/net/runelite/client/eventbus/Subscription.java diff --git a/runelite-api/src/main/java/net/runelite/api/config/Constants.java b/runelite-api/src/main/java/net/runelite/api/config/Constants.java deleted file mode 100644 index 7d385420ee..0000000000 --- a/runelite-api/src/main/java/net/runelite/api/config/Constants.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright (c) 2018, Lotto - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package net.runelite.api.config; - -import java.awt.Dimension; - -/** - * A utility class containing constant values. - */ -public class Constants -{ - /** - * The original width of the game when running in fixed mode. - */ - public static final int GAME_FIXED_WIDTH = 765; - - /** - * The original height of the game when running in fixed mode. - */ - public static final int GAME_FIXED_HEIGHT = 503; - - /** - * Dimension representation of the width and height of the game in fixed mode. - */ - public static final Dimension GAME_FIXED_SIZE = new Dimension(GAME_FIXED_WIDTH, GAME_FIXED_HEIGHT); - - /** - * The aspect ratio of the game when running in fixed mode. - */ - public static final double GAME_FIXED_ASPECT_RATIO = (double) GAME_FIXED_WIDTH / (double) GAME_FIXED_HEIGHT; - - /** - * The default camera zoom value. - */ - public static final int CLIENT_DEFAULT_ZOOM = 512; - - /** - * The width and length of a chunk (8x8 tiles). - */ - public static final int CHUNK_SIZE = 8; - - /** - * The width and length of a map region (64x64 tiles). - */ - public static final int REGION_SIZE = 64; - - /** - * The width and length of the scene (13 chunks x 8 tiles). - */ - public static final int SCENE_SIZE = 104; - - /** - * The max allowed plane by the game. - *

- * This value is exclusive. The plane is set by 2 bits which restricts - * the plane value to 0-3. - */ - public static final int MAX_Z = 4; - - public static final int TILE_FLAG_BRIDGE = 2; - - /** - * The number of milliseconds in a client tick. - *

- * This is the length of a single frame when the client is running at - * the maximum framerate of 50 fps. - */ - public static final int CLIENT_TICK_LENGTH = 20; - - /** - * The number of milliseconds in a server game tick. - *

- * This is the length of a single game cycle under ideal conditions. - * All game-play actions operate within multiples of this duration. - */ - public static final int GAME_TICK_LENGTH = 600; - - /** - * Used when getting High Alchemy value - multiplied by general store price. - */ - public static final float HIGH_ALCHEMY_CONSTANT = 0.6f; -} diff --git a/runelite-client/src/main/java/net/runelite/client/eventbus/AccessorGenerator.java b/runelite-client/src/main/java/net/runelite/client/eventbus/AccessorGenerator.java new file mode 100644 index 0000000000..a87f9cd191 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/eventbus/AccessorGenerator.java @@ -0,0 +1,85 @@ +package net.runelite.client.eventbus; + +import com.google.common.collect.ImmutableSet; +import io.reactivex.functions.Consumer; +import java.lang.invoke.CallSite; +import java.lang.invoke.LambdaMetafactory; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodHandles.Lookup; +import java.lang.invoke.MethodType; +import static java.lang.invoke.MethodType.methodType; +import java.lang.reflect.Method; +import java.util.Set; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.events.Event; + +@Slf4j +public class AccessorGenerator +{ + public static Set scanSubscribes(Lookup caller, Object ref) + { + ImmutableSet.Builder builder = ImmutableSet.builder(); + + final Class refClass = ref.getClass(); + caller = getPrivateAccess(refClass, caller).in(refClass); + + for (Method method : refClass.getDeclaredMethods()) + { + Subscribe sub = method.getAnnotation(Subscribe.class); + if (sub != null) + { + final Consumer accessor; + final Class paramType = method.getParameterTypes()[0]; + + try + { + accessor = getConsumerFor(caller, ref, method); + } + catch (Throwable t) + { + log.warn("Creating consumer lambda for {} failed!", method, t); + continue; + } + + builder.add(new Subscription(paramType, accessor, sub.takeUntil(), sub.subscribe(), sub.observe())); + } + } + return builder.build(); + } + + @SuppressWarnings("unchecked") + private static Consumer getConsumerFor(Lookup caller, Object ref, Method method) throws Throwable + { + final MethodHandle methodHandle = caller.unreflect(method); + + final MethodType actualConsumer = methodHandle.type().dropParameterTypes(0, 1); + final MethodType eventsConsumer = actualConsumer.erase(); + final MethodType factoryType = methodType(Consumer.class, ref.getClass()); + + final CallSite callsite = LambdaMetafactory.metafactory( + caller, // To get past security checks + "accept", // The name of the method to implement inside the lambda type + factoryType, // Signature of the factory method + eventsConsumer, // Signature of function implementation + methodHandle, // Target method + actualConsumer // Target method signature + ); + + final MethodHandle factory = callsite.getTarget(); + return (Consumer) factory.invoke(ref); + } + + private static Lookup getPrivateAccess(Class into, Lookup from) + { + try + { + return MethodHandles.privateLookupIn(into, from); + } + catch (IllegalAccessException a) + { + log.warn("Failed to get private access in {} from {}", into, from, a); + return from; + } + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/eventbus/Subscription.java b/runelite-client/src/main/java/net/runelite/client/eventbus/Subscription.java new file mode 100644 index 0000000000..22acf41126 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/eventbus/Subscription.java @@ -0,0 +1,20 @@ +package net.runelite.client.eventbus; + +import io.reactivex.functions.Consumer; +import lombok.Value; + +@Value +public class Subscription +{ + private final Class type; + private final Consumer method; + private final int takeUntil; + private final EventScheduler subscribe; + private final EventScheduler observe; + + @SuppressWarnings("unchecked") + public void subscribe(EventBus eventBus, Object lifecycle) + { + eventBus.subscribe(type, lifecycle, method, takeUntil, subscribe, observe); + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/Plugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/Plugin.java index 6ff4642b8f..fddb364e9e 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/Plugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/Plugin.java @@ -24,25 +24,23 @@ */ package net.runelite.client.plugins; -import com.google.common.collect.ImmutableSet; import com.google.inject.Binder; import com.google.inject.Injector; import com.google.inject.Module; -import io.reactivex.functions.Consumer; -import java.lang.reflect.Method; +import io.reactivex.Observable; +import io.reactivex.schedulers.Schedulers; +import java.lang.invoke.MethodHandles; +import java.util.Collection; import java.util.Set; import lombok.AccessLevel; import lombok.Getter; -import lombok.Value; -import net.runelite.api.events.Event; +import net.runelite.client.eventbus.AccessorGenerator; import net.runelite.client.eventbus.EventBus; -import net.runelite.client.eventbus.EventScheduler; -import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.eventbus.Subscription; public abstract class Plugin implements Module { - private final Set annotatedSubscriptions = findSubscriptions(); - private final Object annotatedSubsLock = new Object(); + private Set annotatedSubscriptions = null; @Getter(AccessLevel.PROTECTED) protected Injector injector; @@ -60,53 +58,33 @@ public abstract class Plugin implements Module { } - @SuppressWarnings("unchecked") final void addAnnotatedSubscriptions(EventBus eventBus) { - annotatedSubscriptions.forEach(sub -> eventBus.subscribe(sub.type, annotatedSubsLock, sub.method, sub.takeUntil, sub.subscribe, sub.observe)); + if (annotatedSubscriptions == null) + { + Observable.fromCallable(this::findSubscriptions) + .subscribeOn(Schedulers.computation()) + .observeOn(Schedulers.single()) + .subscribe(subs -> addSubs(eventBus, (annotatedSubscriptions = subs))); + } + else + { + addSubs(eventBus, annotatedSubscriptions); + } } final void removeAnnotatedSubscriptions(EventBus eventBus) { - eventBus.unregister(annotatedSubsLock); + eventBus.unregister(this); } private Set findSubscriptions() { - ImmutableSet.Builder builder = ImmutableSet.builder(); - - for (Method method : this.getClass().getDeclaredMethods()) - { - Subscribe annotation = method.getAnnotation(Subscribe.class); - if (annotation == null) - { - continue; - } - - assert method.getParameterCount() == 1 : "Methods annotated with @Subscribe should have only one parameter"; - - Class type = method.getParameterTypes()[0]; - - assert Event.class.isAssignableFrom(type) : "Parameters of methods annotated with @Subscribe should implement net.runelite.api.events.Event"; - assert method.getReturnType() == void.class : "Methods annotated with @Subscribe should have a void return type"; - - method.setAccessible(true); - - Subscription sub = new Subscription(type.asSubclass(Event.class), event -> method.invoke(this, event), annotation.takeUntil(), annotation.subscribe(), annotation.observe()); - - builder.add(sub); - } - - return builder.build(); + return AccessorGenerator.scanSubscribes(MethodHandles.lookup(), this); } - @Value - private static class Subscription + private void addSubs(EventBus eventBus, Collection subs) { - private final Class type; - private final Consumer method; - private final int takeUntil; - private final EventScheduler subscribe; - private final EventScheduler observe; + subs.forEach(s -> s.subscribe(eventBus, this)); } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/PluginManager.java b/runelite-client/src/main/java/net/runelite/client/plugins/PluginManager.java index 0e19d0b66c..ebca9bc207 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/PluginManager.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/PluginManager.java @@ -25,6 +25,7 @@ package net.runelite.client.plugins; import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Stopwatch; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import com.google.common.graph.Graph; @@ -218,13 +219,17 @@ public class PluginManager public void startCorePlugins() { List scannedPlugins = new ArrayList<>(plugins); - int loaded = 0; + int loaded = 0, started = 0; + final Stopwatch timer = Stopwatch.createStarted(); for (Plugin plugin : scannedPlugins) { try { - startPlugin(plugin); + if (startPlugin(plugin)) + { + ++started; + } } catch (PluginInstantiationException ex) { @@ -236,6 +241,8 @@ public class PluginManager RuneLiteSplashScreen.stage(.80, 1, "Starting plugins", loaded, scannedPlugins.size()); } + + log.debug("Started {}/{} plugins in {}", started, loaded, timer); } @SuppressWarnings("unchecked")