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());
} else if (args[0].toLowerCase().equals("size")) {
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 {
return false;
}

View File

@ -103,10 +103,9 @@ public class VariedItemManager implements Listener {
@EventHandler(priority = EventPriority.MONITOR)
public void onEntityPickup(EntityPickupItemEvent e) {
ItemStack item = e.getItem().getItemStack();
System.out.println("PICKUP");
VariedItem variedItem = getVariedItemFromItemStack(item);
if (variedItem != null) {
if (e.getEntityType() == EntityType.PLAYER) {
if (e.getEntityType() == EntityType.PLAYER) {
VariedItem variedItem = getVariedItemFromItemStack(item);
if (variedItem != null) {
Player player = (Player) e.getEntity();
if (player.getInventory().getHeldItemSlot() == player.getInventory().firstEmpty()) {
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 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) {
data = new ConcurrentHashMap<>(maxSize + 1, 0.75f, 16);
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() {
this(1024);
this(-1);
}
/**
@ -35,7 +43,7 @@ public class Cache<K, V> {
CacheValue<K, V> val = new CacheValue<>(key, value);
data.put(key, val);
usage.add(val);
if (data.size() > maxSize) {
if (maxSize > 0 && data.size() > maxSize) {
data.remove(usage.pop().key);
}
} finally {
@ -58,6 +66,34 @@ public class Cache<K, V> {
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.
*/

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() {
CacheValue<K, V> cacheValue;
lock.lock();
@ -103,14 +113,7 @@ class CacheUsageStack<K, V> {
return size;
}
@Override
public String toString() {
StringBuilder stringBulder = new StringBuilder();
CacheValue<K, V> current = first;
while (current != null) {
stringBulder.append(first + "\n");
current = current.back;
}
return stringBulder.toString();
public CacheValue<K, V> getTop() {
return first;
}
}

View File

@ -1,36 +1,31 @@
package ca.recrown.islandsurvivalcraft.world.Information;
import java.lang.Thread.State;
import java.util.HashSet;
import java.util.concurrent.CompletableFuture;
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.caching.Cache;
import ca.recrown.islandsurvivalcraft.utilities.datatypes.Point2;
import ca.recrown.islandsurvivalcraft.world.IslandWorldMap;
public class IslandInformationManager {
public class IslandInformationManager implements Runnable {
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<HashSet<IslandInformation>>> building = new ConcurrentHashMap<>();
private final Thread loader;
private final Cache<Point2, CompletableFuture<HashSet<IslandInformation>>> buildingCache = new Cache<>();
private final ConcurrentHashMap<Point2, HashSet<IslandInformation>> islandSets = new ConcurrentHashMap<>();
public IslandInformationManager(IslandWorldMap 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<>();
Point2[] surroundings = new Point2[4];
surroundings[0] = chunkCoords.shift(1, 0);
@ -39,7 +34,8 @@ public class IslandInformationManager {
surroundings[3] = chunkCoords.shift(0, 1);
HashSet<Point2> checkedCoordinates = new HashSet<>();
for (Point2 surrounding : surroundings) {
if (Thread.currentThread().isInterrupted()) return null;
if (Thread.currentThread().isInterrupted())
return null;
HashSet<IslandInformation> chunkIslandSet = islandSets.get(surrounding);
if (chunkIslandSet != null) {
for (IslandInformation islandInformation : chunkIslandSet) {
@ -53,15 +49,28 @@ public class IslandInformationManager {
for (int x = 0; x < GeneralUtilities.CHUNK_SIZE; x++) {
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 worldZ = chunkCoords.y * GeneralUtilities.CHUNK_SIZE + z;
if (islandMap.isIsland(worldX, worldZ)) {
IslandInformationBuilder infoBuilder = new IslandInformationBuilder();
Point2 origin = islandMap.findIslandOrigin(worldX, worldZ, (p) -> {
if (Thread.currentThread().isInterrupted()) return true;
if (Thread.currentThread().isInterrupted())
return true;
if (!checkedCoordinates.add(p))
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.addChunk(GeneralUtilities.worldToChunkCoordinates(p));
return false;
@ -76,49 +85,67 @@ public class IslandInformationManager {
return result;
}
public void loadChunkIslandInformationAsync(Point2 chunkCoords) {
public void loadChunkIslandInformation(Point2 chunkCoords) {
CompletableFuture<HashSet<IslandInformation>> completableFuture = new CompletableFuture<>();
executors.execute(() -> {
completableFuture.complete(loadChunkIslandInformation(chunkCoords));
});
building.put(chunkCoords, completableFuture);
completableFuture.thenAccept((p) -> {
if (Thread.currentThread().isInterrupted()) return;
islandSets.put(chunkCoords, p);
building.remove(chunkCoords);
});
buildingCache.set(chunkCoords, completableFuture);
if (loader.getState() == State.WAITING) {
synchronized (loader) {
loader.notifyAll();
}
}
}
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);
}
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.
*
* @param coords The coordinates to check for island information.
* @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) {
HashSet<IslandInformation> chunkIslandSet = getChunkIslandInformationSet(GeneralUtilities.worldToChunkCoordinates(coords), checkLoading);
if (chunkIslandSet != null) {
for (IslandInformation islandInformation : chunkIslandSet) {
if (islandInformation.isWithinIsland(coords)) return islandInformation;
Point2 chunkCoords = GeneralUtilities.worldToChunkCoordinates(coords);
if (!islandSets.containsKey(chunkCoords)) {
if (checkLoading && buildingCache.contains(chunkCoords)) {
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;
}
@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;
WorldInfo worldInfo = retrieve(event.getWorld().getName());
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)