Merge branch 'distant-horizons-core-refactor/world-gen-module'

This commit is contained in:
James Seibel
2023-08-25 20:09:53 -05:00
14 changed files with 239 additions and 158 deletions
@@ -319,6 +319,10 @@ public class ClientApi
if (clientWorld != null)
{
clientWorld.clientTick();
// Ignore local world gen, as it's managed by server ticking
if (!(clientWorld instanceof DhClientServerWorld))
SharedApi.worldGenTick(clientWorld::doWorldGen);
}
profiler.pop();
}
@@ -78,12 +78,7 @@ public class ServerApi
if (serverWorld != null)
{
serverWorld.serverTick();
this.lastWorldGenTickDelta--;
if (this.lastWorldGenTickDelta <= 0)
{
serverWorld.doWorldGen();
this.lastWorldGenTickDelta = 20;
}
SharedApi.worldGenTick(serverWorld::doWorldGen);
}
}
catch (Exception e)
@@ -12,6 +12,7 @@ import com.seibel.distanthorizons.core.world.*;
public class SharedApi
{
private static AbstractDhWorld currentWorld;
private static int lastWorldGenTickDelta = 0;
@@ -52,6 +53,16 @@ public class SharedApi
}
}
public static void worldGenTick(Runnable worldGenRunnable)
{
lastWorldGenTickDelta--;
if (lastWorldGenTickDelta <= 0)
{
worldGenRunnable.run();
lastWorldGenTickDelta = 20;
}
}
public static AbstractDhWorld getAbstractDhWorld() { return currentWorld; }
/** returns null if the {@link SharedApi#currentWorld} isn't a {@link DhClientServerWorld} */
public static DhClientServerWorld getDhClientServerWorld() { return (currentWorld != null && DhClientServerWorld.class.isInstance(currentWorld)) ? (DhClientServerWorld) currentWorld : null; }
@@ -5,11 +5,11 @@ import com.seibel.distanthorizons.core.dataObjects.fullData.sources.CompleteFull
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.interfaces.IFullDataSource;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.interfaces.IIncompleteFullDataSource;
import com.seibel.distanthorizons.core.file.structure.AbstractSaveStructure;
import com.seibel.distanthorizons.core.generation.WorldGenerationQueue;
import com.seibel.distanthorizons.core.generation.IWorldGenerationQueue;
import com.seibel.distanthorizons.core.generation.tasks.IWorldGenTaskTracker;
import com.seibel.distanthorizons.core.generation.tasks.WorldGenResult;
import com.seibel.distanthorizons.core.level.DhLevel;
import com.seibel.distanthorizons.core.level.IDhServerLevel;
import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhLodPos;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
@@ -28,14 +28,14 @@ public class GeneratedFullDataFileHandler extends FullDataFileHandler
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
private final AtomicReference<WorldGenerationQueue> worldGenQueueRef = new AtomicReference<>(null);
private final AtomicReference<IWorldGenerationQueue> worldGenQueueRef = new AtomicReference<>(null);
private final ArrayList<IOnWorldGenCompleteListener> onWorldGenTaskCompleteListeners = new ArrayList<>();
// Use to hold onto incomplete data sources that are waiting for generation, so that they don't get GC'd before they are generated
private final ConcurrentHashMap<DhSectionPos, IIncompleteFullDataSource> incompleteDataSources = new ConcurrentHashMap<>();
public GeneratedFullDataFileHandler(IDhServerLevel level, AbstractSaveStructure saveStructure) { super(level, saveStructure); }
public GeneratedFullDataFileHandler(IDhLevel level, AbstractSaveStructure saveStructure) { super(level, saveStructure); }
@@ -56,7 +56,7 @@ public class GeneratedFullDataFileHandler extends FullDataFileHandler
//==================//
/** Assumes there isn't a pre-existing queue. */
public void setGenerationQueue(WorldGenerationQueue newWorldGenQueue)
public void setGenerationQueue(IWorldGenerationQueue newWorldGenQueue)
{
boolean oldQueueExists = this.worldGenQueueRef.compareAndSet(null, newWorldGenQueue);
LodUtil.assertTrue(oldQueueExists, "previous world gen queue is still here!");
@@ -115,13 +115,13 @@ public class GeneratedFullDataFileHandler extends FullDataFileHandler
@Nullable
private CompletableFuture<IFullDataSource> tryStartGenTask(FullDataMetaFile file, IIncompleteFullDataSource dataSource)
{
WorldGenerationQueue worldGenQueue = this.worldGenQueueRef.get();
IWorldGenerationQueue worldGenQueue = this.worldGenQueueRef.get();
// breaks down the missing positions into the desired detail level that the gen queue could accept
if (worldGenQueue != null && !file.genQueueChecked)
{
DhSectionPos pos = file.pos;
file.genQueueChecked = true;
byte maxSectDataDetailLevel = worldGenQueue.largestDataDetail;
byte maxSectDataDetailLevel = worldGenQueue.largestDataDetail();
byte targetDataDetailLevel = dataSource.getDataDetailLevel();
if (targetDataDetailLevel > maxSectDataDetailLevel)
@@ -222,7 +222,7 @@ public class GeneratedFullDataFileHandler extends FullDataFileHandler
if (source instanceof IIncompleteFullDataSource && !file.genQueueChecked)
{
WorldGenerationQueue worldGenQueue = this.worldGenQueueRef.get();
IWorldGenerationQueue worldGenQueue = this.worldGenQueueRef.get();
if (worldGenQueue != null)
{
CompletableFuture<IFullDataSource> future = this.updateFromExistingDataSources(file, (IIncompleteFullDataSource) source);
@@ -0,0 +1,26 @@
package com.seibel.distanthorizons.core.generation;
import com.seibel.distanthorizons.core.generation.tasks.IWorldGenTaskTracker;
import com.seibel.distanthorizons.core.generation.tasks.WorldGenResult;
import com.seibel.distanthorizons.core.pos.DhBlockPos2D;
import com.seibel.distanthorizons.core.pos.DhLodPos;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import java.io.Closeable;
import java.util.concurrent.CompletableFuture;
public interface IWorldGenerationQueue extends Closeable
{
byte largestDataDetail();
CompletableFuture<WorldGenResult> submitGenTask(DhLodPos pos, byte requiredDataDetail, IWorldGenTaskTracker tracker);
void cancelGenTasks(Iterable<DhSectionPos> positions);
void runCurrentGenTasksUntilBusy(DhBlockPos2D targetPos);
int getWaitingTaskCount();
int getInProgressTaskCount();
CompletableFuture<Void> startClosing(boolean cancelCurrentGeneration, boolean alsoInterruptRunning);
void close();
}
@@ -31,7 +31,7 @@ import java.util.*;
import java.util.concurrent.*;
import java.util.function.Consumer;
public class WorldGenerationQueue implements Closeable, IDebugRenderable
public class WorldGenerationQueue implements IWorldGenerationQueue, IDebugRenderable
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
@@ -51,6 +51,8 @@ public class WorldGenerationQueue implements Closeable, IDebugRenderable
/** largest numerical detail level allowed */
public final byte largestDataDetail;
@Override
public byte largestDataDetail() { return this.largestDataDetail; }
/** lowest numerical detail level allowed */
public final byte smallestDataDetail;
@@ -155,6 +157,11 @@ public class WorldGenerationQueue implements Closeable, IDebugRenderable
//}
}
@Override
public void cancelGenTasks(Iterable<DhSectionPos> positions)
{
// TODO Should anything be here?
}
//===============//
// running tasks //
@@ -76,26 +76,26 @@ public class DhClientServerLevel extends DhLevel implements IDhClientLevel, IDhS
{
serverside.worldGeneratorEnabledConfig.pollNewValue();
boolean shouldDoWorldGen = serverside.worldGeneratorEnabledConfig.get() && clientside.isRendering();
boolean isWorldGenRunning = serverside.isWorldGenRunning();
boolean isWorldGenRunning = serverside.worldGenModule.isWorldGenRunning();
if (shouldDoWorldGen && !isWorldGenRunning)
{
// start world gen
serverside.startWorldGen();
serverside.worldGenModule.startWorldGen(serverside.dataFileHandler, new ServerLevelModule.WorldGenState(this));
}
else if (!shouldDoWorldGen && isWorldGenRunning)
{
// stop world gen
serverside.stopWorldGen();
serverside.worldGenModule.stopWorldGen(serverside.dataFileHandler);
}
if (serverside.isWorldGenRunning())
if (serverside.worldGenModule.isWorldGenRunning())
{
ClientLevelModule.ClientRenderState renderState = clientside.ClientRenderStateRef.get();
if (renderState != null && renderState.quadtree != null)
{
serverside.dataFileHandler.removeGenRequestIf(p -> !renderState.quadtree.isSectionPosInBounds(p));
}
serverside.worldGenTick(new DhBlockPos2D(MC_CLIENT.getPlayerBlockPos()));
serverside.worldGenModule.worldGenTick(new DhBlockPos2D(MC_CLIENT.getPlayerBlockPos()));
}
}
@@ -67,21 +67,21 @@ public class DhServerLevel extends DhLevel implements IDhServerLevel
public void doWorldGen()
{
boolean shouldDoWorldGen = true; //todo;
boolean isWorldGenRunning = serverside.isWorldGenRunning();
boolean isWorldGenRunning = serverside.worldGenModule.isWorldGenRunning();
if (shouldDoWorldGen && !isWorldGenRunning)
{
// start world gen
serverside.startWorldGen();
serverside.worldGenModule.startWorldGen(serverside.dataFileHandler, new ServerLevelModule.WorldGenState(this));
}
else if (!shouldDoWorldGen && isWorldGenRunning)
{
// stop world gen
serverside.stopWorldGen();
serverside.worldGenModule.stopWorldGen(serverside.dataFileHandler);
}
if (serverside.isWorldGenRunning())
if (serverside.worldGenModule.isWorldGenRunning())
{
serverside.worldGenTick(new DhBlockPos2D(0, 0)); // todo;
serverside.worldGenModule.worldGenTick(new DhBlockPos2D(0, 0)); // todo;
}
}
@@ -3,11 +3,9 @@ package com.seibel.distanthorizons.core.level;
import com.seibel.distanthorizons.core.file.fullDatafile.GeneratedFullDataFileHandler;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
public interface IDhServerLevel extends IDhLevel, GeneratedFullDataFileHandler.IOnWorldGenCompleteListener
public interface IDhServerLevel extends IDhWorldGenLevel
{
void serverTick();
void doWorldGen();
IServerLevelWrapper getServerLevelWrapper();
void serverTick();
IServerLevelWrapper getServerLevelWrapper();
}
@@ -0,0 +1,9 @@
package com.seibel.distanthorizons.core.level;
import com.seibel.distanthorizons.core.file.fullDatafile.GeneratedFullDataFileHandler;
public interface IDhWorldGenLevel extends IDhLevel, GeneratedFullDataFileHandler.IOnWorldGenCompleteListener
{
void doWorldGen();
}
@@ -8,15 +8,10 @@ import com.seibel.distanthorizons.core.file.structure.AbstractSaveStructure;
import com.seibel.distanthorizons.core.generation.BatchGenerator;
import com.seibel.distanthorizons.core.generation.WorldGenerationQueue;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.logging.f3.F3Screen;
import com.seibel.distanthorizons.core.pos.DhBlockPos2D;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
import com.seibel.distanthorizons.coreapi.DependencyInjection.WorldGeneratorInjector;
import org.apache.logging.log4j.Logger;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicReference;
public class ServerLevelModule
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
@@ -27,8 +22,7 @@ public class ServerLevelModule
public final GeneratedFullDataFileHandler dataFileHandler;
public final AppliedConfigState<Boolean> worldGeneratorEnabledConfig;
private final AtomicReference<WorldGenState> worldGenStateRef = new AtomicReference<>();
private final F3Screen.DynamicMessage worldGenF3Message;
public final WorldGenModule worldGenModule;
@@ -39,109 +33,17 @@ public class ServerLevelModule
this.saveStructure = saveStructure;
this.dataFileHandler = new GeneratedFullDataFileHandler(parent, saveStructure);
this.worldGeneratorEnabledConfig = new AppliedConfigState<>(Config.Client.Advanced.WorldGenerator.enableDistantGeneration);
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";
}
});
this.worldGenModule = new WorldGenModule(this.dataFileHandler, this.parent);
}
//==============//
// tick methods //
//==============//
public void startWorldGen()
{
// create the new world generator
WorldGenState newWgs = new WorldGenState(parent);
if (!this.worldGenStateRef.compareAndSet(null, newWgs))
{
LOGGER.warn("Failed to start world gen due to concurrency");
newWgs.closeAsync(false);
}
dataFileHandler.addWorldGenCompleteListener(parent);
dataFileHandler.setGenerationQueue(newWgs.worldGenerationQueue);
}
public void stopWorldGen()
{
WorldGenState worldGenState = this.worldGenStateRef.get();
if (worldGenState == null)
{
LOGGER.warn("Attempted to stop world gen when it was not running");
return;
}
// shut down the world generator
while (!this.worldGenStateRef.compareAndSet(worldGenState, null))
{
worldGenState = this.worldGenStateRef.get();
if (worldGenState == null)
{
return;
}
}
dataFileHandler.clearGenerationQueue();
worldGenState.closeAsync(true).join(); //TODO: Make it async.
dataFileHandler.removeWorldGenCompleteListener(parent);
}
public boolean isWorldGenRunning()
{
return this.worldGenStateRef.get() != null;
}
public void worldGenTick(DhBlockPos2D targetPosForGeneration)
{
WorldGenState worldGenState = this.worldGenStateRef.get();
if (worldGenState != null)
{
// queue new world generation requests
worldGenState.tick(targetPosForGeneration);
}
}
//===============//
// data handling //
//===============//
public void close()
{
// shutdown the world-gen
WorldGenState worldGenState = this.worldGenStateRef.get();
if (worldGenState != null)
{
while (!this.worldGenStateRef.compareAndSet(worldGenState, null))
{
worldGenState = this.worldGenStateRef.get();
if (worldGenState == null)
{
break;
}
}
if (worldGenState != null)
{
worldGenState.closeAsync(true).join(); //TODO: Make it async.
}
}
this.dataFileHandler.close();
this.worldGenF3Message.close();
this.worldGenModule.close();
dataFileHandler.close();
}
@@ -150,9 +52,8 @@ public class ServerLevelModule
// helper classes //
//================//
private static class WorldGenState
public static class WorldGenState extends WorldGenModule.WorldGenState
{
public final WorldGenerationQueue worldGenerationQueue;
WorldGenState(IDhServerLevel level)
{
IDhApiWorldGenerator worldGenerator = WorldGeneratorInjector.INSTANCE.get(level.getLevelWrapper());
@@ -167,27 +68,6 @@ public class ServerLevelModule
this.worldGenerationQueue = new WorldGenerationQueue(worldGenerator);
}
CompletableFuture<Void> closeAsync(boolean doInterrupt)
{
return this.worldGenerationQueue.startClosing(true, doInterrupt)
.exceptionally(ex ->
{
LOGGER.error("Error closing generation queue", ex);
return null;
}
).thenRun(this.worldGenerationQueue::close)
.exceptionally(ex ->
{
LOGGER.error("Error closing world gen", ex);
return null;
});
}
public void tick(DhBlockPos2D targetPosForGeneration)
{
worldGenerationQueue.runCurrentGenTasksUntilBusy(targetPosForGeneration);
}
}
}
@@ -0,0 +1,145 @@
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;
import java.io.Closeable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicReference;
public class WorldGenModule implements Closeable
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
private final GeneratedFullDataFileHandler dataFileHandler;
private final GeneratedFullDataFileHandler.IOnWorldGenCompleteListener onWorldGenCompleteListener;
private final AtomicReference<WorldGenState> worldGenStateRef = new AtomicReference<>();
private final F3Screen.DynamicMessage worldGenF3Message;
public static abstract class WorldGenState
{
public IWorldGenerationQueue worldGenerationQueue;
CompletableFuture<Void> closeAsync(boolean doInterrupt)
{
return this.worldGenerationQueue.startClosing(true, doInterrupt)
.exceptionally(ex ->
{
LOGGER.error("Error closing generation queue", ex);
return null;
}
).thenRun(this.worldGenerationQueue::close)
.exceptionally(ex ->
{
LOGGER.error("Error closing world gen", ex);
return null;
});
}
public void tick(DhBlockPos2D targetPosForGeneration)
{
worldGenerationQueue.runCurrentGenTasksUntilBusy(targetPosForGeneration);
}
}
public WorldGenModule(GeneratedFullDataFileHandler dataFileHandler, GeneratedFullDataFileHandler.IOnWorldGenCompleteListener onWorldGenCompleteListener)
{
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)
{
// create the new world generator
if (!this.worldGenStateRef.compareAndSet(null, newWgs))
{
LOGGER.warn("Failed to start world gen due to concurrency");
newWgs.closeAsync(false);
}
dataFileHandler.addWorldGenCompleteListener(onWorldGenCompleteListener);
dataFileHandler.setGenerationQueue(newWgs.worldGenerationQueue);
}
public void stopWorldGen(GeneratedFullDataFileHandler dataFileHandler)
{
WorldGenState worldGenState = this.worldGenStateRef.get();
if (worldGenState == null)
{
LOGGER.warn("Attempted to stop world gen when it was not running");
return;
}
// shut down the world generator
while (!this.worldGenStateRef.compareAndSet(worldGenState, null))
{
worldGenState = this.worldGenStateRef.get();
if (worldGenState == null)
{
return;
}
}
dataFileHandler.clearGenerationQueue();
worldGenState.closeAsync(true).join(); //TODO: Make it async.
dataFileHandler.removeWorldGenCompleteListener(onWorldGenCompleteListener);
}
public boolean isWorldGenRunning()
{
return this.worldGenStateRef.get() != null;
}
public void worldGenTick(DhBlockPos2D targetPosForGeneration)
{
WorldGenState worldGenState = this.worldGenStateRef.get();
if (worldGenState != null)
{
// queue new world generation requests
worldGenState.tick(targetPosForGeneration);
}
}
public void close()
{
// shutdown the world-gen
WorldGenState worldGenState = this.worldGenStateRef.get();
if (worldGenState != null)
{
while (!this.worldGenStateRef.compareAndSet(worldGenState, null))
{
worldGenState = this.worldGenStateRef.get();
if (worldGenState == null)
{
break;
}
}
if (worldGenState != null)
{
worldGenState.closeAsync(true).join(); //TODO: Make it async.
}
}
dataFileHandler.close();
this.worldGenF3Message.close();
}
}
@@ -149,8 +149,12 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
public void clientTick() { this.eventLoop.tick(); }
@Override
public CompletableFuture<Void> saveAndFlush()
public void doWorldGen() {
// Not implemented
}
@Override
public CompletableFuture<Void> saveAndFlush()
{
return CompletableFuture.allOf(this.levels.values().stream().map(DhClientLevel::saveAsync).toArray(CompletableFuture[]::new));
}
@@ -7,6 +7,8 @@ public interface IDhClientWorld extends IDhWorld
{
void clientTick();
void doWorldGen();
default IDhClientLevel getOrLoadClientLevel(ILevelWrapper levelWrapper) { return (IDhClientLevel) this.getOrLoadLevel(levelWrapper); }
}