diff --git a/core/src/main/java/com/seibel/distanthorizons/core/config/Config.java b/core/src/main/java/com/seibel/distanthorizons/core/config/Config.java index 68ff786bf..15abd43d7 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/config/Config.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/config/Config.java @@ -28,8 +28,10 @@ import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiDistantGenerat import com.seibel.distanthorizons.core.config.eventHandlers.QuickRenderToggleConfigEventHandler; import com.seibel.distanthorizons.core.config.eventHandlers.RenderCacheConfigEventHandler; import com.seibel.distanthorizons.core.config.eventHandlers.UnsafeValuesConfigListener; +import com.seibel.distanthorizons.core.config.eventHandlers.WorldCurvatureConfigEventHandler; import com.seibel.distanthorizons.core.config.eventHandlers.presets.ThreadPresetConfigEventHandler; import com.seibel.distanthorizons.core.config.eventHandlers.presets.RenderQualityPresetConfigEventHandler; +import com.seibel.distanthorizons.core.config.listeners.ConfigChangeListener; import com.seibel.distanthorizons.core.config.types.*; import com.seibel.distanthorizons.core.config.types.enums.EConfigEntryAppearance; import com.seibel.distanthorizons.core.config.types.enums.EConfigEntryPerformance; @@ -575,6 +577,7 @@ public class Config + "Note: Due to current limitations, the min value is 50 \n" + "and the max value is 5000. Any values outside this range \n" + "will be set to 0 (disabled).") + .addListener(WorldCurvatureConfigEventHandler.INSTANCE) .build(); public static ConfigEntry lodBias = new ConfigEntry.Builder() @@ -734,6 +737,7 @@ public class Config + "") .build(); + // not currently implemented public static ConfigEntry enableMultiverseNetworking = new ConfigEntry.Builder() .set(true) .comment("" @@ -742,6 +746,7 @@ public class Config + "") .build(); + // not currently implemented public static ConfigEntry enableServerNetworking = new ConfigEntry.Builder() .set(false) .comment("" diff --git a/core/src/main/java/com/seibel/distanthorizons/core/config/eventHandlers/WorldCurvatureConfigEventHandler.java b/core/src/main/java/com/seibel/distanthorizons/core/config/eventHandlers/WorldCurvatureConfigEventHandler.java new file mode 100644 index 000000000..0fdea7e48 --- /dev/null +++ b/core/src/main/java/com/seibel/distanthorizons/core/config/eventHandlers/WorldCurvatureConfigEventHandler.java @@ -0,0 +1,47 @@ +package com.seibel.distanthorizons.core.config.eventHandlers; + +import com.seibel.distanthorizons.api.DhApi; +import com.seibel.distanthorizons.api.enums.config.ELodShading; +import com.seibel.distanthorizons.api.enums.config.EMaxHorizontalResolution; +import com.seibel.distanthorizons.api.enums.config.EVerticalQuality; +import com.seibel.distanthorizons.core.config.Config; +import com.seibel.distanthorizons.core.config.listeners.IConfigListener; + +import java.util.Timer; +import java.util.TimerTask; + +/** + * Listens to the config and will automatically + * clear the current render cache if certain settings are changed.

