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..429252e82 100644 --- a/src/main/java/com/seibel/lod/core/api/EventApi.java +++ b/src/main/java/com/seibel/lod/core/api/EventApi.java @@ -81,7 +81,6 @@ public class EventApi if (lodDim == null) return; - // FIXME: This is in server thread. We shouldn't be accessing the client's renderer! LodWorldGenerator.INSTANCE.queueGenerationRequests(lodDim, ApiShared.lodBuilder); } @@ -99,7 +98,7 @@ public class EventApi public void worldSaveEvent() { - ApiShared.lodWorld.saveAllDimensions(); + ApiShared.lodWorld.saveAllDimensions(false); // Do an async save. } /** This is also called when a new dimension loads */ @@ -126,11 +125,12 @@ public class EventApi 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 +141,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 @@ -157,7 +157,6 @@ public class EventApi LodWorldGenerator.INSTANCE.numberOfChunksWaitingToGenerate.set(0); ApiShared.lodWorld.deselectWorld(); - // prevent issues related to the buffer builder // breaking or retaining previous data when changing worlds. ClientApi.renderer.destroyBuffers(); @@ -213,7 +212,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 +234,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/builders/bufferBuilding/LodBufferBuilderFactory.java b/src/main/java/com/seibel/lod/core/builders/bufferBuilding/LodBufferBuilderFactory.java index f2756813c..bca3a0bbe 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)+"!"); } } @@ -372,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"); //================================// @@ -400,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) { @@ -840,7 +840,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 35c2da8a0..d26c86ad0 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 @@ -205,7 +205,7 @@ public class LodBuilder posX = LevelPosUtil.convert((byte) 0, chunk.getChunkPosX() * 16 + startX, minDetailLevel); posZ = LevelPosUtil.convert((byte) 0, chunk.getChunkPosZ() * 16 + startZ, minDetailLevel); if (!lodDim.doesDataExist(minDetailLevel, posX, posZ)) { - lodDim.addVerticalData(minDetailLevel, posX, posZ, data, false); + lodDim.addVerticalData(minDetailLevel, posX, posZ, data); lodDim.updateData(minDetailLevel, posX, posZ); } } 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 4baa4bc30..28321ee87 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 @@ -97,12 +97,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; } } @@ -157,9 +166,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; 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 53a4f8448..a2ddc873a 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) @@ -112,7 +117,13 @@ public class LodDimensionFileHandler */ public LodRegion loadRegionFromFile(byte detailLevel, RegionPos regionPos, DistanceGenerationMode generationMode, VerticalQuality verticalQuality) { - LodRegion region = new LodRegion((byte) (LodUtil.REGION_DETAIL_LEVEL+1), 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); } @@ -123,14 +134,12 @@ public class LodDimensionFileHandler public LodRegion loadRegionFromFile(byte detailLevel, LodRegion region, DistanceGenerationMode generationMode, VerticalQuality verticalQuality) { if (region.getGenerationMode().compareTo(generationMode)<0 || region.getVerticalQuality().compareTo(verticalQuality)<0) { - //TODO: add flush and save region for old one + 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 = (byte) (region.getMinDetailLevel()-1); tempDetailLevel >= detailLevel; tempDetailLevel--) { @@ -214,33 +223,88 @@ 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); + 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(); + 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); + } /** * Save a specific region to disk.
@@ -256,7 +320,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 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/lod/LodDimension.java b/src/main/java/com/seibel/lod/core/objects/lod/LodDimension.java index 8c5aa7f2c..b249e95fe 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 @@ -38,13 +38,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 @@ -62,7 +62,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; @@ -77,11 +76,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 @@ -140,8 +136,6 @@ public class LodDimension regions = new LodRegion[width][width]; - isRegionDirty = new boolean[width][width]; - regenRegionBuffer = new int[width][width]; center = new RegionPos(0, 0); } @@ -158,6 +152,7 @@ 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; @@ -169,7 +164,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; @@ -187,14 +181,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; - } } } } @@ -205,14 +195,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; - } + } } } @@ -225,14 +212,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; - } } } } @@ -243,15 +226,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; - } - } } } } @@ -378,18 +356,17 @@ public class LodDimension byte minAllowedDetailLevel; regionX = (x + center.x) - halfWidth; regionZ = (z + center.z) - halfWidth; - - if (regions[x][z] != null) { + 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 (regions[x][z].getMinDetailLevel() < minAllowedDetailLevel) { - regions[x][z].cutTree(minAllowedDetailLevel); - regenRegionBuffer[x][z] = 2; + if (region.getMinDetailLevel() < minAllowedDetailLevel) { + region.cutTree(minAllowedDetailLevel); + region.needRegenBuffer = 2; regenDimensionBuffers = true; } } @@ -436,12 +413,15 @@ public class LodDimension boolean updated = false; if (region == null) { - regions[x][z] = getRegionFromFile(regionPos, minDetail, generationMode, verticalQuality); + 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) { - regions[x][z] = getRegionFromFile(regions[x][z], minDetail, generationMode, verticalQuality); + // 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 (region.lastMaxDetailLevel != maxDetail) { region.lastMaxDetailLevel = maxDetail; @@ -449,9 +429,8 @@ public class LodDimension } else if (region.lastMaxDetailLevel != region.getMinDetailLevel()) { updated = true; } - if (updated) { - regenRegionBuffer[x][z] = 2; + region.needRegenBuffer = 2; regenDimensionBuffers = true; } }); @@ -462,55 +441,12 @@ public class LodDimension 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; - - 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; - } - /** * Add whole column of LODs 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 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); @@ -521,44 +457,28 @@ 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; posToGenerate = new PosToGenerateContainer((byte) 8, maxDataToGenerate, playerBlockPosX, playerBlockPosZ); @@ -566,7 +486,7 @@ public class LodDimension //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); + lodRegion.getPosToGenerate(posToGenerate, playerBlockPosX, playerBlockPosZ, priority); }); return posToGenerate; } @@ -656,8 +576,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; } /** @@ -666,16 +587,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; @@ -696,11 +613,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 */ @@ -732,9 +648,10 @@ public class LodDimension } /** 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); } @@ -789,13 +706,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; } @@ -818,14 +728,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 304444c1d..b29d4a127 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,7 @@ package com.seibel.lod.core.objects.lod; import com.seibel.lod.core.enums.config.DistanceGenerationMode; +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; @@ -67,6 +68,9 @@ public class LodRegion /** this region's z RegionPos */ public final int regionPosZ; + public volatile int needRegenBuffer = 2; + public volatile boolean needSaving = false; + public LodRegion(byte minDetailLevel, RegionPos regionPos, DistanceGenerationMode generationMode, VerticalQuality verticalQuality) { this.minDetailLevel = minDetailLevel; @@ -81,73 +85,8 @@ public class LodRegion { 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. *

@@ -229,9 +168,9 @@ public class LodRegion * TODO why don't we return the posToGenerate, it would make this easier to understand */ public void getPosToGenerate(PosToGenerateContainer posToGenerate, - int playerBlockPosX, int playerBlockPosZ) + int playerBlockPosX, int playerBlockPosZ, GenerationPriority priority) { - getPosToGenerate(posToGenerate, LodUtil.REGION_DETAIL_LEVEL, 0, 0, playerBlockPosX, playerBlockPosZ); + getPosToGenerate(posToGenerate, LodUtil.REGION_DETAIL_LEVEL, 0, 0, playerBlockPosX, playerBlockPosZ, priority); } @@ -242,7 +181,7 @@ public class LodRegion * 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) + int childOffsetPosX, int childOffsetPosZ, int playerPosX, int playerPosZ, GenerationPriority priority) { // equivalent to 2^(...) int size = 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel); @@ -275,11 +214,14 @@ public class LodRegion { for (int x = 0; x <= 1; x++) for (int z = 0; z <= 1; z++) - getPosToGenerate(posToGenerate, childDetailLevel, childPosX + x, childPosZ + z, playerPosX, playerPosZ); - } - else - { - getPosToGenerate(posToGenerate, childDetailLevel, childPosX, childPosZ, playerPosX, playerPosZ); + getPosToGenerate(posToGenerate, childDetailLevel, childPosX + x, childPosZ + z, playerPosX, playerPosZ, priority); + } else if (priority == GenerationPriority.FAR_FIRST && detailLevel == posToGenerate.farMinDetail) { + if (!doesDataExist(detailLevel, childOffsetPosX, childOffsetPosZ)) + posToGenerate.addPosToGenerate(detailLevel, childOffsetPosX + regionPosX * size, childOffsetPosZ + regionPosZ * size); + else + getPosToGenerate(posToGenerate, childDetailLevel, childPosX, childPosZ, playerPosX, playerPosZ, priority); + } else { + getPosToGenerate(posToGenerate, childDetailLevel, childPosX, childPosZ, playerPosX, playerPosZ, priority); } } } 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/render/LodRenderer.java b/src/main/java/com/seibel/lod/core/render/LodRenderer.java index 47d6b0f22..c78d41c06 100644 --- a/src/main/java/com/seibel/lod/core/render/LodRenderer.java +++ b/src/main/java/com/seibel/lod/core/render/LodRenderer.java @@ -382,7 +382,7 @@ public class LodRenderer } /** Create all buffers that will be used. */ - public void setupBuffers(LodDimension lodDim) + public void setupBuffers() { lodBufferBuilderFactory.allBuffersRequireReset = true; }