Merge branch 'main' of https://gitlab.com/jeseibel/distant-horizons-core into feat/server-updates

This commit is contained in:
s809
2023-08-22 19:12:43 +05:00
20 changed files with 369 additions and 182 deletions
@@ -28,8 +28,10 @@ import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiDistantGenerat
import com.seibel.distanthorizons.core.config.eventHandlers.QuickRenderToggleConfigEventHandler;
import com.seibel.distanthorizons.core.config.eventHandlers.RenderCacheConfigEventHandler;
import com.seibel.distanthorizons.core.config.eventHandlers.UnsafeValuesConfigListener;
import com.seibel.distanthorizons.core.config.eventHandlers.WorldCurvatureConfigEventHandler;
import com.seibel.distanthorizons.core.config.eventHandlers.presets.ThreadPresetConfigEventHandler;
import com.seibel.distanthorizons.core.config.eventHandlers.presets.RenderQualityPresetConfigEventHandler;
import com.seibel.distanthorizons.core.config.listeners.ConfigChangeListener;
import com.seibel.distanthorizons.core.config.types.*;
import com.seibel.distanthorizons.core.config.types.enums.EConfigEntryAppearance;
import com.seibel.distanthorizons.core.config.types.enums.EConfigEntryPerformance;
@@ -575,6 +577,7 @@ public class Config
+ "Note: Due to current limitations, the min value is 50 \n"
+ "and the max value is 5000. Any values outside this range \n"
+ "will be set to 0 (disabled).")
.addListener(WorldCurvatureConfigEventHandler.INSTANCE)
.build();
public static ConfigEntry<Double> lodBias = new ConfigEntry.Builder<Double>()
@@ -734,6 +737,7 @@ public class Config
+ "")
.build();
// not currently implemented
public static ConfigEntry<Boolean> enableMultiverseNetworking = new ConfigEntry.Builder<Boolean>()
.set(true)
.comment(""
@@ -742,6 +746,7 @@ public class Config
+ "")
.build();
// not currently implemented
public static ConfigEntry<Boolean> enableServerNetworking = new ConfigEntry.Builder<Boolean>()
.set(false)
.comment(""
@@ -0,0 +1,47 @@
package com.seibel.distanthorizons.core.config.eventHandlers;
import com.seibel.distanthorizons.api.DhApi;
import com.seibel.distanthorizons.api.enums.config.ELodShading;
import com.seibel.distanthorizons.api.enums.config.EMaxHorizontalResolution;
import com.seibel.distanthorizons.api.enums.config.EVerticalQuality;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.config.listeners.IConfigListener;
import java.util.Timer;
import java.util.TimerTask;
/**
* Listens to the config and will automatically
* clear the current render cache if certain settings are changed. <br> <br>
*
* Note: if additional settings should clear the render cache, add those to this listener, don't create a new listener
*/
public class WorldCurvatureConfigEventHandler implements IConfigListener
{
public static WorldCurvatureConfigEventHandler INSTANCE = new WorldCurvatureConfigEventHandler();
private static final int MIN_VALID_CURVE_VALUE = 50;
/** private since we only ever need one handler at a time */
private WorldCurvatureConfigEventHandler() { }
@Override
public void onConfigValueSet()
{
int curveRatio = Config.Client.Advanced.Graphics.AdvancedGraphics.earthCurveRatio.get();
if (curveRatio > 0 && curveRatio < MIN_VALID_CURVE_VALUE)
{
// shouldn't update the UI, otherwise we may end up fighting the user
Config.Client.Advanced.Graphics.AdvancedGraphics.earthCurveRatio.set(MIN_VALID_CURVE_VALUE);
}
}
@Override
public void onUiModify() { /* do nothing, we only care about modified config values */ }
}
@@ -215,16 +215,19 @@ public abstract class AbstractMetaDataContainerFile
{
fileChannel.position(METADATA_SIZE_IN_BYTES);
try (DhDataOutputStream compressedOut = new DhDataOutputStream(Channels.newOutputStream(fileChannel));
CheckedOutputStream checkedOut = new CheckedOutputStream(compressedOut, new Adler32())) // TODO: Is Adler32 ok?
{
dataWriterFunc.writeBufferToFile(compressedOut);
this.baseMetaData.checksum = (int) checkedOut.getChecksum().getValue();
}
// the order of these streams is important, otherwise the checksum won't be calculated
CheckedOutputStream checkedOut = new CheckedOutputStream(Channels.newOutputStream(fileChannel), new Adler32());
// normally a DhStream should be the topmost stream to prevent closing the stream accidentally, but since this stream will be closed immediately after writing anyway, it won't be an issue
DhDataOutputStream compressedOut = new DhDataOutputStream(checkedOut);
// write the contained data
dataWriterFunc.writeBufferToFile(compressedOut);
compressedOut.flush();
this.baseMetaData.checksum = (int) checkedOut.getChecksum().getValue();
fileChannel.position(0);
// Write metadata
fileChannel.position(0);
ByteBuffer buffer = ByteBuffer.allocate(METADATA_SIZE_IN_BYTES);
buffer.putInt(METADATA_IDENTITY_BYTES);
buffer.putInt(this.pos.sectionX);
@@ -18,6 +18,9 @@ public interface IWorldGenerationQueue extends Closeable
void runCurrentGenTasksUntilBusy(DhBlockPos2D targetPos);
int getWaitingTaskCount();
int getInProgressTaskCount();
CompletableFuture<Void> startClosing(boolean cancelCurrentGeneration, boolean alsoInterruptRunning);
void close();
}
@@ -18,6 +18,7 @@ import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable;
import com.seibel.distanthorizons.core.util.ThreadUtil;
import com.seibel.distanthorizons.core.util.objects.DhThreadFactory;
import com.seibel.distanthorizons.core.util.objects.RateLimitedThreadPoolExecutor;
import com.seibel.distanthorizons.core.util.objects.UncheckedInterruptedException;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
@@ -34,7 +35,7 @@ public class WorldGenerationQueue implements IWorldGenerationQueue, IDebugRender
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
public static final DhThreadFactory THREAD_FACTORY = new DhThreadFactory(ThreadUtil.THREAD_NAME_PREFIX + "Gen-Worker-Thread", Thread.MIN_PRIORITY);
public static final DhThreadFactory THREAD_FACTORY = new DhThreadFactory(ThreadUtil.THREAD_NAME_PREFIX + "World-Gen-Worker-Thread", Thread.MIN_PRIORITY);
private final IDhApiWorldGenerator generator;
@@ -74,7 +75,7 @@ public class WorldGenerationQueue implements IWorldGenerationQueue, IDebugRender
private final HashMap<DhLodPos, StackTraceElement[]> alreadyGeneratedPosHashSet = new HashMap<>(MAX_ALREADY_GENERATED_COUNT);
private final Queue<DhLodPos> alreadyGeneratedPosQueue = new LinkedList<>();
private static ExecutorService worldGeneratorThreadPool;
private static RateLimitedThreadPoolExecutor worldGeneratorThreadPool;
private static ConfigChangeListener<Integer> configListener;
@@ -533,6 +534,7 @@ public class WorldGenerationQueue implements IWorldGenerationQueue, IDebugRender
}
worldGeneratorThreadPool = ThreadUtil.makeRateLimitedThreadPool(threadPoolSize, THREAD_FACTORY, Config.Client.Advanced.MultiThreading.runTimeRatioForWorldGenerationThreads);
worldGeneratorThreadPool.setOnTerminatedEventHandler(WorldGenerationQueue::onWorldGenThreadPoolTerminated);
}
/**
@@ -548,6 +550,20 @@ public class WorldGenerationQueue implements IWorldGenerationQueue, IDebugRender
}
}
private static void onWorldGenThreadPoolTerminated()
{
LOGGER.debug("World generator thread pool terminated. Suggesting the JVM runs a garbage collection to clean up any loose world generation objects...");
System.gc();
}
//=========//
// getters //
//=========//
public int getWaitingTaskCount() { return this.waitingTasks.size(); }
public int getInProgressTaskCount() { return this.inProgressGenTasksByLodPos.size(); }
//==========//
@@ -681,6 +697,11 @@ public class WorldGenerationQueue implements IWorldGenerationQueue, IDebugRender
}
//=======//
// debug //
//=======//
@Override
public void debugRender(DebugRenderer r)
{
@@ -44,7 +44,6 @@ public class WorldRemoteGenerationQueue implements IWorldGenerationQueue, IDebug
private final ConcurrentMap<DhSectionPos, WorldGenQueueEntry> waitingTasks = new ConcurrentHashMap<>();
private final Semaphore pendingTasksSemaphore = new Semaphore(Short.MAX_VALUE, true);
private int pendingTasks() { return Short.MAX_VALUE - pendingTasksSemaphore.availablePermits(); }
private CompletableFuture<?> genTaskPriorityRequest = CompletableFuture.completedFuture(null);
private final Semaphore genTaskPriorityRequestSemaphore = new Semaphore(1, true);
@@ -87,8 +86,8 @@ public class WorldRemoteGenerationQueue implements IWorldGenerationQueue, IDebug
{
if (generatorClosingFuture != null || !networkState.getClient().isReady()) return;
while (waitingTasks.size() > pendingTasks()
&& pendingTasks() < this.networkState.config.fullDataRequestRateLimit
while (getWaitingTaskCount() > getInProgressTaskCount()
&& getInProgressTaskCount() < this.networkState.config.fullDataRequestRateLimit
&& pendingTasksSemaphore.tryAcquire())
{
sendNewRequest(targetPos);
@@ -224,11 +223,15 @@ public class WorldRemoteGenerationQueue implements IWorldGenerationQueue, IDebug
{
ArrayList<String> lines = new ArrayList<>();
lines.add("World Remote Generation Queue ["+level.getClientLevelWrapper().getDimensionType().getDimensionName()+"]");
lines.add(" Requests: "+this.finishedRequests+" / "+(this.waitingTasks.size() + this.finishedRequests.get())+" (failed: "+ this.failedRequests+")");
lines.add(" Pending: "+this.pendingTasks()+" / "+this.networkState.config.fullDataRequestRateLimit);
lines.add("Requests: "+this.finishedRequests+" / "+(this.getWaitingTaskCount() + this.finishedRequests.get())+" (failed: "+ this.failedRequests+", rate limit: "+this.networkState.config.fullDataRequestRateLimit+")");
return lines.toArray(new String[0]);
}
@Override
public int getWaitingTaskCount() { return this.waitingTasks.size(); }
@Override
public int getInProgressTaskCount() { return Short.MAX_VALUE - pendingTasksSemaphore.availablePermits(); }
@Override
public CompletableFuture<Void> startClosing(boolean cancelCurrentGeneration, boolean alsoInterruptRunning)
{
@@ -1,17 +1,14 @@
package com.seibel.distanthorizons.core.level;
import com.seibel.distanthorizons.api.enums.rendering.EDebugRendering;
import com.seibel.distanthorizons.core.config.AppliedConfigState;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dataObjects.fullData.accessor.ChunkSizedFullDataAccessor;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.file.fullDatafile.IFullDataSourceProvider;
import com.seibel.distanthorizons.core.file.renderfile.RenderSourceFileHandler;
import com.seibel.distanthorizons.core.file.structure.AbstractSaveStructure;
import com.seibel.distanthorizons.core.generation.WorldRemoteGenerationQueue;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.logging.f3.F3Screen;
import com.seibel.distanthorizons.core.network.NetworkClient;
import com.seibel.distanthorizons.core.pos.DhBlockPos2D;
import com.seibel.distanthorizons.core.pos.DhLodPos;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
@@ -22,31 +19,31 @@ import com.seibel.distanthorizons.core.render.renderer.LodRenderer;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.coreapi.util.math.Mat4f;
import org.apache.logging.log4j.Logger;
import java.io.Closeable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicReference;
public class ClientLevelModule
public class ClientLevelModule implements Closeable
{
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);
this.parent = parent;
this.f3Message = new F3Screen.NestedMessage(this::f3Log);
}
//==============//
// tick methods //
//==============//
private EDebugRendering lastDebugRendering = EDebugRendering.OFF;
public void clientTick()
@@ -56,8 +53,9 @@ public class ClientLevelModule
{
return;
}
ClientRenderState clientRenderState = this.ClientRenderStateRef.get(); if (clientRenderState == null)
ClientRenderState clientRenderState = this.ClientRenderStateRef.get();
if (clientRenderState == null)
{
return;
}
@@ -70,40 +68,50 @@ public class ClientLevelModule
return;
}
clientRenderState.close(); 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.close(); return;
clientRenderState.close();
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.close();
return;
}
}
} clientRenderState.quadtree.tick(new DhBlockPos2D(MC_CLIENT.getPlayerBlockPos()));
boolean isBuffersDirty = false; EDebugRendering newDebugRendering = Config.Client.Advanced.Debugging.debugRendering.get();
clientRenderState.quadtree.tick(new DhBlockPos2D(MC_CLIENT.getPlayerBlockPos()));
boolean isBuffersDirty = false;
EDebugRendering newDebugRendering = Config.Client.Advanced.Debugging.debugRendering.get();
if (newDebugRendering != lastDebugRendering)
{
lastDebugRendering = newDebugRendering; isBuffersDirty = true;
lastDebugRendering = newDebugRendering;
isBuffersDirty = true;
}
if (isBuffersDirty)
{
clientRenderState.renderer.bufferHandler.MarkAllBuffersDirty();
}
}
//========//
// 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.close(); return false;
}
else
{
return true;
}
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.close();
return false;
}
else
{
return true;
}
}
public boolean isRendering()
@@ -113,27 +121,34 @@ public class ClientLevelModule
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);
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;
}
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 = this.ClientRenderStateRef.get();
if (ClientRenderState == null)
{
return;
}
}
} ClientRenderState.close();
ClientRenderState.close();
}
//===============//
@@ -141,47 +156,54 @@ public class ClientLevelModule
//===============//
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);
}
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);
}
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;
}
}
ClientRenderState ClientRenderState = this.ClientRenderStateRef.get();
if (ClientRenderState != null)
{
ClientRenderState.close();
// 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.close();
}
}
} f3Message.close();
this.f3Message.close();
}
@@ -199,30 +221,34 @@ public class ClientLevelModule
/** 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"};
}
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();
}
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);
}
ClientRenderState clientRenderState = this.ClientRenderStateRef.get();
if (clientRenderState != null && clientRenderState.quadtree != null)
{
clientRenderState.quadtree.reloadPos(pos);
}
}
public static class ClientRenderState
@@ -236,15 +262,19 @@ public class ClientLevelModule
public final LodRenderer renderer;
public ClientRenderState(
IDhClientLevel dhClientLevel, IFullDataSourceProvider fullDataSourceProvider, AbstractSaveStructure saveStructure)
IDhClientLevel dhClientLevel, IFullDataSourceProvider fullDataSourceProvider,
AbstractSaveStructure saveStructure)
{
this.levelWrapper = dhClientLevel.getLevelWrapper(); this.renderSourceFileHandler = new RenderSourceFileHandler(fullDataSourceProvider, dhClientLevel, saveStructure);
this.levelWrapper = dhClientLevel.getLevelWrapper();
this.renderSourceFileHandler = new RenderSourceFileHandler(fullDataSourceProvider, dhClientLevel, saveStructure);
this.quadtree = new LodQuadTree(dhClientLevel, Config.Client.Advanced.Graphics.Quality.lodChunkRenderDistance.get() * LodUtil.CHUNK_WIDTH,
// initial position is (0,0) just in case the player hasn't loaded in yet, the tree will be moved once the level starts ticking
0, 0, this.renderSourceFileHandler);
0, 0,
this.renderSourceFileHandler);
RenderBufferHandler renderBufferHandler = new RenderBufferHandler(this.quadtree); this.renderer = new LodRenderer(renderBufferHandler);
RenderBufferHandler renderBufferHandler = new RenderBufferHandler(this.quadtree);
this.renderer = new LodRenderer(renderBufferHandler);
}
@@ -253,7 +283,9 @@ public class ClientLevelModule
{
LOGGER.info("Shutting down " + ClientRenderState.class.getSimpleName());
this.renderer.close(); this.quadtree.close(); this.renderSourceFileHandler.close();
this.renderer.close();
this.quadtree.close();
this.renderSourceFileHandler.close();
}
}
@@ -32,7 +32,8 @@ public class DhClientServerLevel extends DhLevel implements IDhClientLevel, IDhS
public final ClientLevelModule clientside;
private final IServerLevelWrapper serverLevelWrapper;
public IClientLevelWrapper clientLevelWrapper;
public DhClientServerLevel(AbstractSaveStructure saveStructure, IServerLevelWrapper serverLevelWrapper)
{
@@ -41,11 +42,13 @@ public class DhClientServerLevel extends DhLevel implements IDhClientLevel, IDhS
LOGGER.warn("unable to create data folder.");
}
this.serverLevelWrapper = serverLevelWrapper;
serverside = new ServerLevelModule(this, serverLevelWrapper, saveStructure);
clientside = new ClientLevelModule(this);
this.serverside = new ServerLevelModule(this, serverLevelWrapper, saveStructure);
this.clientside = new ClientLevelModule(this);
LOGGER.info("Started " + DhClientServerLevel.class.getSimpleName() + " for " + serverLevelWrapper + " with saves at " + saveStructure);
}
//==============//
// tick methods //
//==============//
@@ -105,11 +108,7 @@ public class DhClientServerLevel extends DhLevel implements IDhClientLevel, IDhS
clientside.startRenderer();
}
public void stopRenderer()
{
clientside.stopRenderer();
clientLevelWrapper = null;
}
public void stopRenderer() { this.clientside.stopRenderer(); }
//================//
// level handling //
@@ -3,6 +3,7 @@ package com.seibel.distanthorizons.core.level;
import com.seibel.distanthorizons.core.file.fullDatafile.GeneratedFullDataFileHandler;
import com.seibel.distanthorizons.core.generation.IWorldGenerationQueue;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.logging.f3.F3Screen;
import com.seibel.distanthorizons.core.pos.DhBlockPos2D;
import org.apache.logging.log4j.Logger;
@@ -16,7 +17,9 @@ public class WorldGenModule implements Closeable
private final GeneratedFullDataFileHandler dataFileHandler;
private final GeneratedFullDataFileHandler.IOnWorldGenCompleteListener onWorldGenCompleteListener;
private final AtomicReference<WorldGenState> worldGenStateRef = new AtomicReference<>();
private final F3Screen.DynamicMessage worldGenF3Message;
public static abstract class WorldGenState
{
@@ -48,6 +51,22 @@ public class WorldGenModule implements Closeable
{
this.dataFileHandler = dataFileHandler;
this.onWorldGenCompleteListener = onWorldGenCompleteListener;
this.worldGenF3Message = new F3Screen.DynamicMessage(() ->
{
WorldGenState worldGenState = this.worldGenStateRef.get();
if (worldGenState != null)
{
int waiting = worldGenState.worldGenerationQueue.getWaitingTaskCount();
int inProgress = worldGenState.worldGenerationQueue.getInProgressTaskCount();
return "World Gen Tasks: "+waiting+", (in progress: "+inProgress+")";
}
else
{
return "World Gen Disabled";
}
});
}
public void startWorldGen(GeneratedFullDataFileHandler dataFileHandler, WorldGenState newWgs)
@@ -121,5 +140,6 @@ public class WorldGenModule implements Closeable
}
}
dataFileHandler.close();
this.worldGenF3Message.close();
}
}
@@ -329,8 +329,6 @@ public class DebugRenderer
public void render(Mat4f transform)
{
if (!Config.Client.Advanced.Debugging.debugWireframeRendering.get()) return;
transform_this_frame = transform;
Vec3d cam = MC_RENDER.getCameraExactPosition();
camf = new Vec3f((float) cam.x, (float) cam.y, (float) cam.z);
@@ -301,7 +301,15 @@ public class LodRenderer
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, 0);
this.shaderProgram.unbind();
DebugRenderer.INSTANCE.render(modelViewProjectionMatrix);
if (Config.Client.Advanced.Debugging.debugWireframeRendering.get())
{
profiler.popPush("Debug wireframes");
// Note: this can be very slow if a lot of boxes are being rendered
DebugRenderer.INSTANCE.render(modelViewProjectionMatrix);
profiler.popPush("LOD cleanup");
}
GL32.glClear(GL32.GL_DEPTH_BUFFER_BIT);
minecraftGlState.restore();
@@ -15,6 +15,8 @@ public class RateLimitedThreadPoolExecutor extends ThreadPoolExecutor
/** How long it took this thread to run its last task */
private final ThreadLocal<Long> lastRunDurationNanoTimeRef = ThreadLocal.withInitial(() -> -1L);
private Runnable onTerminatedEventHandler = null;
//==============//
@@ -63,4 +65,23 @@ public class RateLimitedThreadPoolExecutor extends ThreadPoolExecutor
this.lastRunDurationNanoTimeRef.set(System.nanoTime() - this.runStartNanoTimeRef.get());
}
@Override
protected void terminated()
{
super.terminated();
if (this.onTerminatedEventHandler != null)
{
this.onTerminatedEventHandler.run();
}
}
//==============//
// custom logic //
//==============//
/** only one event handler can be present at a time */
public void setOnTerminatedEventHandler(Runnable runnable) { this.onTerminatedEventHandler = runnable; }
}
@@ -19,9 +19,9 @@ import java.util.concurrent.ExecutorService;
public class DhClientServerWorld extends AbstractDhWorld implements IDhClientWorld, IDhServerWorld
{
private final HashMap<ILevelWrapper, DhClientServerLevel> levelObjMap;
private final HashSet<DhClientServerLevel> dhLevels;
public final LocalSaveStructure saveStructure;
private final HashMap<ILevelWrapper, DhClientServerLevel> levelWrapperByDhLevel = new HashMap<>();
private final HashSet<DhClientServerLevel> dhLevels = new HashSet<>();
public final LocalSaveStructure saveStructure = new LocalSaveStructure();
public ExecutorService dhTickerThread = ThreadUtil.makeSingleThreadPool("DH Client Server World Ticker Thread", 2);
public EventLoop eventLoop = new EventLoop(this.dhTickerThread, this::_clientTick); //TODO: Rate-limit the loop
@@ -30,12 +30,13 @@ public class DhClientServerWorld extends AbstractDhWorld implements IDhClientWor
//=============//
// constructor //
//=============//
public DhClientServerWorld()
{
super(EWorldEnvironment.Client_Server);
this.saveStructure = new LocalSaveStructure();
this.levelObjMap = new HashMap<>();
this.dhLevels = new HashSet<>();
LOGGER.info("Started DhWorld of type " + this.environment);
@@ -44,12 +45,16 @@ public class DhClientServerWorld extends AbstractDhWorld implements IDhClientWor
//=========//
// methods //
//=========//
@Override
public DhClientServerLevel getOrLoadLevel(ILevelWrapper wrapper)
{
if (wrapper instanceof IServerLevelWrapper)
{
return this.levelObjMap.computeIfAbsent(wrapper, (levelWrapper) ->
return this.levelWrapperByDhLevel.computeIfAbsent(wrapper, (levelWrapper) ->
{
File levelFile = this.saveStructure.getLevelFolder(levelWrapper);
LodUtil.assertTrue(levelFile != null);
@@ -60,7 +65,7 @@ public class DhClientServerWorld extends AbstractDhWorld implements IDhClientWor
}
else
{
return this.levelObjMap.computeIfAbsent(wrapper, (levelWrapper) ->
return this.levelWrapperByDhLevel.computeIfAbsent(wrapper, (levelWrapper) ->
{
IClientLevelWrapper clientLevelWrapper = (IClientLevelWrapper) levelWrapper;
IServerLevelWrapper serverLevelWrapper = clientLevelWrapper.tryGetServerSideWrapper();
@@ -68,7 +73,7 @@ public class DhClientServerWorld extends AbstractDhWorld implements IDhClientWor
LodUtil.assertTrue(clientLevelWrapper.getDimensionType().equals(serverLevelWrapper.getDimensionType()), "tryGetServerSideWrapper returned a level for a different dimension. ClientLevelWrapper dim: " + clientLevelWrapper.getDimensionType().getDimensionName() + " ServerLevelWrapper dim: " + serverLevelWrapper.getDimensionType().getDimensionName());
DhClientServerLevel level = this.levelObjMap.get(serverLevelWrapper);
DhClientServerLevel level = this.levelWrapperByDhLevel.get(serverLevelWrapper);
if (level == null)
{
return null;
@@ -81,7 +86,7 @@ public class DhClientServerWorld extends AbstractDhWorld implements IDhClientWor
}
@Override
public DhClientServerLevel getLevel(ILevelWrapper wrapper) { return this.levelObjMap.get(wrapper); }
public DhClientServerLevel getLevel(ILevelWrapper wrapper) { return this.levelWrapperByDhLevel.get(wrapper); }
@Override
public Iterable<? extends IDhLevel> getAllLoadedLevels() { return this.dhLevels; }
@@ -89,21 +94,23 @@ public class DhClientServerWorld extends AbstractDhWorld implements IDhClientWor
@Override
public void unloadLevel(ILevelWrapper wrapper)
{
if (this.levelObjMap.containsKey(wrapper))
if (this.levelWrapperByDhLevel.containsKey(wrapper))
{
if (wrapper instanceof IServerLevelWrapper)
{
LOGGER.info("Unloading level " + this.levelObjMap.get(wrapper));
DhClientServerLevel clientServerLevel = this.levelObjMap.remove(wrapper);
this.dhLevels.remove(clientServerLevel);
LOGGER.info("Unloading level " + this.levelWrapperByDhLevel.get(wrapper));
wrapper.onUnload();
DhClientServerLevel clientServerLevel = this.levelWrapperByDhLevel.remove(wrapper);
clientServerLevel.close();
this.dhLevels.remove(clientServerLevel);
}
else
{
// If the level wrapper is a Client Level Wrapper, then that means the client side leaves the level,
// but note that the server side still has the level loaded. So, we don't want to unload the level,
// we just want to stop rendering it.
this.levelObjMap.remove(wrapper).stopRenderer(); // Ignore resource warning. The level obj is referenced elsewhere.
this.levelWrapperByDhLevel.remove(wrapper).stopRenderer(); // Ignore resource warning. The level obj is referenced elsewhere.
}
}
}
@@ -137,10 +144,18 @@ public class DhClientServerWorld extends AbstractDhWorld implements IDhClientWor
for (DhClientServerLevel level : this.dhLevels)
{
LOGGER.info("Unloading level " + level.getServerLevelWrapper().getDimensionType().getDimensionName());
// level wrapper shouldn't be null, but just in case
IServerLevelWrapper serverLevelWrapper = level.getServerLevelWrapper();
if (serverLevelWrapper != null)
{
serverLevelWrapper.onUnload();
}
level.close();
}
this.levelObjMap.clear();
this.levelWrapperByDhLevel.clear();
this.eventLoop.close();
LOGGER.info("Closed DhWorld of type " + this.environment);
}
@@ -28,8 +28,7 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
@CheckForNull
private final ClientNetworkState networkState;
// TODO why does this executor have 2 threads?
public ExecutorService dhTickerThread = ThreadUtil.makeSingleThreadPool("DH Client World Ticker Thread", 2);
public ExecutorService dhTickerThread = ThreadUtil.makeSingleThreadPool("DH Client World Ticker Thread");
public EventLoop eventLoop = new EventLoop(this.dhTickerThread, this::_clientTick);
@@ -111,6 +110,7 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
if (this.levels.containsKey(wrapper))
{
LOGGER.info("Unloading level " + this.levels.get(wrapper));
wrapper.onUnload();
this.levels.remove(wrapper).close();
}
}
@@ -145,6 +145,14 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
for (DhClientLevel dhClientLevel : this.levels.values())
{
LOGGER.info("Unloading level " + dhClientLevel.getLevelWrapper().getDimensionType().getDimensionName());
// level wrapper shouldn't be null, but just in case
IClientLevelWrapper clientLevelWrapper = dhClientLevel.getClientLevelWrapper();
if (clientLevelWrapper != null)
{
clientLevelWrapper.onUnload();
}
dhClientLevel.close();
}
@@ -28,6 +28,10 @@ public class DhServerWorld extends AbstractDhWorld implements IDhServerWorld
//==============//
// constructors //
//==============//
public DhServerWorld()
{
super(EWorldEnvironment.Server_Only);
@@ -54,6 +58,12 @@ public class DhServerWorld extends AbstractDhWorld implements IDhServerWorld
});
}
//=========//
// methods //
//=========//
public void addPlayer(IServerPlayerWrapper serverPlayer)
{
this.remotePlayerConnectionHandler.registerJoinedPlayer(serverPlayer);
@@ -111,6 +121,7 @@ public class DhServerWorld extends AbstractDhWorld implements IDhServerWorld
if (this.levels.containsKey(wrapper))
{
LOGGER.info("Unloading level {} ", this.levels.get(wrapper));
wrapper.onUnload();
this.levels.remove(wrapper).close();
}
}
@@ -146,6 +157,14 @@ public class DhServerWorld extends AbstractDhWorld implements IDhServerWorld
for (DhServerLevel level : this.levels.values())
{
LOGGER.info("Unloading level " + level.getLevelWrapper().getDimensionType().getDimensionName());
// level wrapper shouldn't be null, but just in case
IServerLevelWrapper serverLevelWrapper = level.getServerLevelWrapper();
if (serverLevelWrapper != null)
{
serverLevelWrapper.onUnload();
}
level.close();
}
@@ -39,10 +39,6 @@ public interface ILevelWrapper extends IDhApiLevelWrapper, IBindable
@Override
IDhApiDimensionTypeWrapper getDimensionType();
int getBlockLight(int x, int y, int z);
int getSkyLight(int x, int y, int z);
@Override
boolean hasCeiling();
@@ -65,7 +61,7 @@ public interface ILevelWrapper extends IDhApiLevelWrapper, IBindable
@Deprecated
IBiomeWrapper getBiome(DhBlockPos pos);
// TODO implement onUnload
// necessary so ChunkToLodBuilder can have its cache cleared after the level closes
/** Fired when the level is being unloaded. Doesn't unload the level. */
void onUnload();
}
@@ -257,7 +257,7 @@
"distanthorizons.config.client.advanced.graphics.advancedGraphics.earthCurveRatio":
"Earth Curve Ratio §6(EXPERIMENTAL)§r",
"distanthorizons.config.client.advanced.graphics.advancedGraphics.earthCurveRatio.@tooltip":
"A value of 1 is equivalent to the curvature of Earth in real life.",
"A value of 1 is equivalent to the curvature of Earth in real life. \nThe minimum accepted value is 50 and the maximum value is 5000. \nEverything between 1 and 49 will be rounded up to 50.",
"distanthorizons.config.client.advanced.graphics.advancedGraphics.lodBias":
"LOD Bias §6(Affects vanilla terrain)§r",
"distanthorizons.config.client.advanced.graphics.advancedGraphics.lodBias.@tooltip":
+20 -34
View File
@@ -8,6 +8,8 @@ out vec4 vertexColor;
out vec3 vertexWorldPos;
out float vertexYPos;
uniform bool whiteWorld;
uniform mat4 combinedMatrix;
uniform vec3 modelOffset;
uniform float worldYOffset;
@@ -19,6 +21,8 @@ uniform float mircoOffset;
uniform float earthRadius;
/**
* TODO in the future this and standard.vert should be merged together to prevent inconsistencies between the two
*
* Vertex Shader
*
* author: James Seibel
@@ -51,43 +55,25 @@ void main()
vertexWorldPos.x += mx;
vertexWorldPos.y += my;
vertexWorldPos.z += mz;
#if 0
// Old (disabled) vertex transformation logic - Leetom
// Calculate the vertex pos due to curvature of the earth
// We use spherical coordinates to calculate the vertex position
//if (vertexWorldPos.x == 0.0 && vertexWorldPos.z == 0.0)
//{
// // In the center. No curvature needed
//}
//else
//{
float theta = atan(vertexWorldPos.z, vertexWorldPos.x); // in radians (-pi, pi)
float trueY = earthRadius + vertexWorldPos.y; // true Y position, or height
float phi = sqrt(vertexWorldPos.z * vertexWorldPos.z + vertexWorldPos.x * vertexWorldPos.x) / trueY;
// Convert spherical coordinates to cartesian coordinates
vertexWorldPos.x = trueY * sin(phi) * cos(theta);
vertexWorldPos.z = trueY * sin(phi) * sin(theta);
vertexWorldPos.y = trueY * cos(phi) - earthRadius;
//}
#else
// new vertex transformation logic - stduhpf
float localRadius = earthRadius + vertexYPos;// vertexWorldPos.y + cameraPosition.y - Center_Y;
float phi = length(vertexWorldPos.xz) / localRadius;
vertexWorldPos.y += (cos(phi) - 1.0) * localRadius;
vertexWorldPos.xz = vertexWorldPos.xz * sin(phi) / phi;
#endif
// vertex transformation logic - stduhpf
float localRadius = earthRadius + vertexYPos;
float phi = length(vertexWorldPos.xz) / localRadius;
vertexWorldPos.y += (cos(phi) - 1.0) * localRadius;
vertexWorldPos.xz = vertexWorldPos.xz * sin(phi) / phi;
uint lights = meta & 0xFFu;
float light2 = (mod(float(lights), 16.0) + 0.5) / 16.0;
float light = (float(lights / 16u) + 0.5) / 16.0;
vertexColor = color * vec4(texture(lightMap, vec2(light, light2)).xyz, 1.0);
vertexColor = vec4(texture(lightMap, vec2(light, light2)).xyz, 1.0);
if (!whiteWorld)
{
vertexColor *= color;
}
gl_Position = combinedMatrix * vec4(vertexWorldPos, 1.0);
}
+6 -5
View File
@@ -46,16 +46,17 @@ void main()
{
vec3 samplePos = vec3(0.0) + (TBN * gKernel[i]);
samplePos = viewPos + samplePos * gSampleRad;
vec4 offset = gProj * vec4(samplePos + viewPos, 1.0);
offset.xy /= offset.w;
offset.xy = offset.xy * HALF_2 + HALF_2;
float geometryDepth = calcViewPosition(offset.xy).z;
float rangeCheck = smoothstep(0.0, 1.0, gSampleRad / abs(viewPos.z - geometryDepth));
occlusion_factor += float(geometryDepth >= samplePos.z + 0.05) * rangeCheck;
// the number added to the samplePos.z can be used to reduce noise in the SSAO application at the cost of reducing the overall affect
occlusion_factor += float(geometryDepth >= samplePos.z + 1.0) * rangeCheck;
}
float visibility_factor = 1.0 - (occlusion_factor / MAX_KERNEL_SIZE);
@@ -20,6 +20,8 @@ uniform float mircoOffset;
/**
* TODO in the future this and curve.vert should be merged together to prevent inconsistencies between the two
*
* Vertex Shader
*
* author: James Seibel