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:
parent
a032f007c1
commit
df605880b7
@ -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;
|
||||
}
|
||||
|
@ -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");
|
||||
if (e.getEntityType() == EntityType.PLAYER) {
|
||||
VariedItem variedItem = getVariedItemFromItemStack(item);
|
||||
if (variedItem != null) {
|
||||
if (e.getEntityType() == EntityType.PLAYER) {
|
||||
Player player = (Player) e.getEntity();
|
||||
if (player.getInventory().getHeldItemSlot() == player.getInventory().firstEmpty()) {
|
||||
variedItem.onItemHeld(item, player);
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user