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:
@@ -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()
|
||||
|
||||
+29
-21
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user