From 4b8e4cb542095babbf6e52112b248d8da1666078 Mon Sep 17 00:00:00 2001 From: austin Date: Wed, 6 Nov 2024 10:05:06 -0500 Subject: [PATCH] fix server caching of terrain --- .../TestGenerationChunkGenerator.java | 36 ++++++--- .../manager/ChunkGenerationThread.java | 6 +- .../terrain/manager/ServerChunkCache.java | 79 +++++++++++++------ .../terrain/manager/ServerTerrainManager.java | 10 +-- 4 files changed, 91 insertions(+), 40 deletions(-) diff --git a/src/main/java/electrosphere/server/terrain/generation/TestGenerationChunkGenerator.java b/src/main/java/electrosphere/server/terrain/generation/TestGenerationChunkGenerator.java index aa299212..05c5ba93 100644 --- a/src/main/java/electrosphere/server/terrain/generation/TestGenerationChunkGenerator.java +++ b/src/main/java/electrosphere/server/terrain/generation/TestGenerationChunkGenerator.java @@ -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, diff --git a/src/main/java/electrosphere/server/terrain/manager/ChunkGenerationThread.java b/src/main/java/electrosphere/server/terrain/manager/ChunkGenerationThread.java index 5446f56e..816036b6 100644 --- a/src/main/java/electrosphere/server/terrain/manager/ChunkGenerationThread.java +++ b/src/main/java/electrosphere/server/terrain/manager/ChunkGenerationThread.java @@ -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){ diff --git a/src/main/java/electrosphere/server/terrain/manager/ServerChunkCache.java b/src/main/java/electrosphere/server/terrain/manager/ServerChunkCache.java index 3d8fbc9b..188a5d37 100644 --- a/src/main/java/electrosphere/server/terrain/manager/ServerChunkCache.java +++ b/src/main/java/electrosphere/server/terrain/manager/ServerChunkCache.java @@ -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 chunkCache = new HashMap(); + Map cacheMapFullRes = new ConcurrentHashMap(); + + /** + * The map of half res chunk key -> chunk data + */ + Map cacheMapHalfRes = new ConcurrentHashMap(); /** * Tracks how recently a chunk has been queries for (used for evicting old chunks from cache) */ - List queryRecencyQueue = new LinkedList(); + List queryRecencyQueue = new LinkedList(); /** * Tracks what chunks are already queued to be asynchronously loaded. Used so we don't have two threads generating/fetching the same chunk */ - Map queuedChunkMap = new HashMap(); + Map queuedChunkMap = new HashMap(); /** * The lock for thread safety @@ -49,7 +57,7 @@ public class ServerChunkCache { */ public Collection getContents(){ lock.acquireUninterruptibly(); - Collection rVal = Collections.unmodifiableCollection(chunkCache.values()); + Collection 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 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 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 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 getCache(int stride){ + switch(stride){ + case 0: { + return cacheMapFullRes; + } + case 1: { + return cacheMapHalfRes; + } + default: { + throw new Error("Invalid stride probided! " + stride); + } + } + } } diff --git a/src/main/java/electrosphere/server/terrain/manager/ServerTerrainManager.java b/src/main/java/electrosphere/server/terrain/manager/ServerTerrainManager.java index 3a3ad4b9..e022d83b 100644 --- a/src/main/java/electrosphere/server/terrain/manager/ServerTerrainManager.java +++ b/src/main/java/electrosphere/server/terrain/manager/ServerTerrainManager.java @@ -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); } }