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

View File

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