+ * + * Note: if additional settings should clear the render cache, add those to this listener, don't create a new listener + */ +public class WorldCurvatureConfigEventHandler implements IConfigListener +{ + public static WorldCurvatureConfigEventHandler INSTANCE = new WorldCurvatureConfigEventHandler(); + + private static final int MIN_VALID_CURVE_VALUE = 50; + + + /** private since we only ever need one handler at a time */ + private WorldCurvatureConfigEventHandler() { } + + + + @Override + public void onConfigValueSet() + { + int curveRatio = Config.Client.Advanced.Graphics.AdvancedGraphics.earthCurveRatio.get(); + if (curveRatio > 0 && curveRatio < MIN_VALID_CURVE_VALUE) + { + // shouldn't update the UI, otherwise we may end up fighting the user + Config.Client.Advanced.Graphics.AdvancedGraphics.earthCurveRatio.set(MIN_VALID_CURVE_VALUE); + } + + } + + @Override + public void onUiModify() { /* do nothing, we only care about modified config values */ } + + +} diff --git a/core/src/main/java/com/seibel/distanthorizons/core/config/eventHandlers/presets/AbstractPresetConfigEventHandler.java b/core/src/main/java/com/seibel/distanthorizons/core/config/eventHandlers/presets/AbstractPresetConfigEventHandler.java index a524a3e59..8e3681f34 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/config/eventHandlers/presets/AbstractPresetConfigEventHandler.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/config/eventHandlers/presets/AbstractPresetConfigEventHandler.java @@ -1,10 +1,8 @@ package com.seibel.distanthorizons.core.config.eventHandlers.presets; -import com.seibel.distanthorizons.core.api.internal.SharedApi; import com.seibel.distanthorizons.core.config.ConfigEntryWithPresetOptions; import com.seibel.distanthorizons.core.config.listeners.IConfigListener; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; -import com.seibel.distanthorizons.core.world.EWorldEnvironment; import com.seibel.distanthorizons.core.wrapperInterfaces.config.IConfigGui; import com.seibel.distanthorizons.coreapi.interfaces.config.IConfigEntry; import com.seibel.distanthorizons.coreapi.util.StringUtil; diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/accessor/ChunkSizedFullDataAccessor.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/accessor/ChunkSizedFullDataAccessor.java index faf82939b..51da8db2d 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/accessor/ChunkSizedFullDataAccessor.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/accessor/ChunkSizedFullDataAccessor.java @@ -1,11 +1,21 @@ package com.seibel.distanthorizons.core.dataObjects.fullData.accessor; +import com.seibel.distanthorizons.core.dataObjects.fullData.FullDataPointIdMap; +import com.seibel.distanthorizons.core.dataObjects.fullData.sources.interfaces.IFullDataSource; +import com.seibel.distanthorizons.core.dataObjects.fullData.sources.interfaces.IStreamableFullDataSource; +import com.seibel.distanthorizons.core.level.IDhLevel; +import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.pos.DhChunkPos; import com.seibel.distanthorizons.core.pos.DhLodPos; -import com.seibel.distanthorizons.core.dataObjects.fullData.FullDataPointIdMap; import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.util.FullDataPointUtil; import com.seibel.distanthorizons.core.util.LodUtil; +import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataInputStream; +import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataOutputStream; +import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; +import org.apache.logging.log4j.Logger; + +import java.io.IOException; /** * A more specific version of {@link FullDataArrayAccessor} @@ -15,6 +25,8 @@ import com.seibel.distanthorizons.core.util.LodUtil; */ public class ChunkSizedFullDataAccessor extends FullDataArrayAccessor { + private static final Logger LOGGER = DhLoggerBuilder.getLogger(); + public final DhChunkPos pos; // TODO replace this var with LodUtil.BLOCK_DETAIL_LEVEL public final byte detailLevel = LodUtil.BLOCK_DETAIL_LEVEL; @@ -31,6 +43,163 @@ public class ChunkSizedFullDataAccessor extends FullDataArrayAccessor } + //=================// + // stream handling // + //=================// + + + public void writeSourceSummaryInfo(IDhLevel level, DhDataOutputStream outputStream) throws IOException + { + outputStream.writeInt(level.getMinY()); + } + + public void readSourceSummaryInfo(DhDataInputStream inputStream, IDhLevel level) throws IOException + { + int minY = inputStream.readInt(); + if (minY != level.getMinY()) + { + LOGGER.warn("Data minY mismatch: " + minY + " != " + level.getMinY() + ". Will ignore data's y level"); + } + } + + public boolean writeDataPoints(DhDataOutputStream outputStream) throws IOException + { + outputStream.writeInt(IFullDataSource.DATA_GUARD_BYTE); + + + + // Data array length + for (int x = 0; x < this.width; x++) + { + for (int z = 0; z < this.width; z++) + { + outputStream.writeInt(this.get(x, z).getSingleLength()); + } + } + + + + // Data array content (only on non-empty columns) + outputStream.writeInt(IFullDataSource.DATA_GUARD_BYTE); + for (int x = 0; x < this.width; x++) + { + for (int z = 0; z < this.width; z++) + { + SingleColumnFullDataAccessor columnAccessor = this.get(x, z); + if (columnAccessor.doesColumnExist()) + { + long[] dataPointArray = columnAccessor.getRaw(); + for (long dataPoint : dataPointArray) + { + outputStream.writeLong(dataPoint); + } + } + } + } + + + return true; + } + public long[][] readDataPoints(DhDataInputStream dataInputStream) throws IOException + { + // Data array length + int dataPresentFlag = dataInputStream.readInt(); + if (dataPresentFlag != IFullDataSource.DATA_GUARD_BYTE) + { + throw new IOException("Invalid file format. Data Points guard byte expected: (no data) [" + IFullDataSource.NO_DATA_FLAG_BYTE + "] or (data present) [" + IFullDataSource.DATA_GUARD_BYTE + "], but found [" + dataPresentFlag + "]."); + } + + + + long[][] dataPointArray = new long[width * width][]; + for (int x = 0; x < width; x++) + { + for (int z = 0; z < width; z++) + { + dataPointArray[x * width + z] = new long[dataInputStream.readInt()]; + } + } + + + + // check if the array start flag is present + int arrayStartFlag = dataInputStream.readInt(); + if (arrayStartFlag != IFullDataSource.DATA_GUARD_BYTE) + { + throw new IOException("invalid data length end guard"); + } + + for (int xz = 0; xz < dataPointArray.length; xz++) // x and z are combined + { + if (dataPointArray[xz].length != 0) + { + for (int y = 0; y < dataPointArray[xz].length; y++) + { + dataPointArray[xz][y] = dataInputStream.readLong(); + } + } + } + + + + return dataPointArray; + } + public void setDataPoints(long[][] dataPoints) + { + LodUtil.assertTrue(this.dataArrays.length == dataPoints.length, "Data point array length mismatch."); + + System.arraycopy(dataPoints, 0, this.dataArrays, 0, dataPoints.length); + } + + + public void writeIdMappings(DhDataOutputStream outputStream, ILevelWrapper levelWrapper) throws IOException + { + outputStream.writeInt(IFullDataSource.DATA_GUARD_BYTE); + this.mapping.serialize(outputStream, levelWrapper); + } + public FullDataPointIdMap readIdMappings(DhDataInputStream inputStream, ILevelWrapper levelWrapper) throws IOException, InterruptedException + { + int guardByte = inputStream.readInt(); + if (guardByte != IFullDataSource.DATA_GUARD_BYTE) + { + throw new IOException("Invalid data content end guard for ID mapping"); + } + + return FullDataPointIdMap.deserialize(inputStream, null, levelWrapper); + } + public void setIdMapping(FullDataPointIdMap mappings) { this.mapping.mergeAndReturnRemappedEntityIds(mappings); } + + + public void populateFromStream(DhDataInputStream inputStream, IDhLevel level) throws IOException, InterruptedException + { + this.readSourceSummaryInfo(inputStream, level); + + long[][] dataPoints = this.readDataPoints(inputStream); + if (dataPoints == null) + { + return; + } + this.setDataPoints(dataPoints); + + + FullDataPointIdMap mapping = this.readIdMappings(inputStream, level.getLevelWrapper()); + this.setIdMapping(mapping); + + } + + public void writeToStream(DhDataOutputStream outputStream, IDhLevel level) throws IOException + { + this.writeSourceSummaryInfo(level, outputStream); + + boolean hasData = this.writeDataPoints(outputStream); + if (!hasData) + { + return; + } + + this.writeIdMappings(outputStream, level.getLevelWrapper()); + } + public void setSingleColumn(long[] data, int xRelative, int zRelative) { this.dataArrays[xRelative * LodUtil.CHUNK_WIDTH + zRelative] = data; } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/sources/CompleteFullDataSource.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/sources/CompleteFullDataSource.java index 14fe953bb..99558e014 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/sources/CompleteFullDataSource.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/sources/CompleteFullDataSource.java @@ -10,6 +10,7 @@ import com.seibel.distanthorizons.core.file.fullDatafile.FullDataMetaFile; import com.seibel.distanthorizons.core.level.IDhLevel; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.pos.DhBlockPos2D; +import com.seibel.distanthorizons.core.pos.DhChunkPos; import com.seibel.distanthorizons.core.pos.DhLodPos; import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.util.FullDataPointUtil; @@ -23,6 +24,7 @@ import org.apache.logging.log4j.Logger; import javax.annotation.CheckForNull; import java.io.*; +import java.util.function.Consumer; /** * This data source contains every datapoint over its given {@link DhSectionPos}. @@ -368,6 +370,27 @@ public class CompleteFullDataSource extends FullDataArrayAccessor implements IFu } } + public void splitIntoChunkSizedAccessors(Consumer consumer) + { + LodUtil.assertTrue(sectionPos.sectionDetailLevel == DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL, "Data source detail level must be at block detail level."); + + sectionPos.forEachChildAtLevel(LodUtil.CHUNK_DETAIL_LEVEL, childPos -> { + ChunkSizedFullDataAccessor accessor = new ChunkSizedFullDataAccessor(new DhChunkPos(childPos.sectionX, childPos.sectionZ)); + + int detailLevelDifference = sectionPos.sectionDetailLevel - childPos.sectionDetailLevel; + int childRelativeX = childPos.sectionX - sectionPos.sectionX * BitShiftUtil.powerOfTwo(detailLevelDifference); + int childRelativeZ = childPos.sectionZ - sectionPos.sectionZ * BitShiftUtil.powerOfTwo(detailLevelDifference); + + subView( + LodUtil.CHUNK_WIDTH, + childRelativeX * LodUtil.CHUNK_WIDTH, + childRelativeZ * LodUtil.CHUNK_WIDTH + ).shadowCopyTo(accessor); + + consumer.accept(accessor); + }); + } + //=====================// diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/ChunkToLodBuilder.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/ChunkToLodBuilder.java index e49587a3d..aec487e74 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/ChunkToLodBuilder.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/ChunkToLodBuilder.java @@ -82,8 +82,6 @@ public class ChunkToLodBuilder implements AutoCloseable } else if (MC != null && !MC.playerExists()) { - // TODO handle server side properly - // MC hasn't finished loading (or is currently unloaded) // can be uncommented if tasks aren't being cleared correctly diff --git a/core/src/main/java/com/seibel/distanthorizons/core/file/metaData/AbstractMetaDataContainerFile.java b/core/src/main/java/com/seibel/distanthorizons/core/file/metaData/AbstractMetaDataContainerFile.java index 1248de29d..bf50944cf 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/file/metaData/AbstractMetaDataContainerFile.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/file/metaData/AbstractMetaDataContainerFile.java @@ -215,16 +215,19 @@ public abstract class AbstractMetaDataContainerFile { fileChannel.position(METADATA_SIZE_IN_BYTES); - try (DhDataOutputStream compressedOut = new DhDataOutputStream(Channels.newOutputStream(fileChannel)); - CheckedOutputStream checkedOut = new CheckedOutputStream(compressedOut, new Adler32())) // TODO: Is Adler32 ok? - { - dataWriterFunc.writeBufferToFile(compressedOut); - this.baseMetaData.checksum = (int) checkedOut.getChecksum().getValue(); - } + // the order of these streams is important, otherwise the checksum won't be calculated + CheckedOutputStream checkedOut = new CheckedOutputStream(Channels.newOutputStream(fileChannel), new Adler32()); + // normally a DhStream should be the topmost stream to prevent closing the stream accidentally, but since this stream will be closed immediately after writing anyway, it won't be an issue + DhDataOutputStream compressedOut = new DhDataOutputStream(checkedOut); + + // write the contained data + dataWriterFunc.writeBufferToFile(compressedOut); + compressedOut.flush(); + this.baseMetaData.checksum = (int) checkedOut.getChecksum().getValue(); - fileChannel.position(0); // Write metadata + fileChannel.position(0); ByteBuffer buffer = ByteBuffer.allocate(METADATA_SIZE_IN_BYTES); buffer.putInt(METADATA_IDENTITY_BYTES); buffer.putInt(this.pos.sectionX); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/generation/IWorldGenerationQueue.java b/core/src/main/java/com/seibel/distanthorizons/core/generation/IWorldGenerationQueue.java index f3d2da864..e1aa2e376 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/generation/IWorldGenerationQueue.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/generation/IWorldGenerationQueue.java @@ -18,6 +18,9 @@ public interface IWorldGenerationQueue extends Closeable void runCurrentGenTasksUntilBusy(DhBlockPos2D targetPos); + int getWaitingTaskCount(); + int getInProgressTaskCount(); + CompletableFuture startClosing(boolean cancelCurrentGeneration, boolean alsoInterruptRunning); void close(); } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldGenerationQueue.java b/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldGenerationQueue.java index 78c8da695..53260b68e 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldGenerationQueue.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldGenerationQueue.java @@ -18,6 +18,7 @@ import com.seibel.distanthorizons.core.render.renderer.DebugRenderer; import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable; import com.seibel.distanthorizons.core.util.ThreadUtil; import com.seibel.distanthorizons.core.util.objects.DhThreadFactory; +import com.seibel.distanthorizons.core.util.objects.RateLimitedThreadPoolExecutor; import com.seibel.distanthorizons.core.util.objects.UncheckedInterruptedException; import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory; @@ -34,7 +35,7 @@ public class WorldGenerationQueue implements IWorldGenerationQueue, IDebugRender { private static final Logger LOGGER = DhLoggerBuilder.getLogger(); - public static final DhThreadFactory THREAD_FACTORY = new DhThreadFactory(ThreadUtil.THREAD_NAME_PREFIX + "Gen-Worker-Thread", Thread.MIN_PRIORITY); + public static final DhThreadFactory THREAD_FACTORY = new DhThreadFactory(ThreadUtil.THREAD_NAME_PREFIX + "World-Gen-Worker-Thread", Thread.MIN_PRIORITY); private final IDhApiWorldGenerator generator; @@ -74,7 +75,7 @@ public class WorldGenerationQueue implements IWorldGenerationQueue, IDebugRender private final HashMap alreadyGeneratedPosHashSet = new HashMap<>(MAX_ALREADY_GENERATED_COUNT); private final Queue alreadyGeneratedPosQueue = new LinkedList<>(); - private static ExecutorService worldGeneratorThreadPool; + private static RateLimitedThreadPoolExecutor worldGeneratorThreadPool; private static ConfigChangeListener configListener; @@ -533,6 +534,7 @@ public class WorldGenerationQueue implements IWorldGenerationQueue, IDebugRender } worldGeneratorThreadPool = ThreadUtil.makeRateLimitedThreadPool(threadPoolSize, THREAD_FACTORY, Config.Client.Advanced.MultiThreading.runTimeRatioForWorldGenerationThreads); + worldGeneratorThreadPool.setOnTerminatedEventHandler(WorldGenerationQueue::onWorldGenThreadPoolTerminated); } /** @@ -548,6 +550,20 @@ public class WorldGenerationQueue implements IWorldGenerationQueue, IDebugRender } } + private static void onWorldGenThreadPoolTerminated() + { + LOGGER.debug("World generator thread pool terminated. Suggesting the JVM runs a garbage collection to clean up any loose world generation objects..."); + System.gc(); + } + + + + //=========// + // getters // + //=========// + + public int getWaitingTaskCount() { return this.waitingTasks.size(); } + public int getInProgressTaskCount() { return this.inProgressGenTasksByLodPos.size(); } //==========// @@ -681,6 +697,11 @@ public class WorldGenerationQueue implements IWorldGenerationQueue, IDebugRender } + + //=======// + // debug // + //=======// + @Override public void debugRender(DebugRenderer r) { diff --git a/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldRemoteGenerationQueue.java b/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldRemoteGenerationQueue.java index 9ab3eab32..91410b3a4 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldRemoteGenerationQueue.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldRemoteGenerationQueue.java @@ -44,7 +44,6 @@ public class WorldRemoteGenerationQueue implements IWorldGenerationQueue, IDebug private final ConcurrentMap waitingTasks = new ConcurrentHashMap<>(); private final Semaphore pendingTasksSemaphore = new Semaphore(Short.MAX_VALUE, true); - private int pendingTasks() { return Short.MAX_VALUE - pendingTasksSemaphore.availablePermits(); } private CompletableFuture genTaskPriorityRequest = CompletableFuture.completedFuture(null); private final Semaphore genTaskPriorityRequestSemaphore = new Semaphore(1, true); @@ -87,8 +86,8 @@ public class WorldRemoteGenerationQueue implements IWorldGenerationQueue, IDebug { if (generatorClosingFuture != null || !networkState.getClient().isReady()) return; - while (waitingTasks.size() > pendingTasks() - && pendingTasks() < this.networkState.config.fullDataRequestRateLimit + while (getWaitingTaskCount() > getInProgressTaskCount() + && getInProgressTaskCount() < this.networkState.config.fullDataRequestRateLimit && pendingTasksSemaphore.tryAcquire()) { sendNewRequest(targetPos); @@ -195,21 +194,7 @@ public class WorldRemoteGenerationQueue implements IWorldGenerationQueue, IDebug if (chunkDataConsumer == null) return entry.future.cancel(false); - sectionPos.forEachChildAtLevel(LodUtil.CHUNK_DETAIL_LEVEL, childPos -> { - ChunkSizedFullDataAccessor accessor = new ChunkSizedFullDataAccessor(new DhChunkPos(childPos.sectionX, childPos.sectionZ)); - - int detailLevelDifference = sectionPos.sectionDetailLevel - childPos.sectionDetailLevel; - int childRelativeX = childPos.sectionX - sectionPos.sectionX * BitShiftUtil.powerOfTwo(detailLevelDifference); - int childRelativeZ = childPos.sectionZ - sectionPos.sectionZ * BitShiftUtil.powerOfTwo(detailLevelDifference); - - fullDataSource.subView( - LodUtil.CHUNK_WIDTH, - childRelativeX * LodUtil.CHUNK_WIDTH, - childRelativeZ * LodUtil.CHUNK_WIDTH - ).shadowCopyTo(accessor); - - chunkDataConsumer.accept(accessor); - }); + fullDataSource.splitIntoChunkSizedAccessors(chunkDataConsumer); } catch (ChannelException | RateLimitedException e) { @@ -238,18 +223,23 @@ public class WorldRemoteGenerationQueue implements IWorldGenerationQueue, IDebug { ArrayList lines = new ArrayList<>(); lines.add("World Remote Generation Queue ["+level.getClientLevelWrapper().getDimensionType().getDimensionName()+"]"); - lines.add(" Requests: "+this.finishedRequests+" / "+(this.waitingTasks.size() + this.finishedRequests.get())+" (failed: "+ this.failedRequests+")"); - lines.add(" Pending: "+this.pendingTasks()+" / "+this.networkState.config.fullDataRequestRateLimit); + lines.add("Requests: "+this.finishedRequests+" / "+(this.getWaitingTaskCount() + this.finishedRequests.get())+" (failed: "+ this.failedRequests+", rate limit: "+this.networkState.config.fullDataRequestRateLimit+")"); return lines.toArray(new String[0]); } + @Override + public int getWaitingTaskCount() { return this.waitingTasks.size(); } + @Override + public int getInProgressTaskCount() { return Short.MAX_VALUE - pendingTasksSemaphore.availablePermits(); } + @Override public CompletableFuture startClosing(boolean cancelCurrentGeneration, boolean alsoInterruptRunning) { return this.generatorClosingFuture = CompletableFuture.runAsync(() -> { while (!genTaskPriorityRequestSemaphore.tryAcquire()) { - genTaskPriorityRequest.cancel(false); + if (genTaskPriorityRequest.cancel(false)) + genTaskPriorityRequestSemaphore.release(); } while (!pendingTasksSemaphore.tryAcquire(Short.MAX_VALUE)) @@ -257,8 +247,8 @@ public class WorldRemoteGenerationQueue implements IWorldGenerationQueue, IDebug for (WorldGenQueueEntry entry : this.waitingTasks.values()) { entry.future.cancel(alsoInterruptRunning); - if (entry.request != null) - entry.request.cancel(alsoInterruptRunning); + if (entry.request != null && entry.request.cancel(alsoInterruptRunning)) + pendingTasksSemaphore.release(); } } }); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/level/ClientLevelModule.java b/core/src/main/java/com/seibel/distanthorizons/core/level/ClientLevelModule.java index 1d4dc68f8..34ab6ae82 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/level/ClientLevelModule.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/level/ClientLevelModule.java @@ -1,17 +1,14 @@ package com.seibel.distanthorizons.core.level; import com.seibel.distanthorizons.api.enums.rendering.EDebugRendering; -import com.seibel.distanthorizons.core.config.AppliedConfigState; import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.dataObjects.fullData.accessor.ChunkSizedFullDataAccessor; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.file.fullDatafile.IFullDataSourceProvider; import com.seibel.distanthorizons.core.file.renderfile.RenderSourceFileHandler; import com.seibel.distanthorizons.core.file.structure.AbstractSaveStructure; -import com.seibel.distanthorizons.core.generation.WorldRemoteGenerationQueue; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.f3.F3Screen; -import com.seibel.distanthorizons.core.network.NetworkClient; import com.seibel.distanthorizons.core.pos.DhBlockPos2D; import com.seibel.distanthorizons.core.pos.DhLodPos; import com.seibel.distanthorizons.core.pos.DhSectionPos; @@ -22,31 +19,31 @@ import com.seibel.distanthorizons.core.render.renderer.LodRenderer; import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper; -import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; import com.seibel.distanthorizons.coreapi.util.math.Mat4f; import org.apache.logging.log4j.Logger; +import java.io.Closeable; import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicReference; -public class ClientLevelModule +public class ClientLevelModule implements Closeable { private static final Logger LOGGER = DhLoggerBuilder.getLogger(); private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class); private final IDhClientLevel parent; public final AtomicReference ClientRenderStateRef = new AtomicReference<>(); public final F3Screen.NestedMessage f3Message; - public ClientLevelModule(IDhClientLevel parent) { - this.parent = parent; this.f3Message = new F3Screen.NestedMessage(this::f3Log); + this.parent = parent; + this.f3Message = new F3Screen.NestedMessage(this::f3Log); } - + //==============// // tick methods // //==============// - + private EDebugRendering lastDebugRendering = EDebugRendering.OFF; public void clientTick() @@ -56,8 +53,9 @@ public class ClientLevelModule { return; } - - ClientRenderState clientRenderState = this.ClientRenderStateRef.get(); if (clientRenderState == null) + + ClientRenderState clientRenderState = this.ClientRenderStateRef.get(); + if (clientRenderState == null) { return; } @@ -70,40 +68,50 @@ public class ClientLevelModule return; } - clientRenderState.close(); clientRenderState = new ClientRenderState(parent, parent.getFileHandler(), parent.getSaveStructure()); if (!this.ClientRenderStateRef.compareAndSet(null, clientRenderState)) - { - //FIXME: How to handle this? - LOGGER.warn("Failed to set render state due to concurrency after changing view distance"); clientRenderState.close(); return; + clientRenderState.close(); + clientRenderState = new ClientRenderState(parent, parent.getFileHandler(), parent.getSaveStructure()); + if (!this.ClientRenderStateRef.compareAndSet(null, clientRenderState)) + { + //FIXME: How to handle this? + LOGGER.warn("Failed to set render state due to concurrency after changing view distance"); + clientRenderState.close(); + return; + } } - } clientRenderState.quadtree.tick(new DhBlockPos2D(MC_CLIENT.getPlayerBlockPos())); - - boolean isBuffersDirty = false; EDebugRendering newDebugRendering = Config.Client.Advanced.Debugging.debugRendering.get(); + clientRenderState.quadtree.tick(new DhBlockPos2D(MC_CLIENT.getPlayerBlockPos())); + + boolean isBuffersDirty = false; + EDebugRendering newDebugRendering = Config.Client.Advanced.Debugging.debugRendering.get(); if (newDebugRendering != lastDebugRendering) { - lastDebugRendering = newDebugRendering; isBuffersDirty = true; + lastDebugRendering = newDebugRendering; + isBuffersDirty = true; } if (isBuffersDirty) { clientRenderState.renderer.bufferHandler.MarkAllBuffersDirty(); } } - - + + //========// // render // //========// - + /** @return if the {@link ClientRenderState} was successfully swapped */ public boolean startRenderer() { - ClientRenderState ClientRenderState = new ClientRenderState(parent, parent.getFileHandler(), parent.getSaveStructure()); if (!this.ClientRenderStateRef.compareAndSet(null, ClientRenderState)) - { - LOGGER.warn("Failed to start renderer due to concurrency"); ClientRenderState.close(); return false; - } - else - { - return true; - } + ClientRenderState ClientRenderState = new ClientRenderState(parent, parent.getFileHandler(), parent.getSaveStructure()); + if (!this.ClientRenderStateRef.compareAndSet(null, ClientRenderState)) + { + LOGGER.warn("Failed to start renderer due to concurrency"); + ClientRenderState.close(); + return false; + } + else + { + return true; + } } public boolean isRendering() @@ -113,27 +121,34 @@ public class ClientLevelModule public void render(Mat4f mcModelViewMatrix, Mat4f mcProjectionMatrix, float partialTicks, IProfilerWrapper profiler) { - ClientRenderState ClientRenderState = this.ClientRenderStateRef.get(); if (ClientRenderState == null) - { - // either the renderer hasn't been started yet, or is being reloaded - return; - } ClientRenderState.renderer.drawLODs(mcModelViewMatrix, mcProjectionMatrix, partialTicks, profiler); + ClientRenderState ClientRenderState = this.ClientRenderStateRef.get(); + if (ClientRenderState == null) + { + // either the renderer hasn't been started yet, or is being reloaded + return; + } + ClientRenderState.renderer.drawLODs(mcModelViewMatrix, mcProjectionMatrix, partialTicks, profiler); } public void stopRenderer() { - LOGGER.info("Stopping renderer for " + this); ClientRenderState ClientRenderState = this.ClientRenderStateRef.get(); if (ClientRenderState == null) - { - LOGGER.warn("Tried to stop renderer for " + this + " when it was not started!"); return; - } + LOGGER.info("Stopping renderer for " + this); + ClientRenderState ClientRenderState = this.ClientRenderStateRef.get(); + if (ClientRenderState == null) + { + LOGGER.warn("Tried to stop renderer for " + this + " when it was not started!"); + return; + } // stop the render state while (!this.ClientRenderStateRef.compareAndSet(ClientRenderState, null)) // TODO why is there a while loop here? { - ClientRenderState = this.ClientRenderStateRef.get(); if (ClientRenderState == null) - { - return; + ClientRenderState = this.ClientRenderStateRef.get(); + if (ClientRenderState == null) + { + return; + } } - } ClientRenderState.close(); + ClientRenderState.close(); } //===============// @@ -141,47 +156,54 @@ public class ClientLevelModule //===============// public void saveWrites(ChunkSizedFullDataAccessor data) { - ClientRenderState ClientRenderState = this.ClientRenderStateRef.get(); DhLodPos pos = data.getLodPos().convertToDetailLevel(CompleteFullDataSource.SECTION_SIZE_OFFSET); if (ClientRenderState != null) - { - ClientRenderState.renderSourceFileHandler.writeChunkDataToFile(new DhSectionPos(pos.detailLevel, pos.x, pos.z), data); - } - else - { - parent.getFileHandler().write(new DhSectionPos(pos.detailLevel, pos.x, pos.z), data); - } + ClientRenderState ClientRenderState = this.ClientRenderStateRef.get(); + DhLodPos pos = data.getLodPos().convertToDetailLevel(CompleteFullDataSource.SECTION_SIZE_OFFSET); + if (ClientRenderState != null) + { + ClientRenderState.renderSourceFileHandler.writeChunkDataToFile(new DhSectionPos(pos.detailLevel, pos.x, pos.z), data); + } + else + { + parent.getFileHandler().write(new DhSectionPos(pos.detailLevel, pos.x, pos.z), data); + } } public CompletableFuture saveAsync() { - ClientRenderState ClientRenderState = this.ClientRenderStateRef.get(); if (ClientRenderState != null) - { - return ClientRenderState.renderSourceFileHandler.flushAndSaveAsync(); - } - else - { - return CompletableFuture.completedFuture(null); - } + ClientRenderState ClientRenderState = this.ClientRenderStateRef.get(); + if (ClientRenderState != null) + { + return ClientRenderState.renderSourceFileHandler.flushAndSaveAsync(); + } + else + { + return CompletableFuture.completedFuture(null); + } } public void close() { // shutdown the renderer - ClientRenderState ClientRenderState = this.ClientRenderStateRef.get(); if (ClientRenderState != null) - { - // TODO does this have to be in a while loop, if so why? - while (!this.ClientRenderStateRef.compareAndSet(ClientRenderState, null)) - { - ClientRenderState = this.ClientRenderStateRef.get(); if (ClientRenderState == null) - { - break; - } - } - + ClientRenderState ClientRenderState = this.ClientRenderStateRef.get(); if (ClientRenderState != null) { - ClientRenderState.close(); + // TODO does this have to be in a while loop, if so why? + while (!this.ClientRenderStateRef.compareAndSet(ClientRenderState, null)) + { + ClientRenderState = this.ClientRenderStateRef.get(); + if (ClientRenderState == null) + { + break; + } + } + + if (ClientRenderState != null) + { + ClientRenderState.close(); + } } - } f3Message.close(); + + this.f3Message.close(); } @@ -199,30 +221,34 @@ public class ClientLevelModule /** Returns what should be displayed in Minecraft's F3 debug menu */ protected String[] f3Log() { - String dimName = parent.getClientLevelWrapper().getDimensionType().getDimensionName(); ClientRenderState renderState = this.ClientRenderStateRef.get(); if (renderState == null) - { - return new String[]{"level @ " + dimName + ": Inactive"}; - } - else - { - return new String[]{"level @ " + dimName + ": Active"}; - } + String dimName = parent.getClientLevelWrapper().getDimensionType().getDimensionName(); + ClientRenderState renderState = this.ClientRenderStateRef.get(); + if (renderState == null) + { + return new String[]{"level @ " + dimName + ": Inactive"}; + } + else + { + return new String[]{"level @ " + dimName + ": Active"}; + } } public void clearRenderCache() { - ClientRenderState ClientRenderState = this.ClientRenderStateRef.get(); if (ClientRenderState != null && ClientRenderState.quadtree != null) - { - ClientRenderState.quadtree.clearRenderDataCache(); - } + ClientRenderState ClientRenderState = this.ClientRenderStateRef.get(); + if (ClientRenderState != null && ClientRenderState.quadtree != null) + { + ClientRenderState.quadtree.clearRenderDataCache(); + } } public void reloadPos(DhSectionPos pos) { - ClientRenderState clientRenderState = this.ClientRenderStateRef.get(); if (clientRenderState != null && clientRenderState.quadtree != null) - { - clientRenderState.quadtree.reloadPos(pos); - } + ClientRenderState clientRenderState = this.ClientRenderStateRef.get(); + if (clientRenderState != null && clientRenderState.quadtree != null) + { + clientRenderState.quadtree.reloadPos(pos); + } } public static class ClientRenderState @@ -236,15 +262,19 @@ public class ClientLevelModule public final LodRenderer renderer; public ClientRenderState( - IDhClientLevel dhClientLevel, IFullDataSourceProvider fullDataSourceProvider, AbstractSaveStructure saveStructure) + IDhClientLevel dhClientLevel, IFullDataSourceProvider fullDataSourceProvider, + AbstractSaveStructure saveStructure) { - this.levelWrapper = dhClientLevel.getLevelWrapper(); this.renderSourceFileHandler = new RenderSourceFileHandler(fullDataSourceProvider, dhClientLevel, saveStructure); + this.levelWrapper = dhClientLevel.getLevelWrapper(); + this.renderSourceFileHandler = new RenderSourceFileHandler(fullDataSourceProvider, dhClientLevel, saveStructure); this.quadtree = new LodQuadTree(dhClientLevel, Config.Client.Advanced.Graphics.Quality.lodChunkRenderDistance.get() * LodUtil.CHUNK_WIDTH, // initial position is (0,0) just in case the player hasn't loaded in yet, the tree will be moved once the level starts ticking - 0, 0, this.renderSourceFileHandler); + 0, 0, + this.renderSourceFileHandler); - RenderBufferHandler renderBufferHandler = new RenderBufferHandler(this.quadtree); this.renderer = new LodRenderer(renderBufferHandler); + RenderBufferHandler renderBufferHandler = new RenderBufferHandler(this.quadtree); + this.renderer = new LodRenderer(renderBufferHandler); } @@ -253,7 +283,9 @@ public class ClientLevelModule { LOGGER.info("Shutting down " + ClientRenderState.class.getSimpleName()); - this.renderer.close(); this.quadtree.close(); this.renderSourceFileHandler.close(); + this.renderer.close(); + this.quadtree.close(); + this.renderSourceFileHandler.close(); } } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientLevel.java b/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientLevel.java index 1438f7108..bea8afdcf 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientLevel.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientLevel.java @@ -1,6 +1,7 @@ package com.seibel.distanthorizons.core.level; import com.seibel.distanthorizons.core.dataObjects.fullData.accessor.ChunkSizedFullDataAccessor; +import com.seibel.distanthorizons.core.dataObjects.fullData.sources.CompleteFullDataSource; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.file.fullDatafile.IFullDataSourceProvider; import com.seibel.distanthorizons.core.file.fullDatafile.RemoteFullDataFileHandler; @@ -8,6 +9,9 @@ import com.seibel.distanthorizons.core.file.structure.AbstractSaveStructure; import com.seibel.distanthorizons.core.generation.WorldRemoteGenerationQueue; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.multiplayer.ClientNetworkState; +import com.seibel.distanthorizons.core.network.NetworkClient; +import com.seibel.distanthorizons.core.network.ScopedNetworkEventSource; +import com.seibel.distanthorizons.core.network.messages.FullDataPartialUpdateMessage; import com.seibel.distanthorizons.core.pos.DhBlockPos; import com.seibel.distanthorizons.core.pos.DhBlockPos2D; import com.seibel.distanthorizons.core.pos.DhSectionPos; @@ -47,6 +51,8 @@ public class DhClientLevel extends DhLevel implements IDhClientLevel @CheckForNull private final ClientNetworkState networkState; + @Nullable + private final ScopedNetworkEventSource eventSource; public final WorldGenModule worldGenModule; @@ -63,12 +69,41 @@ public class DhClientLevel extends DhLevel implements IDhClientLevel this.networkState = networkState; this.worldGenModule = new WorldGenModule(dataFileHandler, this); + if (networkState != null) + { + this.eventSource = new ScopedNetworkEventSource<>(networkState.getClient()); + this.registerNetworkHandlers(); + } + else + { + this.eventSource = null; + } clientside = new ClientLevelModule(this); clientside.startRenderer(); LOGGER.info("Started DHLevel for " + this.levelWrapper + " with saves at " + this.saveStructure); } + private void registerNetworkHandlers() + { + assert this.eventSource != null; + + this.eventSource.registerHandler(FullDataPartialUpdateMessage.class, msg -> + { + try + { + ChunkSizedFullDataAccessor fullDataAccessor = msg.getFullDataSource(this); + if (fullDataAccessor == null) return; + + this.saveWrites(fullDataAccessor); + } + catch (Exception e) + { + LOGGER.error("Error while updating full data source", e); + } + }); + } + //==============// // tick methods // //==============// diff --git a/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientServerLevel.java b/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientServerLevel.java index d9a954455..acc6ce14a 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientServerLevel.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientServerLevel.java @@ -32,7 +32,8 @@ public class DhClientServerLevel extends DhLevel implements IDhClientLevel, IDhS public final ClientLevelModule clientside; private final IServerLevelWrapper serverLevelWrapper; - public IClientLevelWrapper clientLevelWrapper; + + public DhClientServerLevel(AbstractSaveStructure saveStructure, IServerLevelWrapper serverLevelWrapper) { @@ -41,11 +42,13 @@ public class DhClientServerLevel extends DhLevel implements IDhClientLevel, IDhS LOGGER.warn("unable to create data folder."); } this.serverLevelWrapper = serverLevelWrapper; - serverside = new ServerLevelModule(this, serverLevelWrapper, saveStructure); - clientside = new ClientLevelModule(this); + this.serverside = new ServerLevelModule(this, serverLevelWrapper, saveStructure); + this.clientside = new ClientLevelModule(this); LOGGER.info("Started " + DhClientServerLevel.class.getSimpleName() + " for " + serverLevelWrapper + " with saves at " + saveStructure); } + + //==============// // tick methods // //==============// @@ -105,11 +108,7 @@ public class DhClientServerLevel extends DhLevel implements IDhClientLevel, IDhS clientside.startRenderer(); } - public void stopRenderer() - { - clientside.stopRenderer(); - clientLevelWrapper = null; - } + public void stopRenderer() { this.clientside.stopRenderer(); } //================// // level handling // diff --git a/core/src/main/java/com/seibel/distanthorizons/core/level/DhLevel.java b/core/src/main/java/com/seibel/distanthorizons/core/level/DhLevel.java index 08fd32fd8..5fb8d2d86 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/level/DhLevel.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/level/DhLevel.java @@ -25,7 +25,7 @@ public abstract class DhLevel implements IDhLevel } @Override - public void updateChunkAsync(IChunkWrapper chunk) + public CompletableFuture updateChunkAsync(IChunkWrapper chunk) { CompletableFuture future = this.chunkToLodBuilder.tryGenerateData(chunk); if (future != null) @@ -44,6 +44,7 @@ public abstract class DhLevel implements IDhLevel new DhApiChunkModifiedEvent.EventParam(this.getLevelWrapper(), chunk.getChunkPos().x, chunk.getChunkPos().z)); }); } + return future; } @Override diff --git a/core/src/main/java/com/seibel/distanthorizons/core/level/DhServerLevel.java b/core/src/main/java/com/seibel/distanthorizons/core/level/DhServerLevel.java index 6cd032a10..ac1934c88 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/level/DhServerLevel.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/level/DhServerLevel.java @@ -15,10 +15,12 @@ import com.seibel.distanthorizons.core.network.NetworkServer; import com.seibel.distanthorizons.core.network.exceptions.RateLimitedException; import com.seibel.distanthorizons.core.network.messages.*; import com.seibel.distanthorizons.core.pos.DhBlockPos2D; +import com.seibel.distanthorizons.core.pos.DhChunkPos; import com.seibel.distanthorizons.core.pos.DhLodPos; import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.util.LodUtil; +import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper; @@ -26,9 +28,7 @@ import com.seibel.distanthorizons.coreapi.util.math.Vec3d; import org.apache.logging.log4j.Logger; import javax.annotation.CheckForNull; -import java.util.Iterator; import java.util.Map; -import java.util.Set; import java.util.concurrent.*; public class DhServerLevel extends DhLevel implements IDhServerLevel @@ -40,7 +40,7 @@ public class DhServerLevel extends DhLevel implements IDhServerLevel private final RemotePlayerConnectionHandler remotePlayerConnectionHandler; private final ScopedNetworkEventSource eventSource; - private final ConcurrentLinkedQueue worldGenLoopingQueue = new ConcurrentLinkedQueue<>(); + private final LinkedBlockingQueue worldGenLoopingQueue = new LinkedBlockingQueue<>(); private final ConcurrentMap incompleteDataSources = new ConcurrentHashMap<>(); private final ConcurrentMap fullDataRequests = new ConcurrentHashMap<>(); private final AppliedConfigState rateLimitConfig = new AppliedConfigState<>(Config.Client.Advanced.Multiplayer.serverNetworkingRateLimit); @@ -66,12 +66,16 @@ public class DhServerLevel extends DhLevel implements IDhServerLevel // TODO implement transparent message handling restriction by level // workaround: // ServerPlayerState serverPlayerState = remotePlayerConnectionHandler.getConnectedPlayer(msg); +// if (serverPlayerState == null) return; +// // if (serverPlayerState.serverPlayer.getLevel() != this.serverLevelWrapper) // return; this.eventSource.registerHandler(FullDataSourceRequestMessage.class, msg -> { ServerPlayerState serverPlayerState = remotePlayerConnectionHandler.getConnectedPlayer(msg); + if (serverPlayerState == null) return; + if (serverPlayerState.serverPlayer.getLevel() != this.serverLevelWrapper) return; @@ -106,6 +110,8 @@ public class DhServerLevel extends DhLevel implements IDhServerLevel this.eventSource.registerHandler(GenTaskPriorityRequestMessage.class, msg -> { ServerPlayerState serverPlayerState = remotePlayerConnectionHandler.getConnectedPlayer(msg); + if (serverPlayerState == null) return; + if (serverPlayerState.serverPlayer.getLevel() != this.serverLevelWrapper) return; @@ -120,7 +126,9 @@ public class DhServerLevel extends DhLevel implements IDhServerLevel if (entry == null) return; FullDataSourceRequestMessage requestMessage = entry.requestMessages.remove(msg.futureId); - remotePlayerConnectionHandler.getConnectedPlayer(msg).pendingFullDataRequests.decrementAndGet(); + ServerPlayerState serverPlayerState = remotePlayerConnectionHandler.getConnectedPlayer(msg); + if (serverPlayerState != null) + serverPlayerState.pendingFullDataRequests.decrementAndGet(); entry.requestCollectionSemaphore.acquireUninterruptibly(Short.MAX_VALUE); if (entry.requestMessages.isEmpty()) @@ -135,18 +143,12 @@ public class DhServerLevel extends DhLevel implements IDhServerLevel public void addPlayer(IServerPlayerWrapper serverPlayer) { - synchronized (worldGenLoopingQueue) - { - this.worldGenLoopingQueue.add(serverPlayer); - } + this.worldGenLoopingQueue.add(serverPlayer); } public void removePlayer(IServerPlayerWrapper serverPlayer) { - synchronized (worldGenLoopingQueue) - { - this.worldGenLoopingQueue.remove(serverPlayer); - } + boolean ignored = this.worldGenLoopingQueue.remove(serverPlayer); } public void serverTick() @@ -189,6 +191,28 @@ public class DhServerLevel extends DhLevel implements IDhServerLevel } } + @Override + public CompletableFuture updateChunkAsync(IChunkWrapper chunk) + { + CompletableFuture future = super.updateChunkAsync(chunk); + if (future == null) + return null; + + future.thenAccept(chunkSizedFullDataAccessor -> + { + for (IServerPlayerWrapper serverPlayer : worldGenLoopingQueue) + { + ServerPlayerState serverPlayerState = remotePlayerConnectionHandler.getPlayer(serverPlayer); + if (serverPlayerState == null) continue; + + if (chunk.getChunkPos().distance(new DhChunkPos(serverPlayer.getPosition())) <= serverPlayerState.config.renderDistance) + serverPlayerState.channelContext.writeAndFlush(new FullDataPartialUpdateMessage(chunkSizedFullDataAccessor, this)); + } + }); + + return future; + } + @Override public void saveWrites(ChunkSizedFullDataAccessor data) { diff --git a/core/src/main/java/com/seibel/distanthorizons/core/level/IDhLevel.java b/core/src/main/java/com/seibel/distanthorizons/core/level/IDhLevel.java index 688e7fe29..69ab18061 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/level/IDhLevel.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/level/IDhLevel.java @@ -1,9 +1,11 @@ package com.seibel.distanthorizons.core.level; +import com.seibel.distanthorizons.core.dataObjects.fullData.accessor.ChunkSizedFullDataAccessor; import com.seibel.distanthorizons.core.file.fullDatafile.IFullDataSourceProvider; import com.seibel.distanthorizons.core.file.structure.AbstractSaveStructure; import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; +import org.jetbrains.annotations.Nullable; import java.util.concurrent.CompletableFuture; @@ -20,7 +22,8 @@ public interface IDhLevel extends AutoCloseable */ ILevelWrapper getLevelWrapper(); - void updateChunkAsync(IChunkWrapper chunk); + @Nullable + CompletableFuture updateChunkAsync(IChunkWrapper chunk); IFullDataSourceProvider getFileHandler(); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/level/WorldGenModule.java b/core/src/main/java/com/seibel/distanthorizons/core/level/WorldGenModule.java index dfe30a940..97b0c9232 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/level/WorldGenModule.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/level/WorldGenModule.java @@ -3,6 +3,7 @@ package com.seibel.distanthorizons.core.level; import com.seibel.distanthorizons.core.file.fullDatafile.GeneratedFullDataFileHandler; import com.seibel.distanthorizons.core.generation.IWorldGenerationQueue; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; +import com.seibel.distanthorizons.core.logging.f3.F3Screen; import com.seibel.distanthorizons.core.pos.DhBlockPos2D; import org.apache.logging.log4j.Logger; @@ -16,7 +17,9 @@ public class WorldGenModule implements Closeable private final GeneratedFullDataFileHandler dataFileHandler; private final GeneratedFullDataFileHandler.IOnWorldGenCompleteListener onWorldGenCompleteListener; + private final AtomicReference worldGenStateRef = new AtomicReference<>(); + private final F3Screen.DynamicMessage worldGenF3Message; public static abstract class WorldGenState { @@ -48,6 +51,22 @@ public class WorldGenModule implements Closeable { this.dataFileHandler = dataFileHandler; this.onWorldGenCompleteListener = onWorldGenCompleteListener; + this.worldGenF3Message = new F3Screen.DynamicMessage(() -> + { + WorldGenState worldGenState = this.worldGenStateRef.get(); + if (worldGenState != null) + { + int waiting = worldGenState.worldGenerationQueue.getWaitingTaskCount(); + int inProgress = worldGenState.worldGenerationQueue.getInProgressTaskCount(); + + return "World Gen Tasks: "+waiting+", (in progress: "+inProgress+")"; + } + else + { + return "World Gen Disabled"; + } + }); + } public void startWorldGen(GeneratedFullDataFileHandler dataFileHandler, WorldGenState newWgs) @@ -121,5 +140,6 @@ public class WorldGenModule implements Closeable } } dataFileHandler.close(); + this.worldGenF3Message.close(); } } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/ClientNetworkState.java b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/ClientNetworkState.java index 41744ecfd..f1d4411b6 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/ClientNetworkState.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/ClientNetworkState.java @@ -2,7 +2,6 @@ package com.seibel.distanthorizons.core.multiplayer; import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; -import com.seibel.distanthorizons.core.network.IClientRequestHandler; import com.seibel.distanthorizons.core.network.ScopedNetworkEventSource; import com.seibel.distanthorizons.core.network.NetworkClient; import com.seibel.distanthorizons.core.network.messages.AckMessage; @@ -10,6 +9,7 @@ import com.seibel.distanthorizons.core.network.messages.HelloMessage; import com.seibel.distanthorizons.core.network.messages.PlayerUUIDMessage; import com.seibel.distanthorizons.core.network.messages.RemotePlayerConfigMessage; import org.apache.logging.log4j.Logger; +import org.jetbrains.annotations.NotNull; import java.io.Closeable; import java.util.UUID; @@ -26,7 +26,7 @@ public class ClientNetworkState implements Closeable * Returns the client used by this instance.

* If you need to subscribe to any packet events, create an instance of {@link ScopedNetworkEventSource} using the returned instance. */ - public IClientRequestHandler getClient() { return this.client; } + public NetworkClient getClient() { return this.client; } /** * Constructs a new instance. diff --git a/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/RemotePlayerConnectionHandler.java b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/RemotePlayerConnectionHandler.java index 8b0d83c79..52048f097 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/RemotePlayerConnectionHandler.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/RemotePlayerConnectionHandler.java @@ -10,6 +10,7 @@ import com.seibel.distanthorizons.core.network.messages.PlayerUUIDMessage; import com.seibel.distanthorizons.core.network.protocol.NetworkMessage; import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper; import io.netty.channel.ChannelHandlerContext; +import org.jetbrains.annotations.Nullable; import java.io.Closeable; import java.util.HashMap; @@ -69,11 +70,13 @@ public class RemotePlayerConnectionHandler implements Closeable return playersByConnection.values(); } + @Nullable public ServerPlayerState getConnectedPlayer(NetworkMessage msg) { return playersByConnection.get(msg.getChannelContext()); } + @Nullable public ServerPlayerState getPlayer(IServerPlayerWrapper serverPlayer) { return playersByUUID.get(serverPlayer.getUUID()); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/IClientRequestHandler.java b/core/src/main/java/com/seibel/distanthorizons/core/network/IClientRequestHandler.java deleted file mode 100644 index a91b03798..000000000 --- a/core/src/main/java/com/seibel/distanthorizons/core/network/IClientRequestHandler.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.seibel.distanthorizons.core.network; - -import com.seibel.distanthorizons.core.network.protocol.FutureTrackableNetworkMessage; - -import java.util.concurrent.CompletableFuture; - -public interface IClientRequestHandler -{ - /** Indicates whether the client is initialized and not started connecting yet. */ - boolean isInitialState(); - /** Indicates whether the client is closed(-ing) and should not be used. */ - boolean isClosed(); - /** Indicates whether the connection is established and first message is sent. */ - boolean isReady(); - - /** Sends a new request. */ - CompletableFuture sendRequest(FutureTrackableNetworkMessage msg); -} - diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/NetworkClient.java b/core/src/main/java/com/seibel/distanthorizons/core/network/NetworkClient.java index 8eb4e5a19..f59d9e751 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/network/NetworkClient.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/NetworkClient.java @@ -19,7 +19,7 @@ import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; -public class NetworkClient extends NetworkEventSource implements IClientRequestHandler, AutoCloseable +public class NetworkClient extends NetworkEventSource implements AutoCloseable { private static final Logger LOGGER = DhLoggerBuilder.getLogger(); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/NetworkEventSource.java b/core/src/main/java/com/seibel/distanthorizons/core/network/NetworkEventSource.java index 0d6daaa23..3e9837f56 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/network/NetworkEventSource.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/NetworkEventSource.java @@ -5,8 +5,10 @@ import com.google.common.collect.Table; import com.google.common.collect.Tables; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.network.messages.CancelMessage; +import com.seibel.distanthorizons.core.network.messages.CloseEvent; import com.seibel.distanthorizons.core.network.messages.ExceptionMessage; import com.seibel.distanthorizons.core.network.protocol.FutureTrackableNetworkMessage; +import com.seibel.distanthorizons.core.network.protocol.MessageRegistry; import com.seibel.distanthorizons.core.network.protocol.NetworkMessage; import io.netty.channel.ChannelException; import io.netty.channel.ChannelHandlerContext; @@ -75,7 +77,13 @@ public abstract class NetworkEventSource public void registerHandler(Class handlerClass, Consumer handlerImplementation) { - this.handlers.computeIfAbsent(handlerClass, missingHandlerClass -> new HashSet<>()) + this.handlers.computeIfAbsent(handlerClass, missingHandlerClass -> + { + // Will throw if the handler class is not found + if (handlerClass != CloseEvent.class) + MessageRegistry.INSTANCE.getMessageId(handlerClass); + return new HashSet<>(); + }) .add((Consumer) handlerImplementation); } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/messages/FullDataPartialUpdateMessage.java b/core/src/main/java/com/seibel/distanthorizons/core/network/messages/FullDataPartialUpdateMessage.java new file mode 100644 index 000000000..03d0614db --- /dev/null +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/messages/FullDataPartialUpdateMessage.java @@ -0,0 +1,83 @@ +package com.seibel.distanthorizons.core.network.messages; + +import com.seibel.distanthorizons.core.dataObjects.fullData.accessor.ChunkSizedFullDataAccessor; +import com.seibel.distanthorizons.core.level.DhServerLevel; +import com.seibel.distanthorizons.core.level.IDhLevel; +import com.seibel.distanthorizons.core.network.protocol.FutureTrackableNetworkMessage; +import com.seibel.distanthorizons.core.pos.DhChunkPos; +import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataInputStream; +import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataOutputStream; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufInputStream; + +import javax.annotation.Nullable; +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +public class FullDataPartialUpdateMessage extends FutureTrackableNetworkMessage +{ + private ChunkSizedFullDataAccessor fullDataAccessor; + private DhServerLevel level; + + private int levelHashCode; + private DhChunkPos chunkPos; + private ByteBuf dataBuffer; + + public FullDataPartialUpdateMessage() {} + public FullDataPartialUpdateMessage(ChunkSizedFullDataAccessor fullDataAccessor, DhServerLevel level) + { + this.fullDataAccessor = fullDataAccessor; + this.level = level; + + // TODO Multiverse support + this.levelHashCode = level.getLevelWrapper().getDimensionType().getDimensionName().hashCode(); + } + + @Override + public void encode0(ByteBuf out) throws IOException + { + try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) + { + DhDataOutputStream dhOutputStream = new DhDataOutputStream(outputStream); + fullDataAccessor.writeToStream(dhOutputStream, level); + dhOutputStream.flush(); + + out.writeInt(levelHashCode); + + out.writeInt(fullDataAccessor.pos.x); + out.writeInt(fullDataAccessor.pos.z); + + out.writeInt(outputStream.size()); + out.writeBytes(outputStream.toByteArray()); + } + } + + @Override + public void decode0(ByteBuf in) + { + levelHashCode = in.readInt(); + + chunkPos = new DhChunkPos(in.readInt(), in.readInt()); + + this.dataBuffer = in.readBytes(in.readInt()); + } + + @Nullable + public ChunkSizedFullDataAccessor getFullDataSource(IDhLevel level) throws IOException, InterruptedException + { + // TODO Multiverse support + if (levelHashCode != level.getLevelWrapper().getDimensionType().getDimensionName().hashCode()) + return null; + + try (ByteBufInputStream inputStream = new ByteBufInputStream(dataBuffer)) + { + ChunkSizedFullDataAccessor result = new ChunkSizedFullDataAccessor(chunkPos); + result.populateFromStream(new DhDataInputStream(inputStream), level); + return result; + } + finally + { + dataBuffer.release(); + } + } +} diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/messages/FullDataSourceResponseMessage.java b/core/src/main/java/com/seibel/distanthorizons/core/network/messages/FullDataSourceResponseMessage.java index d8f550fe7..4f3ca1716 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/network/messages/FullDataSourceResponseMessage.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/messages/FullDataSourceResponseMessage.java @@ -71,5 +71,9 @@ public class FullDataSourceResponseMessage extends FutureTrackableNetworkMessage { return fullDataSourceLoader.loadData(pos, new DhDataInputStream(inputStream), level); } + finally + { + dataBuffer.release(); + } } } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/MessageRegistry.java b/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/MessageRegistry.java index d8cf5974a..77df81aa2 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/MessageRegistry.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/MessageRegistry.java @@ -35,9 +35,10 @@ public class MessageRegistry this.registerMessage(PlayerUUIDMessage.class, PlayerUUIDMessage::new); this.registerMessage(RemotePlayerConfigMessage.class, RemotePlayerConfigMessage::new); - // Full data requests + // Full data requests & updates this.registerMessage(FullDataSourceRequestMessage.class, FullDataSourceRequestMessage::new); this.registerMessage(FullDataSourceResponseMessage.class, FullDataSourceResponseMessage::new); + this.registerMessage(FullDataPartialUpdateMessage.class, FullDataPartialUpdateMessage::new); // Generation task prioritization this.registerMessage(GenTaskPriorityRequestMessage.class, GenTaskPriorityRequestMessage::new); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/pos/DhChunkPos.java b/core/src/main/java/com/seibel/distanthorizons/core/pos/DhChunkPos.java index b8d12ea55..ff7d77be8 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/pos/DhChunkPos.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/pos/DhChunkPos.java @@ -19,6 +19,8 @@ package com.seibel.distanthorizons.core.pos; +import com.seibel.distanthorizons.coreapi.util.math.Vec3d; + import java.util.Objects; public class DhChunkPos @@ -48,6 +50,10 @@ public class DhChunkPos // >> 4 is the Same as div 16 this(blockPos.x >> 4, blockPos.z >> 4); } + public DhChunkPos(Vec3d pos) + { + this(((int)pos.x) >> 4, ((int)pos.z) >> 4); + } public DhChunkPos(long packed) { this(getX(packed), getZ(packed)); } @@ -72,6 +78,11 @@ public class DhChunkPos public long getLong() { return toLong(x, z); } + public double distance(DhChunkPos other) + { + return Math.sqrt(Math.pow(x - other.x, 2) + Math.pow(z - other.z, 2)); + } + @Override public boolean equals(Object obj) { diff --git a/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/DebugRenderer.java b/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/DebugRenderer.java index add3f6330..b8ad72317 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/DebugRenderer.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/DebugRenderer.java @@ -329,8 +329,6 @@ public class DebugRenderer public void render(Mat4f transform) { - if (!Config.Client.Advanced.Debugging.debugWireframeRendering.get()) return; - transform_this_frame = transform; Vec3d cam = MC_RENDER.getCameraExactPosition(); camf = new Vec3f((float) cam.x, (float) cam.y, (float) cam.z); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/LodRenderer.java b/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/LodRenderer.java index 9a9c46c5f..ce8721347 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/LodRenderer.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/LodRenderer.java @@ -301,7 +301,15 @@ public class LodRenderer GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, 0); this.shaderProgram.unbind(); - DebugRenderer.INSTANCE.render(modelViewProjectionMatrix); + + if (Config.Client.Advanced.Debugging.debugWireframeRendering.get()) + { + profiler.popPush("Debug wireframes"); + // Note: this can be very slow if a lot of boxes are being rendered + DebugRenderer.INSTANCE.render(modelViewProjectionMatrix); + profiler.popPush("LOD cleanup"); + } + GL32.glClear(GL32.GL_DEPTH_BUFFER_BIT); minecraftGlState.restore(); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/util/objects/RateLimitedThreadPoolExecutor.java b/core/src/main/java/com/seibel/distanthorizons/core/util/objects/RateLimitedThreadPoolExecutor.java index a732842f9..ee77c442c 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/util/objects/RateLimitedThreadPoolExecutor.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/util/objects/RateLimitedThreadPoolExecutor.java @@ -15,6 +15,8 @@ public class RateLimitedThreadPoolExecutor extends ThreadPoolExecutor /** How long it took this thread to run its last task */ private final ThreadLocal lastRunDurationNanoTimeRef = ThreadLocal.withInitial(() -> -1L); + private Runnable onTerminatedEventHandler = null; + //==============// @@ -63,4 +65,23 @@ public class RateLimitedThreadPoolExecutor extends ThreadPoolExecutor this.lastRunDurationNanoTimeRef.set(System.nanoTime() - this.runStartNanoTimeRef.get()); } + @Override + protected void terminated() + { + super.terminated(); + if (this.onTerminatedEventHandler != null) + { + this.onTerminatedEventHandler.run(); + } + } + + + + //==============// + // custom logic // + //==============// + + /** only one event handler can be present at a time */ + public void setOnTerminatedEventHandler(Runnable runnable) { this.onTerminatedEventHandler = runnable; } + } \ No newline at end of file diff --git a/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientServerWorld.java b/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientServerWorld.java index 6dd3317c9..150983118 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientServerWorld.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientServerWorld.java @@ -19,9 +19,9 @@ import java.util.concurrent.ExecutorService; public class DhClientServerWorld extends AbstractDhWorld implements IDhClientWorld, IDhServerWorld { - private final HashMap levelObjMap; - private final HashSet dhLevels; - public final LocalSaveStructure saveStructure; + private final HashMap levelWrapperByDhLevel = new HashMap<>(); + private final HashSet dhLevels = new HashSet<>(); + public final LocalSaveStructure saveStructure = new LocalSaveStructure(); public ExecutorService dhTickerThread = ThreadUtil.makeSingleThreadPool("DH Client Server World Ticker Thread", 2); public EventLoop eventLoop = new EventLoop(this.dhTickerThread, this::_clientTick); //TODO: Rate-limit the loop @@ -30,12 +30,13 @@ public class DhClientServerWorld extends AbstractDhWorld implements IDhClientWor + //=============// + // constructor // + //=============// + public DhClientServerWorld() { super(EWorldEnvironment.Client_Server); - this.saveStructure = new LocalSaveStructure(); - this.levelObjMap = new HashMap<>(); - this.dhLevels = new HashSet<>(); LOGGER.info("Started DhWorld of type " + this.environment); @@ -44,12 +45,16 @@ public class DhClientServerWorld extends AbstractDhWorld implements IDhClientWor + //=========// + // methods // + //=========// + @Override public DhClientServerLevel getOrLoadLevel(ILevelWrapper wrapper) { if (wrapper instanceof IServerLevelWrapper) { - return this.levelObjMap.computeIfAbsent(wrapper, (levelWrapper) -> + return this.levelWrapperByDhLevel.computeIfAbsent(wrapper, (levelWrapper) -> { File levelFile = this.saveStructure.getLevelFolder(levelWrapper); LodUtil.assertTrue(levelFile != null); @@ -60,7 +65,7 @@ public class DhClientServerWorld extends AbstractDhWorld implements IDhClientWor } else { - return this.levelObjMap.computeIfAbsent(wrapper, (levelWrapper) -> + return this.levelWrapperByDhLevel.computeIfAbsent(wrapper, (levelWrapper) -> { IClientLevelWrapper clientLevelWrapper = (IClientLevelWrapper) levelWrapper; IServerLevelWrapper serverLevelWrapper = clientLevelWrapper.tryGetServerSideWrapper(); @@ -68,7 +73,7 @@ public class DhClientServerWorld extends AbstractDhWorld implements IDhClientWor LodUtil.assertTrue(clientLevelWrapper.getDimensionType().equals(serverLevelWrapper.getDimensionType()), "tryGetServerSideWrapper returned a level for a different dimension. ClientLevelWrapper dim: " + clientLevelWrapper.getDimensionType().getDimensionName() + " ServerLevelWrapper dim: " + serverLevelWrapper.getDimensionType().getDimensionName()); - DhClientServerLevel level = this.levelObjMap.get(serverLevelWrapper); + DhClientServerLevel level = this.levelWrapperByDhLevel.get(serverLevelWrapper); if (level == null) { return null; @@ -81,7 +86,7 @@ public class DhClientServerWorld extends AbstractDhWorld implements IDhClientWor } @Override - public DhClientServerLevel getLevel(ILevelWrapper wrapper) { return this.levelObjMap.get(wrapper); } + public DhClientServerLevel getLevel(ILevelWrapper wrapper) { return this.levelWrapperByDhLevel.get(wrapper); } @Override public Iterable getAllLoadedLevels() { return this.dhLevels; } @@ -89,21 +94,23 @@ public class DhClientServerWorld extends AbstractDhWorld implements IDhClientWor @Override public void unloadLevel(ILevelWrapper wrapper) { - if (this.levelObjMap.containsKey(wrapper)) + if (this.levelWrapperByDhLevel.containsKey(wrapper)) { if (wrapper instanceof IServerLevelWrapper) { - LOGGER.info("Unloading level " + this.levelObjMap.get(wrapper)); - DhClientServerLevel clientServerLevel = this.levelObjMap.remove(wrapper); - this.dhLevels.remove(clientServerLevel); + LOGGER.info("Unloading level " + this.levelWrapperByDhLevel.get(wrapper)); + wrapper.onUnload(); + + DhClientServerLevel clientServerLevel = this.levelWrapperByDhLevel.remove(wrapper); clientServerLevel.close(); + this.dhLevels.remove(clientServerLevel); } else { // If the level wrapper is a Client Level Wrapper, then that means the client side leaves the level, // but note that the server side still has the level loaded. So, we don't want to unload the level, // we just want to stop rendering it. - this.levelObjMap.remove(wrapper).stopRenderer(); // Ignore resource warning. The level obj is referenced elsewhere. + this.levelWrapperByDhLevel.remove(wrapper).stopRenderer(); // Ignore resource warning. The level obj is referenced elsewhere. } } } @@ -137,10 +144,18 @@ public class DhClientServerWorld extends AbstractDhWorld implements IDhClientWor for (DhClientServerLevel level : this.dhLevels) { LOGGER.info("Unloading level " + level.getServerLevelWrapper().getDimensionType().getDimensionName()); + + // level wrapper shouldn't be null, but just in case + IServerLevelWrapper serverLevelWrapper = level.getServerLevelWrapper(); + if (serverLevelWrapper != null) + { + serverLevelWrapper.onUnload(); + } + level.close(); } - this.levelObjMap.clear(); + this.levelWrapperByDhLevel.clear(); this.eventLoop.close(); LOGGER.info("Closed DhWorld of type " + this.environment); } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientWorld.java b/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientWorld.java index 23e1d2113..e0e343e02 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientWorld.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientWorld.java @@ -28,8 +28,7 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld @CheckForNull private final ClientNetworkState networkState; - // TODO why does this executor have 2 threads? - public ExecutorService dhTickerThread = ThreadUtil.makeSingleThreadPool("DH Client World Ticker Thread", 2); + public ExecutorService dhTickerThread = ThreadUtil.makeSingleThreadPool("DH Client World Ticker Thread"); public EventLoop eventLoop = new EventLoop(this.dhTickerThread, this::_clientTick); @@ -111,6 +110,7 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld if (this.levels.containsKey(wrapper)) { LOGGER.info("Unloading level " + this.levels.get(wrapper)); + wrapper.onUnload(); this.levels.remove(wrapper).close(); } } @@ -145,6 +145,14 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld for (DhClientLevel dhClientLevel : this.levels.values()) { LOGGER.info("Unloading level " + dhClientLevel.getLevelWrapper().getDimensionType().getDimensionName()); + + // level wrapper shouldn't be null, but just in case + IClientLevelWrapper clientLevelWrapper = dhClientLevel.getClientLevelWrapper(); + if (clientLevelWrapper != null) + { + clientLevelWrapper.onUnload(); + } + dhClientLevel.close(); } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/world/DhServerWorld.java b/core/src/main/java/com/seibel/distanthorizons/core/world/DhServerWorld.java index 32f664861..fdbca1b2b 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/world/DhServerWorld.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/world/DhServerWorld.java @@ -28,6 +28,10 @@ public class DhServerWorld extends AbstractDhWorld implements IDhServerWorld + //==============// + // constructors // + //==============// + public DhServerWorld() { super(EWorldEnvironment.Server_Only); @@ -54,6 +58,12 @@ public class DhServerWorld extends AbstractDhWorld implements IDhServerWorld }); } + + + //=========// + // methods // + //=========// + public void addPlayer(IServerPlayerWrapper serverPlayer) { this.remotePlayerConnectionHandler.registerJoinedPlayer(serverPlayer); @@ -111,6 +121,7 @@ public class DhServerWorld extends AbstractDhWorld implements IDhServerWorld if (this.levels.containsKey(wrapper)) { LOGGER.info("Unloading level {} ", this.levels.get(wrapper)); + wrapper.onUnload(); this.levels.remove(wrapper).close(); } } @@ -146,6 +157,14 @@ public class DhServerWorld extends AbstractDhWorld implements IDhServerWorld for (DhServerLevel level : this.levels.values()) { LOGGER.info("Unloading level " + level.getLevelWrapper().getDimensionType().getDimensionName()); + + // level wrapper shouldn't be null, but just in case + IServerLevelWrapper serverLevelWrapper = level.getServerLevelWrapper(); + if (serverLevelWrapper != null) + { + serverLevelWrapper.onUnload(); + } + level.close(); } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/world/ILevelWrapper.java b/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/world/ILevelWrapper.java index 0bc41d07b..8c660fccd 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/world/ILevelWrapper.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/world/ILevelWrapper.java @@ -39,10 +39,6 @@ public interface ILevelWrapper extends IDhApiLevelWrapper, IBindable @Override IDhApiDimensionTypeWrapper getDimensionType(); - int getBlockLight(int x, int y, int z); - - int getSkyLight(int x, int y, int z); - @Override boolean hasCeiling(); @@ -65,7 +61,7 @@ public interface ILevelWrapper extends IDhApiLevelWrapper, IBindable @Deprecated IBiomeWrapper getBiome(DhBlockPos pos); - // TODO implement onUnload - // necessary so ChunkToLodBuilder can have its cache cleared after the level closes + /** Fired when the level is being unloaded. Doesn't unload the level. */ + void onUnload(); } diff --git a/core/src/main/resources/assets/distanthorizons/lang/en_us.json b/core/src/main/resources/assets/distanthorizons/lang/en_us.json index f14754785..5eed3ea08 100644 --- a/core/src/main/resources/assets/distanthorizons/lang/en_us.json +++ b/core/src/main/resources/assets/distanthorizons/lang/en_us.json @@ -257,7 +257,7 @@ "distanthorizons.config.client.advanced.graphics.advancedGraphics.earthCurveRatio": "Earth Curve Ratio §6(EXPERIMENTAL)§r", "distanthorizons.config.client.advanced.graphics.advancedGraphics.earthCurveRatio.@tooltip": - "A value of 1 is equivalent to the curvature of Earth in real life.", + "A value of 1 is equivalent to the curvature of Earth in real life. \nThe minimum accepted value is 50 and the maximum value is 5000. \nEverything between 1 and 49 will be rounded up to 50.", "distanthorizons.config.client.advanced.graphics.advancedGraphics.lodBias": "LOD Bias §6(Affects vanilla terrain)§r", "distanthorizons.config.client.advanced.graphics.advancedGraphics.lodBias.@tooltip": diff --git a/core/src/main/resources/shaders/curve.vert b/core/src/main/resources/shaders/curve.vert index 5db053d1e..03876a40c 100644 --- a/core/src/main/resources/shaders/curve.vert +++ b/core/src/main/resources/shaders/curve.vert @@ -8,6 +8,8 @@ out vec4 vertexColor; out vec3 vertexWorldPos; out float vertexYPos; +uniform bool whiteWorld; + uniform mat4 combinedMatrix; uniform vec3 modelOffset; uniform float worldYOffset; @@ -19,6 +21,8 @@ uniform float mircoOffset; uniform float earthRadius; /** + * TODO in the future this and standard.vert should be merged together to prevent inconsistencies between the two + * * Vertex Shader * * author: James Seibel @@ -51,43 +55,25 @@ void main() vertexWorldPos.x += mx; vertexWorldPos.y += my; vertexWorldPos.z += mz; - - #if 0 - // Old (disabled) vertex transformation logic - Leetom - - // Calculate the vertex pos due to curvature of the earth - // We use spherical coordinates to calculate the vertex position - //if (vertexWorldPos.x == 0.0 && vertexWorldPos.z == 0.0) - //{ - // // In the center. No curvature needed - //} - //else - //{ - float theta = atan(vertexWorldPos.z, vertexWorldPos.x); // in radians (-pi, pi) - float trueY = earthRadius + vertexWorldPos.y; // true Y position, or height - float phi = sqrt(vertexWorldPos.z * vertexWorldPos.z + vertexWorldPos.x * vertexWorldPos.x) / trueY; - // Convert spherical coordinates to cartesian coordinates - vertexWorldPos.x = trueY * sin(phi) * cos(theta); - vertexWorldPos.z = trueY * sin(phi) * sin(theta); - vertexWorldPos.y = trueY * cos(phi) - earthRadius; - //} - - #else - // new vertex transformation logic - stduhpf - - float localRadius = earthRadius + vertexYPos;// vertexWorldPos.y + cameraPosition.y - Center_Y; - - float phi = length(vertexWorldPos.xz) / localRadius; - - vertexWorldPos.y += (cos(phi) - 1.0) * localRadius; - vertexWorldPos.xz = vertexWorldPos.xz * sin(phi) / phi; - #endif - + + + // vertex transformation logic - stduhpf + float localRadius = earthRadius + vertexYPos; + float phi = length(vertexWorldPos.xz) / localRadius; + vertexWorldPos.y += (cos(phi) - 1.0) * localRadius; + vertexWorldPos.xz = vertexWorldPos.xz * sin(phi) / phi; + + uint lights = meta & 0xFFu; float light2 = (mod(float(lights), 16.0) + 0.5) / 16.0; float light = (float(lights / 16u) + 0.5) / 16.0; - vertexColor = color * vec4(texture(lightMap, vec2(light, light2)).xyz, 1.0); - + vertexColor = vec4(texture(lightMap, vec2(light, light2)).xyz, 1.0); + + if (!whiteWorld) + { + vertexColor *= color; + } + gl_Position = combinedMatrix * vec4(vertexWorldPos, 1.0); } diff --git a/core/src/main/resources/shaders/ssao/ao.frag b/core/src/main/resources/shaders/ssao/ao.frag index 33be6a37d..9538f54c4 100644 --- a/core/src/main/resources/shaders/ssao/ao.frag +++ b/core/src/main/resources/shaders/ssao/ao.frag @@ -46,16 +46,17 @@ void main() { vec3 samplePos = vec3(0.0) + (TBN * gKernel[i]); samplePos = viewPos + samplePos * gSampleRad; - + vec4 offset = gProj * vec4(samplePos + viewPos, 1.0); offset.xy /= offset.w; offset.xy = offset.xy * HALF_2 + HALF_2; - + float geometryDepth = calcViewPosition(offset.xy).z; - + float rangeCheck = smoothstep(0.0, 1.0, gSampleRad / abs(viewPos.z - geometryDepth)); - occlusion_factor += float(geometryDepth >= samplePos.z + 0.05) * rangeCheck; - + // the number added to the samplePos.z can be used to reduce noise in the SSAO application at the cost of reducing the overall affect + occlusion_factor += float(geometryDepth >= samplePos.z + 1.0) * rangeCheck; + } float visibility_factor = 1.0 - (occlusion_factor / MAX_KERNEL_SIZE); diff --git a/core/src/main/resources/shaders/standard.vert b/core/src/main/resources/shaders/standard.vert index c7e446cf8..316559b6e 100644 --- a/core/src/main/resources/shaders/standard.vert +++ b/core/src/main/resources/shaders/standard.vert @@ -20,6 +20,8 @@ uniform float mircoOffset; /** + * TODO in the future this and curve.vert should be merged together to prevent inconsistencies between the two + * * Vertex Shader * * author: James Seibel