voxelImprovements #5

Merged
railgun merged 21 commits from voxelImprovements into master 2024-11-07 15:08:54 -05:00
4 changed files with 91 additions and 40 deletions
Showing only changes of commit 4b8e4cb542 - Show all commits

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()); 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 //presolve heightfield
float[][] heightfield = new float[ServerTerrainChunk.CHUNK_DIMENSION][ServerTerrainChunk.CHUNK_DIMENSION]; float[][] heightfield = new float[ServerTerrainChunk.CHUNK_DIMENSION][ServerTerrainChunk.CHUNK_DIMENSION];
for(int x = 0; x < ServerTerrainChunk.CHUNK_DIMENSION; x++){ for(int x = 0; x < ServerTerrainChunk.CHUNK_DIMENSION; x++){
for(int z = 0; z < ServerTerrainChunk.CHUNK_DIMENSION; z++){ 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"); Globals.profiler.beginAggregateCpuSample("TestGenerationChunkGenerator - Generate slice");
for(int y = 0; y < ServerTerrainChunk.CHUNK_DIMENSION; y++){ for(int y = 0; y < ServerTerrainChunk.CHUNK_DIMENSION; y++){
for(int z = 0; z < ServerTerrainChunk.CHUNK_DIMENSION; z++){ 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; weights[x][y][z] = voxel.weight;
values[x][y][z] = voxel.type; values[x][y][z] = voxel.type;
} }
@ -144,7 +161,7 @@ public class TestGenerationChunkGenerator implements ChunkGenerator {
* @param chunkX The chunk x pos * @param chunkX The chunk x pos
* @param chunkY The chunk y pos * @param chunkY The chunk y pos
* @param chunkZ The chunk z 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 terrainModel The terrain model
* @param surfaceBiome The surface biome of the chunk * @param surfaceBiome The surface biome of the chunk
* @return The value of the chunk * @return The value of the chunk
@ -152,7 +169,7 @@ public class TestGenerationChunkGenerator implements ChunkGenerator {
private GeneratedVoxel getVoxel( private GeneratedVoxel getVoxel(
int worldX, int worldY, int worldZ, int worldX, int worldY, int worldZ,
int chunkX, int chunkY, int chunkZ, int chunkX, int chunkY, int chunkZ,
float[][] heightfield, float surfaceHeight,
TerrainModel terrainModel, TerrainModel terrainModel,
BiomeData surfaceBiome 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()); 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 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); double flooredSurfaceHeight = Math.floor(surfaceHeight);
Globals.profiler.endCpuSample(); Globals.profiler.endCpuSample();
if(realY < surfaceHeight - 1){ if(realY < surfaceHeight - 1){
return getSubsurfaceVoxel( return getSubsurfaceVoxel(
worldX,worldY, worldZ, worldX, worldY, worldZ,
chunkX, chunkY, chunkZ, chunkX, chunkY, chunkZ,
realX, realY, realZ, realX, realY, realZ,
surfaceHeight, flooredSurfaceHeight, surfaceHeight, flooredSurfaceHeight,
@ -181,7 +197,7 @@ public class TestGenerationChunkGenerator implements ChunkGenerator {
); );
} else if(realY > flooredSurfaceHeight) { } else if(realY > flooredSurfaceHeight) {
return getOverSurfaceVoxel( return getOverSurfaceVoxel(
worldX,worldY, worldZ, worldX, worldY, worldZ,
chunkX, chunkY, chunkZ, chunkX, chunkY, chunkZ,
realX, realY, realZ, realX, realY, realZ,
surfaceHeight, flooredSurfaceHeight, surfaceHeight, flooredSurfaceHeight,
@ -190,7 +206,7 @@ public class TestGenerationChunkGenerator implements ChunkGenerator {
); );
} else { } else {
return getSurfaceVoxel( return getSurfaceVoxel(
worldX,worldY, worldZ, worldX, worldY, worldZ,
chunkX, chunkY, chunkZ, chunkX, chunkY, chunkZ,
realX, realY, realZ, realX, realY, realZ,
surfaceHeight, flooredSurfaceHeight, surfaceHeight, flooredSurfaceHeight,

View File

@ -96,8 +96,8 @@ public class ChunkGenerationThread implements Runnable {
ServerTerrainChunk chunk = null; ServerTerrainChunk chunk = null;
int i = 0; int i = 0;
while(chunk == null && i < MAX_TIME_TO_WAIT && Globals.threadManager.shouldKeepRunning()){ while(chunk == null && i < MAX_TIME_TO_WAIT && Globals.threadManager.shouldKeepRunning()){
if(chunkCache.containsChunk(worldX,worldY,worldZ)){ if(chunkCache.containsChunk(worldX,worldY,worldZ,stride)){
chunk = chunkCache.get(worldX,worldY,worldZ); chunk = chunkCache.get(worldX, worldY, worldZ, stride);
} else { } else {
//pull from disk if it exists //pull from disk if it exists
if(chunkDiskMap != null){ if(chunkDiskMap != null){
@ -110,7 +110,7 @@ public class ChunkGenerationThread implements Runnable {
chunk = chunkGenerator.generateChunk(worldX, worldY, worldZ, stride); chunk = chunkGenerator.generateChunk(worldX, worldY, worldZ, stride);
} }
if(chunk != null){ if(chunk != null){
chunkCache.add(worldX, worldY, worldZ, chunk); chunkCache.add(worldX, worldY, worldZ, stride, chunk);
} }
} }
if(chunk == null){ if(chunk == null){

View File

@ -6,8 +6,11 @@ import java.util.HashMap;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Semaphore; import java.util.concurrent.Semaphore;
import io.github.studiorailgun.HashUtils;
/** /**
* Caches chunk data on the server * Caches chunk data on the server
*/ */
@ -16,7 +19,7 @@ public class ServerChunkCache {
/** /**
* Number of chunks to cache * Number of chunks to cache
*/ */
static final int CACHE_SIZE = 5000; static final int CACHE_SIZE = 2500;
/** /**
* The size of the cache * The size of the cache
@ -24,19 +27,24 @@ public class ServerChunkCache {
int cacheSize = CACHE_SIZE; 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) * 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 * 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 * The lock for thread safety
@ -49,7 +57,7 @@ public class ServerChunkCache {
*/ */
public Collection<ServerTerrainChunk> getContents(){ public Collection<ServerTerrainChunk> getContents(){
lock.acquireUninterruptibly(); lock.acquireUninterruptibly();
Collection<ServerTerrainChunk> rVal = Collections.unmodifiableCollection(chunkCache.values()); Collection<ServerTerrainChunk> rVal = Collections.unmodifiableCollection(cacheMapFullRes.values());
lock.release(); lock.release();
return rVal; return rVal;
} }
@ -59,7 +67,8 @@ public class ServerChunkCache {
*/ */
public void clear(){ public void clear(){
lock.acquireUninterruptibly(); lock.acquireUninterruptibly();
chunkCache.clear(); cacheMapFullRes.clear();
cacheMapHalfRes.clear();
lock.release(); lock.release();
} }
@ -68,15 +77,17 @@ public class ServerChunkCache {
* @param worldX The world x coordinate * @param worldX The world x coordinate
* @param worldY The world y coordinate * @param worldY The world y coordinate
* @param worldZ The world z coordinate * @param worldZ The world z coordinate
* @param stride The stride of the data
* @return The chunk * @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; ServerTerrainChunk rVal = null;
String key = this.getKey(worldX, worldY, worldZ); Long key = this.getKey(worldX, worldY, worldZ);
lock.acquireUninterruptibly(); lock.acquireUninterruptibly();
queryRecencyQueue.remove(key); queryRecencyQueue.remove(key);
queryRecencyQueue.add(0, key); queryRecencyQueue.add(0, key);
rVal = this.chunkCache.get(key); Map<Long,ServerTerrainChunk> cache = this.getCache(stride);
rVal = cache.get(key);
lock.release(); lock.release();
return rVal; return rVal;
} }
@ -86,13 +97,15 @@ public class ServerChunkCache {
* @param worldX The world x coordinate of the chunk * @param worldX The world x coordinate of the chunk
* @param worldY The world y coordinate of the chunk * @param worldY The world y coordinate of the chunk
* @param worldZ The world z 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 * @param chunk The chunk itself
*/ */
public void add(int worldX, int worldY, int worldZ, ServerTerrainChunk chunk){ public void add(int worldX, int worldY, int worldZ, int stride, ServerTerrainChunk chunk){
String key = this.getKey(worldX, worldY, worldZ); Long key = this.getKey(worldX, worldY, worldZ);
lock.acquireUninterruptibly(); lock.acquireUninterruptibly();
queryRecencyQueue.add(0, key); queryRecencyQueue.add(0, key);
this.chunkCache.put(key, chunk); Map<Long,ServerTerrainChunk> cache = this.getCache(stride);
cache.put(key, chunk);
lock.release(); lock.release();
} }
@ -101,12 +114,14 @@ public class ServerChunkCache {
* @param worldX The world x coordinate * @param worldX The world x coordinate
* @param worldY The world y coordinate * @param worldY The world y coordinate
* @param worldZ The world z coordinate * @param worldZ The world z coordinate
* @param stride The stride of the data
* @return true if the cache contains this chunk, false otherwise * @return true if the cache contains this chunk, false otherwise
*/ */
public boolean containsChunk(int worldX, int worldY, int worldZ){ public boolean containsChunk(int worldX, int worldY, int worldZ, int stride){
String key = this.getKey(worldX,worldY,worldZ); Long key = this.getKey(worldX,worldY,worldZ);
lock.acquireUninterruptibly(); lock.acquireUninterruptibly();
boolean rVal = this.chunkCache.containsKey(key); Map<Long,ServerTerrainChunk> cache = this.getCache(stride);
boolean rVal = cache.containsKey(key);
lock.release(); lock.release();
return rVal; return rVal;
} }
@ -118,8 +133,8 @@ public class ServerChunkCache {
* @param worldZ The z component * @param worldZ The z component
* @return The key * @return The key
*/ */
public String getKey(int worldX, int worldY, int worldZ){ public long getKey(int worldX, int worldY, int worldZ){
return worldX + "_" + worldY + "_" + worldZ; return HashUtils.cantorHash(worldX, worldY, worldZ);
} }
/** /**
@ -130,7 +145,7 @@ public class ServerChunkCache {
* @return true if the chunk is already queued, false otherwise * @return true if the chunk is already queued, false otherwise
*/ */
public boolean chunkIsQueued(int worldX, int worldY, int worldZ){ 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(); lock.acquireUninterruptibly();
boolean rVal = this.queuedChunkMap.containsKey(key); boolean rVal = this.queuedChunkMap.containsKey(key);
lock.release(); lock.release();
@ -144,7 +159,7 @@ public class ServerChunkCache {
* @param worldZ The world z position of the chunk * @param worldZ The world z position of the chunk
*/ */
public void queueChunk(int worldX, int worldY, int worldZ){ 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(); lock.acquireUninterruptibly();
this.queuedChunkMap.put(key,true); this.queuedChunkMap.put(key,true);
lock.release(); lock.release();
@ -155,12 +170,32 @@ public class ServerChunkCache {
* @param worldX The world x position of the chunk * @param worldX The world x position of the chunk
* @param worldY The world y position of the chunk * @param worldY The world y position of the chunk
* @param worldZ The world z 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){ public void unqueueChunk(int worldX, int worldY, int worldZ, int stride){
String key = this.getKey(worldX,worldY,worldZ); Long key = this.getKey(worldX,worldY,worldZ);
lock.acquireUninterruptibly(); lock.acquireUninterruptibly();
this.queuedChunkMap.remove(key); this.queuedChunkMap.remove(key);
lock.release(); 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"); Globals.profiler.beginCpuSample("ServerTerrainManager.getChunk");
//THIS FIRES IF THERE IS A MAIN GAME WORLD RUNNING //THIS FIRES IF THERE IS A MAIN GAME WORLD RUNNING
ServerTerrainChunk returnedChunk = null; ServerTerrainChunk returnedChunk = null;
if(chunkCache.containsChunk(worldX,worldY,worldZ)){ if(chunkCache.containsChunk(worldX,worldY,worldZ,ChunkData.NO_STRIDE)){
returnedChunk = chunkCache.get(worldX,worldY,worldZ); returnedChunk = chunkCache.get(worldX,worldY,worldZ, ChunkData.NO_STRIDE);
} else { } else {
//pull from disk if it exists //pull from disk if it exists
if(chunkDiskMap != null){ if(chunkDiskMap != null){
@ -245,7 +245,7 @@ public class ServerTerrainManager {
if(returnedChunk == null){ if(returnedChunk == null){
returnedChunk = chunkGenerator.generateChunk(worldX, worldY, worldZ, ChunkData.NO_STRIDE); 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(); Globals.profiler.endCpuSample();
return returnedChunk; return returnedChunk;
@ -289,8 +289,8 @@ public class ServerTerrainManager {
if(model != null){ if(model != null){
model.addModification(modification); model.addModification(modification);
} }
if(chunkCache.containsChunk(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); ServerTerrainChunk chunk = chunkCache.get(worldPos.x,worldPos.y,worldPos.z, ChunkData.NO_STRIDE);
chunk.addModification(modification); chunk.addModification(modification);
} }
} }