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 full res chunk key -> chunk data */ Map cacheMapFullRes = new ConcurrentHashMap(); /** * The map of half res chunk key -> chunk data */ Map cacheMapHalfRes = new ConcurrentHashMap(); /** * The map of half res chunk key -> chunk data */ Map cacheMapQuarterRes = 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(); /** * 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){ Map cache = this.getCache(chunkData.getStride()); cache.put(getKey(worldX,worldY,worldZ),chunkData); chunkPositionMap.put(chunkData,new Vector3i(worldX,worldY,worldZ)); while(cacheList.size() > cacheSize){ Long currentChunk = cacheList.remove(0); cache.remove(currentChunk); } } /** * Evicts all chunks from the cache */ public void evictAll(){ this.cacheList.clear(); this.cacheMapFullRes.clear(); this.cacheMapHalfRes.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 * @param stride The stride of the data * @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)); } /** * 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 * @param stride The stride of the data * @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)); } /** * Gets the list of all chunks in the cache * @return The list of all chunks in the cache */ public Collection getAllChunks(){ return this.cacheMapFullRes.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 cache * @param stride The stride of the data * @return The cache to use */ public Map getCache(int stride){ switch(stride){ case 0: { return cacheMapFullRes; } case 1: { return cacheMapHalfRes; } case 2: { return cacheMapQuarterRes; } default: { throw new Error("Invalid stride probided! " + stride); } } } }