Changed island information creation to be async.

Flooder no longer uses it's own interface for flooding.
This commit is contained in:
Harrison Deng 2020-05-14 15:07:46 -05:00
parent 78f0a846da
commit ea4ba47427
9 changed files with 142 additions and 70 deletions

View File

@ -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()),
;

View File

@ -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<Player> playersHighlighting = new HashSet<>();
LinkedList<Player> 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<Point2> islandBorder = islandInfo.getEdgeCoordinates();
Iterator<Point2> 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<Point2> 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 <start | stop> 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 <start | stop> argument is required to dicate whether or not to start the highlighting or stop it.";
}
@Override

View File

@ -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());
}
}

View File

@ -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<Point2> 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);
}
}
}

View File

@ -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);
}

View File

@ -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<Point2, Future<Void>> building = new ConcurrentHashMap<>();
private final ConcurrentHashMap<Point2, HashSet<IslandInformation>> islandSets = new ConcurrentHashMap<>();
public IslandInformationManager(IslandWorldMap islandWorldMap) {
@ -23,7 +37,7 @@ public class IslandInformationManager {
surrounding[3] = chunkCoords.shift(0, 1);
HashSet<Point2> checkedCoordinates = new HashSet<>();
for (Point2 chunk : surrounding) {
HashSet<IslandInformation> chunkIslandSet = getChunkIslandInformationSet(chunk);
HashSet<IslandInformation> 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<IslandInformation> 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<IslandInformation> 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<IslandInformation> chunkIslandSet = getChunkIslandInformationSet(GeneralUtilities.worldToChunkCoordinates(coords));
HashSet<IslandInformation> chunkIslandSet = getChunkIslandInformationSet(GeneralUtilities.worldToChunkCoordinates(coords), true);
for (IslandInformation islandInformation : chunkIslandSet) {
if (islandInformation.isWithinIsland(coords)) return islandInformation;
}

View File

@ -18,6 +18,8 @@ import ca.recrown.islandsurvivalcraft.world.generation.chunks.IslandWorldChunkGe
public class WorldInfoManager implements Listener {
public final ConcurrentHashMap<String, WorldInfo> 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)

View File

@ -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);

View File

@ -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) {