mirror of
https://github.com/BentoBoxWorld/Level.git
synced 2024-11-30 14:03:27 +01:00
Adds new config options. Prevents queue stuffing.
Players without cooldowns will not be able to stuff the level queue with calculation requests. Only one island level calculation at a time. Watch dog timeout on calculations added. Default 5 minutes. Config option to not use island zeroing.
This commit is contained in:
parent
49e56b515a
commit
000463e10c
@ -147,17 +147,20 @@ public class IslandLevelCalculator {
|
|||||||
|
|
||||||
private final Results results;
|
private final Results results;
|
||||||
private long duration;
|
private long duration;
|
||||||
|
private final boolean zeroIsland;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor to get the level for an island
|
* Constructor to get the level for an island
|
||||||
* @param addon - Level addon
|
* @param addon - Level addon
|
||||||
* @param island - the island to scan
|
* @param island - the island to scan
|
||||||
* @param r - completable result that will be completed when the calculation is complete
|
* @param r - completable result that will be completed when the calculation is complete
|
||||||
|
* @param zeroIsland - true if the calculation is due to an island zeroing
|
||||||
*/
|
*/
|
||||||
public IslandLevelCalculator(Level addon, Island island, CompletableFuture<Results> r) {
|
public IslandLevelCalculator(Level addon, Island island, CompletableFuture<Results> r, boolean zeroIsland) {
|
||||||
this.addon = addon;
|
this.addon = addon;
|
||||||
this.island = island;
|
this.island = island;
|
||||||
this.r = r;
|
this.r = r;
|
||||||
|
this.zeroIsland = zeroIsland;
|
||||||
results = new Results();
|
results = new Results();
|
||||||
duration = System.currentTimeMillis();
|
duration = System.currentTimeMillis();
|
||||||
chunksToCheck = getChunksToScan(island);
|
chunksToCheck = getChunksToScan(island);
|
||||||
@ -536,4 +539,11 @@ public class IslandLevelCalculator {
|
|||||||
addon.getPipeliner().setTime(System.currentTimeMillis() - duration);
|
addon.getPipeliner().setTime(System.currentTimeMillis() - duration);
|
||||||
// All done.
|
// All done.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the zeroIsland
|
||||||
|
*/
|
||||||
|
boolean isNotZeroIsland() {
|
||||||
|
return !zeroIsland;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
package world.bentobox.level.calculators;
|
package world.bentobox.level.calculators;
|
||||||
|
|
||||||
import java.util.HashSet;
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
|
|
||||||
@ -12,6 +12,7 @@ import org.bukkit.scheduler.BukkitTask;
|
|||||||
import world.bentobox.bentobox.BentoBox;
|
import world.bentobox.bentobox.BentoBox;
|
||||||
import world.bentobox.bentobox.database.objects.Island;
|
import world.bentobox.bentobox.database.objects.Island;
|
||||||
import world.bentobox.level.Level;
|
import world.bentobox.level.Level;
|
||||||
|
import world.bentobox.level.calculators.Results.Result;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A pipeliner that will process one island at a time
|
* A pipeliner that will process one island at a time
|
||||||
@ -22,7 +23,7 @@ public class Pipeliner {
|
|||||||
|
|
||||||
private static final int START_DURATION = 10; // 10 seconds
|
private static final int START_DURATION = 10; // 10 seconds
|
||||||
private final Queue<IslandLevelCalculator> toProcessQueue;
|
private final Queue<IslandLevelCalculator> toProcessQueue;
|
||||||
private final Set<IslandLevelCalculator> inProcessQueue;
|
private final Map<IslandLevelCalculator, Long> inProcessQueue;
|
||||||
private final BukkitTask task;
|
private final BukkitTask task;
|
||||||
private final Level addon;
|
private final Level addon;
|
||||||
private long time;
|
private long time;
|
||||||
@ -34,7 +35,7 @@ public class Pipeliner {
|
|||||||
public Pipeliner(Level addon) {
|
public Pipeliner(Level addon) {
|
||||||
this.addon = addon;
|
this.addon = addon;
|
||||||
toProcessQueue = new ConcurrentLinkedQueue<>();
|
toProcessQueue = new ConcurrentLinkedQueue<>();
|
||||||
inProcessQueue = new HashSet<>();
|
inProcessQueue = new HashMap<>();
|
||||||
// Loop continuously - check every tick if there is an island to scan
|
// Loop continuously - check every tick if there is an island to scan
|
||||||
task = Bukkit.getScheduler().runTaskTimer(BentoBox.getInstance(), () -> {
|
task = Bukkit.getScheduler().runTaskTimer(BentoBox.getInstance(), () -> {
|
||||||
if (!BentoBox.getInstance().isEnabled()) {
|
if (!BentoBox.getInstance().isEnabled()) {
|
||||||
@ -47,7 +48,7 @@ public class Pipeliner {
|
|||||||
IslandLevelCalculator iD = toProcessQueue.poll();
|
IslandLevelCalculator iD = toProcessQueue.poll();
|
||||||
// Ignore deleted or unonwed islands
|
// Ignore deleted or unonwed islands
|
||||||
if (!iD.getIsland().isDeleted() && !iD.getIsland().isUnowned()) {
|
if (!iD.getIsland().isDeleted() && !iD.getIsland().isUnowned()) {
|
||||||
inProcessQueue.add(iD);
|
inProcessQueue.put(iD, System.currentTimeMillis());
|
||||||
// Start the scanning of a island with the first chunk
|
// Start the scanning of a island with the first chunk
|
||||||
scanChunk(iD);
|
scanChunk(iD);
|
||||||
}
|
}
|
||||||
@ -82,6 +83,17 @@ public class Pipeliner {
|
|||||||
if (!Bukkit.isPrimaryThread()) {
|
if (!Bukkit.isPrimaryThread()) {
|
||||||
addon.getPlugin().logError("scanChunk not on Primary Thread!");
|
addon.getPlugin().logError("scanChunk not on Primary Thread!");
|
||||||
}
|
}
|
||||||
|
// Timeout check
|
||||||
|
if (System.currentTimeMillis() - inProcessQueue.get(iD) > addon.getSettings().getCalculationTimeout() * 60000) {
|
||||||
|
// Done
|
||||||
|
inProcessQueue.remove(iD);
|
||||||
|
iD.getR().complete(new Results(Result.TIMEOUT));
|
||||||
|
addon.logError("Level calculation timed out after " + addon.getSettings().getCalculationTimeout() + "m for island: " + iD.getIsland());
|
||||||
|
if (!iD.isNotZeroIsland()) {
|
||||||
|
addon.logError("Island level was being zeroed.");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (Boolean.TRUE.equals(r) || task.isCancelled()) {
|
if (Boolean.TRUE.equals(r) || task.isCancelled()) {
|
||||||
// scanNextChunk returns true if there are more chunks to scan
|
// scanNextChunk returns true if there are more chunks to scan
|
||||||
scanChunk(iD);
|
scanChunk(iD);
|
||||||
@ -96,18 +108,33 @@ public class Pipeliner {
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds an island to the scanning queue
|
* Adds an island to the scanning queue but only if the island is not already in the queue
|
||||||
* @param island - the island to scan
|
* @param island - the island to scan
|
||||||
*
|
* @return CompletableFuture of the results. Results will be null if the island is already in the queue
|
||||||
*/
|
*/
|
||||||
public CompletableFuture<Results> addIsland(Island island) {
|
public CompletableFuture<Results> addIsland(Island island) {
|
||||||
|
// Check if queue already contains island and it's not an island zero calculation
|
||||||
|
if (inProcessQueue.keySet().parallelStream().filter(IslandLevelCalculator::isNotZeroIsland)
|
||||||
|
.map(IslandLevelCalculator::getIsland).anyMatch(island::equals)
|
||||||
|
|| toProcessQueue.parallelStream().filter(IslandLevelCalculator::isNotZeroIsland)
|
||||||
|
.map(IslandLevelCalculator::getIsland).anyMatch(island::equals)) {
|
||||||
|
return CompletableFuture.completedFuture(new Results(Result.IN_PROGRESS));
|
||||||
|
}
|
||||||
|
return addToQueue(island, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an island to the scanning queue
|
||||||
|
* @param island - the island to scan
|
||||||
|
* @return CompletableFuture of the results
|
||||||
|
*/
|
||||||
|
public CompletableFuture<Results> zeroIsland(Island island) {
|
||||||
|
return addToQueue(island, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private CompletableFuture<Results> addToQueue(Island island, boolean zeroing) {
|
||||||
CompletableFuture<Results> r = new CompletableFuture<>();
|
CompletableFuture<Results> r = new CompletableFuture<>();
|
||||||
// Check if queue already contains island
|
toProcessQueue.add(new IslandLevelCalculator(addon, island, r, zeroing));
|
||||||
/*
|
|
||||||
if (processQueue.parallelStream().map(IslandLevelCalculator::getIsland).anyMatch(island::equals)) {
|
|
||||||
return CompletableFuture.completedFuture(null);
|
|
||||||
}*/
|
|
||||||
toProcessQueue.add(new IslandLevelCalculator(addon, island, r));
|
|
||||||
count++;
|
count++;
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
@ -136,8 +163,10 @@ public class Pipeliner {
|
|||||||
addon.log("Stopping Level queue");
|
addon.log("Stopping Level queue");
|
||||||
task.cancel();
|
task.cancel();
|
||||||
this.inProcessQueue.clear();
|
this.inProcessQueue.clear();
|
||||||
this.toProcessQueue.clear();
|
this.toProcessQueue.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,20 @@ import com.google.common.collect.HashMultiset;
|
|||||||
import com.google.common.collect.Multiset;
|
import com.google.common.collect.Multiset;
|
||||||
|
|
||||||
public class Results {
|
public class Results {
|
||||||
|
public enum Result {
|
||||||
|
/**
|
||||||
|
* A level calc is already in progress
|
||||||
|
*/
|
||||||
|
IN_PROGRESS,
|
||||||
|
/**
|
||||||
|
* Results will be available
|
||||||
|
*/
|
||||||
|
AVAILABLE,
|
||||||
|
/**
|
||||||
|
* Result if calculation timed out
|
||||||
|
*/
|
||||||
|
TIMEOUT
|
||||||
|
}
|
||||||
List<String> report;
|
List<String> report;
|
||||||
final Multiset<Material> mdCount = HashMultiset.create();
|
final Multiset<Material> mdCount = HashMultiset.create();
|
||||||
final Multiset<Material> uwCount = HashMultiset.create();
|
final Multiset<Material> uwCount = HashMultiset.create();
|
||||||
@ -22,7 +36,15 @@ public class Results {
|
|||||||
AtomicInteger deathHandicap = new AtomicInteger(0);
|
AtomicInteger deathHandicap = new AtomicInteger(0);
|
||||||
AtomicLong pointsToNextLevel = new AtomicLong(0);
|
AtomicLong pointsToNextLevel = new AtomicLong(0);
|
||||||
AtomicLong initialLevel = new AtomicLong(0);
|
AtomicLong initialLevel = new AtomicLong(0);
|
||||||
|
final Result state;
|
||||||
|
|
||||||
|
public Results(Result state) {
|
||||||
|
this.state = state;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Results() {
|
||||||
|
this.state = Result.AVAILABLE;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* @return the deathHandicap
|
* @return the deathHandicap
|
||||||
*/
|
*/
|
||||||
@ -101,5 +123,11 @@ public class Results {
|
|||||||
public Multiset<Material> getUwCount() {
|
public Multiset<Material> getUwCount() {
|
||||||
return uwCount;
|
return uwCount;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* @return the state
|
||||||
|
*/
|
||||||
|
public Result getState() {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -10,6 +10,7 @@ import world.bentobox.bentobox.api.user.User;
|
|||||||
import world.bentobox.bentobox.database.objects.Island;
|
import world.bentobox.bentobox.database.objects.Island;
|
||||||
import world.bentobox.level.Level;
|
import world.bentobox.level.Level;
|
||||||
import world.bentobox.level.calculators.Results;
|
import world.bentobox.level.calculators.Results;
|
||||||
|
import world.bentobox.level.calculators.Results.Result;
|
||||||
|
|
||||||
public class IslandLevelCommand extends CompositeCommand {
|
public class IslandLevelCommand extends CompositeCommand {
|
||||||
|
|
||||||
@ -88,6 +89,13 @@ public class IslandLevelCommand extends CompositeCommand {
|
|||||||
long oldLevel = addon.getManager().getIslandLevel(getWorld(), playerUUID);
|
long oldLevel = addon.getManager().getIslandLevel(getWorld(), playerUUID);
|
||||||
addon.getManager().calculateLevel(playerUUID, island).thenAccept(results -> {
|
addon.getManager().calculateLevel(playerUUID, island).thenAccept(results -> {
|
||||||
if (results == null) return; // island was deleted or become unowned
|
if (results == null) return; // island was deleted or become unowned
|
||||||
|
if (results.getState().equals(Result.IN_PROGRESS)) {
|
||||||
|
user.sendMessage("island.level.in-progress");
|
||||||
|
return;
|
||||||
|
} else if (results.getState().equals(Result.TIMEOUT)) {
|
||||||
|
user.sendMessage("island.level.time-out");
|
||||||
|
return;
|
||||||
|
}
|
||||||
showResult(user, playerUUID, island, oldLevel, results);
|
showResult(user, playerUUID, island, oldLevel, results);
|
||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
|
@ -18,13 +18,30 @@ public class ConfigSettings implements ConfigObject {
|
|||||||
@ConfigComment("Level will NOT hook into these game mode addons.")
|
@ConfigComment("Level will NOT hook into these game mode addons.")
|
||||||
@ConfigEntry(path = "disabled-game-modes")
|
@ConfigEntry(path = "disabled-game-modes")
|
||||||
private List<String> gameModes = Collections.emptyList();
|
private List<String> gameModes = Collections.emptyList();
|
||||||
|
|
||||||
@ConfigComment("")
|
@ConfigComment("")
|
||||||
@ConfigComment("Number of concurrent island calculations")
|
@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")
|
@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")
|
@ConfigEntry(path = "concurrent-island-calcs")
|
||||||
private int concurrentIslandCalcs = 1;
|
private int concurrentIslandCalcs = 1;
|
||||||
|
|
||||||
|
@ConfigComment("")
|
||||||
|
@ConfigComment("Island level calculation timeout in minutes.")
|
||||||
|
@ConfigComment("If an island takes longer that this time to calculate, then the calculation will abort.")
|
||||||
|
@ConfigComment("Generally, calculation should only take a few seconds, so if this ever triggers then something is not right.")
|
||||||
|
@ConfigEntry(path = "calculation-timeout")
|
||||||
|
private int calculationTimeout = 5;
|
||||||
|
|
||||||
|
|
||||||
|
@ConfigComment("")
|
||||||
|
@ConfigComment("Zero island levels on new island or island reset")
|
||||||
|
@ConfigComment("If true, Level will calculate the starter island's level and remove it from any future level calculations.")
|
||||||
|
@ConfigComment("If false, the player's starter island and will count towards their level.")
|
||||||
|
@ConfigComment("This will reduce CPU if it isn't used.")
|
||||||
|
@ConfigEntry(path = "zero-new-island-levels")
|
||||||
|
private boolean zeroNewIslandLevels = true;
|
||||||
|
|
||||||
|
|
||||||
@ConfigComment("")
|
@ConfigComment("")
|
||||||
@ConfigComment("Calculate island level on login")
|
@ConfigComment("Calculate island level on login")
|
||||||
@ConfigComment("This silently calculates the player's island level when they login")
|
@ConfigComment("This silently calculates the player's island level when they login")
|
||||||
@ -316,8 +333,35 @@ public class ConfigSettings implements ConfigObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the zeroNewIslandLevels
|
||||||
|
*/
|
||||||
|
public boolean isZeroNewIslandLevels() {
|
||||||
|
return zeroNewIslandLevels;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param zeroNewIslandLevels the zeroNewIslandLevels to set
|
||||||
|
*/
|
||||||
|
public void setZeroNewIslandLevels(boolean zeroNewIslandLevels) {
|
||||||
|
this.zeroNewIslandLevels = zeroNewIslandLevels;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the calculationTimeout
|
||||||
|
*/
|
||||||
|
public int getCalculationTimeout() {
|
||||||
|
return calculationTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param calculationTimeout the calculationTimeout to set
|
||||||
|
*/
|
||||||
|
public void setCalculationTimeout(int calculationTimeout) {
|
||||||
|
this.calculationTimeout = calculationTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -39,20 +39,22 @@ public class IslandActivitiesListeners implements Listener {
|
|||||||
|
|
||||||
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
|
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
|
||||||
public void onNewIsland(IslandCreatedEvent e) {
|
public void onNewIsland(IslandCreatedEvent e) {
|
||||||
|
if (addon.getSettings().isZeroNewIslandLevels()) {
|
||||||
zeroIsland(e.getIsland());
|
zeroIsland(e.getIsland());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
|
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
|
||||||
public void onNewIsland(IslandResettedEvent e) {
|
public void onNewIsland(IslandResettedEvent e) {
|
||||||
|
if (addon.getSettings().isZeroNewIslandLevels()) {
|
||||||
zeroIsland(e.getIsland());
|
zeroIsland(e.getIsland());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void zeroIsland(final Island island) {
|
private void zeroIsland(final Island island) {
|
||||||
// Clear the island setting
|
// Clear the island setting
|
||||||
if (island.getOwner() != null && island.getWorld() != null) {
|
if (island.getOwner() != null && island.getWorld() != null) {
|
||||||
addon.getPipeliner().addIsland(island).thenAccept(results ->
|
addon.getPipeliner().zeroIsland(island).thenAccept(results ->
|
||||||
addon.getManager().setInitialIslandLevel(island, results.getLevel()));
|
addon.getManager().setInitialIslandLevel(island, results.getLevel()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -775,6 +775,11 @@ blocks:
|
|||||||
ZOMBIE_HEAD: 1
|
ZOMBIE_HEAD: 1
|
||||||
ZOMBIE_WALL_HEAD: 1
|
ZOMBIE_WALL_HEAD: 1
|
||||||
worlds:
|
worlds:
|
||||||
|
caveblock-world:
|
||||||
|
STONE: 0
|
||||||
|
GRANITE: 0
|
||||||
|
ANDESITE: 0
|
||||||
|
DIORITE: 0
|
||||||
acidisland_world:
|
acidisland_world:
|
||||||
SAND: 0
|
SAND: 0
|
||||||
SANDSTONE: 0
|
SANDSTONE: 0
|
||||||
|
@ -9,6 +9,17 @@ disabled-game-modes:
|
|||||||
# Number of concurrent island calculations
|
# 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
|
# 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
|
concurrent-island-calcs: 1
|
||||||
|
#
|
||||||
|
# Island level calculation timeout in minutes.
|
||||||
|
# If an island takes longer that this time to calculate, then the calculation will abort.
|
||||||
|
# Generally, calculation should only take a few seconds, so if this ever triggers then something is not right.
|
||||||
|
calculation-timeout: 5
|
||||||
|
#
|
||||||
|
# Zero island levels on new island or island reset
|
||||||
|
# If true, Level will calculate the starter island's level and remove it from any future level calculations.
|
||||||
|
# If this is false, the player's starter island and will count towards their level.
|
||||||
|
# This will reduce CPU if it isn't used.
|
||||||
|
zero-new-island-levels: true
|
||||||
#
|
#
|
||||||
# Calculate island level on login
|
# Calculate island level on login
|
||||||
# This silently calculates the player's island level when they login
|
# This silently calculates the player's island level when they login
|
||||||
|
@ -35,6 +35,8 @@ island:
|
|||||||
required-points-to-next-level: "&a [points] points required until the next level"
|
required-points-to-next-level: "&a [points] points required until the next level"
|
||||||
deaths: "&c([number] deaths)"
|
deaths: "&c([number] deaths)"
|
||||||
cooldown: "&c You must wait &b[time] &c seconds until you can do that again"
|
cooldown: "&c You must wait &b[time] &c seconds until you can do that again"
|
||||||
|
in-progress: "&6 Island level calculation is in progress..."
|
||||||
|
time-out: "&c The level calculation took too long. Please try again later."
|
||||||
|
|
||||||
top:
|
top:
|
||||||
description: "show the Top Ten"
|
description: "show the Top Ten"
|
||||||
|
Loading…
Reference in New Issue
Block a user