Potentially fix lag when moving across chunk borders

This commit is contained in:
James Seibel
2023-08-15 20:49:22 -05:00
parent 300834f582
commit 55bf122bc1
5 changed files with 64 additions and 40 deletions
@@ -22,6 +22,7 @@ package com.seibel.distanthorizons.core.api.internal;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiLevelLoadEvent;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiLevelUnloadEvent;
import com.seibel.distanthorizons.core.generation.DhLightingEngine;
import com.seibel.distanthorizons.core.util.ThreadUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper;
import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector;
import com.seibel.distanthorizons.core.level.IDhLevel;
@@ -37,6 +38,7 @@ import org.apache.logging.log4j.Logger;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ThreadPoolExecutor;
/**
* This holds the methods that should be called by the host mod loader (Fabric,
@@ -48,6 +50,14 @@ public class ServerApi
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
private static final ThreadPoolExecutor LIGHT_POPULATOR_THREAD_POOL = ThreadUtil.makeRateLimitedThreadPool(
// thread count doesn't need to be very high since the player can only move so fast, 1 should be plenty
(Runtime.getRuntime().availableProcessors() <= 12) ? 1 : 2,
"Server Light Populator",
// only run the thread 50% of the time to prevent lagging the server thread
0.5,
ThreadUtil.MINIMUM_RELATIVE_PRIORITY);
private int lastWorldGenTickDelta = 0;
@@ -56,9 +66,9 @@ public class ServerApi
// =============//
// tick events //
// =============//
//=============//
// tick events //
//=============//
public void serverTickEvent()
{
@@ -152,40 +162,48 @@ public class ServerApi
public void serverChunkSaveEvent(IChunkWrapper chunk, ILevelWrapper level)
{
AbstractDhWorld dhWorld = SharedApi.getAbstractDhWorld();
if (dhWorld != null)
if (dhWorld == null)
{
IDhLevel dhLevel = SharedApi.getAbstractDhWorld().getLevel(level);
if (dhLevel != null)
return;
}
IDhLevel dhLevel = SharedApi.getAbstractDhWorld().getLevel(level);
if (dhLevel == null)
{
return;
}
// lighting the chunk needs to be done outside the event thread to prevent lagging the server thread
LIGHT_POPULATOR_THREAD_POOL.execute(() ->
{
// Save or populate the chunk wrapper's lighting
// this is done so we don't have to worry about MC unloading the lighting data for this chunk
if (chunk.isLightCorrect())
{
// Save or populate the chunk wrapper's lighting
// this is done so we don't have to worry about MC unloading the lighting data for this chunk
if (chunk.isLightCorrect())
try
{
try
{
chunk.bakeDhLightingUsingMcLightingEngine();
chunk.setUseDhLighting(true);
}
catch (IllegalStateException e)
{
LOGGER.warn(e.getMessage(), e);
}
}
else
{
// generate the chunk's lighting, ignoring neighbors.
// not a perfect solution, but should prevent chunks from having completely broken lighting
List<IChunkWrapper> nearbyChunkList = new LinkedList<>();
nearbyChunkList.add(chunk);
DhLightingEngine.INSTANCE.lightChunks(chunk, nearbyChunkList, level.hasSkyLight() ? 15 : 0);
// If MC's lighting engine isn't thread safe this may cause the server thread to lag
chunk.bakeDhLightingUsingMcLightingEngine();
chunk.setUseDhLighting(true);
}
dhLevel.updateChunkAsync(chunk);
catch (IllegalStateException e)
{
LOGGER.warn(e.getMessage(), e);
}
}
}
else
{
// generate the chunk's lighting, ignoring neighbors.
// not a perfect solution, but should prevent chunks from having completely broken lighting
List<IChunkWrapper> nearbyChunkList = new LinkedList<>();
nearbyChunkList.add(chunk);
DhLightingEngine.INSTANCE.lightChunks(chunk, nearbyChunkList, level.hasSkyLight() ? 15 : 0);
chunk.setUseDhLighting(true);
}
dhLevel.updateChunkAsync(chunk);
});
}
public void serverPlayerJoinEvent(IServerPlayerWrapper player)
@@ -12,6 +12,7 @@ import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.objects.quadTree.QuadNode;
import com.seibel.distanthorizons.core.util.objects.quadTree.QuadTree;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper;
import com.seibel.distanthorizons.coreapi.util.MathUtil;
import org.apache.logging.log4j.Logger;
@@ -9,7 +9,7 @@ import java.util.concurrent.*;
public class ThreadUtil
{
public static int MINIMUM_RELATIVE_PRIORITY = -5;
public static int MINIMUM_RELATIVE_PRIORITY = -4;
public static int DEFAULT_RELATIVE_PRIORITY = 0;
// TODO currently only used for RateLimitedThreadPools could this be used for all thread pools?
@@ -20,7 +20,7 @@ public class ThreadUtil
// create rate limited thread pool //
public static RateLimitedThreadPoolExecutor makeRateLimitedThreadPool(int poolSize, String name, ConfigEntry<Double> runTimeRatioConfigEntry) { return makeRateLimitedThreadPool(poolSize, name, 0, runTimeRatioConfigEntry); }
public static RateLimitedThreadPoolExecutor makeRateLimitedThreadPool(int poolSize, String name, ConfigEntry<Double> runTimeRatioConfigEntry) { return makeRateLimitedThreadPool(poolSize, name, DEFAULT_RELATIVE_PRIORITY, runTimeRatioConfigEntry); }
public static RateLimitedThreadPoolExecutor makeRateLimitedThreadPool(int poolSize, String name, int relativePriority, ConfigEntry<Double> runTimeRatioConfigEntry)
{
// remove the old listener if one exists
@@ -31,7 +31,7 @@ public class ThreadUtil
THREAD_CHANGE_LISTENERS_BY_THREAD_NAME.remove(name);
}
RateLimitedThreadPoolExecutor executor = new RateLimitedThreadPoolExecutor(poolSize, runTimeRatioConfigEntry.get(), new DhThreadFactory("DH-" + name, Thread.NORM_PRIORITY + relativePriority));
RateLimitedThreadPoolExecutor executor = makeRateLimitedThreadPool(poolSize, name, runTimeRatioConfigEntry.get(), relativePriority);
ConfigChangeListener<Double> changeListener = new ConfigChangeListener<>(runTimeRatioConfigEntry, (newRunTimeRatio) -> { executor.runTimeRatio = newRunTimeRatio; });
THREAD_CHANGE_LISTENERS_BY_THREAD_NAME.put(name, changeListener);
@@ -39,6 +39,12 @@ public class ThreadUtil
return executor;
}
/** should only be used if there isn't a config controlling the run time ratio of this thread pool */
public static RateLimitedThreadPoolExecutor makeRateLimitedThreadPool(int poolSize, String name, Double runTimeRatio, int relativePriority)
{
return new RateLimitedThreadPoolExecutor(poolSize, runTimeRatio, new DhThreadFactory("DH-" + name, Thread.NORM_PRIORITY + relativePriority));
}
// create thread pool //
@@ -54,16 +60,16 @@ public class ThreadUtil
}
public static ThreadPoolExecutor makeThreadPool(int poolSize, Class<?> clazz, int relativePriority) { return makeThreadPool(poolSize, clazz.getSimpleName(), relativePriority); }
public static ThreadPoolExecutor makeThreadPool(int poolSize, String name) { return makeThreadPool(poolSize, name, 0); }
public static ThreadPoolExecutor makeThreadPool(int poolSize, Class<?> clazz) { return makeThreadPool(poolSize, clazz.getSimpleName(), 0); }
public static ThreadPoolExecutor makeThreadPool(int poolSize, String name) { return makeThreadPool(poolSize, name, DEFAULT_RELATIVE_PRIORITY); }
public static ThreadPoolExecutor makeThreadPool(int poolSize, Class<?> clazz) { return makeThreadPool(poolSize, clazz.getSimpleName(), DEFAULT_RELATIVE_PRIORITY); }
// create single thread pool //
public static ThreadPoolExecutor makeSingleThreadPool(String name, int relativePriority) { return makeThreadPool(1, name, relativePriority); }
public static ThreadPoolExecutor makeSingleThreadPool(Class<?> clazz, int relativePriority) { return makeThreadPool(1, clazz.getSimpleName(), relativePriority); }
public static ThreadPoolExecutor makeSingleThreadPool(String name) { return makeThreadPool(1, name, 0); }
public static ThreadPoolExecutor makeSingleThreadPool(Class<?> clazz) { return makeThreadPool(1, clazz.getSimpleName(), 0); }
public static ThreadPoolExecutor makeSingleThreadPool(String name) { return makeThreadPool(1, name, DEFAULT_RELATIVE_PRIORITY); }
public static ThreadPoolExecutor makeSingleThreadPool(Class<?> clazz) { return makeThreadPool(1, clazz.getSimpleName(), DEFAULT_RELATIVE_PRIORITY); }
}
@@ -49,7 +49,7 @@ public class DhThreadFactory implements ThreadFactory
{
if (priority < Thread.MIN_PRIORITY || priority > Thread.MAX_PRIORITY)
{
throw new IllegalArgumentException("Thread priority should be [" + Thread.MIN_PRIORITY + "-" + Thread.MAX_PRIORITY + "]!");
throw new IllegalArgumentException("Thread priority [" + priority + "] out of bounds. Priority should be between [" + Thread.MIN_PRIORITY + "-" + Thread.MAX_PRIORITY + "]!");
}
this.threadName = newThreadName + " Thread";
@@ -23,7 +23,6 @@ public class DhClientServerWorld extends AbstractDhWorld implements IDhClientWor
private final HashSet<DhClientServerLevel> dhLevels;
public final LocalSaveStructure saveStructure;
// TODO why does this executor have 2 threads?
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