package electrosphere.client.terrain.cache; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import org.joml.Vector3i; import io.github.studiorailgun.HashUtils; /** * Acts as a cache in front of terrain model to streamline receiving chunks */ public class ClientTerrainCache { //cache capacity int cacheSize; //the map of chunk key -> chunk data Map cacheMap = new ConcurrentHashMap(); //the list of keys in the cache List cacheList = new CopyOnWriteArrayList(); //A map of chunk to its world position Map chunkPositionMap = new ConcurrentHashMap(); /** * The map tracking chunks that have been requested */ Map requestedChunks = new ConcurrentHashMap(); /** * Constructor * @param cacheSize The capacity of the cache */ public ClientTerrainCache(int cacheSize){ this.cacheSize = cacheSize; } /** * Adds a chunk data to the terrain cache * @param worldX The x world position * @param worldY The y world position * @param worldZ The z world position * @param chunkData The chunk data to add at the specified positions */ public void addChunkDataToCache(int worldX, int worldY, int worldZ, ChunkData chunkData){ cacheMap.put(getKey(worldX,worldY,worldZ),chunkData); chunkPositionMap.put(chunkData,new Vector3i(worldX,worldY,worldZ)); while(cacheList.size() > cacheSize){ Long currentChunk = cacheList.remove(0); ChunkData data = cacheMap.remove(currentChunk); Vector3i worldPos = data.getWorldPos(); requestedChunks.remove(getKey(worldPos.x,worldPos.y,worldPos.z)); } } /** * Evicts all chunks from the cache */ public void evictAll(){ this.cacheList.clear(); this.cacheMap.clear(); this.chunkPositionMap.clear(); } /** * Generates a key for the cache based on the position provided * @param worldX The x world position * @param worldY The y world position * @param worldZ The z world position * @return The cache key */ public long getKey(int worldX, int worldY, int worldZ){ return HashUtils.cantorHash(worldX, worldY, worldZ); } /** * Checks whether the cache contains chunk data at a given world point * @param worldX The x world position * @param worldY The y world position * @param worldZ The z world position * @return True if the cache contains chunk data at the specified point, false otherwise */ public boolean containsChunkDataAtWorldPoint(int worldX, int worldY, int worldZ){ return cacheMap.containsKey(getKey(worldX,worldY,worldZ)); } /** * Gets chunk data at the given world point * @param worldX The x world position * @param worldY The y world position * @param worldZ The z world position * @return The chunk data if it exists, null otherwise */ public ChunkData getSubChunkDataAtPoint(int worldX, int worldY, int worldZ){ return cacheMap.get(getKey(worldX,worldY,worldZ)); } /** * Gets the list of all chunks in the cache * @return The list of all chunks in the cache */ public Collection getAllChunks(){ return this.cacheMap.values(); } /** * Gets the world position of a chunk * @param chunk The chunk * @return The world position of the chunk */ public Vector3i getChunkPosition(ChunkData chunk){ return chunkPositionMap.get(chunk); } /** * Gets the number of cells that have been requested * @return The number of cells that have been requested */ public int getRequestedCellCount(){ return this.requestedChunks.size(); } /** * Checks if a chunk has been requested or not * @param worldX The x coordinate in the world * @param worldY The y coordinate in the world * @param worldZ The z coordinate in the world * @return true if it has been requested, false otherwise */ public boolean hasRequested(int worldX, int worldY, int worldZ){ return this.requestedChunks.containsKey(getKey(worldX, worldY, worldZ)); } /** * Marks a chunk as requested * @param worldX The x coordinate in the world * @param worldY The y coordinate in the world * @param worldZ The z coordinate in the world */ public void markAsRequested(int worldX, int worldY, int worldZ){ this.requestedChunks.put(getKey(worldX, worldY, worldZ),true); } }