Use LambdaMetaFactory to generate accessors for @Subscribe annotations
This commit is contained in:
@@ -1,105 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2018, Lotto <https://github.com/devLotto>
|
|
||||||
* 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.
|
|
||||||
* <p>
|
|
||||||
* 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.
|
|
||||||
* <p>
|
|
||||||
* 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.
|
|
||||||
* <p>
|
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
@@ -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<Subscription> scanSubscribes(Lookup caller, Object ref)
|
||||||
|
{
|
||||||
|
ImmutableSet.Builder<Subscription> 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 <EVENT extends Event> Consumer<EVENT> 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<EVENT>) 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,25 +24,23 @@
|
|||||||
*/
|
*/
|
||||||
package net.runelite.client.plugins;
|
package net.runelite.client.plugins;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableSet;
|
|
||||||
import com.google.inject.Binder;
|
import com.google.inject.Binder;
|
||||||
import com.google.inject.Injector;
|
import com.google.inject.Injector;
|
||||||
import com.google.inject.Module;
|
import com.google.inject.Module;
|
||||||
import io.reactivex.functions.Consumer;
|
import io.reactivex.Observable;
|
||||||
import java.lang.reflect.Method;
|
import io.reactivex.schedulers.Schedulers;
|
||||||
|
import java.lang.invoke.MethodHandles;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import lombok.AccessLevel;
|
import lombok.AccessLevel;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Value;
|
import net.runelite.client.eventbus.AccessorGenerator;
|
||||||
import net.runelite.api.events.Event;
|
|
||||||
import net.runelite.client.eventbus.EventBus;
|
import net.runelite.client.eventbus.EventBus;
|
||||||
import net.runelite.client.eventbus.EventScheduler;
|
import net.runelite.client.eventbus.Subscription;
|
||||||
import net.runelite.client.eventbus.Subscribe;
|
|
||||||
|
|
||||||
public abstract class Plugin implements Module
|
public abstract class Plugin implements Module
|
||||||
{
|
{
|
||||||
private final Set<Subscription> annotatedSubscriptions = findSubscriptions();
|
private Set<Subscription> annotatedSubscriptions = null;
|
||||||
private final Object annotatedSubsLock = new Object();
|
|
||||||
|
|
||||||
@Getter(AccessLevel.PROTECTED)
|
@Getter(AccessLevel.PROTECTED)
|
||||||
protected Injector injector;
|
protected Injector injector;
|
||||||
@@ -60,53 +58,33 @@ public abstract class Plugin implements Module
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
final void addAnnotatedSubscriptions(EventBus eventBus)
|
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)
|
final void removeAnnotatedSubscriptions(EventBus eventBus)
|
||||||
{
|
{
|
||||||
eventBus.unregister(annotatedSubsLock);
|
eventBus.unregister(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Set<Subscription> findSubscriptions()
|
private Set<Subscription> findSubscriptions()
|
||||||
{
|
{
|
||||||
ImmutableSet.Builder<Subscription> builder = ImmutableSet.builder();
|
return AccessorGenerator.scanSubscribes(MethodHandles.lookup(), this);
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Value
|
private void addSubs(EventBus eventBus, Collection<Subscription> subs)
|
||||||
private static class Subscription
|
|
||||||
{
|
{
|
||||||
private final Class type;
|
subs.forEach(s -> s.subscribe(eventBus, this));
|
||||||
private final Consumer method;
|
|
||||||
private final int takeUntil;
|
|
||||||
private final EventScheduler subscribe;
|
|
||||||
private final EventScheduler observe;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
package net.runelite.client.plugins;
|
package net.runelite.client.plugins;
|
||||||
|
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
import com.google.common.base.Stopwatch;
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import com.google.common.graph.Graph;
|
import com.google.common.graph.Graph;
|
||||||
@@ -218,13 +219,17 @@ public class PluginManager
|
|||||||
public void startCorePlugins()
|
public void startCorePlugins()
|
||||||
{
|
{
|
||||||
List<Plugin> scannedPlugins = new ArrayList<>(plugins);
|
List<Plugin> scannedPlugins = new ArrayList<>(plugins);
|
||||||
int loaded = 0;
|
int loaded = 0, started = 0;
|
||||||
|
|
||||||
|
final Stopwatch timer = Stopwatch.createStarted();
|
||||||
for (Plugin plugin : scannedPlugins)
|
for (Plugin plugin : scannedPlugins)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
startPlugin(plugin);
|
if (startPlugin(plugin))
|
||||||
|
{
|
||||||
|
++started;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (PluginInstantiationException ex)
|
catch (PluginInstantiationException ex)
|
||||||
{
|
{
|
||||||
@@ -236,6 +241,8 @@ public class PluginManager
|
|||||||
|
|
||||||
RuneLiteSplashScreen.stage(.80, 1, "Starting plugins", loaded, scannedPlugins.size());
|
RuneLiteSplashScreen.stage(.80, 1, "Starting plugins", loaded, scannedPlugins.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.debug("Started {}/{} plugins in {}", started, loaded, timer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
|
|||||||
Reference in New Issue
Block a user