client: add world service to manage world fetching

This commit is contained in:
Adam
2019-11-25 09:39:56 -05:00
committed by Adam
parent 93b042a5d0
commit 5407e162a3
3 changed files with 164 additions and 85 deletions

View File

@@ -0,0 +1,121 @@
/*
* Copyright (c) 2019, 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.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<WorldResult> 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;
}
}

View File

@@ -25,7 +25,6 @@
package net.runelite.client.plugins.defaultworld; package net.runelite.client.plugins.defaultworld;
import com.google.inject.Provides; import com.google.inject.Provides;
import java.io.IOException;
import javax.inject.Inject; import javax.inject.Inject;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import net.runelite.api.Client; 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.config.ConfigManager;
import net.runelite.client.eventbus.Subscribe; import net.runelite.client.eventbus.Subscribe;
import net.runelite.client.events.SessionOpen; import net.runelite.client.events.SessionOpen;
import net.runelite.client.game.WorldService;
import net.runelite.client.plugins.Plugin; import net.runelite.client.plugins.Plugin;
import net.runelite.client.plugins.PluginDescriptor; import net.runelite.client.plugins.PluginDescriptor;
import net.runelite.client.util.WorldUtil; import net.runelite.client.util.WorldUtil;
import net.runelite.http.api.worlds.World; 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.WorldResult;
@PluginDescriptor( @PluginDescriptor(
@@ -56,7 +55,7 @@ public class DefaultWorldPlugin extends Plugin
private DefaultWorldConfig config; private DefaultWorldConfig config;
@Inject @Inject
private WorldClient worldClient; private WorldService worldService;
private int worldCache; private int worldCache;
private boolean worldChangeRequired; private boolean worldChangeRequired;
@@ -116,32 +115,32 @@ public class DefaultWorldPlugin extends Plugin
return; return;
} }
try final WorldResult worldResult = worldService.getWorlds();
if (worldResult == null)
{ {
final WorldResult worldResult = worldClient.lookupWorlds(); log.warn("Failed to lookup worlds.");
final World world = worldResult.findWorld(correctedWorld); return;
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);
}
} }
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);
} }
} }

View File

@@ -31,10 +31,8 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.ObjectArrays; import com.google.common.collect.ObjectArrays;
import com.google.inject.Provides; import com.google.inject.Provides;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.IOException;
import java.time.Duration; import java.time.Duration;
import java.time.Instant; import java.time.Instant;
import java.util.Comparator;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@@ -76,6 +74,7 @@ import net.runelite.client.chat.QueuedMessage;
import net.runelite.client.config.ConfigManager; import net.runelite.client.config.ConfigManager;
import net.runelite.client.eventbus.Subscribe; import net.runelite.client.eventbus.Subscribe;
import net.runelite.client.events.ConfigChanged; import net.runelite.client.events.ConfigChanged;
import net.runelite.client.game.WorldService;
import net.runelite.client.input.KeyManager; import net.runelite.client.input.KeyManager;
import net.runelite.client.plugins.Plugin; import net.runelite.client.plugins.Plugin;
import net.runelite.client.plugins.PluginDescriptor; 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.Text;
import net.runelite.client.util.WorldUtil; import net.runelite.client.util.WorldUtil;
import net.runelite.http.api.worlds.World; 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.WorldResult;
import net.runelite.http.api.worlds.WorldType; import net.runelite.http.api.worlds.WorldType;
import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.ArrayUtils;
@@ -101,7 +99,6 @@ import org.apache.commons.lang3.ArrayUtils;
@Slf4j @Slf4j
public class WorldHopperPlugin extends Plugin 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 REFRESH_THROTTLE = 60_000; // ms
private static final int TICK_THROTTLE = (int) Duration.ofMinutes(10).toMillis(); private static final int TICK_THROTTLE = (int) Duration.ofMinutes(10).toMillis();
@@ -130,9 +127,6 @@ public class WorldHopperPlugin extends Plugin
@Inject @Inject
private ChatMessageManager chatMessageManager; private ChatMessageManager chatMessageManager;
@Inject
private ScheduledExecutorService executorService;
@Inject @Inject
private WorldHopperConfig config; private WorldHopperConfig config;
@@ -143,7 +137,7 @@ public class WorldHopperPlugin extends Plugin
private WorldHopperPingOverlay worldHopperOverlay; private WorldHopperPingOverlay worldHopperOverlay;
@Inject @Inject
private WorldClient worldClient; private WorldService worldService;
private ScheduledExecutorService hopperExecutorService; private ScheduledExecutorService hopperExecutorService;
@@ -158,11 +152,9 @@ public class WorldHopperPlugin extends Plugin
private int favoriteWorld1, favoriteWorld2; private int favoriteWorld1, favoriteWorld2;
private ScheduledFuture<?> worldResultFuture, pingFuture, currPingFuture; private ScheduledFuture<?> pingFuture, currPingFuture;
private WorldResult worldResult;
private int currentWorld; private int currentWorld;
private Instant lastFetch; private Instant lastFetch;
private boolean firstRun;
@Getter(AccessLevel.PACKAGE) @Getter(AccessLevel.PACKAGE)
private int currentPing; private int currentPing;
@@ -193,7 +185,6 @@ public class WorldHopperPlugin extends Plugin
@Override @Override
protected void startUp() throws Exception protected void startUp() throws Exception
{ {
firstRun = true;
currentPing = -1; currentPing = -1;
keyManager.registerKeyListener(previousKeyListener); 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 // The plugin has its own executor for pings, as it blocks for a long time
hopperExecutorService = new ExecutorServiceExceptionLogger(Executors.newSingleThreadScheduledExecutor()); hopperExecutorService = new ExecutorServiceExceptionLogger(Executors.newSingleThreadScheduledExecutor());
// On first run this schedules an initial ping on hopperExecutorService // Run the first-run ping
worldResultFuture = executorService.scheduleAtFixedRate(this::tick, 0, WORLD_FETCH_TIMER, TimeUnit.MINUTES); hopperExecutorService.execute(this::pingInitialWorlds);
// Give some initial delay - this won't run until after pingInitialWorlds finishes from tick() anyway // Give some initial delay - this won't run until after pingInitialWorlds finishes from tick() anyway
pingFuture = hopperExecutorService.scheduleWithFixedDelay(this::pingNextWorld, 15, 3, TimeUnit.SECONDS); pingFuture = hopperExecutorService.scheduleWithFixedDelay(this::pingNextWorld, 15, 3, TimeUnit.SECONDS);
@@ -247,11 +238,6 @@ public class WorldHopperPlugin extends Plugin
keyManager.unregisterKeyListener(previousKeyListener); keyManager.unregisterKeyListener(previousKeyListener);
keyManager.unregisterKeyListener(nextKeyListener); keyManager.unregisterKeyListener(nextKeyListener);
worldResultFuture.cancel(true);
worldResultFuture = null;
worldResult = null;
lastFetch = null;
clientToolbar.removeNavigation(navButton); clientToolbar.removeNavigation(navButton);
hopperExecutorService.shutdown(); hopperExecutorService.shutdown();
@@ -385,6 +371,7 @@ public class WorldHopperPlugin extends Plugin
// Don't add entry if user is offline // Don't add entry if user is offline
ChatPlayer player = getChatPlayerFromName(event.getTarget()); ChatPlayer player = getChatPlayerFromName(event.getTarget());
WorldResult worldResult = worldService.getWorlds();
if (player == null || player.getWorld() == 0 || player.getWorld() == client.getWorld() if (player == null || player.getWorld() == 0 || player.getWorld() == client.getWorld()
|| worldResult == null) || worldResult == null)
@@ -475,26 +462,6 @@ public class WorldHopperPlugin extends Plugin
this.lastFetch = Instant.now(); // This counts as a fetch as it updates populations 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() void refresh()
{ {
Instant now = Instant.now(); Instant now = Instant.now();
@@ -504,25 +471,8 @@ public class WorldHopperPlugin extends Plugin
return; return;
} }
fetchWorlds(); lastFetch = now;
} worldService.refresh();
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);
}
} }
/** /**
@@ -530,11 +480,16 @@ public class WorldHopperPlugin extends Plugin
*/ */
private void updateList() 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) private void hop(boolean previous)
{ {
WorldResult worldResult = worldService.getWorlds();
if (worldResult == null || client.getGameState() != GameState.LOGGED_IN) if (worldResult == null || client.getGameState() != GameState.LOGGED_IN)
{ {
return; return;
@@ -645,6 +600,7 @@ public class WorldHopperPlugin extends Plugin
private void hop(int worldId) private void hop(int worldId)
{ {
WorldResult worldResult = worldService.getWorlds();
// Don't try to hop if the world doesn't exist // Don't try to hop if the world doesn't exist
World world = worldResult.findWorld(worldId); World world = worldResult.findWorld(worldId);
if (world == null) if (world == null)
@@ -778,6 +734,7 @@ public class WorldHopperPlugin extends Plugin
*/ */
private void pingInitialWorlds() private void pingInitialWorlds()
{ {
WorldResult worldResult = worldService.getWorlds();
if (worldResult == null || !config.showSidebar() || !config.ping()) if (worldResult == null || !config.showSidebar() || !config.ping())
{ {
return; return;
@@ -801,6 +758,7 @@ public class WorldHopperPlugin extends Plugin
*/ */
private void pingNextWorld() private void pingNextWorld()
{ {
WorldResult worldResult = worldService.getWorlds();
if (worldResult == null || !config.showSidebar() || !config.ping()) if (worldResult == null || !config.showSidebar() || !config.ping())
{ {
return; return;
@@ -837,6 +795,7 @@ public class WorldHopperPlugin extends Plugin
*/ */
private void pingCurrentWorld() 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 // 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) if (worldResult == null || !config.displayPing() || client.getGameState() != GameState.LOGGED_IN)
{ {