fix server caching of terrain
This commit is contained in:
parent
6b2f323fdf
commit
4b8e4cb542
@ -105,11 +105,22 @@ public class TestGenerationChunkGenerator implements ChunkGenerator {
|
||||
throw new Error("Undefined heightmap generator in biome! " + surfaceBiome.getId() + " " + surfaceBiome.getDisplayName() + " " + surfaceParams.getSurfaceGenTag());
|
||||
}
|
||||
|
||||
//stride value
|
||||
int strideValue = (int)Math.pow(2,stride);
|
||||
|
||||
//presolve heightfield
|
||||
float[][] heightfield = new float[ServerTerrainChunk.CHUNK_DIMENSION][ServerTerrainChunk.CHUNK_DIMENSION];
|
||||
for(int x = 0; x < ServerTerrainChunk.CHUNK_DIMENSION; x++){
|
||||
for(int z = 0; z < ServerTerrainChunk.CHUNK_DIMENSION; z++){
|
||||
heightfield[x][z] = heightmapGen.getHeight(this.terrainModel.getSeed(), this.serverWorldData.convertVoxelToRealSpace(x, worldX), this.serverWorldData.convertVoxelToRealSpace(z, worldZ));
|
||||
int finalWorldX = worldX + ((x * strideValue) / ServerTerrainChunk.CHUNK_DIMENSION);
|
||||
int finalWorldZ = worldZ + ((z * strideValue) / ServerTerrainChunk.CHUNK_DIMENSION);
|
||||
int finalChunkX = (x * strideValue) % ServerTerrainChunk.CHUNK_DIMENSION;
|
||||
int finalChunkZ = (z * strideValue) % ServerTerrainChunk.CHUNK_DIMENSION;
|
||||
heightfield[x][z] = heightmapGen.getHeight(
|
||||
this.terrainModel.getSeed(),
|
||||
this.serverWorldData.convertVoxelToRealSpace(finalChunkX, finalWorldX),
|
||||
this.serverWorldData.convertVoxelToRealSpace(finalChunkZ, finalWorldZ)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -117,7 +128,13 @@ public class TestGenerationChunkGenerator implements ChunkGenerator {
|
||||
Globals.profiler.beginAggregateCpuSample("TestGenerationChunkGenerator - Generate slice");
|
||||
for(int y = 0; y < ServerTerrainChunk.CHUNK_DIMENSION; y++){
|
||||
for(int z = 0; z < ServerTerrainChunk.CHUNK_DIMENSION; z++){
|
||||
GeneratedVoxel voxel = this.getVoxel(worldX, worldY, worldZ, x, y, z, heightfield, this.terrainModel, surfaceBiome);
|
||||
int finalWorldX = worldX + ((x * strideValue) / ServerTerrainChunk.CHUNK_DIMENSION);
|
||||
int finalWorldY = worldY + ((y * strideValue) / ServerTerrainChunk.CHUNK_DIMENSION);
|
||||
int finalWorldZ = worldZ + ((z * strideValue) / ServerTerrainChunk.CHUNK_DIMENSION);
|
||||
int finalChunkX = (x * strideValue) % ServerTerrainChunk.CHUNK_DIMENSION;
|
||||
int finalChunkY = (y * strideValue) % ServerTerrainChunk.CHUNK_DIMENSION;
|
||||
int finalChunkZ = (z * strideValue) % ServerTerrainChunk.CHUNK_DIMENSION;
|
||||
GeneratedVoxel voxel = this.getVoxel(finalWorldX, finalWorldY, finalWorldZ, finalChunkX, finalChunkY, finalChunkZ, heightfield[x][z], this.terrainModel, surfaceBiome);
|
||||
weights[x][y][z] = voxel.weight;
|
||||
values[x][y][z] = voxel.type;
|
||||
}
|
||||
@ -144,7 +161,7 @@ public class TestGenerationChunkGenerator implements ChunkGenerator {
|
||||
* @param chunkX The chunk x pos
|
||||
* @param chunkY The chunk y pos
|
||||
* @param chunkZ The chunk z pos
|
||||
* @param heightfield The precomputed heightfield
|
||||
* @param surfaceHeight The height of the surface at x,z
|
||||
* @param terrainModel The terrain model
|
||||
* @param surfaceBiome The surface biome of the chunk
|
||||
* @return The value of the chunk
|
||||
@ -152,7 +169,7 @@ public class TestGenerationChunkGenerator implements ChunkGenerator {
|
||||
private GeneratedVoxel getVoxel(
|
||||
int worldX, int worldY, int worldZ,
|
||||
int chunkX, int chunkY, int chunkZ,
|
||||
float[][] heightfield,
|
||||
float surfaceHeight,
|
||||
TerrainModel terrainModel,
|
||||
BiomeData surfaceBiome
|
||||
){
|
||||
@ -163,16 +180,15 @@ public class TestGenerationChunkGenerator implements ChunkGenerator {
|
||||
throw new Error("Undefined heightmap generator in biome! " + surfaceBiome.getId() + " " + surfaceBiome.getDisplayName() + " " + surfaceParams.getSurfaceGenTag());
|
||||
}
|
||||
|
||||
double realX = this.serverWorldData.convertVoxelToRealSpace(chunkY,worldX);
|
||||
double realX = this.serverWorldData.convertVoxelToRealSpace(chunkX,worldX);
|
||||
double realY = this.serverWorldData.convertVoxelToRealSpace(chunkY,worldY);
|
||||
double realZ = this.serverWorldData.convertVoxelToRealSpace(chunkY,worldZ);
|
||||
double realZ = this.serverWorldData.convertVoxelToRealSpace(chunkZ,worldZ);
|
||||
|
||||
float surfaceHeight = heightfield[chunkX][chunkZ];
|
||||
double flooredSurfaceHeight = Math.floor(surfaceHeight);
|
||||
Globals.profiler.endCpuSample();
|
||||
if(realY < surfaceHeight - 1){
|
||||
return getSubsurfaceVoxel(
|
||||
worldX,worldY, worldZ,
|
||||
worldX, worldY, worldZ,
|
||||
chunkX, chunkY, chunkZ,
|
||||
realX, realY, realZ,
|
||||
surfaceHeight, flooredSurfaceHeight,
|
||||
@ -181,7 +197,7 @@ public class TestGenerationChunkGenerator implements ChunkGenerator {
|
||||
);
|
||||
} else if(realY > flooredSurfaceHeight) {
|
||||
return getOverSurfaceVoxel(
|
||||
worldX,worldY, worldZ,
|
||||
worldX, worldY, worldZ,
|
||||
chunkX, chunkY, chunkZ,
|
||||
realX, realY, realZ,
|
||||
surfaceHeight, flooredSurfaceHeight,
|
||||
@ -190,7 +206,7 @@ public class TestGenerationChunkGenerator implements ChunkGenerator {
|
||||
);
|
||||
} else {
|
||||
return getSurfaceVoxel(
|
||||
worldX,worldY, worldZ,
|
||||
worldX, worldY, worldZ,
|
||||
chunkX, chunkY, chunkZ,
|
||||
realX, realY, realZ,
|
||||
surfaceHeight, flooredSurfaceHeight,
|
||||
|
||||
@ -96,8 +96,8 @@ public class ChunkGenerationThread implements Runnable {
|
||||
ServerTerrainChunk chunk = null;
|
||||
int i = 0;
|
||||
while(chunk == null && i < MAX_TIME_TO_WAIT && Globals.threadManager.shouldKeepRunning()){
|
||||
if(chunkCache.containsChunk(worldX,worldY,worldZ)){
|
||||
chunk = chunkCache.get(worldX,worldY,worldZ);
|
||||
if(chunkCache.containsChunk(worldX,worldY,worldZ,stride)){
|
||||
chunk = chunkCache.get(worldX, worldY, worldZ, stride);
|
||||
} else {
|
||||
//pull from disk if it exists
|
||||
if(chunkDiskMap != null){
|
||||
@ -110,7 +110,7 @@ public class ChunkGenerationThread implements Runnable {
|
||||
chunk = chunkGenerator.generateChunk(worldX, worldY, worldZ, stride);
|
||||
}
|
||||
if(chunk != null){
|
||||
chunkCache.add(worldX, worldY, worldZ, chunk);
|
||||
chunkCache.add(worldX, worldY, worldZ, stride, chunk);
|
||||
}
|
||||
}
|
||||
if(chunk == null){
|
||||
|
||||
@ -6,8 +6,11 @@ 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.Semaphore;
|
||||
|
||||
import io.github.studiorailgun.HashUtils;
|
||||
|
||||
/**
|
||||
* Caches chunk data on the server
|
||||
*/
|
||||
@ -16,7 +19,7 @@ public class ServerChunkCache {
|
||||
/**
|
||||
* Number of chunks to cache
|
||||
*/
|
||||
static final int CACHE_SIZE = 5000;
|
||||
static final int CACHE_SIZE = 2500;
|
||||
|
||||
/**
|
||||
* The size of the cache
|
||||
@ -24,19 +27,24 @@ public class ServerChunkCache {
|
||||
int cacheSize = CACHE_SIZE;
|
||||
|
||||
/**
|
||||
* The cached data
|
||||
* The map of full res chunk key -> chunk data
|
||||
*/
|
||||
Map<String, ServerTerrainChunk> chunkCache = new HashMap<String,ServerTerrainChunk>();
|
||||
Map<Long,ServerTerrainChunk> cacheMapFullRes = new ConcurrentHashMap<Long,ServerTerrainChunk>();
|
||||
|
||||
/**
|
||||
* The map of half res chunk key -> chunk data
|
||||
*/
|
||||
Map<Long,ServerTerrainChunk> cacheMapHalfRes = new ConcurrentHashMap<Long,ServerTerrainChunk>();
|
||||
|
||||
/**
|
||||
* Tracks how recently a chunk has been queries for (used for evicting old chunks from cache)
|
||||
*/
|
||||
List<String> queryRecencyQueue = new LinkedList<String>();
|
||||
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<String, Boolean> queuedChunkMap = new HashMap<String,Boolean>();
|
||||
Map<Long, Boolean> queuedChunkMap = new HashMap<Long,Boolean>();
|
||||
|
||||
/**
|
||||
* The lock for thread safety
|
||||
@ -49,7 +57,7 @@ public class ServerChunkCache {
|
||||
*/
|
||||
public Collection<ServerTerrainChunk> getContents(){
|
||||
lock.acquireUninterruptibly();
|
||||
Collection<ServerTerrainChunk> rVal = Collections.unmodifiableCollection(chunkCache.values());
|
||||
Collection<ServerTerrainChunk> rVal = Collections.unmodifiableCollection(cacheMapFullRes.values());
|
||||
lock.release();
|
||||
return rVal;
|
||||
}
|
||||
@ -59,7 +67,8 @@ public class ServerChunkCache {
|
||||
*/
|
||||
public void clear(){
|
||||
lock.acquireUninterruptibly();
|
||||
chunkCache.clear();
|
||||
cacheMapFullRes.clear();
|
||||
cacheMapHalfRes.clear();
|
||||
lock.release();
|
||||
}
|
||||
|
||||
@ -68,15 +77,17 @@ public class ServerChunkCache {
|
||||
* @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 ServerTerrainChunk get(int worldX, int worldY, int worldZ){
|
||||
public ServerTerrainChunk get(int worldX, int worldY, int worldZ, int stride){
|
||||
ServerTerrainChunk rVal = null;
|
||||
String key = this.getKey(worldX, worldY, worldZ);
|
||||
Long key = this.getKey(worldX, worldY, worldZ);
|
||||
lock.acquireUninterruptibly();
|
||||
queryRecencyQueue.remove(key);
|
||||
queryRecencyQueue.add(0, key);
|
||||
rVal = this.chunkCache.get(key);
|
||||
Map<Long,ServerTerrainChunk> cache = this.getCache(stride);
|
||||
rVal = cache.get(key);
|
||||
lock.release();
|
||||
return rVal;
|
||||
}
|
||||
@ -86,13 +97,15 @@ public class ServerChunkCache {
|
||||
* @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, ServerTerrainChunk chunk){
|
||||
String key = this.getKey(worldX, worldY, worldZ);
|
||||
public void add(int worldX, int worldY, int worldZ, int stride, ServerTerrainChunk chunk){
|
||||
Long key = this.getKey(worldX, worldY, worldZ);
|
||||
lock.acquireUninterruptibly();
|
||||
queryRecencyQueue.add(0, key);
|
||||
this.chunkCache.put(key, chunk);
|
||||
Map<Long,ServerTerrainChunk> cache = this.getCache(stride);
|
||||
cache.put(key, chunk);
|
||||
lock.release();
|
||||
}
|
||||
|
||||
@ -101,12 +114,14 @@ public class ServerChunkCache {
|
||||
* @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){
|
||||
String key = this.getKey(worldX,worldY,worldZ);
|
||||
public boolean containsChunk(int worldX, int worldY, int worldZ, int stride){
|
||||
Long key = this.getKey(worldX,worldY,worldZ);
|
||||
lock.acquireUninterruptibly();
|
||||
boolean rVal = this.chunkCache.containsKey(key);
|
||||
Map<Long,ServerTerrainChunk> cache = this.getCache(stride);
|
||||
boolean rVal = cache.containsKey(key);
|
||||
lock.release();
|
||||
return rVal;
|
||||
}
|
||||
@ -118,8 +133,8 @@ public class ServerChunkCache {
|
||||
* @param worldZ The z component
|
||||
* @return The key
|
||||
*/
|
||||
public String getKey(int worldX, int worldY, int worldZ){
|
||||
return worldX + "_" + worldY + "_" + worldZ;
|
||||
public long getKey(int worldX, int worldY, int worldZ){
|
||||
return HashUtils.cantorHash(worldX, worldY, worldZ);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -130,7 +145,7 @@ public class ServerChunkCache {
|
||||
* @return true if the chunk is already queued, false otherwise
|
||||
*/
|
||||
public boolean chunkIsQueued(int worldX, int worldY, int worldZ){
|
||||
String key = this.getKey(worldX,worldY,worldZ);
|
||||
Long key = this.getKey(worldX,worldY,worldZ);
|
||||
lock.acquireUninterruptibly();
|
||||
boolean rVal = this.queuedChunkMap.containsKey(key);
|
||||
lock.release();
|
||||
@ -144,7 +159,7 @@ public class ServerChunkCache {
|
||||
* @param worldZ The world z position of the chunk
|
||||
*/
|
||||
public void queueChunk(int worldX, int worldY, int worldZ){
|
||||
String key = this.getKey(worldX,worldY,worldZ);
|
||||
Long key = this.getKey(worldX,worldY,worldZ);
|
||||
lock.acquireUninterruptibly();
|
||||
this.queuedChunkMap.put(key,true);
|
||||
lock.release();
|
||||
@ -155,12 +170,32 @@ public class ServerChunkCache {
|
||||
* @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){
|
||||
String key = this.getKey(worldX,worldY,worldZ);
|
||||
public void unqueueChunk(int worldX, int worldY, int worldZ, int stride){
|
||||
Long key = this.getKey(worldX,worldY,worldZ);
|
||||
lock.acquireUninterruptibly();
|
||||
this.queuedChunkMap.remove(key);
|
||||
lock.release();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the cache
|
||||
* @param stride The stride of the data
|
||||
* @return The cache to use
|
||||
*/
|
||||
public Map<Long,ServerTerrainChunk> getCache(int stride){
|
||||
switch(stride){
|
||||
case 0: {
|
||||
return cacheMapFullRes;
|
||||
}
|
||||
case 1: {
|
||||
return cacheMapHalfRes;
|
||||
}
|
||||
default: {
|
||||
throw new Error("Invalid stride probided! " + stride);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -232,8 +232,8 @@ public class ServerTerrainManager {
|
||||
Globals.profiler.beginCpuSample("ServerTerrainManager.getChunk");
|
||||
//THIS FIRES IF THERE IS A MAIN GAME WORLD RUNNING
|
||||
ServerTerrainChunk returnedChunk = null;
|
||||
if(chunkCache.containsChunk(worldX,worldY,worldZ)){
|
||||
returnedChunk = chunkCache.get(worldX,worldY,worldZ);
|
||||
if(chunkCache.containsChunk(worldX,worldY,worldZ,ChunkData.NO_STRIDE)){
|
||||
returnedChunk = chunkCache.get(worldX,worldY,worldZ, ChunkData.NO_STRIDE);
|
||||
} else {
|
||||
//pull from disk if it exists
|
||||
if(chunkDiskMap != null){
|
||||
@ -245,7 +245,7 @@ public class ServerTerrainManager {
|
||||
if(returnedChunk == null){
|
||||
returnedChunk = chunkGenerator.generateChunk(worldX, worldY, worldZ, ChunkData.NO_STRIDE);
|
||||
}
|
||||
this.chunkCache.add(worldX, worldY, worldZ, returnedChunk);
|
||||
this.chunkCache.add(worldX, worldY, worldZ, ChunkData.NO_STRIDE, returnedChunk);
|
||||
}
|
||||
Globals.profiler.endCpuSample();
|
||||
return returnedChunk;
|
||||
@ -289,8 +289,8 @@ public class ServerTerrainManager {
|
||||
if(model != null){
|
||||
model.addModification(modification);
|
||||
}
|
||||
if(chunkCache.containsChunk(worldPos.x,worldPos.y,worldPos.z)){
|
||||
ServerTerrainChunk chunk = chunkCache.get(worldPos.x,worldPos.y,worldPos.z);
|
||||
if(chunkCache.containsChunk(worldPos.x,worldPos.y,worldPos.z,ChunkData.NO_STRIDE)){
|
||||
ServerTerrainChunk chunk = chunkCache.get(worldPos.x,worldPos.y,worldPos.z, ChunkData.NO_STRIDE);
|
||||
chunk.addModification(modification);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user