fix server caching of terrain
Some checks failed
studiorailgun/Renderer/pipeline/head There was a failure building this commit
studiorailgun/Renderer/pipeline/pr-master There was a failure building this commit

This commit is contained in:
austin 2024-11-06 10:05:06 -05:00
parent 6b2f323fdf
commit 4b8e4cb542
4 changed files with 91 additions and 40 deletions

View File

@ -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,

View File

@ -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){

View File

@ -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);
}
}
}
}

View File

@ -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);
}
}