diff --git a/_wiki-files/class_diagram.drawio b/_wiki-files/class_diagram.drawio new file mode 100644 index 000000000..59bbb0fd0 --- /dev/null +++ b/_wiki-files/class_diagram.drawio @@ -0,0 +1 @@ +7V1db5s6GP41kbaLTHxDLpt063aWHfWs2zrtzgtOwhnBOcZpm/36Y2M7CRg60mGoNEuVGt44Bvw876eNGbmzzcMVBtv1BxTDdORY8cPIvRw5jmPZDv3HJHsuCQIhWOEk5iL7KLhJfkIhtIR0l8QwLzUkCKUk2ZaFC5RlcEFKMoAxui83W6K0fNYtWEFFcLMAqSq9TWKy5tLICY/ytzBZreWZ7WDCv9kA2VjcSb4GMbo/EbmvR+4MI0T4p83DDKZs8OS43L7b36bzH8HVX//k/4HP0/ef/v4y5p29Oecnh1vAMCNP7prcfA4vxsv3wY+3YTb+mb27Dq/GchhyspcDBmM6fuIQYbJGK5SB9PVROsVol8WQdWvRo2ObOUJbKrSp8F9IyF6QAewIoqI12aTi25xg9APOUIowlcRwCXYpvbEpzOILBjkVZiiDXPImSVNxKn6t7AIr2P5iYES7HO3wAj4yGq4vCArwCpLHGk4O+FPFgWgDCd7TH2KYApLclS8PCAavDu0OP71GCb1wxxLa5kqqCV1z/KjcBb8D8atTrNWOXlmu51Ga+1HgWGGp28NpZLf8fpVu6YeTuzuKCmKdQTJpCvol2eBkmrQkk+f2QiYvsp5MplJHftAzffw/kT6cFS3oE9i90Cd0PB22KLLakYliAfYnzbasQa6DbtIJ3IF0JwZvjuJbhGngUeVhfp9sUlCwY4kyIinJ+APSZJXRzwtKB0jd3PQOYpLQuORCfEEYEaeLdZLGc7BHO4ZxTsDihzyarhFOftJuwdFvAkwEY52g1OKG/VJQE8OctrmWTLQrog/godRwDnIiBAuUpmCbJ98Pt7GhcCTZFBGCNiXms9uBD0/gvspUGWt6Fe8nqXF/jNtsaXvWJzFbFDSTu8SOs6kQKFQYOVMWhSaQhaEXTD/3W6jwgo4MUeIcYUaW1IZURJIrKVySRqbkW7BIstW8aHPpHSUfxUgwEaK/XaaF1VoncQyzwhQSQACHlOEnVIdeqD+lf3RAZ9Yrf+TTC5/RY/t4TP9Yc0xmKKP3ApICXUj5cg8ZZ9pR4REtU7kgsQ/aQe9ogz6sgb6CcpoU6HGUZXJhPwniDQWr8FsC00+FlxrbCu6uirtbg3EKvsP0GuUJSRDrH/O2FewHg9d3Wmq2LnSjBsWm3a9R/IIp9Uuj3/oIELQ07fr0e1Ln5S+TDcxypjLG0/fo6d22dLBlNapzPniqRRjzqtQyWeXcFMzhHUzzWSEyJuF8k9BQNHkuLt92nRoOzJzRhZUy5GdM/ajLxydsOMoMHzrmQ9sYQRsfPNuEgPrgDaNhQ0BPVXYTAvZJgGhoex+5CqjPoK7YNDcyei71Rr+TcnVzPa++iuhblXKkVZnt+kX7wPcqhOFX0GnpUI7haVLxeJRg0ooO04qD+ThEEGpacShrl0yMdAbdexmvNq0o4skbuGCuWSQXH9AdM+BXOInnCR1PJyjUfn7SkspWhSngjtt4pHM9UtNMW3MGUk8XbRGnOvVgIs7O4K1JKGrh1RZxNs0m7LYxIHBOg84YUj+RFjpPFWKL8q/83zcRid4hegaj9x0To6b61K/eR6qX4MygkZjhxCCcqMlO+uWEbzVyIvuEoUlIO4f8UFweDvOmmsRiRwzmWjB31TJUv5jbVlNYkEPyEWYxxHTs36Rglb8wFl8nFQJvaCrYdYsOBBWuYAYxYJG14YJ+LkyGDgn9usmHggrgDl4mmOwPRQEDfsdlAHvw2K+mjohYkRi+BRlN2fGIndq1j8U0U1LUU1L0K6tgA9tuxw1taxL9unDB1Ija6n7T7MWZJUBdNSJfXZbEzX6KQGwsvibU3aEjv6Ap2Wfe3sCuCfbB636BGuSV53uMa+9vtjDyWtLBtxxdfFDrP2y28GTYCzBY3icfjzEG4RyD0PSs1HOZAAxVgzAWt8qgMfBrhb/tBKE+f6DmfeOi+lO4g2s29WPQ14V+zYLEntFXY/8y+t8M+trQt62hc4Cw3vkL+C9PZoELFnzfEzMf1D0N3KFzglBNBRkNtqJkckmHlBMgp2EhMUvBNPHAHzwUVNeCnYaCRx6wQTEs0MOCaOiIMKxfP7pgyBkK9JESWkOHhWF9UpCyUxsK9EGB4cvDdesDzZxPZ1WfljGfrkmfqGmuny8MrqwA5cHg18Onb/wTVTSIT6vHL40l6J4pQzuDoO4JAbFS+PokRXhh0O8e/cngfqBpqSBF/8tJYmDQ7x59t/XDZNrQb1odSNGfyYTAQK8Bem/wYkDTfkUU+rlMBAz0GqCXVdnhng1qXBpiPL529CdDx3th03qw3Hh83eh7ztDxXvjI8wDG4+uE3h882Gvy+Lnx+HqhD4cO9iaBguofvC+NXEjf05785+5L41U3LRY5etO+NNX2bmhVCKNhX5pIrRs8upehWWd6PpVbP0JyeDPKrwyMvs0uIzWuYFNKG/AgY8rySkMxsdT8zJHxOr/yOlwFn+/K00n9cgO+8WWCFyn8COIE7XLDiv5YMfiC1Kgu/TQTjx3B23bFqa55x0lTesnnHc1Gx3pgH3xrGds2GcapGkQtMwxNL2ryJuUA0ZfHT34lTv15qlti+uIdF2230JTvfdKaqkzUusccxdMdTSfM9pn9Jiqtt890K2+G6nBvHLMF9+84Iq5Mv5106Io/bEsNQKiy802PjLbr1faxUzHvrRc46FN3WRkx6v4kdRf69Hz1XTLHxJ18OKy+As/fhE1Vy+luuYTYhGVDGOogGtxQuyYu+y1DzTVqAENND48vLOc52/G17+7r/wE= diff --git a/src/main/java/com/seibel/lod/core/api/ApiShared.java b/src/main/java/com/seibel/lod/core/api/ApiShared.java index 36f8635b5..6dd1cb740 100644 --- a/src/main/java/com/seibel/lod/core/api/ApiShared.java +++ b/src/main/java/com/seibel/lod/core/api/ApiShared.java @@ -43,6 +43,9 @@ public class ApiShared /** Used to determine if the LODs should be regenerated */ public static int previousLodRenderDistance = 0; + /** Signal whether a world is shutting down */ + public static volatile boolean isShuttingDown = false; + private ApiShared() diff --git a/src/main/java/com/seibel/lod/core/api/ClientApi.java b/src/main/java/com/seibel/lod/core/api/ClientApi.java index eb338e824..2fc973d4e 100644 --- a/src/main/java/com/seibel/lod/core/api/ClientApi.java +++ b/src/main/java/com/seibel/lod/core/api/ClientApi.java @@ -19,20 +19,28 @@ package com.seibel.lod.core.api; +import java.util.HashSet; + import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import com.seibel.lod.core.ModInfo; +import com.seibel.lod.core.enums.WorldType; +import com.seibel.lod.core.enums.config.DistanceGenerationMode; import com.seibel.lod.core.objects.lod.LodDimension; import com.seibel.lod.core.objects.math.Mat4f; import com.seibel.lod.core.render.GLProxy; import com.seibel.lod.core.render.LodRenderer; import com.seibel.lod.core.util.DetailDistanceUtil; import com.seibel.lod.core.util.SingletonHandler; +import com.seibel.lod.core.wrapperInterfaces.IWrapperFactory; +import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper; +import com.seibel.lod.core.wrapperInterfaces.chunk.IChunkWrapper; import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton; import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper; import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftWrapper; import com.seibel.lod.core.wrapperInterfaces.minecraft.IProfilerWrapper; +import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper; /** * This holds the methods that should be called @@ -69,14 +77,27 @@ public class ClientApi } + + private HashSet toBeLoaded = null; - + // TODO: Fix it + @Deprecated + public void clientChunkLoadEvent(IChunkWrapper chunk, IWorldWrapper world) + { + //EventApi.INSTANCE.chunkLoadEvent(chunk, world.getDimensionType()); + //ClientApi.LOGGER.info("ChunkLoadEvent called for "+ (world.getWorldType() == WorldType.ClientWorld ? "clientLevel" : "serverLevel"), new RuntimeException()); + + if (toBeLoaded == null) toBeLoaded = new HashSet(); + //toBeLoaded.add(WRAPPER_FACTORY.createChunkPos(chunk.getChunkPosX(), chunk.getChunkPosZ())); + } + + private HashSet lastFrame = new HashSet(); public void renderLods(Mat4f mcModelViewMatrix, Mat4f mcProjectionMatrix, float partialTicks) { // comment out when creating a release applyConfigOverrides(); - + // clear any out of date objects MC.clearFrameObjectCache(); @@ -85,8 +106,19 @@ public class ClientApi // only run the first time setup once if (!firstTimeSetupComplete) firstFrameSetup(); - - + + IWorldWrapper world = MC.getWrappedClientWorld(); + HashSet chunks = MC_RENDER.getVanillaRenderedChunks(); + if (chunks != null) { + for (AbstractChunkPosWrapper pos : chunks) { + if (lastFrame.contains(pos)) continue; + IChunkWrapper chunk = world.tryGetChunk(pos); + if (chunk == null) continue; + ApiShared.lodBuilder.generateLodNodeDirect(chunk, ApiShared.lodWorld, + world.getDimensionType(), DistanceGenerationMode.FULL, true); + } + } + lastFrame = chunks; if (!MC.playerExists() || ApiShared.lodWorld.getIsWorldNotLoaded()) return; @@ -98,6 +130,8 @@ public class ClientApi EVENT_API.viewDistanceChangedEvent(); EVENT_API.playerMoveEvent(lodDim); + + lodDim.cutRegionNodesAsync(MC.getPlayerBlockPos().getX(), MC.getPlayerBlockPos().getZ()); lodDim.expandOrLoadRegionsAsync(MC.getPlayerBlockPos().getX(), MC.getPlayerBlockPos().getZ()); diff --git a/src/main/java/com/seibel/lod/core/api/EventApi.java b/src/main/java/com/seibel/lod/core/api/EventApi.java index 0930c48ac..12a04ed66 100644 --- a/src/main/java/com/seibel/lod/core/api/EventApi.java +++ b/src/main/java/com/seibel/lod/core/api/EventApi.java @@ -23,9 +23,10 @@ import org.lwjgl.glfw.GLFW; import com.seibel.lod.core.builders.lodBuilding.LodBuilder; import com.seibel.lod.core.builders.worldGeneration.LodWorldGenerator; -import com.seibel.lod.core.enums.config.DistanceGenerationMode; +import com.seibel.lod.core.enums.WorldType; import com.seibel.lod.core.objects.lod.LodDimension; import com.seibel.lod.core.objects.lod.RegionPos; +import com.seibel.lod.core.render.GLProxy; import com.seibel.lod.core.render.LodRenderer; import com.seibel.lod.core.util.DataPointUtil; import com.seibel.lod.core.util.DetailDistanceUtil; @@ -64,8 +65,12 @@ public class EventApi { } - - + + @Deprecated + public void chunkLoadEvent(IChunkWrapper chunk, IDimensionTypeWrapper dimType) + { + //ApiShared.lodBuilder.generateLodNodeAsync(chunk, ApiShared.lodWorld, dimType, DistanceGenerationMode.FULL, true); + } //=============// @@ -80,8 +85,8 @@ public class EventApi LodDimension lodDim = ApiShared.lodWorld.getLodDimension(MC.getCurrentDimension()); if (lodDim == null) return; + if (ApiShared.isShuttingDown) return; - // FIXME: This is in server thread. We shouldn't be accessing the client's renderer! LodWorldGenerator.INSTANCE.queueGenerationRequests(lodDim, ApiShared.lodBuilder); } @@ -92,19 +97,22 @@ public class EventApi // world events // //==============// - public void chunkLoadEvent(IChunkWrapper chunk, IDimensionTypeWrapper dimType) - { - ApiShared.lodBuilder.generateLodNodeAsync(chunk, ApiShared.lodWorld, dimType, DistanceGenerationMode.FULL); - } - public void worldSaveEvent() { - ApiShared.lodWorld.saveAllDimensions(); + ApiShared.lodWorld.saveAllDimensions(false); // Do an async save. } + private boolean isCurrentlyOnSinglePlayerServer = false; + /** This is also called when a new dimension loads */ public void worldLoadEvent(IWorldWrapper world) { + ClientApi.LOGGER.info("WorldLoadEvent called here for "+ (world.getWorldType() == WorldType.ClientWorld ? + "clientLevel" : "serverLevel"), new RuntimeException()); + // Always ignore ServerWorld event + if (world.getWorldType() == WorldType.ServerWorld) return; + isCurrentlyOnSinglePlayerServer = MC.hasSinglePlayerServer(); + ApiShared.isShuttingDown = false; DataPointUtil.WORLD_HEIGHT = world.getHeight(); LodBuilder.MIN_WORLD_HEIGHT = world.getMinHeight(); // This updates the World height @@ -120,17 +128,22 @@ public class EventApi } /** This is also called when the user disconnects from a server+ */ - public void worldUnloadEvent() + public void worldUnloadEvent(IWorldWrapper world) { + ClientApi.LOGGER.info("WorldUnloadEvent called here for "+ (world.getWorldType() == WorldType.ClientWorld ? "clientLevel" : "serverLevel"), new RuntimeException()); + // If it's single player, ignore the client side world unload event + // Note: using this as often API call unload event AFTER setting MC to not be in a singlePlayerServer + if (isCurrentlyOnSinglePlayerServer && world.getWorldType() == WorldType.ClientWorld) return; // the player just unloaded a world/dimension ThreadMapUtil.clearMaps(); // ClientApi.renderer.markForCleanup(); // ClientApi.renderer.destroyBuffers(); - - new Thread(() -> checkIfDisconnectedFromServer()).start(); + checkIfDisconnectedFromServer(); + //new Thread(() -> checkIfDisconnectedFromServer()).start(); } private void checkIfDisconnectedFromServer() { + /* try { // world unloading events are called before disconnecting from the server, @@ -141,10 +154,10 @@ public class EventApi { // this should never happen, but just in case e.printStackTrace(); - } + }*/ - if (MC.getWrappedClientWorld() == null || (!MC.connectedToServer() && !MC.hasSinglePlayerServer())) + //if (MC.getWrappedClientWorld() == null || (!MC.connectedToServer() && !MC.hasSinglePlayerServer())) { // the player just left the server @@ -152,16 +165,17 @@ public class EventApi // if this isn't done unfinished tasks may be left in the queue // preventing new LodChunks form being generated + ApiShared.isShuttingDown = true; LodWorldGenerator.INSTANCE.restartExecutorService(); LodWorldGenerator.INSTANCE.numberOfChunksWaitingToGenerate.set(0); - ApiShared.lodWorld.deselectWorld(); - + ApiShared.lodWorld.deselectWorld(); // This force a save // prevent issues related to the buffer builder // breaking or retaining previous data when changing worlds. ClientApi.renderer.destroyBuffers(); ClientApi.renderer.requestCleanup(); + GLProxy.ensureAllGLJobCompleted(); recalculateWidths = true; // TODO: Check if after the refactoring, is this still needed ClientApi.renderer = new LodRenderer(ApiShared.lodBufferBuilderFactory); @@ -205,6 +219,7 @@ public class EventApi } } + // NOTE: This is being called from Render Thread. /** Re-centers the given LodDimension if it needs to be. */ public void playerMoveEvent(LodDimension lodDim) { @@ -213,7 +228,6 @@ public class EventApi RegionPos worldRegionOffset = new RegionPos(playerRegionPos.x - lodDim.getCenterRegionPosX(), playerRegionPos.z - lodDim.getCenterRegionPosZ()); if (worldRegionOffset.x != 0 || worldRegionOffset.z != 0) { - ApiShared.lodWorld.saveAllDimensions(); lodDim.move(worldRegionOffset); //LOGGER.info("offset: " + worldRegionOffset.x + "," + worldRegionOffset.z + "\t center: " + lodDim.getCenterX() + "," + lodDim.getCenterZ()); } @@ -236,12 +250,10 @@ public class EventApi // do the dimensions need to change in size? if (ApiShared.lodBuilder.defaultDimensionWidthInRegions != newWidth || recalculateWidths) { - ApiShared.lodWorld.saveAllDimensions(); - // update the dimensions to fit the new width ApiShared.lodWorld.resizeDimensionRegionWidth(newWidth); ApiShared.lodBuilder.defaultDimensionWidthInRegions = newWidth; - ClientApi.renderer.setupBuffers(ApiShared.lodWorld.getLodDimension(MC.getCurrentDimension())); + ClientApi.renderer.setupBuffers(); recalculateWidths = false; //LOGGER.info("new dimension width in regions: " + newWidth + "\t potential: " + newWidth ); diff --git a/src/main/java/com/seibel/lod/core/api/ModAccessorApi.java b/src/main/java/com/seibel/lod/core/api/ModAccessorApi.java new file mode 100644 index 000000000..adc1ec942 --- /dev/null +++ b/src/main/java/com/seibel/lod/core/api/ModAccessorApi.java @@ -0,0 +1,53 @@ +package com.seibel.lod.core.api; + +import java.util.HashMap; +import java.util.Map; + +import com.seibel.lod.core.wrapperInterfaces.modAccessor.IModAccessor; + +/** + * This class takes care of dependency injection + * for mods (for mod compatibility support). + * + * (Basically the same as SingletonHandler, except + * it can return null which means that mod aren't + * loaded in the game, or it haven't been implemented + * for that build.) + */ +public class ModAccessorApi { + private static final Map, IModAccessor> singletons = new HashMap, IModAccessor>(); + + public static void bind(Class interfaceClass, IModAccessor modAccessor) throws IllegalStateException + { + // make sure we haven't already bound this singleton + if (singletons.containsKey(interfaceClass)) + { + throw new IllegalStateException("The modAccessor [" + interfaceClass.getSimpleName() + "] has already been bound."); + } + + // make sure the given singleton implements the interface + boolean singletonImplementsInterface = false; + for (Class singletonInterface : modAccessor.getClass().getInterfaces()) + { + if (singletonInterface.equals(interfaceClass)) + { + singletonImplementsInterface = true; + break; + } + } + if (!singletonImplementsInterface) + { + throw new IllegalStateException("The singleton [" + interfaceClass.getSimpleName() + "] doesn't implement the interface [" + interfaceClass.getSimpleName() + "]."); + } + ClientApi.LOGGER.info("DistantHorizon: Registored mod comatibility accessor for "+modAccessor.getModName()); + singletons.put(interfaceClass, modAccessor); + } + + @SuppressWarnings("unchecked") + public static T get(Class objectClass) throws ClassCastException + { + IModAccessor modAccessor = singletons.get(objectClass); + return modAccessor==null ? null : (T) modAccessor; + } + +} diff --git a/src/main/java/com/seibel/lod/core/builders/bufferBuilding/CubicLodTemplate.java b/src/main/java/com/seibel/lod/core/builders/bufferBuilding/CubicLodTemplate.java index a12229f84..76730bc06 100644 --- a/src/main/java/com/seibel/lod/core/builders/bufferBuilding/CubicLodTemplate.java +++ b/src/main/java/com/seibel/lod/core/builders/bufferBuilding/CubicLodTemplate.java @@ -54,7 +54,12 @@ public class CubicLodTemplate int color; if (debugging != DebugMode.OFF) - color = LodUtil.DEBUG_DETAIL_LEVEL_COLORS[detailLevel].getRGB(); + { + if (debugging == DebugMode.SHOW_DETAIL || debugging == DebugMode.SHOW_DETAIL_WIREFRAME) + color = LodUtil.DEBUG_DETAIL_LEVEL_COLORS[detailLevel].getRGB(); + else ///if (debugging == DebugMode.SHOW_GENMODE || debugging == DebugMode.SHOW_GENMODE_WIREFRAME) + color = LodUtil.DEBUG_DETAIL_LEVEL_COLORS[DataPointUtil.getGenerationMode(data)].getRGB(); + } else color = DataPointUtil.getColor(data); diff --git a/src/main/java/com/seibel/lod/core/builders/bufferBuilding/LodBufferBuilderFactory.java b/src/main/java/com/seibel/lod/core/builders/bufferBuilding/LodBufferBuilderFactory.java index 100ffea61..646672558 100644 --- a/src/main/java/com/seibel/lod/core/builders/bufferBuilding/LodBufferBuilderFactory.java +++ b/src/main/java/com/seibel/lod/core/builders/bufferBuilding/LodBufferBuilderFactory.java @@ -78,7 +78,7 @@ public class LodBufferBuilderFactory public void end(String source) { timer = System.nanoTime() - timer; if (timer> 16000000) { //16 ms - ClientApi.LOGGER.info("NOTE: "+source+" took "+Duration.ofNanos(timer)+"!"); + ClientApi.LOGGER.debug("NOTE: "+source+" took "+Duration.ofNanos(timer)+"!"); } } @@ -280,7 +280,9 @@ public class LodBufferBuilderFactory vboY = buildableCenterBlockY; vboZ = buildableCenterBlockZ; buildableBuffers.move(playerRegionX, playerRegionZ); - buildableVbos.move(playerRegionX, playerRegionZ); + buildableVbos.move(playerRegionX, playerRegionZ, (bs) -> { + if (bs!=null) for (LodVertexBuffer b : bs) if (b!=null) b.close(); + }); setsToRender.move(playerRegionX, playerRegionZ); vertexOptimizerCache.move(playerRegionX, playerRegionZ); } @@ -370,7 +372,7 @@ public class LodBufferBuilderFactory long buildTime = endTime - startTime; long executeTime = executeEnd - executeStart; if (enableLogging) - ClientApi.LOGGER.info("Thread Build("+nodeToRenderThreads.size()+"/"+(lodDim.getWidth()*lodDim.getWidth())+ (fullRegen ? "FULL" : "")+") time: " + buildTime + " ms" + '\n' + + ClientApi.LOGGER.debug("Thread Build("+nodeToRenderThreads.size()+"/"+(lodDim.getWidth()*lodDim.getWidth())+ (fullRegen ? "FULL" : "")+") time: " + buildTime + " ms" + '\n' + "thread execute time: " + executeTime + " ms"); //================================// @@ -398,7 +400,7 @@ public class LodBufferBuilderFactory uploadBuffers(posToUpload); long uploadTime = System.currentTimeMillis() - startUploadTime; if (enableLogging) - ClientApi.LOGGER.info("Thread Upload time: " + uploadTime + " ms"); + ClientApi.LOGGER.debug("Thread Upload time: " + uploadTime + " ms"); } catch (Exception e) { @@ -459,21 +461,22 @@ public class LodBufferBuilderFactory int chunkXdist = LevelPosUtil.getChunkPos(detailLevel, posX) - playerChunkX; int chunkZdist = LevelPosUtil.getChunkPos(detailLevel, posZ) - playerChunkZ; - // Currently fixing below - // FIXME: We don't need to ignore rendered chunks! Just build it and leave it for the renderer to decide! - //We don't want to render this fake block if - //The block is inside the render distance with, is not bigger than a chunk and is positioned in a chunk set as vanilla rendered - // - //The block is in the player chunk or in a chunk adjacent to the player - //if(isThisPositionGoingToBeRendered(detailLevel, posX, posZ, playerChunkX, playerChunkZ, vanillaRenderedChunks, gameChunkRenderDistance)) - //{ - // continue; - //} + // TODO: In the future, We don't need to ignore rendered chunks! Just build it and leave it for the renderer to decide! + // We don't want to render this fake block if + // The block is inside the render distance with, is not bigger than a chunk and is positioned in a chunk set as vanilla rendered + + // The block is in the player chunk or in a chunk adjacent to the player + if(detailLevel <= LodUtil.CHUNK_DETAIL_LEVEL && + isThisPositionGoingToBeRendered(LevelPosUtil.getChunkPos(detailLevel, posX), + LevelPosUtil.getChunkPos(detailLevel, posZ))) + { + continue; + } //we check if the block to render is not in player chunk boolean posNotInPlayerChunk = !(chunkXdist == 0 && chunkZdist == 0); - - // We extract the adj data in the four cardinal direction + + // We extract the adj data in the four cardinal direction // we first reset the adjShadeDisabled. This is used to disable the shade on the border when we have transparent block like water or glass // to avoid having a "darker border" underground @@ -556,31 +559,20 @@ public class LodBufferBuilderFactory return true; } - - + // Will be removed in a1.7 @Deprecated - private boolean isThisPositionGoingToBeRendered(byte detailLevel, int posX, int posZ, int chunkPosX, int chunkPosZ, boolean[][] vanillaRenderedChunks, int gameChunkRenderDistance){ + private boolean isThisPositionGoingToBeRendered(int chunkX, int chunkZ){ + MovableGridList chunkGrid = ClientApi.renderer.vanillaRenderedChunks; + Boolean isRendered = chunkGrid.get(chunkX, chunkZ); + // skip any chunks that Minecraft is going to render - int chunkXdist = LevelPosUtil.getChunkPos(detailLevel, posX) - chunkPosX; - int chunkZdist = LevelPosUtil.getChunkPos(detailLevel, posZ) - chunkPosZ; - + if (isRendered == null || !isRendered) return false; + // check if the chunk is on the border - boolean isItBorderPos; if (CONFIG.client().graphics().advancedGraphics().getVanillaOverdraw() == VanillaOverdraw.BORDER) - isItBorderPos = LodUtil.isBorderChunk(vanillaRenderedChunks, chunkXdist + gameChunkRenderDistance + 1, chunkZdist + gameChunkRenderDistance + 1); + return !LodUtil.isBorderChunk(ClientApi.renderer.vanillaRenderedChunks, chunkX, chunkZ); else - isItBorderPos = false; - - - //boolean smallRenderDistance = gameChunkRenderDistance <= LodUtil.MINIMUM_RENDER_DISTANCE_FOR_PARTIAL_OVERDRAW; - - // get the positions that will be rendered - - return (gameChunkRenderDistance >= Math.abs(chunkXdist) - && gameChunkRenderDistance >= Math.abs(chunkZdist) - && detailLevel <= LodUtil.CHUNK_DETAIL_LEVEL - && vanillaRenderedChunks[chunkXdist + gameChunkRenderDistance + 1][chunkZdist + gameChunkRenderDistance + 1]) - && (!isItBorderPos); + return true; } @@ -838,7 +830,7 @@ public class LodBufferBuilderFactory private boolean swapBuffers() { bufferLock.lock(); - ClientApi.LOGGER.info("Lod Swap Buffers"); + ClientApi.LOGGER.debug("Lod Swap Buffers"); { boolean shouldRegenBuff = true; try diff --git a/src/main/java/com/seibel/lod/core/builders/lodBuilding/LodBuilder.java b/src/main/java/com/seibel/lod/core/builders/lodBuilding/LodBuilder.java index fd29d5475..e1f1dc0b3 100644 --- a/src/main/java/com/seibel/lod/core/builders/lodBuilding/LodBuilder.java +++ b/src/main/java/com/seibel/lod/core/builders/lodBuilding/LodBuilder.java @@ -90,10 +90,10 @@ public class LodBuilder public void generateLodNodeAsync(IChunkWrapper chunk, LodWorld lodWorld, IDimensionTypeWrapper dim) { - generateLodNodeAsync(chunk, lodWorld, dim, DistanceGenerationMode.FULL); + generateLodNodeAsync(chunk, lodWorld, dim, DistanceGenerationMode.FULL, false); } - public void generateLodNodeAsync(IChunkWrapper chunk, LodWorld lodWorld, IDimensionTypeWrapper dim, DistanceGenerationMode generationMode) + public void generateLodNodeAsync(IChunkWrapper chunk, LodWorld lodWorld, IDimensionTypeWrapper dim, DistanceGenerationMode generationMode, boolean override) { if (lodWorld == null || lodWorld.getIsWorldNotLoaded()) return; @@ -130,7 +130,7 @@ public class LodBuilder { lodDim = lodWorld.getLodDimension(dim); } - generateLodNodeFromChunk(lodDim, chunk, new LodBuilderConfig(generationMode)); + generateLodNodeFromChunk(lodDim, chunk, new LodBuilderConfig(generationMode), override); //} //catch (IllegalArgumentException | NullPointerException e) //{ @@ -142,6 +142,52 @@ public class LodBuilder }); lodGenThreadPool.execute(thread); } + + public void generateLodNodeDirect(IChunkWrapper chunk, LodWorld lodWorld, IDimensionTypeWrapper dim, DistanceGenerationMode generationMode, boolean override) + { + if (lodWorld == null || lodWorld.getIsWorldNotLoaded()) + return; + + // don't try to create an LOD object + // if for some reason we aren't + // given a valid chunk object + if (chunk == null) + return; + + //noinspection GrazieInspection + try + { + // we need a loaded client world in order to + // get the textures for blocks + if (MC.getWrappedClientWorld() == null) + return; + + // don't try to generate LODs if the user isn't in the world anymore + // (this happens a lot when the user leaves a world/server) + if (!MC.hasSinglePlayerServer() && !MC.connectedToServer()) + return; + + // make sure the dimension exists + LodDimension lodDim; + if (lodWorld.getLodDimension(dim) == null) + { + lodDim = new LodDimension(dim, lodWorld, defaultDimensionWidthInRegions); + lodWorld.addLodDimension(lodDim); + } + else + { + lodDim = lodWorld.getLodDimension(dim); + } + generateLodNodeFromChunk(lodDim, chunk, new LodBuilderConfig(generationMode), override); + } + catch (IllegalArgumentException | NullPointerException e) + { + e.printStackTrace(); + // if the world changes while LODs are being generated + // they will throw errors as they try to access things that no longer + // exist. + } + } /** * Creates a LodNode for a chunk in the given world. @@ -149,14 +195,14 @@ public class LodBuilder */ public void generateLodNodeFromChunk(LodDimension lodDim, IChunkWrapper chunk) throws IllegalArgumentException { - generateLodNodeFromChunk(lodDim, chunk, new LodBuilderConfig()); + generateLodNodeFromChunk(lodDim, chunk, new LodBuilderConfig(), false); } /** * Creates a LodNode for a chunk in the given world. * @throws IllegalArgumentException thrown if either the chunk or world is null. */ - public void generateLodNodeFromChunk(LodDimension lodDim, IChunkWrapper chunk, LodBuilderConfig config) + public void generateLodNodeFromChunk(LodDimension lodDim, IChunkWrapper chunk, LodBuilderConfig config, boolean override) throws IllegalArgumentException { //long executeTime = System.currentTimeMillis(); @@ -176,6 +222,7 @@ public class LodBuilder return; // determine how many LODs to generate horizontally + byte minDetailLevel = region.getMinDetailLevel(); HorizontalResolution detail = DetailDistanceUtil.getLodGenDetail(minDetailLevel); @@ -201,12 +248,16 @@ public class LodBuilder //lodDim.clear(detailLevel, posX, posZ); if (data != null && data.length != 0) { - posX = LevelPosUtil.convert((byte) 0, chunk.getChunkPosX() * 16 + startX, detail.detailLevel); - posZ = LevelPosUtil.convert((byte) 0, chunk.getChunkPosZ() * 16 + startZ, detail.detailLevel); - lodDim.addVerticalData(detailLevel, posX, posZ, data, false); + posX = LevelPosUtil.convert((byte) 0, chunk.getChunkPosX() * 16 + startX, minDetailLevel); + posZ = LevelPosUtil.convert((byte) 0, chunk.getChunkPosZ() * 16 + startZ, minDetailLevel); + long oldData = lodDim.getSingleData(minDetailLevel, posX, posZ); + if (override || !DataPointUtil.doesItExist(oldData) || + DataPointUtil.getGenerationMode(oldData) 0) ClientApi.LOGGER.info("generateLodNodeFromChunk level: " + detailLevel + " time ms: " + executeTime); } @@ -394,7 +445,6 @@ public class LodBuilder // 1 means the lighting is a guess int isDefault = 0; - IWorldWrapper world = MC.getWrappedServerWorld(); int blockBrightness = chunk.getEmittedBrightness(x, y, z); // get the air block above or below this block @@ -403,12 +453,24 @@ public class LodBuilder else y++; + blockLight = chunk.getBlockLight(x, y, z); + if (hasSkyLight) + skyLight = chunk.getSkyLight(x, y, z); + else + skyLight = 0; + if (blockLight != -1 && skyLight != -1) { + blockLight = LodUtil.clamp(0, Math.max(blockLight, blockBrightness), DEFAULT_MAX_LIGHT); + return blockLight + (skyLight << 4) + (isDefault << 8); + } + + IWorldWrapper world = MC.getWrappedServerWorld(); if (world != null) { // server world sky light (always accurate) blockLight = world.getBlockLight(x,y,z); + if (topBlock && !hasCeiling && hasSkyLight) skyLight = DEFAULT_MAX_LIGHT; else @@ -424,11 +486,10 @@ public class LodBuilder // lets just take a guess if (y >= MC.getWrappedClientWorld().getSeaLevel() - 5) { - skyLight = 12; - isDefault = 1; + skyLight = 6; } else - skyLight = 0; + skyLight = 6; } } else @@ -437,8 +498,7 @@ public class LodBuilder if (world==null) { blockLight = 0; - skyLight = 12; - isDefault = 1; + skyLight = 6; } else { @@ -461,11 +521,10 @@ public class LodBuilder // lets just take a guess if (y >= MC.getWrappedClientWorld().getSeaLevel() - 5) { - skyLight = 12; - isDefault = 1; + skyLight = 6; } else - skyLight = 0; + skyLight = 6; } } } diff --git a/src/main/java/com/seibel/lod/core/builders/worldGeneration/LodWorldGenerator.java b/src/main/java/com/seibel/lod/core/builders/worldGeneration/LodWorldGenerator.java index cddd755f6..273d6f118 100644 --- a/src/main/java/com/seibel/lod/core/builders/worldGeneration/LodWorldGenerator.java +++ b/src/main/java/com/seibel/lod/core/builders/worldGeneration/LodWorldGenerator.java @@ -23,11 +23,14 @@ import java.util.HashSet; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import com.google.common.util.concurrent.ThreadFactoryBuilder; +import com.seibel.lod.core.api.ClientApi; import com.seibel.lod.core.builders.lodBuilding.LodBuilder; import com.seibel.lod.core.enums.config.DistanceGenerationMode; +import com.seibel.lod.core.enums.config.GenerationPriority; import com.seibel.lod.core.objects.PosToGenerateContainer; import com.seibel.lod.core.objects.lod.LodDimension; import com.seibel.lod.core.util.LevelPosUtil; @@ -96,12 +99,21 @@ public class LodWorldGenerator // TODO: Rename the config option if (CONFIG.client().worldGenerator().getAllowUnstableFeatureGeneration()) { if (experimentalWorldGenerator == null) { - experimentalWorldGenerator = WRAPPER_FACTORY.createExperimentalWorldGenerator(lodBuilder, lodDim, world); + try { + experimentalWorldGenerator = WRAPPER_FACTORY.createExperimentalWorldGenerator(lodBuilder, lodDim, world); if (experimentalWorldGenerator == null) CONFIG.client().worldGenerator().setAllowUnstableFeatureGeneration(false); + } catch (RuntimeException e) { + // Exception may happen if world got unloaded unorderly + e.printStackTrace(); + } } } else { if (experimentalWorldGenerator != null) { - experimentalWorldGenerator.stop(); + try { + experimentalWorldGenerator.stop(); + } catch (RuntimeException e) { + e.printStackTrace(); + } experimentalWorldGenerator = null; } } @@ -115,6 +127,10 @@ public class LodWorldGenerator // This is fine currently since DistanceGenerationMode doesn't care about the detail level for now. // However, If that was to be changed, This will need to be fixed. DistanceGenerationMode mode = CONFIG.client().worldGenerator().getDistanceGenerationMode(); + final GenerationPriority priority; + if (CONFIG.client().worldGenerator().getGenerationPriority() == GenerationPriority.AUTO) + priority = MC.hasSinglePlayerServer() ? GenerationPriority.FAR_FIRST : GenerationPriority.NEAR_FIRST; + else priority = CONFIG.client().worldGenerator().getGenerationPriority(); if (mode != DistanceGenerationMode.NONE && !generatorThreadRunning @@ -152,9 +168,7 @@ public class LodWorldGenerator IWorldWrapper serverWorld = LodUtil.getServerWorldFromDimension(lodDim.dimension); PosToGenerateContainer posToGenerate = lodDim.getPosToGenerate( - maxChunkGenRequests, - playerPosX, - playerPosZ); + maxChunkGenRequests, playerPosX, playerPosZ, priority); byte detailLevel; int posX; @@ -192,7 +206,9 @@ public class LodWorldGenerator // add the far positions - if (farIndex < posToGenerate.getNumberOfFarPos() && posToGenerate.getNthDetail(farIndex, false) != 0) + // But if priority is NEAR_FIRST, we only do that if near pos has ran out. + if ((nearIndex >= posToGenerate.getNumberOfNearPos() || priority != GenerationPriority.NEAR_FIRST) && + farIndex < posToGenerate.getNumberOfFarPos() && posToGenerate.getNthDetail(farIndex, false) != 0) { detailLevel = (byte) (posToGenerate.getNthDetail(farIndex, false) - 1); posX = posToGenerate.getNthPosX(farIndex, false); @@ -364,10 +380,21 @@ public class LodWorldGenerator if (genSubThreads != null && !genSubThreads.isShutdown()) { - genSubThreads.shutdownNow(); + ClientApi.LOGGER.info("Blocking until generator sub threads terminated!!"); + try { + mainGenThread.shutdownNow(); + genSubThreads.shutdownNow(); + boolean worked = genSubThreads.awaitTermination(30, TimeUnit.SECONDS); + if (!worked) + ClientApi.LOGGER.error("Generator sub threads timed out! May cause crash on game exit due to cleanup failure."); + } catch (InterruptedException e) { + ClientApi.LOGGER.error("Generator sub threads shutdown is interrupted! May cause crash on game exit due to cleanup failure."); + e.printStackTrace(); + } finally { + genSubThreads = Executors.newFixedThreadPool(CONFIG.client().advanced().threading().getNumberOfWorldGenerationThreads(), + new ThreadFactoryBuilder().setNameFormat("Gen-Worker-Thread-%d").build()); + } } - genSubThreads = Executors.newFixedThreadPool(CONFIG.client().advanced().threading().getNumberOfWorldGenerationThreads(), - new ThreadFactoryBuilder().setNameFormat("Gen-Worker-Thread-%d").build()); } } diff --git a/src/main/java/com/seibel/lod/core/enums/config/DropoffQuality.java b/src/main/java/com/seibel/lod/core/enums/config/DropoffQuality.java new file mode 100644 index 000000000..271c8c68b --- /dev/null +++ b/src/main/java/com/seibel/lod/core/enums/config/DropoffQuality.java @@ -0,0 +1,48 @@ +/* + * This file is part of the Distant Horizon mod (formerly the LOD Mod), + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2022 Tom Lee (TomTheFurry) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.lod.core.enums.config; + +/** + * AUTO
+ * SMOOTH_DROPOFF
+ * PERFORMANCE_FOCUSED
+ *
+ * Determines how lod level drop off should be done + * + * @author Tom Lee + * @version 7-1-2022 + */ +public enum DropoffQuality { + + /** SMOOTH_DROPOFF when <128 lod view distance, or PERFORMANCE_FOCUSED otherwise */ + AUTO(-1), + + SMOOTH_DROPOFF(10), + + PERFORMANCE_FOCUSED(0); + + public final int fastModeSwitch; + + DropoffQuality(int fastModeSwitch) { + this.fastModeSwitch = fastModeSwitch; + } + + +} diff --git a/src/main/java/com/seibel/lod/core/enums/rendering/DebugMode.java b/src/main/java/com/seibel/lod/core/enums/rendering/DebugMode.java index cdc02fa1f..90baabd48 100644 --- a/src/main/java/com/seibel/lod/core/enums/rendering/DebugMode.java +++ b/src/main/java/com/seibel/lod/core/enums/rendering/DebugMode.java @@ -34,7 +34,13 @@ public enum DebugMode SHOW_DETAIL, /** LOD colors are based on their detail, and draws in wireframe. */ - SHOW_DETAIL_WIREFRAME; + SHOW_DETAIL_WIREFRAME, + + /** LOD colors are based on their gen mode. */ + SHOW_GENMODE, + + /** LOD colors are based on their gen mode, and draws in wireframe. */ + SHOW_GENMODE_WIREFRAME; /** used when cycling through the different modes */ private DebugMode next; @@ -43,7 +49,9 @@ public enum DebugMode { OFF.next = SHOW_DETAIL; SHOW_DETAIL.next = SHOW_DETAIL_WIREFRAME; - SHOW_DETAIL_WIREFRAME.next = OFF; + SHOW_DETAIL_WIREFRAME.next = SHOW_GENMODE; + SHOW_GENMODE.next = SHOW_GENMODE_WIREFRAME; + SHOW_GENMODE_WIREFRAME.next = OFF; } /** returns the next debug mode */ diff --git a/src/main/java/com/seibel/lod/core/handlers/LodDimensionFileHandler.java b/src/main/java/com/seibel/lod/core/handlers/LodDimensionFileHandler.java index 6473c6ade..3d056d106 100644 --- a/src/main/java/com/seibel/lod/core/handlers/LodDimensionFileHandler.java +++ b/src/main/java/com/seibel/lod/core/handlers/LodDimensionFileHandler.java @@ -21,11 +21,15 @@ package com.seibel.lod.core.handlers; import java.io.*; import java.nio.file.Files; -import java.nio.file.Paths; import java.nio.file.StandardCopyOption; -import java.util.Arrays; +import java.time.Duration; +import java.time.Instant; +import java.util.ConcurrentModificationException; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import org.apache.commons.compress.compressors.xz.XZCompressorInputStream; import org.apache.commons.compress.compressors.xz.XZCompressorOutputStream; @@ -39,7 +43,7 @@ import com.seibel.lod.core.objects.lod.RegionPos; import com.seibel.lod.core.objects.lod.VerticalLevelContainer; import com.seibel.lod.core.util.LodThreadFactory; import com.seibel.lod.core.util.LodUtil; -import com.seibel.lod.core.util.ThreadMapUtil; + /** * This object handles creating LodRegions @@ -84,9 +88,10 @@ public class LodDimensionFileHandler * Allow saving asynchronously, but never try to save multiple regions * at a time */ - private final ExecutorService fileWritingThreadPool = Executors.newSingleThreadExecutor(new LodThreadFactory(this.getClass().getSimpleName())); - + private AtomicBoolean isFileWritingThreadRunning = new AtomicBoolean(false); + private ExecutorService fileWritingThreadPool = Executors.newSingleThreadExecutor(new LodThreadFactory(this.getClass().getSimpleName())); + private ConcurrentHashMap regionToSave = new ConcurrentHashMap(); public LodDimensionFileHandler(File newSaveFolder, LodDimension newLodDimension) @@ -105,19 +110,39 @@ public class LodDimensionFileHandler //================// // read from file // //================// - + /** - * Returns the LodRegion at the given coordinates. + * Returns a new LodRegion at the given coordinates. * Returns an empty region if the file doesn't exist. */ public LodRegion loadRegionFromFile(byte detailLevel, RegionPos regionPos, DistanceGenerationMode generationMode, VerticalQuality verticalQuality) { - int regionX = regionPos.x; - int regionZ = regionPos.z; - LodRegion region = new LodRegion(LodUtil.REGION_DETAIL_LEVEL, regionPos, generationMode, verticalQuality); + // Get one from the region hot cache + LodRegion region = regionToSave.get(regionPos); + if (region!=null && region.getMinDetailLevel()<=detailLevel && + region.getGenerationMode().compareTo(generationMode)>=0 && + region.getVerticalQuality().compareTo(verticalQuality)>=0) + return region; // The current hot cache to-be-saved region match our requirement. + region = new LodRegion((byte) (LodUtil.REGION_DETAIL_LEVEL+1), regionPos, generationMode, verticalQuality); + return loadRegionFromFile(detailLevel, region, generationMode, verticalQuality); + } + + /** + * Returns the LodRegion that is filled at the given coordinates. + * Returns an empty region if the file doesn't exist. + */ + public LodRegion loadRegionFromFile(byte detailLevel, LodRegion region, DistanceGenerationMode generationMode, VerticalQuality verticalQuality) + { + if (region.getGenerationMode().compareTo(generationMode)<0 || region.getVerticalQuality().compareTo(verticalQuality)<0) { + regionToSave.put(region.getRegionPos(), region); //FIXME: The hashMap key should prob be a {regionPos,VertQual} pair. + region = new LodRegion((byte) (LodUtil.REGION_DETAIL_LEVEL+1), region.getRegionPos(), generationMode, verticalQuality); + } + int regionX = region.regionPosX; + int regionZ = region.regionPosZ; - for (byte tempDetailLevel = LodUtil.REGION_DETAIL_LEVEL; tempDetailLevel >= detailLevel; tempDetailLevel--) + for (byte tempDetailLevel = (byte) (region.getMinDetailLevel()-1); tempDetailLevel >= detailLevel; tempDetailLevel--) { + File file = getBestMatchingRegionFile(tempDetailLevel, regionX, regionZ, generationMode, verticalQuality); if (file == null) { region.addLevelContainer(new VerticalLevelContainer(tempDetailLevel)); @@ -198,33 +223,96 @@ public class LodDimensionFileHandler // Save to File // //==============// - /** Save all dirty regions in this LodDimension to file */ - public void saveDirtyRegionsToFileAsync() - { - fileWritingThreadPool.execute(saveDirtyRegionsThread); + public void addRegionsToSave(LodRegion r) { + regionToSave.put(r.getRegionPos(), r); } - private final Thread saveDirtyRegionsThread = new Thread(() -> + /** Save all dirty regions in this LodDimension to file */ + public void saveDirtyRegionsToFile(boolean blockUntilFinished) { - try + for (int i = 0; i < lodDimension.getWidth(); i++) { - for (int i = 0; i < lodDimension.getWidth(); i++) + for (int j = 0; j < lodDimension.getWidth(); j++) { - for (int j = 0; j < lodDimension.getWidth(); j++) + LodRegion r = lodDimension.getRegionByArrayIndex(i, j); + + if (r != null && r.needSaving) { - if (lodDimension.GetIsRegionDirty(i, j) && lodDimension.getRegionByArrayIndex(i, j) != null) - { - saveRegionToFile(lodDimension.getRegionByArrayIndex(i, j)); - lodDimension.SetIsRegionDirty(i, j, false); - } + r.needSaving = false; + regionToSave.put(r.getRegionPos(), r); } } } - catch (Exception e) - { - e.printStackTrace(); + trySaveRegionsToBeSaved(); + if (blockUntilFinished) { + ClientApi.LOGGER.info("Blocking until lod file save finishes!"); + try { + fileWritingThreadPool.shutdown(); + boolean worked = fileWritingThreadPool.awaitTermination(30, TimeUnit.SECONDS); + if (!worked) + ClientApi.LOGGER.error("File writing timed out! File data may not be saved correctly and may cause corruptions!!!"); + } catch (InterruptedException e) { + ClientApi.LOGGER.error("File writing wait is interrupted! File data may not be saved correctly and may cause corruptions!!!"); + e.printStackTrace(); + } finally { + fileWritingThreadPool = Executors.newSingleThreadExecutor(new LodThreadFactory(this.getClass().getSimpleName())); + } } - }); + } + + public void trySaveRegionsToBeSaved() { + if (regionToSave.isEmpty()) return; + // Use Memory order Acquire to acquire any memory changes on getting this boolean + // (Corresponding call is the this::writerMain(...)::...setRelease(false);) + //boolean haventStarted = !isFileWritingThreadRunning.compareAndExchangeAcquire(false, true); + // The above needs java 9! + boolean haventStarted = isFileWritingThreadRunning.compareAndSet(false, true); + + if (haventStarted) { + // We acquired the atomic lock. + fileWritingThreadPool.execute(this::writerMain); + } + } + + private void writerMain() { + // Use Memory order Relaxed as no additional memory changes needed to be visible. + // (This is just a safety checks) + // boolean isStarted = isFileWritingThreadRunning.getPlain(); + // The above needs java 9! + boolean isStarted = isFileWritingThreadRunning.get(); + + if (!isStarted) throw new ConcurrentModificationException("WriterMain Triggered but the thead state is not started!?"); + ClientApi.LOGGER.info("Lod File Writer started. To-be-written-regions: "+regionToSave.size()); + Instant start = Instant.now(); + // Note: Since regionToSave is a ConcurrentHashMap, and the .values() return one that support concurrency, + // this for loop should be safe and loop until all values are gone. + while (!regionToSave.isEmpty()) { + for (LodRegion r : regionToSave.values()) { + //Check if the data has been swapped out right under me. Otherwise remove it from the entry + if (!regionToSave.remove(r.getRegionPos(), r)) continue; + try + { + Instant i = Instant.now(); + ClientApi.LOGGER.info("Lod: Saving Region "+r.getRegionPos()); + saveRegionToFile(r); + Instant j = Instant.now(); + Duration d = Duration.between(i, j); + ClientApi.LOGGER.info("Lod: Region "+r.getRegionPos()+" save finish. Took "+d); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + } + Instant end = Instant.now(); + ClientApi.LOGGER.info("Lod File Writer completed. Took "+Duration.between(start, end)); + // Use Memory order Release to release any memory changes on setting this boolean + // (Corresponding call is the this::saveRegions(...)::...compareAndExchangeAcquire(false, true);) + // isFileWritingThreadRunning.setRelease(false); + // The above needs java 9! + isFileWritingThreadRunning.set(false); + } /** * Save a specific region to disk.
@@ -240,7 +328,7 @@ public class LodDimensionFileHandler { // Get the old file File oldFile = getRegionFile(region.regionPosX, region.regionPosZ, region.getGenerationMode(), detailLevel, region.getVerticalQuality()); - ClientApi.LOGGER.debug("saving region [" + region.regionPosX + ", " + region.regionPosZ + "] to file."); + ClientApi.LOGGER.debug("saving region [" + region.regionPosX + ", " + region.regionPosZ + "] detail "+detailLevel+" to file."); boolean isFileFullyGened = false; // make sure the file and folder exists @@ -366,14 +454,14 @@ public class LodDimensionFileHandler // Return null if no file found private File getBestMatchingRegionFile(byte detailLevel, int regionX, int regionZ, DistanceGenerationMode targetGenMode, VerticalQuality targetVertQuality) { - DistanceGenerationMode genMode = targetGenMode; + DistanceGenerationMode genMode = DistanceGenerationMode.FULL; // Search from least GenMode to max GenMode, than least vertQuality to max vertQuality do { File file = getRegionFile(regionX, regionZ, genMode, detailLevel, targetVertQuality); if (file.exists()) return file; // Found target file. - targetGenMode = DistanceGenerationMode.next(targetGenMode); - if (targetGenMode == null) { // Failed to find any files for this vertQuality. Try next one up. - targetGenMode = genMode; + genMode = DistanceGenerationMode.previous(genMode); + if (genMode==null || genMode==DistanceGenerationMode.previous(targetGenMode)) { // Failed to find any files for this vertQuality. Try next one up. + genMode = DistanceGenerationMode.FULL; targetVertQuality = VerticalQuality.next(targetVertQuality); } } while (targetVertQuality != null); diff --git a/src/main/java/com/seibel/lod/core/objects/PosToGenerateContainer.java b/src/main/java/com/seibel/lod/core/objects/PosToGenerateContainer.java index 730c9d6cb..ad098aafc 100644 --- a/src/main/java/com/seibel/lod/core/objects/PosToGenerateContainer.java +++ b/src/main/java/com/seibel/lod/core/objects/PosToGenerateContainer.java @@ -31,7 +31,7 @@ public class PosToGenerateContainer { private final int playerPosX; private final int playerPosZ; - private final byte farMinDetail; + public final byte farMinDetail; private int nearSize; private int farSize; diff --git a/src/main/java/com/seibel/lod/core/objects/VertexOptimizer.java b/src/main/java/com/seibel/lod/core/objects/VertexOptimizer.java index e88936592..fb58e5ff6 100644 --- a/src/main/java/com/seibel/lod/core/objects/VertexOptimizer.java +++ b/src/main/java/com/seibel/lod/core/objects/VertexOptimizer.java @@ -152,6 +152,7 @@ public class VertexOptimizer * This is a map from Direction to the relative normal vector * we are using this since I'm not sure if the getNormal create new object at every call */ + // FIXME: No. It doesn't. Just remove this. @SuppressWarnings("serial") public static final Map DIRECTION_NORMAL_MAP = new HashMap() {{ diff --git a/src/main/java/com/seibel/lod/core/objects/lod/LodDimension.java b/src/main/java/com/seibel/lod/core/objects/lod/LodDimension.java index 30d5c97c4..3b59af3ce 100644 --- a/src/main/java/com/seibel/lod/core/objects/lod/LodDimension.java +++ b/src/main/java/com/seibel/lod/core/objects/lod/LodDimension.java @@ -24,7 +24,9 @@ import java.io.IOException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import com.seibel.lod.core.api.ClientApi; import com.seibel.lod.core.enums.config.DistanceGenerationMode; +import com.seibel.lod.core.enums.config.DropoffQuality; import com.seibel.lod.core.enums.config.GenerationPriority; import com.seibel.lod.core.enums.config.VerticalQuality; import com.seibel.lod.core.handlers.LodDimensionFileHandler; @@ -37,13 +39,13 @@ import com.seibel.lod.core.util.LodThreadFactory; import com.seibel.lod.core.util.LodUtil; import com.seibel.lod.core.util.SingletonHandler; import com.seibel.lod.core.wrapperInterfaces.IWrapperFactory; -import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper; import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton; import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftWrapper; import com.seibel.lod.core.wrapperInterfaces.world.IDimensionTypeWrapper; import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper; +//FIXME: Race condition on lodDim move/resize! /** * This object holds all loaded LOD regions @@ -61,7 +63,6 @@ public class LodDimension { private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class); private static final IMinecraftWrapper MC = SingletonHandler.get(IMinecraftWrapper.class); - private static final IWrapperFactory FACTORY = SingletonHandler.get(IWrapperFactory.class); public final IDimensionTypeWrapper dimension; @@ -76,11 +77,8 @@ public class LodDimension public volatile LodRegion[][] regions; /** stores if the region at the given x and z index needs to be saved to disk */ - private volatile boolean[][] isRegionDirty; /** stores if the region at the given x and z index needs to be regenerated */ // Use int because I need Tri state: - // 0: both buffer good. 1: the displaying buffer good. 2: both buffer bad. - private volatile int[][] regenRegionBuffer; /** * if true that means there are regions in this dimension @@ -92,10 +90,9 @@ public class LodDimension private final RegionPos center; - /** prevents the cutAndExpandThread from expanding at the same location multiple times */ - private volatile AbstractChunkPosWrapper lastExpandedChunk; - /** prevents the cutAndExpandThread from cutting at the same location multiple times */ - private volatile AbstractChunkPosWrapper lastCutChunk; + private boolean isCutting = false; + private boolean isExpanding = false; + private final ExecutorService cutAndExpandThread = Executors.newSingleThreadExecutor(new LodThreadFactory(this.getClass().getSimpleName() + " - Cut and Expand")); /** @@ -104,8 +101,6 @@ public class LodDimension */ public LodDimension(IDimensionTypeWrapper newDimension, LodWorld lodWorld, int newWidth) { - lastCutChunk = null; - lastExpandedChunk = null; dimension = newDimension; width = newWidth; halfWidth = width / 2; @@ -142,8 +137,6 @@ public class LodDimension regions = new LodRegion[width][width]; - isRegionDirty = new boolean[width][width]; - regenRegionBuffer = new int[width][width]; center = new RegionPos(0, 0); } @@ -159,6 +152,8 @@ public class LodDimension */ public synchronized void move(RegionPos regionOffset) { + ClientApi.LOGGER.info("LodDim MOVE. Offset: "+regionOffset); + saveDirtyRegionsToFile(false); //async add dirty regions to be saved. int xOffset = regionOffset.x; int zOffset = regionOffset.z; @@ -170,7 +165,6 @@ public class LodDimension for (int x = 0; x < width; x++) for (int z = 0; z < width; z++) { regions[x][z] = null; - regenRegionBuffer[x][z] = 0; } // update the new center center.x += xOffset; @@ -188,14 +182,10 @@ public class LodDimension { for (int z = 0; z < width; z++) { - if (x + xOffset < width) { + if (x + xOffset < width) regions[x][z] = regions[x + xOffset][z]; - regenRegionBuffer[x][z] = regenRegionBuffer[x + xOffset][z]; - } - else { + else regions[x][z] = null; - regenRegionBuffer[x][z] = 0; - } } } } @@ -206,14 +196,11 @@ public class LodDimension { for (int z = 0; z < width; z++) { - if (x + xOffset >= 0) { + if (x + xOffset >= 0) regions[x][z] = regions[x + xOffset][z]; - regenRegionBuffer[x][z] = regenRegionBuffer[x + xOffset][z]; - } - else { + else regions[x][z] = null; - regenRegionBuffer[x][z] = 0; - } + } } } @@ -226,14 +213,10 @@ public class LodDimension { for (int z = 0; z < width; z++) { - if (z + zOffset < width) { + if (z + zOffset < width) regions[x][z] = regions[x][z + zOffset]; - regenRegionBuffer[x][z] = regenRegionBuffer[x][z + zOffset]; - } - else { + else regions[x][z] = null; - regenRegionBuffer[x][z] = 0; - } } } } @@ -244,15 +227,10 @@ public class LodDimension { for (int z = width - 1; z >= 0; z--) { - if (z + zOffset >= 0) { + if (z + zOffset >= 0) regions[x][z] = regions[x][z + zOffset]; - regenRegionBuffer[x][z] = regenRegionBuffer[x][z + zOffset]; - } - else { + else regions[x][z] = null; - regenRegionBuffer[x][z] = 0; - } - } } } } @@ -261,6 +239,7 @@ public class LodDimension // update the new center center.x += xOffset; center.z += zOffset; + ClientApi.LOGGER.info("LodDim MOVE complete. Offset: "+regionOffset); } @@ -362,152 +341,109 @@ public class LodDimension */ public void cutRegionNodesAsync(int playerPosX, int playerPosZ) { - AbstractChunkPosWrapper newPlayerChunk = FACTORY.createChunkPos(LevelPosUtil.getChunkPos((byte) 0, playerPosX), LevelPosUtil.getChunkPos((byte) 0, playerPosZ)); - - if (lastCutChunk == null) - lastCutChunk = FACTORY.createChunkPos(newPlayerChunk.getX() + 1, newPlayerChunk.getZ() - 1); - + if (isCutting) return; + isCutting = true; // don't run the tree cutter multiple times // for the same location - if (newPlayerChunk.getX() != lastCutChunk.getX() || newPlayerChunk.getZ() != lastCutChunk.getZ()) { - lastCutChunk = newPlayerChunk; + Runnable thread = () -> { + //ClientApi.LOGGER.info("LodDim cut Region: " + playerPosX + "," + playerPosZ); - Runnable thread = () -> { - - // go over every region in the dimension - iterateWithSpiral((int x, int z) -> { - int regionX; - int regionZ; - int minDistance; - byte detail; - byte minAllowedDetailLevel; - regionX = (x + center.x) - halfWidth; - regionZ = (z + center.z) - halfWidth; - - if (regions[x][z] != null) { - // check what detail level this region should be - // and cut it if it is higher then that - minDistance = LevelPosUtil.minDistance(LodUtil.REGION_DETAIL_LEVEL, regionX, regionZ, - playerPosX, playerPosZ); - detail = DetailDistanceUtil.getTreeCutDetailFromDistance(minDistance); - minAllowedDetailLevel = DetailDistanceUtil.getCutLodDetail(detail); - - if (regions[x][z].getMinDetailLevel() > minAllowedDetailLevel) { - regions[x][z].cutTree(minAllowedDetailLevel); - regenRegionBuffer[x][z] = 2; - regenDimensionBuffers = true; - } + // go over every region in the dimension + iterateWithSpiral((int x, int z) -> { + int regionX; + int regionZ; + int minDistance; + byte detail; + byte minAllowedDetailLevel; + regionX = (x + center.x) - halfWidth; + regionZ = (z + center.z) - halfWidth; + LodRegion region = regions[x][z]; + if (region != null) { + // check what detail level this region should be + // and cut it if it is higher then that + minDistance = LevelPosUtil.minDistance(LodUtil.REGION_DETAIL_LEVEL, regionX, regionZ, + playerPosX, playerPosZ); + detail = DetailDistanceUtil.getTreeCutDetailFromDistance(minDistance); + minAllowedDetailLevel = DetailDistanceUtil.getCutLodDetail(detail); + if (region.getMinDetailLevel() < minAllowedDetailLevel) { + region.cutTree(minAllowedDetailLevel); + region.needRegenBuffer = 2; + regenDimensionBuffers = true; } - }); - }; - cutAndExpandThread.execute(thread); - } + } + }); + //ClientApi.LOGGER.info("LodDim cut Region complete: " + playerPosX + "," + playerPosZ); + isCutting = false; + }; + cutAndExpandThread.execute(thread); } /** Either expands or loads all regions in the rendered LOD area */ public void expandOrLoadRegionsAsync(int playerPosX, int playerPosZ) { + + if (isExpanding) return; + isExpanding = true; + DistanceGenerationMode generationMode = CONFIG.client().worldGenerator().getDistanceGenerationMode(); - AbstractChunkPosWrapper newPlayerChunk = FACTORY.createChunkPos(LevelPosUtil.getChunkPos((byte) 0, playerPosX), - LevelPosUtil.getChunkPos((byte) 0, playerPosZ)); VerticalQuality verticalQuality = CONFIG.client().graphics().quality().getVerticalQuality(); - - if (lastExpandedChunk == null) - lastExpandedChunk = FACTORY.createChunkPos(newPlayerChunk.getX() + 1, newPlayerChunk.getZ() - 1); - + DropoffQuality dropoffQuality = CONFIG.client().graphics().quality().getDropoffQuality(); + if (dropoffQuality == DropoffQuality.AUTO) + dropoffQuality = CONFIG.client().graphics().quality().getLodChunkRenderDistance() < 128 ? + DropoffQuality.SMOOTH_DROPOFF : DropoffQuality.PERFORMANCE_FOCUSED; + int dropoffSwitch = dropoffQuality.fastModeSwitch; // don't run the expander multiple times // for the same location - if (newPlayerChunk.getX() != lastExpandedChunk.getX() || newPlayerChunk.getZ() != lastExpandedChunk.getZ()) { - lastExpandedChunk = newPlayerChunk; + Runnable thread = () -> { + //ClientApi.LOGGER.info("LodDim expend Region: " + playerPosX + "," + playerPosZ); - Runnable thread = () -> { + iterateWithSpiral((int x, int z) -> { + int regionX; + int regionZ; + LodRegion region; + int minDistance; + int maxDistance; + byte minDetail; + byte maxDetail; + regionX = (x + center.x) - halfWidth; + regionZ = (z + center.z) - halfWidth; + final RegionPos regionPos = new RegionPos(regionX, regionZ); + region = regions[x][z]; - iterateWithSpiral((int x, int z) -> { - int regionX; - int regionZ; - LodRegion region; - int minDistance; - byte detail; - byte levelToGen; - regionX = (x + center.x) - halfWidth; - regionZ = (z + center.z) - halfWidth; - final RegionPos regionPos = new RegionPos(regionX, regionZ); - region = regions[x][z]; - - minDistance = LevelPosUtil.minDistance(LodUtil.REGION_DETAIL_LEVEL, regionX, regionZ, playerPosX, - playerPosZ); - detail = DetailDistanceUtil.getTreeGenDetailFromDistance(minDistance); - levelToGen = DetailDistanceUtil.getLodGenDetail(detail).detailLevel; - - // check that the region isn't null and at least this detail level - if (region == null || region.getGenerationMode() != generationMode) { - // First case, region has to be created - - // try to get the region from file - regions[x][z] = getRegionFromFile(regionPos, levelToGen, generationMode, verticalQuality); - - // if there is no region file create an empty region - if (regions[x][z] == null) - regions[x][z] = new LodRegion(levelToGen, regionPos, generationMode, verticalQuality); - - regenRegionBuffer[x][z] = 2; - regenDimensionBuffers = true; - } else if (region.getMinDetailLevel() > levelToGen) { - // Second case, the region exists at a higher detail level. - - // Expand the region by introducing the missing layer - region.growTree(levelToGen); - regions[x][z] = getRegionFromFile(regionPos, levelToGen, generationMode, verticalQuality); - regenRegionBuffer[x][z] = 2; - regenDimensionBuffers = true; - } - }); - }; - - cutAndExpandThread.execute(thread); - } - } - - /** - * Use addVerticalData when possible. - * Add the given LOD to this dimension at the coordinate - * stored in the LOD. If an LOD already exists at the given - * coordinate it will be overwritten. - */ - public Boolean addData(byte detailLevel, int posX, int posZ, int verticalIndex, long data, boolean dontSave) - { - int regionPosX = LevelPosUtil.getRegion(detailLevel, posX); - int regionPosZ = LevelPosUtil.getRegion(detailLevel, posZ); - - // don't continue if the region can't be saved - LodRegion region = getRegion(regionPosX, regionPosZ); - if (region == null) - return false; - - boolean nodeAdded = region.addData(detailLevel, posX, posZ, verticalIndex, data); - - // only save valid LODs to disk - if (!dontSave && fileHandler != null) - { - try - { - // mark the region as dirty, so it will be saved to disk - int xIndex = (regionPosX - center.x) + halfWidth; - int zIndex = (regionPosZ - center.z) + halfWidth; + minDistance = LevelPosUtil.minDistance(LodUtil.REGION_DETAIL_LEVEL, regionX, regionZ, playerPosX, + playerPosZ); + maxDistance = LevelPosUtil.maxDistance(LodUtil.REGION_DETAIL_LEVEL, regionX, regionZ, playerPosX, + playerPosZ); + minDetail = DetailDistanceUtil.getTreeGenDetailFromDistance(minDistance); + maxDetail = DetailDistanceUtil.getTreeGenDetailFromDistance(maxDistance); - isRegionDirty[xIndex][zIndex] = true; - regenRegionBuffer[xIndex][zIndex] = 2; - regenDimensionBuffers = true; - } - catch (ArrayIndexOutOfBoundsException e) - { - e.printStackTrace(); - // If this happens, the method was probably - // called when the dimension was changing size. - // Hopefully this shouldn't be an issue. - } - } - - return nodeAdded; + boolean updated = false; + if (region == null) { + region = getRegionFromFile(regionPos, minDetail, generationMode, verticalQuality); + regions[x][z] = region; + updated = true; + } else if (region.getGenerationMode().compareTo(generationMode) < 0 || + region.getVerticalQuality() != verticalQuality || + region.getMinDetailLevel() > minDetail) { + // The 'getRegionFromFile' will flush and save the region if it returns a new one + region = getRegionFromFile(regions[x][z], minDetail, generationMode, verticalQuality); + regions[x][z] = region; + updated = true; + } else if (minDetail <= dropoffSwitch && region.lastMaxDetailLevel != maxDetail) { + region.lastMaxDetailLevel = maxDetail; + updated = true; + } else if (minDetail <= dropoffSwitch && region.lastMaxDetailLevel != region.getMinDetailLevel()) { + updated = true; + } + if (updated) { + region.needRegenBuffer = 2; + regenDimensionBuffers = true; + } + }); + //ClientApi.LOGGER.info("LodDim expend Region complete: " + playerPosX + "," + playerPosZ); + isExpanding = false; + }; + + cutAndExpandThread.execute(thread); } /** @@ -515,7 +451,7 @@ public class LodDimension * stored in the LOD. If an LOD already exists at the given * coordinate it will be overwritten. */ - public Boolean addVerticalData(byte detailLevel, int posX, int posZ, long[] data, boolean dontSave) + public Boolean addVerticalData(byte detailLevel, int posX, int posZ, long[] data) { int regionPosX = LevelPosUtil.getRegion(detailLevel, posX); int regionPosZ = LevelPosUtil.getRegion(detailLevel, posZ); @@ -526,178 +462,38 @@ public class LodDimension return false; boolean nodeAdded = region.addVerticalData(detailLevel, posX, posZ, data); - - // only save valid LODs to disk - if (!dontSave && fileHandler != null) - { - try - { - // mark the region as dirty, so it will be saved to disk - int xIndex = (regionPosX - center.x) + halfWidth; - int zIndex = (regionPosZ - center.z) + halfWidth; - - isRegionDirty[xIndex][zIndex] = true; - regenRegionBuffer[xIndex][zIndex] = 2; - regenDimensionBuffers = true; - } - catch (ArrayIndexOutOfBoundsException e) - { - e.printStackTrace(); - // If this happens, the method was probably - // called when the dimension was changing size. - // Hopefully this shouldn't be an issue. - } + if (nodeAdded) { + region.needRegenBuffer = 2; + region.needSaving = true; + regenDimensionBuffers = true; } - return nodeAdded; } /** marks the region at the given region position to have its buffer rebuilt */ public void markRegionBufferToRegen(int xRegion, int zRegion) { - int xIndex = (xRegion - center.x) + halfWidth; - int zIndex = (zRegion - center.z) + halfWidth; - regenRegionBuffer[xIndex][zIndex] = 2; + LodRegion r = getRegion(xRegion,zRegion); + if (r!=null) { + r.needRegenBuffer = 2; + regenDimensionBuffers = true; + } } /** * Returns every position that need to be generated based on the position of the player */ - public PosToGenerateContainer getPosToGenerate(int maxDataToGenerate, int playerBlockPosX, int playerBlockPosZ) + public PosToGenerateContainer getPosToGenerate(int maxDataToGenerate, int playerBlockPosX, int playerBlockPosZ, GenerationPriority priority) { PosToGenerateContainer posToGenerate; - LodRegion lodRegion; - // all the following values are used for the spiral matrix visit - // x and z are the matrix coord - // dx and dz is the next move on the coordinate in the range -1 0 +1 - int x, z, dx, dz, t; - x = 0; - z = 0; - dx = 0; - dz = -1; - - // We can use two type of generation scheduling - switch (CONFIG.client().worldGenerator().getGenerationPriority()) - { - case NEAR_FIRST: - //in the NEAR_FIRST generation scheduling we prioritize the nearest un-generated position to the player - //the chunk position to generate will be stored in a posToGenerate object - posToGenerate = new PosToGenerateContainer((byte) 10, maxDataToGenerate, playerBlockPosX, playerBlockPosZ); - - int playerChunkX = LevelPosUtil.getChunkPos(LodUtil.BLOCK_DETAIL_LEVEL, playerBlockPosX); - int playerChunkZ = LevelPosUtil.getChunkPos(LodUtil.BLOCK_DETAIL_LEVEL, playerBlockPosZ); - - int complexity; - int xChunkToCheck; - int zChunkToCheck; - byte detailLevel; - int posX; - int posZ; - long data; - int numbChunksWide = (width) * 32; - int circleLimit = Integer.MAX_VALUE; - - //posToGenerate is using an insertion sort algorithm which can become really fast if the - //original data order is almost ordered. For this reason we explore the matrix of the position to generate - //with a spiral matrix visit (a square spiral is almost ordered in the "nearest to farthest" order) - for (int i = 0; i < numbChunksWide * numbChunksWide; i++) - { - //Firstly we check if the posToGenerate has been filled - if (maxDataToGenerate == 0) - { - maxDataToGenerate--; - //if it has been filled then we set a stop distance - //the stop distance will be current distance (generically x) per square root of 2 - //this would guarantee a circular generation since (Math.abs(x) * 1.41f) is the - //radius of a circle that inscribe a square - circleLimit = (int) (Math.abs(x) * 1.41f); - } - //This second if check if we reached the circleLimit decided in the previous if - //if so we stop - else if (maxDataToGenerate < 0) - { - if (circleLimit < Math.abs(x) && circleLimit < Math.abs(z)) - break; - } - - - xChunkToCheck = x + playerChunkX; - zChunkToCheck = z + playerChunkZ; - - //we get the lod region in which the chunk is present - lodRegion = getRegion(LodUtil.CHUNK_DETAIL_LEVEL, xChunkToCheck, zChunkToCheck); - if (lodRegion == null) - continue; - - //Now we check if the current chunk has been generated with the correct complexity - //if(lodRegion.isChunkPreGenerated(xChunkToCheck,zChunkToCheck)) - // complexity = DistanceGenerationMode.SERVER.complexity; - //else - complexity = CONFIG.client().worldGenerator().getDistanceGenerationMode().complexity; - - - //we create the level position info of the chunk - detailLevel = lodRegion.getMinDetailLevel(); - posX = LevelPosUtil.convert(LodUtil.CHUNK_DETAIL_LEVEL, xChunkToCheck, detailLevel); - posZ = LevelPosUtil.convert(LodUtil.CHUNK_DETAIL_LEVEL, zChunkToCheck, detailLevel); - - data = getSingleData(detailLevel, posX, posZ); - - //we will generate the position only if the current generation complexity is lower than the target one. - //an un-generated area will always have 0 generation - if (DataPointUtil.getGenerationMode(data) < complexity) - { - posToGenerate.addPosToGenerate(detailLevel, posX, posZ); - if (maxDataToGenerate >= 0) - maxDataToGenerate--; - } - - //with this code section we find the next chunk to check - if ((x == z) || ((x < 0) && (x == -z)) || ((x > 0) && (x == 1 - z))) - { - t = dx; - dx = -dz; - dz = t; - } - x += dx; - z += dz; - } - break; - - default: - case FAR_FIRST: - //in the FAR_FIRST generation we dedicate part of the generation process to the far region with really - //low detail quality. - - posToGenerate = new PosToGenerateContainer((byte) 8, maxDataToGenerate, playerBlockPosX, playerBlockPosZ); - - int xRegion; - int zRegion; - - for (int i = 0; i < width * width; i++) - { - xRegion = x + center.x; - zRegion = z + center.z; - - //All of this is handled directly by the region, which scan every pos from top to bottom of the quad tree - lodRegion = getRegion(xRegion, zRegion); - if (lodRegion != null) - lodRegion.getPosToGenerate(posToGenerate, playerBlockPosX, playerBlockPosZ); - - - //with this code section we find the next chunk to check - if ((x == z) || ((x < 0) && (x == -z)) || ((x > 0) && (x == 1 - z))) - { - t = dx; - dx = -dz; - dz = t; - } - x += dx; - z += dz; - } - break; - } - return posToGenerate; + posToGenerate = new PosToGenerateContainer((byte) 8, maxDataToGenerate, playerBlockPosX, playerBlockPosZ); + iterateWithSpiral((int x, int z) -> { + //All of this is handled directly by the region, which scan every pos from top to bottom of the quad tree + LodRegion lodRegion = regions[x][z]; + if (lodRegion != null) + lodRegion.getPosToGenerate(posToGenerate, playerBlockPosX, playerBlockPosZ, priority); + }); + return posToGenerate; } /** @@ -712,11 +508,16 @@ public class LodDimension GenerationPriority generationPriority = CONFIG.client().worldGenerator().getGenerationPriority(); if (generationPriority == GenerationPriority.AUTO) generationPriority = MC.hasSinglePlayerServer() ? GenerationPriority.FAR_FIRST : GenerationPriority.NEAR_FIRST; + + DropoffQuality dropoffQuality = CONFIG.client().graphics().quality().getDropoffQuality(); + if (dropoffQuality == DropoffQuality.AUTO) + dropoffQuality = CONFIG.client().graphics().quality().getLodChunkRenderDistance() < 128 ? + DropoffQuality.SMOOTH_DROPOFF : DropoffQuality.PERFORMANCE_FOCUSED; boolean requireCorrectDetailLevel = generationPriority == GenerationPriority.NEAR_FIRST; if (region != null) - region.getPosToRender(posToRender, playerPosX, playerPosZ, requireCorrectDetailLevel); + region.getPosToRender(posToRender, playerPosX, playerPosZ, requireCorrectDetailLevel, dropoffQuality); } /** @@ -785,8 +586,9 @@ public class LodDimension LodRegion region = getRegion(xRegion, zRegion); if (region == null) return; - markRegionBufferToRegen(xRegion, zRegion); region.clear(detailLevel, posX, posZ); + region.needRegenBuffer = 2; + regenDimensionBuffers = true; } /** @@ -795,16 +597,12 @@ public class LodDimension */ public boolean getAndClearRegionNeedBufferRegen(int regionX, int regionZ) { - //FIXME: Use actual atomics on regenRegionBuffer - //FIXME: Race condition on lodDim move/resize! - int xIndex = (regionX - center.x) + halfWidth; - int zIndex = (regionZ - center.z) + halfWidth; - - if (xIndex < 0 || xIndex >= width || zIndex < 0 || zIndex >= width) - return false; - int i = regenRegionBuffer[xIndex][zIndex]; + //FIXME: Use actual atomics on needRegenBuffer + LodRegion region = getRegion(regionX, regionZ); + if (region == null) return false; + int i = region.needRegenBuffer; if (i > 0) { - regenRegionBuffer[xIndex][zIndex]--; + region.needRegenBuffer--; return true; } return false; @@ -825,11 +623,10 @@ public class LodDimension int xRegion = LevelPosUtil.getRegion(detailLevel, posX); int zRegion = LevelPosUtil.getRegion(detailLevel, posZ); LodRegion region = getRegion(xRegion, zRegion); - if (region == null) - return; - markRegionBufferToRegen(xRegion, zRegion); - + if (region == null) return; region.updateArea(detailLevel, posX, posZ); + region.needRegenBuffer = 2; + regenDimensionBuffers = true; } /** Returns true if a region exists at the given LevelPos */ @@ -846,13 +643,25 @@ public class LodDimension public LodRegion getRegionFromFile(RegionPos regionPos, byte detailLevel, DistanceGenerationMode generationMode, VerticalQuality verticalQuality) { - return fileHandler != null ? fileHandler.loadRegionFromFile(detailLevel, regionPos, generationMode, verticalQuality) : null; + return fileHandler != null ? fileHandler.loadRegionFromFile(detailLevel, regionPos, generationMode, verticalQuality) : + new LodRegion(detailLevel, regionPos, generationMode, verticalQuality); + } + /** + * Loads the region at the given region from file, + * if a file exists for that region. + */ + public LodRegion getRegionFromFile(LodRegion existingRegion, byte detailLevel, + DistanceGenerationMode generationMode, VerticalQuality verticalQuality) + { + return fileHandler != null ? fileHandler.loadRegionFromFile(detailLevel, existingRegion, generationMode, verticalQuality) : + new LodRegion(detailLevel, existingRegion.getRegionPos(), generationMode, verticalQuality); } /** Save all dirty regions in this LodDimension to file. */ - public void saveDirtyRegionsToFileAsync() + public void saveDirtyRegionsToFile(boolean blockUntilFinished) { - fileHandler.saveDirtyRegionsToFileAsync(); + if (fileHandler == null) return; + fileHandler.saveDirtyRegionsToFile(blockUntilFinished); } @@ -907,13 +716,6 @@ public class LodDimension halfWidth = width/ 2; regions = new LodRegion[width][width]; - isRegionDirty = new boolean[width][width]; - regenRegionBuffer = new int[width][width]; - - // populate isRegionDirty - for (int i = 0; i < width; i++) - for (int j = 0; j < width; j++) - isRegionDirty[i][j] = false; } @@ -936,14 +738,4 @@ public class LodDimension } return stringBuilder.toString(); } - - public boolean GetIsRegionDirty(int i, int j) - { - return isRegionDirty[i][j]; - } - - public void SetIsRegionDirty(int i, int j, boolean val) - { - isRegionDirty[i][j] = val; - } } diff --git a/src/main/java/com/seibel/lod/core/objects/lod/LodRegion.java b/src/main/java/com/seibel/lod/core/objects/lod/LodRegion.java index 02f98f2ba..b5dc6a9aa 100644 --- a/src/main/java/com/seibel/lod/core/objects/lod/LodRegion.java +++ b/src/main/java/com/seibel/lod/core/objects/lod/LodRegion.java @@ -20,6 +20,8 @@ package com.seibel.lod.core.objects.lod; import com.seibel.lod.core.enums.config.DistanceGenerationMode; +import com.seibel.lod.core.enums.config.DropoffQuality; +import com.seibel.lod.core.enums.config.GenerationPriority; import com.seibel.lod.core.enums.config.VerticalQuality; import com.seibel.lod.core.objects.PosToGenerateContainer; import com.seibel.lod.core.objects.PosToRenderContainer; @@ -29,8 +31,9 @@ import com.seibel.lod.core.util.LevelPosUtil; import com.seibel.lod.core.util.LodUtil; /** - * This object holds all loaded LevelContainers acting as a quad tree - * for a given region.

+ * This object holds all loaded LevelContainers acting as a quad tree for a + * given region.
+ *
* * Coordinate Standard:
* Coordinate called posX or posZ are relative LevelPos coordinates
@@ -39,445 +42,371 @@ import com.seibel.lod.core.util.LodUtil; * @author Leonardo Amato * @version 10-10-2021 */ -public class LodRegion -{ +public class LodRegion { /** Number of detail level supported by a region */ private static final byte POSSIBLE_LOD = 10; - - + /** Holds the lowest (least detailed) detail level in this region */ private byte minDetailLevel; - + public byte lastMaxDetailLevel = LodUtil.REGION_DETAIL_LEVEL; + /** * This holds all data for this region */ private final LevelContainer[] dataContainer; - + /** This chunk Pos has been generated */ - //private final boolean[] preGeneratedChunkPos; - + // private final boolean[] preGeneratedChunkPos; + /** the generation mode for this region */ private final DistanceGenerationMode generationMode; /** the vertical quality of this region */ private final VerticalQuality verticalQuality; - + /** this region's x RegionPos */ public final int regionPosX; /** this region's z RegionPos */ public final int regionPosZ; - - public LodRegion(byte minDetailLevel, RegionPos regionPos, DistanceGenerationMode generationMode, VerticalQuality verticalQuality) - { + + public volatile int needRegenBuffer = 2; + public volatile boolean needSaving = false; + + public LodRegion(byte minDetailLevel, RegionPos regionPos, DistanceGenerationMode generationMode, + VerticalQuality verticalQuality) { this.minDetailLevel = minDetailLevel; this.regionPosX = regionPos.x; this.regionPosZ = regionPos.z; this.verticalQuality = verticalQuality; this.generationMode = generationMode; dataContainer = new LevelContainer[POSSIBLE_LOD]; - - + // Initialize all the different matrices - for (byte lod = minDetailLevel; lod <= LodUtil.REGION_DETAIL_LEVEL; lod++) - { + for (byte lod = minDetailLevel; lod <= LodUtil.REGION_DETAIL_LEVEL; lod++) { dataContainer[lod] = new VerticalLevelContainer(lod); } - - boolean fileFound = false; - - /* - preGeneratedChunkPos = new boolean[32 * 32]; - if (MinecraftWrapper.INSTANCE.hasSinglePlayerServer() && LodConfig.CLIENT.worldGenerator.useExperimentalPreGenLoading.get()) - { - File regionFileDirHead; - File regionFileDirParent; - // local world - - ServerWorld serverWorld = LodUtil.getServerWorldFromDimension(MinecraftWrapper.INSTANCE.getCurrentDimension()); - - // provider needs a separate variable to prevent - // the compiler from complaining - StringBuilder string = new StringBuilder(); - try - { - ServerChunkProvider provider = serverWorld.getChunkSource(); - - //System.out.println(provider.dataStorage.dataFolder); - regionFileDirHead = new File(provider.dataStorage.dataFolder.getCanonicalFile().getParentFile().toPath().toAbsolutePath().toString() + File.separatorChar + "region", "r." + regionPosZ + "." + regionPosX + ".mca"); - if (regionFileDirHead.exists()) - { - regionFileDirParent = regionFileDirHead.getParentFile(); - //string.append(regionFileDirParent.toString()); - string.append(regionFileDirHead); - RegionFile regionFile = new RegionFile(regionFileDirHead, regionFileDirParent, true); - for (int x = 0; x < 32; x++) - { - for (int z = 0; z < 32; z++) - { - preGeneratedChunkPos[x * 32 + z] = regionFile.doesChunkExist(new ChunkPos(regionPosX * 32 + x, regionPosZ * 32 + z)); - } - } - - string.append("region " + regionPosX + " " + regionPosZ + "\n"); - for (int x = 0; x < 32; x++) - { - for (int z = 0; z < 32; z++) - { - //regionFile.doesChunkExist() - string.append(preGeneratedChunkPos[x * 32 + z] + "\t"); - } - string.append("\n"); - } - regionFile.close(); - } - } - catch (Exception e) - { - e.printStackTrace(); - } - System.out.println(string); - }*/ - } - - - /** Return true if the chunk has been pregenerated in game */ - //public boolean isChunkPreGenerated(int xChunkPos, int zChunkPos) - //{ - // xChunkPos = LevelPosUtil.getRegionModule(LodUtil.CHUNK_DETAIL_LEVEL, xChunkPos); - // zChunkPos = LevelPosUtil.getRegionModule(LodUtil.CHUNK_DETAIL_LEVEL, zChunkPos); - // return preGeneratedChunkPos[xChunkPos * 32 + zChunkPos]; - //} - + /** * Inserts the data point into the region. *

* TODO this will always return true unless it has + * * @return true if the data was added successfully */ - public boolean addData(byte detailLevel, int posX, int posZ, int verticalIndex, long data) - { + public boolean addData(byte detailLevel, int posX, int posZ, int verticalIndex, long data) { posX = LevelPosUtil.getRegionModule(detailLevel, posX); posZ = LevelPosUtil.getRegionModule(detailLevel, posZ); - + // The dataContainer could have null entries if the // detailLevel changes. if (this.dataContainer[detailLevel] == null) - this.dataContainer[detailLevel] = new VerticalLevelContainer(detailLevel); - + return false;// this.dataContainer[detailLevel] = new VerticalLevelContainer(detailLevel); + this.dataContainer[detailLevel].addData(data, posX, posZ, verticalIndex); - + return true; } - + /** * Inserts the vertical data into the region. *

* TODO this will always return true unless it has + * * @return true if the data was added successfully */ - public boolean addVerticalData(byte detailLevel, int posX, int posZ, long[] data) - { + public boolean addVerticalData(byte detailLevel, int posX, int posZ, long[] data) { posX = LevelPosUtil.getRegionModule(detailLevel, posX); posZ = LevelPosUtil.getRegionModule(detailLevel, posZ); - + // The dataContainer could have null entries if the // detailLevel changes. if (this.dataContainer[detailLevel] == null) - this.dataContainer[detailLevel] = new VerticalLevelContainer(detailLevel); - + return false;// this.dataContainer[detailLevel] = new VerticalLevelContainer(detailLevel); + return this.dataContainer[detailLevel].addVerticalData(data, posX, posZ); } - + /** * Get the dataPoint at the given relative position. - * @return the data at the relative pos and detail level, - * 0 if the data doesn't exist. + * + * @return the data at the relative pos and detail level, 0 if the data doesn't + * exist. */ - public long getData(byte detailLevel, int posX, int posZ, int verticalIndex) - { + public long getData(byte detailLevel, int posX, int posZ, int verticalIndex) { posX = LevelPosUtil.getRegionModule(detailLevel, posX); posZ = LevelPosUtil.getRegionModule(detailLevel, posZ); return dataContainer[detailLevel].getData(posX, posZ, verticalIndex); } - + /** * Get the dataPoint at the given relative position. - * @return the data at the relative pos and detail level, - * 0 if the data doesn't exist. + * + * @return the data at the relative pos and detail level, 0 if the data doesn't + * exist. */ - public long getSingleData(byte detailLevel, int posX, int posZ) - { + public long getSingleData(byte detailLevel, int posX, int posZ) { posX = LevelPosUtil.getRegionModule(detailLevel, posX); posZ = LevelPosUtil.getRegionModule(detailLevel, posZ); return dataContainer[detailLevel].getSingleData(posX, posZ); } - + /** * Clears the datapoint at the given relative position */ - public void clear(byte detailLevel, int posX, int posZ) - { + public void clear(byte detailLevel, int posX, int posZ) { posX = LevelPosUtil.getRegionModule(detailLevel, posX); posZ = LevelPosUtil.getRegionModule(detailLevel, posZ); dataContainer[detailLevel].clear(posX, posZ); } - + /** - * This method will fill the posToGenerate array with all levelPos that - * are render-able. + * This method will fill the posToGenerate array with all levelPos that are + * render-able. *

- * TODO why don't we return the posToGenerate, it would make this easier to understand + * TODO why don't we return the posToGenerate, it would make this easier to + * understand */ - public void getPosToGenerate(PosToGenerateContainer posToGenerate, - int playerBlockPosX, int playerBlockPosZ) - { - getPosToGenerate(posToGenerate, LodUtil.REGION_DETAIL_LEVEL, 0, 0, playerBlockPosX, playerBlockPosZ); - + public void getPosToGenerate(PosToGenerateContainer posToGenerate, int playerBlockPosX, int playerBlockPosZ, + GenerationPriority priority) { + getPosToGenerate(posToGenerate, LodUtil.REGION_DETAIL_LEVEL, 0, 0, playerBlockPosX, playerBlockPosZ, priority); + } - + /** * A recursive method that fills the posToGenerate array with all levelPos that * need to be generated. *

- * TODO why don't we return the posToGenerate, it would make this easier to understand + * TODO why don't we return the posToGenerate, it would make this easier to + * understand */ - private void getPosToGenerate(PosToGenerateContainer posToGenerate, byte detailLevel, - int childOffsetPosX, int childOffsetPosZ, int playerPosX, int playerPosZ) - { + private void getPosToGenerate(PosToGenerateContainer posToGenerate, byte detailLevel, int childOffsetPosX, + int childOffsetPosZ, int playerPosX, int playerPosZ, GenerationPriority priority) { // equivalent to 2^(...) int size = 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel); - + // calculate what LevelPos are in range to generate - int maxDistance = LevelPosUtil.maxDistance(detailLevel, childOffsetPosX, childOffsetPosZ, playerPosX, playerPosZ, regionPosX, regionPosZ); - + int minDistance = LevelPosUtil.minDistance(LodUtil.REGION_DETAIL_LEVEL, regionPosX, regionPosZ, playerPosX, + playerPosZ); + // determine this child's levelPos byte childDetailLevel = (byte) (detailLevel - 1); int childPosX = childOffsetPosX * 2; int childPosZ = childOffsetPosZ * 2; - - int childSize = 1 << (LodUtil.REGION_DETAIL_LEVEL - childDetailLevel); - - byte targetDetailLevel = DetailDistanceUtil.getLodGenDetail(DetailDistanceUtil.getGenerationDetailFromDistance(maxDistance)).detailLevel; - if (targetDetailLevel <= detailLevel) - { - if (targetDetailLevel == detailLevel) - { + + + byte targetDetailLevel = DetailDistanceUtil.getGenerationDetailFromDistance(minDistance); + if (targetDetailLevel <= detailLevel) { + if (targetDetailLevel == detailLevel) { if (!doesDataExist(detailLevel, childOffsetPosX, childOffsetPosZ)) - posToGenerate.addPosToGenerate(detailLevel, childOffsetPosX + regionPosX * size, childOffsetPosZ + regionPosZ * size); - } - else - { - // we want at max one request per chunk (since the world generator creates chunks). - // So for lod smaller than a chunk, only recurse down - // the top right child - - if (detailLevel > LodUtil.CHUNK_DETAIL_LEVEL) - { - int ungeneratedChildren = 0; - - // make sure all children are generated to this detailLevel + posToGenerate.addPosToGenerate(detailLevel, childOffsetPosX + regionPosX * size, + childOffsetPosZ + regionPosZ * size); + } else { + if (priority == GenerationPriority.FAR_FIRST && detailLevel >= posToGenerate.farMinDetail + && !doesDataExist(detailLevel, childOffsetPosX, childOffsetPosZ)) { + posToGenerate.addPosToGenerate(detailLevel, childOffsetPosX + regionPosX * size, + childOffsetPosZ + regionPosZ * size); + } else if (detailLevel > LodUtil.CHUNK_DETAIL_LEVEL) { for (int x = 0; x <= 1; x++) - { for (int z = 0; z <= 1; z++) - { - if (!doesDataExist(childDetailLevel, childPosX + x, childPosZ + z)) - { - ungeneratedChildren++; - posToGenerate.addPosToGenerate(childDetailLevel, childPosX + x + regionPosX * childSize, childPosZ + z + regionPosZ * childSize); - } - } - } - - // only if all the children are correctly generated - // should we go deeper - if (ungeneratedChildren == 0) - for (int x = 0; x <= 1; x++) - for (int z = 0; z <= 1; z++) - getPosToGenerate(posToGenerate, childDetailLevel, childPosX + x, childPosZ + z, playerPosX, playerPosZ); - } - else - { - // The detail Level is smaller than a chunk. - // Only recurse down the top right child. - - if (DetailDistanceUtil.getLodGenDetail(childDetailLevel).detailLevel <= (childDetailLevel)) - { - if (!doesDataExist(childDetailLevel, childPosX, childPosZ)) - posToGenerate.addPosToGenerate(childDetailLevel, childPosX + regionPosX * childSize, childPosZ + regionPosZ * childSize); - else - getPosToGenerate(posToGenerate, childDetailLevel, childPosX, childPosZ, playerPosX, playerPosZ); - } + getPosToGenerate(posToGenerate, childDetailLevel, childPosX + x, childPosZ + z, playerPosX, + playerPosZ, priority); + } else { + // we want at max one request per chunk (since the world generator creates + // chunks). + // So for lod smaller than a chunk, only recurse down + // the top right child + getPosToGenerate(posToGenerate, childDetailLevel, childPosX, childPosZ, playerPosX, playerPosZ, + priority); } } } // we have gone beyond the target Detail level // we can stop generating - + } - - + /** - * This method will fill the posToRender array with all levelPos that - * are render-able. + * This method will fill the posToRender array with all levelPos that are + * render-able. *

- * TODO why don't we return the posToRender, it would make this easier to understand + * TODO why don't we return the posToRender, it would make this easier to + * understand */ - public void getPosToRender(PosToRenderContainer posToRender, - int playerPosX, int playerPosZ, boolean requireCorrectDetailLevel) - { - getPosToRender(posToRender, LodUtil.REGION_DETAIL_LEVEL, 0, 0, playerPosX, playerPosZ, requireCorrectDetailLevel); + public void getPosToRender(PosToRenderContainer posToRender, int playerPosX, int playerPosZ, + boolean requireCorrectDetailLevel, DropoffQuality dropoffQuality) { + int minDistance = LevelPosUtil.minDistance(LodUtil.REGION_DETAIL_LEVEL, 0, 0, playerPosX, playerPosZ, regionPosX, regionPosZ); + byte targetLevel = DetailDistanceUtil.getDrawDetailFromDistance(minDistance); + if (targetLevel <= dropoffQuality.fastModeSwitch) { + getPosToRender(posToRender, LodUtil.REGION_DETAIL_LEVEL, 0, 0, playerPosX, playerPosZ, + requireCorrectDetailLevel); + } else { + getPosToRenderFlat(posToRender, LodUtil.REGION_DETAIL_LEVEL, 0, 0, targetLevel, requireCorrectDetailLevel); + } } - + /** - * This method will fill the posToRender array with all levelPos that - * are render-able. + * This method will fill the posToRender array with all levelPos that are + * render-able. *

- * TODO why don't we return the posToRender, it would make this easier to understand - * TODO this needs some more comments, James was only able to figure out part of it + * TODO why don't we return the posToRender, it would make this easier to + * understand TODO this needs some more comments, James was only able to figure + * out part of it */ - private void getPosToRender(PosToRenderContainer posToRender, - byte detailLevel, int posX, int posZ, - int playerPosX, int playerPosZ, boolean requireCorrectDetailLevel) - { + private void getPosToRender(PosToRenderContainer posToRender, byte detailLevel, int posX, int posZ, int playerPosX, + int playerPosZ, boolean requireCorrectDetailLevel) { // equivalent to 2^(...) int size = 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel); - + byte desiredLevel; int maxDistance; int minDistance; int childLevel; - - + // calculate the LevelPos that are in range maxDistance = LevelPosUtil.maxDistance(detailLevel, posX, posZ, playerPosX, playerPosZ, regionPosX, regionPosZ); desiredLevel = DetailDistanceUtil.getLodDrawDetail(DetailDistanceUtil.getDrawDetailFromDistance(maxDistance)); minDistance = LevelPosUtil.minDistance(detailLevel, posX, posZ, playerPosX, playerPosZ, regionPosX, regionPosZ); childLevel = DetailDistanceUtil.getLodDrawDetail(DetailDistanceUtil.getDrawDetailFromDistance(minDistance)); - - if (detailLevel == childLevel - 1) + + if (detailLevel == childLevel - 1) { + posToRender.addPosToRender(detailLevel, posX + regionPosX * size, posZ + regionPosZ * size); + } else + // if (desiredLevel > detailLevel) + // { + // we have gone beyond the target Detail level + // we can stop generating + // } else + if (desiredLevel == detailLevel) { + posToRender.addPosToRender(detailLevel, posX + regionPosX * size, posZ + regionPosZ * size); + } else // case where (detailLevel > desiredLevel) { - posToRender.addPosToRender(detailLevel, - posX + regionPosX * size, - posZ + regionPosZ * size); + int childPosX = posX * 2; + int childPosZ = posZ * 2; + byte childDetailLevel = (byte) (detailLevel - 1); + int childrenCount = 0; + + for (int x = 0; x <= 1; x++) { + for (int z = 0; z <= 1; z++) { + if (doesDataExist(childDetailLevel, childPosX + x, childPosZ + z)) { + if (!requireCorrectDetailLevel) + childrenCount++; + else + getPosToRender(posToRender, childDetailLevel, childPosX + x, childPosZ + z, playerPosX, + playerPosZ, requireCorrectDetailLevel); + } + } + } + + if (!requireCorrectDetailLevel) { + // If all the four children exist go deeper + if (childrenCount == 4) { + for (int x = 0; x <= 1; x++) + for (int z = 0; z <= 1; z++) + getPosToRender(posToRender, childDetailLevel, childPosX + x, childPosZ + z, playerPosX, + playerPosZ, requireCorrectDetailLevel); + } else { + posToRender.addPosToRender(detailLevel, posX + regionPosX * size, posZ + regionPosZ * size); + } + } } - else - //if (desiredLevel > detailLevel) - //{ - // we have gone beyond the target Detail level - // we can stop generating - //} else - if (desiredLevel == detailLevel) - { - posToRender.addPosToRender(detailLevel, - posX + regionPosX * size, - posZ + regionPosZ * size); - } - else //case where (detailLevel > desiredLevel) - { - int childPosX = posX * 2; - int childPosZ = posZ * 2; - byte childDetailLevel = (byte) (detailLevel - 1); - int childrenCount = 0; - - for (int x = 0; x <= 1; x++) - { - for (int z = 0; z <= 1; z++) - { - if (doesDataExist(childDetailLevel, childPosX + x, childPosZ + z)) - { - if (!requireCorrectDetailLevel) - childrenCount++; - else - getPosToRender(posToRender, childDetailLevel, childPosX + x, childPosZ + z, playerPosX, playerPosZ, requireCorrectDetailLevel); - } - } - } - - - if (!requireCorrectDetailLevel) - { - // If all the four children exist go deeper - if (childrenCount == 4) - { - for (int x = 0; x <= 1; x++) - for (int z = 0; z <= 1; z++) - getPosToRender(posToRender, childDetailLevel, childPosX + x, childPosZ + z, playerPosX, playerPosZ, requireCorrectDetailLevel); - } - else - { - posToRender.addPosToRender(detailLevel, - posX + regionPosX * size, - posZ + regionPosZ * size); - } - } - } } - - + + /** + * This method will fill the posToRender array with all levelPos that are + * render-able. But the entire region try use the same detail level. + */ + private void getPosToRenderFlat(PosToRenderContainer posToRender, byte detailLevel, int posX, int posZ, byte targetLevel, boolean requireCorrectDetailLevel) { + // equivalent to 2^(...) + int size = 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel); + + if (detailLevel == targetLevel) { + posToRender.addPosToRender(detailLevel, posX + regionPosX * size, posZ + regionPosZ * size); + } else // case where (detailLevel > desiredLevel) + { + int childPosX = posX * 2; + int childPosZ = posZ * 2; + byte childDetailLevel = (byte) (detailLevel - 1); + int childrenCount = 0; + + for (int x = 0; x <= 1; x++) { + for (int z = 0; z <= 1; z++) { + if (doesDataExist(childDetailLevel, childPosX + x, childPosZ + z)) { + if (!requireCorrectDetailLevel) + childrenCount++; + else + getPosToRenderFlat(posToRender, childDetailLevel, childPosX + x, childPosZ + z, targetLevel, requireCorrectDetailLevel); + } + } + } + + if (!requireCorrectDetailLevel) { + // If all the four children exist go deeper + if (childrenCount == 4) { + for (int x = 0; x <= 1; x++) + for (int z = 0; z <= 1; z++) + getPosToRenderFlat(posToRender, childDetailLevel, childPosX + x, childPosZ + z, targetLevel, requireCorrectDetailLevel); + } else { + posToRender.addPosToRender(detailLevel, posX + regionPosX * size, posZ + regionPosZ * size); + } + } + } + } + /** * Updates all children. *

* TODO could this be renamed mergeArea? */ - public void updateArea(byte detailLevel, int posX, int posZ) - { + public void updateArea(byte detailLevel, int posX, int posZ) { int width; int startX; int startZ; - - // TODO what are each of these loops updating? - for (byte down = (byte) (minDetailLevel + 1); down <= detailLevel; down++) - { + + // Update the level lower or equal to the detail level + for (byte down = (byte) (minDetailLevel + 1); down <= detailLevel; down++) { startX = LevelPosUtil.convert(detailLevel, posX, down); startZ = LevelPosUtil.convert(detailLevel, posZ, down); width = 1 << (detailLevel - down); - + for (int x = 0; x < width; x++) for (int z = 0; z < width; z++) update(down, startX + x, startZ + z); } - - - for (byte up = (byte) (detailLevel + 1); up <= LodUtil.REGION_DETAIL_LEVEL; up++) - { - update(up, - LevelPosUtil.convert(detailLevel, posX, up), - LevelPosUtil.convert(detailLevel, posZ, up)); + + // Update the level higher than the detail level + for (byte up = (byte) (Math.max(detailLevel, minDetailLevel) + 1); up <= LodUtil.REGION_DETAIL_LEVEL; up++) { + update(up, LevelPosUtil.convert(detailLevel, posX, up), LevelPosUtil.convert(detailLevel, posZ, up)); } } - + /** * Update the child at the given relative Pos *

* TODO could this be renamed mergeChildData? */ - private void update(byte detailLevel, int posX, int posZ) - { + private void update(byte detailLevel, int posX, int posZ) { posX = LevelPosUtil.getRegionModule(detailLevel, posX); posZ = LevelPosUtil.getRegionModule(detailLevel, posZ); dataContainer[detailLevel].updateData(dataContainer[detailLevel - 1], posX, posZ); } - - + /** * Returns if data exists at the given relative Pos. */ - public boolean doesDataExist(byte detailLevel, int posX, int posZ) - { + public boolean doesDataExist(byte detailLevel, int posX, int posZ) { if (detailLevel < minDetailLevel || dataContainer[detailLevel] == null) return false; - + posX = LevelPosUtil.getRegionModule(detailLevel, posX); posZ = LevelPosUtil.getRegionModule(detailLevel, posZ); - + return dataContainer[detailLevel].doesItExist(posX, posZ); } - + /** * Gets the generation mode for the data point at the given relative pos. */ - public byte getGenerationMode(byte detailLevel, int posX, int posZ) - { + public byte getGenerationMode(byte detailLevel, int posX, int posZ) { if (dataContainer[detailLevel].doesItExist(posX, posZ)) // We take the bottom information always // TODO what does that mean? bottom of what? @@ -485,129 +414,115 @@ public class LodRegion else return DistanceGenerationMode.NONE.complexity; } - + /** - * Returns the lowest (least detailed) detail level in this region - * TODO is that right? + * Returns the lowest (least detailed) detail level in this region TODO is that + * right? */ - public byte getMinDetailLevel() - { + public byte getMinDetailLevel() { return minDetailLevel; } - + /** * Returns the LevelContainer for the detailLevel - * @throws IllegalArgumentException if the detailLevel is less than minDetailLevel + * + * @throws IllegalArgumentException if the detailLevel is less than + * minDetailLevel */ - public LevelContainer getLevel(byte detailLevel) - { + public LevelContainer getLevel(byte detailLevel) { if (detailLevel < minDetailLevel) - throw new IllegalArgumentException("getLevel asked for a detail level that does not exist: minimum: [" + minDetailLevel + "] level requested: [" + detailLevel + "]"); - + throw new IllegalArgumentException("getLevel asked for a detail level that does not exist: minimum: [" + + minDetailLevel + "] level requested: [" + detailLevel + "]"); + return dataContainer[detailLevel]; } - + /** - * Add the levelContainer to this Region, updating the minDetailLevel - * if necessary. - * @throws IllegalArgumentException if the LevelContainer's detailLevel - * is 2 or more detail levels lower than the - * minDetailLevel of this region. + * Add the levelContainer to this Region, updating the minDetailLevel if + * necessary. + * + * @throws IllegalArgumentException if the LevelContainer's detailLevel is 2 or + * more detail levels lower than the + * minDetailLevel of this region. */ - public void addLevelContainer(LevelContainer levelContainer) - { - if (levelContainer.getDetailLevel() < minDetailLevel - 1) - { - throw new IllegalArgumentException( - "the LevelContainer's detailLevel was " - + "[" + levelContainer.getDetailLevel() + "] but this region " - + "only allows adding LevelContainers with a " - + "detail level of [" + (minDetailLevel - 1) + "]"); + public void addLevelContainer(LevelContainer levelContainer) { + if (levelContainer.getDetailLevel() < minDetailLevel - 1) { + throw new IllegalArgumentException("the LevelContainer's detailLevel was " + "[" + + levelContainer.getDetailLevel() + "] but this region " + + "only allows adding LevelContainers with a " + "detail level of [" + (minDetailLevel - 1) + "]"); } - + if (levelContainer.getDetailLevel() == minDetailLevel - 1) minDetailLevel = levelContainer.getDetailLevel(); - + dataContainer[levelContainer.getDetailLevel()] = levelContainer; } - + // TODO James thinks cutTree and growTree (which he renamed to match cutTree) // should have more descriptive names, to make sure the "Tree" portion isn't // confused with Minecraft trees (the plant). - + /** - * Removes any dataContainers that are higher than - * the given detailLevel + * Removes any dataContainers that are higher than the given detailLevel */ - public void cutTree(byte detailLevel) - { - if (detailLevel > minDetailLevel) - { + public void cutTree(byte detailLevel) { + if (detailLevel > minDetailLevel) { for (byte detailLevelIndex = 0; detailLevelIndex < detailLevel; detailLevelIndex++) dataContainer[detailLevelIndex] = null; - + minDetailLevel = detailLevel; } } - + /** - * Make this region more detailed to the detailLevel given. - * TODO is that correct? + * Make this region more detailed to the detailLevel given. TODO is that + * correct? */ - public void growTree(byte detailLevel) - { - if (detailLevel < minDetailLevel) - { - for (byte detailLevelIndex = (byte) (minDetailLevel - 1); detailLevelIndex >= detailLevel; detailLevelIndex--) - { + public void growTree(byte detailLevel) { + if (detailLevel < minDetailLevel) { + for (byte detailLevelIndex = (byte) (minDetailLevel + - 1); detailLevelIndex >= detailLevel; detailLevelIndex--) { if (dataContainer[detailLevelIndex + 1] == null) dataContainer[detailLevelIndex + 1] = new VerticalLevelContainer((byte) (detailLevelIndex + 1)); - + dataContainer[detailLevelIndex] = dataContainer[detailLevelIndex + 1].expand(); } minDetailLevel = detailLevel; } } - + /** * return RegionPos of this lod region */ - public RegionPos getRegionPos() - { + public RegionPos getRegionPos() { return new RegionPos(regionPosX, regionPosZ); } - + /** * Returns how many LODs are in this region */ - public int getNumberOfLods() - { + public int getNumberOfLods() { int count = 0; for (LevelContainer container : dataContainer) count += container.getMaxNumberOfLods(); - + return count; } - - public VerticalQuality getVerticalQuality() - { + + public VerticalQuality getVerticalQuality() { return verticalQuality; } - - public DistanceGenerationMode getGenerationMode() - { + + public DistanceGenerationMode getGenerationMode() { return generationMode; } - - public int getMaxVerticalData(byte detailLevel) - { + + public int getMaxVerticalData(byte detailLevel) { return dataContainer[detailLevel].getVerticalSize(); } - - + @Override - public String toString() - { + public String toString() { return getLevel(LodUtil.REGION_DETAIL_LEVEL).toString(); } } diff --git a/src/main/java/com/seibel/lod/core/objects/lod/LodWorld.java b/src/main/java/com/seibel/lod/core/objects/lod/LodWorld.java index 9897d297d..69c21f23f 100644 --- a/src/main/java/com/seibel/lod/core/objects/lod/LodWorld.java +++ b/src/main/java/com/seibel/lod/core/objects/lod/LodWorld.java @@ -92,6 +92,7 @@ public class LodWorld public void deselectWorld() { worldName = NO_WORLD_LOADED; + saveAllDimensions(true); // Make sure all dims are saved. This will block threads lodDimensions = null; isWorldLoaded = false; } @@ -106,7 +107,8 @@ public class LodWorld if (lodDimensions == null) return; - lodDimensions.put(newDimension.dimension, newDimension); + LodDimension oldDim = lodDimensions.put(newDimension.dimension, newDimension); + if (oldDim != null) oldDim.saveDirtyRegionsToFile(true); } /** @@ -129,7 +131,7 @@ public class LodWorld if (lodDimensions == null) return; - saveAllDimensions(); + saveAllDimensions(true); //block until saving is done for (IDimensionTypeWrapper key : lodDimensions.keySet()) lodDimensions.get(key).setRegionWidth(newRegionWidth); @@ -138,7 +140,7 @@ public class LodWorld /** * Requests all dimensions save any dirty regions they may have. */ - public void saveAllDimensions() + public void saveAllDimensions(boolean isBlocking) { if (lodDimensions == null) return; @@ -147,8 +149,10 @@ public class LodWorld // but that requires a LodDimension.hasDirtyRegions() method or something similar ClientApi.LOGGER.info("Saving LODs"); - for (IDimensionTypeWrapper key : lodDimensions.keySet()) - lodDimensions.get(key).saveDirtyRegionsToFileAsync(); + for (IDimensionTypeWrapper key : lodDimensions.keySet()) { + lodDimensions.get(key).saveDirtyRegionsToFile(isBlocking); + } + //FIXME: This should block until file is saved. } diff --git a/src/main/java/com/seibel/lod/core/objects/lod/RegionPos.java b/src/main/java/com/seibel/lod/core/objects/lod/RegionPos.java index f50754d02..6260cb418 100644 --- a/src/main/java/com/seibel/lod/core/objects/lod/RegionPos.java +++ b/src/main/java/com/seibel/lod/core/objects/lod/RegionPos.java @@ -82,10 +82,29 @@ public class RegionPos .offset(LodUtil.CHUNK_WIDTH / 2, 0, LodUtil.CHUNK_WIDTH / 2); } + @Override + public boolean equals(Object o) { + // If the object is compared with itself then return true + if (o == this) { + return true; + } + // Check if o is an instance of RegionPos or not + if (!(o instanceof RegionPos)) { + return false; + } + RegionPos c = (RegionPos) o; + return c.x==x &&c.z==z; + } + @Override public String toString() { return "(" + x + "," + z + ")"; } + + @Override + public int hashCode() { + return Long.hashCode((long)(x) << Integer.BYTES + z); + } } diff --git a/src/main/java/com/seibel/lod/core/objects/lod/VerticalLevelContainer.java b/src/main/java/com/seibel/lod/core/objects/lod/VerticalLevelContainer.java index d0a280a49..1e580769b 100644 --- a/src/main/java/com/seibel/lod/core/objects/lod/VerticalLevelContainer.java +++ b/src/main/java/com/seibel/lod/core/objects/lod/VerticalLevelContainer.java @@ -210,14 +210,14 @@ public class VerticalLevelContainer implements LevelContainer } private static long[] downgradeVerticalSize(int oldVertSize, int newVertSize, long[] data) { - long[] dataToMerge = new long[oldVertSize]; int size = data.length/oldVertSize; + long[] dataToMerge = new long[oldVertSize]; long[] newData = new long[size * newVertSize]; for (int i = 0; i < size; i++) { - System.arraycopy(oldVertSize, i * oldVertSize, dataToMerge, 0, oldVertSize); - dataToMerge = DataPointUtil.mergeMultiData(dataToMerge, oldVertSize, newVertSize); - System.arraycopy(dataToMerge, 0, newData, i * newVertSize, newVertSize); + System.arraycopy(data, i * oldVertSize, dataToMerge, 0, oldVertSize); + long[] tempBuffer = DataPointUtil.mergeMultiData(dataToMerge, oldVertSize, newVertSize); + System.arraycopy(tempBuffer, 0, newData, i * newVertSize, newVertSize); } return newData; } diff --git a/src/main/java/com/seibel/lod/core/objects/opengl/LodVertexBuffer.java b/src/main/java/com/seibel/lod/core/objects/opengl/LodVertexBuffer.java index 4bf6e94b5..14127a28d 100644 --- a/src/main/java/com/seibel/lod/core/objects/opengl/LodVertexBuffer.java +++ b/src/main/java/com/seibel/lod/core/objects/opengl/LodVertexBuffer.java @@ -21,6 +21,7 @@ package com.seibel.lod.core.objects.opengl; import org.lwjgl.opengl.GL32; +import com.seibel.lod.core.api.ClientApi; import com.seibel.lod.core.enums.rendering.GLProxyContext; import com.seibel.lod.core.render.GLProxy; @@ -33,6 +34,7 @@ import com.seibel.lod.core.render.GLProxy; */ public class LodVertexBuffer implements AutoCloseable { + public static int count = 0; public int id; public int vertexCount; public final boolean isBufferStorage; @@ -44,6 +46,8 @@ public class LodVertexBuffer implements AutoCloseable throw new IllegalStateException("Thread [" +Thread.currentThread().getName() + "] tried to create a [" + LodVertexBuffer.class.getSimpleName() + "] outside a OpenGL contex."); this.id = GL32.glGenBuffers(); this.isBufferStorage = isBufferStorage; + count++; + //ClientApi.LOGGER.info("LodVertexBuffer Count: "+count); } @@ -52,8 +56,15 @@ public class LodVertexBuffer implements AutoCloseable { if (this.id >= 0) { - GLProxy.getInstance().recordOpenGlCall(() -> GL32.glDeleteBuffers(this.id)); + if (GLProxy.getInstance().getGlContext() == GLProxyContext.PROXY_WORKER) { + GL32.glDeleteBuffers(this.id); + } else { + final int id = this.id; + GLProxy.getInstance().recordOpenGlCall(() -> GL32.glDeleteBuffers(id)); + } this.id = -1; + count--; + //ClientApi.LOGGER.info("LodVertexBuffer Count: "+count); } } } \ No newline at end of file diff --git a/src/main/java/com/seibel/lod/core/render/GLProxy.java b/src/main/java/com/seibel/lod/core/render/GLProxy.java index 1b9c34f05..99ceb1109 100644 --- a/src/main/java/com/seibel/lod/core/render/GLProxy.java +++ b/src/main/java/com/seibel/lod/core/render/GLProxy.java @@ -25,6 +25,7 @@ import java.io.IOException; import java.io.PrintStream; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; import org.lwjgl.glfw.GLFW; import org.lwjgl.opengl.GL; @@ -39,6 +40,7 @@ import com.seibel.lod.core.api.ClientApi; import com.seibel.lod.core.enums.config.GpuUploadMethod; import com.seibel.lod.core.enums.rendering.DebugMode; import com.seibel.lod.core.enums.rendering.GLProxyContext; +import com.seibel.lod.core.util.LodThreadFactory; import com.seibel.lod.core.util.SingletonHandler; import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton; import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftWrapper; @@ -65,7 +67,7 @@ public class GLProxy private static final IMinecraftWrapper MC = SingletonHandler.get(IMinecraftWrapper.class); - private static final ExecutorService workerThread = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat(GLProxy.class.getSimpleName() + "-Worker-Thread").build()); + private ExecutorService workerThread = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat(GLProxy.class.getSimpleName() + "-Worker-Thread").build()); private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class); @@ -423,6 +425,22 @@ public class GLProxy } } + public static void ensureAllGLJobCompleted() { + if (!hasInstance()) return; + ClientApi.LOGGER.info("Blocking until GL jobs finished!"); + try { + instance.workerThread.shutdown(); + boolean worked = instance.workerThread.awaitTermination(30, TimeUnit.SECONDS); + if (!worked) + ClientApi.LOGGER.error("GLWorkerThread shutdown timed out! Game may crash on exit due to cleanup failure!"); + } catch (InterruptedException e) { + ClientApi.LOGGER.error("GLWorkerThread shutdown is interrupted! Game may crash on exit due to cleanup failure!"); + e.printStackTrace(); + } finally { + instance.workerThread = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat(GLProxy.class.getSimpleName() + "-Worker-Thread").build()); + } + } + /** * If called from a legacy OpenGL context this will * set the fog end to infinity with a density of 0. diff --git a/src/main/java/com/seibel/lod/core/render/LodRenderer.java b/src/main/java/com/seibel/lod/core/render/LodRenderer.java index 3d94d14a7..233737f92 100644 --- a/src/main/java/com/seibel/lod/core/render/LodRenderer.java +++ b/src/main/java/com/seibel/lod/core/render/LodRenderer.java @@ -49,6 +49,7 @@ import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton; import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper; import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftWrapper; import com.seibel.lod.core.wrapperInterfaces.minecraft.IProfilerWrapper; +import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper; /** * This is where all the magic happens.
@@ -59,21 +60,6 @@ import com.seibel.lod.core.wrapperInterfaces.minecraft.IProfilerWrapper; */ public class LodRenderer { - public static class VanillaRenderedChunksList extends GridList { - private static final long serialVersionUID = -5448501880911391315L; - - public final int centerX; - public final int centerZ; - - public VanillaRenderedChunksList(int range, int centerX, int centerZ) { - super(range); - this.centerX = centerX; - this.centerZ = centerZ; - for (int i=0; i vanillaRenderedChunks; public int vanillaRenderedChunksCenterX; public int vanillaRenderedChunksCenterZ; public int vanillaRenderedChunksRefreshTimer; @@ -245,7 +231,7 @@ public class LodRenderer GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, 0); // set the required open GL settings - if (CONFIG.client().advanced().debugging().getDebugMode() == DebugMode.SHOW_DETAIL_WIREFRAME) + if (CONFIG.client().advanced().debugging().getDebugMode() == DebugMode.SHOW_DETAIL_WIREFRAME || CONFIG.client().advanced().debugging().getDebugMode() == DebugMode.SHOW_GENMODE_WIREFRAME) GL32.glPolygonMode(GL32.GL_FRONT_AND_BACK, GL32.GL_LINE); else GL32.glPolygonMode(GL32.GL_FRONT_AND_BACK, GL32.GL_FILL); @@ -304,14 +290,18 @@ public class LodRenderer int lowRegionX = vbos.getCenterX() - vbos.gridCentreToEdge; int lowRegionZ = vbos.getCenterY() - vbos.gridCentreToEdge; int drawCall = 0; - for (int regionX=lowRegionX; regionX 256) { - vanillaRenderedChunks = new VanillaRenderedChunksList( + IWorldWrapper world = MC.getWrappedClientWorld(); + if (lastUpdatedPos.getY() > world.getHeight()-world.getMinHeight()) { + vanillaRenderedChunks = new MovableGridList( chunkRenderDistance, chunkX, chunkZ); return true; } - VanillaRenderedChunksList chunkList; - - if (recreateChunks) { - vanillaRenderedChunks = new VanillaRenderedChunksList(chunkRenderDistance, chunkX, chunkZ); - return true; - } else { - chunkList = vanillaRenderedChunks; - chunkX = chunkList.centerX; - chunkZ = chunkList.centerZ; - chunkRenderDistance = (short) vanillaRenderedChunks.gridCentreToEdge; - } + MovableGridList chunkList; boolean anyChanged = false; + if (vanillaRenderedChunks == null || vanillaRenderedChunks.gridCentreToEdge != chunkRenderDistance || + vanillaRenderedChunks.getCenterX()!=chunkX || vanillaRenderedChunks.getCenterY()!=chunkZ) { + chunkList = new MovableGridList(chunkRenderDistance, chunkX, chunkZ); + anyChanged = true; + } else { + chunkList = vanillaRenderedChunks; + } + LagSpikeCatcher getChunks = new LagSpikeCatcher(); - Set chunkPosToSkip = LodUtil.getNearbyLodChunkPosToSkip(lodDim, previousPos); + Set chunkPosToSkip = LodUtil.getNearbyLodChunkPosToSkip(lodDim, lastUpdatedPos); getChunks.end("LodDrawSetup:UpdateStatus:UpdateVanillaChunks:getChunks"); for (AbstractChunkPosWrapper pos : chunkPosToSkip) { - int xIndex = (pos.getX() - chunkX) + (chunkRenderDistance + 1); - int zIndex = (pos.getZ() - chunkZ) + (chunkRenderDistance + 1); - // sometimes we are given chunks that are outside the render distance, // This prevents index out of bounds exceptions - if (xIndex >= 0 && zIndex >= 0 - && xIndex < vanillaRenderedChunks.gridSize - && zIndex < vanillaRenderedChunks.gridSize) + if (!chunkList.inRange(pos.getX(), pos.getZ())) continue; + Boolean oldBool = chunkList.swap(pos.getX(), pos.getZ(), true); + if (oldBool == null || !oldBool) { - if (!chunkList.get(chunkList.calculateOffset(xIndex, zIndex))) - { - chunkList.set(chunkList.calculateOffset(xIndex, zIndex), true); - anyChanged = true; - lodDim.markRegionBufferToRegen(pos.getRegionX(), pos.getRegionZ()); - } + anyChanged = true; + lodDim.markRegionBufferToRegen(pos.getRegionX(), pos.getRegionZ()); } } - vanillaRenderedChunks = chunkList; + if (anyChanged) vanillaRenderedChunks = chunkList; return anyChanged; } @@ -552,7 +534,6 @@ public class LodRenderer long newTime = System.currentTimeMillis(); AbstractBlockPosWrapper newPos = MC.getPlayerBlockPos(); boolean shouldUpdateChunks = false; - boolean posUpdated = false; boolean tryPartialGen = false; boolean tryFullGen = false; @@ -573,13 +554,11 @@ public class LodRenderer // check if the player has moved if (newTime - prevPlayerPosTime > CONFIG.client().advanced().buffers().getRebuildTimes().playerMoveTimeout) { - if (previousPos == null - || Math.abs(newPos.getX() - previousPos.getX()) > CONFIG.client().advanced().buffers().getRebuildTimes().playerMoveDistance*16 - || Math.abs(newPos.getZ() - previousPos.getZ()) > CONFIG.client().advanced().buffers().getRebuildTimes().playerMoveDistance*16) + if (lastUpdatedPos == null + || Math.abs(newPos.getX() - lastUpdatedPos.getX()) > CONFIG.client().advanced().buffers().getRebuildTimes().playerMoveDistance*16 + || Math.abs(newPos.getZ() - lastUpdatedPos.getZ()) > CONFIG.client().advanced().buffers().getRebuildTimes().playerMoveDistance*16) { - tryPartialGen = true; - previousPos = newPos; - posUpdated = true; + shouldUpdateChunks = true; } prevPlayerPosTime = newTime; } @@ -598,14 +577,10 @@ public class LodRenderer prevChunkTime = newTime; } - - if (tryFullGen && !posUpdated) { - previousPos = newPos; - posUpdated = true; - } - shouldUpdateChunks |= posUpdated; + shouldUpdateChunks |= tryFullGen; if (shouldUpdateChunks) { - tryPartialGen |= updateVanillaRenderedChunks(lodDim, posUpdated); + lastUpdatedPos = newPos; + tryPartialGen |= updateVanillaRenderedChunks(lodDim); } if (tryFullGen) { diff --git a/src/main/java/com/seibel/lod/core/util/BooleanMovableGridList.java b/src/main/java/com/seibel/lod/core/util/BooleanMovableGridList.java new file mode 100644 index 000000000..2cdb5058f --- /dev/null +++ b/src/main/java/com/seibel/lod/core/util/BooleanMovableGridList.java @@ -0,0 +1,248 @@ +package com.seibel.lod.core.util; + +/*Layout: + * 0,1,2, + * 3,4,5, + * 6,7,8 + */ + +public class BooleanMovableGridList { + + private int centerX; + private int centerY; + + public final int gridCentreToEdge; + public final int gridSize; + private boolean[] b; + + public BooleanMovableGridList(int gridCentreToEdge, int centerX, int centerY) { + gridSize = gridCentreToEdge * 2 + 1; + this.gridCentreToEdge = gridCentreToEdge; + this.centerX = centerX; + this.centerY = centerY; + clear(); + } + + public void clear() { + b = new boolean[gridSize*gridSize]; + } + + public int getCenterX() {return centerX;} + public int getCenterY() {return centerY;} + + private void assertIndex(int ix, int iy) { + if (ix<0 || ix>=gridSize || iy<0 || iy>=gridSize) + throw new IndexOutOfBoundsException("BooleanMovableGridList index position out of bound"); + } + + public boolean isInBound(int x, int y) { + x = x-centerX+gridCentreToEdge; + y = y-centerY+gridCentreToEdge; + return !(x<0 || x>=gridSize || y<0 || y>=gridSize); + } + + // return onFail if x,y is outside of the grid + public boolean get(int x, int y) { + x = x-centerX+gridCentreToEdge; + y = y-centerY+gridCentreToEdge; + return _getDirect(x,y); + } + + // return false if x,y is outside of the grid + public void set(int x, int y, boolean t) { + x = x-centerX+gridCentreToEdge; + y = y-centerY+gridCentreToEdge; + _setDirect(x,y,t); + } + + // return onFail if x,y is outside of the grid + // Otherwise, return the new value (for chaining) + public boolean setAndGet(int x, int y, boolean t) { + x = x-centerX+gridCentreToEdge; + y = y-centerY+gridCentreToEdge; + _setDirect(x,y,t); + return t; + } + // return null if x,y is outside of the grid + // Otherwise, return the old value + public boolean swap(int x, int y, boolean t, boolean onFail) { + x = x-centerX+gridCentreToEdge; + y = y-centerY+gridCentreToEdge; + return _swapDirect(x,y, t); + } + + private final boolean _getDirect(int x, int y) { + assertIndex(x,y); + return b[x + y * gridSize]; + } + private final void _setDirect(int x, int y, boolean t) { + assertIndex(x,y); + b[x + y * gridSize] = t; + } + private final boolean _swapDirect(int x, int y, boolean t) { + assertIndex(x,y); + boolean r = b[x + y * gridSize]; + b[x + y * gridSize] = t; + return r; + } + + interface BoolTransformer { + boolean transform(boolean oldValue, int x, int y); + } + + // Transform the list via the function. The data can still be accessed + // inside the function, and the returned value will not be applied + // until all elements have done the transform. + public void twoStageTransform(BoolTransformer transformer) { + boolean[] nb = new boolean[b.length]; + int i=0; + for (int y=0; y { + if (v!=t) return v; + boolean r = false; + r |= (isInBound(x-1,y) ? get(x-1,y)==!t : false); + r |= (isInBound(x,y-1) ? get(x,y-1)==!t : false); + r |= (isInBound(x+1,y) ? get(x+1,y)==!t : false); + r |= (isInBound(x,y+1) ? get(x,y+1)==!t : false); + return r ? !t : t; + }; + twoStageTransform(tran); + } + public void flipBorderCorner(boolean valueToBeFlipped) { + boolean t = valueToBeFlipped; + BoolTransformer tran = (v, x, y) -> { + if (v!=t) return v; + boolean r = false; + r |= (isInBound(x-1,y) ? get(x-1,y)==!t : false); + r |= (isInBound(x,y-1) ? get(x,y-1)==!t : false); + r |= (isInBound(x+1,y) ? get(x+1,y)==!t : false); + r |= (isInBound(x,y+1) ? get(x,y+1)==!t : false); + r |= (isInBound(x-1,y-1) ? get(x-1,y-1)==!t : false); + r |= (isInBound(x+1,y-1) ? get(x+1,y-1)==!t : false); + r |= (isInBound(x+1,y+1) ? get(x+1,y+1)==!t : false); + r |= (isInBound(x-1,y+1) ? get(x-1,y+1)==!t : false); + return r ? !t : t; + }; + twoStageTransform(tran); + } + public void flipBorderCorner(boolean valueToBeFlipped, int range) { + boolean t = valueToBeFlipped; + BoolTransformer tran = (v, x, y) -> { + if (v!=t) return v; + boolean r = false; + for (int dx=-range;dx<=range;dx++) + for (int dy=-range;dy<=range;dy++) + r |= (isInBound(x+dx,y+dy) ? get(x+dx,y+dy)==!t : false); + return r ? !t : t; + }; + twoStageTransform(tran); + } + + + // Return false if haven't changed. Return true if it did + public boolean move(int newCenterX, int newCenterY) { + return move(newCenterX, newCenterY, false); + } + + // Return false if haven't changed. Return true if it did + public boolean move(int newCenterX, int newCenterY, boolean value) { + if (centerX == newCenterX && centerY == newCenterY) return false; + int deltaX = newCenterX - centerX; + int deltaY = newCenterY - centerY; + + // if the x or z offset is equal to or greater than + // the total width, just delete the current data + // and update the centerX and/or centerZ + if (Math.abs(deltaX) >= gridSize || Math.abs(deltaY) >= gridSize) + { + clear(); + // update the new center + centerX = newCenterX; + centerY = newCenterY; + return true; + } + centerX = newCenterX; + centerY = newCenterY; + + // X + if (deltaX >= 0 && deltaY >= 0) + { + // move everything over to the left-up (as the center moves to the right-down) + for (int x = 0; x < gridSize; x++) + { + for (int y = 0; y < gridSize; y++) + { + _setDirect(x, y, _getDirect(x+deltaX, y+deltaY)); + } + } + } + else if (deltaX < 0 && deltaY >= 0) + { + // move everything over to the right-up (as the center moves to the left-down) + for (int x = gridSize - 1; x >= 0; x--) + { + for (int y = 0; y < gridSize; y++) + { + _setDirect(x, y, _getDirect(x+deltaX, y+deltaY)); + } + } + } + else if (deltaX >= 0 && deltaY < 0) + { + // move everything over to the left-down (as the center moves to the right-up) + for (int x = 0; x < gridSize; x++) + { + for (int y = gridSize - 1; y >= 0; y--) + { + _setDirect(x, y, _getDirect(x+deltaX, y+deltaY)); + } + } + } + else //if (deltaX < 0 && deltaY < 0) + { + // move everything over to the right-down (as the center moves to the left-up) + for (int x = gridSize - 1; x >= 0; x--) + { + for (int y = gridSize - 1; y >= 0; y--) + { + _setDirect(x, y, _getDirect(x+deltaX, y+deltaY)); + } + } + } + return true; + } + + + @Override + public String toString() { + return "MovableGridList[" + centerX + "," + centerY + "] " + gridSize + "*" + gridSize + "[" + b.length + "]"; + } + + public String toDetailString() { + StringBuilder str = new StringBuilder("\n"); + int i = 0; + str.append(toString()); + str.append("\n"); + for (boolean t : b) { + str.append(t ? "#" : "."); + i++; + if (i % gridSize == 0) { + str.append("\n"); + } else { + //str.append(", "); + } + } + return str.toString(); + } +} diff --git a/src/main/java/com/seibel/lod/core/util/LodUtil.java b/src/main/java/com/seibel/lod/core/util/LodUtil.java index 507918280..d5644873c 100644 --- a/src/main/java/com/seibel/lod/core/util/LodUtil.java +++ b/src/main/java/com/seibel/lod/core/util/LodUtil.java @@ -365,7 +365,7 @@ public class LodUtil // get the chunks that are going to be rendered by Minecraft - HashSet posToSkip = REFLECTION_HANDLER.sodiumPresent() ? MC_RENDER.getSodiumRenderedChunks() : MC_RENDER.getVanillaRenderedChunks(); + HashSet posToSkip = MC_RENDER.getVanillaRenderedChunks(); // remove everything outside the skipRadius, @@ -395,6 +395,7 @@ public class LodUtil * @param z relative (to the matrix) z chunk to check * @return true if and only if the chunk is a border of the renderable chunks */ + @Deprecated public static boolean isBorderChunk(boolean[][] vanillaRenderedChunks, int x, int z) { if (x < 0 || z < 0 || x >= vanillaRenderedChunks.length || z >= vanillaRenderedChunks[0].length) @@ -411,6 +412,17 @@ public class LodUtil } return false; } + public static boolean isBorderChunk(MovableGridList vanillaRenderedChunks, int chunkX, int chunkZ) + { + for (LodDirection lodDirection : VertexOptimizer.ADJ_DIRECTIONS) + { + int tempX = chunkX + lodDirection.getNormal().x; + int tempZ = chunkZ + lodDirection.getNormal().z; + Boolean b = vanillaRenderedChunks.get(tempX, tempZ); + if (b == null || !b) return true; + } + return false; + } /** This is copied from Minecraft's MathHelper class */ diff --git a/src/main/java/com/seibel/lod/core/util/MovableGridList.java b/src/main/java/com/seibel/lod/core/util/MovableGridList.java index 7a2d75968..79d6093f2 100644 --- a/src/main/java/com/seibel/lod/core/util/MovableGridList.java +++ b/src/main/java/com/seibel/lod/core/util/MovableGridList.java @@ -2,6 +2,7 @@ package com.seibel.lod.core.util; import java.util.ArrayList; import java.util.List; +import java.util.function.Consumer; /*Layout: * 0,1,2, @@ -46,6 +47,14 @@ public class MovableGridList extends ArrayList implements List { super.add(null); } } + public void clear(Consumer d) { + super.forEach(d); + super.clear(); + super.ensureCapacity(gridSize*gridSize); + for (int i=0; i extends ArrayList implements List { return _getDirect(x,y); } + // return false if x,y is outside of the grid + public boolean set(int x, int y, T t) { + x = x-centerX+gridCentreToEdge; + y = y-centerY+gridCentreToEdge; + return _setDirect(x,y, t); + } + // return null if x,y is outside of the grid + // Otherwise, return the new value (for chaining) public T setAndGet(int x, int y, T t) { x = x-centerX+gridCentreToEdge; y = y-centerY+gridCentreToEdge; return _setDirect(x,y, t) ? t : null; } + // return null if x,y is outside of the grid + // Otherwise, return the old value + public T swap(int x, int y, T t) { + x = x-centerX+gridCentreToEdge; + y = y-centerY+gridCentreToEdge; + return _swapDirect(x,y, t); + } + + public boolean inRange(int x, int y) { + x = x-centerX+gridCentreToEdge; + y = y-centerY+gridCentreToEdge; + return (x>=0 && x=0 && y=gridSize || y<0 || y>=gridSize) return null; @@ -73,9 +103,14 @@ public class MovableGridList extends ArrayList implements List { super.set(x + y * gridSize, t); return true; } + private final T _swapDirect(int x, int y, T t) { + if (x<0 || x>=gridSize || y<0 || y>=gridSize) return null; + return super.set(x + y * gridSize, t); + } - public void move(int newCenterX, int newCenterY) { - if (centerX == newCenterX && centerY == newCenterY) return; + // Return false if haven't changed. Return true if it did + public boolean move(int newCenterX, int newCenterY) { + if (centerX == newCenterX && centerY == newCenterY) return false; int deltaX = newCenterX - centerX; int deltaY = newCenterY - centerY; @@ -88,8 +123,87 @@ public class MovableGridList extends ArrayList implements List { // update the new center centerX = newCenterX; centerY = newCenterY; + return true; + } + centerX = newCenterX; + centerY = newCenterY; + + // X + if (deltaX >= 0 && deltaY >= 0) + { + // move everything over to the left-up (as the center moves to the right-down) + for (int x = 0; x < gridSize; x++) + { + for (int y = 0; y < gridSize; y++) + { + _setDirect(x, y, _getDirect(x+deltaX, y+deltaY)); + } + } + } + else if (deltaX < 0 && deltaY >= 0) + { + // move everything over to the right-up (as the center moves to the left-down) + for (int x = gridSize - 1; x >= 0; x--) + { + for (int y = 0; y < gridSize; y++) + { + _setDirect(x, y, _getDirect(x+deltaX, y+deltaY)); + } + } + } + else if (deltaX >= 0 && deltaY < 0) + { + // move everything over to the left-down (as the center moves to the right-up) + for (int x = 0; x < gridSize; x++) + { + for (int y = gridSize - 1; y >= 0; y--) + { + _setDirect(x, y, _getDirect(x+deltaX, y+deltaY)); + } + } + } + else //if (deltaX < 0 && deltaY < 0) + { + // move everything over to the right-down (as the center moves to the left-up) + for (int x = gridSize - 1; x >= 0; x--) + { + for (int y = gridSize - 1; y >= 0; y--) + { + _setDirect(x, y, _getDirect(x+deltaX, y+deltaY)); + } + } + } + return true; + } + + public void move(int newCenterX, int newCenterY, Consumer d) { + if (centerX == newCenterX && centerY == newCenterY) return; + int deltaX = newCenterX - centerX; + int deltaY = newCenterY - centerY; + + // if the x or z offset is equal to or greater than + // the total width, just delete the current data + // and update the centerX and/or centerZ + if (Math.abs(deltaX) >= gridSize || Math.abs(deltaY) >= gridSize) + { + clear(d); + // update the new center + centerX = newCenterX; + centerY = newCenterY; return; } + centerX = newCenterX; + centerY = newCenterY; + + // Dealloc stuff + for (int x=0; x=gridSize || y-deltaY>=gridSize) { + d.accept(_getDirect(x,y)); + } + } + } // X if (deltaX >= 0 && deltaY >= 0) @@ -136,10 +250,8 @@ public class MovableGridList extends ArrayList implements List { } } } - centerX = newCenterX; - centerY = newCenterY; } - + // TODO: This is unused but may be useful later on. /* @@ -154,6 +266,8 @@ public class MovableGridList extends ArrayList implements List { public String toDetailString() { StringBuilder str = new StringBuilder("\n"); int i = 0; + str.append(toString()); + str.append("\n"); for (T t : this) { str.append(t!=null ? t.toString() : "NULL"); diff --git a/src/main/java/com/seibel/lod/core/wrapperInterfaces/chunk/IChunkWrapper.java b/src/main/java/com/seibel/lod/core/wrapperInterfaces/chunk/IChunkWrapper.java index 89f73401a..d37b3bcdf 100644 --- a/src/main/java/com/seibel/lod/core/wrapperInterfaces/chunk/IChunkWrapper.java +++ b/src/main/java/com/seibel/lod/core/wrapperInterfaces/chunk/IChunkWrapper.java @@ -55,4 +55,8 @@ public interface IChunkWrapper boolean isWaterLogged(int x, int y, int z); int getEmittedBrightness(int x, int y, int z); + + default int getBlockLight(int x, int y, int z) {return -1;} + + default int getSkyLight(int x, int y, int z) {return -1;} } diff --git a/src/main/java/com/seibel/lod/core/wrapperInterfaces/config/ILodConfigWrapperSingleton.java b/src/main/java/com/seibel/lod/core/wrapperInterfaces/config/ILodConfigWrapperSingleton.java index 5f4f335c1..00da18d5c 100644 --- a/src/main/java/com/seibel/lod/core/wrapperInterfaces/config/ILodConfigWrapperSingleton.java +++ b/src/main/java/com/seibel/lod/core/wrapperInterfaces/config/ILodConfigWrapperSingleton.java @@ -22,6 +22,7 @@ package com.seibel.lod.core.wrapperInterfaces.config; import com.seibel.lod.core.enums.config.BlocksToAvoid; import com.seibel.lod.core.enums.config.BufferRebuildTimes; import com.seibel.lod.core.enums.config.DistanceGenerationMode; +import com.seibel.lod.core.enums.config.DropoffQuality; import com.seibel.lod.core.enums.config.GenerationPriority; import com.seibel.lod.core.enums.config.GpuUploadMethod; import com.seibel.lod.core.enums.config.HorizontalQuality; @@ -54,6 +55,13 @@ public interface ILodConfigWrapperSingleton IGraphics graphics(); IWorldGenerator worldGenerator(); IAdvanced advanced(); + + + boolean OPTIONS_BUTTON_DEFAULT = true; + String OPTIONS_BUTTON_DESC = "" + + " Show the lod button in the options screen next to fov"; + boolean getOptionsButton(); + void setOptionsButton(boolean newOptionsButton); //==================// @@ -133,6 +141,21 @@ public interface ILodConfigWrapperSingleton + " Highest Quality: " + HorizontalQuality.HIGH; HorizontalQuality getHorizontalQuality(); void setHorizontalQuality(HorizontalQuality newHorizontalQuality); + + DropoffQuality DROPOFF_QUALITY_DEFAULT = DropoffQuality.AUTO; + String DROPOFF_QUALITY_DESC = "" + + " This determines how lod level drop off will be done. \n" + + "\n" + + " " + DropoffQuality.SMOOTH_DROPOFF + ": \n" + + " The lod level is calculated for each point, making the drop off a smooth circle. \n" + + " " + DropoffQuality.PERFORMANCE_FOCUSED + ": \n" + + " One detail level for an entire region. Minimize CPU usage and \n" + + " improve terrain refresh delay, especially for high Lod render distance. \n" + + " " + DropoffQuality.AUTO + ": \n" + + " Use "+ DropoffQuality.SMOOTH_DROPOFF + " for less then 128 Lod render distance, \n" + + " or "+ DropoffQuality.PERFORMANCE_FOCUSED +" otherwise. \n"; + DropoffQuality getDropoffQuality(); + void setDropoffQuality(DropoffQuality newDropoffQuality); } interface IFogQuality @@ -274,7 +297,7 @@ public interface ILodConfigWrapperSingleton + " Higher settings will make terrain look good when looking backwards \n" + " when changing speeds quickly, but will increase upload times and GPU usage."; int getBacksideCullingRange(); - void setBacksideCullingRange(int backsideCullingRange); + void setBacksideCullingRange(int newBacksideCullingRange); boolean USE_EXTENDED_NEAR_CLIP_PLANE_DEFAULT = false; String USE_EXTENDED_NEAR_CLIP_PLANE_DESC = "" diff --git a/src/main/java/com/seibel/lod/core/wrapperInterfaces/minecraft/IMinecraftRenderWrapper.java b/src/main/java/com/seibel/lod/core/wrapperInterfaces/minecraft/IMinecraftRenderWrapper.java index 886e9603a..6ef47b760 100644 --- a/src/main/java/com/seibel/lod/core/wrapperInterfaces/minecraft/IMinecraftRenderWrapper.java +++ b/src/main/java/com/seibel/lod/core/wrapperInterfaces/minecraft/IMinecraftRenderWrapper.java @@ -22,6 +22,7 @@ package com.seibel.lod.core.wrapperInterfaces.minecraft; import java.awt.Color; import java.util.HashSet; +import com.seibel.lod.core.api.ModAccessorApi; import com.seibel.lod.core.objects.math.Mat4f; import com.seibel.lod.core.objects.math.Vec3d; import com.seibel.lod.core.objects.math.Vec3f; @@ -29,6 +30,7 @@ import com.seibel.lod.core.util.SingletonHandler; import com.seibel.lod.core.wrapperInterfaces.IWrapperFactory; import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper; import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper; +import com.seibel.lod.core.wrapperInterfaces.modAccessor.ISodiumAccessor; /** * Contains everything related to @@ -77,18 +79,8 @@ public interface IMinecraftRenderWrapper */ public default HashSet getVanillaRenderedChunks() { - return getMaximumRenderedChunks(); - } - - /** - * This method returns the ChunkPos of every chunk that - * Sodium is going to render this frame. - *
- * If not implemented this calls {@link #getMaximumRenderedChunks()}. - */ - public default HashSet getSodiumRenderedChunks() - { - return getMaximumRenderedChunks(); + ISodiumAccessor sodium = ModAccessorApi.get(ISodiumAccessor.class); + return sodium==null ? getMaximumRenderedChunks() : sodium.getNormalRenderedChunks(); } /** @@ -101,9 +93,6 @@ public interface IMinecraftRenderWrapper IWrapperFactory factory = SingletonHandler.get(IWrapperFactory.class); int chunkRenderDist = this.getRenderDistance(); - // if we have a odd render distance, we'll have a empty gap. This way we'll overlap by 1 instead, - // which is preferable to having a hole in the world - chunkRenderDist = chunkRenderDist % 2 == 0 ? chunkRenderDist : chunkRenderDist - 1; AbstractChunkPosWrapper centerChunkPos = mcWrapper.getPlayerChunkPos(); int startChunkX = centerChunkPos.getX() - chunkRenderDist; @@ -111,9 +100,9 @@ public interface IMinecraftRenderWrapper // add every position within render distance HashSet renderedPos = new HashSet(); - for (int chunkX = 0; chunkX < (chunkRenderDist * 2); chunkX++) + for (int chunkX = 0; chunkX < (chunkRenderDist * 2+1); chunkX++) { - for(int chunkZ = 0; chunkZ < (chunkRenderDist * 2); chunkZ++) + for(int chunkZ = 0; chunkZ < (chunkRenderDist * 2+1); chunkZ++) { renderedPos.add(factory.createChunkPos(startChunkX + chunkX, startChunkZ + chunkZ)); } diff --git a/src/main/java/com/seibel/lod/core/wrapperInterfaces/modAccessor/IModAccessor.java b/src/main/java/com/seibel/lod/core/wrapperInterfaces/modAccessor/IModAccessor.java new file mode 100644 index 000000000..dbf0b878e --- /dev/null +++ b/src/main/java/com/seibel/lod/core/wrapperInterfaces/modAccessor/IModAccessor.java @@ -0,0 +1,5 @@ +package com.seibel.lod.core.wrapperInterfaces.modAccessor; + +public abstract interface IModAccessor { + String getModName(); +} diff --git a/src/main/java/com/seibel/lod/core/wrapperInterfaces/modAccessor/ISodiumAccessor.java b/src/main/java/com/seibel/lod/core/wrapperInterfaces/modAccessor/ISodiumAccessor.java new file mode 100644 index 000000000..d2335c8e2 --- /dev/null +++ b/src/main/java/com/seibel/lod/core/wrapperInterfaces/modAccessor/ISodiumAccessor.java @@ -0,0 +1,9 @@ +package com.seibel.lod.core.wrapperInterfaces.modAccessor; + +import java.util.HashSet; + +import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper; + +public interface ISodiumAccessor extends IModAccessor { + HashSet getNormalRenderedChunks(); +} diff --git a/src/main/java/com/seibel/lod/core/wrapperInterfaces/world/IWorldWrapper.java b/src/main/java/com/seibel/lod/core/wrapperInterfaces/world/IWorldWrapper.java index ba6214e62..6726d1a56 100644 --- a/src/main/java/com/seibel/lod/core/wrapperInterfaces/world/IWorldWrapper.java +++ b/src/main/java/com/seibel/lod/core/wrapperInterfaces/world/IWorldWrapper.java @@ -23,6 +23,8 @@ import java.io.File; import com.seibel.lod.core.enums.WorldType; import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper; +import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper; +import com.seibel.lod.core.wrapperInterfaces.chunk.IChunkWrapper; /** * Can be either a Server world or a Client world. @@ -60,5 +62,7 @@ public interface IWorldWrapper /** @throws UnsupportedOperationException if the WorldWrapper isn't for a ServerWorld */ File getSaveFolder() throws UnsupportedOperationException; + default IChunkWrapper tryGetChunk(AbstractChunkPosWrapper pos) {return null;} + } diff --git a/src/main/resources/assets/lod/lang/en_us.json b/src/main/resources/assets/lod/lang/en_us.json index 51724f6d2..d80c49345 100644 --- a/src/main/resources/assets/lod/lang/en_us.json +++ b/src/main/resources/assets/lod/lang/en_us.json @@ -1,8 +1,8 @@ { "lod.title": "Distant Horizons", "DistantHorizons.config.title": "Distant Horizons config", - "DistantHorizons.config.ShowButton": "Show menu button", - "DistantHorizons.config.ShowButton.@tooltip": "Show the custom button to the left of the fov button", + "DistantHorizons.config.optionsButton": "Show options button", + "DistantHorizons.config.optionsButton.@tooltip": "Show the custom button to the left of the fov button", "DistantHorizons.config.client": "Client", "DistantHorizons.config.client.graphics": "Graphics", "DistantHorizons.config.client.graphics.quality": "Quality options", @@ -16,6 +16,8 @@ "DistantHorizons.config.client.graphics.quality.horizontalScale.@tooltip": "This indicates how quickly fake chunks drop off in quality", "DistantHorizons.config.client.graphics.quality.horizontalQuality": "Horizontal quality", "DistantHorizons.config.client.graphics.quality.horizontalQuality.@tooltip": "This indicates the exponential base of the quadratic drop-off", + "DistantHorizons.config.client.graphics.quality.dropoffQuality": "Dropoff quality", + "DistantHorizons.config.client.graphics.quality.dropoffQuality.@tooltip": "This change how detail dropoff is done.", "DistantHorizons.config.client.graphics.fogQuality": "Fog options", "DistantHorizons.config.client.graphics.fogQuality.fogDistance": "Fog distance", "DistantHorizons.config.client.graphics.fogQuality.fogDistance.@tooltip": "At what distance should Fog be drawn on the fake chunks?", @@ -26,6 +28,7 @@ "DistantHorizons.config.client.graphics.fogQuality.disableVanillaFog": "Disable vanilla fog", "DistantHorizons.config.client.graphics.fogQuality.disableVanillaFog.@tooltip": "If true disable Minecraft's fog. \nMay cause issues with other mods that edit fog. \nMay cause errors with other fog editing mods", "DistantHorizons.config.client.graphics.cloudQuality": "Cloud options", + "DistantHorizons.config.client.graphics.cloudQuality.cloudWarning": "§l§nWARNING:§r§n Clouds are still experimental", "DistantHorizons.config.client.graphics.cloudQuality.customClouds": "Custom cloud", "DistantHorizons.config.client.graphics.cloudQuality.customClouds.@tooltip": "Do you want to use a custom way or rendering the clouds. \nIf you turn this off then the other settings here wont do anything \nThis will also make the clouds more dynamic", "DistantHorizons.config.client.graphics.cloudQuality.fabulousClouds": "Fabulous clouds", @@ -120,6 +123,8 @@ "DistantHorizons.config.enum.DebugMode.OFF": "Off", "DistantHorizons.config.enum.DebugMode.SHOW_DETAIL": "Show detail", "DistantHorizons.config.enum.DebugMode.SHOW_DETAIL_WIREFRAME": "Show detail with wireframe", + "DistantHorizons.config.enum.DebugMode.SHOW_GENMODE": "Show generation mode", + "DistantHorizons.config.enum.DebugMode.SHOW_GENMODE_WIREFRAME": "Show generation mode with wireframe", "DistantHorizons.config.enum.GpuUploadMethod.AUTO": "Auto", "DistantHorizons.config.enum.GpuUploadMethod.BUFFER_STORAGE": "Buffer storage", "DistantHorizons.config.enum.GpuUploadMethod.SUB_DATA": "Sub data", @@ -127,5 +132,8 @@ "DistantHorizons.config.enum.GpuUploadMethod.DATA": "Data", "DistantHorizons.config.enum.BufferRebuildTimes.FREQUENT": "Frequent", "DistantHorizons.config.enum.BufferRebuildTimes.NORMAL": "Normal", - "DistantHorizons.config.enum.BufferRebuildTimes.RARE": "Rare" + "DistantHorizons.config.enum.BufferRebuildTimes.RARE": "Rare", + "DistantHorizons.config.enum.DropoffQuality.AUTO": "Auto", + "DistantHorizons.config.enum.DropoffQuality.SMOOTH_DROPOFF": "Smooth dropoff", + "DistantHorizons.config.enum.DropoffQuality.PERFORMANCE_FOCUSED": "Performance focused" } diff --git a/src/main/resources/assets/lod/textures/environment/clouds_small.png b/src/main/resources/assets/lod/textures/environment/clouds_small.png new file mode 100644 index 000000000..973b20e6e Binary files /dev/null and b/src/main/resources/assets/lod/textures/environment/clouds_small.png differ