Refactor the DhLevel class structure.

Note: Known issue where sections need reload before gen is queued up. Will fix next
This commit is contained in:
TomTheFurry
2023-06-18 00:23:51 +08:00
parent 1c65d030b1
commit fb77c766a6
17 changed files with 750 additions and 729 deletions
@@ -47,9 +47,7 @@ public class FullDataMetaFile extends AbstractMetaDataContainerFile
*/
private SoftReference<IFullDataSource> cachedFullDataSource = new SoftReference<>(null);
private CompletableFuture<IFullDataSource> dataSourceWriteQueueFuture;
//TODO: use ConcurrentAppendSingleSwapContainer<LodDataSource> instead of below:
private static class GuardedMultiAppendQueue
{
@@ -137,7 +135,20 @@ public class FullDataMetaFile extends AbstractMetaDataContainerFile
//==========//
// get data //
//==========//
// Try get cached data source. Used for temp impl for re-queueing world gen tasks.
// (Read-only access! As writes should always be done async)
public IFullDataSource getCachedDataSourceNowOrNull()
{
debugPhantomLifeCycleCheck();
return this.cachedFullDataSource.get();
}
public CompletableFuture<IFullDataSource> forceReload() {
cachedFullDataSource = new SoftReference<>(null);
return loadOrGetCachedDataSourceAsync();
}
// Cause: Generic Type runtime casting cannot safety check it.
// However, the Union type ensures the 'data' should only contain the listed type.
public CompletableFuture<IFullDataSource> loadOrGetCachedDataSourceAsync()
@@ -70,6 +70,16 @@ public class GeneratedFullDataFileHandler extends FullDataFileHandler
{
boolean oldQueueExists = this.worldGenQueueRef.compareAndSet(null, newWorldGenQueue);
LodUtil.assertTrue(oldQueueExists, "previous world gen queue is still here!");
for (FullDataMetaFile metaFile : this.fileBySectionPos.values())
{
IFullDataSource data = metaFile.getCachedDataSourceNowOrNull();
if (data instanceof IIncompleteFullDataSource) {
//todo
//metaFile.flushAndSaveAsync().thenApply(() -> metaFile.forceReload());
}
}
}
public void clearGenerationQueue() { this.worldGenQueueRef.set(null); }
@@ -101,8 +111,6 @@ public class GeneratedFullDataFileHandler extends FullDataFileHandler
// confirm the quad tree has at least one node in it
LodUtil.assertTrue(!missingPositions.isEmpty() || !existingFiles.isEmpty());
// determine the type of dataSource that should be used for this position
IIncompleteFullDataSource incompleteFullDataSource;
if (data == null)
@@ -121,31 +129,31 @@ public class GeneratedFullDataFileHandler extends FullDataFileHandler
incompleteFullDataSource = data;
}
WorldGenerationQueue worldGenQueue = this.worldGenQueueRef.get();
// breaks down the missing positions into the desired detail level that the gen queue could accept
byte maxSectDataDetailLevel = worldGenQueueRef.get().largestDataDetail;
byte targetDataDetailLevel = incompleteFullDataSource.getDataDetailLevel();
if (targetDataDetailLevel > maxSectDataDetailLevel) {
byte sectDetailLevel = (byte) (DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL + maxSectDataDetailLevel);
missingPositions = missingPositions.stream()
.flatMap(missingPos -> {
if (missingPos.sectionDetailLevel > sectDetailLevel) {
// split this position into smaller positions
ArrayList<DhSectionPos> splitPositions = new ArrayList<>();
missingPos.forEachChildAtLevel(sectDetailLevel, splitPositions::add);
return splitPositions.stream();
}
else {
return Stream.of(missingPos);
}
})
.collect(Collectors.toCollection(ArrayList::new));
if (worldGenQueue != null) {
byte maxSectDataDetailLevel = worldGenQueue.largestDataDetail;
byte targetDataDetailLevel = incompleteFullDataSource.getDataDetailLevel();
if (targetDataDetailLevel > maxSectDataDetailLevel) {
byte sectDetailLevel = (byte) (DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL + maxSectDataDetailLevel);
missingPositions = missingPositions.stream()
.flatMap(missingPos -> {
if (missingPos.sectionDetailLevel > sectDetailLevel) {
// split this position into smaller positions
ArrayList<DhSectionPos> splitPositions = new ArrayList<>();
missingPos.forEachChildAtLevel(sectDetailLevel, splitPositions::add);
return splitPositions.stream();
} else {
return Stream.of(missingPos);
}
})
.collect(Collectors.toCollection(ArrayList::new));
}
}
if (missingPositions.size() == 1 && existingFiles.isEmpty() && missingPositions.get(0).equals(pos))
{
// No LOD data exists for this position yet
WorldGenerationQueue worldGenQueue = this.worldGenQueueRef.get();
if (worldGenQueue != null)
{
this.incompleteSourceGenRequests.add(pos);
@@ -1,286 +0,0 @@
package com.seibel.lod.core.level;
import com.seibel.lod.core.config.Config;
import com.seibel.lod.core.dataObjects.fullData.accessor.ChunkSizedFullDataAccessor;
import com.seibel.lod.core.dataObjects.fullData.sources.CompleteFullDataSource;
import com.seibel.lod.core.dataObjects.transformers.ChunkToLodBuilder;
import com.seibel.lod.core.dependencyInjection.SingletonInjector;
import com.seibel.lod.core.file.fullDatafile.FullDataFileHandler;
import com.seibel.lod.core.file.fullDatafile.IFullDataSourceProvider;
import com.seibel.lod.core.file.fullDatafile.RemoteFullDataFileHandler;
import com.seibel.lod.core.file.structure.AbstractSaveStructure;
import com.seibel.lod.core.level.states.ClientRenderState;
import com.seibel.lod.core.logging.DhLoggerBuilder;
import com.seibel.lod.core.pos.DhBlockPos2D;
import com.seibel.lod.core.pos.DhLodPos;
import com.seibel.lod.core.pos.DhSectionPos;
import com.seibel.lod.core.util.FileScanUtil;
import com.seibel.lod.core.util.LodUtil;
import com.seibel.lod.coreapi.util.math.Mat4f;
import com.seibel.lod.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IProfilerWrapper;
import com.seibel.lod.core.wrapperInterfaces.world.ILevelWrapper;
import org.apache.logging.log4j.Logger;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicReference;
/**
* This contains code that is shared between {@link DhClientLevel} {@link DhClientServerLevel}
*/
public abstract class AbstractDhClientLevel implements IDhClientLevel
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
public final AbstractSaveStructure saveStructure;
public final ChunkToLodBuilder chunkToLodBuilder;
public FullDataFileHandler fullDataFileHandler;
public final AtomicReference<ClientRenderState> ClientRenderStateRef = new AtomicReference<>();
//=============//
// constructor //
//=============//
public AbstractDhClientLevel(AbstractSaveStructure saveStructure, ILevelWrapper levelWrapper)
{
this.saveStructure = saveStructure;
if (this.saveStructure.getFullDataFolder(levelWrapper).mkdirs())
{
LOGGER.warn("unable to create full data folder.");
}
if (this.saveStructure.getRenderCacheFolder(levelWrapper).mkdirs())
{
LOGGER.warn("unable to create cache folder.");
}
this.fullDataFileHandler = new RemoteFullDataFileHandler(this, this.saveStructure.getFullDataFolder(levelWrapper));
FileScanUtil.scanFiles(saveStructure, levelWrapper, this.fullDataFileHandler, null);
this.chunkToLodBuilder = new ChunkToLodBuilder();
}
//==============//
// tick methods //
//==============//
/**
* Includes logic used by both {@link DhClientServerLevel} and {@link DhClientServerLevel}
* @return whether the tick method completed
*/
protected boolean baseClientTick()
{
ClientRenderState clientRenderState = this.ClientRenderStateRef.get();
if (clientRenderState == null)
{
return false;
}
// TODO this should probably be handled via a config change listener
// recreate the RenderState if the render distance changes
if (clientRenderState.quadtree.blockRenderDistanceRadius != Config.Client.Advanced.Graphics.Quality.lodChunkRenderDistance.get() * LodUtil.CHUNK_WIDTH)
{
if (!this.ClientRenderStateRef.compareAndSet(clientRenderState, null))
{
return false; //If we fail, we'll just wait for the next tick
}
clientRenderState.closeAsync().join(); //TODO: Make it async.
clientRenderState = new ClientRenderState(this, this.fullDataFileHandler, this.saveStructure);
if (!this.ClientRenderStateRef.compareAndSet(null, clientRenderState))
{
//FIXME: How to handle this?
LOGGER.warn("Failed to set render state due to concurrency after changing view distance");
clientRenderState.closeAsync();
return false;
}
}
clientRenderState.quadtree.tick(new DhBlockPos2D(MC_CLIENT.getPlayerBlockPos()));
clientRenderState.renderer.bufferHandler.updateQuadTreeRenderSources();
return true;
}
//========//
// render //
//========//
/** @return if the {@link ClientRenderState} was successfully swapped */
protected boolean setAndStartRenderer()
{
ClientRenderState ClientRenderState = new ClientRenderState(this, this.fullDataFileHandler, this.saveStructure);
if (!this.ClientRenderStateRef.compareAndSet(null, ClientRenderState))
{
LOGGER.warn("Failed to start renderer due to concurrency");
ClientRenderState.closeAsync();
return false;
}
else
{
return true;
}
}
@Override
public void render(Mat4f mcModelViewMatrix, Mat4f mcProjectionMatrix, float partialTicks, IProfilerWrapper profiler)
{
ClientRenderState ClientRenderState = this.ClientRenderStateRef.get();
if (ClientRenderState == null)
{
// either the renderer hasn't been started yet, or is being reloaded
return;
}
ClientRenderState.renderer.drawLODs(mcModelViewMatrix, mcProjectionMatrix, partialTicks, profiler);
}
public void stopRenderer()
{
LOGGER.info("Stopping renderer for "+this);
ClientRenderState ClientRenderState = this.ClientRenderStateRef.get();
if (ClientRenderState == null)
{
LOGGER.warn("Tried to stop renderer for "+this+" when it was not started!");
return;
}
// stop the render state
while (!this.ClientRenderStateRef.compareAndSet(ClientRenderState, null)) // TODO why is there a while loop here?
{
ClientRenderState = this.ClientRenderStateRef.get();
if (ClientRenderState == null)
{
return;
}
}
ClientRenderState.closeAsync().join(); //TODO: Make it async.
}
//===============//
// data handling //
//===============//
@Override
public void updateChunkAsync(IChunkWrapper chunk)
{
CompletableFuture<ChunkSizedFullDataAccessor> future = this.chunkToLodBuilder.tryGenerateData(chunk);
if (future != null)
{
future.thenAccept(this::saveWrites);
}
}
private void saveWrites(ChunkSizedFullDataAccessor data)
{
ClientRenderState ClientRenderState = this.ClientRenderStateRef.get();
DhLodPos pos = data.getLodPos().convertToDetailLevel(CompleteFullDataSource.SECTION_SIZE_OFFSET);
if (ClientRenderState != null)
{
ClientRenderState.renderSourceFileHandler.writeChunkDataToFile(new DhSectionPos(pos.detailLevel, pos.x, pos.z), data);
}
else
{
this.fullDataFileHandler.write(new DhSectionPos(pos.detailLevel, pos.x, pos.z), data);
}
}
@Override
public CompletableFuture<Void> saveAsync()
{
ClientRenderState ClientRenderState = this.ClientRenderStateRef.get();
if (ClientRenderState != null)
{
return ClientRenderState.renderSourceFileHandler.flushAndSaveAsync().thenCombine(this.fullDataFileHandler.flushAndSave(), (voidA, voidB) -> null);
}
else
{
return this.fullDataFileHandler.flushAndSave();
}
}
/** Includes logic used by both {@link DhClientServerLevel} and {@link DhClientServerLevel} */
protected void baseClose()
{
// shut down to prevent reading/writing files after the client has left the world
this.fullDataFileHandler.close();
// clear the chunk builder to prevent generating LODs for chunks that are unloaded
this.chunkToLodBuilder.close();
// shutdown the renderer
ClientRenderState ClientRenderState = this.ClientRenderStateRef.get();
if (ClientRenderState != null)
{
// TODO does this have to be in a while loop, if so why?
while (!this.ClientRenderStateRef.compareAndSet(ClientRenderState, null))
{
ClientRenderState = this.ClientRenderStateRef.get();
if (ClientRenderState == null)
{
break;
}
}
if (ClientRenderState != null)
{
ClientRenderState.closeAsync().join(); //TODO: Make this async.
}
}
}
//=======================//
// misc helper functions //
//=======================//
@Override
public void dumpRamUsage()
{
//TODO
}
/** Returns what should be displayed in Minecraft's F3 debug menu */
protected String[] f3Log()
{
String dimName = this.getLevelWrapper().getDimensionType().getDimensionName();
ClientRenderState renderState = this.ClientRenderStateRef.get();
if (renderState == null)
{
return new String[] { "level @ "+dimName+": Inactive" };
}
else
{
return new String[] { "level @ "+dimName+": Active" };
}
}
@Override
public IFullDataSourceProvider getFileHandler() { return this.fullDataFileHandler; }
@Override
public void clearRenderDataCache()
{
ClientRenderState ClientRenderState = this.ClientRenderStateRef.get();
if (ClientRenderState != null && ClientRenderState.quadtree != null)
{
ClientRenderState.quadtree.clearRenderDataCache();
}
}
}
@@ -0,0 +1,271 @@
package com.seibel.lod.core.level;
import com.seibel.lod.core.config.Config;
import com.seibel.lod.core.dataObjects.fullData.accessor.ChunkSizedFullDataAccessor;
import com.seibel.lod.core.dataObjects.fullData.sources.CompleteFullDataSource;
import com.seibel.lod.core.dependencyInjection.SingletonInjector;
import com.seibel.lod.core.file.fullDatafile.IFullDataSourceProvider;
import com.seibel.lod.core.file.renderfile.RenderSourceFileHandler;
import com.seibel.lod.core.file.structure.AbstractSaveStructure;
import com.seibel.lod.core.logging.DhLoggerBuilder;
import com.seibel.lod.core.logging.f3.F3Screen;
import com.seibel.lod.core.pos.DhBlockPos2D;
import com.seibel.lod.core.pos.DhLodPos;
import com.seibel.lod.core.pos.DhSectionPos;
import com.seibel.lod.core.render.LodQuadTree;
import com.seibel.lod.core.render.RenderBufferHandler;
import com.seibel.lod.core.render.renderer.LodRenderer;
import com.seibel.lod.core.util.FileScanUtil;
import com.seibel.lod.core.util.LodUtil;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IProfilerWrapper;
import com.seibel.lod.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.lod.coreapi.util.math.Mat4f;
import org.apache.logging.log4j.Logger;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicReference;
public class ClientLevelModule {
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
private final IDhClientLevel parent;
public final AtomicReference<ClientRenderState> ClientRenderStateRef = new AtomicReference<>();
public final F3Screen.NestedMessage f3Message;
public ClientLevelModule(IDhClientLevel parent)
{
this.parent = parent;
this.f3Message = new F3Screen.NestedMessage(this::f3Log);
if (parent.getSaveStructure().getRenderCacheFolder(parent.getLevelWrapper()).mkdirs())
{
LOGGER.warn("unable to create cache folder.");
}
}
//==============//
// tick methods //
//==============//
public void clientTick()
{
ClientRenderState clientRenderState = this.ClientRenderStateRef.get();
if (clientRenderState == null)
{
return;
}
// TODO this should probably be handled via a config change listener
// recreate the RenderState if the render distance changes
if (clientRenderState.quadtree.blockRenderDistanceRadius != Config.Client.Advanced.Graphics.Quality.lodChunkRenderDistance.get() * LodUtil.CHUNK_WIDTH)
{
if (!this.ClientRenderStateRef.compareAndSet(clientRenderState, null))
{
return;
}
clientRenderState.closeAsync().join(); //TODO: Make it async.
clientRenderState = new ClientRenderState(parent, parent.getFileHandler(), parent.getSaveStructure());
if (!this.ClientRenderStateRef.compareAndSet(null, clientRenderState))
{
//FIXME: How to handle this?
LOGGER.warn("Failed to set render state due to concurrency after changing view distance");
clientRenderState.closeAsync();
return;
}
}
clientRenderState.quadtree.tick(new DhBlockPos2D(MC_CLIENT.getPlayerBlockPos()));
clientRenderState.renderer.bufferHandler.updateQuadTreeRenderSources();
}
//========//
// render //
//========//
/** @return if the {@link ClientRenderState} was successfully swapped */
public boolean startRenderer()
{
ClientRenderState ClientRenderState = new ClientRenderState(parent, parent.getFileHandler(), parent.getSaveStructure());
if (!this.ClientRenderStateRef.compareAndSet(null, ClientRenderState))
{
LOGGER.warn("Failed to start renderer due to concurrency");
ClientRenderState.closeAsync();
return false;
}
else
{
return true;
}
}
public boolean isRendering() {
return this.ClientRenderStateRef.get() != null;
}
public void render(Mat4f mcModelViewMatrix, Mat4f mcProjectionMatrix, float partialTicks, IProfilerWrapper profiler)
{
ClientRenderState ClientRenderState = this.ClientRenderStateRef.get();
if (ClientRenderState == null)
{
// either the renderer hasn't been started yet, or is being reloaded
return;
}
ClientRenderState.renderer.drawLODs(mcModelViewMatrix, mcProjectionMatrix, partialTicks, profiler);
}
public void stopRenderer()
{
LOGGER.info("Stopping renderer for "+this);
ClientRenderState ClientRenderState = this.ClientRenderStateRef.get();
if (ClientRenderState == null)
{
LOGGER.warn("Tried to stop renderer for "+this+" when it was not started!");
return;
}
// stop the render state
while (!this.ClientRenderStateRef.compareAndSet(ClientRenderState, null)) // TODO why is there a while loop here?
{
ClientRenderState = this.ClientRenderStateRef.get();
if (ClientRenderState == null)
{
return;
}
}
ClientRenderState.closeAsync().join(); //TODO: Make it async.
}
//===============//
// data handling //
//===============//
public void saveWrites(ChunkSizedFullDataAccessor data)
{
ClientRenderState ClientRenderState = this.ClientRenderStateRef.get();
DhLodPos pos = data.getLodPos().convertToDetailLevel(CompleteFullDataSource.SECTION_SIZE_OFFSET);
if (ClientRenderState != null)
{
ClientRenderState.renderSourceFileHandler.writeChunkDataToFile(new DhSectionPos(pos.detailLevel, pos.x, pos.z), data);
}
else
{
parent.getFileHandler().write(new DhSectionPos(pos.detailLevel, pos.x, pos.z), data);
}
}
public CompletableFuture<Void> saveAsync()
{
ClientRenderState ClientRenderState = this.ClientRenderStateRef.get();
if (ClientRenderState != null)
{
return ClientRenderState.renderSourceFileHandler.flushAndSaveAsync();
}
else
{
return CompletableFuture.completedFuture(null);
}
}
public void close()
{
// shutdown the renderer
ClientRenderState ClientRenderState = this.ClientRenderStateRef.get();
if (ClientRenderState != null)
{
// TODO does this have to be in a while loop, if so why?
while (!this.ClientRenderStateRef.compareAndSet(ClientRenderState, null))
{
ClientRenderState = this.ClientRenderStateRef.get();
if (ClientRenderState == null)
{
break;
}
}
if (ClientRenderState != null)
{
ClientRenderState.closeAsync().join(); //TODO: Make this async.
}
}
}
//=======================//
// misc helper functions //
//=======================//
public void dumpRamUsage()
{
//TODO
}
/** Returns what should be displayed in Minecraft's F3 debug menu */
protected String[] f3Log()
{
String dimName = parent.getClientLevelWrapper().getDimensionType().getDimensionName();
ClientRenderState renderState = this.ClientRenderStateRef.get();
if (renderState == null)
{
return new String[] { "level @ "+dimName+": Inactive" };
}
else
{
return new String[] { "level @ "+dimName+": Active" };
}
}
public void clearRenderCache()
{
ClientRenderState ClientRenderState = this.ClientRenderStateRef.get();
if (ClientRenderState != null && ClientRenderState.quadtree != null)
{
ClientRenderState.quadtree.clearRenderDataCache();
}
}
public void reloadPos(DhSectionPos pos) {
ClientRenderState clientRenderState = this.ClientRenderStateRef.get();
if (clientRenderState != null && clientRenderState.quadtree != null)
{
clientRenderState.quadtree.reloadPos(pos);
}
}
private static class ClientRenderState
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
public final ILevelWrapper levelWrapper;
public final LodQuadTree quadtree;
public final RenderSourceFileHandler renderSourceFileHandler;
public final LodRenderer renderer;
public ClientRenderState(IDhClientLevel dhClientLevel, IFullDataSourceProvider fullDataSourceProvider,
AbstractSaveStructure saveStructure)
{
this.levelWrapper = dhClientLevel.getLevelWrapper();
this.renderSourceFileHandler = new RenderSourceFileHandler(fullDataSourceProvider, dhClientLevel, saveStructure.getRenderCacheFolder(this.levelWrapper));
this.quadtree = new LodQuadTree(dhClientLevel, Config.Client.Advanced.Graphics.Quality.lodChunkRenderDistance.get() * LodUtil.CHUNK_WIDTH,
MC_CLIENT.getPlayerBlockPos().x, MC_CLIENT.getPlayerBlockPos().z, this.renderSourceFileHandler);
RenderBufferHandler renderBufferHandler = new RenderBufferHandler(this.quadtree);
FileScanUtil.scanFiles(saveStructure, this.levelWrapper, fullDataSourceProvider, this.renderSourceFileHandler);
this.renderer = new LodRenderer(renderBufferHandler);
}
public CompletableFuture<Void> closeAsync()
{
LOGGER.info("Shutting down "+ ClientRenderState.class.getSimpleName()+" async...");
this.renderer.close();
this.quadtree.close();
return this.renderSourceFileHandler.flushAndSaveAsync();
}
}
}
@@ -1,55 +1,50 @@
package com.seibel.lod.core.level;
import com.seibel.lod.core.file.fullDatafile.FullDataFileHandler;
import com.seibel.lod.core.dataObjects.fullData.accessor.ChunkSizedFullDataAccessor;
import com.seibel.lod.core.file.fullDatafile.IFullDataSourceProvider;
import com.seibel.lod.core.file.fullDatafile.RemoteFullDataFileHandler;
import com.seibel.lod.core.file.structure.AbstractSaveStructure;
import com.seibel.lod.core.level.states.ClientRenderState;
import com.seibel.lod.core.logging.f3.F3Screen;
import com.seibel.lod.core.dependencyInjection.SingletonInjector;
import com.seibel.lod.core.logging.DhLoggerBuilder;
import com.seibel.lod.core.pos.DhBlockPos;
import com.seibel.lod.core.wrapperInterfaces.block.IBlockStateWrapper;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IProfilerWrapper;
import com.seibel.lod.core.wrapperInterfaces.world.IBiomeWrapper;
import com.seibel.lod.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.lod.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.lod.coreapi.util.math.Mat4f;
import org.apache.logging.log4j.Logger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.CompletableFuture;
/** The level used when connected to a server */
public class DhClientLevel extends AbstractDhClientLevel implements IDhClientLevel
public class DhClientLevel extends DhLevel implements IDhClientLevel
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
private final IClientLevelWrapper clientLevelWrapper;
public final F3Screen.NestedMessage f3Message;
public FullDataFileHandler fullDataFileHandler;
public final AtomicReference<ClientRenderState> ClientRenderStateRef = new AtomicReference<>();
public final ClientLevelModule clientside;
public final IClientLevelWrapper levelWrapper;
public final AbstractSaveStructure saveStructure;
public final RemoteFullDataFileHandler dataFileHandler;
//=============//
// constructor //
//=============//
public DhClientLevel(AbstractSaveStructure saveStructure, IClientLevelWrapper clientLevelWrapper)
{
super(saveStructure, clientLevelWrapper);
this.clientLevelWrapper = clientLevelWrapper;
this.f3Message = new F3Screen.NestedMessage(super::f3Log);
LOGGER.info("Started DHLevel for "+this.clientLevelWrapper+" with saves at "+this.saveStructure);
super();
this.levelWrapper = clientLevelWrapper;
this.saveStructure = saveStructure;
if (saveStructure.getFullDataFolder(levelWrapper).mkdirs())
{
LOGGER.warn("unable to create full data folder.");
}
dataFileHandler = new RemoteFullDataFileHandler(this, saveStructure.getFullDataFolder(levelWrapper));
clientside = new ClientLevelModule(this);
clientside.startRenderer();
LOGGER.info("Started DHLevel for "+this.levelWrapper+" with saves at "+this.saveStructure);
}
//==============//
// tick methods //
//==============//
@@ -57,63 +52,55 @@ public class DhClientLevel extends AbstractDhClientLevel implements IDhClientLev
@Override
public void clientTick()
{
if (!this.baseClientTick())
{
return;
}
this.chunkToLodBuilder.tick();
chunkToLodBuilder.tick();
clientside.clientTick();
}
//========//
// render //
//========//
public void startRenderer(IClientLevelWrapper clientLevel)
{
LOGGER.info("Starting renderer for "+this);
this.setAndStartRenderer();
@Override
public void render(Mat4f mcModelViewMatrix, Mat4f mcProjectionMatrix, float partialTicks, IProfilerWrapper profiler) {
clientside.render(mcModelViewMatrix, mcProjectionMatrix, partialTicks, profiler);
}
//================//
// level handling //
//================//
@Override
public int computeBaseColor(DhBlockPos pos, IBiomeWrapper biome, IBlockStateWrapper block) { return this.clientLevelWrapper.computeBaseColor(pos, biome, block); }
public int computeBaseColor(DhBlockPos pos, IBiomeWrapper biome, IBlockStateWrapper block) { return levelWrapper.computeBaseColor(pos, biome, block); }
@Override
public IClientLevelWrapper getClientLevelWrapper() { return this.clientLevelWrapper; }
public IClientLevelWrapper getClientLevelWrapper() { return levelWrapper; }
@Override
public ILevelWrapper getLevelWrapper() { return this.clientLevelWrapper; }
public void clearRenderCache() {
clientside.clearRenderCache();
}
@Override
public int getMinY() { return this.clientLevelWrapper.getMinHeight(); }
//===============//
// data handling //
//===============//
public ILevelWrapper getLevelWrapper() { return levelWrapper; }
@Override
public CompletableFuture<Void> saveAsync() {
return CompletableFuture.allOf(clientside.saveAsync(), dataFileHandler.flushAndSave());
}
@Override
protected void saveWrites(ChunkSizedFullDataAccessor data) {
clientside.saveWrites(data);
}
@Override
public int getMinY() { return levelWrapper.getMinHeight(); }
@Override
public void close()
{
this.f3Message.close();
this.baseClose();
LOGGER.info("Closed "+DhClientLevel.class.getSimpleName()+" for "+this.clientLevelWrapper);
clientside.close();
super.close();
dataFileHandler.close();
LOGGER.info("Closed "+DhClientLevel.class.getSimpleName()+" for "+levelWrapper);
}
//=======================//
// misc helper functions //
//=======================//
@@ -125,6 +112,13 @@ public class DhClientLevel extends AbstractDhClientLevel implements IDhClientLev
}
@Override
public IFullDataSourceProvider getFileHandler() { return this.fullDataFileHandler; }
public IFullDataSourceProvider getFileHandler() {
return dataFileHandler;
}
@Override
public AbstractSaveStructure getSaveStructure() {
return saveStructure;
}
}
@@ -1,20 +1,12 @@
package com.seibel.lod.core.level;
import com.seibel.lod.api.interfaces.override.worldGenerator.IDhApiWorldGenerator;
import com.seibel.lod.coreapi.DependencyInjection.WorldGeneratorInjector;
import com.seibel.lod.core.config.AppliedConfigState;
import com.seibel.lod.core.dataObjects.fullData.accessor.ChunkSizedFullDataAccessor;
import com.seibel.lod.core.file.fullDatafile.IFullDataSourceProvider;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IProfilerWrapper;
import com.seibel.lod.core.dependencyInjection.SingletonInjector;
import com.seibel.lod.core.file.fullDatafile.FullDataFileHandler;
import com.seibel.lod.core.file.structure.AbstractSaveStructure;
import com.seibel.lod.core.generation.BatchGenerator;
import com.seibel.lod.core.generation.WorldGenerationQueue;
import com.seibel.lod.core.file.fullDatafile.GeneratedFullDataFileHandler;
import com.seibel.lod.core.level.states.ClientRenderState;
import com.seibel.lod.core.logging.f3.F3Screen;
import com.seibel.lod.core.pos.DhSectionPos;
import com.seibel.lod.core.util.FileScanUtil;
import com.seibel.lod.core.pos.DhBlockPos2D;
import com.seibel.lod.core.config.Config;
import com.seibel.lod.core.logging.DhLoggerBuilder;
import com.seibel.lod.core.pos.DhBlockPos;
import com.seibel.lod.core.wrapperInterfaces.block.IBlockStateWrapper;
@@ -23,56 +15,29 @@ import com.seibel.lod.core.wrapperInterfaces.world.IBiomeWrapper;
import com.seibel.lod.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.lod.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.lod.core.wrapperInterfaces.world.IServerLevelWrapper;
import com.seibel.lod.coreapi.util.math.Mat4f;
import org.apache.logging.log4j.Logger;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicReference;
/** The level used on a singleplayer world */
public class DhClientServerLevel extends AbstractDhClientLevel implements IDhClientLevel, IDhServerLevel, GeneratedFullDataFileHandler.IOnWorldGenCompleteListener
public class DhClientServerLevel extends DhLevel implements IDhClientLevel, IDhServerLevel
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
public final IServerLevelWrapper serverLevelWrapper;
public final F3Screen.NestedMessage f3Message;
/**
* This is separate from {@link AbstractDhClientLevel#fullDataFileHandler}
* since the base {@link FullDataFileHandler} doesn't support world generation
*/
public final GeneratedFullDataFileHandler generatedFullDataFileHandler;
private final AppliedConfigState<Boolean> worldGeneratorEnabledConfig;
private final AtomicReference<WorldGenState> worldGenStateRef = new AtomicReference<>();
public final ServerLevelModule serverside;
public final ClientLevelModule clientside;
public IClientLevelWrapper clientLevelWrapper;
public DhClientServerLevel(AbstractSaveStructure saveStructure, IServerLevelWrapper serverLevelWrapper)
{
super(saveStructure, serverLevelWrapper);
this.serverLevelWrapper = serverLevelWrapper;
this.f3Message = new F3Screen.NestedMessage(super::f3Log);
if (this.fullDataFileHandler != null)
{
// done since the super() constructor creates a FullDataFileHandler first that we don't want // TODO in the future it would be nice if we didn't have to overwrite an existing object like this
this.fullDataFileHandler.close();
}
this.generatedFullDataFileHandler = new GeneratedFullDataFileHandler(this, saveStructure.getFullDataFolder(serverLevelWrapper));
this.fullDataFileHandler = this.generatedFullDataFileHandler;
FileScanUtil.scanFiles(saveStructure, this.serverLevelWrapper, this.fullDataFileHandler, null);
this.worldGeneratorEnabledConfig = new AppliedConfigState<>(Config.Client.Advanced.WorldGenerator.enableDistantGeneration);
serverside = new ServerLevelModule(this, serverLevelWrapper, saveStructure);
clientside = new ClientLevelModule(this);
LOGGER.info("Started "+DhClientServerLevel.class.getSimpleName()+" for "+ serverLevelWrapper +" with saves at "+saveStructure);
}
//==============//
// tick methods //
//==============//
@@ -80,104 +45,58 @@ public class DhClientServerLevel extends AbstractDhClientLevel implements IDhCli
@Override
public void clientTick()
{
if (!super.baseClientTick())
{
return;
}
// additional tick logic can be added if necessary
clientside.clientTick();
}
@Override
public void serverTick() { this.chunkToLodBuilder.tick(); }
public void render(Mat4f mcModelViewMatrix, Mat4f mcProjectionMatrix, float partialTicks, IProfilerWrapper profiler) {
clientside.render(mcModelViewMatrix, mcProjectionMatrix, partialTicks, profiler);
}
@Override
public void serverTick()
{
chunkToLodBuilder.tick();
}
@Override
public void doWorldGen()
{
WorldGenState worldGenState = this.worldGenStateRef.get();
// create/destroy the world generator as necessary
boolean shouldDoWorldGen = this.worldGeneratorEnabledConfig.get() && this.ClientRenderStateRef.get() != null;
if (shouldDoWorldGen && worldGenState == null)
serverside.worldGeneratorEnabledConfig.pollNewValue();
boolean shouldDoWorldGen = serverside.worldGeneratorEnabledConfig.get() && clientside.isRendering();
boolean isWorldGenRunning = serverside.isWorldGenRunning();
if (shouldDoWorldGen && !isWorldGenRunning)
{
// create the new world generator
WorldGenState newWgs = new WorldGenState(this);
if (!this.worldGenStateRef.compareAndSet(null, newWgs))
{
LOGGER.warn("Failed to start world gen due to concurrency");
newWgs.closeAsync(false);
}
// start world gen
serverside.startWorldGen();
}
else if (!shouldDoWorldGen && worldGenState != null)
else if (!shouldDoWorldGen && isWorldGenRunning)
{
// shut down the world generator
while (!this.worldGenStateRef.compareAndSet(worldGenState, null))
{
worldGenState = this.worldGenStateRef.get();
if (worldGenState == null)
{
return;
}
}
worldGenState.closeAsync(true).join(); //TODO: Make it async.
// stop world gen
serverside.stopWorldGen();
}
if (worldGenState != null)
if (serverside.isWorldGenRunning())
{
// queue new world generation requests
worldGenState.chunkGenerator.preGeneratorTaskStart();
worldGenState.worldGenerationQueue.runCurrentGenTasksUntilBusy(new DhBlockPos2D(MC_CLIENT.getPlayerBlockPos()));
serverside.worldGenTick(new DhBlockPos2D(MC_CLIENT.getPlayerBlockPos()));
}
}
//========//
// render //
//========//
public void startRenderer(IClientLevelWrapper clientLevel)
{
LOGGER.info("Starting renderer for "+this);
if (super.setAndStartRenderer())
{
this.worldGeneratorEnabledConfig.pollNewValue();
if (this.worldGeneratorEnabledConfig.get() && this.worldGenStateRef.get() == null)
{
WorldGenState worldGenState = new WorldGenState(this);
if (!this.worldGenStateRef.compareAndSet(null, worldGenState))
{
LOGGER.warn("Failed to start world gen due to concurrency");
worldGenState.closeAsync(false);
}
}
}
clientside.startRenderer();
}
public void stopRenderer()
{
super.stopRenderer();
// stop the world generator
WorldGenState worldGenState = this.worldGenStateRef.get();
if (worldGenState != null)
{
while (!this.worldGenStateRef.compareAndSet(worldGenState, null))
{
worldGenState = this.worldGenStateRef.get();
if (worldGenState == null)
{
return;
}
}
worldGenState.closeAsync(true).join(); //TODO: Make it async.
}
clientside.stopRenderer();
clientLevelWrapper = null;
}
//================//
// level handling //
//================//
@@ -197,17 +116,41 @@ public class DhClientServerLevel extends AbstractDhClientLevel implements IDhCli
}
@Override
public IClientLevelWrapper getClientLevelWrapper() { return this.serverLevelWrapper.tryGetClientLevelWrapper(); }
public IClientLevelWrapper getClientLevelWrapper() { return serverside.levelWrapper.tryGetClientLevelWrapper(); }
@Override
public IServerLevelWrapper getServerLevelWrapper() { return this.serverLevelWrapper; }
public void clearRenderCache() {
clientside.clearRenderCache();
}
@Override
public ILevelWrapper getLevelWrapper() { return this.serverLevelWrapper; }
public IServerLevelWrapper getServerLevelWrapper() { return serverside.levelWrapper; }
@Override
public int getMinY() { return this.serverLevelWrapper.getMinHeight(); }
public ILevelWrapper getLevelWrapper() { return getServerLevelWrapper(); }
@Override
public IFullDataSourceProvider getFileHandler() {
return serverside.dataFileHandler;
}
@Override
public AbstractSaveStructure getSaveStructure() {
return serverside.saveStructure;
}
@Override
protected void saveWrites(ChunkSizedFullDataAccessor data) {
clientside.saveWrites(data);
}
@Override
public int getMinY() { return getLevelWrapper().getMinHeight(); }
@Override
public CompletableFuture<Void> saveAsync() {
return CompletableFuture.allOf(clientside.saveAsync(), getFileHandler().flushAndSave());
}
//===============//
// data handling //
//===============//
@@ -215,93 +158,20 @@ public class DhClientServerLevel extends AbstractDhClientLevel implements IDhCli
@Override
public void close()
{
super.baseClose();
this.f3Message.close();
WorldGenState worldGenState = this.worldGenStateRef.get();
if (worldGenState != null)
{
// TODO does this have to be in a while loop, if so why?
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.
}
}
LOGGER.info("Closed "+this.getClass().getSimpleName()+" for "+this.serverLevelWrapper);
clientside.close();
super.close();
serverside.close();
LOGGER.info("Closed "+this.getClass().getSimpleName()+" for "+this.getServerLevelWrapper());
}
@Override
public void onWorldGenTaskComplete(DhSectionPos pos)
{
ClientRenderState clientRenderState = this.ClientRenderStateRef.get();
if (clientRenderState != null && clientRenderState.quadtree != null)
{
clientRenderState.quadtree.reloadPos(pos);
}
clientside.reloadPos(pos);
}
//================//
// helper classes //
//================//
private class WorldGenState
{
public final IDhApiWorldGenerator chunkGenerator;
public final WorldGenerationQueue worldGenerationQueue;
WorldGenState(DhClientServerLevel level)
{
IDhApiWorldGenerator worldGenerator = WorldGeneratorInjector.INSTANCE.get(level.getLevelWrapper());
if (worldGenerator == null)
{
// no override generator is bound, use the Core world generator
worldGenerator = new BatchGenerator(level);
// binding the core generator won't prevent other mods from binding their own generators
// since core world generator's should have the lowest override priority
WorldGeneratorInjector.INSTANCE.bind(level.getLevelWrapper(), worldGenerator);
}
this.chunkGenerator = worldGenerator;
this.worldGenerationQueue = new WorldGenerationQueue(this.chunkGenerator);
DhClientServerLevel.this.generatedFullDataFileHandler.setGenerationQueue(this.worldGenerationQueue);
DhClientServerLevel.this.generatedFullDataFileHandler.addWorldGenCompleteListener(level);
}
CompletableFuture<Void> closeAsync(boolean doInterrupt)
{
DhClientServerLevel.this.generatedFullDataFileHandler.clearGenerationQueue();
return this.worldGenerationQueue.startClosing(true, doInterrupt)
.exceptionally(ex ->
{
LOGGER.error("Error closing generation queue", ex);
return null;
}
).thenRun(this.chunkGenerator::close)
.exceptionally(ex ->
{
LOGGER.error("Error closing world gen", ex);
return null;
});
}
@Override
public void dumpRamUsage() {
}
}
@@ -0,0 +1,42 @@
package com.seibel.lod.core.level;
import com.seibel.lod.core.dataObjects.fullData.accessor.ChunkSizedFullDataAccessor;
import com.seibel.lod.core.dataObjects.transformers.ChunkToLodBuilder;
import com.seibel.lod.core.file.fullDatafile.IFullDataSourceProvider;
import com.seibel.lod.core.file.structure.AbstractSaveStructure;
import com.seibel.lod.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.lod.core.wrapperInterfaces.world.ILevelWrapper;
import java.util.concurrent.CompletableFuture;
public abstract class DhLevel implements IDhLevel {
public final ChunkToLodBuilder chunkToLodBuilder;
protected DhLevel() {
this.chunkToLodBuilder = new ChunkToLodBuilder();
}
protected abstract void saveWrites(ChunkSizedFullDataAccessor data);
@Override
public int getMinY() {
return 0;
}
@Override
public void updateChunkAsync(IChunkWrapper chunk)
{
CompletableFuture<ChunkSizedFullDataAccessor> future = this.chunkToLodBuilder.tryGenerateData(chunk);
if (future != null)
{
future.thenAccept(this::saveWrites);
}
}
@Override
public void close() {
chunkToLodBuilder.close();
}
}
@@ -1,6 +1,12 @@
package com.seibel.lod.core.level;
import com.seibel.lod.core.dataObjects.fullData.accessor.ChunkSizedFullDataAccessor;
import com.seibel.lod.core.dataObjects.fullData.sources.CompleteFullDataSource;
import com.seibel.lod.core.file.fullDatafile.IFullDataSourceProvider;
import com.seibel.lod.core.file.structure.AbstractSaveStructure;
import com.seibel.lod.core.pos.DhBlockPos2D;
import com.seibel.lod.core.pos.DhLodPos;
import com.seibel.lod.core.pos.DhSectionPos;
import com.seibel.lod.core.util.FileScanUtil;
import com.seibel.lod.core.file.fullDatafile.FullDataFileHandler;
import com.seibel.lod.core.file.structure.LocalSaveStructure;
@@ -12,31 +18,30 @@ import org.apache.logging.log4j.Logger;
import java.util.concurrent.CompletableFuture;
public class DhServerLevel implements IDhServerLevel
public class DhServerLevel extends DhLevel implements IDhServerLevel
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
public final LocalSaveStructure save;
public final FullDataFileHandler dataFileHandler;
public final IServerLevelWrapper level;
public DhServerLevel(LocalSaveStructure save, IServerLevelWrapper level)
public final ServerLevelModule serverside;
public DhServerLevel(AbstractSaveStructure saveStructure, IServerLevelWrapper serverLevelWrapper)
{
this.save = save;
this.level = level;
save.getFullDataFolder(level).mkdirs();
this.dataFileHandler = new FullDataFileHandler(this, save.getFullDataFolder(level)); //FIXME: GenerationQueue
FileScanUtil.scanFiles(save, level, this.dataFileHandler, null);
LOGGER.info("Started DHLevel for {} with saves at {}", level, save);
serverside = new ServerLevelModule(this, serverLevelWrapper, saveStructure);
LOGGER.info("Started DHLevel for {} with saves at {}", serverLevelWrapper, saveStructure);
}
public void serverTick()
{
//Nothing for now
chunkToLodBuilder.tick();
}
@Override
public int getMinY() { return this.level.getMinHeight(); }
protected void saveWrites(ChunkSizedFullDataAccessor data) {
DhLodPos pos = data.getLodPos().convertToDetailLevel(CompleteFullDataSource.SECTION_SIZE_OFFSET);
getFileHandler().write(new DhSectionPos(pos.detailLevel, pos.x, pos.z), data);
}
@Override
public int getMinY() { return getLevelWrapper().getMinHeight(); }
@Override
public void dumpRamUsage()
@@ -47,38 +52,52 @@ public class DhServerLevel implements IDhServerLevel
@Override
public void close()
{
this.dataFileHandler.close();
LOGGER.info("Closed DHLevel for {}", this.level);
super.close();
serverside.close();
LOGGER.info("Closed DHLevel for {}", getLevelWrapper());
}
@Override
public CompletableFuture<Void> saveAsync() { return this.dataFileHandler.flushAndSave(); }
public CompletableFuture<Void> saveAsync() { return getFileHandler().flushAndSave(); }
@Override
public void doWorldGen()
{
// FIXME: No world gen for server side only for now
boolean shouldDoWorldGen = true; //todo;
boolean isWorldGenRunning = serverside.isWorldGenRunning();
if (shouldDoWorldGen && !isWorldGenRunning)
{
// start world gen
serverside.startWorldGen();
}
else if (!shouldDoWorldGen && isWorldGenRunning)
{
// stop world gen
serverside.stopWorldGen();
}
if (serverside.isWorldGenRunning())
{
serverside.worldGenTick(new DhBlockPos2D(0, 0)); // todo;
}
}
@Override
public IServerLevelWrapper getServerLevelWrapper() { return this.level; }
public IServerLevelWrapper getServerLevelWrapper() { return serverside.levelWrapper; }
@Override
public ILevelWrapper getLevelWrapper() { return this.level; }
public ILevelWrapper getLevelWrapper() { return getServerLevelWrapper(); }
@Override
public IFullDataSourceProvider getFileHandler() { return this.dataFileHandler; }
@Override
public void clearRenderDataCache()
{
// Do nothing, there is no render data on the server
public IFullDataSourceProvider getFileHandler() { return serverside.dataFileHandler; }
@Override
public AbstractSaveStructure getSaveStructure() {
return serverside.saveStructure;
}
@Override
public void updateChunkAsync(IChunkWrapper chunk)
{
//TODO
public void onWorldGenTaskComplete(DhSectionPos pos) {
//TODO: Send packet to client
}
}
@@ -16,5 +16,10 @@ public interface IDhClientLevel extends IDhLevel
int computeBaseColor(DhBlockPos pos, IBiomeWrapper biome, IBlockStateWrapper block);
IClientLevelWrapper getClientLevelWrapper();
/**
* Re-creates the color, render data.
* This method should be called after resource packs are changed or LOD settings are modified.
*/
void clearRenderCache();
}
@@ -1,6 +1,7 @@
package com.seibel.lod.core.level;
import com.seibel.lod.core.file.fullDatafile.IFullDataSourceProvider;
import com.seibel.lod.core.file.structure.AbstractSaveStructure;
import com.seibel.lod.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.lod.core.wrapperInterfaces.world.ILevelWrapper;
@@ -22,11 +23,9 @@ public interface IDhLevel extends AutoCloseable
void updateChunkAsync(IChunkWrapper chunk);
IFullDataSourceProvider getFileHandler();
/**
* Re-creates the color, render data.
* This method should be called after resource packs are changed or LOD settings are modified.
*/
void clearRenderDataCache(); // TODO make all methods in this stack named the same
AbstractSaveStructure getSaveStructure();
}
@@ -1,9 +1,10 @@
package com.seibel.lod.core.level;
import com.seibel.lod.core.file.fullDatafile.GeneratedFullDataFileHandler;
import com.seibel.lod.core.wrapperInterfaces.world.IServerLevelWrapper;
public interface IDhServerLevel extends IDhLevel
{
public interface IDhServerLevel extends IDhLevel, GeneratedFullDataFileHandler.IOnWorldGenCompleteListener
{
void serverTick();
void doWorldGen();
@@ -0,0 +1,170 @@
package com.seibel.lod.core.level;
import com.seibel.lod.api.interfaces.override.worldGenerator.IDhApiWorldGenerator;
import com.seibel.lod.core.config.AppliedConfigState;
import com.seibel.lod.core.config.Config;
import com.seibel.lod.core.file.fullDatafile.GeneratedFullDataFileHandler;
import com.seibel.lod.core.file.structure.AbstractSaveStructure;
import com.seibel.lod.core.generation.BatchGenerator;
import com.seibel.lod.core.generation.WorldGenerationQueue;
import com.seibel.lod.core.logging.DhLoggerBuilder;
import com.seibel.lod.core.pos.DhBlockPos2D;
import com.seibel.lod.core.util.FileScanUtil;
import com.seibel.lod.core.wrapperInterfaces.world.IServerLevelWrapper;
import com.seibel.lod.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 class WorldGenState
{
public final IDhApiWorldGenerator chunkGenerator;
public final WorldGenerationQueue worldGenerationQueue;
WorldGenState(IDhServerLevel level)
{
IDhApiWorldGenerator worldGenerator = WorldGeneratorInjector.INSTANCE.get(level.getLevelWrapper());
if (worldGenerator == null)
{
// no override generator is bound, use the Core world generator
worldGenerator = new BatchGenerator(level);
// binding the core generator won't prevent other mods from binding their own generators
// since core world generator's should have the lowest override priority
WorldGeneratorInjector.INSTANCE.bind(level.getLevelWrapper(), worldGenerator);
}
this.chunkGenerator = worldGenerator;
this.worldGenerationQueue = new WorldGenerationQueue(this.chunkGenerator);
}
CompletableFuture<Void> closeAsync(boolean doInterrupt)
{
return this.worldGenerationQueue.startClosing(true, doInterrupt)
.exceptionally(ex ->
{
LOGGER.error("Error closing generation queue", ex);
return null;
}
).thenRun(this.chunkGenerator::close)
.exceptionally(ex ->
{
LOGGER.error("Error closing world gen", ex);
return null;
});
}
}
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
public final IServerLevelWrapper levelWrapper;
public final IDhServerLevel parent;
public final AbstractSaveStructure saveStructure;
public final GeneratedFullDataFileHandler dataFileHandler;
public final AppliedConfigState<Boolean> worldGeneratorEnabledConfig;
private final AtomicReference<WorldGenState> worldGenStateRef = new AtomicReference<>();
public ServerLevelModule(IDhServerLevel parent, IServerLevelWrapper levelWrapper, AbstractSaveStructure saveStructure)
{
this.parent = parent;
this.levelWrapper = levelWrapper;
this.saveStructure = saveStructure;
this.dataFileHandler = new GeneratedFullDataFileHandler(parent, saveStructure.getFullDataFolder(levelWrapper));
FileScanUtil.scanFiles(saveStructure, this.levelWrapper, this.dataFileHandler, null);
this.worldGeneratorEnabledConfig = new AppliedConfigState<>(Config.Client.Advanced.WorldGenerator.enableDistantGeneration);
}
//==============//
// 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.setGenerationQueue(newWgs.worldGenerationQueue);
dataFileHandler.addWorldGenCompleteListener(parent);
}
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.chunkGenerator.preGeneratorTaskStart();//new DhBlockPos2D(MC_CLIENT.getPlayerBlockPos()
worldGenState.worldGenerationQueue.runCurrentGenTasksUntilBusy(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.
}
}
dataFileHandler.close();
}
//=======================//
// misc helper functions //
//=======================//
public void dumpRamUsage()
{
//TODO
}
}
@@ -1,58 +0,0 @@
package com.seibel.lod.core.level.states;
import com.seibel.lod.core.config.Config;
import com.seibel.lod.core.dependencyInjection.SingletonInjector;
import com.seibel.lod.core.file.fullDatafile.IFullDataSourceProvider;
import com.seibel.lod.core.file.renderfile.RenderSourceFileHandler;
import com.seibel.lod.core.file.structure.AbstractSaveStructure;
import com.seibel.lod.core.level.IDhClientLevel;
import com.seibel.lod.core.logging.DhLoggerBuilder;
import com.seibel.lod.core.render.LodQuadTree;
import com.seibel.lod.core.render.RenderBufferHandler;
import com.seibel.lod.core.render.renderer.LodRenderer;
import com.seibel.lod.core.util.FileScanUtil;
import com.seibel.lod.core.util.LodUtil;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.lod.core.wrapperInterfaces.world.ILevelWrapper;
import org.apache.logging.log4j.Logger;
import java.util.concurrent.CompletableFuture;
public class ClientRenderState
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
public final ILevelWrapper levelWrapper;
public final LodQuadTree quadtree;
public final RenderSourceFileHandler renderSourceFileHandler;
public final LodRenderer renderer;
public ClientRenderState(IDhClientLevel dhClientLevel, IFullDataSourceProvider fullDataSourceProvider,
AbstractSaveStructure saveStructure)
{
this.levelWrapper = dhClientLevel.getLevelWrapper();
this.renderSourceFileHandler = new RenderSourceFileHandler(fullDataSourceProvider, dhClientLevel, saveStructure.getRenderCacheFolder(this.levelWrapper));
this.quadtree = new LodQuadTree(dhClientLevel, Config.Client.Advanced.Graphics.Quality.lodChunkRenderDistance.get() * LodUtil.CHUNK_WIDTH,
MC_CLIENT.getPlayerBlockPos().x, MC_CLIENT.getPlayerBlockPos().z, this.renderSourceFileHandler);
RenderBufferHandler renderBufferHandler = new RenderBufferHandler(this.quadtree);
FileScanUtil.scanFiles(saveStructure, this.levelWrapper, fullDataSourceProvider, this.renderSourceFileHandler);
this.renderer = new LodRenderer(renderBufferHandler);
}
public CompletableFuture<Void> closeAsync()
{
LOGGER.info("Shutting down "+ClientRenderState.class.getSimpleName()+" async...");
this.renderer.close();
this.quadtree.close();
return this.renderSourceFileHandler.flushAndSaveAsync();
}
}
@@ -3,6 +3,7 @@ package com.seibel.lod.core.render;
import com.seibel.lod.api.interfaces.render.IDhApiRenderProxy;
import com.seibel.lod.api.objects.DhApiResult;
import com.seibel.lod.core.api.internal.SharedApi;
import com.seibel.lod.core.level.IDhClientLevel;
import com.seibel.lod.core.level.IDhLevel;
import com.seibel.lod.core.world.AbstractDhWorld;
@@ -36,9 +37,9 @@ public class DhApiRenderProxy implements IDhApiRenderProxy
Iterable<? extends IDhLevel> loadedLevels = world.getAllLoadedLevels();
for (IDhLevel level : loadedLevels)
{
if (level != null)
if (level instanceof IDhClientLevel)
{
level.clearRenderDataCache();
((IDhClientLevel) level).clearRenderCache();
}
}
@@ -135,7 +135,7 @@ public class DhClientServerWorld extends AbstractDhWorld implements IDhClientWor
for (DhClientServerLevel level : this.dhLevels)
{
LOGGER.info("Unloading level "+level.serverLevelWrapper.getDimensionType().getDimensionName());
LOGGER.info("Unloading level "+level.getServerLevelWrapper().getDimensionType().getDimensionName());
level.close();
}
@@ -3,17 +3,13 @@ package com.seibel.lod.core.world;
import com.seibel.lod.core.level.DhClientLevel;
import com.seibel.lod.core.level.IDhLevel;
import com.seibel.lod.core.file.structure.ClientOnlySaveStructure;
import com.seibel.lod.core.config.Config;
import com.seibel.lod.core.level.states.ClientRenderState;
import com.seibel.lod.core.util.ThreadUtil;
import com.seibel.lod.core.util.objects.EventLoop;
import com.seibel.lod.core.util.LodUtil;
import com.seibel.lod.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.lod.core.wrapperInterfaces.world.ILevelWrapper;
import java.io.File;
import java.util.HashMap;
import java.util.Iterator;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
@@ -53,11 +49,8 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
{
return null;
}
DhClientLevel level = new DhClientLevel(this.saveStructure, clientLevelWrapper);
level.startRenderer(clientLevelWrapper);
return level;
return new DhClientLevel(this.saveStructure, clientLevelWrapper);
});
}
@@ -92,25 +85,6 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
private void _clientTick()
{
int newBlockRenderDistance = Config.Client.Advanced.Graphics.Quality.lodChunkRenderDistance.get() * LodUtil.CHUNK_WIDTH;
Iterator<DhClientLevel> iterator = this.levels.values().iterator();
while (iterator.hasNext())
{
DhClientLevel level = iterator.next();
ClientRenderState clientRenderState = level.ClientRenderStateRef.get();
if (clientRenderState != null && clientRenderState.quadtree != null)
{
if (clientRenderState.quadtree.blockRenderDistanceRadius != newBlockRenderDistance)
{
// TODO is this the best way to handle changing the render distance?
level.close();
iterator.remove();
}
}
}
this.levels.values().forEach(DhClientLevel::clientTick);
}
@@ -90,7 +90,7 @@ public class DhServerWorld extends AbstractDhWorld implements IDhServerWorld
{
for (DhServerLevel level : this.levels.values())
{
LOGGER.info("Unloading level " + level.level.getDimensionType().getDimensionName());
LOGGER.info("Unloading level " + level.getLevelWrapper().getDimensionType().getDimensionName());
level.close();
}