From 333c9a82729e08eec2f319518466e22d91752bfa Mon Sep 17 00:00:00 2001 From: Huynh Tien Date: Mon, 20 Dec 2021 19:37:50 +0700 Subject: [PATCH] load chunks passively --- .../util/teleport/SafeSpotTeleport.java | 147 +++++++++--------- 1 file changed, 75 insertions(+), 72 deletions(-) diff --git a/src/main/java/world/bentobox/bentobox/util/teleport/SafeSpotTeleport.java b/src/main/java/world/bentobox/bentobox/util/teleport/SafeSpotTeleport.java index ed68ea525..a41dcbfd6 100644 --- a/src/main/java/world/bentobox/bentobox/util/teleport/SafeSpotTeleport.java +++ b/src/main/java/world/bentobox/bentobox/util/teleport/SafeSpotTeleport.java @@ -18,39 +18,40 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicBoolean; /** * A class that calculates finds a safe spot asynchronously and then teleports the player there. - * @author tastybento * + * @author tastybento */ public class SafeSpotTeleport { private static final int MAX_CHUNKS = 6; private static final long SPEED = 1; private static final int MAX_RADIUS = 50; - private boolean notChecking; - private BukkitTask task; - // Parameters private final Entity entity; private final Location location; - private boolean portal; private final int homeNumber; - - // Locations - private Location bestSpot; - private final BentoBox plugin; - private List> chunksToScan; private final Runnable runnable; private final Runnable failRunnable; private final CompletableFuture result; private final String homeName; private final int maxHeight; + private final World world; + private final AtomicBoolean checking = new AtomicBoolean(); + private BukkitTask task; + private boolean portal; + // Locations + private Location bestSpot; + private Iterator> chunksToScanIterator; + private int checkedChunks = 0; /** * Teleports and entity to a safe spot on island + * * @param builder - safe spot teleport builder */ SafeSpotTeleport(Builder builder) { @@ -63,9 +64,11 @@ public class SafeSpotTeleport { this.runnable = builder.getRunnable(); this.failRunnable = builder.getFailRunnable(); this.result = builder.getResult(); - this.maxHeight = location.getWorld().getMaxHeight() - 20; + this.world = location.getWorld(); + assert world != null; + this.maxHeight = world.getMaxHeight() - 20; // Try to go - Util.getChunkAtAsync(location).thenRun(()-> tryToGo(builder.getFailureMessage())); + Util.getChunkAtAsync(location).thenRun(() -> tryToGo(builder.getFailureMessage())); } private void tryToGo(String failureMessage) { @@ -83,41 +86,42 @@ public class SafeSpotTeleport { } } // Get chunks to scan - chunksToScan = getChunksToScan(); - - // Start checking - notChecking = true; + chunksToScanIterator = getChunksToScan().iterator(); // Start a recurring task until done or cancelled task = Bukkit.getScheduler().runTaskTimer(plugin, () -> gatherChunks(failureMessage), 0L, SPEED); } private void gatherChunks(String failureMessage) { - if (!notChecking) { + if (checking.get()) { return; } - notChecking = false; - List chunkSnapshot = new ArrayList<>(); - Iterator> it = chunksToScan.iterator(); - if (!it.hasNext()) { + checking.set(true); + if (checkedChunks > MAX_CHUNKS || !chunksToScanIterator.hasNext()) { // Nothing left tidyUp(entity, failureMessage); return; } - // Add chunk snapshots to the list - while (it.hasNext() && chunkSnapshot.size() < MAX_CHUNKS) { - Pair pair = it.next(); - if (location.getWorld() != null) { - boolean isLoaded = location.getWorld().getChunkAt(pair.x, pair.z).isLoaded(); - chunkSnapshot.add(location.getWorld().getChunkAt(pair.x, pair.z).getChunkSnapshot()); - if (!isLoaded) { - location.getWorld().getChunkAt(pair.x, pair.z).unload(); - } - } - it.remove(); + + // Get the chunk + Pair chunkPair = chunksToScanIterator.next(); + chunksToScanIterator.remove(); + checkedChunks++; + if (checkedChunks >= MAX_CHUNKS) { + checking.set(false); + return; } - // Move to next step - checkChunks(chunkSnapshot); + + // Get the chunk snapshot and scan it + Util.getChunkAtAsync(world, chunkPair.x, chunkPair.z) + .thenApply(Chunk::getChunkSnapshot) + .whenCompleteAsync((snapshot, e) -> { + if (snapshot != null && scanChunk(snapshot)) { + task.cancel(); + } else { + checking.set(false); + } + }); } private void tidyUp(Entity entity, String failureMessage) { @@ -128,7 +132,7 @@ public class SafeSpotTeleport { if (portal && bestSpot != null) { // Portals found, teleport to the best spot we found teleportEntity(bestSpot); - } else if (entity instanceof Player) { + } else if (entity instanceof Player player) { // Return to main thread and teleport the player Bukkit.getScheduler().runTask(plugin, () -> { // Failed, no safe spot @@ -137,15 +141,15 @@ public class SafeSpotTeleport { } if (!plugin.getIWM().inWorld(entity.getLocation())) { // Last resort - ((Player)entity).performCommand("spawn"); + player.performCommand("spawn"); } else { // Create a spot for the player to be - if (location.getWorld().getEnvironment().equals(Environment.NETHER)) { - makeAndTelport(Material.NETHERRACK); - } else if (location.getWorld().getEnvironment().equals(Environment.THE_END)) { - makeAndTelport(Material.END_STONE); + if (world.getEnvironment().equals(Environment.NETHER)) { + makeAndTeleport(Material.NETHERRACK); + } else if (world.getEnvironment().equals(Environment.THE_END)) { + makeAndTeleport(Material.END_STONE); } else { - makeAndTelport(Material.COBBLESTONE); + makeAndTeleport(Material.COBBLESTONE); } } if (failRunnable != null) { @@ -161,7 +165,7 @@ public class SafeSpotTeleport { } } - private void makeAndTelport(Material m) { + private void makeAndTeleport(Material m) { location.getBlock().getRelative(BlockFace.DOWN).setType(m, false); location.getBlock().setType(Material.AIR, false); location.getBlock().getRelative(BlockFace.UP).setType(Material.AIR, false); @@ -174,20 +178,21 @@ public class SafeSpotTeleport { /** * Gets a set of chunk coords that will be scanned. + * * @return - list of chunk coords to be scanned */ private List> getChunksToScan() { List> chunksToScan = new ArrayList<>(); - int maxRadius = plugin.getIslands().getIslandAt(location).map(Island::getProtectionRange).orElseGet(() -> plugin.getIWM().getIslandProtectionRange(location.getWorld())); + int maxRadius = plugin.getIslands().getIslandAt(location).map(Island::getProtectionRange).orElseGet(() -> plugin.getIWM().getIslandProtectionRange(world)); maxRadius = Math.min(MAX_RADIUS, maxRadius); int x = location.getBlockX(); int z = location.getBlockZ(); // Create ever increasing squares around the target location int radius = 0; do { - for (int i = x - radius; i <= x + radius; i+=16) { - for (int j = z - radius; j <= z + radius; j+=16) { - addChunk(chunksToScan, new Pair<>(i,j), new Pair<>(i >> 4, j >> 4)); + for (int i = x - radius; i <= x + radius; i += 16) { + for (int j = z - radius; j <= z + radius; j += 16) { + addChunk(chunksToScan, new Pair<>(i, j), new Pair<>(i >> 4, j >> 4)); } } radius++; @@ -201,32 +206,13 @@ public class SafeSpotTeleport { } } - /** - * Loops through the chunks and if a safe spot is found, fires off the teleportation - * @param chunkSnapshot - list of chunk snapshots to check - */ - private void checkChunks(final List chunkSnapshot) { - // Run async task to scan chunks - Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { - for (ChunkSnapshot chunk: chunkSnapshot) { - if (scanChunk(chunk)) { - task.cancel(); - return; - } - } - // Nothing happened, change state - notChecking = true; - }); - } - - /** * @param chunk - chunk snapshot * @return true if a safe spot was found */ private boolean scanChunk(ChunkSnapshot chunk) { int startY = location.getBlockY(); - int minY = location.getWorld().getMinHeight(); + int minY = world.getMinHeight(); int maxY = 60; // Just a dummy value // Check the safe spot at the current height @@ -296,14 +282,14 @@ public class SafeSpotTeleport { /** * Returns true if the location is a safe one. + * * @param chunk - chunk snapshot - * @param x - x coordinate - * @param y - y coordinate - * @param z - z coordinate + * @param x - x coordinate + * @param y - y coordinate + * @param z - z coordinate * @return true if this is a safe spot, false if this is a portal scan */ boolean checkBlock(ChunkSnapshot chunk, int x, int y, int z) { - World world = location.getWorld(); Material type = chunk.getBlockType(x, y, z); Material space1 = chunk.getBlockType(x, Math.min(y + 1, maxHeight), z); Material space2 = chunk.getBlockType(x, Math.min(y + 2, maxHeight), z); @@ -333,6 +319,7 @@ public class SafeSpotTeleport { public static class Builder { private final BentoBox plugin; + private final CompletableFuture result = new CompletableFuture<>(); private Entity entity; private int homeNumber = 0; private String homeName = ""; @@ -341,7 +328,6 @@ public class SafeSpotTeleport { private Location location; private Runnable runnable; private Runnable failRunnable; - private final CompletableFuture result = new CompletableFuture<>(); public Builder(BentoBox plugin) { this.plugin = plugin; @@ -349,6 +335,7 @@ public class SafeSpotTeleport { /** * Set who or what is going to teleport + * * @param entity entity to teleport * @return Builder */ @@ -359,6 +346,7 @@ public class SafeSpotTeleport { /** * Set the island to teleport to + * * @param island island destination * @return Builder */ @@ -369,6 +357,7 @@ public class SafeSpotTeleport { /** * Set the home number to this number + * * @param homeNumber home number * @return Builder * @deprecated use {@link #homeName} @@ -381,6 +370,7 @@ public class SafeSpotTeleport { /** * Set the home name + * * @param homeName - home name * @return Builder * @since 1.16.0 @@ -392,6 +382,7 @@ public class SafeSpotTeleport { /** * This is a portal teleportation + * * @return Builder */ public Builder portal() { @@ -401,6 +392,7 @@ public class SafeSpotTeleport { /** * Set the failure message if this teleport cannot happen + * * @param failureMessage failure message to report to user * @return Builder */ @@ -411,6 +403,7 @@ public class SafeSpotTeleport { /** * Set the desired location + * * @param location the location * @return Builder */ @@ -421,6 +414,7 @@ public class SafeSpotTeleport { /** * Try to teleport the player + * * @return CompletableFuture that will become true if successful and false if not * @since 1.14.0 */ @@ -432,6 +426,7 @@ public class SafeSpotTeleport { /** * Try to teleport the player + * * @return SafeSpotTeleport */ @Nullable @@ -447,6 +442,11 @@ public class SafeSpotTeleport { result.complete(null); return null; } + if (location.getWorld() == null) { + plugin.logError("Attempt to safe teleport to a null world!"); + result.complete(null); + return null; + } if (failureMessage.isEmpty() && entity instanceof Player) { failureMessage = "general.errors.no-safe-location-found"; } @@ -455,6 +455,7 @@ public class SafeSpotTeleport { /** * The task to run after the player is safely teleported. + * * @param runnable - task * @return Builder * @since 1.13.0 @@ -466,14 +467,16 @@ public class SafeSpotTeleport { /** * The task to run if the player is not safely teleported + * * @param runnable - task * @return Builder * @since 1.18.0 */ - public Builder ifFail(Runnable rannable) { + public Builder ifFail(Runnable runnable) { this.failRunnable = runnable; return this; } + /** * @return the plugin */