Uses multi-threaded island scanning.

Optimized for Paper. Requires BentoBox 1.9.0 or later.

Makes admin calc command alias recount.

https://github.com/BentoBoxWorld/Limits/issues/52
This commit is contained in:
tastybento 2019-11-16 16:23:37 -08:00
parent d97fa9b9ae
commit af59047f57
10 changed files with 67 additions and 105 deletions

View File

@ -46,13 +46,13 @@
<powermock.version>2.0.2</powermock.version>
<!-- More visible way how to change dependency versions -->
<spigot.version>1.13.2-R0.1-SNAPSHOT</spigot.version>
<bentobox.version>1.6.0</bentobox.version>
<bentobox.version>1.9.0-SNAPSHOT</bentobox.version>
<!-- Revision variable removes warning about dynamic version -->
<revision>${build.version}-SNAPSHOT</revision>
<!-- 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.7.0</build.version>
<build.version>1.9.0</build.version>
</properties>
<!-- Profiles will allow to automatically change build version. -->

View File

@ -24,7 +24,7 @@ public class CalcCommand extends CompositeCommand {
* @param addon - addon
*/
public CalcCommand(Limits addon, CompositeCommand parent) {
super(parent, "calc");
super(parent, "calc", "recount");
this.addon = addon;
}
@ -60,7 +60,7 @@ public class CalcCommand extends CompositeCommand {
}
}
public void calcLimits(UUID targetPlayer, User sender) {
private void calcLimits(UUID targetPlayer, User sender) {
if (addon.getIslands().getIsland(getWorld(), targetPlayer) != null) {
new LimitsCalc(getWorld(), getPlugin(), targetPlayer, addon, sender);
} else {

View File

@ -2,6 +2,7 @@ package bentobox.addon.limits.commands;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
@ -58,8 +59,8 @@ public class LimitPanel {
.put(Material.BEETROOTS, Material.BEETROOT)
.put(Material.REDSTONE_WIRE, Material.REDSTONE);
// Block to Material icons
Optional.ofNullable(Material.getMaterial("SWEET_BERRY_BUSH")).ifPresent(material -> builder.put(material, Material.getMaterial("SWEET_BERRIES")));
Optional.ofNullable(Material.getMaterial("BAMBOO_SAPLING")).ifPresent(material -> builder.put(material, Material.getMaterial("BAMBOO")));
Optional.ofNullable(Material.getMaterial("SWEET_BERRY_BUSH")).ifPresent(material -> builder.put(material, Objects.requireNonNull(Material.getMaterial("SWEET_BERRIES"))));
Optional.ofNullable(Material.getMaterial("BAMBOO_SAPLING")).ifPresent(material -> builder.put(material, Objects.requireNonNull(Material.getMaterial("BAMBOO"))));
B2M = builder.build();
}
@ -105,7 +106,7 @@ public class LimitPanel {
addon.getSettings().getLimits().forEach((k,v) -> {
PanelItemBuilder pib = new PanelItemBuilder();
pib.name(Util.prettifyText(k.toString()));
Material m = Material.BARRIER;
Material m;
try {
if (E2M.containsKey(k)) {
m = E2M.get(k);

View File

@ -1,16 +1,13 @@
package bentobox.addon.limits.commands;
import java.util.EnumMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import org.bukkit.Bukkit;
import org.bukkit.ChunkSnapshot;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.scheduler.BukkitTask;
import bentobox.addon.limits.Limits;
import bentobox.addon.limits.listeners.BlockLimitsListener;
@ -19,71 +16,47 @@ import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.util.Pair;
import world.bentobox.bentobox.util.Util;
/**
*
* @author YellowZaki
* @author YellowZaki, tastybento
*/
public class LimitsCalc {
private boolean checking;
private Limits addon;
private World world;
private Island island;
private BlockLimitsListener bll;
private final Limits addon;
private final World world;
private final Island island;
private final BlockLimitsListener bll;
private IslandBlockCount ibc;
private Map<Material, Integer> blockCount;
private BukkitTask task;
private User sender;
private final Map<Material, AtomicInteger> blockCount;
private final User sender;
private final Set<Pair<Integer, Integer>> chunksToScan;
private int count;
LimitsCalc(World world, BentoBox instance, UUID targetPlayer, Limits addon, User sender) {
this.checking = true;
this.addon = addon;
this.world = world;
this.island = instance.getIslands().getIsland(world, targetPlayer);
this.bll = addon.getBlockLimitListener();
this.ibc = bll.getIsland(island.getUniqueId());
blockCount = new EnumMap<>(Material.class);
this.sender = sender;
Set<Pair<Integer, Integer>> chunksToScan = getChunksToScan(island);
this.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() < 200) {
Pair<Integer, Integer> pair = it.next();
if (!world.isChunkLoaded(pair.x, pair.z)) {
world.loadChunk(pair.x, pair.z);
chunkSnapshot.add(world.getChunkAt(pair.x, pair.z).getChunkSnapshot());
world.unloadChunk(pair.x, pair.z);
} else {
chunkSnapshot.add(world.getChunkAt(pair.x, pair.z).getChunkSnapshot());
}
it.remove();
}
// Move to next step
checking = false;
checkChunksAsync(chunkSnapshot);
}
}, 0L, 1);
}
this.world = world;
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;
});
// Get chunks to scan
chunksToScan = getChunksToScan(island);
count = 0;
chunksToScan.forEach(c -> Util.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();
}
});
}));
}
@ -98,7 +71,7 @@ public class LimitsCalc {
if (chunk.getZ() * 16 + z < island.getMinProtectedZ() || chunk.getZ() * 16 + z >= island.getMinProtectedZ() + island.getProtectionRange() * 2) {
continue;
}
for (int y = 0; y < island.getCenter().getWorld().getMaxHeight(); y++) {
for (int y = 0; y < Objects.requireNonNull(island.getCenter().getWorld()).getMaxHeight(); y++) {
Material blockData = chunk.getBlockType(x, y, z);
// Air is free
if (!blockData.equals(Material.AIR)) {
@ -114,9 +87,9 @@ public class LimitsCalc {
// md is limited
if (bll.getMaterialLimits(world, island.getUniqueId()).containsKey(md)) {
if (!blockCount.containsKey(md)) {
blockCount.put(md, 1);
blockCount.put(md, new AtomicInteger(1));
} else {
blockCount.put(md, blockCount.get(md) + 1);
blockCount.get(md).getAndIncrement();
}
}
}
@ -133,14 +106,15 @@ public class LimitsCalc {
}
private void tidyUp() {
// Cancel
task.cancel();
if (ibc == null) {
ibc = new IslandBlockCount();
}
ibc.setBlockCount(blockCount);
ibc.setBlockCount(blockCount.entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
entry -> entry.getValue().get())));
bll.setIsland(island.getUniqueId(), ibc);
sender.sendMessage("admin.limits.calc.finished");
Bukkit.getScheduler().runTask(addon.getPlugin(), () -> sender.sendMessage("admin.limits.calc.finished"));
}
}

View File

@ -1,13 +1,6 @@
package bentobox.addon.limits.listeners;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.*;
import org.bukkit.Bukkit;
import org.bukkit.Material;
@ -98,19 +91,19 @@ public class BlockLimitsListener implements Listener {
addon.log("Loading default limits");
if (addon.getConfig().isConfigurationSection("blocklimits")) {
ConfigurationSection limitConfig = addon.getConfig().getConfigurationSection("blocklimits");
defaultLimitMap = loadLimits(limitConfig);
defaultLimitMap = loadLimits(Objects.requireNonNull(limitConfig));
}
// Load specific worlds
if (addon.getConfig().isConfigurationSection("worlds")) {
ConfigurationSection worlds = addon.getConfig().getConfigurationSection("worlds");
for (String worldName : worlds.getKeys(false)) {
for (String worldName : Objects.requireNonNull(worlds).getKeys(false)) {
World world = Bukkit.getWorld(worldName);
if (world != null && addon.inGameModeWorld(world)) {
addon.log("Loading limits for " + world.getName());
worldLimitMap.putIfAbsent(world, new HashMap<>());
ConfigurationSection matsConfig = worlds.getConfigurationSection(worldName);
worldLimitMap.put(world, loadLimits(matsConfig));
worldLimitMap.put(world, loadLimits(Objects.requireNonNull(matsConfig)));
}
}
}
@ -156,7 +149,7 @@ public class BlockLimitsListener implements Listener {
handleBreak(e, e.getPlayer(), e.getBlock());
}
void handleBreak(Cancellable e, Player player, Block b) {
private void handleBreak(Cancellable e, Player player, Block b) {
Material mat = b.getType();
// Special handling for crops that can break in different ways
if (mat.equals(Material.WHEAT_SEEDS)) {

View File

@ -1,6 +1,9 @@
package bentobox.addon.limits.listeners;
import java.util.Objects;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
@ -41,7 +44,7 @@ public class EntityLimitListener implements Listener {
}
if (addon.getSettings().getLimits().containsKey(e.getVehicle().getType())) {
// If someone in that area has the bypass permission, allow the spawning
for (Entity entity : e.getVehicle().getLocation().getWorld().getNearbyEntities(e.getVehicle().getLocation(), 5, 5, 5)) {
for (Entity entity : Objects.requireNonNull(e.getVehicle().getLocation().getWorld()).getNearbyEntities(e.getVehicle().getLocation(), 5, 5, 5)) {
if (entity instanceof Player) {
Player player = (Player)entity;
boolean bypass = (player.isOp() || player.hasPermission(addon.getPlugin().getIWM().getPermissionPrefix(e.getVehicle().getWorld()) + MOD_BYPASS));
@ -103,7 +106,7 @@ public class EntityLimitListener implements Listener {
private boolean checkByPass(Location l) {
// If someone in that area has the bypass permission, allow the spawning
for (Entity entity : l.getWorld().getNearbyEntities(l, 5, 5, 5)) {
for (Entity entity : Objects.requireNonNull(l.getWorld()).getNearbyEntities(l, 5, 5, 5)) {
if (entity instanceof Player) {
Player player = (Player)entity;
if (player.isOp() || player.hasPermission(addon.getPlugin().getIWM().getPermissionPrefix(l.getWorld()) + MOD_BYPASS)) {
@ -121,8 +124,9 @@ public class EntityLimitListener implements Listener {
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onBlock(HangingPlaceEvent e) {
Player player = e.getPlayer();
if (player == null) return;
addon.getIslands().getIslandAt(e.getEntity().getLocation()).ifPresent(island -> {
boolean bypass = player.isOp() || player.hasPermission(addon.getPlugin().getIWM().getPermissionPrefix(e.getEntity().getWorld()) + MOD_BYPASS);
boolean bypass = Objects.requireNonNull(player).isOp() || player.hasPermission(addon.getPlugin().getIWM().getPermissionPrefix(e.getEntity().getWorld()) + MOD_BYPASS);
// Check if entity can be hung
if (!bypass && !island.isSpawn() && atLimit(island, e.getEntity())) {
// Not allowed
@ -143,7 +147,9 @@ public class EntityLimitListener implements Listener {
e.setCancelled(true);
// If the reason is anything but because of a spawner then tell players within range
if (!e.getSpawnReason().equals(SpawnReason.SPAWNER) && !e.getSpawnReason().equals(SpawnReason.NATURAL) && !e.getSpawnReason().equals(SpawnReason.INFECTION) && !e.getSpawnReason().equals(SpawnReason.NETHER_PORTAL) && !e.getSpawnReason().equals(SpawnReason.REINFORCEMENTS) && !e.getSpawnReason().equals(SpawnReason.SLIME_SPLIT)) {
for (Entity ent : e.getLocation().getWorld().getNearbyEntities(e.getLocation(), 5, 5, 5)) {
World w = e.getLocation().getWorld();
if (w == null) return;
for (Entity ent : w.getNearbyEntities(e.getLocation(), 5, 5, 5)) {
if (ent instanceof Player) {
User.getInstance(ent).sendMessage("entity-limits.hit-limit", "[entity]",
Util.prettifyText(e.getEntityType().toString()),

View File

@ -1,6 +1,7 @@
package bentobox.addon.limits.listeners;
import java.util.Locale;
import java.util.Objects;
import java.util.UUID;
import org.apache.commons.lang.math.NumberUtils;
@ -141,8 +142,8 @@ public class JoinListener implements Listener {
// Set perm-based limits
String prefix = addon.getGameModePermPrefix(world);
String name = addon.getGameModeName(world);
if (!prefix.isEmpty() && !name.isEmpty()) {
checkPerms(owner.getPlayer(), prefix + "island.limit.", island.getUniqueId(), name);
if (!prefix.isEmpty() && !name.isEmpty() && owner.getPlayer() != null) {
checkPerms(Objects.requireNonNull(owner.getPlayer()), prefix + "island.limit.", island.getUniqueId(), name);
}
}
}

View File

@ -27,7 +27,7 @@ worlds:
HOPPER: 11
# Default entity limits within a player's island space (protected area and to island limit).
# A limit of 5 will allow up to 5 entites in over world, 5 in nether and 5 in the end.
# A limit of 5 will allow up to 5 entities in over world, 5 in nether and 5 in the end.
# Affects all types of creature spawning. Also includes entities like MINECARTS.
# Note: Only the first 49 limited blocks and entities are shown in the limits GUI.
entitylimits:

View File

@ -19,7 +19,7 @@ admin:
calc:
parameters: "<player>"
description: "recalculate the island limits for player"
finished: "&aIsland recalc finished sucessfully!"
finished: "&aIsland recalc finished successfully!"
island:
limits:

View File

@ -1,6 +1,3 @@
/**
*
*/
package bentobox.addon.limits.listeners;
import static org.mockito.Mockito.mock;
@ -10,6 +7,7 @@ import static org.mockito.Mockito.when;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.times;
import java.util.Collections;
@ -24,7 +22,6 @@ import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.permissions.PermissionAttachmentInfo;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -70,11 +67,8 @@ public class JoinListenerTest {
@Mock
private Island island;
/**
* @throws java.lang.Exception
*/
@Before
public void setUp() throws Exception {
public void setUp() {
jl = new JoinListener(addon);
// Setup addon
when(addon.getGameModes()).thenReturn(Collections.singletonList(bskyblock));
@ -111,13 +105,6 @@ public class JoinListenerTest {
when(island.getOwner()).thenReturn(UUID.randomUUID());
}
/**
* @throws java.lang.Exception
*/
@After
public void tearDown() throws Exception {
}
/**
* Test method for {@link bentobox.addon.limits.listeners.JoinListener#onNewIsland(world.bentobox.bentobox.api.events.island.IslandEvent)}.
*/
@ -157,7 +144,7 @@ public class JoinListenerTest {
IslandEvent e = new IslandEvent(island, null, false, null, IslandEvent.Reason.CREATED);
jl.onNewIsland(e);
verify(island).getWorld();
verify(owner).getPlayer();
verify(owner, times(2)).getPlayer();
}
/**