Optimized the world island information building.

Island information building is now priority based.

Island information building uses distanced previously built information.

Changed thread safe cache to be more multi-purpose.
This commit is contained in:
Harrison Deng 2020-05-17 18:08:01 -05:00
parent a032f007c1
commit df605880b7
6 changed files with 134 additions and 64 deletions

View File

@ -58,7 +58,12 @@ public class IslandCommand implements CommandRunnable {
sender.sendMessage("Current island's origin point: " + islandSurvivalCraft.getWorldInfoManager().retrieve(player.getWorld().getName()).getIslandInfoManager().getIslandInformation(playerLoc, true).getIslandOrigin()); sender.sendMessage("Current island's origin point: " + islandSurvivalCraft.getWorldInfoManager().retrieve(player.getWorld().getName()).getIslandInfoManager().getIslandInformation(playerLoc, true).getIslandOrigin());
} else if (args[0].toLowerCase().equals("size")) { } else if (args[0].toLowerCase().equals("size")) {
Point2 playerLoc = new Point2(player.getLocation().getBlockX(), player.getLocation().getBlockZ()); Point2 playerLoc = new Point2(player.getLocation().getBlockX(), player.getLocation().getBlockZ());
sender.sendMessage("Current island's raw size in blocks: " + islandSurvivalCraft.getWorldInfoManager().retrieve(player.getWorld().getName()).getIslandInfoManager().getIslandInformation(playerLoc, true).getIslandSize()); IslandInformation info = islandSurvivalCraft.getWorldInfoManager().retrieve(player.getWorld().getName()).getIslandInfoManager().getIslandInformation(playerLoc, true);
if (info != null) {
sender.sendMessage("Current island's raw size in blocks: " + info.getIslandSize());
} else {
sender.sendMessage("Current location is not an island!");
}
} else { } else {
return false; return false;
} }

View File

