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());
|
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;
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -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.
|
||||||
*/
|
*/
|
||||||
|
@ -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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user