237 lines
7.2 KiB
Java
237 lines
7.2 KiB
Java
package electrosphere.client.block;
|
|
|
|
import java.util.Collection;
|
|
import java.util.Collections;
|
|
import java.util.HashMap;
|
|
import java.util.LinkedList;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.concurrent.ConcurrentHashMap;
|
|
import java.util.concurrent.locks.ReentrantLock;
|
|
|
|
import io.github.studiorailgun.HashUtils;
|
|
|
|
/**
|
|
* Caches chunk data on the server
|
|
*/
|
|
public class BlockChunkCache {
|
|
|
|
/**
|
|
* Number of chunks to cache
|
|
*/
|
|
static final int CACHE_SIZE = 1500;
|
|
|
|
/**
|
|
* The size of the cache
|
|
*/
|
|
int cacheSize = CACHE_SIZE;
|
|
|
|
/**
|
|
* The map of full res chunk key -> chunk data
|
|
*/
|
|
Map<Long,BlockChunkData> cacheMapFullRes = new ConcurrentHashMap<Long,BlockChunkData>();
|
|
|
|
/**
|
|
* The map of half res chunk key -> chunk data
|
|
*/
|
|
Map<Long,BlockChunkData> cacheMapHalfRes = new ConcurrentHashMap<Long,BlockChunkData>();
|
|
|
|
/**
|
|
* The map of quarter res chunk key -> chunk data
|
|
*/
|
|
Map<Long,BlockChunkData> cacheMapQuarterRes = new ConcurrentHashMap<Long,BlockChunkData>();
|
|
|
|
/**
|
|
* The map of eighth res chunk key -> chunk data
|
|
*/
|
|
Map<Long,BlockChunkData> cacheMapEighthRes = new ConcurrentHashMap<Long,BlockChunkData>();
|
|
|
|
/**
|
|
* The map of sixteenth res chunk key -> chunk data
|
|
*/
|
|
Map<Long,BlockChunkData> cacheMapSixteenthRes = new ConcurrentHashMap<Long,BlockChunkData>();
|
|
|
|
/**
|
|
* Tracks how recently a chunk has been queries for (used for evicting old chunks from cache)
|
|
*/
|
|
List<Long> queryRecencyQueue = new LinkedList<Long>();
|
|
|
|
/**
|
|
* Tracks what chunks are already queued to be asynchronously loaded. Used so we don't have two threads generating/fetching the same chunk
|
|
*/
|
|
Map<Long, Boolean> queuedChunkMap = new HashMap<Long,Boolean>();
|
|
|
|
/**
|
|
* The lock for thread safety
|
|
*/
|
|
ReentrantLock lock = new ReentrantLock();
|
|
|
|
/**
|
|
* Gets the collection of server block chunks that are cached
|
|
* @return The collection of chunks
|
|
*/
|
|
public Collection<BlockChunkData> getContents(){
|
|
lock.lock();
|
|
Collection<BlockChunkData> rVal = Collections.unmodifiableCollection(cacheMapFullRes.values());
|
|
lock.unlock();
|
|
return rVal;
|
|
}
|
|
|
|
/**
|
|
* Evicts all chunks in the cache
|
|
*/
|
|
public void clear(){
|
|
lock.lock();
|
|
cacheMapFullRes.clear();
|
|
cacheMapHalfRes.clear();
|
|
cacheMapQuarterRes.clear();
|
|
cacheMapEighthRes.clear();
|
|
cacheMapSixteenthRes.clear();
|
|
lock.unlock();
|
|
}
|
|
|
|
/**
|
|
* Gets the chunk at a given world position
|
|
* @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 chunk
|
|
*/
|
|
public BlockChunkData get(int worldX, int worldY, int worldZ, int stride){
|
|
BlockChunkData rVal = null;
|
|
Long key = this.getKey(worldX, worldY, worldZ);
|
|
lock.lock();
|
|
queryRecencyQueue.remove(key);
|
|
queryRecencyQueue.add(0, key);
|
|
Map<Long,BlockChunkData> cache = this.getCache(stride);
|
|
rVal = cache.get(key);
|
|
lock.unlock();
|
|
return rVal;
|
|
}
|
|
|
|
/**
|
|
* Adds a chunk to the cache
|
|
* @param worldX The world x coordinate of the chunk
|
|
* @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
|
|
* @param chunk The chunk itself
|
|
*/
|
|
public void add(int worldX, int worldY, int worldZ, int stride, BlockChunkData chunk){
|
|
Long key = this.getKey(worldX, worldY, worldZ);
|
|
lock.lock();
|
|
queryRecencyQueue.add(0, key);
|
|
Map<Long,BlockChunkData> cache = this.getCache(stride);
|
|
cache.put(key, chunk);
|
|
while(queryRecencyQueue.size() > cacheSize){
|
|
Long oldKey = queryRecencyQueue.remove(queryRecencyQueue.size() - 1);
|
|
cacheMapFullRes.remove(oldKey);
|
|
cacheMapHalfRes.remove(oldKey);
|
|
cacheMapQuarterRes.remove(oldKey);
|
|
cacheMapEighthRes.remove(oldKey);
|
|
cacheMapSixteenthRes.remove(oldKey);
|
|
}
|
|
lock.unlock();
|
|
}
|
|
|
|
/**
|
|
* Checks if the cache contains the chunk at a given world position
|
|
* @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 true if the cache contains this chunk, false otherwise
|
|
*/
|
|
public boolean containsChunk(int worldX, int worldY, int worldZ, int stride){
|
|
Long key = this.getKey(worldX,worldY,worldZ);
|
|
lock.lock();
|
|
Map<Long,BlockChunkData> cache = this.getCache(stride);
|
|
boolean rVal = cache.containsKey(key);
|
|
lock.unlock();
|
|
return rVal;
|
|
}
|
|
|
|
/**
|
|
* Gets the key for a given world position
|
|
* @param worldX The x component
|
|
* @param worldY The y component
|
|
* @param worldZ The z component
|
|
* @return The key
|
|
*/
|
|
public long getKey(int worldX, int worldY, int worldZ){
|
|
return HashUtils.cantorHash(worldX, worldY, worldZ);
|
|
}
|
|
|
|
/**
|
|
* Checks if the chunk is already queued or not
|
|
* @param worldX The world x position of the chunk
|
|
* @param worldY The world y position of the chunk
|
|
* @param worldZ The world z position of the chunk
|
|
* @return true if the chunk is already queued, false otherwise
|
|
*/
|
|
public boolean chunkIsQueued(int worldX, int worldY, int worldZ){
|
|
Long key = this.getKey(worldX,worldY,worldZ);
|
|
lock.lock();
|
|
boolean rVal = this.queuedChunkMap.containsKey(key);
|
|
lock.unlock();
|
|
return rVal;
|
|
}
|
|
|
|
/**
|
|
* Flags a chunk as queued
|
|
* @param worldX The world x position of the chunk
|
|
* @param worldY The world y position of the chunk
|
|
* @param worldZ The world z position of the chunk
|
|
*/
|
|
public void queueChunk(int worldX, int worldY, int worldZ){
|
|
Long key = this.getKey(worldX,worldY,worldZ);
|
|
lock.lock();
|
|
this.queuedChunkMap.put(key,true);
|
|
lock.unlock();
|
|
}
|
|
|
|
/**
|
|
* Unflags a chunk as queued
|
|
* @param worldX The world x position of the chunk
|
|
* @param worldY The world y position of the chunk
|
|
* @param worldZ The world z position of the chunk
|
|
* @param stride The stride of the chunk
|
|
*/
|
|
public void unqueueChunk(int worldX, int worldY, int worldZ, int stride){
|
|
Long key = this.getKey(worldX,worldY,worldZ);
|
|
lock.lock();
|
|
this.queuedChunkMap.remove(key);
|
|
lock.unlock();
|
|
}
|
|
|
|
/**
|
|
* Gets the cache
|
|
* @param stride The stride of the data
|
|
* @return The cache to use
|
|
*/
|
|
public Map<Long,BlockChunkData> getCache(int stride){
|
|
switch(stride){
|
|
case 0: {
|
|
return cacheMapFullRes;
|
|
}
|
|
case 1: {
|
|
return cacheMapHalfRes;
|
|
}
|
|
case 2: {
|
|
return cacheMapQuarterRes;
|
|
}
|
|
case 3: {
|
|
return cacheMapEighthRes;
|
|
}
|
|
case 4: {
|
|
return cacheMapSixteenthRes;
|
|
}
|
|
default: {
|
|
throw new Error("Invalid stride probided! " + stride);
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|