mirror of
https://github.com/BentoBoxWorld/Level.git
synced 2024-11-23 18:45:17 +01:00
Uses PaperLib to get chunks async (#95)
* Uses PaperLib to get chunks async. Appears to work fine on regular Spigot too. * Removes config settings not required anymore. * Future is on main thread, so do calcs async * Implements multi-threaded level calculation. * Remove debug from version
This commit is contained in:
parent
e383f79e3e
commit
d4c9bd654d
34
pom.xml
34
pom.xml
@ -65,7 +65,7 @@
|
||||
<!-- Do not change unless you want different name for local builds. -->
|
||||
<build.number>-LOCAL</build.number>
|
||||
<!-- This allows to change between versions. -->
|
||||
<build.version>1.8.0</build.version>
|
||||
<build.version>1.8.1</build.version>
|
||||
</properties>
|
||||
|
||||
<!-- Profiles will allow to automatically change build version. -->
|
||||
@ -147,6 +147,10 @@
|
||||
<id>codemc-public</id>
|
||||
<url>https://repo.codemc.org/repository/maven-public/</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>papermc</id>
|
||||
<url>https://papermc.io/repo/repository/maven-public/</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
@ -182,6 +186,12 @@
|
||||
<version>${bentobox.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.papermc</groupId>
|
||||
<artifactId>paperlib</artifactId>
|
||||
<version>1.0.2</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
@ -278,6 +288,28 @@
|
||||
<artifactId>maven-deploy-plugin</artifactId>
|
||||
<version>2.8.2</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.1.1</version>
|
||||
<configuration>
|
||||
<dependencyReducedPomLocation>${project.build.directory}/dependency-reduced-pom.xml</dependencyReducedPomLocation>
|
||||
<relocations>
|
||||
<relocation>
|
||||
<pattern>io.papermc.lib</pattern>
|
||||
<shadedPattern>world.bentobox.level.paperlib</shadedPattern> <!-- Replace this -->
|
||||
</relocation>
|
||||
</relocations>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.jacoco</groupId>
|
||||
<artifactId>jacoco-maven-plugin</artifactId>
|
||||
|
@ -8,6 +8,8 @@ import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.ChunkSnapshot;
|
||||
@ -16,13 +18,13 @@ import org.bukkit.Tag;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.block.data.type.Slab;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
|
||||
import com.google.common.collect.HashMultiset;
|
||||
import com.google.common.collect.Multiset;
|
||||
import com.google.common.collect.Multiset.Entry;
|
||||
import com.google.common.collect.Multisets;
|
||||
|
||||
import io.papermc.lib.PaperLib;
|
||||
import world.bentobox.bentobox.database.objects.Island;
|
||||
import world.bentobox.bentobox.util.Pair;
|
||||
import world.bentobox.bentobox.util.Util;
|
||||
@ -31,11 +33,9 @@ import world.bentobox.level.Level;
|
||||
|
||||
public class CalcIslandLevel {
|
||||
|
||||
private final int maxChunks;
|
||||
private final long speed;
|
||||
private static final String LINE_BREAK = "==================================";
|
||||
private boolean checking;
|
||||
private final BukkitTask task;
|
||||
|
||||
public static final long MAX_AMOUNT = 10000;
|
||||
|
||||
private final Level addon;
|
||||
|
||||
@ -49,6 +49,8 @@ public class CalcIslandLevel {
|
||||
private final World world;
|
||||
private final List<World> worlds;
|
||||
|
||||
private int count;
|
||||
|
||||
|
||||
/**
|
||||
* Calculate the island's level
|
||||
@ -78,56 +80,22 @@ public class CalcIslandLevel {
|
||||
result = new Results();
|
||||
|
||||
// Set the initial island handicap
|
||||
result.initialLevel = addon.getInitialIslandLevel(island);
|
||||
|
||||
speed = addon.getSettings().getUpdateTickDelay();
|
||||
maxChunks = addon.getSettings().getChunksPerTick();
|
||||
result.setInitialLevel(addon.getInitialIslandLevel(island));
|
||||
|
||||
// Get chunks to scan
|
||||
chunksToScan = getChunksToScan(island);
|
||||
|
||||
// Start checking
|
||||
checking = true;
|
||||
|
||||
// Start a recurring task until done or cancelled
|
||||
task = addon.getServer().getScheduler().runTaskTimer(addon.getPlugin(), () -> {
|
||||
Set<ChunkSnapshot> chunkSnapshot = new HashSet<>();
|
||||
if (checking) {
|
||||
Iterator<Pair<Integer, Integer>> it = chunksToScan.iterator();
|
||||
if (!it.hasNext()) {
|
||||
// Nothing left
|
||||
tidyUp();
|
||||
return;
|
||||
}
|
||||
// Add chunk snapshots to the list
|
||||
while (it.hasNext() && chunkSnapshot.size() < maxChunks) {
|
||||
Pair<Integer, Integer> pair = it.next();
|
||||
for (World worldToScan : worlds) {
|
||||
if (!worldToScan.isChunkLoaded(pair.x, pair.z)) {
|
||||
//worldToScan.loadChunk(pair.x, pair.z);
|
||||
chunkSnapshot.add(worldToScan.getChunkAt(pair.x, pair.z).getChunkSnapshot());
|
||||
worldToScan.unloadChunk(pair.x, pair.z, false);
|
||||
} else {
|
||||
chunkSnapshot.add(worldToScan.getChunkAt(pair.x, pair.z).getChunkSnapshot());
|
||||
}
|
||||
count = 0;
|
||||
chunksToScan.forEach(c -> {
|
||||
PaperLib.getChunkAtAsync(world, c.x, c.z).thenAccept(ch -> {
|
||||
ChunkSnapshot snapShot = ch.getChunkSnapshot();
|
||||
Bukkit.getScheduler().runTaskAsynchronously(addon.getPlugin(), () -> {
|
||||
this.scanChunk(snapShot);
|
||||
count++;
|
||||
if (count == chunksToScan.size()) {
|
||||
this.tidyUp();
|
||||
}
|
||||
it.remove();
|
||||
}
|
||||
// Move to next step
|
||||
checking = false;
|
||||
checkChunksAsync(chunkSnapshot);
|
||||
}
|
||||
}, 0L, speed);
|
||||
}
|
||||
|
||||
private void checkChunksAsync(final Set<ChunkSnapshot> chunkSnapshot) {
|
||||
// Run async task to scan chunks
|
||||
addon.getServer().getScheduler().runTaskAsynchronously(addon.getPlugin(), () -> {
|
||||
for (ChunkSnapshot chunk: chunkSnapshot) {
|
||||
scanChunk(chunk);
|
||||
}
|
||||
// Nothing happened, change state
|
||||
checking = true;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
@ -168,10 +136,10 @@ public class CalcIslandLevel {
|
||||
private void checkBlock(BlockData bd, boolean belowSeaLevel) {
|
||||
int count = limitCount(bd.getMaterial());
|
||||
if (belowSeaLevel) {
|
||||
result.underWaterBlockCount += count;
|
||||
result.underWaterBlockCount.addAndGet(count);
|
||||
result.uwCount.add(bd.getMaterial());
|
||||
} else {
|
||||
result.rawBlockCount += count;
|
||||
result.rawBlockCount.addAndGet(count);
|
||||
result.mdCount.add(bd.getMaterial());
|
||||
}
|
||||
}
|
||||
@ -231,40 +199,40 @@ public class CalcIslandLevel {
|
||||
}
|
||||
|
||||
private void tidyUp() {
|
||||
// Cancel
|
||||
task.cancel();
|
||||
// Finalize calculations
|
||||
result.rawBlockCount += (long)(result.underWaterBlockCount * addon.getSettings().getUnderWaterMultiplier());
|
||||
result.rawBlockCount.addAndGet((long)(result.underWaterBlockCount.get() * addon.getSettings().getUnderWaterMultiplier()));
|
||||
|
||||
// Set the death penalty
|
||||
if (this.addon.getSettings().isSumTeamDeaths())
|
||||
{
|
||||
for (UUID uuid : this.island.getMemberSet())
|
||||
{
|
||||
this.result.deathHandicap += this.addon.getPlayers().getDeaths(this.world, uuid);
|
||||
this.result.deathHandicap.addAndGet(this.addon.getPlayers().getDeaths(this.world, uuid));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// At this point, it may be that the island has become unowned.
|
||||
this.result.deathHandicap = this.island.getOwner() == null ? 0 :
|
||||
this.addon.getPlayers().getDeaths(this.world, this.island.getOwner());
|
||||
this.result.deathHandicap.set(this.island.getOwner() == null ? 0 :
|
||||
this.addon.getPlayers().getDeaths(this.world, this.island.getOwner()));
|
||||
}
|
||||
|
||||
long blockAndDeathPoints = this.result.rawBlockCount;
|
||||
long blockAndDeathPoints = this.result.rawBlockCount.get();
|
||||
|
||||
if (this.addon.getSettings().getDeathPenalty() > 0)
|
||||
{
|
||||
// Proper death penalty calculation.
|
||||
blockAndDeathPoints -= this.result.deathHandicap * this.addon.getSettings().getDeathPenalty();
|
||||
blockAndDeathPoints -= this.result.deathHandicap.get() * this.addon.getSettings().getDeathPenalty();
|
||||
}
|
||||
|
||||
this.result.level = blockAndDeathPoints / this.addon.getSettings().getLevelCost() - this.island.getLevelHandicap() - result.initialLevel;
|
||||
|
||||
this.result.level.set(calculateLevel(blockAndDeathPoints));
|
||||
|
||||
// Calculate how many points are required to get to the next level
|
||||
this.result.pointsToNextLevel = this.addon.getSettings().getLevelCost() -
|
||||
(blockAndDeathPoints % this.addon.getSettings().getLevelCost());
|
||||
long nextLevel = this.result.level.get();
|
||||
long blocks = blockAndDeathPoints;
|
||||
while (nextLevel < this.result.level.get() + 1 && blocks - blockAndDeathPoints < MAX_AMOUNT) {
|
||||
nextLevel = calculateLevel(++blocks);
|
||||
}
|
||||
this.result.pointsToNextLevel.set(blocks - blockAndDeathPoints);
|
||||
|
||||
// Report
|
||||
result.report = getReport();
|
||||
@ -275,16 +243,23 @@ public class CalcIslandLevel {
|
||||
}
|
||||
|
||||
|
||||
private long calculateLevel(long blockAndDeathPoints) {
|
||||
String calcString = addon.getSettings().getLevelCalc();
|
||||
String withValues = calcString.replace("blocks", String.valueOf(blockAndDeathPoints)).replace("level_cost", String.valueOf(this.addon.getSettings().getLevelCost()));
|
||||
return (long)eval(withValues) - this.island.getLevelHandicap() - result.initialLevel.get();
|
||||
}
|
||||
|
||||
private List<String> getReport() {
|
||||
List<String> reportLines = new ArrayList<>();
|
||||
// provide counts
|
||||
reportLines.add("Level Log for island in " + addon.getPlugin().getIWM().getFriendlyName(island.getWorld()) + " at " + Util.xyz(island.getCenter().toVector()));
|
||||
reportLines.add("Island owner UUID = " + island.getOwner());
|
||||
reportLines.add("Total block value count = " + String.format("%,d",result.rawBlockCount));
|
||||
reportLines.add("Total block value count = " + String.format("%,d",result.rawBlockCount.get()));
|
||||
reportLines.add("Formula to calculate island level: " + addon.getSettings().getLevelCalc());
|
||||
reportLines.add("Level cost = " + addon.getSettings().getLevelCost());
|
||||
reportLines.add("Deaths handicap = " + result.deathHandicap);
|
||||
reportLines.add("Initial island level = " + (0L - result.initialLevel));
|
||||
reportLines.add("Level calculated = " + result.level);
|
||||
reportLines.add("Deaths handicap = " + result.deathHandicap.get());
|
||||
reportLines.add("Initial island level = " + (0L - result.initialLevel.get()));
|
||||
reportLines.add("Level calculated = " + result.level.get());
|
||||
reportLines.add(LINE_BREAK);
|
||||
int total = 0;
|
||||
if (!result.uwCount.isEmpty()) {
|
||||
@ -359,18 +334,19 @@ public class CalcIslandLevel {
|
||||
private final Multiset<Material> uwCount = HashMultiset.create();
|
||||
private final Multiset<Material> ncCount = HashMultiset.create();
|
||||
private final Multiset<Material> ofCount = HashMultiset.create();
|
||||
private long rawBlockCount = 0;
|
||||
private long underWaterBlockCount = 0;
|
||||
private long level = 0;
|
||||
private int deathHandicap = 0;
|
||||
private long pointsToNextLevel = 0;
|
||||
private long initialLevel = 0;
|
||||
// AtomicLong and AtomicInteger must be used because they are changed by multiple concurrent threads
|
||||
private AtomicLong rawBlockCount = new AtomicLong(0);
|
||||
private AtomicLong underWaterBlockCount = new AtomicLong(0);
|
||||
private AtomicLong level = new AtomicLong(0);
|
||||
private AtomicInteger deathHandicap = new AtomicInteger(0);
|
||||
private AtomicLong pointsToNextLevel = new AtomicLong(0);
|
||||
private AtomicLong initialLevel = new AtomicLong(0);
|
||||
|
||||
/**
|
||||
* @return the deathHandicap
|
||||
*/
|
||||
public int getDeathHandicap() {
|
||||
return deathHandicap;
|
||||
return deathHandicap.get();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -384,27 +360,27 @@ public class CalcIslandLevel {
|
||||
* @param level - level
|
||||
*/
|
||||
public void setLevel(int level) {
|
||||
this.level = level;
|
||||
this.level.set(level);
|
||||
}
|
||||
/**
|
||||
* @return the level
|
||||
*/
|
||||
public long getLevel() {
|
||||
return level;
|
||||
return level.get();
|
||||
}
|
||||
/**
|
||||
* @return the pointsToNextLevel
|
||||
*/
|
||||
public long getPointsToNextLevel() {
|
||||
return pointsToNextLevel;
|
||||
return pointsToNextLevel.get();
|
||||
}
|
||||
|
||||
public long getInitialLevel() {
|
||||
return initialLevel;
|
||||
return initialLevel.get();
|
||||
}
|
||||
|
||||
public void setInitialLevel(long initialLevel) {
|
||||
this.initialLevel = initialLevel;
|
||||
this.initialLevel.set(initialLevel);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
@ -419,4 +395,84 @@ public class CalcIslandLevel {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static double eval(final String str) {
|
||||
return new Object() {
|
||||
int pos = -1, ch;
|
||||
|
||||
void nextChar() {
|
||||
ch = (++pos < str.length()) ? str.charAt(pos) : -1;
|
||||
}
|
||||
|
||||
boolean eat(int charToEat) {
|
||||
while (ch == ' ') nextChar();
|
||||
if (ch == charToEat) {
|
||||
nextChar();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
double parse() {
|
||||
nextChar();
|
||||
double x = parseExpression();
|
||||
if (pos < str.length()) throw new RuntimeException("Unexpected: " + (char)ch);
|
||||
return x;
|
||||
}
|
||||
|
||||
// Grammar:
|
||||
// expression = term | expression `+` term | expression `-` term
|
||||
// term = factor | term `*` factor | term `/` factor
|
||||
// factor = `+` factor | `-` factor | `(` expression `)`
|
||||
// | number | functionName factor | factor `^` factor
|
||||
|
||||
double parseExpression() {
|
||||
double x = parseTerm();
|
||||
for (;;) {
|
||||
if (eat('+')) x += parseTerm(); // addition
|
||||
else if (eat('-')) x -= parseTerm(); // subtraction
|
||||
else return x;
|
||||
}
|
||||
}
|
||||
|
||||
double parseTerm() {
|
||||
double x = parseFactor();
|
||||
for (;;) {
|
||||
if (eat('*')) x *= parseFactor(); // multiplication
|
||||
else if (eat('/')) x /= parseFactor(); // division
|
||||
else return x;
|
||||
}
|
||||
}
|
||||
|
||||
double parseFactor() {
|
||||
if (eat('+')) return parseFactor(); // unary plus
|
||||
if (eat('-')) return -parseFactor(); // unary minus
|
||||
|
||||
double x;
|
||||
int startPos = this.pos;
|
||||
if (eat('(')) { // parentheses
|
||||
x = parseExpression();
|
||||
eat(')');
|
||||
} else if ((ch >= '0' && ch <= '9') || ch == '.') { // numbers
|
||||
while ((ch >= '0' && ch <= '9') || ch == '.') nextChar();
|
||||
x = Double.parseDouble(str.substring(startPos, this.pos));
|
||||
} else if (ch >= 'a' && ch <= 'z') { // functions
|
||||
while (ch >= 'a' && ch <= 'z') nextChar();
|
||||
String func = str.substring(startPos, this.pos);
|
||||
x = parseFactor();
|
||||
if (func.equals("sqrt")) x = Math.sqrt(x);
|
||||
else if (func.equals("sin")) x = Math.sin(Math.toRadians(x));
|
||||
else if (func.equals("cos")) x = Math.cos(Math.toRadians(x));
|
||||
else if (func.equals("tan")) x = Math.tan(Math.toRadians(x));
|
||||
else throw new RuntimeException("Unknown function: " + func);
|
||||
} else {
|
||||
throw new RuntimeException("Unexpected: " + (char)ch);
|
||||
}
|
||||
|
||||
if (eat('^')) x = Math.pow(x, parseFactor()); // exponentiation
|
||||
|
||||
return x;
|
||||
}
|
||||
}.parse();
|
||||
}
|
||||
}
|
||||
|
@ -90,7 +90,7 @@ public class PlayerLevel {
|
||||
asker.sendMessage("island.level.deaths", "[number]", String.valueOf(results.getDeathHandicap()));
|
||||
}
|
||||
// Send player how many points are required to reach next island level
|
||||
if (results.getPointsToNextLevel() >= 0) {
|
||||
if (results.getPointsToNextLevel() >= 0 && results.getPointsToNextLevel() < CalcIslandLevel.MAX_AMOUNT) {
|
||||
asker.sendMessage("island.level.required-points-to-next-level", "[points]", String.valueOf(results.getPointsToNextLevel()));
|
||||
}
|
||||
// Tell other team members
|
||||
|
@ -23,16 +23,6 @@ public class Settings {
|
||||
private long levelCost;
|
||||
private int levelWait;
|
||||
|
||||
/**
|
||||
* Stores number of chunks that can be updated in single tick.
|
||||
*/
|
||||
private int chunksPerTick;
|
||||
|
||||
/**
|
||||
* Stores number of tick delay between each chunk loading.
|
||||
*/
|
||||
private long updateTickDelay;
|
||||
|
||||
private List<String> gameModes;
|
||||
|
||||
|
||||
@ -43,22 +33,6 @@ public class Settings {
|
||||
// GameModes
|
||||
gameModes = level.getConfig().getStringList("game-modes");
|
||||
|
||||
// Level calculation chunk load speed
|
||||
this.setUpdateTickDelay(level.getConfig().getLong("updatetickdelay", 1));
|
||||
|
||||
if (this.getUpdateTickDelay() <= 0)
|
||||
{
|
||||
this.setUpdateTickDelay(1);
|
||||
}
|
||||
|
||||
// Level calculation chunk count per update
|
||||
this.setChunksPerTick(level.getConfig().getInt("chunkspertick", 200));
|
||||
|
||||
if (this.getChunksPerTick() <= 0)
|
||||
{
|
||||
this.setChunksPerTick(200);
|
||||
}
|
||||
|
||||
setLevelWait(level.getConfig().getInt("levelwait", 60));
|
||||
if (getLevelWait() < 0) {
|
||||
setLevelWait(0);
|
||||
@ -246,45 +220,11 @@ public class Settings {
|
||||
return level.getConfig().getBoolean("shorthand");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This method returns the number of chunks that can be processed at single tick.
|
||||
* @return the value of chunksPerTick.
|
||||
* @return the formula to calculate island levels
|
||||
*/
|
||||
public int getChunksPerTick()
|
||||
{
|
||||
return this.chunksPerTick;
|
||||
public String getLevelCalc() {
|
||||
return level.getConfig().getString("level-calc", "blocks / level_cost");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This method sets the chunksPerTick value.
|
||||
* @param chunksPerTick the chunksPerTick new value.
|
||||
*
|
||||
*/
|
||||
public void setChunksPerTick(int chunksPerTick)
|
||||
{
|
||||
this.chunksPerTick = chunksPerTick;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This method returns the delay between each update call.
|
||||
* @return the value of updateTickDelay.
|
||||
*/
|
||||
public long getUpdateTickDelay()
|
||||
{
|
||||
return this.updateTickDelay;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This method sets the updateTickDelay value.
|
||||
* @param updateTickDelay the updateTickDelay new value.
|
||||
*
|
||||
*/
|
||||
public void setUpdateTickDelay(long updateTickDelay)
|
||||
{
|
||||
this.updateTickDelay = updateTickDelay;
|
||||
}
|
||||
}
|
||||
|
@ -37,17 +37,17 @@ underwater: 1.0
|
||||
# Value of one island level. Default 100. Minimum value is 1.
|
||||
levelcost: 100
|
||||
|
||||
# Island level calculation formula
|
||||
# blocks - the sum total of all block values, less any death penalty
|
||||
# level_cost - in a linear equation, the value of one level
|
||||
# This formula can include +,=,*,/,sqrt,^,sin,cos,tan. Result will always be rounded to a long integer
|
||||
# for example, an alternative non-linear option could be: 3 * sqrt(blocks / level_cost)
|
||||
level-calc: blocks / level_cost
|
||||
|
||||
|
||||
# Cooldown between level requests in seconds
|
||||
levelwait: 60
|
||||
|
||||
# Delay between each task that loads chunks for calculating island level.
|
||||
# Increasing this will increase time to calculate island level.
|
||||
updatetickdelay: 1
|
||||
|
||||
# Number of chunks that will be processed at the same tick.
|
||||
# Decreasing this will increase time to calculate island level.
|
||||
chunkspertick: 200
|
||||
|
||||
# Death penalty
|
||||
# How many block values a player will lose per death.
|
||||
# Default value of 100 means that for every death, the player will lose 1 level (if levelcost is 100)
|
||||
|
Loading…
Reference in New Issue
Block a user