From cc114027da76673f241cdaadecbbae8754140261 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 16 Jan 2021 09:16:09 -0800 Subject: [PATCH] Made roof search async --- .../greenhouses/greenhouse/MinMaxXZ.java | 7 + .../bentobox/greenhouses/greenhouse/Roof.java | 234 ++++++++++-------- .../greenhouses/greenhouse/Walls.java | 10 +- .../managers/GreenhouseFinder.java | 57 +++-- .../managers/GreenhouseManager.java | 70 ++++-- .../greenhouses/ui/panel/PanelClick.java | 10 +- .../greenhouses/ui/user/MakeCommand.java | 11 +- .../greenhouses/world/AsyncWorldCache.java | 105 ++++++++ .../greenhouses/greenhouse/RoofTest.java | 18 +- .../managers/GreenhouseFinderTest.java | 11 +- 10 files changed, 362 insertions(+), 171 deletions(-) create mode 100644 src/main/java/world/bentobox/greenhouses/world/AsyncWorldCache.java diff --git a/src/main/java/world/bentobox/greenhouses/greenhouse/MinMaxXZ.java b/src/main/java/world/bentobox/greenhouses/greenhouse/MinMaxXZ.java index 04b0a94..8a2970c 100644 --- a/src/main/java/world/bentobox/greenhouses/greenhouse/MinMaxXZ.java +++ b/src/main/java/world/bentobox/greenhouses/greenhouse/MinMaxXZ.java @@ -46,4 +46,11 @@ public abstract class MinMaxXZ { public int getArea() { return (maxX - minX) * (maxZ - minZ); } + + @Override + public String toString() { + return "MinMaxXZ [minX=" + minX + ", maxX=" + maxX + ", minZ=" + minZ + ", maxZ=" + maxZ + "]"; + } + + } diff --git a/src/main/java/world/bentobox/greenhouses/greenhouse/Roof.java b/src/main/java/world/bentobox/greenhouses/greenhouse/Roof.java index cf68d47..e773f4d 100644 --- a/src/main/java/world/bentobox/greenhouses/greenhouse/Roof.java +++ b/src/main/java/world/bentobox/greenhouses/greenhouse/Roof.java @@ -3,15 +3,18 @@ package world.bentobox.greenhouses.greenhouse; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; +import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.World; -import org.bukkit.block.Block; import org.bukkit.util.Vector; +import world.bentobox.bentobox.BentoBox; import world.bentobox.greenhouses.Greenhouses; +import world.bentobox.greenhouses.world.AsyncWorldCache; /** * Contains the parameters of a greenhouse roof @@ -19,6 +22,7 @@ import world.bentobox.greenhouses.Greenhouses; * */ public class Roof extends MinMaxXZ { + private static final BentoBox PLUGIN = Greenhouses.getInstance().getPlugin(); private static final List ROOF_BLOCKS; static { List r = Arrays.stream(Material.values()) @@ -29,40 +33,111 @@ public class Roof extends MinMaxXZ { .collect(Collectors.toList()); ROOF_BLOCKS = Collections.unmodifiableList(r); } - private final Location location; + /** + * Check if material is a roof material + * @param m - material + * @return true if roof material + */ + public static boolean roofBlocks(Material m) { + return ROOF_BLOCKS.contains(m) + || (m.equals(Material.GLOWSTONE) && Greenhouses.getInstance().getSettings().isAllowGlowstone()) + || (m.name().endsWith("GLASS_PANE") && Greenhouses.getInstance().getSettings().isAllowPanes()); + } + private final AsyncWorldCache cache; private int height; + private final Location location; private boolean roofFound; + private final World world; + + /** * Finds a roof from a starting location under the roof and characterizes it + * @param cache * @param loc - starting location */ - public Roof(Location loc) { + public Roof(AsyncWorldCache cache, Location loc) { + this.cache = cache; this.location = loc; - roofFound = findRoof(loc); + this.world = loc.getWorld(); } - private boolean findRoof(Location loc) { - World world = loc.getWorld(); + + /** + * This takes any location and tries to go as far as possible in NWSE directions finding contiguous roof blocks + * up to 100 in any direction + * @param vector - vector to start search + */ + private void expandCoords(Vector vector) { + Vector maxx = vector.clone(); + Vector minx = vector.clone(); + Vector maxz = vector.clone(); + Vector minz = vector.clone(); + int limit = 0; + while (roofBlocks(cache.getBlockType(maxx)) && limit < 100) { + limit++; + maxx.add(new Vector(1,0,0)); + } + // Set Max x + if (maxx.getBlockX() - 1 > maxX) { + maxX = maxx.getBlockX() - 1; + } + limit = 0; + while (roofBlocks(cache.getBlockType(minx)) && limit < 100) { + limit++; + minx.subtract(new Vector(1,0,0)); + } + if (minx.getBlockX() + 1 < minX) { + minX = minx.getBlockX() + 1; + } + limit = 0; + while (roofBlocks(cache.getBlockType(maxz)) && limit < 100) { + limit++; + maxz.add(new Vector(0,0,1)); + } + if (maxz.getBlockZ() - 1 > maxZ) { + maxZ = maxz.getBlockZ() - 1; + } + limit = 0; + while (roofBlocks(cache.getBlockType(minz)) && limit < 100) { + limit++; + minz.subtract(new Vector(0,0,1)); + } + if (minz.getBlockZ() + 1 < minZ) { + minZ = minz.getBlockZ() + 1; + } + } + + public CompletableFuture findRoof() { + CompletableFuture r = new CompletableFuture<>(); + Vector loc = location.toVector(); // This section tries to find a roof block // Try just going up - this covers every case except if the player is standing under a hole - roofFound = false; + Bukkit.getScheduler().runTaskAsynchronously(PLUGIN, () -> { + boolean found = findRoof(loc); + Bukkit.getScheduler().runTask(PLUGIN, () -> r.complete(found)); + }); + return r; + } + private boolean findRoof(Vector loc) { // This does a ever-growing check around the player to find a wall block. It is possible for the player // to be outside the greenhouse in this situation, so a check is done later to make sure the player is inside int roofY = loc.getBlockY(); for (int y = roofY; y < world.getMaxHeight(); y++) { - if (roofBlocks(world.getBlockAt(loc.getBlockX(),y,loc.getBlockZ()).getType())) { + if (roofBlocks(cache.getBlockType(loc.getBlockX(),y,loc.getBlockZ()))) { roofFound = true; - loc = new Location(world,loc.getBlockX(),y,loc.getBlockZ()); + loc = new Vector(loc.getBlockX(),y,loc.getBlockZ()); break; } } // If the roof was not found start going around in circles until something is found // Expand in ever increasing squares around location until a wall block is found spiralSearch(loc, roofY); - if (!roofFound) return false; + if (!roofFound) { + return false; + } // Record the height this.height = loc.getBlockY(); // Now we have a roof block, find how far we can go NSWE @@ -70,7 +145,7 @@ public class Roof extends MinMaxXZ { maxX = loc.getBlockX(); minZ = loc.getBlockZ(); maxZ = loc.getBlockZ(); - expandCoords(world, loc.toVector()); + expandCoords(loc); int minx; int maxx; int minz; @@ -84,7 +159,7 @@ public class Roof extends MinMaxXZ { for (int x = minx; x <= maxx; x++) { for (int z = minz; z <= maxz; z++) { // This will push out the coords if possible - expandCoords(world, new Vector(x, loc.getBlockY(), z)); + expandCoords(new Vector(x, loc.getBlockY(), z)); } } // Repeat until nothing changes @@ -93,98 +168,6 @@ public class Roof extends MinMaxXZ { return true; } - private void spiralSearch(Location loc, int roofY) { - for (int radius = 0; radius < 3 && !roofFound; radius++) { - for (int x = loc.getBlockX() - radius; x <= loc.getBlockX() + radius && !roofFound; x++) { - for (int z = loc.getBlockZ() - radius; z <= loc.getBlockZ() + radius && !roofFound; z++) { - if (!((x > loc.getBlockX() - radius && x < loc.getBlockX() + radius) && (z > loc.getBlockZ() - radius && z < loc.getBlockZ() + radius))) { - checkVertically(loc, x, roofY, z); - } - } - } - } - - } - - private void checkVertically(Location loc, int x, int roofY, int z) { - World world = loc.getWorld(); - Block b = world.getBlockAt(x, roofY, z); - if (!Walls.wallBlocks(b.getType())) { - // Look up - for (int y = roofY; y < world.getMaxHeight() && !roofFound; y++) { - if (roofBlocks(world.getBlockAt(x,y,z).getType())) { - roofFound = true; - loc = new Location(world,x,y,z); - } - } - } - - } - - /** - * This takes any location and tries to go as far as possible in NWSE directions finding contiguous roof blocks - * up to 100 in any direction - * @param height - location to start search - */ - private void expandCoords(World world, Vector height) { - Location maxx = height.toLocation(world); - Location minx = height.toLocation(world); - Location maxz = height.toLocation(world); - Location minz = height.toLocation(world); - int limit = 0; - while (ROOF_BLOCKS - .contains(world.getBlockAt(maxx).getType()) && limit < 100) { - limit++; - maxx.add(new Vector(1,0,0)); - } - if (maxx.getBlockX()-1 > maxX) { - maxX = maxx.getBlockX()-1; - } - - while (roofBlocks(world.getBlockAt(minx).getType()) && limit < 200) { - limit++; - minx.subtract(new Vector(1,0,0)); - } - if (minx.getBlockX() + 1 < minX) { - minX = minx.getBlockX() + 1; - } - - while (roofBlocks(world.getBlockAt(maxz).getType()) && limit < 300) { - limit++; - maxz.add(new Vector(0,0,1)); - } - if (maxz.getBlockZ() - 1 > maxZ) { - maxZ = maxz.getBlockZ() - 1; - } - - while (roofBlocks(world.getBlockAt(minz).getType()) && limit < 400) { - limit++; - minz.subtract(new Vector(0,0,1)); - } - if (minz.getBlockZ() + 1 < minZ) { - minZ = minz.getBlockZ() + 1; - } - } - - /** - * Check if material is a roof material - * @param m - material - * @return true if roof material - */ - public static boolean roofBlocks(Material m) { - return ROOF_BLOCKS.contains(m) - || (m.equals(Material.GLOWSTONE) && Greenhouses.getInstance().getSettings().isAllowGlowstone()) - || (m.name().endsWith("GLASS_PANE") && Greenhouses.getInstance().getSettings().isAllowPanes()); - } - - /** - * @return the roofFound - */ - public boolean isRoofFound() { - return roofFound; - } - - /** * @return the height */ @@ -192,6 +175,7 @@ public class Roof extends MinMaxXZ { return height; } + /** * @return the location */ @@ -199,13 +183,47 @@ public class Roof extends MinMaxXZ { return location; } + private void spiralSearch(Vector v, int roofY) { + for (int radius = 0; radius < 3 && !roofFound; radius++) { + for (int x = v.getBlockX() - radius; x <= v.getBlockX() + radius && !roofFound; x++) { + for (int z = v.getBlockZ() - radius; z <= v.getBlockZ() + radius && !roofFound; z++) { + if (!((x > v.getBlockX() - radius && x < v.getBlockX() + radius) && (z > v.getBlockZ() - radius && z < v.getBlockZ() + radius))) { + checkVertically(v, x, roofY, z); + } + } + } + } - /* (non-Javadoc) - * @see java.lang.Object#toString() + } + + /** + * Get highest roof block + * @param v - vector of search block + * @param x - x coord of current search + * @param roofY - roof y coord + * @param z - z coord of current search */ + private void checkVertically(Vector v, final int x, final int roofY, final int z) { + if (!Walls.wallBlocks(cache.getBlockType(x, roofY, z))) { + // Look up + for (int y = roofY; y < world.getMaxHeight() && !roofFound; y++) { + if (roofBlocks(cache.getBlockType(x,y,z))) { + roofFound = true; + // Move roof up because there is a higher block + v = new Vector(x,y,z); + } + } + } + + } + + @Override public String toString() { - return "Roof [location=" + location + ", minX=" + minX + ", maxX=" + maxX + ", minZ=" + minZ + ", maxZ=" + maxZ - + ", height=" + height + ", roofFound=" + roofFound + "]"; + return "Roof [" + (cache != null ? "cache=" + cache + ", " : "") + "height=" + height + ", " + + (location != null ? "location=" + location + ", " : "") + "roofFound=" + roofFound + ", " + + (world != null ? "world=" + world + ", " : "") + "minX=" + minX + ", maxX=" + maxX + ", minZ=" + minZ + + ", maxZ=" + maxZ + "]"; } + } diff --git a/src/main/java/world/bentobox/greenhouses/greenhouse/Walls.java b/src/main/java/world/bentobox/greenhouses/greenhouse/Walls.java index 616d26c..c68d145 100644 --- a/src/main/java/world/bentobox/greenhouses/greenhouse/Walls.java +++ b/src/main/java/world/bentobox/greenhouses/greenhouse/Walls.java @@ -3,6 +3,7 @@ package world.bentobox.greenhouses.greenhouse; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; import org.bukkit.Location; @@ -43,7 +44,12 @@ public class Walls extends MinMaxXZ { } } - public Walls findWalls(Roof roof) { + /** + * Find walls given a roof + * @param roof - the roof + * @return Future walls + */ + public CompletableFuture findWalls(Roof roof) { // The player is under the roof // Assume the player is inside the greenhouse they are trying to create Location loc = roof.getLocation(); @@ -65,7 +71,7 @@ public class Walls extends MinMaxXZ { maxZ++; // Find the floor again, only looking within the walls floor = getFloorY(world, roof.getHeight(), minX, maxX, minZ,maxZ); - return this; + return CompletableFuture.completedFuture(this); } void lookAround(Location loc, WallFinder wf, Roof roof) { diff --git a/src/main/java/world/bentobox/greenhouses/managers/GreenhouseFinder.java b/src/main/java/world/bentobox/greenhouses/managers/GreenhouseFinder.java index e70556f..98ec100 100644 --- a/src/main/java/world/bentobox/greenhouses/managers/GreenhouseFinder.java +++ b/src/main/java/world/bentobox/greenhouses/managers/GreenhouseFinder.java @@ -3,6 +3,7 @@ package world.bentobox.greenhouses.managers; import java.util.Collection; import java.util.HashSet; import java.util.Set; +import java.util.concurrent.CompletableFuture; import org.bukkit.Location; import org.bukkit.Material; @@ -11,10 +12,12 @@ import org.bukkit.World; import org.bukkit.World.Environment; import org.bukkit.block.Block; +import world.bentobox.bentobox.BentoBox; import world.bentobox.greenhouses.data.Greenhouse; import world.bentobox.greenhouses.greenhouse.Roof; import world.bentobox.greenhouses.greenhouse.Walls; import world.bentobox.greenhouses.managers.GreenhouseManager.GreenhouseResult; +import world.bentobox.greenhouses.world.AsyncWorldCache; public class GreenhouseFinder { @@ -45,32 +48,50 @@ public class GreenhouseFinder { /** * Find out if there is a greenhouse here * @param location - start location - * @return GreenhouseResult class + * @return future GreenhouseResult class */ - public Set find(Location location) { + public CompletableFuture> find(Location location) { + CompletableFuture> r = new CompletableFuture<>(); Set result = new HashSet<>(); redGlass.clear(); + // Get a world cache + AsyncWorldCache cache = new AsyncWorldCache(location.getWorld()); // Find the roof - Roof roof = new Roof(location); - if (!roof.isRoofFound()) { - result.add(GreenhouseResult.FAIL_NO_ROOF); - return result; - } - // Find the walls - Walls walls = new Walls().findWalls(roof); - // Make the initial greenhouse - gh = new Greenhouse(location.getWorld(), walls, roof.getHeight()); - // Set the original biome - gh.setOriginalBiome(location.getBlock().getBiome()); + Roof roof = new Roof(cache, location); + roof.findRoof().thenAccept(found -> { + if (!found) { + result.add(GreenhouseResult.FAIL_NO_ROOF); + r.complete(result); + return; + } + BentoBox.getInstance().logDebug(roof); + // Find the walls + new Walls().findWalls(roof).thenAccept(walls -> { + // Make the initial greenhouse + gh = new Greenhouse(location.getWorld(), walls, roof.getHeight()); + // Set the original biome + gh.setOriginalBiome(location.getBlock().getBiome()); - // Now check to see if the floor really is the floor and the walls follow the rules - result.addAll(checkGreenhouse(gh, roof, walls)); + // Now check to see if the floor really is the floor and the walls follow the rules + checkGreenhouse(gh, roof, walls).thenAccept(c -> { + result.addAll(c); + r.complete(result); + }); + }); - return result; + }); + return r; } - Set checkGreenhouse(Greenhouse gh2, Roof roof, Walls walls) { + /** + * Check the greenhouse has the right number of everything + * @param gh2 - greenhouse + * @param roof - roof object + * @param walls - walls object + * @return future set of Greenhouse Results + */ + CompletableFuture> checkGreenhouse(Greenhouse gh2, Roof roof, Walls walls) { Set result = new HashSet<>(); World world = roof.getLocation().getWorld(); int y; @@ -101,7 +122,7 @@ public class GreenhouseFinder { } result.addAll(checkErrors(roof, y)); - return result; + return CompletableFuture.completedFuture(result); } Collection checkErrors(Roof roof, int y) { diff --git a/src/main/java/world/bentobox/greenhouses/managers/GreenhouseManager.java b/src/main/java/world/bentobox/greenhouses/managers/GreenhouseManager.java index 415a89c..984e191 100644 --- a/src/main/java/world/bentobox/greenhouses/managers/GreenhouseManager.java +++ b/src/main/java/world/bentobox/greenhouses/managers/GreenhouseManager.java @@ -3,6 +3,7 @@ package world.bentobox.greenhouses.managers; import java.util.ArrayList; import java.util.List; import java.util.Set; +import java.util.concurrent.CompletableFuture; import org.bukkit.Location; import org.bukkit.block.Biome; @@ -153,29 +154,36 @@ public class GreenhouseManager implements Listener { * If type is stated then only this specific type will be checked * @param location - location to start search from * @param greenhouseRecipe - recipe requested, or null for a best-effort search - * @return - greenhouse result {@link GhResult} + * @return - future greenhouse result {@link GhResult} */ - public GhResult tryToMakeGreenhouse(Location location, BiomeRecipe greenhouseRecipe) { + public CompletableFuture tryToMakeGreenhouse(Location location, BiomeRecipe greenhouseRecipe) { + CompletableFuture r = new CompletableFuture<>(); GreenhouseFinder finder = new GreenhouseFinder(); - Set resultSet = finder.find(location); - if (!resultSet.isEmpty()) { - // Failure! - return new GhResult().setFinder(finder).setResults(resultSet); - } - // Check if the greenhouse meets the requested recipe - if (greenhouseRecipe != null) { - resultSet = greenhouseRecipe.checkRecipe(finder.getGh()); - if (resultSet.isEmpty()) { - // Success - set recipe and add to map - finder.getGh().setBiomeRecipe(greenhouseRecipe); - resultSet.add(map.addGreenhouse(finder.getGh())); - activateGreenhouse(finder.getGh()); - handler.saveObjectAsync(finder.getGh()); + finder.find(location).thenAccept(resultSet -> { + if (!resultSet.isEmpty()) { + // Failure! + r.complete(new GhResult().setFinder(finder).setResults(resultSet)); + return; + } + // Check if the greenhouse meets the requested recipe + if (greenhouseRecipe != null) { + checkRecipe(r, finder, greenhouseRecipe, resultSet); + return; } - return new GhResult().setFinder(finder).setResults(resultSet); - } - // Try ordered recipes + // Try ordered recipes + findRecipe(finder, resultSet); + r.complete(new GhResult().setFinder(finder).setResults(resultSet)); + }); + return r; + } + + /** + * Tries to match the greenhouse to a recipe by going through all of them in order + * @param finder - finder object + * @param resultSet - result set from find + */ + private void findRecipe(GreenhouseFinder finder, Set resultSet) { resultSet.add(addon.getRecipes().getBiomeRecipes().stream().sorted() .filter(r -> r.checkRecipe(finder.getGh()).isEmpty()).findFirst() .map(r -> { @@ -185,7 +193,29 @@ public class GreenhouseManager implements Listener { handler.saveObjectAsync(finder.getGh()); return map.addGreenhouse(finder.getGh()); }).orElse(GreenhouseResult.FAIL_NO_RECIPE_FOUND)); - return new GhResult().setFinder(finder).setResults(resultSet); + } + + /** + * Checks to see if the greenhouse meets the designated recipe and returns the result + * @param r - completable future + * @param finder - finder object + * @param greenhouseRecipe - recipe requested + * @param resultSet - result set from finder + * @return Greenhouse result + */ + GhResult checkRecipe(CompletableFuture r, GreenhouseFinder finder, BiomeRecipe greenhouseRecipe, Set resultSet) { + resultSet = greenhouseRecipe.checkRecipe(finder.getGh()); + if (resultSet.isEmpty()) { + // Success - set recipe and add to map + finder.getGh().setBiomeRecipe(greenhouseRecipe); + resultSet.add(map.addGreenhouse(finder.getGh())); + activateGreenhouse(finder.getGh()); + handler.saveObjectAsync(finder.getGh()); + } + GhResult recipe = new GhResult().setFinder(finder).setResults(resultSet); + r.complete(recipe); + return recipe; + } private void activateGreenhouse(Greenhouse gh) { diff --git a/src/main/java/world/bentobox/greenhouses/ui/panel/PanelClick.java b/src/main/java/world/bentobox/greenhouses/ui/panel/PanelClick.java index b057044..dc4addd 100644 --- a/src/main/java/world/bentobox/greenhouses/ui/panel/PanelClick.java +++ b/src/main/java/world/bentobox/greenhouses/ui/panel/PanelClick.java @@ -55,12 +55,15 @@ public class PanelClick implements ClickHandler { user.sendMessage("greenhouses.commands.user.make.error.already"); return false; } - GhResult result = addon.getManager().tryToMakeGreenhouse(location, br); + addon.getManager().tryToMakeGreenhouse(location, br).thenAccept(r -> processResult(user, r)); + return true; + } + void processResult(User user, GhResult result) { if (result.getResults().contains(GreenhouseResult.SUCCESS)) { // Success user.sendMessage("greenhouses.commands.user.make.success", "[biome]", result.getFinder().getGh().getBiomeRecipe().getFriendlyName()); - return true; + return; } result.getResults().forEach(r -> user.sendMessage("greenhouses.commands.user.make.error." + r.name())); if (!result.getFinder().getRedGlass().isEmpty()) { @@ -69,8 +72,7 @@ public class PanelClick implements ClickHandler { Bukkit.getScheduler().runTaskLater(addon.getPlugin(), () -> result.getFinder().getRedGlass().forEach(rg -> user.getPlayer().sendBlockChange(rg, rg.getBlock().getBlockData())), 120L); } if (result.getResults().contains(GreenhouseResult.FAIL_INSUFFICIENT_BLOCKS)) { - result.getFinder().getGh().getMissingBlocks().forEach((k,v) -> user.sendMessage("greenhouses.commands.user.make.missing-blocks", "[material]", Util.prettifyText(k.toString()), TextVariables.NUMBER, String.valueOf(v))); + result.getFinder().getGh().getMissingBlocks().forEach((k,v) -> user.sendMessage("greenhouses.commands.user.make.missing-blocks", "[material]", Util.prettifyText(k.toString()), TextVariables.NUMBER, String.valueOf(v))); } - return true; } } diff --git a/src/main/java/world/bentobox/greenhouses/ui/user/MakeCommand.java b/src/main/java/world/bentobox/greenhouses/ui/user/MakeCommand.java index 886e42f..790681e 100644 --- a/src/main/java/world/bentobox/greenhouses/ui/user/MakeCommand.java +++ b/src/main/java/world/bentobox/greenhouses/ui/user/MakeCommand.java @@ -76,7 +76,7 @@ class MakeCommand extends CompositeCommand { return getRecipes(user).get(arg); } /** - * Get a string list of recipies the player has permission to use + * Get a string list of recipes the player has permission to use * @param user - user * @return list */ @@ -99,7 +99,12 @@ class MakeCommand extends CompositeCommand { user.sendMessage("greenhouses.commands.user.make.error.already"); return false; } - GhResult result = ((Greenhouses)this.getAddon()).getManager().tryToMakeGreenhouse(location, br); + // Try to make the greenhouse + ((Greenhouses)this.getAddon()).getManager().tryToMakeGreenhouse(location, br).thenAccept(result -> informUser(user, br, result)); + return true; + } + + private boolean informUser(User user, BiomeRecipe br, GhResult result) { if (result.getResults().contains(GreenhouseResult.SUCCESS)) { // Success user.sendMessage("greenhouses.commands.user.make.success", "[biome]", result.getFinder().getGh().getBiomeRecipe().getFriendlyName()); @@ -114,7 +119,7 @@ class MakeCommand extends CompositeCommand { if (br != null && result.getResults().contains(GreenhouseResult.FAIL_INSUFFICIENT_BLOCKS)) { result.getFinder().getGh().getMissingBlocks().forEach((k,v) -> user.sendMessage("greenhouses.commands.user.make.missing-blocks", "[material]", Util.prettifyText(k.toString()), TextVariables.NUMBER, String.valueOf(v))); } - return true; + return false; } @Override diff --git a/src/main/java/world/bentobox/greenhouses/world/AsyncWorldCache.java b/src/main/java/world/bentobox/greenhouses/world/AsyncWorldCache.java new file mode 100644 index 0000000..4f5e85c --- /dev/null +++ b/src/main/java/world/bentobox/greenhouses/world/AsyncWorldCache.java @@ -0,0 +1,105 @@ +package world.bentobox.greenhouses.world; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +import org.bukkit.Bukkit; +import org.bukkit.ChunkSnapshot; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.util.Vector; + +import world.bentobox.bentobox.BentoBox; +import world.bentobox.bentobox.util.Pair; +import world.bentobox.bentobox.util.Util; +import world.bentobox.greenhouses.Greenhouses; + +/** + * Provides a thread-safe cache world chunks + * @author tastybento + * + */ +public class AsyncWorldCache { + + private final World world; + private final Map, ChunkSnapshot> cache; + + /** + * Chunk cache. This class is designed to be run async and blocks futures + * @param world - world to cache + */ + public AsyncWorldCache(World world) { + this.world = world; + cache = new HashMap<>(); + } + + + /** + * Get chunk snapshot from world + * @param x - coord + * @param z - coord + * @return future chunk snapshot + */ + private CompletableFuture getAChunk(int x, int z) { + CompletableFuture r = new CompletableFuture<>(); + Bukkit.getScheduler().runTask(Greenhouses.getInstance().getPlugin(), () -> + Util.getChunkAtAsync(world, x, z).thenAccept(chunk -> r.complete(chunk.getChunkSnapshot()))); + return r; + } + + /** + * Get snapshot from cache or world + * @param x - block coord + * @param z - block coord + * @return chunk snapshot + */ + private ChunkSnapshot getSnap(int x, int z) { + // Convert from block to chunk coords + Pair key = new Pair<>((x >> 4), (z >> 4)); + // Get from cache if it is available + if (cache.containsKey(key)) { + return cache.get(key); + } + ChunkSnapshot cs; + try { + // Block on getting the chunk because this is running async + cs = getAChunk(key.x, key.z).get(); + } catch (InterruptedException | ExecutionException e) { + // Try again... + return getSnap(x,z); + } + // Store in cache + cache.put(key, cs); + return cs; + + } + + /** + * Get block material for block at corresponding coordinates + * + * @param x block coordinate + * @param y 0-255 + * @param z block coordinate + * @return material type + */ + public Material getBlockType(int x, int y, int z) { + // Convert block coords to chunk coords + int xx = x >= 0 ? x % 16 : 15 + (x % 16); + int zz = z >= 0 ? z % 16 : 15 + (z % 16); + Material m = getSnap(x,z).getBlockType(xx, y, zz); + BentoBox.getInstance().logDebug(m); + return m; + } + + /** + * Get block material for block at corresponding coordinates + * @param v - vector + * @return Material + */ + public Material getBlockType(Vector v) { + return getBlockType(v.getBlockX(), v.getBlockY(), v.getBlockZ()); + } + +} diff --git a/src/test/java/world/bentobox/greenhouses/greenhouse/RoofTest.java b/src/test/java/world/bentobox/greenhouses/greenhouse/RoofTest.java index c9cf476..50dff02 100644 --- a/src/test/java/world/bentobox/greenhouses/greenhouse/RoofTest.java +++ b/src/test/java/world/bentobox/greenhouses/greenhouse/RoofTest.java @@ -22,6 +22,7 @@ import org.powermock.modules.junit4.PowerMockRunner; import world.bentobox.greenhouses.Greenhouses; import world.bentobox.greenhouses.Settings; +import world.bentobox.greenhouses.world.AsyncWorldCache; /** @@ -42,6 +43,7 @@ public class RoofTest { @Mock private Greenhouses addon; private Settings s; + private AsyncWorldCache cache; /** * @throws java.lang.Exception @@ -89,15 +91,17 @@ public class RoofTest { when(location.getBlock()).thenReturn(block); when(location.clone()).thenReturn(location); + cache = new AsyncWorldCache(world); // Test - roof = new Roof(location); + roof = new Roof(cache, location); + roof.findRoof(); } @Test public void testNoGlass() { when(block.getType()).thenReturn(Material.AIR); - roof = new Roof(location); - + roof = new Roof(cache, location); + roof.findRoof(); } /** @@ -140,14 +144,6 @@ public class RoofTest { assertEquals(1406, roof.getArea()); } - /** - * Test method for {@link world.bentobox.greenhouses.greenhouse.Roof#isRoofFound()}. - */ - @Test - public void testIsRoofFound() { - assertTrue(roof.isRoofFound()); - } - /** * Test method for {@link world.bentobox.greenhouses.greenhouse.Roof#getHeight()}. */ diff --git a/src/test/java/world/bentobox/greenhouses/managers/GreenhouseFinderTest.java b/src/test/java/world/bentobox/greenhouses/managers/GreenhouseFinderTest.java index a081701..cbd215d 100644 --- a/src/test/java/world/bentobox/greenhouses/managers/GreenhouseFinderTest.java +++ b/src/test/java/world/bentobox/greenhouses/managers/GreenhouseFinderTest.java @@ -107,11 +107,12 @@ public class GreenhouseFinderTest { @Test public void testCheckGreenhouse() { Greenhouse gh2 = new Greenhouse(world, walls, ROOF_HEIGHT); - Set result = gf.checkGreenhouse(gh2, roof, walls); - assertTrue(result.isEmpty()); // Success - assertEquals(441, gf.getWallBlockCount()); - assertEquals(0, gf.getWallDoors()); - assertEquals(0, gf.getGhHopper()); + gf.checkGreenhouse(gh2, roof, walls).thenAccept(result -> { + assertTrue(result.isEmpty()); // Success + assertEquals(441, gf.getWallBlockCount()); + assertEquals(0, gf.getWallDoors()); + assertEquals(0, gf.getGhHopper()); + }); } /**