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.
This commit is contained in:
Adam
2017-07-11 10:16:15 -04:00
parent 8f4134f4bf
commit 63192e5249
15 changed files with 401 additions and 142 deletions

View File

@@ -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;

View File

@@ -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);

View File

@@ -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<ScheduledMethod> 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);
}
}
}

View File

@@ -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)
{

View File

@@ -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
}
}
}
}

View File

@@ -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<Overlay> 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)

View File

@@ -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

View File

@@ -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)

View File

@@ -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()

View File

@@ -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;

View File

@@ -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)
{

View File

@@ -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<Overlay> 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)
{

View File

@@ -0,0 +1,42 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* 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;
}

View File

@@ -0,0 +1,74 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* 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;
}
}

View File

@@ -0,0 +1,115 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* 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<ScheduledMethod> 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<ScheduledMethod> 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);
}
}
}