diff --git a/src/main/java/ca/recrown/islandsurvivalcraft/interaction/commands/RegisteredCommands.java b/src/main/java/ca/recrown/islandsurvivalcraft/interaction/commands/RegisteredCommands.java index e891816..426f7da 100644 --- a/src/main/java/ca/recrown/islandsurvivalcraft/interaction/commands/RegisteredCommands.java +++ b/src/main/java/ca/recrown/islandsurvivalcraft/interaction/commands/RegisteredCommands.java @@ -1,11 +1,11 @@ package ca.recrown.islandsurvivalcraft.interaction.commands; -import ca.recrown.islandsurvivalcraft.interaction.commands.runnables.HighlightIslandCommand; +import ca.recrown.islandsurvivalcraft.interaction.commands.runnables.HighlightCommand; import ca.recrown.islandsurvivalcraft.interaction.commands.runnables.*; public enum RegisteredCommands { - HIGHLIGHT(new HighlightIslandCommand()), + HIGHLIGHT(new HighlightCommand()), HELP(new HelpCommand()), VALUE(new ValueRunnable()), ; diff --git a/src/main/java/ca/recrown/islandsurvivalcraft/interaction/commands/runnables/HighlightIslandCommand.java b/src/main/java/ca/recrown/islandsurvivalcraft/interaction/commands/runnables/HighlightCommand.java similarity index 62% rename from src/main/java/ca/recrown/islandsurvivalcraft/interaction/commands/runnables/HighlightIslandCommand.java rename to src/main/java/ca/recrown/islandsurvivalcraft/interaction/commands/runnables/HighlightCommand.java index eab2865..f7021de 100644 --- a/src/main/java/ca/recrown/islandsurvivalcraft/interaction/commands/runnables/HighlightIslandCommand.java +++ b/src/main/java/ca/recrown/islandsurvivalcraft/interaction/commands/runnables/HighlightCommand.java @@ -1,8 +1,6 @@ package ca.recrown.islandsurvivalcraft.interaction.commands.runnables; import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedList; import java.util.Set; import org.bukkit.Bukkit; @@ -18,7 +16,7 @@ import ca.recrown.islandsurvivalcraft.utilities.datatypes.Point2; import ca.recrown.islandsurvivalcraft.world.WorldInfo; import ca.recrown.islandsurvivalcraft.world.Information.IslandInformation; -public class HighlightIslandCommand implements CommandRunnable { +public class HighlightCommand implements CommandRunnable { private boolean requiresInit = true; private final Particle.DustOptions EDGE_DUST_OPTIONS = new Particle.DustOptions(Color.RED, 1f); private final Particle.DustOptions ORIGIN_DUST_OPTIONS = new Particle.DustOptions(Color.GREEN, 1f); @@ -26,7 +24,6 @@ public class HighlightIslandCommand implements CommandRunnable { private final double OFFSET_X = 0.0d, OFFSET_Y = 6.0d, OFFSET_Z = 0.0d; private final double EXTRA = 0d; HashSet playersHighlighting = new HashSet<>(); - LinkedList waitingList = new LinkedList<>(); @Override public String getDescription() { @@ -45,7 +42,6 @@ public class HighlightIslandCommand implements CommandRunnable { } if (args[0].toLowerCase().equals("start")) { if (playersHighlighting.add(player)) { - waitingList.add(player); sender.sendMessage("You are now highlighting islands."); } else { sender.sendMessage("You are already highlighting islands."); @@ -68,28 +64,26 @@ public class HighlightIslandCommand implements CommandRunnable { Bukkit.getScheduler().scheduleSyncRepeatingTask(islandsurvivalcraft, new Runnable() { @Override public void run() { - Player player = waitingList.poll(); - if (player == null || !player.isOnline() || !playersHighlighting.contains(player)) { - playersHighlighting.remove(player); - return; - } - World world = player.getWorld(); - WorldInfo worldInfo = islandsurvivalcraft.getWorldInfoManager().retrieve(world.getName()); - Location playerLocation = player.getLocation(); - int playerX = playerLocation.getBlockX(), playerY = playerLocation.getBlockY(), playerZ = playerLocation.getBlockZ(); - Point2 playerCoords = new Point2(playerX, playerZ); - IslandInformation islandInfo = worldInfo.getIslandInfoManager().getIslandInformation(playerCoords); - if (islandInfo != null) { - Set islandBorder = islandInfo.getEdgeCoordinates(); - Iterator islandborderIterable = islandBorder.iterator(); - for (int i = 0; i < islandBorder.size(); i+= 2) { - Point2 current = islandborderIterable.next(); - spawnParticle(world, current.x, playerY, current.y, EDGE_DUST_OPTIONS); - islandborderIterable.next(); + for (Player player : playersHighlighting) { + if (player == null) return; + if (!player.isOnline()) { + playersHighlighting.remove(player); + return; + } + World world = player.getWorld(); + WorldInfo worldInfo = islandsurvivalcraft.getWorldInfoManager().retrieve(world.getName()); + Location playerLocation = player.getLocation(); + int playerX = playerLocation.getBlockX(), playerY = playerLocation.getBlockY(), playerZ = playerLocation.getBlockZ(); + Point2 playerCoords = new Point2(playerX, playerZ); + IslandInformation islandInfo = worldInfo.getIslandInfoManager().getIslandInformation(playerCoords); + if (islandInfo != null) { + Set islandBorder = islandInfo.getEdgeCoordinates(); + for (Point2 current : islandBorder) { + spawnParticle(world, current.x, playerY, current.y, EDGE_DUST_OPTIONS); + } + spawnParticle(world, islandInfo.getIslandOrigin().x, playerY, islandInfo.getIslandOrigin().y, ORIGIN_DUST_OPTIONS); } - spawnParticle(world, islandInfo.getIslandOrigin().x, playerY, islandInfo.getIslandOrigin().y, ORIGIN_DUST_OPTIONS); } - waitingList.add(player); } }, 0, 10); @@ -101,7 +95,9 @@ public class HighlightIslandCommand implements CommandRunnable { @Override public String getDetailedDescription() { - return "A helpful debugging command to analyze what the plugin considers an island by displaying particle effects around the player that is a part of the island. The argument is required to dicate whether or not to start the highlighting or stop it."; + return "A helpful debugging command to analyze what the plugin considers an island by displaying particle effects around the player that is a part of the island. " + + "Red particles indicate the islands border, and green particles indicate the origin. " + + "The argument is required to dicate whether or not to start the highlighting or stop it."; } @Override diff --git a/src/main/java/ca/recrown/islandsurvivalcraft/utilities/drawing/Circle.java b/src/main/java/ca/recrown/islandsurvivalcraft/utilities/drawing/Circle.java new file mode 100644 index 0000000..bb14b37 --- /dev/null +++ b/src/main/java/ca/recrown/islandsurvivalcraft/utilities/drawing/Circle.java @@ -0,0 +1,58 @@ +package ca.recrown.islandsurvivalcraft.utilities.drawing; + +import ca.recrown.islandsurvivalcraft.utilities.datatypes.Point2; +import ca.recrown.islandsurvivalcraft.utilities.pathfinding.CoordinateValidatable; + +public class Circle { + public final Point2 center; + public final CoordinateValidatable drawer; + public final int r; + int p, x, y; + + /** + * A circle drawing object. + * @param center The center point. + * @param radius The radius of the circle to be drawn. + * @param drawer The coordinate validator to be used to draw. The value the validator returns is ignored. + */ + public Circle(Point2 center, int radius, CoordinateValidatable drawer) { + if (center == null) throw new NullPointerException("Center cannot be null."); + if (radius <= 0) throw new IllegalArgumentException("radius cannot be 0 or less."); + if (drawer == null) throw new NullPointerException("Drawer cannot be null."); + this.center = center; + this.r = radius; + this.drawer = drawer; + + p = 1 - radius; + x = 0; + y = this.r; + } + + /** + * + * @return true if done. + */ + public boolean step() { + if (x > y) return true; + drawer.validate(new Point2(center.x + x, center.y + y)); + drawer.validate(new Point2(center.x + x, center.y - y)); + drawer.validate(new Point2(center.x - x, center.y + y)); + drawer.validate(new Point2(center.x - x, center.y - y)); + drawer.validate(new Point2(center.x + y, center.y + x)); + drawer.validate(new Point2(center.x + y, center.y - x)); + drawer.validate(new Point2(center.x - y, center.y + x)); + drawer.validate(new Point2(center.x - y, center.y - x)); + if (p < 0) { + p += 2*x + 3; + } else { + p += 2 * x - 2 * y + 5; + y--; + } + x++; + return x > y; + } + + public void draw() { + while (!step()); + } +} \ No newline at end of file diff --git a/src/main/java/ca/recrown/islandsurvivalcraft/utilities/floodfill/Flooder.java b/src/main/java/ca/recrown/islandsurvivalcraft/utilities/drawing/Flooder.java similarity index 54% rename from src/main/java/ca/recrown/islandsurvivalcraft/utilities/floodfill/Flooder.java rename to src/main/java/ca/recrown/islandsurvivalcraft/utilities/drawing/Flooder.java index 14cb0f5..0ac081d 100644 --- a/src/main/java/ca/recrown/islandsurvivalcraft/utilities/floodfill/Flooder.java +++ b/src/main/java/ca/recrown/islandsurvivalcraft/utilities/drawing/Flooder.java @@ -1,21 +1,22 @@ -package ca.recrown.islandsurvivalcraft.utilities.floodfill; +package ca.recrown.islandsurvivalcraft.utilities.drawing; import java.util.HashSet; import java.util.LinkedList; import ca.recrown.islandsurvivalcraft.utilities.datatypes.Point2; +import ca.recrown.islandsurvivalcraft.utilities.pathfinding.CoordinateValidatable; public class Flooder { private final Point2 start; - private final Floodable floodable; + private final CoordinateValidatable floodable; - public Flooder(Point2 start, Floodable floodable) { + public Flooder(Point2 start, CoordinateValidatable floodable) { this.start = start; this.floodable = floodable; } - public Flooder(int xStart, int yStart, Floodable floodable) { + public Flooder(int xStart, int yStart, CoordinateValidatable floodable) { this(new Point2(xStart, yStart), floodable); } @@ -24,22 +25,22 @@ public class Flooder { LinkedList queue = new LinkedList<>(); if (!checked.add(start)) return; - if (!floodable.flood(start)) return; + if (!floodable.validate(start)) return; queue.add(start); while (!queue.isEmpty()) { Point2 p = queue.pop(); Point2 pE = new Point2(p.x + 1, p.y); - if (checked.add(pE) && floodable.flood(pE)) queue.add(pE); + if (checked.add(pE) && floodable.validate(pE)) queue.add(pE); Point2 pW = new Point2(p.x - 1, p.y); - if (checked.add(pW) && floodable.flood(pW)) queue.add(pW); + if (checked.add(pW) && floodable.validate(pW)) queue.add(pW); Point2 pN = new Point2(p.x, p.y + 1); - if (checked.add(pN) && floodable.flood(pN)) queue.add(pN); + if (checked.add(pN) && floodable.validate(pN)) queue.add(pN); Point2 pS = new Point2(p.x, p.y - 1); - if (checked.add(pS) && floodable.flood(pS)) queue.add(pS); + if (checked.add(pS) && floodable.validate(pS)) queue.add(pS); } } } \ No newline at end of file diff --git a/src/main/java/ca/recrown/islandsurvivalcraft/utilities/floodfill/Floodable.java b/src/main/java/ca/recrown/islandsurvivalcraft/utilities/floodfill/Floodable.java deleted file mode 100644 index d066f8d..0000000 --- a/src/main/java/ca/recrown/islandsurvivalcraft/utilities/floodfill/Floodable.java +++ /dev/null @@ -1,14 +0,0 @@ -package ca.recrown.islandsurvivalcraft.utilities.floodfill; - -import ca.recrown.islandsurvivalcraft.utilities.datatypes.Point2; - -public interface Floodable { - /** - * Called for every block. If it shouldn't be flooded, return false. - * Code to execute for flooding should be placed here. - * @param x the x coordinate. - * @param y the y coordinate. - * @return if this was a successful flooding. - */ - public boolean flood(Point2 point); -} \ No newline at end of file diff --git a/src/main/java/ca/recrown/islandsurvivalcraft/world/Information/IslandInformationManager.java b/src/main/java/ca/recrown/islandsurvivalcraft/world/Information/IslandInformationManager.java index 281c5eb..1111932 100644 --- a/src/main/java/ca/recrown/islandsurvivalcraft/world/Information/IslandInformationManager.java +++ b/src/main/java/ca/recrown/islandsurvivalcraft/world/Information/IslandInformationManager.java @@ -2,6 +2,11 @@ package ca.recrown.islandsurvivalcraft.world.Information; import java.util.HashSet; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.ThreadFactory; import ca.recrown.islandsurvivalcraft.utilities.GeneralUtilities; import ca.recrown.islandsurvivalcraft.utilities.datatypes.Point2; @@ -9,6 +14,15 @@ import ca.recrown.islandsurvivalcraft.world.IslandWorldMap; public class IslandInformationManager { private final IslandWorldMap islandMap; + private final ExecutorService executors = Executors.newFixedThreadPool(2, new ThreadFactory() { + @Override + public Thread newThread(Runnable r) { + Thread thread = new Thread(r); + thread.setName("ISC-Chunk-Info-Loader"); + return thread; + } + }); + private final ConcurrentHashMap> building = new ConcurrentHashMap<>(); private final ConcurrentHashMap> islandSets = new ConcurrentHashMap<>(); public IslandInformationManager(IslandWorldMap islandWorldMap) { @@ -23,7 +37,7 @@ public class IslandInformationManager { surrounding[3] = chunkCoords.shift(0, 1); HashSet checkedCoordinates = new HashSet<>(); for (Point2 chunk : surrounding) { - HashSet chunkIslandSet = getChunkIslandInformationSet(chunk); + HashSet chunkIslandSet = islandSets.computeIfAbsent(chunk, (p) -> new HashSet<>()); for (IslandInformation islandInformation : chunkIslandSet) { if (islandInformation.isIslandInChunk(chunkCoords)) { checkedCoordinates.addAll(islandInformation.getIslandCoordinates()); @@ -31,7 +45,6 @@ public class IslandInformationManager { } } - for (int x = 0; x < GeneralUtilities.CHUNK_SIZE; x++) { for (int z = 0; z < GeneralUtilities.CHUNK_SIZE; z++) { int worldX = chunkCoords.x * GeneralUtilities.CHUNK_SIZE + x; @@ -39,7 +52,8 @@ public class IslandInformationManager { if (islandMap.isIsland(worldX, worldZ)) { IslandInformationBuilder infoBuilder = new IslandInformationBuilder(); Point2 origin = islandMap.findIslandOrigin(worldX, worldZ, (p) -> { - if (!checkedCoordinates.add(p)) return true; + if (!checkedCoordinates.add(p)) + return true; infoBuilder.addCoordinate(p, islandMap.isEdgeOfIsland(p.x, p.y)); infoBuilder.addChunk(GeneralUtilities.worldToChunkCoordinates(p)); return false; @@ -47,25 +61,41 @@ public class IslandInformationManager { if (origin != null) { IslandInformation islandInfo = infoBuilder.build(origin); for (Point2 chunk : infoBuilder.getChunksUsed()) { - getChunkIslandInformationSet(chunk).add(islandInfo); + islandSets.computeIfAbsent(chunk, (p) -> new HashSet<>()).add(islandInfo); } } } } } } - - public void unloadChunkIslandInformation(Point2 chunkCoords) { - islandSets.remove(chunkCoords); + + public void loadChunkIslandInformationAsync(Point2 chunkCoords) { + building.put(chunkCoords, executors.submit(() -> { + loadChunkIslandInformation(chunkCoords); + return null; + })); } - public HashSet getChunkIslandInformationSet(Point2 chunkCoords) { - if (!islandSets.containsKey(chunkCoords)) islandSets.put(chunkCoords, new HashSet<>()); + public void unloadChunkIslandInformation(Point2 chunkCoords) { + islandSets.remove(chunkCoords); + building.remove(chunkCoords); + } + + public HashSet getChunkIslandInformationSet(Point2 chunkCoords, boolean checkBuilding) { + if (!islandSets.containsKey(chunkCoords)) { + if (checkBuilding && building.containsKey(chunkCoords)) { + try { + building.get(chunkCoords).get(); + } catch (InterruptedException | ExecutionException e) { + } + } + } + building.remove(chunkCoords); return islandSets.get(chunkCoords); } public IslandInformation getIslandInformation(Point2 coords) { - HashSet chunkIslandSet = getChunkIslandInformationSet(GeneralUtilities.worldToChunkCoordinates(coords)); + HashSet chunkIslandSet = getChunkIslandInformationSet(GeneralUtilities.worldToChunkCoordinates(coords), true); for (IslandInformation islandInformation : chunkIslandSet) { if (islandInformation.isWithinIsland(coords)) return islandInformation; } diff --git a/src/main/java/ca/recrown/islandsurvivalcraft/world/WorldInfoManager.java b/src/main/java/ca/recrown/islandsurvivalcraft/world/WorldInfoManager.java index 9c50226..97c046b 100644 --- a/src/main/java/ca/recrown/islandsurvivalcraft/world/WorldInfoManager.java +++ b/src/main/java/ca/recrown/islandsurvivalcraft/world/WorldInfoManager.java @@ -18,6 +18,8 @@ import ca.recrown.islandsurvivalcraft.world.generation.chunks.IslandWorldChunkGe public class WorldInfoManager implements Listener { public final ConcurrentHashMap worldInformation = new ConcurrentHashMap<>(); + public WorldInfoManager() { + } /** * Return the world info requested for the given world. * Should not be null. @@ -50,7 +52,7 @@ public class WorldInfoManager implements Listener { if (!worldInformation.containsKey(event.getWorld().getName())) return; WorldInfo worldInfo = retrieve(event.getWorld().getName()); if (!worldInfo.isInitialized()) worldInfo.initialize(event.getWorld()); - worldInfo.getIslandInfoManager().loadChunkIslandInformation(new Point2(chunk.getX(), chunk.getZ())); + worldInfo.getIslandInfoManager().loadChunkIslandInformationAsync(new Point2(chunk.getX(), chunk.getZ())); } @EventHandler(priority = EventPriority.MONITOR) diff --git a/src/main/java/ca/recrown/islandsurvivalcraft/world/generation/biomes/UniqueBiomeGenerator.java b/src/main/java/ca/recrown/islandsurvivalcraft/world/generation/biomes/UniqueBiomeGenerator.java index a84dd77..0a2d24d 100644 --- a/src/main/java/ca/recrown/islandsurvivalcraft/world/generation/biomes/UniqueBiomeGenerator.java +++ b/src/main/java/ca/recrown/islandsurvivalcraft/world/generation/biomes/UniqueBiomeGenerator.java @@ -5,8 +5,7 @@ import ca.recrown.islandsurvivalcraft.utilities.biomes.BiomeInfo; import ca.recrown.islandsurvivalcraft.utilities.caching.Cache; import ca.recrown.islandsurvivalcraft.utilities.datatypes.Point2; import ca.recrown.islandsurvivalcraft.utilities.datatypes.Reference; -import ca.recrown.islandsurvivalcraft.utilities.floodfill.Floodable; -import ca.recrown.islandsurvivalcraft.utilities.floodfill.Flooder; +import ca.recrown.islandsurvivalcraft.utilities.drawing.Flooder; import ca.recrown.islandsurvivalcraft.utilities.pathfinding.CoordinateValidatable; import ca.recrown.islandsurvivalcraft.world.BiomeMap; import ca.recrown.islandsurvivalcraft.world.IslandWorldMap; @@ -46,9 +45,9 @@ public class UniqueBiomeGenerator implements BiomeGenerator { if (currentIslandInfo.value == null) { currentIslandInfo.value = biomeSelector.getLandBiomeInfo(temperature, islandOrigin.x, islandOrigin.y); } - Flooder flooder = new Flooder(worldCoords, new Floodable() { + Flooder flooder = new Flooder(worldCoords, new CoordinateValidatable() { @Override - public boolean flood(Point2 point) { + public boolean validate(Point2 point) { Point2 chunkCoords = GeneralUtilities.worldToChunkCoordinates(point); if (chunkCoords.x != chunkX || chunkCoords.y != chunkZ || !islandMap.isIsland(point.x, point.y)) return false; biomeCache.set(point, currentIslandInfo.value); diff --git a/src/test/java/ca/recrown/islandsurvivalcraft/utilities/floodfill/FloodFillTest.java b/src/test/java/ca/recrown/islandsurvivalcraft/utilities/drawing/FloodFillTest.java similarity index 93% rename from src/test/java/ca/recrown/islandsurvivalcraft/utilities/floodfill/FloodFillTest.java rename to src/test/java/ca/recrown/islandsurvivalcraft/utilities/drawing/FloodFillTest.java index 11bb882..a6bb421 100644 --- a/src/test/java/ca/recrown/islandsurvivalcraft/utilities/floodfill/FloodFillTest.java +++ b/src/test/java/ca/recrown/islandsurvivalcraft/utilities/drawing/FloodFillTest.java @@ -1,4 +1,4 @@ -package ca.recrown.islandsurvivalcraft.utilities.floodfill; +package ca.recrown.islandsurvivalcraft.utilities.drawing; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -14,14 +14,14 @@ import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestInstance.Lifecycle; import ca.recrown.islandsurvivalcraft.utilities.datatypes.Point2; -import ca.recrown.islandsurvivalcraft.utilities.floodfill.Floodable; +import ca.recrown.islandsurvivalcraft.utilities.pathfinding.CoordinateValidatable; @TestInstance(Lifecycle.PER_CLASS) public class FloodFillTest { public byte[][] mapA; public byte[][] mapB; - private class Flood implements Floodable { + private class Flood implements CoordinateValidatable { private final byte[][] map; public Flood(byte[][] map) { @@ -29,7 +29,7 @@ public class FloodFillTest { } @Override - public boolean flood(Point2 point) { + public boolean validate(Point2 point) { try { if (map[point.y][point.x] < 1) return false; } catch (IndexOutOfBoundsException e) {