From 3cc887b6d36067024e1d7381654baa8f636a6a50 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 18 Jul 2020 13:39:10 -0700 Subject: [PATCH] Adds concurrent island leveling. New config.yml entry. Can do concurrent checks up to amount admin decides. Fixes issue with reloading where the queue was not disabled and continued to run. --- pom.xml | 2 +- src/main/java/world/bentobox/level/Level.java | 28 +++++++++ .../world/bentobox/level/LevelsManager.java | 1 - .../bentobox/level/calculators/Pipeliner.java | 61 ++++++++++++------- .../bentobox/level/config/ConfigSettings.java | 24 ++++++++ src/main/resources/config.yml | 4 ++ 6 files changed, 96 insertions(+), 24 deletions(-) diff --git a/pom.xml b/pom.xml index 6cc29cc..9110b4b 100644 --- a/pom.xml +++ b/pom.xml @@ -65,7 +65,7 @@ -LOCAL - 2.3.2 + 2.3.3 diff --git a/src/main/java/world/bentobox/level/Level.java b/src/main/java/world/bentobox/level/Level.java index f74a052..12cf20f 100644 --- a/src/main/java/world/bentobox/level/Level.java +++ b/src/main/java/world/bentobox/level/Level.java @@ -100,8 +100,33 @@ public class Level extends Addon implements Listener { @EventHandler public void onBentoBoxReady(BentoBoxReadyEvent e) { manager.loadTopTens(); + /* + * DEBUG code to generate fake islands and then try to level them all. + Bukkit.getScheduler().runTaskLater(getPlugin(), () -> { + getPlugin().getAddonsManager().getGameModeAddons().stream() + .filter(gm -> !settings.getGameModes().contains(gm.getDescription().getName())) + .forEach(gm -> { + for (int i = 0; i < 1000; i++) { + try { + NewIsland.builder().addon(gm).player(User.getInstance(UUID.randomUUID())).name("default").reason(Reason.CREATE).noPaste().build(); + } catch (IOException e1) { + // TODO Auto-generated catch block + e1.printStackTrace(); + } + } + }); + // Queue all islands DEBUG + + getIslands().getIslands().stream().filter(Island::isOwned).forEach(is -> { + + this.getManager().calculateLevel(is.getOwner(), is).thenAccept(r -> + log("Result for island calc " + r.getLevel() + " at " + is.getCenter())); + + }); + }, 60L);*/ } + private void registerPlaceholders(GameModeAddon gm) { if (getPlugin().getPlaceholdersManager() == null) return; // Island Level @@ -172,6 +197,9 @@ public class Level extends Addon implements Listener { @Override public void onDisable() { + // Stop the pipeline + this.getPipeliner().stop(); + // Save player data and the top tens if (manager != null) { manager.save(); } diff --git a/src/main/java/world/bentobox/level/LevelsManager.java b/src/main/java/world/bentobox/level/LevelsManager.java index 94a4e8f..b1517a9 100644 --- a/src/main/java/world/bentobox/level/LevelsManager.java +++ b/src/main/java/world/bentobox/level/LevelsManager.java @@ -121,7 +121,6 @@ public class LevelsManager { addon.getPipeliner().addIsland(island).thenAccept(r -> { // Results are irrelevant because the island is unowned or deleted, or IslandLevelCalcEvent is cancelled if (r == null || fireIslandLevelCalcEvent(targetPlayer, island, r)) { - addon.logWarning("Island calcs stopped due to event cancelation"); result.complete(null); } // Save result diff --git a/src/main/java/world/bentobox/level/calculators/Pipeliner.java b/src/main/java/world/bentobox/level/calculators/Pipeliner.java index 11e08ba..dfc9462 100644 --- a/src/main/java/world/bentobox/level/calculators/Pipeliner.java +++ b/src/main/java/world/bentobox/level/calculators/Pipeliner.java @@ -1,6 +1,8 @@ package world.bentobox.level.calculators; +import java.util.HashSet; import java.util.Queue; +import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentLinkedQueue; @@ -19,9 +21,9 @@ import world.bentobox.level.Level; public class Pipeliner { private static final int START_DURATION = 10; // 10 seconds - private final Queue processQueue; + private final Queue toProcessQueue; + private final Set inProcessQueue; private final BukkitTask task; - private boolean inProcess; private final Level addon; private long time; private long count; @@ -31,25 +33,26 @@ public class Pipeliner { */ public Pipeliner(Level addon) { this.addon = addon; - processQueue = new ConcurrentLinkedQueue<>(); + toProcessQueue = new ConcurrentLinkedQueue<>(); + inProcessQueue = new HashSet<>(); // Loop continuously - check every tick if there is an island to scan task = Bukkit.getScheduler().runTaskTimer(BentoBox.getInstance(), () -> { if (!BentoBox.getInstance().isEnabled()) { cancel(); return; } - // One island at a time - if (inProcess || processQueue.isEmpty()) return; - - IslandLevelCalculator iD = processQueue.poll(); - // Ignore deleted or unonwed islands - if (iD.getIsland().isDeleted() || iD.getIsland().isUnowned()) return; - // Start the process - inProcess = true; - // Start the scanning of a island with the first chunk - scanChunk(iD); - - }, 1L, 1L); + // Complete the current to Process queue first + if (!inProcessQueue.isEmpty() || toProcessQueue.isEmpty()) return; + for (int j = 0; j < addon.getSettings().getConcurrentIslandCalcs() && !toProcessQueue.isEmpty(); j++) { + IslandLevelCalculator iD = toProcessQueue.poll(); + // Ignore deleted or unonwed islands + if (!iD.getIsland().isDeleted() && !iD.getIsland().isUnowned()) { + inProcessQueue.add(iD); + // Start the scanning of a island with the first chunk + scanChunk(iD); + } + } + }, 1L, 10L); } private void cancel() { @@ -60,7 +63,7 @@ public class Pipeliner { * @return number of islands currently in the queue or in process */ public int getIslandsInQueue() { - return inProcess ? processQueue.size() + 1 : processQueue.size(); + return inProcessQueue.size() + toProcessQueue.size(); } /** @@ -68,10 +71,9 @@ public class Pipeliner { * @param iD */ private void scanChunk(IslandLevelCalculator iD) { - if (iD.getIsland().isDeleted() || iD.getIsland().isUnowned()) { + if (iD.getIsland().isDeleted() || iD.getIsland().isUnowned() || task.isCancelled()) { // Island is deleted, so finish early with nothing - addon.log("Canceling island level calculation - island has been deleted, or has become unowned."); - inProcess = false; + inProcessQueue.remove(iD); iD.getR().complete(null); return; } @@ -80,12 +82,12 @@ public class Pipeliner { if (!Bukkit.isPrimaryThread()) { addon.getPlugin().logError("scanChunk not on Primary Thread!"); } - if (Boolean.TRUE.equals(r)) { + if (Boolean.TRUE.equals(r) || task.isCancelled()) { // scanNextChunk returns true if there are more chunks to scan scanChunk(iD); } else { // Done - inProcess = false; + inProcessQueue.remove(iD); iD.getR().complete(iD.getResults()); } }); @@ -100,7 +102,12 @@ public class Pipeliner { */ public CompletableFuture addIsland(Island island) { CompletableFuture r = new CompletableFuture<>(); - processQueue.add(new IslandLevelCalculator(addon, island, r)); + // Check if queue already contains island + /* + if (processQueue.parallelStream().map(IslandLevelCalculator::getIsland).anyMatch(island::equals)) { + return CompletableFuture.completedFuture(null); + }*/ + toProcessQueue.add(new IslandLevelCalculator(addon, island, r)); count++; return r; } @@ -122,5 +129,15 @@ public class Pipeliner { this.time += time; } + /** + * Stop the current queue. + */ + public void stop() { + addon.log("Stopping Level queue"); + task.cancel(); + this.inProcessQueue.clear(); + this.toProcessQueue.clear(); + } + } diff --git a/src/main/java/world/bentobox/level/config/ConfigSettings.java b/src/main/java/world/bentobox/level/config/ConfigSettings.java index f0b19cc..ea24e65 100644 --- a/src/main/java/world/bentobox/level/config/ConfigSettings.java +++ b/src/main/java/world/bentobox/level/config/ConfigSettings.java @@ -18,6 +18,12 @@ public class ConfigSettings implements ConfigObject { @ConfigComment("Level will NOT hook into these game mode addons.") @ConfigEntry(path = "disabled-game-modes") private List gameModes = Collections.emptyList(); + + @ConfigComment("") + @ConfigComment("Number of concurrent island calculations") + @ConfigComment("If your CPU can handle it, you can run parallel island calcs if there are more than one in the queue") + @ConfigEntry(path = "concurrent-island-calcs") + private int concurrentIslandCalcs = 1; @ConfigComment("") @ConfigComment("Calculate island level on login") @@ -292,6 +298,24 @@ public class ConfigSettings implements ConfigObject { } + /** + * @return the concurrentIslandCalcs + */ + public int getConcurrentIslandCalcs() { + if (concurrentIslandCalcs < 1) concurrentIslandCalcs = 1; + return concurrentIslandCalcs; + } + + + /** + * @param concurrentIslandCalcs the concurrentIslandCalcs to set + */ + public void setConcurrentIslandCalcs(int concurrentIslandCalcs) { + if (concurrentIslandCalcs < 1) concurrentIslandCalcs = 1; + this.concurrentIslandCalcs = concurrentIslandCalcs; + } + + diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index d0d988f..77fda09 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -5,6 +5,10 @@ # Level will NOT hook into these game mode addons. disabled-game-modes: - AOneBlock +# +# Number of concurrent island calculations +# If your CPU can handle it, you can run parallel island calcs if there are more than one in the queue +concurrent-island-calcs: 1 # # Calculate island level on login # This silently calculates the player's island level when they login