From 5407e162a3d31bea20fee55f2c30c0d1af796140 Mon Sep 17 00:00:00 2001 From: Adam Date: Mon, 25 Nov 2019 09:39:56 -0500 Subject: [PATCH] client: add world service to manage world fetching --- .../runelite/client/game/WorldService.java | 121 ++++++++++++++++++ .../defaultworld/DefaultWorldPlugin.java | 51 ++++---- .../worldhopper/WorldHopperPlugin.java | 77 +++-------- 3 files changed, 164 insertions(+), 85 deletions(-) create mode 100644 runelite-client/src/main/java/net/runelite/client/game/WorldService.java diff --git a/runelite-client/src/main/java/net/runelite/client/game/WorldService.java b/runelite-client/src/main/java/net/runelite/client/game/WorldService.java new file mode 100644 index 0000000000..0740c59c07 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/game/WorldService.java @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2019, 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.game; + +import java.io.IOException; +import java.util.Comparator; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import javax.annotation.Nullable; +import javax.inject.Inject; +import javax.inject.Singleton; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.Client; +import net.runelite.api.GameState; +import net.runelite.client.util.RunnableExceptionLogger; +import net.runelite.http.api.worlds.World; +import net.runelite.http.api.worlds.WorldClient; +import net.runelite.http.api.worlds.WorldResult; + +@Singleton +@Slf4j +public class WorldService +{ + private static final int WORLD_FETCH_TIMER = 10; // minutes + + private final Client client; + private final ScheduledExecutorService scheduledExecutorService; + private final WorldClient worldClient; + private final CompletableFuture firstRunFuture = new CompletableFuture<>(); + + private WorldResult worlds; + + @Inject + private WorldService(Client client, ScheduledExecutorService scheduledExecutorService, WorldClient worldClient) + { + this.client = client; + this.scheduledExecutorService = scheduledExecutorService; + this.worldClient = worldClient; + + scheduledExecutorService.scheduleWithFixedDelay(RunnableExceptionLogger.wrap(this::tick), 0, WORLD_FETCH_TIMER, TimeUnit.MINUTES); + } + + private void tick() + { + try + { + if (worlds == null || client.getGameState() == GameState.LOGGED_IN) + { + fetch(); + } + } + finally + { + firstRunFuture.complete(worlds); + } + } + + private void fetch() + { + log.debug("Fetching worlds"); + + try + { + WorldResult worldResult = worldClient.lookupWorlds(); + worldResult.getWorlds().sort(Comparator.comparingInt(World::getId)); + worlds = worldResult; + } + catch (IOException ex) + { + log.warn("Error looking up worlds", ex); + } + } + + public void refresh() + { + scheduledExecutorService.execute(this::fetch); + } + + @Nullable + public WorldResult getWorlds() + { + if (!firstRunFuture.isDone()) + { + try + { + return firstRunFuture.get(10, TimeUnit.SECONDS); + } + catch (InterruptedException | ExecutionException | TimeoutException e) + { + log.warn("Failed to retrieve worlds on first run", e); + } + } + + return worlds; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/defaultworld/DefaultWorldPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/defaultworld/DefaultWorldPlugin.java index eed2830ae1..9010f9a8d0 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/defaultworld/DefaultWorldPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/defaultworld/DefaultWorldPlugin.java @@ -25,7 +25,6 @@ package net.runelite.client.plugins.defaultworld; import com.google.inject.Provides; -import java.io.IOException; import javax.inject.Inject; import lombok.extern.slf4j.Slf4j; import net.runelite.api.Client; @@ -34,11 +33,11 @@ import net.runelite.api.events.GameStateChanged; import net.runelite.client.config.ConfigManager; import net.runelite.client.eventbus.Subscribe; import net.runelite.client.events.SessionOpen; +import net.runelite.client.game.WorldService; import net.runelite.client.plugins.Plugin; import net.runelite.client.plugins.PluginDescriptor; import net.runelite.client.util.WorldUtil; import net.runelite.http.api.worlds.World; -import net.runelite.http.api.worlds.WorldClient; import net.runelite.http.api.worlds.WorldResult; @PluginDescriptor( @@ -56,7 +55,7 @@ public class DefaultWorldPlugin extends Plugin private DefaultWorldConfig config; @Inject - private WorldClient worldClient; + private WorldService worldService; private int worldCache; private boolean worldChangeRequired; @@ -116,32 +115,32 @@ public class DefaultWorldPlugin extends Plugin return; } - try + final WorldResult worldResult = worldService.getWorlds(); + + if (worldResult == null) { - final WorldResult worldResult = worldClient.lookupWorlds(); - final World world = worldResult.findWorld(correctedWorld); - - if (world != null) - { - final net.runelite.api.World rsWorld = client.createWorld(); - rsWorld.setActivity(world.getActivity()); - rsWorld.setAddress(world.getAddress()); - rsWorld.setId(world.getId()); - rsWorld.setPlayerCount(world.getPlayers()); - rsWorld.setLocation(world.getLocation()); - rsWorld.setTypes(WorldUtil.toWorldTypes(world.getTypes())); - - client.changeWorld(rsWorld); - log.debug("Applied new world {}", correctedWorld); - } - else - { - log.warn("World {} not found.", correctedWorld); - } + log.warn("Failed to lookup worlds."); + return; } - catch (IOException e) + + final World world = worldResult.findWorld(correctedWorld); + + if (world != null) { - log.warn("Error looking up world {}. Error: {}", correctedWorld, e); + final net.runelite.api.World rsWorld = client.createWorld(); + rsWorld.setActivity(world.getActivity()); + rsWorld.setAddress(world.getAddress()); + rsWorld.setId(world.getId()); + rsWorld.setPlayerCount(world.getPlayers()); + rsWorld.setLocation(world.getLocation()); + rsWorld.setTypes(WorldUtil.toWorldTypes(world.getTypes())); + + client.changeWorld(rsWorld); + log.debug("Applied new world {}", correctedWorld); + } + else + { + log.warn("World {} not found.", correctedWorld); } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/WorldHopperPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/WorldHopperPlugin.java index 98c8e39a27..fa3b61fd33 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/WorldHopperPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/WorldHopperPlugin.java @@ -31,10 +31,8 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ObjectArrays; import com.google.inject.Provides; import java.awt.image.BufferedImage; -import java.io.IOException; import java.time.Duration; import java.time.Instant; -import java.util.Comparator; import java.util.EnumSet; import java.util.HashMap; import java.util.List; @@ -76,6 +74,7 @@ import net.runelite.client.chat.QueuedMessage; import net.runelite.client.config.ConfigManager; import net.runelite.client.eventbus.Subscribe; import net.runelite.client.events.ConfigChanged; +import net.runelite.client.game.WorldService; import net.runelite.client.input.KeyManager; import net.runelite.client.plugins.Plugin; import net.runelite.client.plugins.PluginDescriptor; @@ -88,7 +87,6 @@ import net.runelite.client.util.HotkeyListener; import net.runelite.client.util.Text; import net.runelite.client.util.WorldUtil; import net.runelite.http.api.worlds.World; -import net.runelite.http.api.worlds.WorldClient; import net.runelite.http.api.worlds.WorldResult; import net.runelite.http.api.worlds.WorldType; import org.apache.commons.lang3.ArrayUtils; @@ -101,7 +99,6 @@ import org.apache.commons.lang3.ArrayUtils; @Slf4j public class WorldHopperPlugin extends Plugin { - private static final int WORLD_FETCH_TIMER = 10; private static final int REFRESH_THROTTLE = 60_000; // ms private static final int TICK_THROTTLE = (int) Duration.ofMinutes(10).toMillis(); @@ -130,9 +127,6 @@ public class WorldHopperPlugin extends Plugin @Inject private ChatMessageManager chatMessageManager; - @Inject - private ScheduledExecutorService executorService; - @Inject private WorldHopperConfig config; @@ -143,7 +137,7 @@ public class WorldHopperPlugin extends Plugin private WorldHopperPingOverlay worldHopperOverlay; @Inject - private WorldClient worldClient; + private WorldService worldService; private ScheduledExecutorService hopperExecutorService; @@ -158,11 +152,9 @@ public class WorldHopperPlugin extends Plugin private int favoriteWorld1, favoriteWorld2; - private ScheduledFuture worldResultFuture, pingFuture, currPingFuture; - private WorldResult worldResult; + private ScheduledFuture pingFuture, currPingFuture; private int currentWorld; private Instant lastFetch; - private boolean firstRun; @Getter(AccessLevel.PACKAGE) private int currentPing; @@ -193,7 +185,6 @@ public class WorldHopperPlugin extends Plugin @Override protected void startUp() throws Exception { - firstRun = true; currentPing = -1; keyManager.registerKeyListener(previousKeyListener); @@ -225,8 +216,8 @@ public class WorldHopperPlugin extends Plugin // The plugin has its own executor for pings, as it blocks for a long time hopperExecutorService = new ExecutorServiceExceptionLogger(Executors.newSingleThreadScheduledExecutor()); - // On first run this schedules an initial ping on hopperExecutorService - worldResultFuture = executorService.scheduleAtFixedRate(this::tick, 0, WORLD_FETCH_TIMER, TimeUnit.MINUTES); + // Run the first-run ping + hopperExecutorService.execute(this::pingInitialWorlds); // Give some initial delay - this won't run until after pingInitialWorlds finishes from tick() anyway pingFuture = hopperExecutorService.scheduleWithFixedDelay(this::pingNextWorld, 15, 3, TimeUnit.SECONDS); @@ -247,11 +238,6 @@ public class WorldHopperPlugin extends Plugin keyManager.unregisterKeyListener(previousKeyListener); keyManager.unregisterKeyListener(nextKeyListener); - worldResultFuture.cancel(true); - worldResultFuture = null; - worldResult = null; - lastFetch = null; - clientToolbar.removeNavigation(navButton); hopperExecutorService.shutdown(); @@ -385,6 +371,7 @@ public class WorldHopperPlugin extends Plugin // Don't add entry if user is offline ChatPlayer player = getChatPlayerFromName(event.getTarget()); + WorldResult worldResult = worldService.getWorlds(); if (player == null || player.getWorld() == 0 || player.getWorld() == client.getWorld() || worldResult == null) @@ -475,26 +462,6 @@ public class WorldHopperPlugin extends Plugin this.lastFetch = Instant.now(); // This counts as a fetch as it updates populations } - private void tick() - { - Instant now = Instant.now(); - if (lastFetch != null && now.toEpochMilli() - lastFetch.toEpochMilli() < TICK_THROTTLE) - { - log.debug("Throttling world refresh tick"); - return; - } - - fetchWorlds(); - - // Ping worlds once at startup - if (firstRun) - { - firstRun = false; - // On first run we ping all of the worlds at once to initialize the ping values - hopperExecutorService.execute(this::pingInitialWorlds); - } - } - void refresh() { Instant now = Instant.now(); @@ -504,25 +471,8 @@ public class WorldHopperPlugin extends Plugin return; } - fetchWorlds(); - } - - private void fetchWorlds() - { - log.debug("Fetching worlds"); - - try - { - WorldResult worldResult = worldClient.lookupWorlds(); - worldResult.getWorlds().sort(Comparator.comparingInt(World::getId)); - this.worldResult = worldResult; - this.lastFetch = Instant.now(); - updateList(); - } - catch (IOException ex) - { - log.warn("Error looking up worlds", ex); - } + lastFetch = now; + worldService.refresh(); } /** @@ -530,11 +480,16 @@ public class WorldHopperPlugin extends Plugin */ private void updateList() { - SwingUtilities.invokeLater(() -> panel.populate(worldResult.getWorlds())); + WorldResult worldResult = worldService.getWorlds(); + if (worldResult != null) + { + SwingUtilities.invokeLater(() -> panel.populate(worldResult.getWorlds())); + } } private void hop(boolean previous) { + WorldResult worldResult = worldService.getWorlds(); if (worldResult == null || client.getGameState() != GameState.LOGGED_IN) { return; @@ -645,6 +600,7 @@ public class WorldHopperPlugin extends Plugin private void hop(int worldId) { + WorldResult worldResult = worldService.getWorlds(); // Don't try to hop if the world doesn't exist World world = worldResult.findWorld(worldId); if (world == null) @@ -778,6 +734,7 @@ public class WorldHopperPlugin extends Plugin */ private void pingInitialWorlds() { + WorldResult worldResult = worldService.getWorlds(); if (worldResult == null || !config.showSidebar() || !config.ping()) { return; @@ -801,6 +758,7 @@ public class WorldHopperPlugin extends Plugin */ private void pingNextWorld() { + WorldResult worldResult = worldService.getWorlds(); if (worldResult == null || !config.showSidebar() || !config.ping()) { return; @@ -837,6 +795,7 @@ public class WorldHopperPlugin extends Plugin */ private void pingCurrentWorld() { + WorldResult worldResult = worldService.getWorlds(); // There is no reason to ping the current world if not logged in, as the overlay doesn't draw if (worldResult == null || !config.displayPing() || client.getGameState() != GameState.LOGGED_IN) {