From ff8b1f24d0ce48b0d45c4be91430b0aace0cbc36 Mon Sep 17 00:00:00 2001 From: s809 <43530948+s809@users.noreply.github.com> Date: Mon, 18 Dec 2023 19:17:14 +0500 Subject: [PATCH] Merge branch 'main' of https://gitlab.com/jeseibel/distant-horizons-core --- .../com/seibel/distanthorizons/api/DhApi.java | 17 +- .../distanthorizons/coreapi/ModInfo.java | 2 + .../core/api/internal/SharedApi.java | 76 +++++--- .../interfaces/IIncompleteFullDataSource.java | 4 +- .../fullDatafile/FullDataFileHandler.java | 11 +- .../file/fullDatafile/FullDataMetaFile.java | 162 ++++++++++-------- .../distanthorizons/core/util/ThreadUtil.java | 4 +- 7 files changed, 179 insertions(+), 97 deletions(-) diff --git a/api/src/main/java/com/seibel/distanthorizons/api/DhApi.java b/api/src/main/java/com/seibel/distanthorizons/api/DhApi.java index 3cabd3b6b..a069e5da3 100644 --- a/api/src/main/java/com/seibel/distanthorizons/api/DhApi.java +++ b/api/src/main/java/com/seibel/distanthorizons/api/DhApi.java @@ -52,7 +52,7 @@ public class DhApi { /** * If you can see this Java Doc, this can be ignored.
- * This is just to you know that Javadocs are available and that you should use the API jar + * This is just to let you know that Javadocs are available and that you should use the API jar * instead of the full mod jar.

