mirror of
https://github.com/BentoBoxWorld/Level.git
synced 2024-11-14 06:36:40 +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 long duration;
|
||||
private final boolean zeroIsland;
|
||||
|
||||
/**
|
||||
* Constructor to get the level for an island
|
||||
* @param addon - Level addon
|
||||
* @param island - the island to scan
|
||||
* @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.island = island;
|
||||
this.r = r;
|
||||
this.zeroIsland = zeroIsland;
|
||||
results = new Results();
|
||||
duration = System.currentTimeMillis();
|
||||
chunksToCheck = getChunksToScan(island);
|
||||
@ -536,4 +539,11 @@ public class IslandLevelCalculator {
|
||||
addon.getPipeliner().setTime(System.currentTimeMillis() - duration);
|
||||
// All done.
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the zeroIsland
|
||||
*/
|
||||
boolean isNotZeroIsland() {
|
||||
return !zeroIsland;
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
package world.bentobox.level.calculators;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
|
||||
@ -12,6 +12,7 @@ import org.bukkit.scheduler.BukkitTask;
|
||||
import world.bentobox.bentobox.BentoBox;
|
||||
import world.bentobox.bentobox.database.objects.Island;
|
||||
import world.bentobox.level.Level;
|
||||
import world.bentobox.level.calculators.Results.Result;
|
||||
|
||||
/**
|
||||
* 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 final Queue<IslandLevelCalculator> toProcessQueue;
|
||||
private final Set<IslandLevelCalculator> inProcessQueue;
|
||||
private final Map<IslandLevelCalculator, Long> inProcessQueue;
|
||||
private final BukkitTask task;
|
||||
private final Level addon;
|
||||
private long time;
|
||||
@ -34,7 +35,7 @@ public class Pipeliner {
|
||||
public Pipeliner(Level addon) {
|
||||
this.addon = addon;
|
||||
toProcessQueue = new ConcurrentLinkedQueue<>();
|
||||
inProcessQueue = new HashSet<>();
|
||||
inProcessQueue = new HashMap<>();
|
||||
// Loop continuously - check every tick if there is an island to scan
|
||||
task = Bukkit.getScheduler().runTaskTimer(BentoBox.getInstance(), () -> {
|
||||
if (!BentoBox.getInstance().isEnabled()) {
|
||||
@ -47,7 +48,7 @@ public class Pipeliner {
|
||||
IslandLevelCalculator iD = toProcessQueue.poll();
|
||||
// Ignore deleted or unonwed islands
|
||||
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
|
||||
scanChunk(iD);
|
||||
}
|
||||
@ -82,6 +83,17 @@ public class Pipeliner {
|
||||
if (!Bukkit.isPrimaryThread()) {
|
||||
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()) {
|
||||
// scanNextChunk returns true if there are more chunks to scan
|
||||
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
|
||||
*
|
||||
* @return CompletableFuture of the results. Results will be null if the island is already in the queue
|
||||
*/
|
||||
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<>();
|
||||
// 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));
|
||||
toProcessQueue.add(new IslandLevelCalculator(addon, island, r, zeroing));
|
||||
count++;
|
||||
return r;
|
||||
}
|
||||
@ -140,4 +167,6 @@ public class Pipeliner {
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
@ -10,6 +10,20 @@ import com.google.common.collect.HashMultiset;
|
||||
import com.google.common.collect.Multiset;
|
||||
|
||||
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;
|
||||
final Multiset<Material> mdCount = HashMultiset.create();
|
||||
final Multiset<Material> uwCount = HashMultiset.create();
|
||||
@ -22,7 +36,15 @@ public class Results {
|
||||
AtomicInteger deathHandicap = new AtomicInteger(0);
|
||||
AtomicLong pointsToNextLevel = 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
|
||||
*/
|
||||
@ -101,5 +123,11 @@ public class Results {
|
||||
public Multiset<Material> getUwCount() {
|
||||
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.level.Level;
|
||||
import world.bentobox.level.calculators.Results;
|
||||
import world.bentobox.level.calculators.Results.Result;
|
||||
|
||||
public class IslandLevelCommand extends CompositeCommand {
|
||||
|
||||
@ -88,6 +89,13 @@ public class IslandLevelCommand extends CompositeCommand {
|
||||
long oldLevel = addon.getManager().getIslandLevel(getWorld(), playerUUID);
|
||||
addon.getManager().calculateLevel(playerUUID, island).thenAccept(results -> {
|
||||
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);
|
||||
});
|
||||
return true;
|
||||
|
@ -25,6 +25,23 @@ public class ConfigSettings implements ConfigObject {
|
||||
@ConfigEntry(path = "concurrent-island-calcs")
|
||||
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("Calculate island level on 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)
|
||||
public void onNewIsland(IslandCreatedEvent e) {
|
||||
|
||||
if (addon.getSettings().isZeroNewIslandLevels()) {
|
||||
zeroIsland(e.getIsland());
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
|
||||
public void onNewIsland(IslandResettedEvent e) {
|
||||
|
||||
if (addon.getSettings().isZeroNewIslandLevels()) {
|
||||
zeroIsland(e.getIsland());
|
||||
}
|
||||
}
|
||||
|
||||
private void zeroIsland(final Island island) {
|
||||
// Clear the island setting
|
||||
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()));
|
||||
}
|
||||
}
|
||||
|
@ -775,6 +775,11 @@ blocks:
|
||||
ZOMBIE_HEAD: 1
|
||||
ZOMBIE_WALL_HEAD: 1
|
||||
worlds:
|
||||
caveblock-world:
|
||||
STONE: 0
|
||||
GRANITE: 0
|
||||
ANDESITE: 0
|
||||
DIORITE: 0
|
||||
acidisland_world:
|
||||
SAND: 0
|
||||
SANDSTONE: 0
|
||||
|
@ -10,6 +10,17 @@ disabled-game-modes:
|
||||
# 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
|
||||
#
|
||||
# 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
|
||||
# This silently calculates the player's island level when they login
|
||||
# This applies to all islands the player has on the server, e.g., BSkyBlock, AcidIsland
|
||||
|
@ -35,6 +35,8 @@ island:
|
||||
required-points-to-next-level: "&a [points] points required until the next level"
|
||||
deaths: "&c([number] deaths)"
|
||||
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:
|
||||
description: "show the Top Ten"
|
||||
|
Loading…
Reference in New Issue
Block a user