From 2b82eb88c891a1d1d334a5bddc1af3682609164f Mon Sep 17 00:00:00 2001 From: James Seibel Date: Sat, 19 Jun 2021 20:36:13 -0500 Subject: [PATCH] Partially address #28 (improve LOD chunk generation outside normal view distance) --- .../seibel/lod/builders/LodBufferBuilder.java | 33 ++- .../lod/builders/LodChunkGenWorker.java | 206 +++++++++++++----- .../seibel/lod/handlers/LodConfigHandler.java | 15 +- .../com/seibel/lod/objects/LodDimension.java | 6 +- .../java/com/seibel/lod/render/LodRender.java | 4 +- .../resources/META-INF/accesstransformer.cfg | 4 + 6 files changed, 204 insertions(+), 64 deletions(-) diff --git a/src/main/java/com/seibel/lod/builders/LodBufferBuilder.java b/src/main/java/com/seibel/lod/builders/LodBufferBuilder.java index d7868c472..81c41189a 100644 --- a/src/main/java/com/seibel/lod/builders/LodBufferBuilder.java +++ b/src/main/java/com/seibel/lod/builders/LodBufferBuilder.java @@ -14,6 +14,8 @@ import com.seibel.lod.util.LodUtils; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.BufferBuilder; import net.minecraft.util.math.ChunkPos; +import net.minecraft.world.biome.BiomeContainer; +import net.minecraft.world.chunk.ChunkStatus; import net.minecraft.world.server.ServerWorld; import net.minecraftforge.common.WorldWorkerManager; @@ -21,7 +23,7 @@ import net.minecraftforge.common.WorldWorkerManager; * This object is used to create NearFarBuffer objects. * * @author James Seibel - * @version 05-06-2021 + * @version 06-19-2021 */ public class LodBufferBuilder { @@ -48,7 +50,7 @@ public class LodBufferBuilder /** If this is greater than 0 no new chunk generation requests will be made * this is to prevent chunks from being generated for a long time in an area * the player is no longer in. */ - public int numberOfChunksWaitingToGenerate = 0; + public volatile int numberOfChunksWaitingToGenerate = 0; /** how many chunks to generate outside of the player's * view distance at one time. (or more specifically how @@ -63,7 +65,8 @@ public class LodBufferBuilder } - + private BiomeContainer biomeContainer = null; + private LodDimension previousDimension = null; /** @@ -85,6 +88,12 @@ public class LodBufferBuilder if (buildableNearBuffer == null || buildableFarBuffer == null) throw new IllegalStateException("generateLodBuffersAsync was called before the buildableNearBuffer and buildableFarBuffer were created."); + if (previousDimension != lodDim) + { + biomeContainer = LodUtils.getServerWorldFromDimension(lodDim.dimension).getChunk(0, 0, ChunkStatus.EMPTY).getBiomes(); + previousDimension = lodDim; + } + generatingBuffers = true; @@ -108,6 +117,9 @@ public class LodBufferBuilder int chunkGenIndex = 0; ChunkPos[] chunksToGen = new ChunkPos[maxChunkGenRequests]; + // if we don't have a full number of chunks to generate in chunksToGen + // we can top it off from the reserve + ChunkPos[] chunksToGenReserve = new ChunkPos[maxChunkGenRequests]; int minChunkDist = Integer.MAX_VALUE; ChunkPos playerChunkPos = new ChunkPos((int)playerX / LodChunk.WIDTH, (int)playerZ / LodChunk.WIDTH); @@ -145,7 +157,7 @@ public class LodBufferBuilder { // generate a new chunk if no chunk currently exists // and we aren't waiting on any other chunks to generate - if (lod == null && numberOfChunksWaitingToGenerate == 0) + if (lod == null && numberOfChunksWaitingToGenerate < maxChunkGenRequests) { ChunkPos pos = new ChunkPos(chunkX, chunkZ); @@ -158,6 +170,7 @@ public class LodBufferBuilder // this chunk is closer, clear any previous // positions and update the new minimum distance minChunkDist = newDistance; + chunksToGenReserve = chunksToGen; chunkGenIndex = 0; chunksToGen = new ChunkPos[maxChunkGenRequests]; @@ -202,6 +215,15 @@ public class LodBufferBuilder { ServerWorld serverWorld = LodUtils.getServerWorldFromDimension(lodDim.dimension); + // make sure we have as many chunks to generate as we are allowed + if (chunkGenIndex < maxChunkGenRequests) + { + for(int i = chunkGenIndex, j = 0; i < maxChunkGenRequests; i++, j++) + { + chunksToGen[i] = chunksToGenReserve[j]; + } + } + // start chunk generation for(ChunkPos chunkPos : chunksToGen) { @@ -210,7 +232,7 @@ public class LodBufferBuilder numberOfChunksWaitingToGenerate++; - LodChunkGenWorker genWorker = new LodChunkGenWorker(chunkPos, renderer, lodBuilder, this, lodDim, serverWorld); + LodChunkGenWorker genWorker = new LodChunkGenWorker(chunkPos, renderer, lodBuilder, this, lodDim, serverWorld, biomeContainer); WorldWorkerManager.addWorker(genWorker); } } @@ -238,7 +260,6 @@ public class LodBufferBuilder - //====================// // generation helpers // //====================// diff --git a/src/main/java/com/seibel/lod/builders/LodChunkGenWorker.java b/src/main/java/com/seibel/lod/builders/LodChunkGenWorker.java index 8167fe5b0..c72c34867 100644 --- a/src/main/java/com/seibel/lod/builders/LodChunkGenWorker.java +++ b/src/main/java/com/seibel/lod/builders/LodChunkGenWorker.java @@ -1,12 +1,28 @@ package com.seibel.lod.builders; +import java.awt.Color; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import com.seibel.lod.enums.LodDetail; +import com.seibel.lod.handlers.LodConfigHandler; +import com.seibel.lod.objects.LodChunk; +import com.seibel.lod.objects.LodDataPoint; import com.seibel.lod.objects.LodDimension; import com.seibel.lod.objects.LodRegion; import com.seibel.lod.proxy.ClientProxy; import com.seibel.lod.render.LodRender; +import com.seibel.lod.util.LodUtils; import net.minecraft.util.math.ChunkPos; +import net.minecraft.world.biome.Biome; +import net.minecraft.world.biome.BiomeContainer; +import net.minecraft.world.chunk.Chunk; import net.minecraft.world.chunk.ChunkStatus; +import net.minecraft.world.chunk.IChunk; +import net.minecraft.world.gen.WorldGenRegion; import net.minecraft.world.server.ServerWorld; import net.minecraftforge.common.WorldWorkerManager.IWorker; @@ -14,71 +30,59 @@ import net.minecraftforge.common.WorldWorkerManager.IWorker; * This is used to generate a LodChunk at a given ChunkPos. * * @author James Seibel - * @version 6-13-2021 + * @version 6-19-2021 */ public class LodChunkGenWorker implements IWorker { - private ServerWorld serverWorld; - private ChunkPos pos; - private LodDimension lodDim; - private LodBuilder lodBuilder; - private LodBufferBuilder lodBufferBuilder; - private LodRender lodRender; + // this seems to be faster as a singled threaded process + private static final ExecutorService genThread = Executors.newSingleThreadExecutor(); + + private boolean threadStarted = false; + private LodChunkGenThread thread; + public LodChunkGenWorker(ChunkPos newPos, LodRender newLodRenderer, LodBuilder newLodBuilder, LodBufferBuilder newLodBufferBuilder, - LodDimension newLodDimension, ServerWorld newServerWorld) + LodDimension newLodDimension, ServerWorld newServerWorld, + BiomeContainer newBiomeContainer) { - serverWorld = newServerWorld; - if (serverWorld == null) + if (newServerWorld == null) throw new IllegalArgumentException("LodChunkGenWorker must have a non-null ServerWorld"); - pos = newPos; - lodDim = newLodDimension; - lodBuilder = newLodBuilder; - lodBufferBuilder = newLodBufferBuilder; - lodRender = newLodRenderer; + thread = new LodChunkGenThread(newPos, newLodRenderer, + newLodBuilder, newLodBufferBuilder, + newLodDimension, newServerWorld, + newBiomeContainer); } @Override public boolean doWork() { - if (pos != null) + if (!threadStarted) { - int x = pos.x; - int z = pos.z; - - // only generate LodChunks if they can - // be added to the current LodDimension - if (lodDim.regionIsInRange(pos.x / LodRegion.SIZE, pos.z / LodRegion.SIZE)) - { - //long startTime = System.currentTimeMillis(); - lodBuilder.generateLodChunkAsync(serverWorld.getChunk(x, z, ChunkStatus.FEATURES), ClientProxy.getLodWorld(), serverWorld); - //long endTime = System.currentTimeMillis(); - //System.out.println(endTime - startTime + "\t" + lodBuilder.hasBlockData(chunk)); - - // this is called so that the new LOD chunk is drawn - // after it is generated - lodRender.regenerateLODsNextFrame(); - - - // useful for debugging -// ClientProxy.LOGGER.info(lodDim.getNumberOfLods()); - -// if (lodDim.getLodFromCoordinates(x, z) != null) -// ClientProxy.LOGGER.info(x + " " + z + " Success!"); -// else -// ClientProxy.LOGGER.info(x + " " + z); - } - // can be used for debugging - //else - //{ - // System.out.println("Out of range " + x + " " + z); - //} - - lodBufferBuilder.numberOfChunksWaitingToGenerate--; - - pos = null; + // make sure we don't generate this chunk again + thread.lodDim.addLod(new LodChunk(thread.pos)); + + thread.lodBufferBuilder.numberOfChunksWaitingToGenerate--; + + if (LodConfigHandler.CLIENT.distanceBiomeOnlyGeneration.get()) + { + // if we are using biome only generation + // that can be done asynchronously + genThread.execute(thread); + } + else + { + // if we are using normal generation that has to be done + // synchronously to prevent crashing and harmful + // interactions with the normal world generator + thread.run(); + } + + threadStarted = true; + + // useful for debugging +// ClientProxy.LOGGER.info(lodDim.getNumberOfLods()); } return false; @@ -87,9 +91,109 @@ public class LodChunkGenWorker implements IWorker @Override public boolean hasWork() { - return pos != null; + return !threadStarted; } + + + + private class LodChunkGenThread implements Runnable + { + public final ServerWorld serverWorld; + public final LodDimension lodDim; + public final LodBuilder lodBuilder; + public final LodRender lodRender; + public final BiomeContainer biomeContainer; + private LodBufferBuilder lodBufferBuilder; + + private ChunkPos pos; + + public LodChunkGenThread(ChunkPos newPos, LodRender newLodRenderer, + LodBuilder newLodBuilder, LodBufferBuilder newLodBufferBuilder, + LodDimension newLodDimension, ServerWorld newServerWorld, + BiomeContainer newBiomeContainer) + { + pos = newPos; + lodRender = newLodRenderer; + lodBuilder = newLodBuilder; + lodBufferBuilder = newLodBufferBuilder; + lodDim = newLodDimension; + serverWorld = newServerWorld; + biomeContainer = newBiomeContainer; + } + + + + @Override + public void run() + { + // only generate LodChunks if they can + // be added to the current LodDimension + if (lodDim.regionIsInRange(pos.x / LodRegion.SIZE, pos.z / LodRegion.SIZE)) + { + long startTime = System.currentTimeMillis(); + + if (LodConfigHandler.CLIENT.distanceBiomeOnlyGeneration.get()) + { + Chunk chunk = new Chunk(serverWorld, pos, biomeContainer); + List chunkList = new LinkedList<>(); + chunkList.add(chunk); + + WorldGenRegion worldGenRegion = new WorldGenRegion(serverWorld, chunkList); + Biome biome = worldGenRegion.getBiome(pos.asBlockPos()); + + //biome.buildSurface(serverWorld.rand, chunk, pos.x, pos.z, 0, 0, Blocks.STONE.getDefaultState(), Blocks.WATER.getDefaultState(), serverWorld.getSeaLevel(), serverWorld.getSeed()); + + // biome.buildSurface(Random random, IChunk chunkIn, int x, int z, int startHeight, double noise, BlockState defaultBlock, BlockState defaultFluid, int seaLevel, long seed) + //chunkGen.generateSurface(worldGenRegion, chunk); + + // generate features + //chunkGen.func_230351_a_(worldGenRegion, + //serverWorld.field_241106_P_); // StructureManager + + LodDataPoint[][] details = new LodDataPoint[1][1]; + Color color; + if (biome.getCategory() == Biome.Category.OCEAN) + { + color = LodUtils.intToColor(biome.getWaterColor()); + } + else if (biome.getCategory() == Biome.Category.ICY) + { + color = Color.WHITE; + } + else + { + color = LodUtils.intToColor(biome.getFoliageColor()); + } + + + details[0][0] = new LodDataPoint(serverWorld.getSeaLevel(), 0, color); + LodChunk lod = new LodChunk(pos, details , LodDetail.SINGLE); + lodDim.addLod(lod); + } + else + { + lodBuilder.generateLodChunkAsync(serverWorld.getChunk(pos.x, pos.z, ChunkStatus.FEATURES), ClientProxy.getLodWorld(), serverWorld); + } + + lodRender.regenerateLODsNextFrame(); + + +// if (lodDim.getLodFromCoordinates(pos.x, pos.z) != null) +// ClientProxy.LOGGER.info(pos.x + " " + pos.z + " Success!"); +// else +// ClientProxy.LOGGER.info(pos.x + " " + pos.z); + + long endTime = System.currentTimeMillis(); + System.out.println(endTime - startTime); + + }// if in range + + }// run + + } + + /* * performance/generation tests related to * serverWorld.getChunk(x, z, ChunkStatus. *** ) diff --git a/src/main/java/com/seibel/lod/handlers/LodConfigHandler.java b/src/main/java/com/seibel/lod/handlers/LodConfigHandler.java index 59c397491..c202f212b 100644 --- a/src/main/java/com/seibel/lod/handlers/LodConfigHandler.java +++ b/src/main/java/com/seibel/lod/handlers/LodConfigHandler.java @@ -21,7 +21,7 @@ import net.minecraftforge.fml.config.ModConfig; /** * * @author James Seibel - * @version 6-17-2021 + * @version 6-19-2021 */ @Mod.EventBusSubscriber public class LodConfigHandler @@ -38,6 +38,8 @@ public class LodConfigHandler public ForgeConfigSpec.EnumValue lodDetail; + public ForgeConfigSpec.BooleanValue distanceBiomeOnlyGeneration; + /** this is multiplied by the default view distance * to determine how far out to generate/render LODs */ public ForgeConfigSpec.IntValue lodChunkRadiusMultiplier; @@ -73,7 +75,7 @@ public class LodConfigHandler + " How should the LODs be drawn? \n" + " " + LodTemplate.CUBIC.toString() + ": LOD Chunks are drawn as rectangular prisms (boxes). \n" + " " + LodTemplate.TRIANGULAR.toString() + ": LOD Chunks smoothly transition between other. \n" - + " " + LodTemplate.DYNAMIC.toString() + ": LOD Chunks smoothly transition between other, " + + " " + LodTemplate.DYNAMIC.toString() + ": LOD Chunks smoothly transition between other, \n" + " " + " unless a neighboring chunk is at a significantly different height. ") .defineEnum("lodTemplate", LodTemplate.CUBIC); @@ -92,6 +94,15 @@ public class LodConfigHandler + " of LODs in each cardinal direction. ") .defineInRange("lodChunkRadiusMultiplier", 6, 2, 32); + distanceBiomeOnlyGeneration = builder + .comment("\n" + + " If true LODs generated outside the normal view distance \n" + + " will be created using a simpler faster method \n" + + " at the cost of visual quality. \n" + + " Nearby chunks will still use the full quality method \n" + + " and will overwrite the lower quality ones. ") + .define("distanceBiomeOnlyGeneration", false); + builder.pop(); } } diff --git a/src/main/java/com/seibel/lod/objects/LodDimension.java b/src/main/java/com/seibel/lod/objects/LodDimension.java index 5b2ead795..9441ea0b3 100644 --- a/src/main/java/com/seibel/lod/objects/LodDimension.java +++ b/src/main/java/com/seibel/lod/objects/LodDimension.java @@ -17,7 +17,7 @@ import net.minecraft.world.server.ServerWorld; * for a given dimension. * * @author James Seibel - * @version 03-19-2021 + * @version 06-19-2021 */ public class LodDimension { @@ -26,8 +26,8 @@ public class LodDimension private volatile int width; private volatile int halfWidth; - public LodRegion regions[][]; - public boolean isRegionDirty[][]; + public volatile LodRegion regions[][]; + public volatile boolean isRegionDirty[][]; private int centerX; private int centerZ; diff --git a/src/main/java/com/seibel/lod/render/LodRender.java b/src/main/java/com/seibel/lod/render/LodRender.java index 3d2ce9c0a..23def246d 100644 --- a/src/main/java/com/seibel/lod/render/LodRender.java +++ b/src/main/java/com/seibel/lod/render/LodRender.java @@ -46,7 +46,7 @@ import net.minecraft.util.math.vector.Vector3f; * This is where LODs are draw to the world. * * @author James Seibel - * @version 06-17-2021 + * @version 06-19-2021 */ public class LodRender { @@ -104,7 +104,7 @@ public class LodRender /** if this is true the LOD buffers should be regenerated, * provided they aren't already being regenerated. */ - private boolean regen = false; + private volatile boolean regen = false; /** This HashSet contains every chunk that Vanilla Minecraft * is going to render */ diff --git a/src/main/resources/META-INF/accesstransformer.cfg b/src/main/resources/META-INF/accesstransformer.cfg index 29413c7ff..4eca63a17 100644 --- a/src/main/resources/META-INF/accesstransformer.cfg +++ b/src/main/resources/META-INF/accesstransformer.cfg @@ -22,6 +22,10 @@ public net.minecraft.client.renderer.WorldRenderer field_72755_R # renderInfos public net.minecraft.client.renderer.WorldRenderer$LocalRenderInformationContainer field_178036_a # renderChunk +# used in world generation +public net.minecraft.world.server.ServerWorld field_241106_P_ # structuremanager + + #=====================# # Examples from Forge # #=====================#