From 63192e524950ce1fef5640ce5e6039a6443f8df1 Mon Sep 17 00:00:00 2001 From: Adam Date: Tue, 11 Jul 2017 10:16:15 -0400 Subject: [PATCH] runelite-client: add synchronous job scheduler Convert existing plugins to use it, so that the code all runs on the same main game thread. Fixes various thread related issues caused from accessing game state from executor threads. --- .../java/net/runelite/client/RuneLite.java | 9 +- .../net/runelite/client/callback/Hooks.java | 32 ++++- .../client/plugins/PluginManager.java | 41 +++++++ .../client/plugins/clanchat/ClanChat.java | 18 ++- .../combatnotifier/CombatNotifier.java | 28 ++--- .../client/plugins/fishing/FishingPlugin.java | 27 ++-- .../plugins/idlenotifier/IdleNotifier.java | 21 ++-- .../woodcutting/WoodcuttingPlugin.java | 17 ++- .../client/plugins/xptracker/XPTracker.java | 29 ++--- .../client/plugins/zulrah/StatusOverlay.java | 39 +++--- .../client/plugins/zulrah/TileOverlay.java | 34 ++---- .../client/plugins/zulrah/Zulrah.java | 17 ++- .../net/runelite/client/task/Schedule.java | 42 +++++++ .../runelite/client/task/ScheduledMethod.java | 74 +++++++++++ .../net/runelite/client/task/Scheduler.java | 115 ++++++++++++++++++ 15 files changed, 401 insertions(+), 142 deletions(-) create mode 100644 runelite-client/src/main/java/net/runelite/client/task/Schedule.java create mode 100644 runelite-client/src/main/java/net/runelite/client/task/ScheduledMethod.java create mode 100644 runelite-client/src/main/java/net/runelite/client/task/Scheduler.java diff --git a/runelite-client/src/main/java/net/runelite/client/RuneLite.java b/runelite-client/src/main/java/net/runelite/client/RuneLite.java index 130105c1da..a68e51a3b0 100644 --- a/runelite-client/src/main/java/net/runelite/client/RuneLite.java +++ b/runelite-client/src/main/java/net/runelite/client/RuneLite.java @@ -49,6 +49,7 @@ import net.runelite.client.events.SessionClose; import net.runelite.client.events.SessionOpen; import net.runelite.client.menus.MenuManager; import net.runelite.client.plugins.PluginManager; +import net.runelite.client.task.Scheduler; import net.runelite.client.ui.ClientUI; import net.runelite.client.ui.overlay.OverlayRenderer; import net.runelite.http.api.account.AccountClient; @@ -75,7 +76,8 @@ public class RuneLite private final MenuManager menuManager = new MenuManager(this); private OverlayRenderer renderer; private final EventBus eventBus = new EventBus(this::eventExceptionHandler); - private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(4); + private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); + private final Scheduler scheduler = new Scheduler(this); private WSClient wsclient; private AccountSession accountSession; @@ -327,6 +329,11 @@ public class RuneLite return executor; } + public Scheduler getScheduler() + { + return scheduler; + } + public static TrayIcon getTrayIcon() { return trayIcon; diff --git a/runelite-client/src/main/java/net/runelite/client/callback/Hooks.java b/runelite-client/src/main/java/net/runelite/client/callback/Hooks.java index d84951f0ae..d5f688464e 100644 --- a/runelite-client/src/main/java/net/runelite/client/callback/Hooks.java +++ b/runelite-client/src/main/java/net/runelite/client/callback/Hooks.java @@ -32,6 +32,7 @@ import net.runelite.api.Skill; import net.runelite.client.RuneLite; import net.runelite.client.events.*; import net.runelite.client.game.DeathChecker; +import net.runelite.client.task.Scheduler; import net.runelite.client.ui.overlay.OverlayRenderer; import net.runelite.rs.api.MainBufferProvider; import org.slf4j.Logger; @@ -41,16 +42,23 @@ public class Hooks { private static final Logger logger = LoggerFactory.getLogger(Hooks.class); + private static final long CHECK = 600; // ms - how often to run checks + private static final RuneLite runelite = RuneLite.getRunelite(); private static final DeathChecker death = new DeathChecker(runelite); - public static void draw(Object provider, Graphics graphics, int x, int y) - { - // XXX fix injector to use interface in signature - MainBufferProvider mpb = (MainBufferProvider) provider; - BufferedImage image = (BufferedImage) mpb.getImage(); + private static long lastCheck; - OverlayRenderer renderer = runelite.getRenderer(); + public static void clientMainLoop(Object client, boolean arg1) + { + long now = System.currentTimeMillis(); + + if (now - lastCheck < CHECK) + { + return; + } + + lastCheck = now; try { @@ -61,6 +69,18 @@ public class Hooks logger.warn("error during death check", ex); } + Scheduler scheduler = runelite.getScheduler(); + scheduler.tick(); + } + + public static void draw(Object provider, Graphics graphics, int x, int y) + { + // XXX fix injector to use interface in signature + MainBufferProvider mpb = (MainBufferProvider) provider; + BufferedImage image = (BufferedImage) mpb.getImage(); + + OverlayRenderer renderer = runelite.getRenderer(); + try { renderer.render(image); 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 3d2fce1e5e..7d22cd2eb7 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 @@ -24,9 +24,11 @@ */ package net.runelite.client.plugins; +import net.runelite.client.task.ScheduledMethod; import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.Service; import com.google.common.util.concurrent.ServiceManager; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -56,6 +58,7 @@ import net.runelite.client.plugins.xpglobes.XpGlobes; import net.runelite.client.plugins.xptracker.XPTracker; import net.runelite.client.plugins.xtea.Xtea; import net.runelite.client.plugins.zulrah.Zulrah; +import net.runelite.client.task.Schedule; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -117,6 +120,8 @@ public class PluginManager { logger.debug("Plugin {} is now running", plugin); runelite.getEventBus().register(plugin); + + schedule(plugin); } @Override @@ -124,6 +129,7 @@ public class PluginManager { logger.debug("Plugin {} is stopping", plugin); runelite.getEventBus().unregister(plugin); + unschedule(plugin); } @Override @@ -134,6 +140,7 @@ public class PluginManager if (from == Service.State.RUNNING) { runelite.getEventBus().unregister(plugin); + unschedule(plugin); } } }; @@ -154,4 +161,38 @@ public class PluginManager .map(s -> (Plugin) s) .collect(Collectors.toList()); } + + private void schedule(Plugin plugin) + { + for (Method method : plugin.getClass().getMethods()) + { + Schedule schedule = method.getAnnotation(Schedule.class); + + if (schedule == null) + { + continue; + } + + ScheduledMethod scheduledMethod = new ScheduledMethod(schedule, method, plugin); + logger.debug("Scheduled task {}", scheduledMethod); + + runelite.getScheduler().addScheduledMethod(scheduledMethod); + } + } + + private void unschedule(Plugin plugin) + { + List methods = new ArrayList<>(runelite.getScheduler().getScheduledMethods()); + + for (ScheduledMethod method : methods) + { + if (method.getObject() != plugin) + { + continue; + } + + logger.debug("Removing scheduled task {}", method); + runelite.getScheduler().removeScheduledMethod(method); + } + } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/clanchat/ClanChat.java b/runelite-client/src/main/java/net/runelite/client/plugins/clanchat/ClanChat.java index 5b9f8d780c..b372428c4f 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/clanchat/ClanChat.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/clanchat/ClanChat.java @@ -24,35 +24,31 @@ */ package net.runelite.client.plugins.clanchat; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; +import java.time.temporal.ChronoUnit; import net.runelite.api.GameState; import net.runelite.api.widgets.Widget; import net.runelite.api.widgets.WidgetInfo; import net.runelite.client.RuneLite; import net.runelite.client.plugins.Plugin; +import net.runelite.client.task.Schedule; public class ClanChat extends Plugin { - - private final long INTERVAL = 600; - private ScheduledFuture future; - @Override protected void startUp() throws Exception { - ScheduledExecutorService executor = RuneLite.getRunelite().getExecutor(); - future = executor.scheduleAtFixedRate(this::updateClanChatTitle, INTERVAL, INTERVAL, TimeUnit.MILLISECONDS); } @Override protected void shutDown() throws Exception { - future.cancel(true); } - private void updateClanChatTitle() + @Schedule( + period = 600, + unit = ChronoUnit.MILLIS + ) + public void updateClanChatTitle() { if (RuneLite.getClient().getGameState() != GameState.LOGGED_IN) { diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/combatnotifier/CombatNotifier.java b/runelite-client/src/main/java/net/runelite/client/plugins/combatnotifier/CombatNotifier.java index 876e1fc425..e366e2047a 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/combatnotifier/CombatNotifier.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/combatnotifier/CombatNotifier.java @@ -24,49 +24,41 @@ */ package net.runelite.client.plugins.combatnotifier; +import java.time.Duration; +import java.time.Instant; +import java.time.temporal.ChronoUnit; import net.runelite.api.Actor; import net.runelite.api.Client; import net.runelite.api.GameState; import net.runelite.api.Player; import net.runelite.client.RuneLite; import net.runelite.client.plugins.Plugin; - -import java.awt.*; -import java.time.Duration; -import java.time.Instant; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - +import net.runelite.client.task.Schedule; public class CombatNotifier extends Plugin { - private static final int CHECK_INTERVAL = 1; - private final Client client = RuneLite.getClient(); private final RuneLite runelite = RuneLite.getRunelite(); private final CombatNotifierConfig config = runelite.getConfigManager() .getConfig(CombatNotifierConfig.class); - private ScheduledFuture future; private Instant lastInteracting; @Override protected void startUp() throws Exception { - ScheduledExecutorService executor = RuneLite.getRunelite().getExecutor(); - future = executor.scheduleAtFixedRate(this::checkIdle, CHECK_INTERVAL, CHECK_INTERVAL, TimeUnit.SECONDS); } @Override protected void shutDown() throws Exception { - future.cancel(true); } - private void checkIdle() + @Schedule( + period = 1, + unit = ChronoUnit.SECONDS + ) + public void checkIdle() { if (client.getGameState() != GameState.LOGGED_IN || !config.isEnabled()) { @@ -88,4 +80,4 @@ public class CombatNotifier extends Plugin } } -} \ No newline at end of file +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/fishing/FishingPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/fishing/FishingPlugin.java index 1d1736450a..78c3b839da 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/fishing/FishingPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/fishing/FishingPlugin.java @@ -25,32 +25,27 @@ package net.runelite.client.plugins.fishing; import com.google.common.eventbus.Subscribe; +import java.time.Duration; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Arrays; +import java.util.Collection; import net.runelite.api.ChatMessageType; import net.runelite.client.RuneLite; import net.runelite.client.events.ChatMessage; import net.runelite.client.events.ConfigChanged; import net.runelite.client.plugins.Plugin; +import net.runelite.client.task.Schedule; import net.runelite.client.ui.overlay.Overlay; -import java.time.Duration; -import java.time.Instant; -import java.util.Arrays; -import java.util.Collection; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; - public class FishingPlugin extends Plugin { - private static final int CHECK_INTERVAL = 1; - private final RuneLite runelite = RuneLite.getRunelite(); private final FishingConfig config = runelite.getConfigManager().getConfig(FishingConfig.class); private final FishingOverlay overlay = new FishingOverlay(this); private final FishingSpotOverlay spotOverlay = new FishingSpotOverlay(this); private FishingSession session = new FishingSession(); - private ScheduledFuture future; @Override public Collection getOverlays() @@ -61,9 +56,6 @@ public class FishingPlugin extends Plugin @Override protected void startUp() throws Exception { - ScheduledExecutorService executor = runelite.getExecutor(); - future = executor.scheduleAtFixedRate(this::checkFishing, CHECK_INTERVAL, CHECK_INTERVAL, TimeUnit.SECONDS); - // Initialize overlay config spotOverlay.updateConfig(); } @@ -71,7 +63,6 @@ public class FishingPlugin extends Plugin @Override protected void shutDown() throws Exception { - future.cancel(true); } public FishingConfig getConfig() @@ -104,7 +95,11 @@ public class FishingPlugin extends Plugin spotOverlay.updateConfig(); } - private void checkFishing() + @Schedule( + period = 1, + unit = ChronoUnit.SECONDS + ) + public void checkFishing() { Instant lastFishCaught = session.getLastFishCaught(); if (lastFishCaught == null) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/idlenotifier/IdleNotifier.java b/runelite-client/src/main/java/net/runelite/client/plugins/idlenotifier/IdleNotifier.java index 5dd8b725e9..e3f2565b13 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/idlenotifier/IdleNotifier.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/idlenotifier/IdleNotifier.java @@ -25,13 +25,9 @@ package net.runelite.client.plugins.idlenotifier; import com.google.common.eventbus.Subscribe; -import java.awt.Toolkit; -import java.awt.TrayIcon; import java.time.Duration; import java.time.Instant; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; +import java.time.temporal.ChronoUnit; import static net.runelite.api.AnimationID.*; import net.runelite.api.Client; import net.runelite.api.GameState; @@ -39,32 +35,26 @@ import net.runelite.api.Player; import net.runelite.client.RuneLite; import net.runelite.client.events.AnimationChanged; import net.runelite.client.plugins.Plugin; +import net.runelite.client.task.Schedule; public class IdleNotifier extends Plugin { - private static final String OPERATING_SYSTEM = System.getProperty("os.name"); - private static final int CHECK_INTERVAL = 2; private static final Duration WAIT_DURATION = Duration.ofMillis(2500L); private final Client client = RuneLite.getClient(); private final RuneLite runelite = RuneLite.getRunelite(); - private final TrayIcon trayIcon = RuneLite.getTrayIcon(); - private ScheduledFuture future; private Instant lastAnimating; private boolean notifyIdle = false; @Override protected void startUp() throws Exception { - ScheduledExecutorService executor = runelite.getExecutor(); - future = executor.scheduleAtFixedRate(this::checkIdle, CHECK_INTERVAL, CHECK_INTERVAL, TimeUnit.SECONDS); } @Override protected void shutDown() throws Exception { - future.cancel(true); } @Subscribe @@ -74,6 +64,7 @@ public class IdleNotifier extends Plugin { return; } + int animation = client.getLocalPlayer().getAnimation(); switch (animation) { @@ -148,7 +139,11 @@ public class IdleNotifier extends Plugin } } - private void checkIdle() + @Schedule( + period = 2, + unit = ChronoUnit.SECONDS + ) + public void checkIdle() { Player local = client.getLocalPlayer(); if (notifyIdle && local.getAnimation() == IDLE diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/woodcutting/WoodcuttingPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/woodcutting/WoodcuttingPlugin.java index 261a44932d..c3a8e94785 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/woodcutting/WoodcuttingPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/woodcutting/WoodcuttingPlugin.java @@ -27,25 +27,21 @@ package net.runelite.client.plugins.woodcutting; import com.google.common.eventbus.Subscribe; import java.time.Duration; import java.time.Instant; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; +import java.time.temporal.ChronoUnit; import net.runelite.api.ChatMessageType; import net.runelite.client.RuneLite; import net.runelite.client.events.ChatMessage; import net.runelite.client.plugins.Plugin; +import net.runelite.client.task.Schedule; import net.runelite.client.ui.overlay.Overlay; public class WoodcuttingPlugin extends Plugin { - private static final int CHECK_INTERVAL = 1; - private final RuneLite runelite = RuneLite.getRunelite(); private final WoodcuttingConfig config = runelite.getConfigManager().getConfig(WoodcuttingConfig.class); private final WoodcuttingOverlay overlay = new WoodcuttingOverlay(this); private WoodcuttingSession session = new WoodcuttingSession(); - private ScheduledFuture future; @Override public Overlay getOverlay() @@ -56,14 +52,11 @@ public class WoodcuttingPlugin extends Plugin @Override protected void startUp() throws Exception { - ScheduledExecutorService executor = runelite.getExecutor(); - future = executor.scheduleAtFixedRate(this::checkCutting, CHECK_INTERVAL, CHECK_INTERVAL, TimeUnit.SECONDS); } @Override protected void shutDown() throws Exception { - future.cancel(true); } public WoodcuttingConfig getConfig() @@ -90,7 +83,11 @@ public class WoodcuttingPlugin extends Plugin } } - private void checkCutting() + @Schedule( + period = 1, + unit = ChronoUnit.SECONDS + ) + public void checkCutting() { Instant lastLogCut = session.getLastLogCut(); if (lastLogCut == null) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XPTracker.java b/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XPTracker.java index 3770715896..7ca55b7190 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XPTracker.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XPTracker.java @@ -27,7 +27,6 @@ package net.runelite.client.plugins.xptracker; import com.google.common.eventbus.Subscribe; import net.runelite.api.Client; import net.runelite.api.GameState; -import net.runelite.api.Player; import net.runelite.api.Skill; import net.runelite.client.RuneLite; import net.runelite.client.events.ExperienceChanged; @@ -35,13 +34,11 @@ import net.runelite.client.events.GameStateChanged; import net.runelite.client.plugins.Plugin; import net.runelite.client.ui.ClientUI; import net.runelite.client.ui.NavigationButton; -import net.runelite.client.util.RunnableExceptionLogger; import java.awt.Font; import java.awt.GraphicsEnvironment; import java.awt.event.ActionEvent; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; +import java.time.temporal.ChronoUnit; +import net.runelite.client.task.Schedule; public class XPTracker extends Plugin { @@ -50,25 +47,25 @@ public class XPTracker extends Plugin private final RuneLite runeLite = RuneLite.getRunelite(); private final ClientUI ui = runeLite.getGui(); private final Client client = RuneLite.getClient(); - private ScheduledFuture future; private final NavigationButton navButton = new NavigationButton("XP Tracker"); private final XPPanel xpPanel = new XPPanel(runeLite, this); - private SkillXPInfo[] xpInfos = new SkillXPInfo[NUMBER_OF_SKILLS]; + private final SkillXPInfo[] xpInfos = new SkillXPInfo[NUMBER_OF_SKILLS]; @Subscribe public void onGameStateChanged(GameStateChanged event) { //reset upon login if (event.getGameState() == GameState.LOGGED_IN) + { xpPanel.resetAllSkillXpHr(); + } } @Subscribe public void onXpChanged(ExperienceChanged event) { Skill skill = event.getSkill(); - String currentUser = client.getLocalPlayer().getName(); int skillIdx = skill.ordinal(); //To catch login ExperienceChanged event. @@ -79,7 +76,7 @@ public class XPTracker extends Plugin else { xpInfos[skillIdx] = new SkillXPInfo(client.getSkillExperience(skill), - skill); + skill); } } @@ -96,10 +93,6 @@ public class XPTracker extends Plugin navButton.getButton().setText("XP"); ui.getNavigationPanel().addNavigation(navButton); - ScheduledExecutorService executor = RuneLite.getRunelite().getExecutor(); - future = executor.scheduleAtFixedRate(RunnableExceptionLogger.wrap( - xpPanel::updateAllSkillXpHr), 0, 600, TimeUnit.MILLISECONDS); - Font font = Font.createFont(Font.TRUETYPE_FONT, getClass().getResourceAsStream("/runescape.ttf")); font = font.deriveFont(Font.BOLD, 16); GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); @@ -109,7 +102,15 @@ public class XPTracker extends Plugin @Override protected void shutDown() throws Exception { - future.cancel(true); + } + + @Schedule( + period = 600, + unit = ChronoUnit.MILLIS + ) + public void updateXp() + { + xpPanel.updateAllSkillXpHr(); } public SkillXPInfo[] getXpInfos() diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/zulrah/StatusOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/zulrah/StatusOverlay.java index 1c73ab8ba9..6160d714ea 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/zulrah/StatusOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/zulrah/StatusOverlay.java @@ -55,32 +55,27 @@ class StatusOverlay extends Overlay @Override public Dimension render(Graphics2D graphics) { - ZulrahInstance current, next; + Fight fight = plugin.getFight(); - synchronized (plugin) + if (client.getGameState() != GameState.LOGGED_IN || fight == null) { - Fight fight = plugin.getFight(); - - if (client.getGameState() != GameState.LOGGED_IN || fight == null) - { - return null; - } - - //TODO: Add prayer checking and health warning - graphics.setColor(Color.WHITE); - - ZulrahPattern pattern = fight.getPattern(); - if (pattern == null) - { - // can draw at least the starting place here? - return null; - } - - // Show current type, next type, and jad - current = fight.getZulrah(); - next = pattern.get(fight.getStage() + 1); + return null; } + //TODO: Add prayer checking and health warning + graphics.setColor(Color.WHITE); + + ZulrahPattern pattern = fight.getPattern(); + if (pattern == null) + { + // can draw at least the starting place here? + return null; + } + + // Show current type, next type, and jad + ZulrahInstance current = fight.getZulrah(); + ZulrahInstance next = pattern.get(fight.getStage() + 1); + String currentStr = "Current: " + current.getType(); String nextStr = "Next: " + (next != null ? next.getType() : "Restart"); String jadStr; diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/zulrah/TileOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/zulrah/TileOverlay.java index c638da06a5..375da64e4b 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/zulrah/TileOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/zulrah/TileOverlay.java @@ -25,7 +25,6 @@ */ package net.runelite.client.plugins.zulrah; -import java.awt.BasicStroke; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics2D; @@ -54,29 +53,22 @@ public class TileOverlay extends Overlay @Override public Dimension render(Graphics2D graphics) { - ZulrahPattern pattern; - Point startLocationWorld; - int stage; + Fight fight = plugin.getFight(); - synchronized (plugin) + if (client.getGameState() != GameState.LOGGED_IN || fight == null) { - Fight fight = plugin.getFight(); - - if (client.getGameState() != GameState.LOGGED_IN || fight == null) - { - return null; - } - - pattern = fight.getPattern(); - if (pattern == null) - { - return null; - } - - startLocationWorld = fight.getStartLocationWorld(); - stage = fight.getStage(); + return null; } + ZulrahPattern pattern = fight.getPattern(); + if (pattern == null) + { + return null; + } + + Point startLocationWorld = fight.getStartLocationWorld(); + int stage = fight.getStage(); + ZulrahInstance current = pattern.get(stage); if (current == null) { @@ -123,7 +115,7 @@ public class TileOverlay extends Overlay //to make the centre of the tile on the point, rather than the tile the point resides in localTile = new Point(localTile.getX() + Perspective.LOCAL_TILE_SIZE / 2, localTile.getY() + Perspective.LOCAL_TILE_SIZE / 2); - + Polygon poly = Perspective.getCanvasTilePoly(client, localTile); if (poly != null) { diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/zulrah/Zulrah.java b/runelite-client/src/main/java/net/runelite/client/plugins/zulrah/Zulrah.java index 9e9e9833a1..9f8125f685 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/zulrah/Zulrah.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/zulrah/Zulrah.java @@ -25,11 +25,9 @@ */ package net.runelite.client.plugins.zulrah; +import java.time.temporal.ChronoUnit; import java.util.Arrays; import java.util.Collection; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; import net.runelite.api.Client; import net.runelite.api.GameState; import net.runelite.api.NPC; @@ -44,8 +42,8 @@ import net.runelite.client.plugins.zulrah.patterns.ZulrahPatternA; import net.runelite.client.plugins.zulrah.patterns.ZulrahPatternB; import net.runelite.client.plugins.zulrah.patterns.ZulrahPatternC; import net.runelite.client.plugins.zulrah.patterns.ZulrahPatternD; +import net.runelite.client.task.Schedule; import net.runelite.client.ui.overlay.Overlay; -import net.runelite.client.util.RunnableExceptionLogger; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -68,8 +66,6 @@ public class Zulrah extends Plugin private Fight fight; - private ScheduledFuture future; - @Override public Collection getOverlays() { @@ -79,17 +75,18 @@ public class Zulrah extends Plugin @Override protected void startUp() throws Exception { - ScheduledExecutorService executor = RuneLite.getRunelite().getExecutor(); - future = executor.scheduleAtFixedRate(RunnableExceptionLogger.wrap(this::update), 100, 100, TimeUnit.MILLISECONDS); } @Override protected void shutDown() throws Exception { - future.cancel(true); } - private synchronized void update() + @Schedule( + period = 600, + unit = ChronoUnit.MILLIS + ) + public void update() { if (client == null || client.getGameState() != GameState.LOGGED_IN) { diff --git a/runelite-client/src/main/java/net/runelite/client/task/Schedule.java b/runelite-client/src/main/java/net/runelite/client/task/Schedule.java new file mode 100644 index 0000000000..14c78dd527 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/task/Schedule.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2017, Adam + * 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.task; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.time.temporal.ChronoUnit; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface Schedule +{ + long period(); + + ChronoUnit unit(); + + boolean asynchronous() default false; +} diff --git a/runelite-client/src/main/java/net/runelite/client/task/ScheduledMethod.java b/runelite-client/src/main/java/net/runelite/client/task/ScheduledMethod.java new file mode 100644 index 0000000000..e91a5a89ff --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/task/ScheduledMethod.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2017, Adam + * 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.task; + +import java.lang.reflect.Method; +import java.time.Instant; + +public class ScheduledMethod +{ + private final Schedule schedule; + private final Method method; + private final Object object; + private Instant last = Instant.now(); + + public ScheduledMethod(Schedule schedule, Method method, Object object) + { + this.schedule = schedule; + this.method = method; + this.object = object; + } + + @Override + public String toString() + { + return "ScheduledMethod{" + "schedule=" + schedule + ", method=" + method + ", object=" + object + '}'; + } + + public Schedule getSchedule() + { + return schedule; + } + + public Method getMethod() + { + return method; + } + + public Object getObject() + { + return object; + } + + public Instant getLast() + { + return last; + } + + public void setLast(Instant last) + { + this.last = last; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/task/Scheduler.java b/runelite-client/src/main/java/net/runelite/client/task/Scheduler.java new file mode 100644 index 0000000000..ae09732940 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/task/Scheduler.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2017, Adam + * 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.task; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.time.Duration; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ScheduledExecutorService; +import net.runelite.client.RuneLite; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class Scheduler +{ + private static final Logger logger = LoggerFactory.getLogger(Scheduler.class); + + private final RuneLite runelite; + private final List scheduledMethods = new ArrayList<>(); + + public Scheduler(RuneLite runelite) + { + this.runelite = runelite; + } + + public void addScheduledMethod(ScheduledMethod method) + { + scheduledMethods.add(method); + } + + public void removeScheduledMethod(ScheduledMethod method) + { + scheduledMethods.remove(method); + } + + public List getScheduledMethods() + { + return Collections.unmodifiableList(scheduledMethods); + } + + public void tick() + { + Instant now = Instant.now(); + + for (ScheduledMethod scheduledMethod : scheduledMethods) + { + Instant last = scheduledMethod.getLast(); + + Duration difference = Duration.between(last, now); + + Schedule schedule = scheduledMethod.getSchedule(); + Duration timeSinceRun = Duration.of(schedule.period(), schedule.unit()); + + if (difference.compareTo(timeSinceRun) > 0) + { + logger.trace("Scheduled task triggered: {}", scheduledMethod); + + scheduledMethod.setLast(now); + + if (schedule.asynchronous()) + { + ScheduledExecutorService executor = runelite.getExecutor(); + executor.submit(() -> run(scheduledMethod)); + } + else + { + run(scheduledMethod); + } + } + } + } + + private void run(ScheduledMethod scheduledMethod) + { + Method method = scheduledMethod.getMethod(); + + try + { + method.invoke(scheduledMethod.getObject()); + } + catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) + { + logger.warn("error invoking scheduled task", ex); + } + catch (Exception ex) + { + logger.warn("error during scheduled task", ex); + } + } +}