Skip to content

Commit b534eb7

Browse files
tastybentoHSGamerRubenicos
authored
Version 2.8.1 (#244)
* Version 2.8.1 * Speeds up level calculation by doing more chunk scans async. If chests are scanned, then it will take longer because these have to be done sync. #243 * add Vietnamese (#240) * Raw island level placeholder (#241) Co-authored-by: Huynh Tien <huynhqtienvtag@gmail.com> Co-authored-by: Rubén <44579213+Rubenicos@users.noreply.github.com>
1 parent 750f07b commit b534eb7

File tree

5 files changed

+221
-76
lines changed

5 files changed

+221
-76
lines changed

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@
6565
<!-- Do not change unless you want different name for local builds. -->
6666
<build.number>-LOCAL</build.number>
6767
<!-- This allows to change between versions. -->
68-
<build.version>2.8.0</build.version>
68+
<build.version>2.8.1</build.version>
6969
<sonar.projectKey>BentoBoxWorld_Level</sonar.projectKey>
7070
<sonar.organization>bentobox-world</sonar.organization>
7171
<sonar.host.url>https://sonarcloud.io</sonar.host.url>

src/main/java/world/bentobox/level/Level.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,9 @@ private void registerPlaceholders(GameModeAddon gm) {
197197
getPlugin().getPlaceholdersManager().registerPlaceholder(this,
198198
gm.getDescription().getName().toLowerCase() + "_island_level",
199199
user -> getManager().getIslandLevelString(gm.getOverWorld(), user.getUniqueId()));
200+
getPlugin().getPlaceholdersManager().registerPlaceholder(this,
201+
gm.getDescription().getName().toLowerCase() + "_island_level_raw",
202+
user -> String.valueOf(getManager().getIslandLevel(gm.getOverWorld(), user.getUniqueId())));
200203
getPlugin().getPlaceholdersManager().registerPlaceholder(this,
201204
gm.getDescription().getName().toLowerCase() + "_points_to_next_level",
202205
user -> getManager().getPointsToNextString(gm.getOverWorld(), user.getUniqueId()));

src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java

Lines changed: 144 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,25 @@
11
package world.bentobox.level.calculators;
22

33
import java.util.ArrayList;
4+
import java.util.Arrays;
45
import java.util.Collection;
56
import java.util.Collections;
67
import java.util.EnumMap;
78
import java.util.HashMap;
9+
import java.util.HashSet;
810
import java.util.Iterator;
911
import java.util.List;
1012
import java.util.Map;
1113
import java.util.Queue;
14+
import java.util.Set;
1215
import java.util.UUID;
1316
import java.util.concurrent.CompletableFuture;
1417
import java.util.concurrent.ConcurrentLinkedQueue;
1518

1619
import org.bukkit.Bukkit;
1720
import org.bukkit.Chunk;
1821
import org.bukkit.ChunkSnapshot;
22+
import org.bukkit.Location;
1923
import org.bukkit.Material;
2024
import org.bukkit.Tag;
2125
import org.bukkit.World;
@@ -26,8 +30,7 @@
2630
import org.bukkit.block.data.BlockData;
2731
import org.bukkit.block.data.type.Slab;
2832
import org.bukkit.inventory.ItemStack;
29-
import org.bukkit.util.Vector;
30-
import org.eclipse.jdt.annotation.Nullable;
33+
import org.bukkit.scheduler.BukkitTask;
3134

3235
import com.bgsoftware.wildstacker.api.WildStackerAPI;
3336
import com.bgsoftware.wildstacker.api.objects.StackedBarrel;
@@ -44,10 +47,19 @@
4447
import world.bentobox.bentobox.util.Pair;
4548
import world.bentobox.bentobox.util.Util;
4649
import world.bentobox.level.Level;
50+
import world.bentobox.level.calculators.Results.Result;
4751

4852
public class IslandLevelCalculator {
4953
private static final String LINE_BREAK = "==================================";
5054
public static final long MAX_AMOUNT = 10000;
55+
private static final List<Material> CHESTS = Arrays.asList(Material.CHEST, Material.CHEST_MINECART, Material.TRAPPED_CHEST,
56+
Material.SHULKER_BOX, Material.BLACK_SHULKER_BOX, Material.BLUE_SHULKER_BOX, Material.BROWN_SHULKER_BOX,
57+
Material.CYAN_SHULKER_BOX, Material.GRAY_SHULKER_BOX, Material.GREEN_SHULKER_BOX, Material.LIGHT_BLUE_SHULKER_BOX,
58+
Material.LIGHT_GRAY_SHULKER_BOX, Material.LIME_SHULKER_BOX, Material.MAGENTA_SHULKER_BOX, Material.ORANGE_SHULKER_BOX,
59+
Material.PINK_SHULKER_BOX, Material.PURPLE_SHULKER_BOX, Material.RED_SHULKER_BOX, Material.RED_SHULKER_BOX,
60+
Material.WHITE_SHULKER_BOX, Material.YELLOW_SHULKER_BOX, Material.COMPOSTER, Material.BARREL, Material.DISPENSER,
61+
Material.DROPPER, Material.SMOKER, Material.BLAST_FURNACE);
62+
private static final int CHUNKS_TO_SCAN = 100;
5163

5264
/**
5365
* Method to evaluate a mathematical equation
@@ -156,6 +168,10 @@ void nextChar() {
156168
private final boolean zeroIsland;
157169
private final Map<Environment, World> worlds = new EnumMap<>(Environment.class);
158170
private final int seaHeight;
171+
private final List<Location> stackedBlocks = new ArrayList<>();
172+
private final Set<Chunk> chestBlocks = new HashSet<>();
173+
private BukkitTask finishTask;
174+
159175

160176
/**
161177
* Constructor to get the level for an island
@@ -339,17 +355,35 @@ private int getValue(Material md) {
339355
* @param z - chunk z coordinate
340356
* @return a future chunk or future null if there is no chunk to load, e.g., there is no island nether
341357
*/
342-
private CompletableFuture<Chunk> getWorldChunk(Environment env, int x, int z) {
358+
private CompletableFuture<List<Chunk>> getWorldChunk(Environment env, Queue<Pair<Integer, Integer>> pairList) {
343359
if (worlds.containsKey(env)) {
344-
CompletableFuture<Chunk> r2 = new CompletableFuture<>();
360+
CompletableFuture<List<Chunk>> r2 = new CompletableFuture<>();
361+
List<Chunk> chunkList = new ArrayList<>();
362+
World world = worlds.get(env);
345363
// Get the chunk, and then coincidentally check the RoseStacker
346-
Util.getChunkAtAsync(worlds.get(env), x, z, true).thenAccept(chunk -> roseStackerCheck(r2, chunk));
364+
loadChunks(r2, world, pairList, chunkList);
347365
return r2;
348366
}
349-
return CompletableFuture.completedFuture(null);
367+
return CompletableFuture.completedFuture(Collections.emptyList());
350368
}
351369

352-
private void roseStackerCheck(CompletableFuture<Chunk> r2, Chunk chunk) {
370+
private void loadChunks(CompletableFuture<List<Chunk>> r2, World world, Queue<Pair<Integer, Integer>> pairList,
371+
List<Chunk> chunkList) {
372+
if (pairList.isEmpty()) {
373+
r2.complete(chunkList);
374+
return;
375+
}
376+
Pair<Integer, Integer> p = pairList.poll();
377+
Util.getChunkAtAsync(world, p.x, p.z, world.getEnvironment().equals(Environment.NETHER)).thenAccept(chunk -> {
378+
if (chunk != null) {
379+
chunkList.add(chunk);
380+
roseStackerCheck(chunk);
381+
}
382+
loadChunks(r2, world, pairList, chunkList); // Iteration
383+
});
384+
}
385+
386+
private void roseStackerCheck(Chunk chunk) {
353387
if (addon.isRoseStackersEnabled()) {
354388
RoseStackerAPI.getInstance().getStackedBlocks(Collections.singletonList(chunk)).forEach(e -> {
355389
// Blocks below sea level can be scored differently
@@ -360,7 +394,6 @@ private void roseStackerCheck(CompletableFuture<Chunk> r2, Chunk chunk) {
360394
}
361395
});
362396
}
363-
r2.complete(chunk);
364397
}
365398

366399
/**
@@ -385,11 +418,10 @@ private int limitCount(Material md) {
385418

386419
/**
387420
* Count the blocks on the island
388-
* @param result - the CompletableFuture that should be completed when this scan is done
389-
* @param chunkSnapshot - the chunk to scan
421+
* @param chunk chunk to scan
390422
*/
391-
private void scanAsync(CompletableFuture<Boolean> result, ChunkSnapshot chunkSnapshot, Chunk chunk) {
392-
List<Vector> stackedBlocks = new ArrayList<>();
423+
private void scanAsync(Chunk chunk) {
424+
ChunkSnapshot chunkSnapshot = chunk.getChunkSnapshot();
393425
for (int x = 0; x< 16; x++) {
394426
// Check if the block coordinate is inside the protection zone and if not, don't count it
395427
if (chunkSnapshot.getX() * 16 + x < island.getMinProtectedX() || chunkSnapshot.getX() * 16 + x >= island.getMinProtectedX() + island.getProtectionRange() * 2) {
@@ -413,29 +445,17 @@ private void scanAsync(CompletableFuture<Boolean> result, ChunkSnapshot chunkSna
413445
}
414446
// Hook for Wild Stackers (Blocks Only) - this has to use the real chunk
415447
if (addon.isStackersEnabled() && blockData.getMaterial() == Material.CAULDRON) {
416-
stackedBlocks.add(new Vector(x,y,z));
448+
stackedBlocks.add(new Location(chunk.getWorld(), x + chunkSnapshot.getX() * 16,y,z + chunkSnapshot.getZ() * 16));
449+
}
450+
// Scan chests
451+
if (addon.getSettings().isIncludeChests() && CHESTS.contains(blockData.getMaterial())) {
452+
chestBlocks.add(chunk);
417453
}
418454
// Add the value of the block's material
419455
checkBlock(blockData.getMaterial(), belowSeaLevel);
420456
}
421457
}
422458
}
423-
// Complete the future - this must go back onto the primary thread to exit async otherwise subsequent actions will be async
424-
Bukkit.getScheduler().runTask(addon.getPlugin(),() -> {
425-
// Deal with any stacked blocks
426-
stackedBlocks.forEach(v -> {
427-
Block cauldronBlock = chunk.getBlock(v.getBlockX(), v.getBlockY(), v.getBlockZ());
428-
boolean belowSeaLevel = seaHeight > 0 && v.getBlockY() <= seaHeight;
429-
if (WildStackerAPI.getWildStacker().getSystemManager().isStackedBarrel(cauldronBlock)) {
430-
StackedBarrel barrel = WildStackerAPI.getStackedBarrel(cauldronBlock);
431-
int barrelAmt = WildStackerAPI.getBarrelAmount(cauldronBlock);
432-
for (int _x = 0; _x < barrelAmt; _x++) {
433-
checkBlock(barrel.getType(), belowSeaLevel);
434-
}
435-
}
436-
});
437-
result.complete(true);
438-
});
439459
}
440460

441461
/**
@@ -473,20 +493,21 @@ private void countItemStack(ItemStack i) {
473493

474494
/**
475495
* Scan the chunk chests and count the blocks
476-
* @param chunk - the chunk to scan
496+
* @param chunks - the chunk to scan
477497
* @return future that completes when the scan is done and supplies a boolean that will be true if the scan was successful, false if not
478498
*/
479-
private CompletableFuture<Boolean> scanChunk(@Nullable Chunk chunk) {
499+
private CompletableFuture<Boolean> scanChunk(List<Chunk> chunks) {
480500
// If the chunk hasn't been generated, return
481-
if (chunk == null) return CompletableFuture.completedFuture(false);
482-
// Scan chests
483-
if (addon.getSettings().isIncludeChests()) {
484-
scanChests(chunk);
501+
if (chunks == null || chunks.isEmpty()) {
502+
return CompletableFuture.completedFuture(false);
485503
}
486504
// Count blocks in chunk
487505
CompletableFuture<Boolean> result = new CompletableFuture<>();
488-
ChunkSnapshot snapshot = chunk.getChunkSnapshot();
489-
Bukkit.getScheduler().runTaskAsynchronously(BentoBox.getInstance(), () -> scanAsync(result, snapshot, chunk));
506+
507+
Bukkit.getScheduler().runTaskAsynchronously(BentoBox.getInstance(), () -> {
508+
chunks.forEach(chunk -> scanAsync(chunk));
509+
Bukkit.getScheduler().runTask(addon.getPlugin(),() -> result.complete(true));
510+
});
490511
return result;
491512
}
492513

@@ -501,16 +522,23 @@ public CompletableFuture<Boolean> scanNextChunk() {
501522
return CompletableFuture.completedFuture(false);
502523
}
503524
// Retrieve and remove from the queue
504-
Pair<Integer, Integer> p = chunksToCheck.poll();
525+
Queue<Pair<Integer, Integer>> pairList = new ConcurrentLinkedQueue<>();
526+
int i = 0;
527+
while (!chunksToCheck.isEmpty() && i++ < CHUNKS_TO_SCAN) {
528+
pairList.add(chunksToCheck.poll());
529+
}
530+
Queue<Pair<Integer, Integer>> endPairList = new ConcurrentLinkedQueue<>(pairList);
531+
Queue<Pair<Integer, Integer>> netherPairList = new ConcurrentLinkedQueue<>(pairList);
505532
// Set up the result
506533
CompletableFuture<Boolean> result = new CompletableFuture<>();
507534
// Get chunks and scan
508-
getWorldChunk(Environment.THE_END, p.x, p.z).thenAccept(endChunk ->
509-
scanChunk(endChunk).thenAccept(b ->
510-
getWorldChunk(Environment.NETHER, p.x, p.z).thenAccept(netherChunk ->
511-
scanChunk(netherChunk).thenAccept(b2 ->
512-
getWorldChunk(Environment.NORMAL, p.x, p.z).thenAccept(normalChunk ->
513-
scanChunk(normalChunk).thenAccept(b3 ->
535+
// Get chunks and scan
536+
getWorldChunk(Environment.THE_END, endPairList).thenAccept(endChunks ->
537+
scanChunk(endChunks).thenAccept(b ->
538+
getWorldChunk(Environment.NETHER, netherPairList).thenAccept(netherChunks ->
539+
scanChunk(netherChunks).thenAccept(b2 ->
540+
getWorldChunk(Environment.NORMAL, pairList).thenAccept(normalChunks ->
541+
scanChunk(normalChunks).thenAccept(b3 ->
514542
// Complete the result now that all chunks have been scanned
515543
result.complete(!chunksToCheck.isEmpty()))))
516544
)
@@ -591,4 +619,76 @@ public void tidyUp() {
591619
boolean isNotZeroIsland() {
592620
return !zeroIsland;
593621
}
622+
623+
public void scanIsland(Pipeliner pipeliner) {
624+
// Scan the next chunk
625+
scanNextChunk().thenAccept(r -> {
626+
if (!Bukkit.isPrimaryThread()) {
627+
addon.getPlugin().logError("scanChunk not on Primary Thread!");
628+
}
629+
// Timeout check
630+
if (System.currentTimeMillis() - pipeliner.getInProcessQueue().get(this) > addon.getSettings().getCalculationTimeout() * 60000) {
631+
// Done
632+
pipeliner.getInProcessQueue().remove(this);
633+
getR().complete(new Results(Result.TIMEOUT));
634+
addon.logError("Level calculation timed out after " + addon.getSettings().getCalculationTimeout() + "m for island: " + getIsland());
635+
if (!isNotZeroIsland()) {
636+
addon.logError("Island level was being zeroed.");
637+
}
638+
return;
639+
}
640+
if (Boolean.TRUE.equals(r) && !pipeliner.getTask().isCancelled()) {
641+
// scanNextChunk returns true if there are more chunks to scan
642+
scanIsland(pipeliner);
643+
} else {
644+
// Done
645+
pipeliner.getInProcessQueue().remove(this);
646+
// Chunk finished
647+
// This was the last chunk
648+
handleStackedBlocks();
649+
handleChests();
650+
long checkTime = System.currentTimeMillis();
651+
finishTask = Bukkit.getScheduler().runTaskTimer(addon.getPlugin(), () -> {
652+
// Check every half second if all the chests and stacks have been cleared
653+
if ((chestBlocks.isEmpty() && stackedBlocks.isEmpty()) || System.currentTimeMillis() - checkTime > MAX_AMOUNT) {
654+
this.tidyUp();
655+
this.getR().complete(getResults());
656+
finishTask.cancel();
657+
}
658+
}, 0, 10L);
659+
660+
}
661+
});
662+
}
663+
664+
private void handleChests() {
665+
Iterator<Chunk> it = chestBlocks.iterator();
666+
while(it.hasNext()) {
667+
Chunk v = it.next();
668+
Util.getChunkAtAsync(v.getWorld(), v.getX(), v.getZ()).thenAccept(c -> {
669+
scanChests(c);
670+
it.remove();
671+
});
672+
}
673+
}
674+
675+
private void handleStackedBlocks() {
676+
// Deal with any stacked blocks
677+
Iterator<Location> it = stackedBlocks.iterator();
678+
while (it.hasNext()) {
679+
Location v = it.next();
680+
Util.getChunkAtAsync(v).thenAccept(c -> {
681+
Block cauldronBlock = v.getBlock();
682+
boolean belowSeaLevel = seaHeight > 0 && v.getBlockY() <= seaHeight;
683+
if (WildStackerAPI.getWildStacker().getSystemManager().isStackedBarrel(cauldronBlock)) {
684+
StackedBarrel barrel = WildStackerAPI.getStackedBarrel(cauldronBlock);
685+
int barrelAmt = WildStackerAPI.getBarrelAmount(cauldronBlock);
686+
for (int _x = 0; _x < barrelAmt; _x++) {
687+
checkBlock(barrel.getType(), belowSeaLevel);
688+
}
689+
}
690+
it.remove();
691+
});
692+
}
693+
}
594694
}

src/main/java/world/bentobox/level/calculators/Pipeliner.java

Lines changed: 17 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ public Pipeliner(Level addon) {
5050
if (!iD.getIsland().isDeleted() && !iD.getIsland().isUnowned()) {
5151
inProcessQueue.put(iD, System.currentTimeMillis());
5252
// Start the scanning of a island with the first chunk
53-
scanChunk(iD);
53+
scanIsland(iD);
5454
}
5555
}
5656
}, 1L, 10L);
@@ -71,42 +71,14 @@ public int getIslandsInQueue() {
7171
* Scans one chunk of an island and adds the results to a results object
7272
* @param iD
7373
*/
74-
private void scanChunk(IslandLevelCalculator iD) {
74+
private void scanIsland(IslandLevelCalculator iD) {
7575
if (iD.getIsland().isDeleted() || iD.getIsland().isUnowned() || task.isCancelled()) {
7676
// Island is deleted, so finish early with nothing
7777
inProcessQueue.remove(iD);
7878
iD.getR().complete(null);
7979
return;
8080
}
81-
// Scan the next chunk
82-
iD.scanNextChunk().thenAccept(r -> {
83-
if (!Bukkit.isPrimaryThread()) {
84-
addon.getPlugin().logError("scanChunk not on Primary Thread!");
85-
}
86-
// Timeout check
87-
if (System.currentTimeMillis() - inProcessQueue.get(iD) > addon.getSettings().getCalculationTimeout() * 60000) {
88-
// Done
89-
inProcessQueue.remove(iD);
90-
iD.getR().complete(new Results(Result.TIMEOUT));
91-
addon.logError("Level calculation timed out after " + addon.getSettings().getCalculationTimeout() + "m for island: " + iD.getIsland());
92-
if (!iD.isNotZeroIsland()) {
93-
addon.logError("Island level was being zeroed.");
94-
}
95-
return;
96-
}
97-
if (Boolean.TRUE.equals(r) || task.isCancelled()) {
98-
// scanNextChunk returns true if there are more chunks to scan
99-
scanChunk(iD);
100-
} else {
101-
// Done
102-
inProcessQueue.remove(iD);
103-
// Chunk finished
104-
// This was the last chunk
105-
iD.tidyUp();
106-
iD.getR().complete(iD.getResults());
107-
}
108-
});
109-
81+
iD.scanIsland(this);
11082
}
11183

11284

@@ -169,6 +141,20 @@ public void stop() {
169141
this.toProcessQueue.clear();
170142
}
171143

144+
/**
145+
* @return the inProcessQueue
146+
*/
147+
protected Map<IslandLevelCalculator, Long> getInProcessQueue() {
148+
return inProcessQueue;
149+
}
150+
151+
/**
152+
* @return the task
153+
*/
154+
protected BukkitTask getTask() {
155+
return task;
156+
}
157+
172158

173159

174160

0 commit comments

Comments
 (0)