diff --git a/src/main/java/electrosphere/client/terrain/cache/ClientTerrainCache.java b/src/main/java/electrosphere/client/terrain/cache/ClientTerrainCache.java index 21ffcda9..0536d585 100644 --- a/src/main/java/electrosphere/client/terrain/cache/ClientTerrainCache.java +++ b/src/main/java/electrosphere/client/terrain/cache/ClientTerrainCache.java @@ -5,6 +5,7 @@ import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.Semaphore; import org.joml.Vector3i; @@ -50,6 +51,11 @@ public class ClientTerrainCache { * A map of chunk to its world position */ Map chunkPositionMap = new ConcurrentHashMap(); + + /** + * The lock on the terrain cache + */ + Semaphore lock = new Semaphore(1); /** * Constructor @@ -67,6 +73,7 @@ public class ClientTerrainCache { * @param chunkData The chunk data to add at the specified positions */ public void addChunkDataToCache(int worldX, int worldY, int worldZ, ChunkData chunkData){ + lock.acquireUninterruptibly(); Map cache = this.getCache(chunkData.getStride()); cache.put(getKey(worldX,worldY,worldZ),chunkData); chunkPositionMap.put(chunkData,new Vector3i(worldX,worldY,worldZ)); @@ -74,16 +81,19 @@ public class ClientTerrainCache { Long currentChunk = cacheList.remove(0); cache.remove(currentChunk); } + lock.release(); } /** * Evicts all chunks from the cache */ public void evictAll(){ + lock.acquireUninterruptibly(); this.cacheList.clear(); this.cacheMapFullRes.clear(); this.cacheMapHalfRes.clear(); this.chunkPositionMap.clear(); + lock.release(); } @@ -107,7 +117,10 @@ public class ClientTerrainCache { * @return True if the cache contains chunk data at the specified point, false otherwise */ public boolean containsChunkDataAtWorldPoint(int worldX, int worldY, int worldZ, int stride){ - return this.getCache(stride).containsKey(getKey(worldX,worldY,worldZ)); + lock.acquireUninterruptibly(); + boolean rVal = this.getCache(stride).containsKey(getKey(worldX,worldY,worldZ)); + lock.release(); + return rVal; } @@ -123,7 +136,10 @@ public class ClientTerrainCache { * @return The chunk data if it exists, null otherwise */ public ChunkData getSubChunkDataAtPoint(int worldX, int worldY, int worldZ, int stride){ - return this.getCache(stride).get(getKey(worldX,worldY,worldZ)); + lock.acquireUninterruptibly(); + ChunkData rVal = this.getCache(stride).get(getKey(worldX,worldY,worldZ)); + lock.release(); + return rVal; } /** diff --git a/src/main/java/electrosphere/client/terrain/cells/ClientDrawCellManager.java b/src/main/java/electrosphere/client/terrain/cells/ClientDrawCellManager.java index 9ee876be..10f93fb2 100644 --- a/src/main/java/electrosphere/client/terrain/cells/ClientDrawCellManager.java +++ b/src/main/java/electrosphere/client/terrain/cells/ClientDrawCellManager.java @@ -602,8 +602,9 @@ public class ClientDrawCellManager { /** * Requests all chunks for a given draw cell * @param cell The cell + * @return true if all cells were successfully requested, false otherwise */ - private void requestChunks(WorldOctTree.FloatingChunkTreeNode node, List highResFaces){ + private boolean requestChunks(WorldOctTree.FloatingChunkTreeNode node, List highResFaces){ DrawCell cell = node.getData(); int lod = this.chunkTree.getMaxLevel() - node.getLevel(); int spacingFactor = (int)Math.pow(2,lod); @@ -622,7 +623,9 @@ public class ClientDrawCellManager { ){ //client should request chunk data from server for each chunk necessary to create the model LoggerInterface.loggerNetworking.DEBUG("(Client) Send Request for terrain at " + posToCheck); - Globals.clientTerrainManager.requestChunk(posToCheck.x, posToCheck.y, posToCheck.z, lod); + if(!Globals.clientTerrainManager.requestChunk(posToCheck.x, posToCheck.y, posToCheck.z, lod)){ + return false; + } } } } @@ -666,12 +669,15 @@ public class ClientDrawCellManager { !Globals.clientTerrainManager.containsChunkDataAtWorldPoint(posToCheck.x, posToCheck.y, posToCheck.z, highResLod) ){ LoggerInterface.loggerNetworking.DEBUG("(Client) Send Request for terrain at " + posToCheck); - Globals.clientTerrainManager.requestChunk(posToCheck.x, posToCheck.y, posToCheck.z, highResLod); + if(!Globals.clientTerrainManager.requestChunk(posToCheck.x, posToCheck.y, posToCheck.z, highResLod)){ + return false; + } } } } } } + return true; } /** diff --git a/src/main/java/electrosphere/client/terrain/manager/ClientTerrainManager.java b/src/main/java/electrosphere/client/terrain/manager/ClientTerrainManager.java index 8bdd02a7..5a3af2c1 100644 --- a/src/main/java/electrosphere/client/terrain/manager/ClientTerrainManager.java +++ b/src/main/java/electrosphere/client/terrain/manager/ClientTerrainManager.java @@ -6,7 +6,9 @@ import java.nio.IntBuffer; import java.util.Collection; import java.util.LinkedList; import java.util.List; +import java.util.Map; import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.Semaphore; @@ -25,6 +27,7 @@ import electrosphere.renderer.meshgen.TerrainChunkModelGeneration; import electrosphere.renderer.model.Model; import electrosphere.server.terrain.manager.ServerTerrainChunk; import electrosphere.server.terrain.manager.ServerTerrainManager; +import io.github.studiorailgun.HashUtils; /** * Manages terrain storage and access on the client @@ -58,6 +61,11 @@ public class ClientTerrainManager { //The queue of terrain chunk data to be buffered to gpu static List terrainChunkGenerationQueue = new CopyOnWriteArrayList(); + + /** + * Tracks what outgoing requests are currently active + */ + Map requestedMap = new ConcurrentHashMap(); /** * Constructor @@ -134,6 +142,8 @@ public class ClientTerrainManager { message.getworldX(), message.getworldY(), message.getworldZ(), data ); + //remove from request map + this.requestedMap.remove(this.getRequestKey(message.getworldX(), message.getworldY(), message.getworldZ(), message.getchunkResolution())); } break; default: LoggerInterface.loggerEngine.WARNING("ClientTerrainManager: unhandled network message of type" + message.getMessageSubtype()); @@ -192,14 +202,23 @@ public class ClientTerrainManager { * @param worldY the world y coordinate of the chunk * @param worldZ the world z coordinate of the chunk * @param stride The stride of the data + * @return true if the request was successfully sent, false otherwise */ - public void requestChunk(int worldX, int worldY, int worldZ, int stride){ - Globals.clientConnection.queueOutgoingMessage(TerrainMessage.constructRequestReducedChunkDataMessage( - worldX, - worldY, - worldZ, - stride - )); + public boolean requestChunk(int worldX, int worldY, int worldZ, int stride){ + boolean rVal = false; + lock.acquireUninterruptibly(); + if(!this.requestedMap.containsKey(this.getRequestKey(worldX, worldY, worldZ, stride))){ + Globals.clientConnection.queueOutgoingMessage(TerrainMessage.constructRequestReducedChunkDataMessage( + worldX, + worldY, + worldZ, + stride + )); + this.requestedMap.put(this.getRequestKey(worldX, worldY, worldZ, stride), true); + rVal = true; + } + lock.release(); + return rVal; } /** @@ -273,5 +292,17 @@ public class ClientTerrainManager { public Vector3i getPositionOfChunk(ChunkData chunk){ return terrainCache.getChunkPosition(chunk); } + + /** + * Gets the key for a given request + * @param worldX The world x coordinate + * @param worldY The world y coordinate + * @param worldZ The world z coordinate + * @param stride The stride of the data + * @return The key + */ + private Long getRequestKey(int worldX, int worldY, int worldZ, int stride){ + return (long)HashUtils.cantorHash(worldY, worldZ, worldZ); + } }