* * Note: Don't use this string in your code. It may change and is only for reference. @@ -118,7 +118,11 @@ public class DhApi + //==================// // always available // + //==================// + + // interfaces // /** * Used to bind/unbind Distant Horizons Api events. @@ -139,6 +143,8 @@ public class DhApi public static final IOverrideInjector overrides = OverrideInjector.INSTANCE; + // getters // + /** * This version should only be updated when breaking changes are introduced to the Distant Horizons API. * @since API 1.0.0 @@ -174,4 +180,13 @@ public class DhApi */ public static int getNetworkProtocolVersion() { return ModInfo.PROTOCOL_VERSION; } + + // methods // + + /** + * Returns true if the thread this method was called from is owned by Distant Horizons. + * @since API 1.1.0 + */ + public static boolean isDhThread() { return Thread.currentThread().getName().startsWith(ModInfo.THREAD_NAME_PREFIX); } + } diff --git a/api/src/main/java/com/seibel/distanthorizons/coreapi/ModInfo.java b/api/src/main/java/com/seibel/distanthorizons/coreapi/ModInfo.java index 9283d700a..8e35bebcd 100644 --- a/api/src/main/java/com/seibel/distanthorizons/coreapi/ModInfo.java +++ b/api/src/main/java/com/seibel/distanthorizons/coreapi/ModInfo.java @@ -48,5 +48,7 @@ public final class ModInfo public static final String NETWORKING_RESOURCE_NAMESPACE = "distant_horizons"; public static final String MULTIVERSE_PLUGIN_NAMESPACE = "world_control"; + /** All DH owned threads should start with this string to allow for easier debugging and profiling. */ + public static String THREAD_NAME_PREFIX = "DH-"; } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/api/internal/SharedApi.java b/core/src/main/java/com/seibel/distanthorizons/core/api/internal/SharedApi.java index fcc4f5f47..b77a95be6 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/api/internal/SharedApi.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/api/internal/SharedApi.java @@ -25,8 +25,10 @@ import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.generation.DhLightingEngine; import com.seibel.distanthorizons.core.level.IDhLevel; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; +import com.seibel.distanthorizons.core.logging.f3.F3Screen; import com.seibel.distanthorizons.core.pos.DhChunkPos; import com.seibel.distanthorizons.core.render.renderer.DebugRenderer; +import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.util.objects.Pair; import com.seibel.distanthorizons.core.util.threading.ThreadPools; import com.seibel.distanthorizons.core.world.*; @@ -52,14 +54,18 @@ public class SharedApi private static final Logger LOGGER = DhLoggerBuilder.getLogger(); private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class); private static final Set UPDATING_CHUNK_SET = ConcurrentHashMap.newKeySet(); - private static final int MAX_UPDATING_CHUNK_COUNT_PER_THREAD = 100; + /** how many chunks can be queued for updating per thread, used to prevent updates from infinitely pilling up if the user flys around extremely fast */ + private static final int MAX_UPDATING_CHUNK_COUNT_PER_THREAD = 500; + private static final int MIN_MS_BETWEEN_OVERLOADED_LOG_MESSAGE = 5_000; + private static final Timer CHUNK_UPDATE_TIMER = new Timer(); private static AbstractDhWorld currentWorld; private static int lastWorldGenTickDelta = 0; + private static long lastOverloadedLogMessageMsTime = 0; - private static final Timer CHUNK_UPDATE_TIMER = new Timer(); + public F3Screen.DynamicMessage f3Message; @@ -67,7 +73,14 @@ public class SharedApi // constructor // //=============// - private SharedApi() { } + private SharedApi() + { + this.f3Message = new F3Screen.DynamicMessage(() -> + { + int maxUpdateCount = MAX_UPDATING_CHUNK_COUNT_PER_THREAD * Config.Client.Advanced.MultiThreading.numberOfLodBuilderThreads.get(); + return LodUtil.formatLog("Queued chunk updates: " + UPDATING_CHUNK_SET.size() + " / " + maxUpdateCount); + }); + } public static void init() { Initializer.init(); } @@ -148,6 +161,10 @@ public class SharedApi public void applyChunkUpdate(IChunkWrapper chunkWrapper, ILevelWrapper level, boolean updateNeighborChunks) { + //========================// + // world and level checks // + //========================// + if (chunkWrapper == null) { // shouldn't happen, but just in case @@ -183,7 +200,42 @@ public class SharedApi } - // update the necessary chunk(s) + + //=====================// + // task limiting check // + //=====================// + + int currentQueueCount = UPDATING_CHUNK_SET.size(); + int maxQueueCount = MAX_UPDATING_CHUNK_COUNT_PER_THREAD * Config.Client.Advanced.MultiThreading.numberOfLodBuilderThreads.get(); + if (currentQueueCount >= maxQueueCount) + { + // The maximum number of chunks are already queued, don't add more. + // This is done to prevent overloading the system if the user fly's extremely fast and queues too many chunks + + long msBetweenLastLog = System.currentTimeMillis() - lastOverloadedLogMessageMsTime; + if (msBetweenLastLog >= MIN_MS_BETWEEN_OVERLOADED_LOG_MESSAGE) + { + lastOverloadedLogMessageMsTime = System.currentTimeMillis(); + LOGGER.warn("Too many chunks queued for updating, max queue count ["+maxQueueCount+"] (["+MAX_UPDATING_CHUNK_COUNT_PER_THREAD+"] per thread). Some LODs may not be updated or may be missing. Please move through the world slower, decrease your vanilla render distance, or increase the CPU load config."); + } + + return; + } + + // prevent duplicate update requests + if (UPDATING_CHUNK_SET.contains(chunkWrapper.getChunkPos())) + { + // this chunk is already being updated + return; + } + UPDATING_CHUNK_SET.add(chunkWrapper.getChunkPos()); + + + + //===============================// + // update the necessary chunk(s) // + //===============================// + if (!updateNeighborChunks) { // only update the center chunk @@ -228,22 +280,6 @@ public class SharedApi } private static void bakeChunkLightingAndSendToLevelAsync(IChunkWrapper chunkWrapper, @Nullable ArrayList neighbourChunkList, IDhLevel dhLevel) { - if (UPDATING_CHUNK_SET.size() >= MAX_UPDATING_CHUNK_COUNT_PER_THREAD * Config.Client.Advanced.MultiThreading.numberOfLodBuilderThreads.get()) - { - // The maximum number of chunks are already queued, don't add more. - // This is done to prevent overloading the system if the user flys extremely fast and queues too many chunks - return; - } - - // prevent duplicate update requests - if (UPDATING_CHUNK_SET.contains(chunkWrapper.getChunkPos())) - { - // this chunk is already being updated - return; - } - UPDATING_CHUNK_SET.add(chunkWrapper.getChunkPos()); - - // lighting the chunk needs to be done on a separate thread to prevent lagging any of the event threads ThreadPoolExecutor executor = ThreadPools.getLightPopulatorExecutor(); if (executor == null) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/sources/interfaces/IIncompleteFullDataSource.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/sources/interfaces/IIncompleteFullDataSource.java index 50f16bd4b..b48bf785b 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/sources/interfaces/IIncompleteFullDataSource.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/sources/interfaces/IIncompleteFullDataSource.java @@ -36,8 +36,8 @@ public interface IIncompleteFullDataSource extends IFullDataSource { DhSectionPos inputPos = inputSource.getSectionPos(); DhSectionPos thisPos = this.getSectionPos(); - LodUtil.assertTrue(inputPos.getDetailLevel() < thisPos.getDetailLevel()); - LodUtil.assertTrue(inputPos.overlapsExactly(this.getSectionPos()), "input source at pos: "+inputPos+" doesn't overlap with this source's pos: "+thisPos); + LodUtil.assertTrue(inputPos.getDetailLevel() < thisPos.getDetailLevel(), "input data source at pos: ["+inputPos+"] has a lower detail level than this: ["+thisPos+"]."); + LodUtil.assertTrue(inputPos.overlapsExactly(this.getSectionPos()), "input source at pos: ["+inputPos+"] (converted to ["+inputPos.convertNewToDetailLevel(thisPos.getDetailLevel())+"]) doesn't overlap with this source's pos: ["+thisPos+"]."); if (inputSource.isEmpty()) { diff --git a/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/FullDataFileHandler.java b/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/FullDataFileHandler.java index e2c73584d..819e37ecb 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/FullDataFileHandler.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/FullDataFileHandler.java @@ -412,6 +412,10 @@ public class FullDataFileHandler implements IFullDataSourceProvider return; } + // can happen if data source caching isn't working correctly + LodUtil.assertTrue(existingFile.pos.equals(existingFullDataSource.getSectionPos()), "Data source returned the wrong position, pooled data source: ["+usePooledDataSources+"]. Expected: ["+existingFile.pos+"] actual: ["+existingFullDataSource.getSectionPos()+"]."); + + if (showFullDataFileSampling) { DebugRenderer.makeParticle(new DebugRenderer.BoxParticle( @@ -429,10 +433,11 @@ public class FullDataFileHandler implements IFullDataSourceProvider //throw e; } - // pooling temporary data sources massively reduces garbage collector overhead when just sampling (going from ~8 GB/sec to ~90 MB/sec) - if (usePooledDataSources && !existingFile.cacheLoadingDataSource) + + // return the pooled data source if necessary + if (usePooledDataSources) { - existingFile.clearCachedDataSource(); + // pooling temporary data sources massively reduces garbage collector overhead when just sampling (going from ~8 GB/sec to ~90 MB/sec) // get the data loader AbstractFullDataSourceLoader dataSourceLoader; diff --git a/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/FullDataMetaFile.java b/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/FullDataMetaFile.java index 3c09bfa5b..9a2c9f74a 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/FullDataMetaFile.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/FullDataMetaFile.java @@ -45,7 +45,6 @@ import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable; import com.seibel.distanthorizons.core.sql.MetaDataDto; import com.seibel.distanthorizons.core.util.AtomicsUtil; import com.seibel.distanthorizons.core.util.LodUtil; -import com.seibel.distanthorizons.core.util.ThreadUtil; import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataInputStream; import com.seibel.distanthorizons.core.render.renderer.DebugRenderer; import com.seibel.distanthorizons.core.util.threading.ThreadPools; @@ -54,8 +53,6 @@ import org.apache.logging.log4j.Logger; /** Represents a File that contains a {@link IFullDataSource}. */ public class FullDataMetaFile extends AbstractMetaDataContainerFile implements IDebugRenderable { - public static final String FILE_SUFFIX = ".lod"; - private static final Logger LOGGER = DhLoggerBuilder.getLogger(FullDataMetaFile.class.getSimpleName()); // === Object lifetime tracking === @@ -89,8 +86,11 @@ public class FullDataMetaFile extends AbstractMetaDataContainerFile implements I * This makes null checks simpler. */ private DataSourceReferenceTracker.FullDataSourceSoftRef cachedFullDataSourceRef = new DataSourceReferenceTracker.FullDataSourceSoftRef(this,null); - private final AtomicReference> dataSourceLoadFutureRef = new AtomicReference<>(null); - public volatile Boolean cacheLoadingDataSource = null; + // two different load futures are used to + // prevent accidentally returning a pooled (non-cached) data source + private final AtomicReference> cachedDataSourceLoadFutureRef = new AtomicReference<>(null); + private final AtomicReference> pooledDataSourceLoadFutureRef = new AtomicReference<>(null); + // === Concurrent Write tracking === private final AtomicReference writeQueueRef = new AtomicReference<>(new GuardedMultiAppendQueue()); @@ -171,7 +171,6 @@ public class FullDataMetaFile extends AbstractMetaDataContainerFile implements I { this.cachedFullDataSourceRef.close(); this.cachedFullDataSourceRef.clear(); - this.cacheLoadingDataSource = null; } return dataExists; @@ -181,18 +180,44 @@ public class FullDataMetaFile extends AbstractMetaDataContainerFile implements I public CompletableFuture getDataSourceWithoutCachingAsync() { return this.getOrLoadCachedDataSourceAsync(false); } public CompletableFuture getOrLoadCachedDataSourceAsync() { return this.getOrLoadCachedDataSourceAsync(true); } - private CompletableFuture getOrLoadCachedDataSourceAsync(boolean cacheLoadingSource) + /** + * Synchronized to help prevent issues where multiple threads try to read as cached and un-cached at the same time. + * Hopefully isn't necessary and could potentially be removed in the future. + */ + private synchronized CompletableFuture getOrLoadCachedDataSourceAsync(boolean cacheLoadingSource) { checkAndLogPhantomDataSourceLifeCycles(); - CompletableFuture potentialLoadFuture = this.getCachedDataSourceAsync(); + AtomicReference> dataSourceLoadFutureRef = cacheLoadingSource ? this.cachedDataSourceLoadFutureRef : this.pooledDataSourceLoadFutureRef; + + + + //========================// + // use the pre-existing // + // load future if present // + //========================// + + CompletableFuture preExistingLoadFuture = dataSourceLoadFutureRef.get(); + if (preExistingLoadFuture != null) + { + return preExistingLoadFuture; + } + + + + //========================// + // attempt to get the // + // cached data if present // + //========================// + + CompletableFuture potentialLoadFuture = null; + if (cacheLoadingSource) + { + potentialLoadFuture = this.getCachedDataSourceAndUpdateIfNeededAsync(); + } + if (potentialLoadFuture != null) { - if (cacheLoadingSource) - { - this.cacheLoadingDataSource = true; - } - // return the in-process future return potentialLoadFuture; } @@ -200,14 +225,14 @@ public class FullDataMetaFile extends AbstractMetaDataContainerFile implements I { // there is no cached data, we'll have to load it + // create a new load future if necessary potentialLoadFuture = new CompletableFuture<>(); - if (!this.dataSourceLoadFutureRef.compareAndSet(null, potentialLoadFuture)) + if (!dataSourceLoadFutureRef.compareAndSet(null, potentialLoadFuture)) { // two threads attempted to start this job at the same time, only use the first future - potentialLoadFuture = this.dataSourceLoadFutureRef.get(); + // (shouldn't happen since this method is synchronized, but just in case) + potentialLoadFuture = dataSourceLoadFutureRef.get(); } - - this.cacheLoadingDataSource = cacheLoadingSource; } @@ -215,7 +240,10 @@ public class FullDataMetaFile extends AbstractMetaDataContainerFile implements I final CompletableFuture dataSourceLoadFuture = potentialLoadFuture; if (!this.doesDtoExist) { - // create a new DTO and data source + //==================// + // create a new DTO // + // and data source // + //==================// this.fullDataSourceProvider.onDataFileCreatedAsync(this) .thenApply((fullDataSource) -> @@ -229,16 +257,18 @@ public class FullDataMetaFile extends AbstractMetaDataContainerFile implements I return fullDataSource; }) - .thenCompose((fullDataSource) -> this.applyWriteQueueAndSaveAsync(fullDataSource)) + .thenCompose((fullDataSource) -> this.applyWriteQueueAndSaveAsync(fullDataSource, cacheLoadingSource)) .thenAccept((fullDataSource) -> { dataSourceLoadFuture.complete(fullDataSource); - this.dataSourceLoadFutureRef.set(null); + dataSourceLoadFutureRef.set(null); }); } else { - // load the existing Meta file and data source + //=========================// + // load the data from file // + //=========================// if (this.baseMetaData == null) { @@ -252,65 +282,54 @@ public class FullDataMetaFile extends AbstractMetaDataContainerFile implements I // load the data source CompletableFuture.supplyAsync(() -> + { + // Load the file. + IFullDataSource fullDataSource; + try (InputStream inputStream = this.getInputStream(); + DhDataInputStream compressedStream = new DhDataInputStream(inputStream)) { - // Load the file. - IFullDataSource fullDataSource; - try (InputStream inputStream = this.getInputStream(); - DhDataInputStream compressedStream = new DhDataInputStream(inputStream)) + if (cacheLoadingSource) { - if (cacheLoadingSource) - { - fullDataSource = this.fullDataSourceLoader.loadDataSource(this, compressedStream, this.level); - } - else - { - fullDataSource = this.fullDataSourceLoader.loadTemporaryDataSource(this, compressedStream, this.level); - } + fullDataSource = this.fullDataSourceLoader.loadDataSource(this, compressedStream, this.level); } - catch (Exception ex) + else { - LOGGER.error("Full Data Load error: "+ ex.getMessage(), ex); - - dataSourceLoadFuture.completeExceptionally(ex); - this.dataSourceLoadFutureRef.set(null); - - // can happen if there is a missing file or the file was incorrectly formatted, or terminated early - throw new CompletionException(ex); + fullDataSource = this.fullDataSourceLoader.loadTemporaryDataSource(this, compressedStream, this.level); } - return fullDataSource; - }, executor) - .thenCompose((fullDataSource) -> this.applyWriteQueueAndSaveAsync(fullDataSource)) - .thenAccept((fullDataSource) -> + } + catch (Exception ex) { - dataSourceLoadFuture.complete(fullDataSource); - this.dataSourceLoadFutureRef.set(null); - }); + LOGGER.error("Full Data Load error: "+ ex.getMessage(), ex); + + dataSourceLoadFuture.completeExceptionally(ex); + dataSourceLoadFutureRef.set(null); + + // can happen if there is a missing file or the file was incorrectly formatted, or terminated early + throw new CompletionException(ex); + } + return fullDataSource; + }, executor) + .thenCompose((fullDataSource) -> this.applyWriteQueueAndSaveAsync(fullDataSource, cacheLoadingSource)) + .thenAccept((fullDataSource) -> + { + dataSourceLoadFuture.complete(fullDataSource); + dataSourceLoadFutureRef.set(null); + }); } else { // don't load anything if the provider has been shut down dataSourceLoadFuture.complete(null); - this.dataSourceLoadFutureRef.set(null); + dataSourceLoadFutureRef.set(null); return dataSourceLoadFuture; } } return dataSourceLoadFuture; } - - - /** @return returns null if {@link FullDataMetaFile#cachedFullDataSourceRef} is empty and no cached {@link IFullDataSource} exists. */ - private CompletableFuture getCachedDataSourceAsync() + private CompletableFuture getCachedDataSourceAndUpdateIfNeededAsync() { - // this data source is being written to, use the existing future - CompletableFuture dataSourceLoadFuture = this.dataSourceLoadFutureRef.get(); - if (dataSourceLoadFuture != null) - { - return dataSourceLoadFuture; - } - - // attempt to get the cached data source IFullDataSource cachedFullDataSource = this.cachedFullDataSourceRef.get(); if (cachedFullDataSource == null) @@ -334,7 +353,7 @@ public class FullDataMetaFile extends AbstractMetaDataContainerFile implements I // Create a new future if one doesn't already exist CompletableFuture newFuture = new CompletableFuture<>(); - CompletableFuture oldFuture = AtomicsUtil.compareAndExchange(this.dataSourceLoadFutureRef, null, newFuture); + CompletableFuture oldFuture = AtomicsUtil.compareAndExchange(this.cachedDataSourceLoadFutureRef, null, newFuture); if (oldFuture != null) { @@ -349,17 +368,17 @@ public class FullDataMetaFile extends AbstractMetaDataContainerFile implements I // wait for the update to finish before returning the data source CompletableFuture.supplyAsync(() -> cachedFullDataSource, executor) - .thenCompose((fullDataSource) -> this.applyWriteQueueAndSaveAsync(fullDataSource)) + .thenCompose((fullDataSource) -> this.applyWriteQueueAndSaveAsync(fullDataSource, true)) .thenAccept((fullDataSource) -> { newFuture.complete(fullDataSource); - this.dataSourceLoadFutureRef.set(null); + this.cachedDataSourceLoadFutureRef.set(null); }); } else { // don't update anything if the provider has been shut down - this.dataSourceLoadFutureRef.set(null); + this.cachedDataSourceLoadFutureRef.set(null); newFuture.complete(null); } @@ -499,7 +518,7 @@ public class FullDataMetaFile extends AbstractMetaDataContainerFile implements I color = Color.YELLOW; } } - else if (this.dataSourceLoadFutureRef.get() != null) + else if (this.cachedDataSourceLoadFutureRef.get() != null) { color = Color.BLUE; } @@ -534,7 +553,7 @@ public class FullDataMetaFile extends AbstractMetaDataContainerFile implements I * and stores the result in {@link FullDataMetaFile#cachedFullDataSourceRef}. */ @SuppressWarnings("resource") // due to DataObjTracker and DataObjSoftTracker being created outside a try-catch block - private CompletableFuture applyWriteQueueAndSaveAsync(IFullDataSource fullDataSourceToUpdate) + private CompletableFuture applyWriteQueueAndSaveAsync(IFullDataSource fullDataSourceToUpdate, boolean cacheLoadingSource) { CompletableFuture completionFuture = new CompletableFuture<>(); @@ -588,8 +607,13 @@ public class FullDataMetaFile extends AbstractMetaDataContainerFile implements I } - if (this.cacheLoadingDataSource) + if (cacheLoadingSource) { + if (fullDataSource != null) + { + LodUtil.assertTrue(this.pos.equals(fullDataSource.getSectionPos()), "Attempting to cache a datasource with the wrong position. Meta file pos: [" + this.pos + "], data source pos: [" + fullDataSource.getSectionPos() + "]."); + } + // save the updated data source this.cachedFullDataSourceRef = new DataSourceReferenceTracker.FullDataSourceSoftRef(this, fullDataSource); } @@ -601,7 +625,7 @@ public class FullDataMetaFile extends AbstractMetaDataContainerFile implements I if (this.needsUpdate) { // another update was requested while this update was being processed - if (this.cacheLoadingDataSource) + if (cacheLoadingSource) { this.getOrLoadCachedDataSourceAsync(); } 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 63a102c64..eacd25811 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 @@ -24,6 +24,7 @@ import com.seibel.distanthorizons.core.config.types.ConfigEntry; import com.seibel.distanthorizons.core.util.threading.DhThreadFactory; import com.seibel.distanthorizons.core.util.threading.RateLimitedThreadPoolExecutor; import com.seibel.distanthorizons.core.util.threading.ThreadPools; +import com.seibel.distanthorizons.coreapi.ModInfo; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -38,8 +39,7 @@ public class ThreadUtil { private static final Logger LOGGER = LogManager.getLogger(); - /** The prefix isn't strictly required, but makes debugging and profiling much easier. */ - public static String THREAD_NAME_PREFIX = "DH-"; + public static String THREAD_NAME_PREFIX = ModInfo.THREAD_NAME_PREFIX; public static int MINIMUM_RELATIVE_PRIORITY = -4; public static int DEFAULT_RELATIVE_PRIORITY = 0;