Add world gen progress updates to the overlay

This commit is contained in:
James Seibel
2025-01-07 19:18:21 -06:00
parent 06b8f88403
commit 036b42d197
15 changed files with 371 additions and 58 deletions
@@ -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 <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.api.enums.worldGeneration;
/**
* OVERLAY <br>
* CHAT <br>
* LOG <br>
* DISABLED <br><br>
*
* @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;
}
@@ -99,6 +99,7 @@ public class ClientApi
private long lastStaticWarningMessageSentMsTime = 0L;
private final Queue<String> chatMessageQueueForNextFrame = new LinkedBlockingQueue<>();
private final Queue<String> 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); }
}
@@ -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<EDhApiDistantGeneratorMode> distantGeneratorMode = new ConfigEntry.Builder<EDhApiDistantGeneratorMode>()
@@ -1237,6 +1241,25 @@ public class Config
+ "")
.build();
public static ConfigEntry<EDhApiDistantGeneratorProgressDisplayLocation> showGenerationProgress = new ConfigEntry.Builder<EDhApiDistantGeneratorProgressDisplayLocation>()
.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<Integer> generationProgressDisplayIntervalInSeconds = new ConfigEntry.Builder<Integer>()
.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
@@ -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<WorldGenResult> 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
@@ -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);
}
//=======//
@@ -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<String> messageList);
/** Can be used to determine roughly how fast the world generator is running. */
RollingAverage getRollingAverageChunkGenTimeInMs();
}
@@ -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<WorldGenResult> 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; }
}
@@ -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<Void> 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<Void> startGenerationEvent(
long requestPos,
byte targetDataDetail,
int generationRequestChunkWidthCount,
Consumer<FullDataSourceV2> 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<String> messageList) { }
@Override
public int getRetrievalEstimatedRemainingChunkCount() { return this.estimatedRemainingChunkCount; }
@Override
public void setRetrievalEstimatedRemainingChunkCount(int newEstimate) { this.estimatedRemainingChunkCount = newEstimate; }
@Override
public void addDebugMenuStringsToList(List<String> 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;
}
@@ -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<Void> 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;
}
}
}
@@ -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));
@@ -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)); }
@@ -628,22 +628,33 @@ public class LodQuadTree extends QuadTree<LodRenderSection> 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)
{
@@ -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()
{
@@ -100,6 +100,8 @@ public interface IMinecraftClientWrapper extends IBindable
ArrayList<ILevelWrapper> 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)
@@ -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",