diff --git a/core/src/main/java/com/seibel/distanthorizons/core/api/internal/ServerApi.java b/core/src/main/java/com/seibel/distanthorizons/core/api/internal/ServerApi.java index 3d517cebe..0d9e1b2e0 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/api/internal/ServerApi.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/api/internal/ServerApi.java @@ -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 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 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) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/render/LodQuadTree.java b/core/src/main/java/com/seibel/distanthorizons/core/render/LodQuadTree.java index 6bf058f38..8774309f6 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/render/LodQuadTree.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/render/LodQuadTree.java @@ -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; diff --git a/core/src/main/java/com/seibel/distanthorizons/core/util/ThreadUtil.java b/core/src/main/java/com/seibel/distanthorizons/core/util/ThreadUtil.java index af9494cd9..6ae635ad8 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/util/ThreadUtil.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/util/ThreadUtil.java @@ -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 runTimeRatioConfigEntry) { return makeRateLimitedThreadPool(poolSize, name, 0, runTimeRatioConfigEntry); } + public static RateLimitedThreadPoolExecutor makeRateLimitedThreadPool(int poolSize, String name, ConfigEntry runTimeRatioConfigEntry) { return makeRateLimitedThreadPool(poolSize, name, DEFAULT_RELATIVE_PRIORITY, runTimeRatioConfigEntry); } public static RateLimitedThreadPoolExecutor makeRateLimitedThreadPool(int poolSize, String name, int relativePriority, ConfigEntry 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 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); } } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/util/objects/DhThreadFactory.java b/core/src/main/java/com/seibel/distanthorizons/core/util/objects/DhThreadFactory.java index d5ac954cc..305f7c0fd 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/util/objects/DhThreadFactory.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/util/objects/DhThreadFactory.java @@ -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"; diff --git a/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientServerWorld.java b/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientServerWorld.java index db97abbbe..6dd3317c9 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientServerWorld.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientServerWorld.java @@ -23,7 +23,6 @@ public class DhClientServerWorld extends AbstractDhWorld implements IDhClientWor private final HashSet 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