@ -103,10 +103,9 @@ public class VariedItemManager implements Listener {
@EventHandler(priority = EventPriority.MONITOR) @EventHandler(priority = EventPriority.MONITOR)
public void onEntityPickup(EntityPickupItemEvent e) { public void onEntityPickup(EntityPickupItemEvent e) {
ItemStack item = e.getItem().getItemStack(); ItemStack item = e.getItem().getItemStack();
System.out.println("PICKUP"); if (e.getEntityType() == EntityType.PLAYER) {
VariedItem variedItem = getVariedItemFromItemStack(item); VariedItem variedItem = getVariedItemFromItemStack(item);
if (variedItem != null) { if (variedItem != null) {
if (e.getEntityType() == EntityType.PLAYER) {
Player player = (Player) e.getEntity(); Player player = (Player) e.getEntity();
if (player.getInventory().getHeldItemSlot() == player.getInventory().firstEmpty()) { if (player.getInventory().getHeldItemSlot() == player.getInventory().firstEmpty()) {
variedItem.onItemHeld(item, player); variedItem.onItemHeld(item, player);

View File

@ -9,13 +9,21 @@ public class Cache<K, V> {
private final CacheUsageStack<K, V> usage = new CacheUsageStack<>(); private final CacheUsageStack<K, V> usage = new CacheUsageStack<>();
private final ReentrantLock lock = new ReentrantLock(); private final ReentrantLock lock = new ReentrantLock();
/**
* Creates a thread safe cache.
* @param maxSize If this is greater than 0, this cache will operate as an LRU cache. Otherwise, the user will need to remove objects manually.
*/
public Cache(int maxSize) { public Cache(int maxSize) {
data = new ConcurrentHashMap<>(maxSize + 1, 0.75f, 16); data = new ConcurrentHashMap<>(maxSize + 1, 0.75f, 16);
this.maxSize = maxSize; this.maxSize = maxSize;
} }
/**
* Creates a thread safe cache.
* This creates the cache without a limit, and therefore, does not operate as a LSU.
*/
public Cache() { public Cache() {
this(1024); this(-1);
} }
/** /**
@ -35,7 +43,7 @@ public class Cache<K, V> {
CacheValue<K, V> val = new CacheValue<>(key, value); CacheValue<K, V> val = new CacheValue<>(key, value);
data.put(key, val); data.put(key, val);
usage.add(val); usage.add(val);
if (data.size() > maxSize) { if (maxSize > 0 && data.size() > maxSize) {
data.remove(usage.pop().key); data.remove(usage.pop().key);
} }
} finally { } finally {
@ -58,6 +66,34 @@ public class Cache<K, V> {
return cacheValue.value; return cacheValue.value;
} }
/**
* Removes the associated key and value pair.
* @param key The associated key.
* @return The associated value.
*/
public V remove(K key) {
CacheValue<K, V> cacheValue = data.remove(key);
if (cacheValue == null) return null;
usage.remove(cacheValue);
return cacheValue.value;
}
public int size() {
return data.size();
}
public boolean contains(K key) {
return data.containsKey(key);
}
public V getMostRecent() {
return usage.getTop().value;
}
public K getMostRecentKey() {
return usage.getTop().key;
}
/** /**
* Clears the cache of all values. * Clears the cache of all values.
*/ */

View File

@ -75,6 +75,16 @@ class CacheUsageStack<K, V> {
} }
} }
public void remove(CacheValue<K, V> value) {
lock.lock();
try {
removeValueFromStack(value);
value.detached = true;
} finally {
lock.unlock();
}
}
public CacheValue<K, V> pop() { public CacheValue<K, V> pop() {
CacheValue<K, V> cacheValue; CacheValue<K, V> cacheValue;
lock.lock(); lock.lock();
@ -103,14 +113,7 @@ class CacheUsageStack<K, V> {
return size; return size;
} }
@Override public CacheValue<K, V> getTop() {
public String toString() { return first;
StringBuilder stringBulder = new StringBuilder();
CacheValue<K, V> current = first;
while (current != null) {
stringBulder.append(first + "\n");
current = current.back;
}
return stringBulder.toString();
} }
} }

View File

@ -1,36 +1,31 @@
package ca.recrown.islandsurvivalcraft.world.Information; package ca.recrown.islandsurvivalcraft.world.Information;
import java.lang.Thread.State;
import java.util.HashSet; import java.util.HashSet;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException; 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.GeneralUtilities;
import ca.recrown.islandsurvivalcraft.utilities.caching.Cache;
import ca.recrown.islandsurvivalcraft.utilities.datatypes.Point2; import ca.recrown.islandsurvivalcraft.utilities.datatypes.Point2;
import ca.recrown.islandsurvivalcraft.world.IslandWorldMap; import ca.recrown.islandsurvivalcraft.world.IslandWorldMap;
public class IslandInformationManager { public class IslandInformationManager implements Runnable {
private final IslandWorldMap islandMap; private final IslandWorldMap islandMap;
private final ExecutorService executors = Executors.newFixedThreadPool(2, new ThreadFactory() { private final Thread loader;
@Override private final Cache<Point2, CompletableFuture<HashSet<IslandInformation>>> buildingCache = new Cache<>();
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setName("ISC-Chunk-Info-Loader");
return thread;
}
});
private final ConcurrentHashMap<Point2, Future<HashSet<IslandInformation>>> building = new ConcurrentHashMap<>();
private final ConcurrentHashMap<Point2, HashSet<IslandInformation>> islandSets = new ConcurrentHashMap<>(); private final ConcurrentHashMap<Point2, HashSet<IslandInformation>> islandSets = new ConcurrentHashMap<>();
public IslandInformationManager(IslandWorldMap islandWorldMap) { public IslandInformationManager(IslandWorldMap islandWorldMap) {
this.islandMap = islandWorldMap; this.islandMap = islandWorldMap;
loader = new Thread(this);
loader.setDaemon(true);
loader.setName("Island-Info-Builder");
loader.setPriority(Thread.MIN_PRIORITY);
loader.start();
} }
public HashSet<IslandInformation> loadChunkIslandInformation(Point2 chunkCoords) { private HashSet<IslandInformation> buildChunkIslandInformation(Point2 chunkCoords) {
HashSet<IslandInformation> result = new HashSet<>(); HashSet<IslandInformation> result = new HashSet<>();
Point2[] surroundings = new Point2[4]; Point2[] surroundings = new Point2[4];
surroundings[0] = chunkCoords.shift(1, 0); surroundings[0] = chunkCoords.shift(1, 0);
@ -39,7 +34,8 @@ public class IslandInformationManager {
surroundings[3] = chunkCoords.shift(0, 1); surroundings[3] = chunkCoords.shift(0, 1);
HashSet<Point2> checkedCoordinates = new HashSet<>(); HashSet<Point2> checkedCoordinates = new HashSet<>();
for (Point2 surrounding : surroundings) { for (Point2 surrounding : surroundings) {
if (Thread.currentThread().isInterrupted()) return null; if (Thread.currentThread().isInterrupted())
return null;
HashSet<IslandInformation> chunkIslandSet = islandSets.get(surrounding); HashSet<IslandInformation> chunkIslandSet = islandSets.get(surrounding);
if (chunkIslandSet != null) { if (chunkIslandSet != null) {
for (IslandInformation islandInformation : chunkIslandSet) { for (IslandInformation islandInformation : chunkIslandSet) {
@ -53,15 +49,28 @@ public class IslandInformationManager {
for (int x = 0; x < GeneralUtilities.CHUNK_SIZE; x++) { for (int x = 0; x < GeneralUtilities.CHUNK_SIZE; x++) {
for (int z = 0; z < GeneralUtilities.CHUNK_SIZE; z++) { for (int z = 0; z < GeneralUtilities.CHUNK_SIZE; z++) {
if (Thread.currentThread().isInterrupted()) return null; if (Thread.currentThread().isInterrupted())
return null;
int worldX = chunkCoords.x * GeneralUtilities.CHUNK_SIZE + x; int worldX = chunkCoords.x * GeneralUtilities.CHUNK_SIZE + x;
int worldZ = chunkCoords.y * GeneralUtilities.CHUNK_SIZE + z; int worldZ = chunkCoords.y * GeneralUtilities.CHUNK_SIZE + z;
if (islandMap.isIsland(worldX, worldZ)) { if (islandMap.isIsland(worldX, worldZ)) {
IslandInformationBuilder infoBuilder = new IslandInformationBuilder(); IslandInformationBuilder infoBuilder = new IslandInformationBuilder();
Point2 origin = islandMap.findIslandOrigin(worldX, worldZ, (p) -> { Point2 origin = islandMap.findIslandOrigin(worldX, worldZ, (p) -> {
if (Thread.currentThread().isInterrupted()) return true; if (Thread.currentThread().isInterrupted())
return true;
if (!checkedCoordinates.add(p)) if (!checkedCoordinates.add(p))
return true; return true;
HashSet<IslandInformation> chunkIslandSet = islandSets.get(GeneralUtilities.worldToChunkCoordinates(p));
if (chunkIslandSet != null) {
for (IslandInformation islandInformation : chunkIslandSet) {
if (islandInformation.isIslandInChunk(chunkCoords)) {
checkedCoordinates.addAll(islandInformation.getIslandCoordinates());
result.add(islandInformation);
return true;
}
}
}
infoBuilder.addCoordinate(p, islandMap.isEdgeOfIsland(p.x, p.y)); infoBuilder.addCoordinate(p, islandMap.isEdgeOfIsland(p.x, p.y));
infoBuilder.addChunk(GeneralUtilities.worldToChunkCoordinates(p)); infoBuilder.addChunk(GeneralUtilities.worldToChunkCoordinates(p));
return false; return false;
@ -76,49 +85,67 @@ public class IslandInformationManager {
return result; return result;
} }
public void loadChunkIslandInformationAsync(Point2 chunkCoords) { public void loadChunkIslandInformation(Point2 chunkCoords) {
CompletableFuture<HashSet<IslandInformation>> completableFuture = new CompletableFuture<>(); CompletableFuture<HashSet<IslandInformation>> completableFuture = new CompletableFuture<>();
executors.execute(() -> { buildingCache.set(chunkCoords, completableFuture);
completableFuture.complete(loadChunkIslandInformation(chunkCoords)); if (loader.getState() == State.WAITING) {
}); synchronized (loader) {
building.put(chunkCoords, completableFuture); loader.notifyAll();
completableFuture.thenAccept((p) -> { }
if (Thread.currentThread().isInterrupted()) return; }
islandSets.put(chunkCoords, p);
building.remove(chunkCoords);
});
} }
public void unloadChunkIslandInformation(Point2 chunkCoords) { public void unloadChunkIslandInformation(Point2 chunkCoords) {
if (building.containsKey(chunkCoords)) building.remove(chunkCoords).cancel(true); if (buildingCache.contains(chunkCoords))
buildingCache.remove(chunkCoords).cancel(true);
islandSets.remove(chunkCoords); islandSets.remove(chunkCoords);
} }
private HashSet<IslandInformation> getChunkIslandInformationSet(Point2 chunkCoords, boolean checkLoading) {
if (!islandSets.containsKey(chunkCoords)) {
if (checkLoading && building.containsKey(chunkCoords)) {
try {
building.get(chunkCoords).get();
} catch (InterruptedException | ExecutionException e) {
}
}
}
return islandSets.get(chunkCoords);
}
/** /**
* Gets the island information for the given coordinates. * Gets the island information for the given coordinates.
*
* @param coords The coordinates to check for island information. * @param coords The coordinates to check for island information.
* @param checkLoading Whether or not to check for the currently loading chunks. * @param checkLoading Whether or not to check for the currently loading chunks.
* @return The island information or null if there is no island at those coordinates. * @return The island information or null if there is no island at those
* coordinates.
*/ */
public IslandInformation getIslandInformation(Point2 coords, boolean checkLoading) { public IslandInformation getIslandInformation(Point2 coords, boolean checkLoading) {
HashSet<IslandInformation> chunkIslandSet = getChunkIslandInformationSet(GeneralUtilities.worldToChunkCoordinates(coords), checkLoading); Point2 chunkCoords = GeneralUtilities.worldToChunkCoordinates(coords);
if (chunkIslandSet != null) { if (!islandSets.containsKey(chunkCoords)) {
for (IslandInformation islandInformation : chunkIslandSet) { if (checkLoading && buildingCache.contains(chunkCoords)) {
if (islandInformation.isWithinIsland(coords)) return islandInformation; try {
buildingCache.get(chunkCoords).get();
} catch (InterruptedException | ExecutionException e) {
}
}
}
HashSet<IslandInformation> chunkIslands = islandSets.get(chunkCoords);
if (chunkIslands != null) {
for (IslandInformation islandInformation : chunkIslands) {
if (islandInformation.isWithinIsland(coords))
return islandInformation;
} }
} }
return null; return null;
} }
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
while (buildingCache.size() > 0) {
Point2 chunkCoords = buildingCache.getMostRecentKey();
CompletableFuture<HashSet<IslandInformation>> future = buildingCache.remove(chunkCoords);
HashSet<IslandInformation> islandInformations = buildChunkIslandInformation(chunkCoords);
islandSets.put(chunkCoords, islandInformations);
future.complete(islandInformations);
}
try {
synchronized (loader) {
loader.wait();
}
} catch (InterruptedException e) {
}
}
}
} }

View File

@ -52,7 +52,7 @@ public class WorldInfoManager implements Listener {
if (!worldInformation.containsKey(event.getWorld().getName())) return; if (!worldInformation.containsKey(event.getWorld().getName())) return;
WorldInfo worldInfo = retrieve(event.getWorld().getName()); WorldInfo worldInfo = retrieve(event.getWorld().getName());
if (!worldInfo.isInitialized()) worldInfo.initialize(event.getWorld()); if (!worldInfo.isInitialized()) worldInfo.initialize(event.getWorld());
worldInfo.getIslandInfoManager().loadChunkIslandInformationAsync(new Point2(chunk.getX(), chunk.getZ())); worldInfo.getIslandInfoManager().loadChunkIslandInformation(new Point2(chunk.getX(), chunk.getZ()));
} }
@EventHandler(priority = EventPriority.MONITOR) @EventHandler(priority = EventPriority.MONITOR)