diff --git a/build-data/divinemc.at b/build-data/divinemc.at index ef8713f..b0f5ab6 100644 --- a/build-data/divinemc.at +++ b/build-data/divinemc.at @@ -6,6 +6,17 @@ private-f net.minecraft.world.level.levelgen.NoiseChunk$FlatCache noiseFiller private-f net.minecraft.world.level.levelgen.NoiseChunk$NoiseInterpolator noiseFiller private-f net.minecraft.world.level.levelgen.RandomState router private-f net.minecraft.world.level.levelgen.RandomState sampler +public ca.spottedleaf.moonrise.patches.chunk_system.scheduling.executor.RadiusAwarePrioritisedExecutor$Task +public ca.spottedleaf.moonrise.patches.chunk_system.scheduling.executor.RadiusAwarePrioritisedExecutor$Task chunkX +public ca.spottedleaf.moonrise.patches.chunk_system.scheduling.executor.RadiusAwarePrioritisedExecutor$Task chunkZ +public ca.spottedleaf.moonrise.patches.chunk_system.scheduling.task.ChunkProgressionTask chunkX +public ca.spottedleaf.moonrise.patches.chunk_system.scheduling.task.ChunkProgressionTask chunkZ +public ca.spottedleaf.moonrise.patches.chunk_system.scheduling.task.ChunkProgressionTask world +public ca.spottedleaf.moonrise.patches.chunk_system.scheduling.task.GenericDataLoadTask chunkX +public ca.spottedleaf.moonrise.patches.chunk_system.scheduling.task.GenericDataLoadTask chunkZ +public ca.spottedleaf.moonrise.patches.chunk_system.scheduling.task.GenericDataLoadTask world +public ca.spottedleaf.moonrise.patches.chunk_system.scheduling.task.GenericDataLoadTask$ProcessOffMainTask +public ca.spottedleaf.moonrise.patches.chunk_system.scheduling.task.GenericDataLoadTask$ProcessOnMainTask public net.minecraft.util.Mth SIN public net.minecraft.world.entity.ai.Brain sensors public net.minecraft.world.entity.ai.memory.NearestVisibleLivingEntities lineOfSightTest diff --git a/divinemc-server/minecraft-patches/features/0011-Chunk-System-Optimizations.patch b/divinemc-server/minecraft-patches/features/0011-Chunk-System-Optimizations.patch index 38bacbe..ae2a61c 100644 --- a/divinemc-server/minecraft-patches/features/0011-Chunk-System-Optimizations.patch +++ b/divinemc-server/minecraft-patches/features/0011-Chunk-System-Optimizations.patch @@ -538,8 +538,66 @@ index 0c99bffa769d53562a10d23c4a9b37dc59c7f478..f4fcc3b2676b17ebc276dcb177285f18 final ConcurrentLong2ReferenceChainedHashTable.TableEntry>> coordinateTickets = iterator.next(); final long coordinate = coordinateTickets.getKey(); final SortedArraySet> tickets = coordinateTickets.getValue(); +diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkTaskScheduler.java b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkTaskScheduler.java +index 67532b85073b7978254a0b04caadfe822679e61f..4b97b676d4245e7ece956eb4c78bed96ff452b2d 100644 +--- a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkTaskScheduler.java ++++ b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkTaskScheduler.java +@@ -65,14 +65,6 @@ public final class ChunkTaskScheduler { + + private static final Logger LOGGER = LogUtils.getClassLogger(); + +- public static void init(final boolean useParallelGen) { +- for (final PrioritisedThreadPool.ExecutorGroup.ThreadPoolExecutor executor : MoonriseCommon.RADIUS_AWARE_GROUP.getAllExecutors()) { +- executor.setMaxParallelism(useParallelGen ? -1 : 1); +- } +- +- LOGGER.info("Chunk system is using population gen parallelism: " + useParallelGen); +- } +- + public static final TicketType CHUNK_LOAD = TicketType.create("chunk_system:chunk_load", Long::compareTo); + private static final AtomicLong CHUNK_LOAD_IDS = new AtomicLong(); + +@@ -115,12 +107,12 @@ public final class ChunkTaskScheduler { + + public final ServerLevel world; + public final RadiusAwarePrioritisedExecutor radiusAwareScheduler; +- public final PrioritisedThreadPool.ExecutorGroup.ThreadPoolExecutor parallelGenExecutor; +- private final PrioritisedThreadPool.ExecutorGroup.ThreadPoolExecutor radiusAwareGenExecutor; +- public final PrioritisedThreadPool.ExecutorGroup.ThreadPoolExecutor loadExecutor; ++ public final org.bxteam.divinemc.server.chunk.TheChunkSystem.ExecutorGroup.ThreadPoolExecutor parallelGenExecutor; ++ private final org.bxteam.divinemc.server.chunk.TheChunkSystem.ExecutorGroup.ThreadPoolExecutor radiusAwareGenExecutor; ++ public final org.bxteam.divinemc.server.chunk.TheChunkSystem.ExecutorGroup.ThreadPoolExecutor loadExecutor; + public final PrioritisedThreadPool.ExecutorGroup.ThreadPoolExecutor ioExecutor; +- public final PrioritisedThreadPool.ExecutorGroup.ThreadPoolExecutor compressionExecutor; +- public final PrioritisedThreadPool.ExecutorGroup.ThreadPoolExecutor saveExecutor; ++ public final org.bxteam.divinemc.server.chunk.TheChunkSystem.ExecutorGroup.ThreadPoolExecutor compressionExecutor; ++ public final org.bxteam.divinemc.server.chunk.TheChunkSystem.ExecutorGroup.ThreadPoolExecutor saveExecutor; + + private final PrioritisedTaskQueue mainThreadExecutor = new PrioritisedTaskQueue(); + +@@ -291,14 +283,14 @@ public final class ChunkTaskScheduler { + this.lockShift = Math.max(((ChunkSystemServerLevel)world).moonrise$getRegionChunkShift(), ThreadedTicketLevelPropagator.SECTION_SHIFT); + this.schedulingLockArea = new ReentrantAreaLock(this.getChunkSystemLockShift()); + +- this.parallelGenExecutor = MoonriseCommon.PARALLEL_GEN_GROUP.createExecutor(-1, MoonriseCommon.WORKER_QUEUE_HOLD_TIME, 0); +- this.radiusAwareGenExecutor = MoonriseCommon.RADIUS_AWARE_GROUP.createExecutor(1, MoonriseCommon.WORKER_QUEUE_HOLD_TIME, 0); +- this.loadExecutor = MoonriseCommon.LOAD_GROUP.createExecutor(-1, MoonriseCommon.WORKER_QUEUE_HOLD_TIME, 0); +- this.radiusAwareScheduler = new RadiusAwarePrioritisedExecutor(this.radiusAwareGenExecutor, 16); ++ this.parallelGenExecutor = MoonriseCommon.PARALLEL_GEN_GROUP.createExecutor(); ++ this.radiusAwareGenExecutor = MoonriseCommon.RADIUS_AWARE_GROUP.createExecutor(); ++ this.loadExecutor = MoonriseCommon.LOAD_GROUP.createExecutor(); ++ this.radiusAwareScheduler = new RadiusAwarePrioritisedExecutor(this.radiusAwareGenExecutor, 10_000); + this.ioExecutor = MoonriseCommon.SERVER_REGION_IO_GROUP.createExecutor(-1, MoonriseCommon.IO_QUEUE_HOLD_TIME, 0); + // we need a separate executor here so that on shutdown we can continue to process I/O tasks +- this.compressionExecutor = MoonriseCommon.LOAD_GROUP.createExecutor(-1, MoonriseCommon.WORKER_QUEUE_HOLD_TIME, 0); +- this.saveExecutor = MoonriseCommon.LOAD_GROUP.createExecutor(-1, MoonriseCommon.WORKER_QUEUE_HOLD_TIME, 0); ++ this.compressionExecutor = MoonriseCommon.LOAD_GROUP.createExecutor(); ++ this.saveExecutor = MoonriseCommon.LOAD_GROUP.createExecutor(); + this.chunkHolderManager = new ChunkHolderManager(world, this); + } + diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/NewChunkHolder.java b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/NewChunkHolder.java -index e4a5fa25ed368fc4662c30934da2963ef446d782..6da0ea5cd83a00578223e0a19f952c917bcbcdae 100644 +index e4a5fa25ed368fc4662c30934da2963ef446d782..33bb74186bfd0e1781e85410e98abe8dfff8150c 100644 --- a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/NewChunkHolder.java +++ b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/NewChunkHolder.java @@ -644,11 +644,19 @@ public final class NewChunkHolder { @@ -562,7 +620,7 @@ index e4a5fa25ed368fc4662c30934da2963ef446d782..6da0ea5cd83a00578223e0a19f952c91 this.scheduler = scheduler; this.vanillaChunkHolder = new ChunkHolder( new ChunkPos(chunkX, chunkZ), ChunkHolderManager.MAX_TICKET_LEVEL, world, -@@ -790,9 +798,11 @@ public final class NewChunkHolder { +@@ -790,12 +798,14 @@ public final class NewChunkHolder { // note: these are completed with null to indicate that no write occurred // they are also completed with null to indicate a null write occurred @@ -576,7 +634,20 @@ index e4a5fa25ed368fc4662c30934da2963ef446d782..6da0ea5cd83a00578223e0a19f952c91 + // DivineMC end - Chunk System optimization public static final record UnloadTask(CallbackCompletable completable, PrioritisedExecutor.PrioritisedTask task, - LazyRunnable toRun) {} +- LazyRunnable toRun) {} ++ org.bxteam.divinemc.server.chunk.ChunkRunnable toRun) {} // DivineMC - Chunk System optimization + + public UnloadTask getUnloadTask(final MoonriseRegionFileIO.RegionFileType type) { + switch (type) { +@@ -858,7 +868,7 @@ public final class NewChunkHolder { + this.priorityLocked = false; + + if (chunk != null) { +- final LazyRunnable toRun = new LazyRunnable(); ++ final org.bxteam.divinemc.server.chunk.ChunkRunnable toRun = new org.bxteam.divinemc.server.chunk.ChunkRunnable(this.chunkX, this.chunkZ, this.world, null); // DivineMC - Chunk System optimization' + this.chunkDataUnload = new UnloadTask(new CallbackCompletable<>(), this.scheduler.saveExecutor.createTask(toRun), toRun); + } + if (poiChunk != null) { @@ -1190,6 +1200,7 @@ public final class NewChunkHolder { for (int dz = -NEIGHBOUR_RADIUS; dz <= NEIGHBOUR_RADIUS; ++dz) { for (int dx = -NEIGHBOUR_RADIUS; dx <= NEIGHBOUR_RADIUS; ++dx) { @@ -633,6 +704,198 @@ index e4a5fa25ed368fc4662c30934da2963ef446d782..6da0ea5cd83a00578223e0a19f952c91 }).add(consumer); } +diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/executor/RadiusAwarePrioritisedExecutor.java b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/executor/RadiusAwarePrioritisedExecutor.java +index 28ffa653e87a4e8ef7cf614916ef3fe61681fe16..b35b92b204fbefd139c4544f15e32d46bfa30777 100644 +--- a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/executor/RadiusAwarePrioritisedExecutor.java ++++ b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/executor/RadiusAwarePrioritisedExecutor.java +@@ -110,60 +110,27 @@ public class RadiusAwarePrioritisedExecutor { + return priorityId == this.selectedQueue ? queue.tryPushTasks() : null; + } + ++ // DivineMC start - Chunk System Optimizations + public PrioritisedExecutor.PrioritisedTask createTask(final int chunkX, final int chunkZ, final int radius, +- final Runnable run, final Priority priority) { ++ final Runnable run, final Priority priority, final net.minecraft.server.level.ServerLevel world) { + if (radius < 0) { + throw new IllegalArgumentException("Radius must be > 0: " + radius); + } +- return new Task(this, chunkX, chunkZ, radius, run, priority); ++ return new Task(this, chunkX, chunkZ, radius, run, priority, world); + } + +- public PrioritisedExecutor.PrioritisedTask createTask(final int chunkX, final int chunkZ, final int radius, +- final Runnable run) { +- return this.createTask(chunkX, chunkZ, radius, run, Priority.NORMAL); +- } +- +- public PrioritisedExecutor.PrioritisedTask queueTask(final int chunkX, final int chunkZ, final int radius, +- final Runnable run, final Priority priority) { +- final PrioritisedExecutor.PrioritisedTask ret = this.createTask(chunkX, chunkZ, radius, run, priority); +- +- ret.queue(); +- +- return ret; +- } +- +- public PrioritisedExecutor.PrioritisedTask queueTask(final int chunkX, final int chunkZ, final int radius, +- final Runnable run) { +- final PrioritisedExecutor.PrioritisedTask ret = this.createTask(chunkX, chunkZ, radius, run); +- +- ret.queue(); +- +- return ret; +- } +- +- public PrioritisedExecutor.PrioritisedTask createInfiniteRadiusTask(final Runnable run, final Priority priority) { +- return new Task(this, 0, 0, -1, run, priority); +- } +- +- public PrioritisedExecutor.PrioritisedTask createInfiniteRadiusTask(final Runnable run) { +- return this.createInfiniteRadiusTask(run, Priority.NORMAL); +- } +- +- public PrioritisedExecutor.PrioritisedTask queueInfiniteRadiusTask(final Runnable run, final Priority priority) { +- final PrioritisedExecutor.PrioritisedTask ret = this.createInfiniteRadiusTask(run, priority); +- +- ret.queue(); +- +- return ret; ++ public PrioritisedExecutor.PrioritisedTask createInfiniteRadiusTask(final Runnable run, final Priority priority, final net.minecraft.server.level.ServerLevel world) { ++ return new Task(this, 0, 0, -1, run, priority, world); + } + +- public PrioritisedExecutor.PrioritisedTask queueInfiniteRadiusTask(final Runnable run) { +- final PrioritisedExecutor.PrioritisedTask ret = this.createInfiniteRadiusTask(run, Priority.NORMAL); ++ public PrioritisedExecutor.PrioritisedTask queueInfiniteRadiusTask(final Runnable run, final net.minecraft.server.level.ServerLevel world) { ++ final PrioritisedExecutor.PrioritisedTask ret = this.createInfiniteRadiusTask(run, Priority.NORMAL, world); + + ret.queue(); + + return ret; + } ++ // DivineMC end - Chunk System Optimizations + + private static void scheduleTasks(final List toSchedule) { + if (toSchedule != null) { +@@ -440,18 +407,20 @@ public class RadiusAwarePrioritisedExecutor { + private final int radius; + private Runnable run; + private Priority priority; ++ public final net.minecraft.server.level.ServerLevel world; // DivineMC - Chunk System Optimization + + private DependencyNode dependencyNode; + private PrioritisedExecutor.PrioritisedTask queuedTask; + + private Task(final RadiusAwarePrioritisedExecutor scheduler, final int chunkX, final int chunkZ, final int radius, +- final Runnable run, final Priority priority) { ++ final Runnable run, final Priority priority, final net.minecraft.server.level.ServerLevel world) { // DivineMC - Chunk System Optimization + this.scheduler = scheduler; + this.chunkX = chunkX; + this.chunkZ = chunkZ; + this.radius = radius; + this.run = run; + this.priority = priority; ++ this.world = world; // DivineMC - Chunk System Optimization + } + + private boolean isFiniteRadius() { +@@ -473,6 +442,7 @@ public class RadiusAwarePrioritisedExecutor { + synchronized (this.scheduler) { + final DependencyNode node = this.dependencyNode; + this.dependencyNode = null; ++ if (node == null) return; // DivineMC - Chunk System Optimization + toSchedule = node.tree.returnNode(node); + } + +@@ -489,6 +459,7 @@ public class RadiusAwarePrioritisedExecutor { + final Runnable run = this.run; + this.run = null; + try { ++ if (run == null) return; // DivineMC - Chunk System Optimization + run.run(); + } finally { + this.returnNode(); +diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkFullTask.java b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkFullTask.java +index 6ab353b0d2465c3680bb3c8d0852ba0f65c00fd2..70067c12d4d82460d55d8f90d01b0bc1e5368408 100644 +--- a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkFullTask.java ++++ b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkFullTask.java +@@ -35,7 +35,7 @@ public final class ChunkFullTask extends ChunkProgressionTask implements Runnabl + super(scheduler, world, chunkX, chunkZ); + this.chunkHolder = chunkHolder; + this.fromChunk = fromChunk; +- this.convertToFullTask = scheduler.createChunkTask(chunkX, chunkZ, this, priority); ++ this.convertToFullTask = scheduler.radiusAwareScheduler.createInfiniteRadiusTask(this, priority, world); // DivineMC - Chunk System Optimizations + } + + @Override +diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkProgressionTask.java b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkProgressionTask.java +index e9623c334858ebc698c92d05b36b70f6d846d0b1..43ef4d7b7db64a766bb815e1c2202b1f30ae4f99 100644 +--- a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkProgressionTask.java ++++ b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkProgressionTask.java +@@ -69,7 +69,7 @@ public abstract class ChunkProgressionTask { + } + } + +- protected final void complete(final ChunkAccess chunk, final Throwable throwable) { ++ protected void complete(final ChunkAccess chunk, final Throwable throwable) { // DivineMC - not final + try { + this.complete0(chunk, throwable); + } catch (final Throwable thr2) { +@@ -81,7 +81,7 @@ public abstract class ChunkProgressionTask { + + private void complete0(final ChunkAccess chunk, final Throwable throwable) { + if ((boolean)COMPLETED_HANDLE.getAndSet((ChunkProgressionTask)this, (boolean)true)) { +- throw new IllegalStateException("Already completed"); ++ return; // DivineMC - do not crash the server + } + this.completedChunk = chunk; + this.completedThrowable = throwable; +diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkUpgradeGenericStatusTask.java b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkUpgradeGenericStatusTask.java +index 25d8da4773dcee5096053e7e3788bfc224d705a7..2112ccbe382993dcfb56a50d991c3613765d7d56 100644 +--- a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkUpgradeGenericStatusTask.java ++++ b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkUpgradeGenericStatusTask.java +@@ -51,9 +51,9 @@ public final class ChunkUpgradeGenericStatusTask extends ChunkProgressionTask im + } else { + final int writeRadius = ((ChunkSystemChunkStatus)this.toStatus).moonrise$getWriteRadius(); + if (writeRadius < 0) { +- this.generateTask = this.scheduler.radiusAwareScheduler.createInfiniteRadiusTask(this, priority); ++ this.generateTask = this.scheduler.radiusAwareScheduler.createInfiniteRadiusTask(this, priority, world); // DivineMC - Chunk System Optimizations + } else { +- this.generateTask = this.scheduler.radiusAwareScheduler.createTask(chunkX, chunkZ, writeRadius, this, priority); ++ this.generateTask = this.scheduler.radiusAwareScheduler.createTask(chunkX, chunkZ, writeRadius, this, priority, world); // DivineMC - Chunk System Optimizations + } + } + } +diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/GenericDataLoadTask.java b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/GenericDataLoadTask.java +index 95ed5a0ff3f0588f625ba48a5fee3aafbab9d13f..f2fd6d5eb024f646875868c441eb2da284206026 100644 +--- a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/GenericDataLoadTask.java ++++ b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/GenericDataLoadTask.java +@@ -333,6 +333,12 @@ public abstract class GenericDataLoadTask { + GenericDataLoadTask.this.onComplete((TaskResult)newData); + } + } ++ ++ // DivineMC start - Chunk System Optimizations ++ public GenericDataLoadTask loadTask() { ++ return GenericDataLoadTask.this; ++ } ++ // DivineMC end - Chunk System Optimizations + } + + public final class ProcessOnMainTask implements Runnable { +@@ -351,6 +357,12 @@ public abstract class GenericDataLoadTask { + + GenericDataLoadTask.this.onComplete(result); + } ++ ++ // DivineMC start - Chunk System Optimizations ++ public GenericDataLoadTask loadTask() { ++ return GenericDataLoadTask.this; ++ } ++ // DivineMC end - Chunk System Optimizations + } + + protected static final class LoadDataFromDiskTask { diff --git a/ca/spottedleaf/moonrise/patches/chunk_tick_iteration/ChunkTickConstants.java b/ca/spottedleaf/moonrise/patches/chunk_tick_iteration/ChunkTickConstants.java index e97e7d276faf055c89207385d3820debffb06463..4aeb75a2cdcfb4206bab3eee5ad674dd9890e720 100644 --- a/ca/spottedleaf/moonrise/patches/chunk_tick_iteration/ChunkTickConstants.java @@ -658,6 +921,35 @@ index 4ca68a903e67606fc4ef0bfa9862a73797121c8b..f94f443f862611f039454d1dc8ff2a4b if (!this.isDirty()) { return false; } +diff --git a/ca/spottedleaf/moonrise/patches/starlight/light/StarLightInterface.java b/ca/spottedleaf/moonrise/patches/starlight/light/StarLightInterface.java +index 571db5f9bf94745a8afe2cd313e593fb15db5e37..c8e59f6abed384f45314fc995d3822724c9b4001 100644 +--- a/ca/spottedleaf/moonrise/patches/starlight/light/StarLightInterface.java ++++ b/ca/spottedleaf/moonrise/patches/starlight/light/StarLightInterface.java +@@ -888,7 +888,7 @@ public final class StarLightInterface { + super(chunkCoordinate, lightEngine, queue); + this.task = ((ChunkSystemServerLevel)(ServerLevel)lightEngine.getWorld()).moonrise$getChunkTaskScheduler().radiusAwareScheduler.createTask( + CoordinateUtils.getChunkX(chunkCoordinate), CoordinateUtils.getChunkZ(chunkCoordinate), +- ((ChunkSystemChunkStatus)ChunkStatus.LIGHT).moonrise$getWriteRadius(), this, priority ++ ((ChunkSystemChunkStatus)ChunkStatus.LIGHT).moonrise$getWriteRadius(), this, priority, lightEngine.world.getMinecraftWorld() // DivineMC - Chunk System Optimizations + ); + } + +diff --git a/io/papermc/paper/FeatureHooks.java b/io/papermc/paper/FeatureHooks.java +index 460bb584db04b582f3297ae419183f430aff1ec0..72c4e1876115745fbeec12fe8a1ad6f4803e9d7f 100644 +--- a/io/papermc/paper/FeatureHooks.java ++++ b/io/papermc/paper/FeatureHooks.java +@@ -32,11 +32,6 @@ import org.bukkit.Chunk; + import org.bukkit.World; + + public final class FeatureHooks { +- +- public static void initChunkTaskScheduler(final boolean useParallelGen) { +- ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler.init(useParallelGen); // Paper - Chunk system +- } +- + public static void registerPaperCommands(final Map, PaperSubcommand> commands) { + commands.put(Set.of("fixlight"), new FixLightCommand()); // Paper - rewrite chunk system + commands.put(Set.of("debug", "chunkinfo", "holderinfo"), new ChunkDebugCommand()); // Paper - rewrite chunk system diff --git a/net/minecraft/server/level/DistanceManager.java b/net/minecraft/server/level/DistanceManager.java index 5eab6179ce3913cb4e4d424f910ba423faf21c85..4b1efd53e423bdfe90d5efd472823869fc87e73b 100644 --- a/net/minecraft/server/level/DistanceManager.java @@ -695,7 +987,7 @@ index ab30af9cd58ff7310e05be87b08f42bacf69e11e..ae0e36d198ad8243920c8e8a55c0be49 } diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java -index 973198ae0a73d6747e73548bdcbc1de46b6fb107..59aa7f9c06b62de4e8997f325d66daba28fa5871 100644 +index 973198ae0a73d6747e73548bdcbc1de46b6fb107..e17f5ba74295eb6585520ab9e5cc1cb2e3107098 100644 --- a/net/minecraft/server/level/ServerLevel.java +++ b/net/minecraft/server/level/ServerLevel.java @@ -181,6 +181,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe @@ -706,7 +998,15 @@ index 973198ae0a73d6747e73548bdcbc1de46b6fb107..59aa7f9c06b62de4e8997f325d66daba private int lastSpawnChunkRadius; final EntityTickList entityTickList = new EntityTickList(this); // DivineMC - parallel world ticking // Paper - rewrite chunk system -@@ -689,6 +690,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -291,6 +292,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + } + // Paper end - optimise getPlayerByUUID + // Paper start - rewrite chunk system ++ public final org.bxteam.divinemc.server.chunk.PriorityHandler chunkSystemPriorities; + private final ca.spottedleaf.moonrise.patches.chunk_system.player.RegionizedPlayerChunkLoader.ViewDistanceHolder viewDistanceHolder = new ca.spottedleaf.moonrise.patches.chunk_system.player.RegionizedPlayerChunkLoader.ViewDistanceHolder(); + private final ca.spottedleaf.moonrise.patches.chunk_system.player.RegionizedPlayerChunkLoader chunkLoader = new ca.spottedleaf.moonrise.patches.chunk_system.player.RegionizedPlayerChunkLoader((ServerLevel)(Object)this); + private final ca.spottedleaf.moonrise.patches.chunk_system.io.datacontroller.EntityDataController entityDataController; +@@ -689,6 +691,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe // Paper start - rewrite chunk system this.moonrise$setEntityLookup(new ca.spottedleaf.moonrise.patches.chunk_system.level.entity.server.ServerEntityLookup((ServerLevel)(Object)this, ((ServerLevel)(Object)this).new EntityCallbacks())); this.chunkTaskScheduler = new ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler((ServerLevel)(Object)this); @@ -714,7 +1014,15 @@ index 973198ae0a73d6747e73548bdcbc1de46b6fb107..59aa7f9c06b62de4e8997f325d66daba this.entityDataController = new ca.spottedleaf.moonrise.patches.chunk_system.io.datacontroller.EntityDataController( new ca.spottedleaf.moonrise.patches.chunk_system.io.datacontroller.EntityDataController.EntityRegionFileStorage( new RegionStorageInfo(levelStorageAccess.getLevelId(), dimension, "entities"), -@@ -832,8 +834,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -703,6 +706,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + this.getCraftServer().addWorld(this.getWorld()); // CraftBukkit + this.tickExecutor = java.util.concurrent.Executors.newSingleThreadExecutor(new org.bxteam.divinemc.server.ServerLevelTickExecutorThreadFactory(getWorld().getName())); // DivineMC - parallel world ticking + this.preciseTime = this.serverLevelData.getDayTime(); // Purpur - Configurable daylight cycle ++ this.chunkSystemPriorities = new org.bxteam.divinemc.server.chunk.PriorityHandler(this); // DivineMC - Chunk System optimizations + } + + // Paper start +@@ -832,8 +836,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe @Override public boolean shouldTickBlocksAt(long chunkPos) { // Paper start - rewrite chunk system @@ -724,7 +1032,7 @@ index 973198ae0a73d6747e73548bdcbc1de46b6fb107..59aa7f9c06b62de4e8997f325d66daba // Paper end - rewrite chunk system } -@@ -889,7 +890,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -889,7 +892,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe private void optimiseRandomTick(final LevelChunk chunk, final int tickSpeed) { final LevelChunkSection[] sections = chunk.getSections(); @@ -733,7 +1041,7 @@ index 973198ae0a73d6747e73548bdcbc1de46b6fb107..59aa7f9c06b62de4e8997f325d66daba final ca.spottedleaf.moonrise.common.util.SimpleThreadUnsafeRandom simpleRandom = this.simpleRandom; final boolean doubleTickFluids = !ca.spottedleaf.moonrise.common.PlatformHooks.get().configFixMC224294(); -@@ -897,42 +898,41 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -897,42 +900,41 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe final int offsetX = cpos.x << 4; final int offsetZ = cpos.z << 4; @@ -786,7 +1094,7 @@ index 973198ae0a73d6747e73548bdcbc1de46b6fb107..59aa7f9c06b62de4e8997f325d66daba } // Paper end - optimise random ticking -@@ -2525,30 +2525,25 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -2525,30 +2527,25 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe private boolean isPositionTickingWithEntitiesLoaded(long chunkPos) { // Paper start - rewrite chunk system @@ -821,6 +1129,19 @@ index 973198ae0a73d6747e73548bdcbc1de46b6fb107..59aa7f9c06b62de4e8997f325d66daba // Paper end - rewrite chunk system } +diff --git a/net/minecraft/server/level/ThreadedLevelLightEngine.java b/net/minecraft/server/level/ThreadedLevelLightEngine.java +index 5c9ac44a3b4bc8e047feaf61a94eb163761498a2..66dc6d77263d6f5de7d0a96b8b6575e7a363f5bf 100644 +--- a/net/minecraft/server/level/ThreadedLevelLightEngine.java ++++ b/net/minecraft/server/level/ThreadedLevelLightEngine.java +@@ -138,7 +138,7 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl + } + } + ); +- }); ++ }, world); // DivineMC - Chunk System Optimizations + + return chunks.size(); + } diff --git a/net/minecraft/world/level/LevelReader.java b/net/minecraft/world/level/LevelReader.java index 26c8c1e5598daf3550aef05b12218c47bda6618b..94c824ab1457939c425e1f99929d3222ee2c18a0 100644 --- a/net/minecraft/world/level/LevelReader.java @@ -2894,16 +3215,3 @@ index ab1dcbe416e2c3c94cfddf04b7ed053425a71806..a37eb2e29b4577ebc711e8ef7b47fbbc private Vec3i size = Vec3i.ZERO; private String author = "?"; // CraftBukkit start - data containers -diff --git a/net/minecraft/world/level/levelgen/synth/PerlinNoise.java b/net/minecraft/world/level/levelgen/synth/PerlinNoise.java -index ffac5b7b1eb1364ab8442d7145a7b4ebde68ee10..ef28df96ed569113a9d61f6ac4b4d84d578c02da 100644 ---- a/net/minecraft/world/level/levelgen/synth/PerlinNoise.java -+++ b/net/minecraft/world/level/levelgen/synth/PerlinNoise.java -@@ -187,7 +187,7 @@ public class PerlinNoise { - } - - public static double wrap(double value) { -- return value - Mth.lfloor(value / 3.3554432E7 + 0.5) * 3.3554432E7; -+ return value - Math.floor(value / 3.3554432E7 + 0.5) * 3.3554432E7; // DivineMC - avoid casting - } - - protected int firstOctave() { diff --git a/divinemc-server/minecraft-patches/features/0012-Optimize-hoppers.patch b/divinemc-server/minecraft-patches/features/0012-Optimize-hoppers.patch index ccc318c..66d8efc 100644 --- a/divinemc-server/minecraft-patches/features/0012-Optimize-hoppers.patch +++ b/divinemc-server/minecraft-patches/features/0012-Optimize-hoppers.patch @@ -17,7 +17,7 @@ index 9f7698f8ce56d5d89cf86f6ea2d5b4d51b18c9a2..351b035d1f3025af28b5147b95b912e0 if (org.bxteam.divinemc.DivineConfig.enableParallelWorldTicking) { serverLevelTickingSemaphore.acquire(); diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java -index 59aa7f9c06b62de4e8997f325d66daba28fa5871..5cb64b5da32183b7cd1e052c638e0789cc796bc4 100644 +index e17f5ba74295eb6585520ab9e5cc1cb2e3107098..15e6804a5faccb0a4f514d6b94f5856c7c15ab76 100644 --- a/net/minecraft/server/level/ServerLevel.java +++ b/net/minecraft/server/level/ServerLevel.java @@ -194,7 +194,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe @@ -29,7 +29,7 @@ index 59aa7f9c06b62de4e8997f325d66daba28fa5871..5cb64b5da32183b7cd1e052c638e0789 protected final Raids raids; private final ObjectLinkedOpenHashSet blockEvents = new ObjectLinkedOpenHashSet<>(); private final List blockEventsToReschedule = new ArrayList<>(64); -@@ -1728,7 +1728,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -1730,7 +1730,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe @Override public void sendBlockUpdated(BlockPos pos, BlockState oldState, BlockState newState, int flags) { @@ -38,7 +38,7 @@ index 59aa7f9c06b62de4e8997f325d66daba28fa5871..5cb64b5da32183b7cd1e052c638e0789 String string = "recursive call to sendBlockUpdated"; Util.logAndPauseIfInIde("recursive call to sendBlockUpdated", new IllegalStateException("recursive call to sendBlockUpdated")); } -@@ -1759,13 +1759,13 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -1761,13 +1761,13 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe // Paper end - catch CME see below why try { @@ -54,7 +54,7 @@ index 59aa7f9c06b62de4e8997f325d66daba28fa5871..5cb64b5da32183b7cd1e052c638e0789 } } } // Paper - option to disable pathfinding updates -@@ -2652,7 +2652,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -2654,7 +2654,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe } if (entity instanceof Mob mob) { @@ -63,7 +63,7 @@ index 59aa7f9c06b62de4e8997f325d66daba28fa5871..5cb64b5da32183b7cd1e052c638e0789 String string = "onTrackingStart called during navigation iteration"; Util.logAndPauseIfInIde( "onTrackingStart called during navigation iteration", new IllegalStateException("onTrackingStart called during navigation iteration") -@@ -2722,7 +2722,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -2724,7 +2724,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe } if (entity instanceof Mob mob) { diff --git a/divinemc-server/minecraft-patches/features/0018-Lag-compensation.patch b/divinemc-server/minecraft-patches/features/0018-Lag-compensation.patch index ad18523..3f05719 100644 --- a/divinemc-server/minecraft-patches/features/0018-Lag-compensation.patch +++ b/divinemc-server/minecraft-patches/features/0018-Lag-compensation.patch @@ -25,7 +25,7 @@ index 351b035d1f3025af28b5147b95b912e0e2ab9212..1bcccba4df407ec4d53f49c3c2c7493d this.tickCount++; this.tickRateManager.tick(); diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java -index 5cb64b5da32183b7cd1e052c638e0789cc796bc4..e3e00afd4621c4b487f5d2f0a74f773e631ed776 100644 +index 15e6804a5faccb0a4f514d6b94f5856c7c15ab76..a7743ea4e5d672e1aa0d0945e19f3469ef81df62 100644 --- a/net/minecraft/server/level/ServerLevel.java +++ b/net/minecraft/server/level/ServerLevel.java @@ -218,6 +218,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe @@ -36,7 +36,7 @@ index 5cb64b5da32183b7cd1e052c638e0789cc796bc4..e3e00afd4621c4b487f5d2f0a74f773e public LevelChunk getChunkIfLoaded(int x, int z) { return this.chunkSource.getChunkAtIfLoadedImmediately(x, z); // Paper - Use getChunkIfLoadedImmediately -@@ -772,6 +773,8 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -774,6 +775,8 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe } } @@ -45,7 +45,7 @@ index 5cb64b5da32183b7cd1e052c638e0789cc796bc4..e3e00afd4621c4b487f5d2f0a74f773e this.updateSkyBrightness(); if (runsNormally) { this.tickTime(); -@@ -851,11 +854,18 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -853,11 +856,18 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe this.setDayTime(this.preciseTime); } else // Purpur end - Configurable daylight cycle diff --git a/divinemc-server/minecraft-patches/features/0019-MSPT-Tracking-for-each-world.patch b/divinemc-server/minecraft-patches/features/0019-MSPT-Tracking-for-each-world.patch index 5c20e16..b8f7b10 100644 --- a/divinemc-server/minecraft-patches/features/0019-MSPT-Tracking-for-each-world.patch +++ b/divinemc-server/minecraft-patches/features/0019-MSPT-Tracking-for-each-world.patch @@ -25,10 +25,10 @@ index 1bcccba4df407ec4d53f49c3c2c7493db87b2240..54c8605a4e36605208344e39726a4c3b CrashReport crashReport = CrashReport.forThrowable(levelTickingException, "Exception ticking world"); serverLevel.fillReportDetails(crashReport); diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java -index e3e00afd4621c4b487f5d2f0a74f773e631ed776..a8a5da1f8519fe97f45ddd140afb9dc2a64c015e 100644 +index a7743ea4e5d672e1aa0d0945e19f3469ef81df62..67a1d115ff5facd2293ac7ba9d5eba890df343dd 100644 --- a/net/minecraft/server/level/ServerLevel.java +++ b/net/minecraft/server/level/ServerLevel.java -@@ -574,6 +574,12 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -575,6 +575,12 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe } // Paper end - chunk tick iteration diff --git a/divinemc-server/minecraft-patches/features/0043-Dynamic-Activation-of-Brain.patch b/divinemc-server/minecraft-patches/features/0043-Dynamic-Activation-of-Brain.patch index 3367bc2..772c9ce 100644 --- a/divinemc-server/minecraft-patches/features/0043-Dynamic-Activation-of-Brain.patch +++ b/divinemc-server/minecraft-patches/features/0043-Dynamic-Activation-of-Brain.patch @@ -31,10 +31,10 @@ index ae0a3c3d9d6300293a6d0dff5cae49ebe7c11dab..3b08dad7a9fac7ac9acec0bfb85d4826 } } diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java -index a8a5da1f8519fe97f45ddd140afb9dc2a64c015e..60fe99648a3d579f24172eb10067d859115d0ac9 100644 +index 67a1d115ff5facd2293ac7ba9d5eba890df343dd..c7bbadc61f51d04bc675926600e83ee0efb98e4a 100644 --- a/net/minecraft/server/level/ServerLevel.java +++ b/net/minecraft/server/level/ServerLevel.java -@@ -816,6 +816,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -818,6 +818,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe this.entityTickList .forEach( entity -> { diff --git a/divinemc-server/paper-patches/features/0006-Chunk-System-Optimizations.patch b/divinemc-server/paper-patches/features/0006-Chunk-System-Optimizations.patch new file mode 100644 index 0000000..3cb412e --- /dev/null +++ b/divinemc-server/paper-patches/features/0006-Chunk-System-Optimizations.patch @@ -0,0 +1,476 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Sun, 6 Apr 2025 18:03:38 +0300 +Subject: [PATCH] Chunk System Optimizations + + +diff --git a/src/main/java/ca/spottedleaf/moonrise/common/list/EntityList.java b/src/main/java/ca/spottedleaf/moonrise/common/list/EntityList.java +index 7fed43a1e7bcf35c4d7fd3224837a47fedd59860..353f1412b6edf481162ded50fa9a23d3442b9ed5 100644 +--- a/src/main/java/ca/spottedleaf/moonrise/common/list/EntityList.java ++++ b/src/main/java/ca/spottedleaf/moonrise/common/list/EntityList.java +@@ -1,5 +1,7 @@ + package ca.spottedleaf.moonrise.common.list; + ++import it.unimi.dsi.fastutil.ints.Int2IntMap; ++import it.unimi.dsi.fastutil.ints.Int2IntMaps; + import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; + import net.minecraft.world.entity.Entity; + import java.util.Arrays; +@@ -13,7 +15,7 @@ import java.util.NoSuchElementException; + */ + public final class EntityList implements Iterable { + +- private final Int2IntOpenHashMap entityToIndex = new Int2IntOpenHashMap(2, 0.8f); ++ private final Int2IntMap entityToIndex = Int2IntMaps.synchronize(new Int2IntOpenHashMap(2, 0.8f)); // DivineMC - Chunk System Optimizations + { + this.entityToIndex.defaultReturnValue(Integer.MIN_VALUE); + } +@@ -27,11 +29,11 @@ public final class EntityList implements Iterable { + return this.count; + } + +- public boolean contains(final Entity entity) { ++ public synchronized boolean contains(final Entity entity) { // DivineMC - Chunk System Optimizations + return this.entityToIndex.containsKey(entity.getId()); + } + +- public boolean remove(final Entity entity) { ++ public synchronized boolean remove(final Entity entity) { // DivineMC - Chunk System Optimizations + final int index = this.entityToIndex.remove(entity.getId()); + if (index == Integer.MIN_VALUE) { + return false; +@@ -50,7 +52,7 @@ public final class EntityList implements Iterable { + return true; + } + +- public boolean add(final Entity entity) { ++ public synchronized boolean add(final Entity entity) { // DivineMC - Chunk System Optimizations + final int count = this.count; + final int currIndex = this.entityToIndex.putIfAbsent(entity.getId(), count); + +@@ -82,18 +84,18 @@ public final class EntityList implements Iterable { + return this.entities[index]; + } + +- public Entity[] getRawData() { ++ public synchronized Entity[] getRawData() { // DivineMC - Chunk System Optimizations + return this.entities; + } + +- public void clear() { ++ public synchronized void clear() { // DivineMC - Chunk System Optimizations + this.entityToIndex.clear(); + Arrays.fill(this.entities, 0, this.count, null); + this.count = 0; + } + + @Override +- public Iterator iterator() { ++ public synchronized Iterator iterator() { // DivineMC - Chunk System Optimizations + return new Iterator<>() { + private Entity lastRet; + private int current; +diff --git a/src/main/java/ca/spottedleaf/moonrise/common/list/ReferenceList.java b/src/main/java/ca/spottedleaf/moonrise/common/list/ReferenceList.java +index 2e876b918672e8ef3b5197b7e6b1597247fdeaa1..aab585e226e0928d778dc83a33bdcaf5f5a6f213 100644 +--- a/src/main/java/ca/spottedleaf/moonrise/common/list/ReferenceList.java ++++ b/src/main/java/ca/spottedleaf/moonrise/common/list/ReferenceList.java +@@ -1,142 +1,26 @@ + package ca.spottedleaf.moonrise.common.list; + +-import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap; +-import java.util.Arrays; +-import java.util.Iterator; +-import java.util.NoSuchElementException; ++// DivineMC start - Chunk System Optimizations - rewrite ++import it.unimi.dsi.fastutil.objects.ReferenceArrayList; ++import it.unimi.dsi.fastutil.objects.ReferenceLists; + +-public final class ReferenceList implements Iterable { +- +- private static final Object[] EMPTY_LIST = new Object[0]; +- +- private final Reference2IntOpenHashMap referenceToIndex; +- private E[] references; +- private int count; +- +- public ReferenceList() { +- this((E[])EMPTY_LIST); +- } +- +- public ReferenceList(final E[] referenceArray) { +- this.references = referenceArray; +- this.referenceToIndex = new Reference2IntOpenHashMap<>(2, 0.8f); +- this.referenceToIndex.defaultReturnValue(Integer.MIN_VALUE); +- } +- +- private ReferenceList(final E[] references, final int count, final Reference2IntOpenHashMap referenceToIndex) { +- this.references = references; +- this.count = count; +- this.referenceToIndex = referenceToIndex; +- } +- +- public ReferenceList copy() { +- return new ReferenceList<>(this.references.clone(), this.count, this.referenceToIndex.clone()); +- } +- +- public int size() { +- return this.count; +- } +- +- public boolean contains(final E obj) { +- return this.referenceToIndex.containsKey(obj); ++public class ReferenceList extends ReferenceLists.SynchronizedList { ++ public ReferenceList(E[] elements) { ++ super(new RefListInner<>(elements)); + } + +- public boolean remove(final E obj) { +- final int index = this.referenceToIndex.removeInt(obj); +- if (index == Integer.MIN_VALUE) { +- return false; +- } +- +- // move the object at the end to this index +- final int endIndex = --this.count; +- final E end = (E)this.references[endIndex]; +- if (index != endIndex) { +- // not empty after this call +- this.referenceToIndex.put(end, index); // update index +- } +- this.references[index] = end; +- this.references[endIndex] = null; +- +- return true; ++ public synchronized E[] getRawDataUnchecked() { ++ return ((RefListInner) this.list).getRawDataUnchecked(); + } + +- public boolean add(final E obj) { +- final int count = this.count; +- final int currIndex = this.referenceToIndex.putIfAbsent(obj, count); +- +- if (currIndex != Integer.MIN_VALUE) { +- return false; // already in this list ++ public static class RefListInner extends ReferenceArrayList { ++ public RefListInner(A[] elements) { ++ super(elements, true); + } + +- E[] list = this.references; +- +- if (list.length == count) { +- // resize required +- list = this.references = Arrays.copyOf(list, (int)Math.max(4L, count * 2L)); // overflow results in negative ++ public A[] getRawDataUnchecked() { ++ return this.a; + } +- +- list[count] = obj; +- this.count = count + 1; +- +- return true; +- } +- +- public E getChecked(final int index) { +- if (index < 0 || index >= this.count) { +- throw new IndexOutOfBoundsException("Index: " + index + " is out of bounds, size: " + this.count); +- } +- return this.references[index]; +- } +- +- public E getUnchecked(final int index) { +- return this.references[index]; +- } +- +- public Object[] getRawData() { +- return this.references; +- } +- +- public E[] getRawDataUnchecked() { +- return this.references; +- } +- +- public void clear() { +- this.referenceToIndex.clear(); +- Arrays.fill(this.references, 0, this.count, null); +- this.count = 0; +- } +- +- @Override +- public Iterator iterator() { +- return new Iterator<>() { +- private E lastRet; +- private int current; +- +- @Override +- public boolean hasNext() { +- return this.current < ReferenceList.this.count; +- } +- +- @Override +- public E next() { +- if (this.current >= ReferenceList.this.count) { +- throw new NoSuchElementException(); +- } +- return this.lastRet = ReferenceList.this.references[this.current++]; +- } +- +- @Override +- public void remove() { +- final E lastRet = this.lastRet; +- +- if (lastRet == null) { +- throw new IllegalStateException(); +- } +- this.lastRet = null; +- +- ReferenceList.this.remove(lastRet); +- --this.current; +- } +- }; + } + } ++// DivineMC end - Chunk System Optimizations - rewrite +diff --git a/src/main/java/ca/spottedleaf/moonrise/common/list/ShortList.java b/src/main/java/ca/spottedleaf/moonrise/common/list/ShortList.java +index 2bae9949ef325d0001aa638150fbbdf968367e75..11bf4ddb298bb39f7f39a9c33c90b48a3171266b 100644 +--- a/src/main/java/ca/spottedleaf/moonrise/common/list/ShortList.java ++++ b/src/main/java/ca/spottedleaf/moonrise/common/list/ShortList.java +@@ -1,11 +1,13 @@ + package ca.spottedleaf.moonrise.common.list; + ++import it.unimi.dsi.fastutil.shorts.Short2ShortMap; ++import it.unimi.dsi.fastutil.shorts.Short2ShortMaps; + import it.unimi.dsi.fastutil.shorts.Short2ShortOpenHashMap; + import java.util.Arrays; + + public final class ShortList { + +- private final Short2ShortOpenHashMap map = new Short2ShortOpenHashMap(); ++ private final Short2ShortMap map = Short2ShortMaps.synchronize(new Short2ShortOpenHashMap()); // DivineMC - Chunk System Optimizations + { + this.map.defaultReturnValue(Short.MIN_VALUE); + } +@@ -13,13 +15,13 @@ public final class ShortList { + private static final short[] EMPTY_LIST = new short[0]; + + private short[] byIndex = EMPTY_LIST; +- private short count; ++ private volatile short count; // DivineMC - Chunk System Optimizations + + public int size() { +- return (int)this.count; ++ return this.count; // DivineMC - Chunk System Optimizations + } + +- public short getRaw(final int index) { ++ public synchronized short getRaw(final int index) { // DivineMC - Chunk System Optimizations + return this.byIndex[index]; + } + +@@ -30,8 +32,8 @@ public final class ShortList { + } + } + +- public boolean add(final short value) { +- final int count = (int)this.count; ++ public synchronized boolean add(final short value) { // DivineMC - Chunk System Optimizations ++ final int count = this.count; // DivineMC - Chunk System Optimizations + final short currIndex = this.map.putIfAbsent(value, (short)count); + + if (currIndex != Short.MIN_VALUE) { +@@ -51,7 +53,7 @@ public final class ShortList { + return true; + } + +- public boolean remove(final short value) { ++ public synchronized boolean remove(final short value) { // DivineMC - Chunk System Optimizations + final short index = this.map.remove(value); + if (index == Short.MIN_VALUE) { + return false; +@@ -70,7 +72,7 @@ public final class ShortList { + return true; + } + +- public void clear() { ++ public synchronized void clear() { // DivineMC - Chunk System Optimizations + this.count = (short)0; + this.map.clear(); + } +diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseCommon.java b/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseCommon.java +index 632920e04686d8a0fd0a60e87348be1fe7862a3c..f10c6c156b8dd9acecc8b1ee81bd28260fb6e4d8 100644 +--- a/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseCommon.java ++++ b/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseCommon.java +@@ -3,6 +3,12 @@ package ca.spottedleaf.moonrise.common.util; + import ca.spottedleaf.concurrentutil.executor.thread.PrioritisedThreadPool; + import ca.spottedleaf.moonrise.common.PlatformHooks; + import com.mojang.logging.LogUtils; ++import org.bxteam.divinemc.DivineConfig; ++import org.bxteam.divinemc.server.chunk.ChunkSystemAlgorithms; ++import org.bxteam.divinemc.server.chunk.TheChunkSystem; ++import org.bxteam.divinemc.spark.ThreadDumperRegistry; ++import org.bxteam.divinemc.util.ThreadBuilder; ++import org.jetbrains.annotations.NotNull; + import org.slf4j.Logger; + import java.util.concurrent.TimeUnit; + import java.util.concurrent.atomic.AtomicInteger; +@@ -12,83 +18,63 @@ public final class MoonriseCommon { + + private static final Logger LOGGER = LogUtils.getClassLogger(); + +- public static final PrioritisedThreadPool WORKER_POOL = new PrioritisedThreadPool( +- new Consumer<>() { +- private final AtomicInteger idGenerator = new AtomicInteger(); ++ public static TheChunkSystem WORKER_POOL; ++ public static TheChunkSystem.ExecutorGroup PARALLEL_GEN_GROUP; ++ public static TheChunkSystem.ExecutorGroup RADIUS_AWARE_GROUP; ++ public static TheChunkSystem.ExecutorGroup LOAD_GROUP; + +- @Override +- public void accept(Thread thread) { +- thread.setDaemon(true); +- thread.setName(PlatformHooks.get().getBrand() + " Common Worker #" + this.idGenerator.getAndIncrement()); +- thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { +- @Override +- public void uncaughtException(final Thread thread, final Throwable throwable) { +- LOGGER.error("Uncaught exception in thread " + thread.getName(), throwable); +- } +- }); +- } +- } +- ); +- public static final long WORKER_QUEUE_HOLD_TIME = (long)(20.0e6); // 20ms +- public static final int CLIENT_DIVISION = 0; +- public static final PrioritisedThreadPool.ExecutorGroup RENDER_EXECUTOR_GROUP = MoonriseCommon.WORKER_POOL.createExecutorGroup(CLIENT_DIVISION, 0); +- public static final int SERVER_DIVISION = 1; +- public static final PrioritisedThreadPool.ExecutorGroup PARALLEL_GEN_GROUP = MoonriseCommon.WORKER_POOL.createExecutorGroup(SERVER_DIVISION, 0); +- public static final PrioritisedThreadPool.ExecutorGroup RADIUS_AWARE_GROUP = MoonriseCommon.WORKER_POOL.createExecutorGroup(SERVER_DIVISION, 0); +- public static final PrioritisedThreadPool.ExecutorGroup LOAD_GROUP = MoonriseCommon.WORKER_POOL.createExecutorGroup(SERVER_DIVISION, 0); +- +- public static void adjustWorkerThreads(final int configWorkerThreads, final int configIoThreads) { +- int defaultWorkerThreads = Runtime.getRuntime().availableProcessors() / 2; +- if (defaultWorkerThreads <= 4) { +- defaultWorkerThreads = defaultWorkerThreads <= 3 ? 1 : 2; +- } else { +- defaultWorkerThreads = defaultWorkerThreads / 2; +- } +- defaultWorkerThreads = Integer.getInteger(PlatformHooks.get().getBrand() + ".WorkerThreadCount", Integer.valueOf(defaultWorkerThreads)); +- +- int workerThreads = configWorkerThreads; ++ public static void init(final int configWorkerThreads, final int configIoThreads) { ++ ChunkSystemAlgorithms algorithm = DivineConfig.chunkWorkerAlgorithm; ++ int workerThreads = algorithm.evalWorkers(configWorkerThreads, configIoThreads); ++ int ioThreads = algorithm.evalIO(configWorkerThreads, configIoThreads); + +- if (workerThreads <= 0) { +- workerThreads = defaultWorkerThreads; +- } ++ WORKER_POOL = buildChunkSystem(workerThreads); + +- final int ioThreads = Math.max(1, configIoThreads); ++ PARALLEL_GEN_GROUP = MoonriseCommon.WORKER_POOL.createExecutorGroup(); ++ RADIUS_AWARE_GROUP = MoonriseCommon.WORKER_POOL.createExecutorGroup(); ++ LOAD_GROUP = MoonriseCommon.WORKER_POOL.createExecutorGroup(); + +- WORKER_POOL.adjustThreadCount(workerThreads); + IO_POOL.adjustThreadCount(ioThreads); ++ LOGGER.info("Running ChunkSystem with {} worker threads and {} I/O threads", workerThreads, ioThreads); ++ } + +- LOGGER.info(PlatformHooks.get().getBrand() + " is using " + workerThreads + " worker threads, " + ioThreads + " I/O threads"); ++ private static @NotNull TheChunkSystem buildChunkSystem(int workerThreads) { ++ return new TheChunkSystem(workerThreads, new ThreadBuilder() { ++ @Override ++ public void accept(final Thread thread) { ++ thread.setPriority(DivineConfig.threadPoolPriority); ++ thread.setDaemon(true); ++ thread.setUncaughtExceptionHandler((thread1, throwable) -> LOGGER.error("Uncaught exception in thread {}", thread1.getName(), throwable)); ++ thread.setName("World Gen Worker #" + getAndIncrementId()); ++ ThreadDumperRegistry.REGISTRY.add(thread.getName()); ++ } ++ }); + } + + public static final PrioritisedThreadPool IO_POOL = new PrioritisedThreadPool( +- new Consumer<>() { +- private final AtomicInteger idGenerator = new AtomicInteger(); +- +- @Override +- public void accept(final Thread thread) { +- thread.setDaemon(true); +- thread.setName(PlatformHooks.get().getBrand() + " I/O Worker #" + this.idGenerator.getAndIncrement()); +- thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { +- @Override +- public void uncaughtException(final Thread thread, final Throwable throwable) { +- LOGGER.error("Uncaught exception in thread " + thread.getName(), throwable); +- } +- }); +- } ++ new Consumer<>() { ++ private final AtomicInteger idGenerator = new AtomicInteger(); ++ ++ @Override ++ public void accept(final Thread thread) { ++ thread.setDaemon(true); ++ thread.setName(PlatformHooks.get().getBrand() + " I/O Worker #" + this.idGenerator.getAndIncrement()); ++ ThreadDumperRegistry.REGISTRY.add(thread.getName()); ++ thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { ++ @Override ++ public void uncaughtException(final Thread thread, final Throwable throwable) { ++ LOGGER.error("Uncaught exception in thread {}", thread.getName(), throwable); ++ } ++ }); + } ++ } + ); + public static final long IO_QUEUE_HOLD_TIME = (long)(100.0e6); // 100ms +- public static final PrioritisedThreadPool.ExecutorGroup CLIENT_PROFILER_IO_GROUP = IO_POOL.createExecutorGroup(CLIENT_DIVISION, 0); +- public static final PrioritisedThreadPool.ExecutorGroup SERVER_REGION_IO_GROUP = IO_POOL.createExecutorGroup(SERVER_DIVISION, 0); ++ public static final PrioritisedThreadPool.ExecutorGroup SERVER_REGION_IO_GROUP = IO_POOL.createExecutorGroup(1, 0); + + public static void haltExecutors() { +- MoonriseCommon.WORKER_POOL.shutdown(false); +- LOGGER.info("Awaiting termination of worker pool for up to 60s..."); +- if (!MoonriseCommon.WORKER_POOL.join(TimeUnit.SECONDS.toMillis(60L))) { +- LOGGER.error("Worker pool did not shut down in time!"); +- MoonriseCommon.WORKER_POOL.halt(false); +- } +- ++ LOGGER.info("Shutting down ChunkSystem..."); ++ MoonriseCommon.WORKER_POOL.shutdown(); + MoonriseCommon.IO_POOL.shutdown(false); + LOGGER.info("Awaiting termination of I/O pool for up to 60s..."); + if (!MoonriseCommon.IO_POOL.join(TimeUnit.SECONDS.toMillis(60L))) { +diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseConstants.java b/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseConstants.java +index 559c959aff3c9deef867b9e425fba3e2e669cac6..a5b0585b56d71d21c9da3b129d213def142bb1f6 100644 +--- a/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseConstants.java ++++ b/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseConstants.java +@@ -4,7 +4,7 @@ import ca.spottedleaf.moonrise.common.PlatformHooks; + + public final class MoonriseConstants { + +- public static final int MAX_VIEW_DISTANCE = Integer.getInteger(PlatformHooks.get().getBrand() + ".MaxViewDistance", 32); ++ public static final int MAX_VIEW_DISTANCE = Integer.getInteger(PlatformHooks.get().getBrand() + ".MaxViewDistance", org.bxteam.divinemc.DivineConfig.maxViewDistance); // DivineMC - Configurable view distance + + private MoonriseConstants() {} + +diff --git a/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java +index 8b70a8e9b0aacbe7964b0441b5bbbaab228962d8..f0c420f4a1b282fb976825c33cb7a118e45de36d 100644 +--- a/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java ++++ b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java +@@ -229,7 +229,7 @@ public class GlobalConfiguration extends ConfigurationPart { + + @PostProcess + private void postProcess() { +- ca.spottedleaf.moonrise.common.util.MoonriseCommon.adjustWorkerThreads(this.workerThreads, this.ioThreads); ++ ca.spottedleaf.moonrise.common.util.MoonriseCommon.init(this.workerThreads, this.ioThreads); + String newChunkSystemGenParallelism = this.genParallelism; + if (newChunkSystemGenParallelism.equalsIgnoreCase("default")) { + newChunkSystemGenParallelism = "true"; +@@ -245,7 +245,6 @@ public class GlobalConfiguration extends ConfigurationPart { + } else { + throw new IllegalStateException("Invalid option for gen-parallelism: must be one of [on, off, enabled, disabled, true, false, default]"); + } +- FeatureHooks.initChunkTaskScheduler(useParallelGen); + } + } + diff --git a/divinemc-server/paper-patches/features/0006-Skip-EntityScheduler-s-executeTick-checks-if-there-i.patch b/divinemc-server/paper-patches/features/0007-Skip-EntityScheduler-s-executeTick-checks-if-there-i.patch similarity index 100% rename from divinemc-server/paper-patches/features/0006-Skip-EntityScheduler-s-executeTick-checks-if-there-i.patch rename to divinemc-server/paper-patches/features/0007-Skip-EntityScheduler-s-executeTick-checks-if-there-i.patch diff --git a/divinemc-server/paper-patches/features/0008-Add-chunk-worker-algorithm.patch b/divinemc-server/paper-patches/features/0008-Add-chunk-worker-algorithm.patch deleted file mode 100644 index 8e2fb32..0000000 --- a/divinemc-server/paper-patches/features/0008-Add-chunk-worker-algorithm.patch +++ /dev/null @@ -1,61 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> -Date: Sat, 22 Feb 2025 02:33:28 +0300 -Subject: [PATCH] Add chunk worker algorithm - - -diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseCommon.java b/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseCommon.java -index 632920e04686d8a0fd0a60e87348be1fe7862a3c..27447481c6e6b526cda032aff54a5c87256c217d 100644 ---- a/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseCommon.java -+++ b/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseCommon.java -@@ -3,6 +3,8 @@ package ca.spottedleaf.moonrise.common.util; - import ca.spottedleaf.concurrentutil.executor.thread.PrioritisedThreadPool; - import ca.spottedleaf.moonrise.common.PlatformHooks; - import com.mojang.logging.LogUtils; -+import org.bxteam.divinemc.DivineConfig; -+import org.bxteam.divinemc.server.chunk.ChunkSystemAlgorithms; - import org.slf4j.Logger; - import java.util.concurrent.TimeUnit; - import java.util.concurrent.atomic.AtomicInteger; -@@ -29,7 +31,7 @@ public final class MoonriseCommon { - } - } - ); -- public static final long WORKER_QUEUE_HOLD_TIME = (long)(20.0e6); // 20ms -+ public static final long WORKER_QUEUE_HOLD_TIME = (long)(2.0e6); // 2ms // DivineMC - Reduce from 20ms to 2ms - public static final int CLIENT_DIVISION = 0; - public static final PrioritisedThreadPool.ExecutorGroup RENDER_EXECUTOR_GROUP = MoonriseCommon.WORKER_POOL.createExecutorGroup(CLIENT_DIVISION, 0); - public static final int SERVER_DIVISION = 1; -@@ -38,26 +40,16 @@ public final class MoonriseCommon { - public static final PrioritisedThreadPool.ExecutorGroup LOAD_GROUP = MoonriseCommon.WORKER_POOL.createExecutorGroup(SERVER_DIVISION, 0); - - public static void adjustWorkerThreads(final int configWorkerThreads, final int configIoThreads) { -- int defaultWorkerThreads = Runtime.getRuntime().availableProcessors() / 2; -- if (defaultWorkerThreads <= 4) { -- defaultWorkerThreads = defaultWorkerThreads <= 3 ? 1 : 2; -- } else { -- defaultWorkerThreads = defaultWorkerThreads / 2; -- } -- defaultWorkerThreads = Integer.getInteger(PlatformHooks.get().getBrand() + ".WorkerThreadCount", Integer.valueOf(defaultWorkerThreads)); -- -- int workerThreads = configWorkerThreads; -- -- if (workerThreads <= 0) { -- workerThreads = defaultWorkerThreads; -- } -- -- final int ioThreads = Math.max(1, configIoThreads); -+ // DivineMC start - Add chunk worker algorithm -+ ChunkSystemAlgorithms algorithm = DivineConfig.chunkWorkerAlgorithm; -+ int workerThreads = algorithm.evalWorkers(configWorkerThreads, configIoThreads); -+ int ioThreads = algorithm.evalIO(configWorkerThreads, configIoThreads); - - WORKER_POOL.adjustThreadCount(workerThreads); - IO_POOL.adjustThreadCount(ioThreads); - -- LOGGER.info(PlatformHooks.get().getBrand() + " is using " + workerThreads + " worker threads, " + ioThreads + " I/O threads"); -+ LOGGER.info("ChunkSystem using '{}' algorithm, {} worker threads, {} I/O threads", algorithm.asDebugString(), workerThreads, ioThreads); -+ // DivineMC end - Add chunk worker algorithm - } - - public static final PrioritisedThreadPool IO_POOL = new PrioritisedThreadPool( diff --git a/divinemc-server/paper-patches/features/0007-Optimize-canSee-checks.patch b/divinemc-server/paper-patches/features/0008-Optimize-canSee-checks.patch similarity index 100% rename from divinemc-server/paper-patches/features/0007-Optimize-canSee-checks.patch rename to divinemc-server/paper-patches/features/0008-Optimize-canSee-checks.patch diff --git a/divinemc-server/paper-patches/features/0009-Configurable-thread-pool-priority.patch b/divinemc-server/paper-patches/features/0009-Configurable-thread-pool-priority.patch index c399ddc..ee4865d 100644 --- a/divinemc-server/paper-patches/features/0009-Configurable-thread-pool-priority.patch +++ b/divinemc-server/paper-patches/features/0009-Configurable-thread-pool-priority.patch @@ -5,14 +5,14 @@ Subject: [PATCH] Configurable thread pool priority diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseCommon.java b/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseCommon.java -index 27447481c6e6b526cda032aff54a5c87256c217d..87d22532c680b7c6d3244a13e91fccbcc1a7e004 100644 +index f10c6c156b8dd9acecc8b1ee81bd28260fb6e4d8..c720304d8f2427cd4433d76e28ede13552181648 100644 --- a/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseCommon.java +++ b/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseCommon.java -@@ -28,6 +28,7 @@ public final class MoonriseCommon { - LOGGER.error("Uncaught exception in thread " + thread.getName(), throwable); - } - }); +@@ -66,6 +66,7 @@ public final class MoonriseCommon { + LOGGER.error("Uncaught exception in thread {}", thread.getName(), throwable); + } + }); + thread.setPriority(DivineConfig.threadPoolPriority); // DivineMC - Configurable thread pool priority - } } + } ); diff --git a/divinemc-server/paper-patches/files/src/main/java/ca/spottedleaf/moonrise/common/list/EntityList.java.patch b/divinemc-server/paper-patches/files/src/main/java/ca/spottedleaf/moonrise/common/list/EntityList.java.patch deleted file mode 100644 index 4620846..0000000 --- a/divinemc-server/paper-patches/files/src/main/java/ca/spottedleaf/moonrise/common/list/EntityList.java.patch +++ /dev/null @@ -1,64 +0,0 @@ ---- a/src/main/java/ca/spottedleaf/moonrise/common/list/EntityList.java -+++ b/src/main/java/ca/spottedleaf/moonrise/common/list/EntityList.java -@@ -1,5 +_,7 @@ - package ca.spottedleaf.moonrise.common.list; - -+import it.unimi.dsi.fastutil.ints.Int2IntMap; -+import it.unimi.dsi.fastutil.ints.Int2IntMaps; - import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; - import net.minecraft.world.entity.Entity; - import java.util.Arrays; -@@ -13,7 +_,7 @@ - */ - public final class EntityList implements Iterable { - -- private final Int2IntOpenHashMap entityToIndex = new Int2IntOpenHashMap(2, 0.8f); -+ private final Int2IntMap entityToIndex = Int2IntMaps.synchronize(new Int2IntOpenHashMap(2, 0.8f)); - { - this.entityToIndex.defaultReturnValue(Integer.MIN_VALUE); - } -@@ -27,11 +_,11 @@ - return this.count; - } - -- public boolean contains(final Entity entity) { -+ public synchronized boolean contains(final Entity entity) { - return this.entityToIndex.containsKey(entity.getId()); - } - -- public boolean remove(final Entity entity) { -+ public synchronized boolean remove(final Entity entity) { - final int index = this.entityToIndex.remove(entity.getId()); - if (index == Integer.MIN_VALUE) { - return false; -@@ -50,7 +_,7 @@ - return true; - } - -- public boolean add(final Entity entity) { -+ public synchronized boolean add(final Entity entity) { - final int count = this.count; - final int currIndex = this.entityToIndex.putIfAbsent(entity.getId(), count); - -@@ -82,18 +_,18 @@ - return this.entities[index]; - } - -- public Entity[] getRawData() { -+ public synchronized Entity[] getRawData() { - return this.entities; - } - -- public void clear() { -+ public synchronized void clear() { - this.entityToIndex.clear(); - Arrays.fill(this.entities, 0, this.count, null); - this.count = 0; - } - - @Override -- public Iterator iterator() { -+ public synchronized Iterator iterator() { - return new Iterator<>() { - private Entity lastRet; - private int current; diff --git a/divinemc-server/paper-patches/files/src/main/java/ca/spottedleaf/moonrise/common/list/ReferenceList.java.patch b/divinemc-server/paper-patches/files/src/main/java/ca/spottedleaf/moonrise/common/list/ReferenceList.java.patch deleted file mode 100644 index 245318f..0000000 --- a/divinemc-server/paper-patches/files/src/main/java/ca/spottedleaf/moonrise/common/list/ReferenceList.java.patch +++ /dev/null @@ -1,165 +0,0 @@ ---- a/src/main/java/ca/spottedleaf/moonrise/common/list/ReferenceList.java -+++ b/src/main/java/ca/spottedleaf/moonrise/common/list/ReferenceList.java -@@ -1,142 +_,24 @@ - package ca.spottedleaf.moonrise.common.list; - --import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap; --import java.util.Arrays; --import java.util.Iterator; --import java.util.NoSuchElementException; -- --public final class ReferenceList implements Iterable { -- -- private static final Object[] EMPTY_LIST = new Object[0]; -- -- private final Reference2IntOpenHashMap referenceToIndex; -- private E[] references; -- private int count; -- -- public ReferenceList() { -- this((E[])EMPTY_LIST); -- } -- -- public ReferenceList(final E[] referenceArray) { -- this.references = referenceArray; -- this.referenceToIndex = new Reference2IntOpenHashMap<>(2, 0.8f); -- this.referenceToIndex.defaultReturnValue(Integer.MIN_VALUE); -- } -- -- private ReferenceList(final E[] references, final int count, final Reference2IntOpenHashMap referenceToIndex) { -- this.references = references; -- this.count = count; -- this.referenceToIndex = referenceToIndex; -- } -- -- public ReferenceList copy() { -- return new ReferenceList<>(this.references.clone(), this.count, this.referenceToIndex.clone()); -- } -- -- public int size() { -- return this.count; -- } -- -- public boolean contains(final E obj) { -- return this.referenceToIndex.containsKey(obj); -- } -- -- public boolean remove(final E obj) { -- final int index = this.referenceToIndex.removeInt(obj); -- if (index == Integer.MIN_VALUE) { -- return false; -- } -- -- // move the object at the end to this index -- final int endIndex = --this.count; -- final E end = (E)this.references[endIndex]; -- if (index != endIndex) { -- // not empty after this call -- this.referenceToIndex.put(end, index); // update index -- } -- this.references[index] = end; -- this.references[endIndex] = null; -- -- return true; -- } -- -- public boolean add(final E obj) { -- final int count = this.count; -- final int currIndex = this.referenceToIndex.putIfAbsent(obj, count); -- -- if (currIndex != Integer.MIN_VALUE) { -- return false; // already in this list -- } -- -- E[] list = this.references; -- -- if (list.length == count) { -- // resize required -- list = this.references = Arrays.copyOf(list, (int)Math.max(4L, count * 2L)); // overflow results in negative -- } -- -- list[count] = obj; -- this.count = count + 1; -- -- return true; -- } -- -- public E getChecked(final int index) { -- if (index < 0 || index >= this.count) { -- throw new IndexOutOfBoundsException("Index: " + index + " is out of bounds, size: " + this.count); -- } -- return this.references[index]; -- } -- -- public E getUnchecked(final int index) { -- return this.references[index]; -- } -- -- public Object[] getRawData() { -- return this.references; -- } -- -- public E[] getRawDataUnchecked() { -- return this.references; -- } -- -- public void clear() { -- this.referenceToIndex.clear(); -- Arrays.fill(this.references, 0, this.count, null); -- this.count = 0; -- } -- -- @Override -- public Iterator iterator() { -- return new Iterator<>() { -- private E lastRet; -- private int current; -- -- @Override -- public boolean hasNext() { -- return this.current < ReferenceList.this.count; -- } -- -- @Override -- public E next() { -- if (this.current >= ReferenceList.this.count) { -- throw new NoSuchElementException(); -- } -- return this.lastRet = ReferenceList.this.references[this.current++]; -- } -- -- @Override -- public void remove() { -- final E lastRet = this.lastRet; -- -- if (lastRet == null) { -- throw new IllegalStateException(); -- } -- this.lastRet = null; -- -- ReferenceList.this.remove(lastRet); -- --this.current; -- } -- }; -+import it.unimi.dsi.fastutil.objects.ReferenceArrayList; -+import it.unimi.dsi.fastutil.objects.ReferenceLists; -+ -+public class ReferenceList extends ReferenceLists.SynchronizedList { -+ public ReferenceList(E[] elements) { -+ super(new RefListInner<>(elements)); -+ } -+ -+ public synchronized E[] getRawDataUnchecked() { -+ return ((RefListInner) this.list).getRawDataUnchecked(); -+ } -+ -+ public static class RefListInner extends ReferenceArrayList { -+ public RefListInner(A[] elements) { -+ super(elements, true); -+ } -+ -+ public A[] getRawDataUnchecked() { -+ return this.a; -+ } - } - } diff --git a/divinemc-server/paper-patches/files/src/main/java/ca/spottedleaf/moonrise/common/list/ShortList.java.patch b/divinemc-server/paper-patches/files/src/main/java/ca/spottedleaf/moonrise/common/list/ShortList.java.patch deleted file mode 100644 index a6ea03b..0000000 --- a/divinemc-server/paper-patches/files/src/main/java/ca/spottedleaf/moonrise/common/list/ShortList.java.patch +++ /dev/null @@ -1,63 +0,0 @@ ---- a/src/main/java/ca/spottedleaf/moonrise/common/list/ShortList.java -+++ b/src/main/java/ca/spottedleaf/moonrise/common/list/ShortList.java -@@ -1,11 +_,13 @@ - package ca.spottedleaf.moonrise.common.list; - -+import it.unimi.dsi.fastutil.shorts.Short2ShortMap; -+import it.unimi.dsi.fastutil.shorts.Short2ShortMaps; - import it.unimi.dsi.fastutil.shorts.Short2ShortOpenHashMap; - import java.util.Arrays; - - public final class ShortList { - -- private final Short2ShortOpenHashMap map = new Short2ShortOpenHashMap(); -+ private final Short2ShortMap map = Short2ShortMaps.synchronize(new Short2ShortOpenHashMap()); - { - this.map.defaultReturnValue(Short.MIN_VALUE); - } -@@ -13,13 +_,13 @@ - private static final short[] EMPTY_LIST = new short[0]; - - private short[] byIndex = EMPTY_LIST; -- private short count; -+ private volatile short count; - - public int size() { -- return (int)this.count; -+ return this.count; - } - -- public short getRaw(final int index) { -+ public synchronized short getRaw(final int index) { - return this.byIndex[index]; - } - -@@ -30,8 +_,8 @@ - } - } - -- public boolean add(final short value) { -- final int count = (int)this.count; -+ public synchronized boolean add(final short value) { -+ final int count = this.count; - final short currIndex = this.map.putIfAbsent(value, (short)count); - - if (currIndex != Short.MIN_VALUE) { -@@ -51,7 +_,7 @@ - return true; - } - -- public boolean remove(final short value) { -+ public synchronized boolean remove(final short value) { - final short index = this.map.remove(value); - if (index == Short.MIN_VALUE) { - return false; -@@ -70,7 +_,7 @@ - return true; - } - -- public void clear() { -+ public synchronized void clear() { - this.count = (short)0; - this.map.clear(); - } diff --git a/divinemc-server/paper-patches/files/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseConstants.java.patch b/divinemc-server/paper-patches/files/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseConstants.java.patch deleted file mode 100644 index 017d982..0000000 --- a/divinemc-server/paper-patches/files/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseConstants.java.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- a/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseConstants.java -+++ b/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseConstants.java -@@ -4,7 +_,7 @@ - - public final class MoonriseConstants { - -- public static final int MAX_VIEW_DISTANCE = Integer.getInteger(PlatformHooks.get().getBrand() + ".MaxViewDistance", 32); -+ public static final int MAX_VIEW_DISTANCE = Integer.getInteger(PlatformHooks.get().getBrand() + ".MaxViewDistance", org.bxteam.divinemc.DivineConfig.maxViewDistance); // DivineMC - Configurable view distance - - private MoonriseConstants() {} - diff --git a/divinemc-server/src/main/java/com/ishland/flowsched/executor/ExecutorManager.java b/divinemc-server/src/main/java/com/ishland/flowsched/executor/ExecutorManager.java new file mode 100644 index 0000000..b1f2d69 --- /dev/null +++ b/divinemc-server/src/main/java/com/ishland/flowsched/executor/ExecutorManager.java @@ -0,0 +1,191 @@ +package com.ishland.flowsched.executor; + +import com.ishland.flowsched.structs.DynamicPriorityQueue; +import com.ishland.flowsched.util.Assertions; +import it.unimi.dsi.fastutil.objects.ReferenceArrayList; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.Executor; +import java.util.function.Consumer; + +public class ExecutorManager { + public final DynamicPriorityQueue globalWorkQueue; + protected final ConcurrentMap lockListeners = new ConcurrentHashMap<>(); + protected final WorkerThread[] workerThreads; + final Object workerMonitor = new Object(); + + /** + * Creates a new executor manager. + * + * @param workerThreadCount the number of worker threads. + */ + public ExecutorManager(int workerThreadCount) { + this(workerThreadCount, thread -> { }); + } + + /** + * Creates a new executor manager. + * + * @param workerThreadCount the number of worker threads. + * @param threadInitializer the thread initializer. + */ + public ExecutorManager(int workerThreadCount, Consumer threadInitializer) { + globalWorkQueue = new DynamicPriorityQueue<>(); + workerThreads = new WorkerThread[workerThreadCount]; + for (int i = 0; i < workerThreadCount; i++) { + final WorkerThread thread = new WorkerThread(this); + threadInitializer.accept(thread); + thread.start(); + workerThreads[i] = thread; + } + } + + /** + * Attempt to lock the given tokens. + * The caller should discard the task if this method returns false, as it reschedules the task. + * + * @return {@code true} if the lock is acquired, {@code false} otherwise. + */ + boolean tryLock(Task task) { + retry: + while (true) { + final FreeableTaskList listenerSet = new FreeableTaskList(); + LockToken[] lockTokens = task.lockTokens(); + for (int i = 0; i < lockTokens.length; i++) { + LockToken token = lockTokens[i]; + final FreeableTaskList present = this.lockListeners.putIfAbsent(token, listenerSet); + if (present != null) { + for (int j = 0; j < i; j++) { + this.lockListeners.remove(lockTokens[j], listenerSet); + } + callListeners(listenerSet); + synchronized (present) { + if (present.freed) { + continue retry; + } else { + present.add(task); + } + } + return false; + } + } + return true; + } + } + + /** + * Release the locks held by the given task. + * + * @param task the task. + */ + void releaseLocks(Task task) { + FreeableTaskList expectedListeners = null; + for (LockToken token : task.lockTokens()) { + final FreeableTaskList listeners = this.lockListeners.remove(token); + if (listeners != null) { + if (expectedListeners == null) { + expectedListeners = listeners; + } else { + Assertions.assertTrue(expectedListeners == listeners, "Inconsistent lock listeners"); + } + } else { + throw new IllegalStateException("Lock token " + token + " is not locked"); + } + } + if (expectedListeners != null) { + callListeners(expectedListeners); // synchronizes + } + } + + private void callListeners(FreeableTaskList listeners) { + synchronized (listeners) { + listeners.freed = true; + if (listeners.isEmpty()) return; + for (Task listener : listeners) { + this.schedule0(listener); + } + } + this.wakeup(); + } + + /** + * Polls an executable task from the global work queue. + * + * @return the task, or {@code null} if no task is executable. + */ + Task pollExecutableTask() { + Task task; + while ((task = this.globalWorkQueue.dequeue()) != null) { + if (this.tryLock(task)) { + return task; + } + } + return null; + } + + /** + * Shuts down the executor manager. + */ + public void shutdown() { + for (WorkerThread workerThread : workerThreads) { + workerThread.shutdown(); + } + } + + /** + * Schedules a task. + * + * @param task the task. + */ + public void schedule(Task task) { + schedule0(task); + wakeup(); + } + + private void schedule0(Task task) { + this.globalWorkQueue.enqueue(task, task.priority()); + } + + public void wakeup() { // Canvas - private -> public + synchronized (this.workerMonitor) { + this.workerMonitor.notify(); + } + } + + public boolean hasPendingTasks() { + return this.globalWorkQueue.size() != 0; + } + + /** + * Schedules a runnable for execution with the given priority. + * + * @param runnable the runnable. + * @param priority the priority. + */ + public void schedule(Runnable runnable, int priority) { + this.schedule(new SimpleTask(runnable, priority)); + } + + /** + * Creates an executor that schedules runnables with the given priority. + * + * @param priority the priority. + * @return the executor. + */ + public Executor executor(int priority) { + return runnable -> this.schedule(runnable, priority); + } + + /** + * Notifies the executor manager that the priority of the given task has changed. + * + * @param task the task. + */ + public void notifyPriorityChange(Task task) { + this.globalWorkQueue.changePriority(task, task.priority()); + } + + protected static class FreeableTaskList extends ReferenceArrayList { // Canvas - private -> protected + private boolean freed = false; + } +} diff --git a/divinemc-server/src/main/java/com/ishland/flowsched/executor/LockToken.java b/divinemc-server/src/main/java/com/ishland/flowsched/executor/LockToken.java new file mode 100644 index 0000000..96b9eb7 --- /dev/null +++ b/divinemc-server/src/main/java/com/ishland/flowsched/executor/LockToken.java @@ -0,0 +1,3 @@ +package com.ishland.flowsched.executor; + +public interface LockToken { } diff --git a/divinemc-server/src/main/java/com/ishland/flowsched/executor/SimpleTask.java b/divinemc-server/src/main/java/com/ishland/flowsched/executor/SimpleTask.java new file mode 100644 index 0000000..93a32fa --- /dev/null +++ b/divinemc-server/src/main/java/com/ishland/flowsched/executor/SimpleTask.java @@ -0,0 +1,37 @@ +package com.ishland.flowsched.executor; + +import java.util.Objects; + +public class SimpleTask implements Task { + private final Runnable wrapped; + private final int priority; + + public SimpleTask(Runnable wrapped, int priority) { + this.wrapped = Objects.requireNonNull(wrapped); + this.priority = priority; + } + + @Override + public void run(Runnable releaseLocks) { + try { + wrapped.run(); + } finally { + releaseLocks.run(); + } + } + + @Override + public void propagateException(Throwable t) { + t.printStackTrace(); + } + + @Override + public LockToken[] lockTokens() { + return new LockToken[0]; + } + + @Override + public int priority() { + return this.priority; + } +} diff --git a/divinemc-server/src/main/java/com/ishland/flowsched/executor/Task.java b/divinemc-server/src/main/java/com/ishland/flowsched/executor/Task.java new file mode 100644 index 0000000..86aa983 --- /dev/null +++ b/divinemc-server/src/main/java/com/ishland/flowsched/executor/Task.java @@ -0,0 +1,11 @@ +package com.ishland.flowsched.executor; + +public interface Task { + void run(Runnable releaseLocks); + + void propagateException(Throwable t); + + LockToken[] lockTokens(); + + int priority(); +} diff --git a/divinemc-server/src/main/java/com/ishland/flowsched/executor/WorkerThread.java b/divinemc-server/src/main/java/com/ishland/flowsched/executor/WorkerThread.java new file mode 100644 index 0000000..6f93999 --- /dev/null +++ b/divinemc-server/src/main/java/com/ishland/flowsched/executor/WorkerThread.java @@ -0,0 +1,84 @@ +package com.ishland.flowsched.executor; + +import ca.spottedleaf.moonrise.common.util.TickThread; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.LockSupport; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class WorkerThread extends TickThread { + private static final Logger LOGGER = LoggerFactory.getLogger("FlowSched Executor Worker Thread"); + + private final ExecutorManager executorManager; + private final AtomicBoolean shutdown = new AtomicBoolean(false); + public volatile boolean active = false; + + public WorkerThread(ExecutorManager executorManager) { + super("null_worker"); + this.executorManager = executorManager; + } + + @Override + public void run() { + main_loop: + while (true) { + if (this.shutdown.get()) { + return; + } + active = true; + if (pollTasks()) { + continue; + } + + synchronized (this.executorManager.workerMonitor) { + if (this.executorManager.hasPendingTasks()) continue main_loop; + try { + active = false; + this.executorManager.workerMonitor.wait(); + } catch (InterruptedException ignored) { + } + } + } + } + + private boolean pollTasks() { + final Task task = executorManager.pollExecutableTask(); + try { + if (task != null) { + AtomicBoolean released = new AtomicBoolean(false); + try { + task.run(() -> { + if (released.compareAndSet(false, true)) { + executorManager.releaseLocks(task); + } + }); + } catch (Throwable t) { + try { + if (released.compareAndSet(false, true)) { + executorManager.releaseLocks(task); + } + } catch (Throwable t1) { + t.addSuppressed(t1); + LOGGER.error("Exception thrown while releasing locks", t); + } + try { + task.propagateException(t); + } catch (Throwable t1) { + t.addSuppressed(t1); + LOGGER.error("Exception thrown while propagating exception", t); + } + } + return true; + } + return false; + } catch (Throwable t) { + LOGGER.error("Exception thrown while executing task", t); + return true; + } + } + + public void shutdown() { + shutdown.set(true); + LockSupport.unpark(this); + } +} diff --git a/divinemc-server/src/main/java/com/ishland/flowsched/structs/DynamicPriorityQueue.java b/divinemc-server/src/main/java/com/ishland/flowsched/structs/DynamicPriorityQueue.java new file mode 100644 index 0000000..48b62a7 --- /dev/null +++ b/divinemc-server/src/main/java/com/ishland/flowsched/structs/DynamicPriorityQueue.java @@ -0,0 +1,85 @@ +package com.ishland.flowsched.structs; + +import org.bxteam.divinemc.server.chunk.PriorityHandler; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.AtomicIntegerArray; + +@SuppressWarnings("unchecked") +public class DynamicPriorityQueue { + private final AtomicIntegerArray taskCount; + public final ConcurrentLinkedQueue[] priorities; + private final ConcurrentHashMap priorityMap = new ConcurrentHashMap<>(); + + public DynamicPriorityQueue() { + this.taskCount = new AtomicIntegerArray(PriorityHandler.MAX_PRIORITY + 1); + this.priorities = new ConcurrentLinkedQueue[PriorityHandler.MAX_PRIORITY + 1]; + for (int i = 0; i < (PriorityHandler.MAX_PRIORITY + 1); i++) { + this.priorities[i] = new ConcurrentLinkedQueue<>(); + } + } + + public void enqueue(E element, int priority) { + if (this.priorityMap.putIfAbsent(element, priority) != null) + throw new IllegalArgumentException("Element already in queue"); + + this.priorities[priority].add(element); + this.taskCount.incrementAndGet(priority); + } + + public boolean changePriority(E element, int newPriority) { + Integer currentPriority = this.priorityMap.get(element); + if (currentPriority == null || currentPriority == newPriority) { + return false; + } + + int currentIndex = currentPriority; + boolean removedFromQueue = this.priorities[currentIndex].remove(element); + if (!removedFromQueue) { + return false; + } + + this.taskCount.decrementAndGet(currentIndex); + final boolean changeSuccess = this.priorityMap.replace(element, currentPriority, newPriority); + if (!changeSuccess) { + return false; + } + + this.priorities[newPriority].add(element); + this.taskCount.incrementAndGet(newPriority); + return true; + } + + public E dequeue() { + for (int i = 0; i < this.priorities.length; i++) { + if (this.taskCount.get(i) == 0) continue; + E element = priorities[i].poll(); + if (element != null) { + this.taskCount.decrementAndGet(i); + this.priorityMap.remove(element); + return element; + } + } + return null; + } + + public boolean contains(E element) { + return priorityMap.containsKey(element); + } + + public void remove(E element) { + Integer priority = this.priorityMap.remove(element); + if (priority == null) return; + + boolean removed = this.priorities[priority].remove(element); + if (removed) this.taskCount.decrementAndGet(priority); + } + + public int size() { + return priorityMap.size(); + } + + public boolean isEmpty() { + return size() == 0; + } +} diff --git a/divinemc-server/src/main/java/com/ishland/flowsched/util/Assertions.java b/divinemc-server/src/main/java/com/ishland/flowsched/util/Assertions.java new file mode 100644 index 0000000..c3ee6c4 --- /dev/null +++ b/divinemc-server/src/main/java/com/ishland/flowsched/util/Assertions.java @@ -0,0 +1,27 @@ +package com.ishland.flowsched.util; + +public final class Assertions { + public static void assertTrue(boolean value, String message) { + if (!value) { + final AssertionError error = new AssertionError(message); + error.printStackTrace(); + throw error; + } + } + + public static void assertTrue(boolean state, String format, Object... args) { + if (!state) { + final AssertionError error = new AssertionError(String.format(format, args)); + error.printStackTrace(); + throw error; + } + } + + public static void assertTrue(boolean value) { + if (!value) { + final AssertionError error = new AssertionError(); + error.printStackTrace(); + throw error; + } + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/server/chunk/ChunkRunnable.java b/divinemc-server/src/main/java/org/bxteam/divinemc/server/chunk/ChunkRunnable.java new file mode 100644 index 0000000..9db47bc --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/server/chunk/ChunkRunnable.java @@ -0,0 +1,32 @@ +package org.bxteam.divinemc.server.chunk; + +import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; +import net.minecraft.server.level.ServerLevel; +import java.lang.invoke.VarHandle; + +public class ChunkRunnable implements Runnable { + public final int chunkX; + public final int chunkZ; + public final ServerLevel world; + private volatile Runnable toRun; + private static final VarHandle TO_RUN_HANDLE = ConcurrentUtil.getVarHandle(ChunkRunnable.class, "toRun", Runnable.class); + + public ChunkRunnable(int chunkX, int chunkZ, ServerLevel world, Runnable run) { + this.chunkX = chunkX; + this.chunkZ = chunkZ; + this.world = world; + this.toRun = run; + } + + public void setRunnable(final Runnable run) { + final Runnable prev = (Runnable)TO_RUN_HANDLE.compareAndExchange(this, (Runnable)null, run); + if (prev != null) { + throw new IllegalStateException("Runnable already set"); + } + } + + @Override + public void run() { + ((Runnable)TO_RUN_HANDLE.getVolatile(this)).run(); + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/server/chunk/ChunkSystemTaskQueue.java b/divinemc-server/src/main/java/org/bxteam/divinemc/server/chunk/ChunkSystemTaskQueue.java new file mode 100644 index 0000000..2581b37 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/server/chunk/ChunkSystemTaskQueue.java @@ -0,0 +1,428 @@ +package org.bxteam.divinemc.server.chunk; + +import ca.spottedleaf.concurrentutil.executor.PrioritisedExecutor; +import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; +import ca.spottedleaf.concurrentutil.util.Priority; +import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.executor.RadiusAwarePrioritisedExecutor; +import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.task.ChunkUpgradeGenericStatusTask; +import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.task.GenericDataLoadTask; +import java.lang.invoke.VarHandle; +import java.util.Comparator; +import java.util.Map; +import java.util.concurrent.ConcurrentSkipListMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; + +public final class ChunkSystemTaskQueue implements PrioritisedExecutor { + private final AtomicLong taskIdGenerator = new AtomicLong(); + private final AtomicLong scheduledTasks = new AtomicLong(); + private final AtomicLong executedTasks = new AtomicLong(); + private final AtomicLong subOrderGenerator = new AtomicLong(); + private final AtomicBoolean shutdown = new AtomicBoolean(); + private final ConcurrentSkipListMap tasks = new ConcurrentSkipListMap<>(ChunkSystemTaskQueue.PrioritisedQueuedTask.COMPARATOR); + private final TheChunkSystem chunkSystem; + + public ChunkSystemTaskQueue(TheChunkSystem chunkSystem) { + this.chunkSystem = chunkSystem; + } + + @Override + public long getTotalTasksScheduled() { + return this.scheduledTasks.get(); + } + + @Override + public long getTotalTasksExecuted() { + return this.executedTasks.get(); + } + + @Override + public long generateNextSubOrder() { + return this.subOrderGenerator.getAndIncrement(); + } + + @Override + public boolean shutdown() { + return !this.shutdown.getAndSet(true); + } + + @Override + public boolean isShutdown() { + return this.shutdown.get(); + } + + @Override + public boolean executeTask() { + for (; ; ) { + final Map.Entry firstEntry = this.tasks.pollFirstEntry(); + if (firstEntry != null) { + final ChunkSystemTaskQueue.PrioritisedQueuedTask.Holder task = firstEntry.getKey(); + task.markRemoved(); + if (!task.task.execute()) { + continue; + } + return true; + } + + return false; + } + } + + @Override + public PrioritisedTask createTask(final Runnable task) { + return this.createTask(task, Priority.NORMAL, this.generateNextSubOrder()); + } + + @Override + public PrioritisedTask createTask(final Runnable task, final Priority priority) { + return this.createTask(task, priority, this.generateNextSubOrder()); + } + + @Override + public PrioritisedTask createTask(final Runnable task, final Priority priority, final long subOrder) { + return new ChunkSystemTaskQueue.PrioritisedQueuedTask(task, priority, subOrder); + } + + @Override + public PrioritisedTask queueTask(final Runnable task) { + return this.queueTask(task, Priority.NORMAL, this.generateNextSubOrder()); + } + + @Override + public PrioritisedTask queueTask(final Runnable task, final Priority priority) { + return this.queueTask(task, priority, this.generateNextSubOrder()); + } + + @Override + public PrioritisedTask queueTask(final Runnable task, final Priority priority, final long subOrder) { + final ChunkSystemTaskQueue.PrioritisedQueuedTask ret = new ChunkSystemTaskQueue.PrioritisedQueuedTask(task, priority, subOrder); + + ret.queue(); + + return ret; + } + + private final class PrioritisedQueuedTask implements PrioritisedExecutor.PrioritisedTask { + public static final Comparator COMPARATOR = (final ChunkSystemTaskQueue.PrioritisedQueuedTask.Holder t1, final ChunkSystemTaskQueue.PrioritisedQueuedTask.Holder t2) -> { + final int priorityCompare = t1.priority - t2.priority; + if (priorityCompare != 0) { + return priorityCompare; + } + + final int subOrderCompare = Long.compare(t1.subOrder, t2.subOrder); + if (subOrderCompare != 0) { + return subOrderCompare; + } + + return Long.compare(t1.id, t2.id); + }; + private final long id; + private final Runnable execute; + private Priority priority; + private long subOrder; + private ChunkSystemTaskQueue.PrioritisedQueuedTask.Holder holder; + + public PrioritisedQueuedTask(final Runnable execute, final Priority priority, final long subOrder) { + if (!Priority.isValidPriority(priority)) { + throw new IllegalArgumentException("Invalid priority " + priority); + } + + this.execute = execute; + this.priority = priority; + this.subOrder = subOrder; + this.id = ChunkSystemTaskQueue.this.taskIdGenerator.getAndIncrement(); + } + + @Override + public PrioritisedExecutor getExecutor() { + return ChunkSystemTaskQueue.this; + } + + @Override + public boolean queue() { + synchronized (this) { + if (this.holder != null || this.priority == Priority.COMPLETING) { + return false; + } + + if (ChunkSystemTaskQueue.this.isShutdown()) { + throw new IllegalStateException("Queue is shutdown"); + } + + this.holder = new Holder(this, this.priority.priority, this.subOrder, this.id); + + ChunkSystemTaskQueue.this.scheduledTasks.getAndIncrement(); + int priority = this.holder.task.priority.priority; + if (this.holder.task.priority.isHigherOrEqualPriority(Priority.BLOCKING)) { + priority = PriorityHandler.BLOCKING; + } else if (this.holder.task.execute instanceof ChunkUpgradeGenericStatusTask upgradeTask) { + int x = upgradeTask.chunkX; + int z = upgradeTask.chunkZ; + priority = upgradeTask.world.chunkSystemPriorities.priority(x, z); + } else if (this.holder.task.execute instanceof RadiusAwarePrioritisedExecutor.Task task) { + int x = task.chunkX; + int z = task.chunkZ; + if (!(x == 0 && z == 0)) { + priority = task.world.chunkSystemPriorities.priority(x, z); + } // else | infinite radius task, ignore. + } else if (this.holder.task.execute instanceof GenericDataLoadTask.ProcessOffMainTask offMainTask) { + int x = offMainTask.loadTask().chunkX; + int z = offMainTask.loadTask().chunkZ; + priority = offMainTask.loadTask().world.chunkSystemPriorities.priority(x, z); + } else if (this.holder.task.execute instanceof ChunkRunnable chunkRunnable) { + int x = chunkRunnable.chunkX; + int z = chunkRunnable.chunkZ; + priority = chunkRunnable.world.chunkSystemPriorities.priority(x, z); + } + ChunkSystemTaskQueue.this.chunkSystem.schedule(this.holder.task.execute, priority); + } + + if (ChunkSystemTaskQueue.this.isShutdown()) { + this.cancel(); + throw new IllegalStateException("Queue is shutdown"); + } + + return true; + } + + @Override + public boolean isQueued() { + synchronized (this) { + return this.holder != null && this.priority != Priority.COMPLETING; + } + } + + @Override + public boolean cancel() { + synchronized (this) { + if (this.priority == Priority.COMPLETING) { + return false; + } + + this.priority = Priority.COMPLETING; + + if (this.holder != null) { + if (this.holder.markRemoved()) { + ChunkSystemTaskQueue.this.tasks.remove(this.holder); + } + ChunkSystemTaskQueue.this.executedTasks.getAndIncrement(); + } + + return true; + } + } + + @Override + public boolean execute() { + final boolean increaseExecuted; + + synchronized (this) { + if (this.priority == Priority.COMPLETING) { + return false; + } + + this.priority = Priority.COMPLETING; + + if (increaseExecuted = (this.holder != null)) { + if (this.holder.markRemoved()) { + ChunkSystemTaskQueue.this.tasks.remove(this.holder); + } + } + } + + try { + this.execute.run(); + return true; + } finally { + if (increaseExecuted) { + ChunkSystemTaskQueue.this.executedTasks.getAndIncrement(); + } + } + } + + @Override + public Priority getPriority() { + synchronized (this) { + return this.priority; + } + } + + @Override + public boolean setPriority(final Priority priority) { + synchronized (this) { + if (this.priority == Priority.COMPLETING || this.priority == priority) { + return false; + } + + this.priority = priority; + + if (this.holder != null) { + if (this.holder.markRemoved()) { + ChunkSystemTaskQueue.this.tasks.remove(this.holder); + } + this.holder = new ChunkSystemTaskQueue.PrioritisedQueuedTask.Holder(this, priority.priority, this.subOrder, this.id); + ChunkSystemTaskQueue.this.tasks.put(this.holder, Boolean.TRUE); + } + + return true; + } + } + + @Override + public boolean raisePriority(final Priority priority) { + synchronized (this) { + if (this.priority == Priority.COMPLETING || this.priority.isHigherOrEqualPriority(priority)) { + return false; + } + + this.priority = priority; + + if (this.holder != null) { + if (this.holder.markRemoved()) { + ChunkSystemTaskQueue.this.tasks.remove(this.holder); + } + this.holder = new ChunkSystemTaskQueue.PrioritisedQueuedTask.Holder(this, priority.priority, this.subOrder, this.id); + ChunkSystemTaskQueue.this.tasks.put(this.holder, Boolean.TRUE); + } + + return true; + } + } + + @Override + public boolean lowerPriority(Priority priority) { + synchronized (this) { + if (this.priority == Priority.COMPLETING || this.priority.isLowerOrEqualPriority(priority)) { + return false; + } + + this.priority = priority; + + if (this.holder != null) { + if (this.holder.markRemoved()) { + ChunkSystemTaskQueue.this.tasks.remove(this.holder); + } + this.holder = new ChunkSystemTaskQueue.PrioritisedQueuedTask.Holder(this, priority.priority, this.subOrder, this.id); + ChunkSystemTaskQueue.this.tasks.put(this.holder, Boolean.TRUE); + } + + return true; + } + } + + @Override + public long getSubOrder() { + synchronized (this) { + return this.subOrder; + } + } + + @Override + public boolean setSubOrder(final long subOrder) { + synchronized (this) { + if (this.priority == Priority.COMPLETING || this.subOrder == subOrder) { + return false; + } + + this.subOrder = subOrder; + + if (this.holder != null) { + if (this.holder.markRemoved()) { + ChunkSystemTaskQueue.this.tasks.remove(this.holder); + } + this.holder = new ChunkSystemTaskQueue.PrioritisedQueuedTask.Holder(this, priority.priority, this.subOrder, this.id); + ChunkSystemTaskQueue.this.tasks.put(this.holder, Boolean.TRUE); + } + + return true; + } + } + + @Override + public boolean raiseSubOrder(long subOrder) { + synchronized (this) { + if (this.priority == Priority.COMPLETING || this.subOrder >= subOrder) { + return false; + } + + this.subOrder = subOrder; + + if (this.holder != null) { + if (this.holder.markRemoved()) { + ChunkSystemTaskQueue.this.tasks.remove(this.holder); + } + this.holder = new ChunkSystemTaskQueue.PrioritisedQueuedTask.Holder(this, priority.priority, this.subOrder, this.id); + ChunkSystemTaskQueue.this.tasks.put(this.holder, Boolean.TRUE); + } + + return true; + } + } + + @Override + public boolean lowerSubOrder(final long subOrder) { + synchronized (this) { + if (this.priority == Priority.COMPLETING || this.subOrder <= subOrder) { + return false; + } + + this.subOrder = subOrder; + + if (this.holder != null) { + if (this.holder.markRemoved()) { + ChunkSystemTaskQueue.this.tasks.remove(this.holder); + } + this.holder = new ChunkSystemTaskQueue.PrioritisedQueuedTask.Holder(this, priority.priority, this.subOrder, this.id); + ChunkSystemTaskQueue.this.tasks.put(this.holder, Boolean.TRUE); + } + + return true; + } + } + + @Override + public boolean setPriorityAndSubOrder(final Priority priority, final long subOrder) { + synchronized (this) { + if (this.priority == Priority.COMPLETING || (this.priority == priority && this.subOrder == subOrder)) { + return false; + } + + this.priority = priority; + this.subOrder = subOrder; + + if (this.holder != null) { + if (this.holder.markRemoved()) { + ChunkSystemTaskQueue.this.tasks.remove(this.holder); + } + this.holder = new ChunkSystemTaskQueue.PrioritisedQueuedTask.Holder(this, priority.priority, this.subOrder, this.id); + ChunkSystemTaskQueue.this.tasks.put(this.holder, Boolean.TRUE); + } + + return true; + } + } + + private static final class Holder { + private static final VarHandle REMOVED_HANDLE = ConcurrentUtil.getVarHandle(ChunkSystemTaskQueue.PrioritisedQueuedTask.Holder.class, "removed", boolean.class); + private final ChunkSystemTaskQueue.PrioritisedQueuedTask task; + private final int priority; + private final long subOrder; + private final long id; + private volatile boolean removed; + + private Holder( + final ChunkSystemTaskQueue.PrioritisedQueuedTask task, final int priority, final long subOrder, + final long id + ) { + this.task = task; + this.priority = priority; + this.subOrder = subOrder; + this.id = id; + } + + public boolean markRemoved() { + return !(boolean) REMOVED_HANDLE.getAndSet((ChunkSystemTaskQueue.PrioritisedQueuedTask.Holder) this, (boolean) true); + } + } + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/server/chunk/PriorityHandler.java b/divinemc-server/src/main/java/org/bxteam/divinemc/server/chunk/PriorityHandler.java new file mode 100644 index 0000000..88ff0e9 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/server/chunk/PriorityHandler.java @@ -0,0 +1,32 @@ +package org.bxteam.divinemc.server.chunk; + +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.util.Mth; +import net.minecraft.world.level.ChunkPos; + +import static ca.spottedleaf.moonrise.common.util.MoonriseConstants.MAX_VIEW_DISTANCE; + +public class PriorityHandler { + public static final int MAX_PRIORITY = MAX_VIEW_DISTANCE + 2; + public static final int BLOCKING = 0; + public final ServerLevel level; + + public PriorityHandler(ServerLevel world) { + this.level = world; + } + + public int priority(int chunkX, int chunkZ) { + int priority = MAX_PRIORITY; + for (final ServerPlayer player : this.level.players()) { + ChunkPos playerChunk = player.chunkPosition(); + int playerChunkX = playerChunk.x; + int playerChunkZ = playerChunk.z; + + int dist = Math.max(Mth.abs(playerChunkX - chunkX), Mth.abs(playerChunkZ - chunkZ)); + int distPriority = Math.max(0, MAX_VIEW_DISTANCE - dist); + priority = Math.min(priority, distPriority); + } + return priority; + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/server/chunk/TheChunkSystem.java b/divinemc-server/src/main/java/org/bxteam/divinemc/server/chunk/TheChunkSystem.java new file mode 100644 index 0000000..2a51c6f --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/server/chunk/TheChunkSystem.java @@ -0,0 +1,355 @@ +package org.bxteam.divinemc.server.chunk; + +import ca.spottedleaf.concurrentutil.executor.PrioritisedExecutor; +import ca.spottedleaf.concurrentutil.util.Priority; +import com.ishland.flowsched.executor.ExecutorManager; +import org.bxteam.divinemc.util.ThreadBuilder; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.lang.reflect.Array; +import java.util.Arrays; +import java.util.concurrent.atomic.AtomicLong; + +public class TheChunkSystem extends ExecutorManager { + protected final Logger LOGGER = LoggerFactory.getLogger("TheChunkSystem"); + + private final TheChunkSystem.COWArrayList executors = new TheChunkSystem.COWArrayList<>(TheChunkSystem.ExecutorGroup.class); + private boolean shutdown; + + public TheChunkSystem(final int workerThreadCount, final ThreadBuilder threadInitializer) { + super(workerThreadCount, threadInitializer); + LOGGER.info("Initialized new ChunkSystem with {} allocated threads", workerThreadCount); + } + + @Override + public void shutdown() { + synchronized (this) { + this.shutdown = true; + } + + super.shutdown(); + this.wakeup(); + + for (final TheChunkSystem.ExecutorGroup group : this.executors.getArray()) { + for (final TheChunkSystem.ExecutorGroup.ThreadPoolExecutor executor : group.executors.getArray()) { + executor.shutdown(); + } + } + + LOGGER.info("ChunkSystem shutdown complete"); + } + + private void notifyAllThreads() { + this.wakeup(); + } + + public TheChunkSystem.ExecutorGroup createExecutorGroup() { + synchronized (this) { + if (this.shutdown) { + throw new IllegalStateException("Queue is shutdown: " + this); + } + + final TheChunkSystem.ExecutorGroup ret = new TheChunkSystem.ExecutorGroup(); + + this.executors.add(ret); + + return ret; + } + } + + private static final class COWArrayList { + private volatile E[] array; + + public COWArrayList(final Class clazz) { + this.array = (E[]) Array.newInstance(clazz, 0); + } + + public E[] getArray() { + return this.array; + } + + public void add(final E element) { + synchronized (this) { + final E[] array = this.array; + + final E[] copy = Arrays.copyOf(array, array.length + 1); + copy[array.length] = element; + + this.array = copy; + } + } + + public boolean remove(final E element) { + synchronized (this) { + final E[] array = this.array; + int index = -1; + for (int i = 0, len = array.length; i < len; ++i) { + if (array[i] == element) { + index = i; + break; + } + } + + if (index == -1) { + return false; + } + + final E[] copy = (E[]) Array.newInstance(array.getClass().getComponentType(), array.length - 1); + + System.arraycopy(array, 0, copy, 0, index); + System.arraycopy(array, index + 1, copy, index, (array.length - 1) - index); + + this.array = copy; + } + + return true; + } + } + + public final class ExecutorGroup { + private final AtomicLong subOrderGenerator = new AtomicLong(); + private final TheChunkSystem.COWArrayList executors = new TheChunkSystem.COWArrayList<>(TheChunkSystem.ExecutorGroup.ThreadPoolExecutor.class); + + private ExecutorGroup() { } + + public TheChunkSystem.ExecutorGroup.ThreadPoolExecutor[] getAllExecutors() { + return this.executors.getArray().clone(); + } + + private TheChunkSystem getThreadPool() { + return TheChunkSystem.this; + } + + public TheChunkSystem.ExecutorGroup.@NotNull ThreadPoolExecutor createExecutor() { + synchronized (TheChunkSystem.this) { + if (TheChunkSystem.this.shutdown) { + throw new IllegalStateException("Queue is shutdown: " + TheChunkSystem.this); + } + + final TheChunkSystem.ExecutorGroup.ThreadPoolExecutor ret = new TheChunkSystem.ExecutorGroup.ThreadPoolExecutor(); + + this.executors.add(ret); + + return ret; + } + } + + public final class ThreadPoolExecutor implements PrioritisedExecutor { + private final ChunkSystemTaskQueue taskBuilder = new ChunkSystemTaskQueue(TheChunkSystem.this); + private volatile boolean halt; + + private ThreadPoolExecutor() { } + + private TheChunkSystem.ExecutorGroup getGroup() { + return TheChunkSystem.ExecutorGroup.this; + } + + private void notifyPriorityShift() { + TheChunkSystem.this.notifyAllThreads(); + } + + private void notifyScheduled() { + TheChunkSystem.this.notifyAllThreads(); + } + + /** + * Removes this queue from the thread pool without shutting the queue down or waiting for queued tasks to be executed + */ + public void halt() { + this.halt = true; + + TheChunkSystem.ExecutorGroup.this.executors.remove(this); + } + + public boolean isActive() { + if (this.halt) { + return false; + } else { + if (!this.isShutdown()) { + return true; + } + + return !TheChunkSystem.this.globalWorkQueue.isEmpty(); + } + } + + @Override + public boolean shutdown() { + if (TheChunkSystem.this.globalWorkQueue.isEmpty()) { + TheChunkSystem.ExecutorGroup.this.executors.remove(this); + } + + return true; + } + + @Override + public boolean isShutdown() { + return TheChunkSystem.this.shutdown; + } + + @Override + public long getTotalTasksScheduled() { + return 0; // TODO: implement + } + + @Override + public long getTotalTasksExecuted() { + return 0; // TODO: implement + } + + @Override + public long generateNextSubOrder() { + return TheChunkSystem.ExecutorGroup.this.subOrderGenerator.getAndIncrement(); + } + + @Override + public boolean executeTask() { + throw new UnsupportedOperationException("Unable to execute task from ThreadPoolExecutor as interface into FlowSched"); + } + + @Override + public PrioritisedTask queueTask(final Runnable task) { + final PrioritisedTask ret = this.createTask(task); + + ret.queue(); + + return ret; + } + + @Override + public PrioritisedTask queueTask(final Runnable task, final Priority priority) { + final PrioritisedTask ret = this.createTask(task, priority); + + ret.queue(); + + return ret; + } + + @Override + public PrioritisedTask queueTask(final Runnable task, final Priority priority, final long subOrder) { + final PrioritisedTask ret = this.createTask(task, priority, subOrder); + + ret.queue(); + + return ret; + } + + @Override + public PrioritisedTask createTask(final Runnable task) { + return this.createTask(task, Priority.NORMAL); + } + + @Override + public PrioritisedTask createTask(final Runnable task, final Priority priority) { + return this.createTask(task, priority, this.generateNextSubOrder()); + } + + @Override + public PrioritisedTask createTask(final Runnable task, final Priority priority, final long subOrder) { + return new TheChunkSystem.ExecutorGroup.ThreadPoolExecutor.WrappedTask(this.taskBuilder.createTask(task, priority, subOrder)); + } + + private final class WrappedTask implements PrioritisedTask { + private final PrioritisedTask wrapped; + + private WrappedTask(final PrioritisedTask wrapped) { + this.wrapped = wrapped; + } + + @Override + public PrioritisedExecutor getExecutor() { + return TheChunkSystem.ExecutorGroup.ThreadPoolExecutor.this; + } + + @Override + public boolean queue() { + if (this.wrapped.queue()) { + final Priority priority = this.getPriority(); + if (priority != Priority.COMPLETING) { + TheChunkSystem.ExecutorGroup.ThreadPoolExecutor.this.notifyScheduled(); + } + return true; + } + + return false; + } + + @Override + public boolean isQueued() { + return this.wrapped.isQueued(); + } + + @Override + public boolean cancel() { + return this.wrapped.cancel(); + } + + @Override + public boolean execute() { + return this.wrapped.execute(); + } + + @Override + public Priority getPriority() { + return this.wrapped.getPriority(); + } + + @Override + public boolean setPriority(final Priority priority) { + if (this.wrapped.setPriority(priority)) { + TheChunkSystem.ExecutorGroup.ThreadPoolExecutor.this.notifyPriorityShift(); + return true; + } + + return false; + } + + @Override + public boolean raisePriority(final Priority priority) { + if (this.wrapped.raisePriority(priority)) { + TheChunkSystem.ExecutorGroup.ThreadPoolExecutor.this.notifyPriorityShift(); + return true; + } + + return false; + } + + @Override + public boolean lowerPriority(final Priority priority) { + return this.wrapped.lowerPriority(priority); + } + + @Override + public long getSubOrder() { + return this.wrapped.getSubOrder(); + } + + @Override + public boolean setSubOrder(final long subOrder) { + return this.wrapped.setSubOrder(subOrder); + } + + @Override + public boolean raiseSubOrder(final long subOrder) { + return this.wrapped.raiseSubOrder(subOrder); + } + + @Override + public boolean lowerSubOrder(final long subOrder) { + return this.wrapped.lowerSubOrder(subOrder); + } + + @Override + public boolean setPriorityAndSubOrder(final Priority priority, final long subOrder) { + if (this.wrapped.setPriorityAndSubOrder(priority, subOrder)) { + TheChunkSystem.ExecutorGroup.ThreadPoolExecutor.this.notifyPriorityShift(); + return true; + } + + return false; + } + } + } + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/util/ThreadBuilder.java b/divinemc-server/src/main/java/org/bxteam/divinemc/util/ThreadBuilder.java new file mode 100644 index 0000000..3922885 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/util/ThreadBuilder.java @@ -0,0 +1,12 @@ +package org.bxteam.divinemc.util; + +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; + +public interface ThreadBuilder extends Consumer { + AtomicInteger id = new AtomicInteger(); + + default int getAndIncrementId() { + return id.getAndIncrement(); + } +}