devtools: add shell window

This commit is contained in:
Max Weber
2021-02-22 10:40:05 -07:00
parent 2c4f3a895e
commit 7480c74750
16 changed files with 1645 additions and 36 deletions

View File

@@ -51,26 +51,20 @@ import net.runelite.client.util.ReflectUtil;
@ThreadSafe
public class EventBus
{
@FunctionalInterface
public interface SubscriberMethod
{
void invoke(Object event);
}
@Value
private static class Subscriber
public static class Subscriber
{
private final Object object;
private final Method method;
private final float priority;
@EqualsAndHashCode.Exclude
private final SubscriberMethod lamda;
private final Consumer<Object> lambda;
void invoke(final Object arg) throws Exception
{
if (lamda != null)
if (lambda != null)
{
lamda.invoke(arg);
lambda.accept(arg);
}
else
{
@@ -80,7 +74,9 @@ public class EventBus
}
private final Consumer<Throwable> exceptionHandler;
private ImmutableMultimap<Class, Subscriber> subscribers = ImmutableMultimap.of();
@Nonnull
private ImmutableMultimap<Class<?>, Subscriber> subscribers = ImmutableMultimap.of();
/**
* Instantiates EventBus with default exception handler
@@ -99,13 +95,8 @@ public class EventBus
*/
public synchronized void register(@Nonnull final Object object)
{
final ImmutableMultimap.Builder<Class, Subscriber> builder = ImmutableMultimap.builder();
if (subscribers != null)
{
builder.putAll(subscribers);
}
final ImmutableMultimap.Builder<Class<?>, Subscriber> builder = ImmutableMultimap.builder();
builder.putAll(subscribers);
builder.orderValuesBy(Comparator.comparing(Subscriber::getPriority).reversed()
.thenComparing(s -> s.object.getClass().getName()));
@@ -141,7 +132,7 @@ public class EventBus
Preconditions.checkArgument(method.getName().equals(preferredName), "Subscribed method " + method + " should be named " + preferredName);
method.setAccessible(true);
SubscriberMethod lambda = null;
Consumer<Object> lambda = null;
try
{
@@ -150,14 +141,14 @@ public class EventBus
final MethodHandle target = caller.findVirtual(clazz, method.getName(), subscription);
final CallSite site = LambdaMetafactory.metafactory(
caller,
"invoke",
MethodType.methodType(SubscriberMethod.class, clazz),
"accept",
MethodType.methodType(Consumer.class, clazz),
subscription.changeParameterType(0, Object.class),
target,
subscription);
final MethodHandle factory = site.getTarget();
lambda = (SubscriberMethod) factory.bindTo(object).invokeExact();
lambda = (Consumer<Object>) factory.bindTo(object).invokeExact();
}
catch (Throwable e)
{
@@ -173,6 +164,21 @@ public class EventBus
subscribers = builder.build();
}
public synchronized <T> Subscriber register(Class<T> clazz, Consumer<T> subFn, float priority)
{
final ImmutableMultimap.Builder<Class<?>, Subscriber> builder = ImmutableMultimap.builder();
builder.putAll(subscribers);
builder.orderValuesBy(Comparator.comparing(Subscriber::getPriority).reversed()
.thenComparing(s -> s.object.getClass().getName()));
Subscriber sub = new Subscriber(subFn, null, priority, (Consumer<Object>) subFn);
builder.put(clazz, sub);
subscribers = builder.build();
return sub;
}
/**
* Unregisters all subscribed methods from provided subscriber object.
*
@@ -180,12 +186,7 @@ public class EventBus
*/
public synchronized void unregister(@Nonnull final Object object)
{
if (subscribers == null)
{
return;
}
final Multimap<Class, Subscriber> map = HashMultimap.create();
final Multimap<Class<?>, Subscriber> map = HashMultimap.create();
map.putAll(subscribers);
for (Class<?> clazz = object.getClass(); clazz != null; clazz = clazz.getSuperclass())
@@ -207,6 +208,21 @@ public class EventBus
subscribers = ImmutableMultimap.copyOf(map);
}
public synchronized void unregister(Subscriber sub)
{
if (sub == null)
{
return;
}
final Multimap<Class<?>, Subscriber> map = HashMultimap.create();
map.putAll(subscribers);
map.values().remove(sub);
subscribers = ImmutableMultimap.copyOf(map);
}
/**
* Posts provided event to all registered subscribers. Subscriber calls are invoked immediately,
* ordered by priority then their declaring class' name.

View File

@@ -25,6 +25,7 @@
*/
package net.runelite.client.plugins.devtools;
import com.google.inject.ProvisionException;
import java.awt.GridLayout;
import java.awt.TrayIcon;
import java.util.concurrent.ScheduledExecutorService;
@@ -32,6 +33,7 @@ import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import javax.swing.JButton;
import javax.swing.JPanel;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.Client;
import net.runelite.api.GameState;
import net.runelite.api.MenuAction;
@@ -44,6 +46,7 @@ import net.runelite.client.ui.overlay.infobox.Counter;
import net.runelite.client.ui.overlay.infobox.InfoBoxManager;
import net.runelite.client.util.ImageUtil;
@Slf4j
class DevToolsPanel extends PluginPanel
{
private final Client client;
@@ -174,6 +177,21 @@ class DevToolsPanel extends PluginPanel
disconnectBtn.addActionListener(e -> clientThread.invoke(() -> client.setGameState(GameState.CONNECTION_LOST)));
container.add(disconnectBtn);
try
{
ShellFrame sf = plugin.getInjector().getInstance(ShellFrame.class);
container.add(plugin.getShell());
plugin.getShell().addFrame(sf);
}
catch (LinkageError | ProvisionException e)
{
log.debug("Shell is not supported", e);
}
catch (Exception e)
{
log.info("Shell couldn't be loaded", e);
}
return container;
}
}

View File

@@ -143,6 +143,7 @@ public class DevToolsPlugin extends Plugin
private DevToolsButton soundEffects;
private DevToolsButton scriptInspector;
private DevToolsButton inventoryInspector;
private DevToolsButton shell;
private NavigationButton navButton;
@Provides
@@ -187,6 +188,7 @@ public class DevToolsPlugin extends Plugin
soundEffects = new DevToolsButton("Sound Effects");
scriptInspector = new DevToolsButton("Script Inspector");
inventoryInspector = new DevToolsButton("Inventory Inspector");
shell = new DevToolsButton("Shell");
overlayManager.add(overlay);
overlayManager.add(locationOverlay);

View File

@@ -0,0 +1,70 @@
/*
* Copyright (c) 2021 Abex
* 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 OWNER 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.client.plugins.devtools;
import java.util.concurrent.ScheduledExecutorService;
import javax.inject.Inject;
import javax.inject.Singleton;
import net.runelite.client.RuneLite;
import net.runelite.client.callback.ClientThread;
import net.runelite.jshell.ShellPanel;
@Singleton
class ShellFrame extends DevToolsFrame
{
private final ShellPanel shellPanel;
@Inject
ShellFrame(ClientThread clientThread, ScheduledExecutorService executor)
{
this.shellPanel = new ShellPanel(executor)
{
@Override
protected void invokeOnClientThread(Runnable r)
{
clientThread.invoke(r);
}
};
setContentPane(shellPanel);
setTitle("RuneLite Shell");
pack();
}
@Override
public void open()
{
shellPanel.switchContext(RuneLite.getInjector());
super.open();
}
@Override
public void close()
{
super.close();
shellPanel.freeContext();
}
}