From ec19e432169d498ea5673c61e18434b111e14324 Mon Sep 17 00:00:00 2001 From: s809 <43530948+s809@users.noreply.github.com> Date: Sat, 11 Jan 2025 02:55:04 +0500 Subject: [PATCH] Add pregen command --- .../distanthorizons/core/config/Config.java | 11 ++ .../core/generation/PregenManager.java | 184 ++++++++++++++++++ .../core/level/DhServerLevel.java | 13 +- 3 files changed, 207 insertions(+), 1 deletion(-) create mode 100644 core/src/main/java/com/seibel/distanthorizons/core/generation/PregenManager.java 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 6b3e064ad..231a13fe4 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 @@ -1623,6 +1623,17 @@ public class Config .build(); + // Pregen + public static ConfigEntry pregenLogIntervalSeconds = new ConfigEntry.Builder() + .setChatCommandName("pregen.logInterval") + .setMinDefaultMax(0, 5, 300 /* 5 minutes */) + .comment("" + + "Interval between pre-generation log messages, in seconds.\n" + + "Value of 0 will log at every generation task." + + "") + .build(); + + // Common public static ConfigEntry maxDataTransferSpeed = new ConfigEntry.Builder() .setChatCommandName("common.maxDataTransferSpeed") diff --git a/core/src/main/java/com/seibel/distanthorizons/core/generation/PregenManager.java b/core/src/main/java/com/seibel/distanthorizons/core/generation/PregenManager.java new file mode 100644 index 000000000..7b810b737 --- /dev/null +++ b/core/src/main/java/com/seibel/distanthorizons/core/generation/PregenManager.java @@ -0,0 +1,184 @@ +package com.seibel.distanthorizons.core.generation; + +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.RemovalCause; +import com.seibel.distanthorizons.core.api.internal.SharedApi; +import com.seibel.distanthorizons.core.config.Config; +import com.seibel.distanthorizons.core.file.fullDatafile.GeneratedFullDataSourceProvider; +import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; +import com.seibel.distanthorizons.core.pos.DhSectionPos; +import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D; +import com.seibel.distanthorizons.core.util.LodUtil; +import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper; +import org.apache.logging.log4j.Logger; + +import java.util.Collections; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; + +public class PregenManager +{ + protected static final Logger LOGGER = DhLoggerBuilder.getLogger(); + + private final AtomicReference> pregenFuture = new AtomicReference<>(); + + + public CompletableFuture startPregen( + IServerLevelWrapper levelWrapper, + DhBlockPos2D origin, + int chunkRadius, + Consumer progressUpdater + ) + { + PregenState pregenState = new PregenState( + (GeneratedFullDataSourceProvider) SharedApi.getIDhServerWorld().getLevel(levelWrapper).getFullDataProvider(), + DhSectionPos.convertToDetailLevel( + DhSectionPos.encode(LodUtil.BLOCK_DETAIL_LEVEL, origin.x, origin.z), + DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL + ), + (int) Math.pow(Math.ceil((double) chunkRadius / 4 * 2), 2), + progressUpdater + ); + + if (!this.pregenFuture.compareAndSet(null, pregenState)) + { + pregenState.completeExceptionally(new IllegalStateException("Pregen is already running.")); + return pregenState; + } + pregenState.whenComplete((result, throwable) -> { + this.pregenFuture.set(null); + }); + + pregenState.fillPendingQueue(); + return pregenState; + } + + public CompletableFuture getRunningPregen() + { + return this.pregenFuture.get(); + } + + + private static class PregenState extends CompletableFuture + { + private final GeneratedFullDataSourceProvider fullDataSourceProvider; + private final long originSectionPos; + private final int sectionsToGenerate; + private final Consumer progressUpdater; + + private final AtomicInteger nextSectionSpiralIndex = new AtomicInteger(0); + private final AtomicLong lastLogTime = new AtomicLong(); + + private final Set pendingGenerations = Collections.newSetFromMap(CacheBuilder.newBuilder() + .expireAfterWrite(2, TimeUnit.MINUTES) + .removalListener(removalNotification -> { + if (removalNotification.getCause() == RemovalCause.EXPIRED) + { + //noinspection DataFlowIssue + LOGGER.warn("Generation for section " + DhSectionPos.toString(removalNotification.getKey()) + " has expired!"); + } + }) + .build().asMap()); + + + public PregenState(GeneratedFullDataSourceProvider fullDataSourceProvider, long originSectionPos, int sectionsToGenerate, Consumer progressUpdater) + { + this.fullDataSourceProvider = fullDataSourceProvider; + this.originSectionPos = originSectionPos; + this.sectionsToGenerate = sectionsToGenerate; + this.progressUpdater = progressUpdater; + } + + + private void fillPendingQueue() + { + while (!this.isDone() && this.pendingGenerations.size() < Config.Common.MultiThreading.numberOfThreads.get()) + { + int nextSpiralIndex = this.nextSectionSpiralIndex.getAndIncrement(); + if (nextSpiralIndex > this.sectionsToGenerate) + { + this.complete(null); + return; + } + + long nextSectionPos = this.sectionPosOnSpiral(nextSpiralIndex); + + long lastLogTime = this.lastLogTime.get(); + if (Config.Server.pregenLogIntervalSeconds.get() == 0 + || (System.currentTimeMillis() - lastLogTime > TimeUnit.SECONDS.toMillis(Config.Server.pregenLogIntervalSeconds.get()) && this.lastLogTime.compareAndSet(lastLogTime, System.currentTimeMillis())) + ) + { + this.progressUpdater.accept("Next section: " + DhSectionPos.toString(nextSectionPos) + ", generated: " + nextSpiralIndex + " / " + this.sectionsToGenerate); + } + + this.pendingGenerations.add(nextSectionPos); + this.fullDataSourceProvider.getAsync(nextSectionPos).thenAccept(fullDataSource -> { + if (this.fullDataSourceProvider.isFullyGenerated(fullDataSource.columnGenerationSteps)) + { + this.pendingGenerations.remove(fullDataSource.getPos()); + PregenState.this.fillPendingQueue(); + } + else + { + this.fullDataSourceProvider.queuePositionForRetrieval(fullDataSource.getPos()).thenAccept(result -> { + if (result.success) + { + this.pendingGenerations.remove(fullDataSource.getPos()); + PregenState.this.fillPendingQueue(); + } + else + { + LOGGER.warn("Failed to generate section " + DhSectionPos.toString(result.pos)); + } + }); + } + + fullDataSource.close(); + }); + } + } + + private long sectionPosOnSpiral(int pos) + { + if (pos == 0) + { + return this.originSectionPos; + } + pos--; + + int ringNumber = 1; + while (pos >= ringNumber * 8) + { + pos -= ringNumber * 8; + ringNumber++; + } + + // 0 <= pos <= (ringNumber * 8) - 1 + // ringNumber * 8 - full ring + // ringNumber * 4 - half-ring + // ringNumber * 2 - quarter-ring, i.e. one side of it + // ringNumber - half of quarter-ring + + int x = -ringNumber + 1 + Math.min(pos % (ringNumber * 4), ringNumber * 2 - 1); + int z = ringNumber - Math.max(0, pos % (ringNumber * 4) - ringNumber * 2 + 1); + + if (pos >= ringNumber * 4) + { + x = -x; + z = -z; + } + + x += DhSectionPos.getX(this.originSectionPos); + z += DhSectionPos.getZ(this.originSectionPos); + + return DhSectionPos.encode(DhSectionPos.getDetailLevel(this.originSectionPos), x, z); + } + + } + +} 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 ecd3fcfb0..c9d4cead1 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 @@ -21,9 +21,11 @@ package com.seibel.distanthorizons.core.level; import com.seibel.distanthorizons.core.file.structure.ISaveStructure; import com.seibel.distanthorizons.core.multiplayer.server.ServerPlayerStateManager; +import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D; import com.seibel.distanthorizons.core.render.RenderBufferHandler; import com.seibel.distanthorizons.core.render.renderer.generic.GenericObjectRenderer; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper; +import org.jetbrains.annotations.Nullable; import java.util.List; @@ -49,7 +51,16 @@ public class DhServerLevel extends AbstractDhServerLevel { return true; //todo; } - + @Override + public @Nullable DhBlockPos2D getTargetPosForGeneration() + { + DhBlockPos2D targetPos = super.getTargetPosForGeneration(); + if (targetPos == null) + { + return DhBlockPos2D.ZERO; + } + return targetPos; + } //=========//