diff --git a/api/src/main/java/com/seibel/distanthorizons/api/enums/worldGeneration/EDhApiDistantGeneratorProgressDisplayLocation.java b/api/src/main/java/com/seibel/distanthorizons/api/enums/worldGeneration/EDhApiDistantGeneratorProgressDisplayLocation.java
new file mode 100644
index 000000000..698927add
--- /dev/null
+++ b/api/src/main/java/com/seibel/distanthorizons/api/enums/worldGeneration/EDhApiDistantGeneratorProgressDisplayLocation.java
@@ -0,0 +1,44 @@
+/*
+ * This file is part of the Distant Horizons mod
+ * licensed under the GNU LGPL v3 License.
+ *
+ * Copyright (C) 2020-2023 James Seibel
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package com.seibel.distanthorizons.api.enums.worldGeneration;
+
+/**
+ * OVERLAY
+ * CHAT
+ * LOG
+ * DISABLED
+ *
+ * @author James Seibel
+ * @version 2025-1-7
+ * @since API 4.0.0
+ */
+public enum EDhApiDistantGeneratorProgressDisplayLocation
+{
+ // Reminder:
+ // when adding items up the API minor version
+ // when removing items up the API major version
+
+
+ OVERLAY,
+ CHAT,
+ LOG,
+ DISABLED;
+
+}
diff --git a/core/src/main/java/com/seibel/distanthorizons/core/api/internal/ClientApi.java b/core/src/main/java/com/seibel/distanthorizons/core/api/internal/ClientApi.java
index c8e779f34..32db1efcc 100644
--- a/core/src/main/java/com/seibel/distanthorizons/core/api/internal/ClientApi.java
+++ b/core/src/main/java/com/seibel/distanthorizons/core/api/internal/ClientApi.java
@@ -99,6 +99,7 @@ public class ClientApi
private long lastStaticWarningMessageSentMsTime = 0L;
private final Queue chatMessageQueueForNextFrame = new LinkedBlockingQueue<>();
+ private final Queue overlayMessageQueueForNextFrame = new LinkedBlockingQueue<>();
public boolean rendererDisabledBecauseOfExceptions = false;
@@ -624,7 +625,7 @@ public class ClientApi
}
- // generic messages
+ // chat messages
while (!this.chatMessageQueueForNextFrame.isEmpty())
{
String message = this.chatMessageQueueForNextFrame.poll();
@@ -635,6 +636,18 @@ public class ClientApi
}
MC_CLIENT.sendChatMessage(message);
}
+
+ // overlay messages
+ while (!this.overlayMessageQueueForNextFrame.isEmpty())
+ {
+ String message = this.overlayMessageQueueForNextFrame.poll();
+ if (message == null)
+ {
+ // done to prevent potential null pointers
+ message = "";
+ }
+ MC_CLIENT.sendOverlayMessage(message);
+ }
}
private void detectAndSendBootTimeWarnings()
{
@@ -722,4 +735,9 @@ public class ClientApi
*/
public void showChatMessageNextFrame(String chatMessage) { this.chatMessageQueueForNextFrame.add(chatMessage); }
+ /**
+ * Similar to {@link ClientApi#showChatMessageNextFrame(String)} but appears above the toolbar.
+ */
+ public void showOverlayMessageNextFrame(String message) { this.overlayMessageQueueForNextFrame.add(message); }
+
}
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 3d80d0800..4990778d3 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
@@ -24,6 +24,7 @@ import com.seibel.distanthorizons.api.enums.config.*;
import com.seibel.distanthorizons.api.enums.config.quickOptions.*;
import com.seibel.distanthorizons.api.enums.rendering.*;
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiDistantGeneratorMode;
+import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiDistantGeneratorProgressDisplayLocation;
import com.seibel.distanthorizons.core.config.eventHandlers.*;
import com.seibel.distanthorizons.core.config.eventHandlers.presets.*;
import com.seibel.distanthorizons.core.config.listeners.ConfigChangeListener;
@@ -1193,7 +1194,10 @@ public class Config
.set(true)
.comment(""
+ " Should Distant Horizons slowly generate LODs \n"
- + " outside the vanilla render distance?")
+ + " outside the vanilla render distance? \n"
+ + "Depending on the generator mode, this will import existing chunks \n"
+ + "and/or generating missing chunks."
+ + "")
.build();
public static ConfigEntry distantGeneratorMode = new ConfigEntry.Builder()
@@ -1237,6 +1241,25 @@ public class Config
+ "")
.build();
+ public static ConfigEntry showGenerationProgress = new ConfigEntry.Builder()
+ .set(EDhApiDistantGeneratorProgressDisplayLocation.OVERLAY)
+ .comment(""
+ + "How should distant generator progress be displayed? \n"
+ + "\n"
+ + EDhApiDistantGeneratorProgressDisplayLocation.OVERLAY + ": may be the same as "+EDhApiDistantGeneratorProgressDisplayLocation.CHAT+" for some Minecraft versions \n"
+ + EDhApiDistantGeneratorProgressDisplayLocation.CHAT + " \n"
+ + EDhApiDistantGeneratorProgressDisplayLocation.LOG + " \n"
+ + EDhApiDistantGeneratorProgressDisplayLocation.DISABLED + " \n"
+ + "")
+ .build();
+
+ public static ConfigEntry generationProgressDisplayIntervalInSeconds = new ConfigEntry.Builder()
+ .setMinDefaultMax(1, 2, 60 * 60 * 4) // max = 4 hours
+ .comment(""
+ + "How often should the distant generator progress be displayed? \n"
+ + "")
+ .build();
+
}
public static class LodBuilding
diff --git a/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/FullDataSourceProviderV2.java b/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/FullDataSourceProviderV2.java
index a7d5200de..6ae54b04c 100644
--- a/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/FullDataSourceProviderV2.java
+++ b/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/FullDataSourceProviderV2.java
@@ -40,7 +40,6 @@ import com.seibel.distanthorizons.core.sql.repo.AbstractDhRepo;
import com.seibel.distanthorizons.core.sql.repo.FullDataSourceV2Repo;
import com.seibel.distanthorizons.core.util.ThreadUtil;
import com.seibel.distanthorizons.core.util.objects.DataCorruptedException;
-import com.seibel.distanthorizons.core.util.threading.PrioritySemaphore;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import it.unimi.dsi.fastutil.longs.LongArrayList;
@@ -611,12 +610,7 @@ public class FullDataSourceProviderV2
*/
@Nullable
public LongArrayList getPositionsToRetrieve(Long pos) { return null; }
- /**
- * Returns how many positions could potentially be generated for this position assuming the position is empty.
- * Used when estimating the total number of retrieval requests.
- */
- public int getMaxPossibleRetrievalPositionCountForPos(Long pos) { return -1; }
-
+
/** @return true if the position was queued, false if not */
@Nullable
public CompletableFuture queuePositionForRetrieval(Long genPos) { return null; }
@@ -628,6 +622,8 @@ public class FullDataSourceProviderV2
/** Can be used to display how many total retrieval requests might be available. */
public void setTotalRetrievalPositionCount(int newCount) { }
+ /** Can be used to display how many total chunk retrieval requests should be available. */
+ public void setEstimatedRemainingRetrievalChunkCount(int newCount) { }
/**
* Returns how many data sources are currently in memory and haven't
diff --git a/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/GeneratedFullDataSourceProvider.java b/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/GeneratedFullDataSourceProvider.java
index 91f74da6b..2edd1d6d5 100644
--- a/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/GeneratedFullDataSourceProvider.java
+++ b/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/GeneratedFullDataSourceProvider.java
@@ -37,7 +37,6 @@ import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
-import com.seibel.distanthorizons.coreapi.util.BitShiftUtil;
import it.unimi.dsi.fastutil.bytes.ByteArrayList;
import it.unimi.dsi.fastutil.longs.LongArrayList;
import org.apache.logging.log4j.Logger;
@@ -168,12 +167,12 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
public boolean canRetrieveMissingDataSources() { return true; }
@Override
- public void setTotalRetrievalPositionCount(int newCount)
+ public void setEstimatedRemainingRetrievalChunkCount(int newCount)
{
IFullDataSourceRetrievalQueue worldGenQueue = this.worldGenQueueRef.get();
if (worldGenQueue != null)
{
- worldGenQueue.setEstimatedTotalTaskCount(newCount);
+ worldGenQueue.setRetrievalEstimatedRemainingChunkCount(newCount);
}
}
@@ -411,21 +410,6 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
return generationList;
}
- @Override
- public int getMaxPossibleRetrievalPositionCountForPos(Long pos)
- {
- IFullDataSourceRetrievalQueue worldGenQueue = this.worldGenQueueRef.get();
- if (worldGenQueue == null)
- {
- return -1;
- }
-
- int minGeneratorSectionDetailLevel = worldGenQueue.highestDataDetail() + DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL;
- int detailLevelDiff = DhSectionPos.getDetailLevel(pos) - minGeneratorSectionDetailLevel;
-
- return BitShiftUtil.powerOfTwo(detailLevelDiff);
- }
-
//=======//
diff --git a/core/src/main/java/com/seibel/distanthorizons/core/generation/IFullDataSourceRetrievalQueue.java b/core/src/main/java/com/seibel/distanthorizons/core/generation/IFullDataSourceRetrievalQueue.java
index 05a103e83..a81f871f7 100644
--- a/core/src/main/java/com/seibel/distanthorizons/core/generation/IFullDataSourceRetrievalQueue.java
+++ b/core/src/main/java/com/seibel/distanthorizons/core/generation/IFullDataSourceRetrievalQueue.java
@@ -24,6 +24,8 @@ import com.seibel.distanthorizons.core.generation.tasks.WorldGenResult;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.render.LodQuadTree;
+import com.seibel.distanthorizons.core.util.objects.RollingAverage;
+import org.jetbrains.annotations.Nullable;
import java.io.Closeable;
import java.util.List;
@@ -113,11 +115,22 @@ public interface IFullDataSourceRetrievalQueue extends Closeable
int getWaitingTaskCount();
int getInProgressTaskCount();
+ /** returns how many chunks are currently queued for retrieval */
+ int getQueuedChunkCount();
+
/** used for rendering to the F3 menu */
- int getEstimatedTotalTaskCount();
- void setEstimatedTotalTaskCount(int newEstimate);
+ int getEstimatedRemainingTaskCount();
+ void setEstimatedRemainingTaskCount(int newEstimate);
+
+ /** used for displaying a progress update to the user */
+ int getRetrievalEstimatedRemainingChunkCount();
+ void setRetrievalEstimatedRemainingChunkCount(int newEstimate);
void addDebugMenuStringsToList(List messageList);
+ /** Can be used to determine roughly how fast the world generator is running. */
+ RollingAverage getRollingAverageChunkGenTimeInMs();
+
+
}
diff --git a/core/src/main/java/com/seibel/distanthorizons/core/generation/RemoteWorldRetrievalQueue.java b/core/src/main/java/com/seibel/distanthorizons/core/generation/RemoteWorldRetrievalQueue.java
index bef67c9a6..4d30db140 100644
--- a/core/src/main/java/com/seibel/distanthorizons/core/generation/RemoteWorldRetrievalQueue.java
+++ b/core/src/main/java/com/seibel/distanthorizons/core/generation/RemoteWorldRetrievalQueue.java
@@ -11,6 +11,7 @@ import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable;
import com.seibel.distanthorizons.core.util.LodUtil;
+import com.seibel.distanthorizons.core.util.objects.RollingAverage;
import org.apache.logging.log4j.Logger;
import java.util.ArrayList;
@@ -21,7 +22,11 @@ public class RemoteWorldRetrievalQueue extends AbstractFullDataNetworkRequestQue
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
- private int estimatedTotalTaskCount;
+ private int estimatedRemainingTaskCount;
+ private int estimatedTotalChunkCount;
+
+ private final RollingAverage rollingAverageChunkGenTimeInMs = new RollingAverage(1_000);
+ public RollingAverage getRollingAverageChunkGenTimeInMs() { return this.rollingAverageChunkGenTimeInMs; }
@@ -49,9 +54,18 @@ public class RemoteWorldRetrievalQueue extends AbstractFullDataNetworkRequestQue
@Override
public CompletableFuture submitRetrievalTask(long sectionPos, byte requiredDataDetail, IWorldGenTaskTracker tracker)
{
+ long generationStartMsTime = System.currentTimeMillis();
+
return super.submitRequest(sectionPos, tracker.getDataSourceConsumer())
.thenApply(requestResult ->
{
+ long totalGenTimeInMs = System.currentTimeMillis() - generationStartMsTime;
+
+ int chunkWidth = DhSectionPos.getChunkWidth(sectionPos);
+ int chunkCount = chunkWidth * chunkWidth;
+ double timePerChunk = (double)totalGenTimeInMs / (double)chunkCount;
+ this.rollingAverageChunkGenTimeInMs.addValue(timePerChunk);
+
switch (requestResult)
{
case SUCCEEDED:
@@ -94,10 +108,17 @@ public class RemoteWorldRetrievalQueue extends AbstractFullDataNetworkRequestQue
//===============//
@Override
- public int getEstimatedTotalTaskCount() { return this.estimatedTotalTaskCount; }
+ public int getEstimatedRemainingTaskCount() { return this.estimatedRemainingTaskCount; }
@Override
- public void setEstimatedTotalTaskCount(int newEstimate) { this.estimatedTotalTaskCount = newEstimate; }
+ public void setEstimatedRemainingTaskCount(int newEstimate) { this.estimatedRemainingTaskCount = newEstimate; }
+ @Override
+ public int getRetrievalEstimatedRemainingChunkCount() { return this.estimatedTotalChunkCount; }
+ @Override
+ public void setRetrievalEstimatedRemainingChunkCount(int newEstimate) { this.estimatedTotalChunkCount = newEstimate; }
+
+ @Override
+ public int getQueuedChunkCount() { return 0; }
}
\ No newline at end of file
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 e236674c8..f52f476c4 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
@@ -42,6 +42,7 @@ import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable;
import com.seibel.distanthorizons.core.util.LodUtil.AssertFailureException;
import com.seibel.distanthorizons.core.util.ThreadUtil;
import com.seibel.distanthorizons.core.util.objects.DataCorruptedException;
+import com.seibel.distanthorizons.core.util.objects.RollingAverage;
import com.seibel.distanthorizons.core.util.objects.UncheckedInterruptedException;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
@@ -98,7 +99,11 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
private DhBlockPos2D generationTargetPos = DhBlockPos2D.ZERO;
/** just used for rendering to the F3 menu */
- private int estimatedTotalTaskCount = 0;
+ private int estimatedRemainingTaskCount = 0;
+ private int estimatedRemainingChunkCount = 0;
+
+ private final RollingAverage rollingAverageChunkGenTimeInMs = new RollingAverage(1_000);
+ public RollingAverage getRollingAverageChunkGenTimeInMs() { return this.rollingAverageChunkGenTimeInMs; }
@@ -247,7 +252,7 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
*/
private boolean startNextWorldGenTask(DhBlockPos2D targetPos)
{
- if (this.waitingTasks.size() == 0)
+ if (this.waitingTasks.isEmpty())
{
return false;
}
@@ -333,7 +338,19 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
long taskPos = newTaskGroup.group.pos;
LodUtil.assertTrue(taskDetailLevel >= this.highestDataDetail && taskDetailLevel <= this.lowestDataDetail);
- newTaskGroup.genFuture = this.startGenerationEvent(taskPos, taskDetailLevel, newTaskGroup.group::consumeDataSource);
+ int generationRequestChunkWidthCount = BitShiftUtil.powerOfTwo(DhSectionPos.getDetailLevel(taskPos) - taskDetailLevel - 4); // minus 4 is equal to dividing by 16 to convert to chunk scale
+
+ long generationStartMsTime = System.currentTimeMillis();
+ CompletableFuture generationFuture = this.startGenerationEvent(taskPos, taskDetailLevel, generationRequestChunkWidthCount, newTaskGroup.group::consumeDataSource);
+ generationFuture.thenRun(() ->
+ {
+ long totalGenTimeInMs = System.currentTimeMillis() - generationStartMsTime;
+ int chunkCount = generationRequestChunkWidthCount * generationRequestChunkWidthCount;
+ double timePerChunk = (double)totalGenTimeInMs / (double)chunkCount;
+ this.rollingAverageChunkGenTimeInMs.addValue(timePerChunk);
+ });
+
+ newTaskGroup.genFuture = generationFuture;
LodUtil.assertTrue(newTaskGroup.genFuture != null);
newTaskGroup.genFuture.whenComplete((voidObj, exception) ->
@@ -369,11 +386,11 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
private CompletableFuture startGenerationEvent(
long requestPos,
byte targetDataDetail,
+ int generationRequestChunkWidthCount,
Consumer dataSourceConsumer
)
{
DhChunkPos chunkPosMin = new DhChunkPos(DhSectionPos.getSectionBBoxPos(requestPos).getCornerBlockPos());
- int generationRequestChunkWidthCount = BitShiftUtil.powerOfTwo(DhSectionPos.getDetailLevel(requestPos) - targetDataDetail - 4); // minus 4 is equal to dividing by 16 to convert to chunk scale
EDhApiDistantGeneratorMode generatorMode = Config.Common.WorldGenerator.distantGeneratorMode.get();
EDhApiWorldGeneratorReturnType returnType = this.generator.getReturnType();
@@ -495,11 +512,30 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
public byte highestDataDetail() { return this.highestDataDetail; }
@Override
- public int getEstimatedTotalTaskCount() { return this.estimatedTotalTaskCount; }
+ public int getEstimatedRemainingTaskCount() { return this.estimatedRemainingTaskCount; }
@Override
- public void setEstimatedTotalTaskCount(int newEstimate) { this.estimatedTotalTaskCount = newEstimate; }
+ public void setEstimatedRemainingTaskCount(int newEstimate) { this.estimatedRemainingTaskCount = newEstimate; }
- @Override public void addDebugMenuStringsToList(List messageList) { }
+ @Override
+ public int getRetrievalEstimatedRemainingChunkCount() { return this.estimatedRemainingChunkCount; }
+ @Override
+ public void setRetrievalEstimatedRemainingChunkCount(int newEstimate) { this.estimatedRemainingChunkCount = newEstimate; }
+
+ @Override
+ public void addDebugMenuStringsToList(List messageList) { }
+
+ @Override
+ public int getQueuedChunkCount()
+ {
+ int chunkCount = 0;
+ for (long pos : this.waitingTasks.keySet())
+ {
+ int chunkWidth = DhSectionPos.getBlockWidth(pos) / LodUtil.CHUNK_WIDTH;
+ chunkCount += (chunkWidth * chunkWidth);
+ }
+
+ return chunkCount;
+ }
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 e99d56d2c..ba7da3c55 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
@@ -19,21 +19,25 @@
package com.seibel.distanthorizons.core.level;
+import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiDistantGeneratorProgressDisplayLocation;
+import com.seibel.distanthorizons.core.api.internal.ClientApi;
+import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.file.fullDatafile.GeneratedFullDataSourceProvider;
import com.seibel.distanthorizons.core.generation.IFullDataSourceRetrievalQueue;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.logging.f3.F3Screen;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
+import com.seibel.distanthorizons.core.util.ThreadUtil;
+import com.seibel.distanthorizons.core.util.objects.RollingAverage;
+import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import com.seibel.distanthorizons.core.world.DhApiWorldProxy;
import org.apache.logging.log4j.Logger;
-import org.jetbrains.annotations.Nullable;
import java.io.Closeable;
import java.util.List;
-import java.util.Optional;
import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.atomic.AtomicReference;
-import java.util.function.BooleanSupplier;
import java.util.function.Supplier;
/**
@@ -188,10 +192,20 @@ public class WorldGenModule implements Closeable
}
+ // estimated tasks
String waitingCountStr = F3Screen.NUMBER_FORMAT.format(worldGenState.worldGenerationQueue.getWaitingTaskCount());
String inProgressCountStr = F3Screen.NUMBER_FORMAT.format(worldGenState.worldGenerationQueue.getInProgressTaskCount());
- String totalCountEstimateStr = F3Screen.NUMBER_FORMAT.format(worldGenState.worldGenerationQueue.getEstimatedTotalTaskCount());
- messageList.add("World Gen/Pull Chunk Tasks: "+waitingCountStr+"/"+totalCountEstimateStr+" (in progress "+inProgressCountStr+")");
+ String totalCountEstimateStr = F3Screen.NUMBER_FORMAT.format(worldGenState.worldGenerationQueue.getRetrievalEstimatedRemainingChunkCount());
+ String message = "World Gen/Import Tasks: "+waitingCountStr+"/"+totalCountEstimateStr+" (in progress "+inProgressCountStr+")";
+
+ // estimated chunks/sec
+ double chunksPerSec = worldGenState.getEstimatedChunksPerSecond();
+ if (chunksPerSec > -1)
+ {
+ message += ", " + F3Screen.NUMBER_FORMAT.format(chunksPerSec) + " chunks/sec";
+ }
+
+ messageList.add(message);
worldGenState.worldGenerationQueue.addDebugMenuStringsToList(messageList);
}
@@ -207,8 +221,15 @@ public class WorldGenModule implements Closeable
{
public IFullDataSourceRetrievalQueue worldGenerationQueue;
+ private static final ThreadPoolExecutor PROGRESS_UPDATER_THREAD = ThreadUtil.makeSingleThreadPool("World Gen Progress Updater");
+ private boolean progressUpdateThreadRunning = false;
+
+
CompletableFuture closeAsync(boolean doInterrupt)
{
+ // this should stop the updater thread
+ this.progressUpdateThreadRunning = false;
+
return this.worldGenerationQueue.startClosingAsync(true, doInterrupt)
.exceptionally(e ->
{
@@ -225,7 +246,119 @@ public class WorldGenModule implements Closeable
/** @param targetPosForGeneration the position that world generation should be centered around */
public void startGenerationQueueAndSetTargetPos(DhBlockPos2D targetPosForGeneration)
- { this.worldGenerationQueue.startAndSetTargetPos(targetPosForGeneration); }
+ {
+ this.worldGenerationQueue.startAndSetTargetPos(targetPosForGeneration);
+ this.startProgressUpdateThread();
+ }
+ private void startProgressUpdateThread()
+ {
+ // only start the thread once
+ if (!this.progressUpdateThreadRunning)
+ {
+ this.progressUpdateThreadRunning = true;
+
+ PROGRESS_UPDATER_THREAD.execute(() ->
+ {
+ while (this.progressUpdateThreadRunning)
+ {
+ try
+ {
+ this.sendRetrievalProgress();
+
+ // sleep so we only see an update once in a while
+ int sleepTimeInSec = Config.Common.WorldGenerator.generationProgressDisplayIntervalInSeconds.get();
+ Thread.sleep(sleepTimeInSec * 1_000L);
+ }
+ catch (Exception e)
+ {
+ LOGGER.error("Unexpected issue displaying chunk retrieval progress [" + e.getMessage() + "].", e);
+ }
+ }
+ });
+ }
+ }
+ private void sendRetrievalProgress()
+ {
+ int remainingChunkCount = this.worldGenerationQueue.getRetrievalEstimatedRemainingChunkCount();
+ remainingChunkCount += this.worldGenerationQueue.getQueuedChunkCount();
+ String remainingChunkCountStr = F3Screen.NUMBER_FORMAT.format(remainingChunkCount);
+
+ String message = "DH Gen/Import: " + remainingChunkCountStr + " chunks";
+
+
+ double chunksPerSec = this.getEstimatedChunksPerSecond();
+ if (chunksPerSec > 0)
+ {
+ long estimatedRemainingTime = (long) (remainingChunkCount / chunksPerSec);
+ message += " Estimated Time: " + formatSeconds(estimatedRemainingTime); // at " + F3Screen.NUMBER_FORMAT.format(chunksPerSec) + " chunks/sec";
+ }
+
+
+ if (remainingChunkCount != 0)
+ {
+ EDhApiDistantGeneratorProgressDisplayLocation displayLocation = Config.Common.WorldGenerator.showGenerationProgress.get();
+ if (displayLocation == EDhApiDistantGeneratorProgressDisplayLocation.OVERLAY)
+ {
+ ClientApi.INSTANCE.showOverlayMessageNextFrame(message);
+ }
+ else if (displayLocation == EDhApiDistantGeneratorProgressDisplayLocation.CHAT)
+ {
+ ClientApi.INSTANCE.showChatMessageNextFrame(message);
+ }
+ else if (displayLocation == EDhApiDistantGeneratorProgressDisplayLocation.LOG)
+ {
+ LOGGER.info(message);
+ }
+
+ }
+ }
+ private static String formatSeconds(long totalSeconds)
+ {
+ long days = totalSeconds / (24 * 3600); // 24 hours in a day
+ long hours = (totalSeconds % (24 * 3600)) / 3600; // Hours
+ long minutes = (totalSeconds % 3600) / 60; // Minutes
+ long seconds = totalSeconds % 60; // Seconds
+
+
+ String timeString = "";
+ if (days > 0)
+ {
+ timeString += days+" ";
+ }
+ if (hours > 0)
+ {
+ timeString += hours+":";
+ }
+ if (minutes > 0)
+ {
+ timeString += String.format("%02d", minutes)+":";
+ }
+ timeString += String.format("%02d", seconds);
+
+ return timeString;
+ }
+
+ /** @return -1 if this method isn't supported or available */
+ public double getEstimatedChunksPerSecond()
+ {
+ RollingAverage avg = this.worldGenerationQueue.getRollingAverageChunkGenTimeInMs();
+ if (avg == null)
+ {
+ return -1;
+ }
+
+
+ // convert chunk generation time in milliseconds to chunks per second
+ double chunksPerSecond = (1 / avg.getAverage()) * 1_000;
+ // estimate the number of chunks that can be processed per second by all threads
+ // Note: this is probably higher than the actual number, we might want to drop this by 1 or 2 to give a more realistic estimate
+ chunksPerSecond = ThreadPoolUtil.getThreadCount() * chunksPerSecond;
+
+ return chunksPerSecond;
+ }
+
}
+
+
}
diff --git a/core/src/main/java/com/seibel/distanthorizons/core/logging/f3/F3Screen.java b/core/src/main/java/com/seibel/distanthorizons/core/logging/f3/F3Screen.java
index 0ee693657..4cccb3f6c 100644
--- a/core/src/main/java/com/seibel/distanthorizons/core/logging/f3/F3Screen.java
+++ b/core/src/main/java/com/seibel/distanthorizons/core/logging/f3/F3Screen.java
@@ -123,7 +123,7 @@ public class F3Screen
if (Config.Client.Advanced.Debugging.F3Screen.showThreadPools.get())
{
// multi thread pools
- messageList.add(getThreadPoolStatString("World Gen/Pull Chunk", worldGenPool));//"World Gen Tasks: 40/5304, (in progress: 7)");
+ messageList.add(getThreadPoolStatString("World Gen/Import", worldGenPool));
messageList.add(getThreadPoolStatString("File Handler", fileHandlerPool));
messageList.add(getThreadPoolStatString("Update Propagator", updatePool));
messageList.add(getThreadPoolStatString("LOD Builder", lodBuilderPool));
diff --git a/core/src/main/java/com/seibel/distanthorizons/core/pos/DhSectionPos.java b/core/src/main/java/com/seibel/distanthorizons/core/pos/DhSectionPos.java
index 48f36c70d..b396d6a79 100644
--- a/core/src/main/java/com/seibel/distanthorizons/core/pos/DhSectionPos.java
+++ b/core/src/main/java/com/seibel/distanthorizons/core/pos/DhSectionPos.java
@@ -224,7 +224,9 @@ public class DhSectionPos
/** @return how wide this section is in blocks */
public static int getBlockWidth(long pos) { return BitShiftUtil.powerOfTwo(getDetailLevel(pos)); }
-
+ /** @return how wide this section is in chunks */
+ public static int getChunkWidth(long pos) { return DhSectionPos.getBlockWidth(pos) / LodUtil.CHUNK_WIDTH; }
+
public static DhBlockPos2D getCenterBlockPos(long pos) { return new DhBlockPos2D(getCenterBlockPosX(pos), getCenterBlockPosZ(pos)); }
diff --git a/core/src/main/java/com/seibel/distanthorizons/core/render/LodQuadTree.java b/core/src/main/java/com/seibel/distanthorizons/core/render/LodQuadTree.java
index 132c48177..69eadd346 100644
--- a/core/src/main/java/com/seibel/distanthorizons/core/render/LodQuadTree.java
+++ b/core/src/main/java/com/seibel/distanthorizons/core/render/LodQuadTree.java
@@ -628,22 +628,33 @@ public class LodQuadTree extends QuadTree implements IDebugRen
renderSection.tryQueuingMissingLodRetrieval();
}
- // calculate an estimate for the max number of tasks for the queue
- int totalWorldGenCount = 0;
+ // calculate an estimate for the max number of chunks for the queue
+ int totalWorldGenChunkCount = 0;
+ int totalWorldGenTaskCount = 0;
for (int i = 0; i < nodeList.size(); i++)
{
LodRenderSection renderSection = nodeList.get(i);
if (!renderSection.missingPositionsCalculated())
{
- // may be higher than the actual amount
- totalWorldGenCount += this.fullDataSourceProvider.getMaxPossibleRetrievalPositionCountForPos(renderSection.pos);
+ // chunk count
+ int sectionWidthInChunks = DhSectionPos.getChunkWidth(renderSection.pos);
+ totalWorldGenChunkCount += sectionWidthInChunks * sectionWidthInChunks;
+
+ // task count
+ totalWorldGenTaskCount += renderSection.ungeneratedPositionCount();
}
else
{
- totalWorldGenCount += renderSection.ungeneratedPositionCount();
+ totalWorldGenChunkCount += renderSection.ungeneratedChunkCount();
+
+ // 1 since we assume the position can be generated in a single go
+ // TODO this is a bad assumption, can we determine what the world gen supports and determine it from that?
+ totalWorldGenTaskCount += 1;
}
}
- this.fullDataSourceProvider.setTotalRetrievalPositionCount(totalWorldGenCount);
+
+ this.fullDataSourceProvider.setEstimatedRemainingRetrievalChunkCount(totalWorldGenChunkCount);
+ this.fullDataSourceProvider.setTotalRetrievalPositionCount(totalWorldGenTaskCount);
}
catch (Exception e)
{
diff --git a/core/src/main/java/com/seibel/distanthorizons/core/render/LodRenderSection.java b/core/src/main/java/com/seibel/distanthorizons/core/render/LodRenderSection.java
index 555dd60e9..62008245e 100644
--- a/core/src/main/java/com/seibel/distanthorizons/core/render/LodRenderSection.java
+++ b/core/src/main/java/com/seibel/distanthorizons/core/render/LodRenderSection.java
@@ -330,6 +330,23 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
LongArrayList missingGenerationPos = this.getMissingGenerationPos();
return missingGenerationPos != null ? missingGenerationPos.size() : 0;
}
+ public int ungeneratedChunkCount()
+ {
+ LongArrayList missingGenerationPos = this.getMissingGenerationPos();
+ if (missingGenerationPos == null)
+ {
+ return 0;
+ }
+
+ int chunkCount = 0;
+ // get the number of chunks each position contains
+ for (int i = 0; i < missingGenerationPos.size(); i++)
+ {
+ int chunkWidth = DhSectionPos.getChunkWidth(missingGenerationPos.getLong(i));
+ chunkCount += (chunkWidth * chunkWidth);
+ }
+ return chunkCount;
+ }
public void tryQueuingMissingLodRetrieval()
{
diff --git a/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/minecraft/IMinecraftClientWrapper.java b/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/minecraft/IMinecraftClientWrapper.java
index fcb22b01e..ea6b5f589 100644
--- a/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/minecraft/IMinecraftClientWrapper.java
+++ b/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/minecraft/IMinecraftClientWrapper.java
@@ -100,6 +100,8 @@ public interface IMinecraftClientWrapper extends IBindable
ArrayList getAllServerWorlds();
void sendChatMessage(String string);
+ /** Will default to sending a chat message if not supported by the current MC version */
+ void sendOverlayMessage(String string);
/** Sends the given message to chat with a formatted prefix and color based on the log level. */
default void logToChat(Level logLevel, String message)
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 0f896ad13..f98ef3b3e 100644
--- a/core/src/main/resources/assets/distanthorizons/lang/en_us.json
+++ b/core/src/main/resources/assets/distanthorizons/lang/en_us.json
@@ -567,15 +567,19 @@
"distanthorizons.config.common.worldGenerator.enableDistantGeneration":
"Enable Distant Generation",
"distanthorizons.config.common.worldGenerator.enableDistantGeneration.@tooltip":
- "§6True:§r in single player LODs will be generated outside the vanilla render distance.\nNote: this can use a large amount of CPU.\n\n§6False:§r LODs will only generate within the vanilla render distance.",
+ "§6True:§r in single player LODs will be created outside the vanilla render distance.\nDepending on the Generator Mode pre-existing chunks will be important and/o\nmissing chunks will be generated.\nNote: this can use a large amount of CPU.\n\n§6False:§r LODs will only generate within the vanilla render distance.",
"distanthorizons.config.common.worldGenerator.distantGeneratorMode":
"Distance Generator Mode",
"distanthorizons.config.common.worldGenerator.distantGeneratorMode.@tooltip":
"How complicated the generation should be when generating LODs outside the vanilla render distance.",
- "distanthorizons.config.common.worldGenerator.worldGenerationTimeoutLengthInSeconds":
- "Timeout Length In Seconds",
- "distanthorizons.config.common.worldGenerator.worldGenerationTimeoutLengthInSeconds.@tooltip":
- "How long should a world generator thread run for before timing out? \nNote: If you are experiencing timeout errors it is better to lower your CPU usage first \nvia the thread config before changing this value.",
+ "distanthorizons.config.common.worldGenerator.showGenerationProgress":
+ "Show Generation Progress",
+ "distanthorizons.config.common.worldGenerator.showGenerationProgress.@tooltip":
+ "Determines how should distant generator progress be displayed.",
+ "distanthorizons.config.common.worldGenerator.generationProgressDisplayIntervalInSeconds":
+ "Progress Display Interval In Seconds",
+ "distanthorizons.config.common.worldGenerator.generationProgressDisplayIntervalInSeconds.@tooltip":
+ "Determines how long between progress update displays.",
"distanthorizons.config.common.lodBuilding":
@@ -877,6 +881,15 @@
"Features",
"distanthorizons.config.enum.EDhApiDistantGeneratorMode.INTERNAL_SERVER":
"Internal Server",
+
+ "distanthorizons.config.enum.EDhApiDistantGeneratorProgressDisplayLocation.OVERLAY":
+ "Overlay",
+ "distanthorizons.config.enum.EDhApiDistantGeneratorProgressDisplayLocation.CHAT":
+ "Chat",
+ "distanthorizons.config.enum.EDhApiDistantGeneratorProgressDisplayLocation.LOG":
+ "Log",
+ "distanthorizons.config.enum.EDhApiDistantGeneratorProgressDisplayLocation.DISABLED":
+ "Disabled",
"distanthorizons.config.enum.EDhApiDataCompressionMode.UNCOMPRESSED":
"Uncompressed",