diff --git a/.github/workflows/upstream.yml b/.github/workflows/upstream.yml deleted file mode 100644 index 125f4fd..0000000 --- a/.github/workflows/upstream.yml +++ /dev/null @@ -1,47 +0,0 @@ -name: Upstream Purpur -on: - workflow_dispatch: {} - schedule: - - cron: "0 0 */3 * *" -jobs: - build: - runs-on: ubuntu-latest - steps: - - name: Checkout DivineMC Repository - uses: actions/checkout@v3 - with: - path: 'DivineMC' - token: ${{ secrets.GITHUB_TOKEN }} - - - name: Checkout Purpur Repository - uses: actions/checkout@v3 - with: - path: 'Purpur' - repository: "PurpurMC/Purpur" - token: ${{ secrets.GITHUB_TOKEN }} - - - name: Get Purpur Latest Commit Hash - id: purpurRef - run: | - ls - cd Purpur - echo "::set-output name=purpurRef::$(git rev-parse HEAD)" - cd .. - - - name: Update purpurRef in DivineMC - run: | - cd DivineMC - sed -i "s/\(purpurRef\s*=\s*\).*/\1$PAPER_REF/" gradle.properties - env: - PAPER_REF: ${{ steps.purpurRef.outputs.purpurRef }} - - name: Check for changes and Write to repository - run: | - cd DivineMC - if ! git diff --quiet; then - git add gradle.properties - git config --global user.email "action@github.com" && git config --global user.name "Github Action" - git commit -m "Updated Upstream (Purpur)" - git push - else - echo "No changes to commit." - fi diff --git a/README.md b/README.md index 584448c..01bbfb4 100644 --- a/README.md +++ b/README.md @@ -16,10 +16,10 @@ DivineMC is a high-performance [Purpur](https://github.com/PurpurMC/Purpur) fork ## ⚙️ Features - **Based on [Purpur](https://github.com/PurpurMC/Purpur)** that adds a high customization level to the server. -- All worlds **are ticked in parallel**, so the server can take full advantage of multicore processors. -- **Implemented Secure Seed mod** that changes default 64-bit seed to a 1024-bit seed, making it almost impossible to crack the seed. +- Implemented **Parallel world ticking** feature, that allows to server take advantage of multiple CPU cores to tick worlds. +- Implemented **Secure Seed** mod that changes default 64-bit seed to a 1024-bit seed, making it almost impossible to crack the seed. - **Optimized chunk generation** that can generate chunks up to 70% faster than vanilla. -- **Async** pathfinding, mob spawning and entity tracker +- **Async** pathfinding, entity tracker, mob spawning and chunk sending. - Implemented **Linear region file format** - **Fully compatible** with Bukkit, Spigot and Paper plugins - **Fixes** some Minecraft bugs @@ -29,7 +29,7 @@ DivineMC is a high-performance [Purpur](https://github.com/PurpurMC/Purpur) fork ## 📥 Downloading & Installing If you want to install DivineMC, you can read our [installation documentation](https://bxteam.org/docs/divinemc/getting-started/installation). -You can find the latest successful build in [GitHub Action](https://github.com/BX-Team/DivineMC/actions) or [Releases](https://github.com/BX-Team/DivineMC/releases) +You can find the latest successful build in [Releases](https://github.com/BX-Team/DivineMC/releases) or you can use [MCJars](https://mcjars.app/DIVINEMC/versions) website. ## 📈 bStats [![bStats](https://bstats.org/signatures/server-implementation/DivineMC.svg)](https://bstats.org/plugin/server-implementation/DivineMC) diff --git a/build-data/divinemc.at b/build-data/divinemc.at index 14aee74..3384a7b 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 @@ -55,6 +66,7 @@ public net.minecraft.world.level.levelgen.Xoroshiro128PlusPlus seedHi public net.minecraft.world.level.levelgen.Xoroshiro128PlusPlus seedLo public net.minecraft.world.level.levelgen.XoroshiroRandomSource randomNumberGenerator public net.minecraft.world.level.levelgen.structure.pools.StructureTemplatePool rawTemplates +public net.minecraft.world.level.levelgen.structure.structures.WoodlandMansionPieces$SimpleGrid public net.minecraft.world.level.levelgen.synth.BlendedNoise mainNoise public net.minecraft.world.level.levelgen.synth.BlendedNoise maxLimitNoise public net.minecraft.world.level.levelgen.synth.BlendedNoise minLimitNoise @@ -72,3 +84,4 @@ public net.minecraft.world.level.levelgen.synth.PerlinNoise lowestFreqValueFacto public net.minecraft.world.level.levelgen.synth.PerlinNoise noiseLevels public net.minecraft.world.level.levelgen.synth.SimplexNoise p public net.minecraft.world.level.pathfinder.SwimNodeEvaluator allowBreaching +public-f ca.spottedleaf.moonrise.paper.PaperHooks diff --git a/divinemc-server/minecraft-patches/features/0001-Rebrand.patch b/divinemc-server/minecraft-patches/features/0001-Rebrand.patch index 1ece2ec..d36e881 100644 --- a/divinemc-server/minecraft-patches/features/0001-Rebrand.patch +++ b/divinemc-server/minecraft-patches/features/0001-Rebrand.patch @@ -17,25 +17,6 @@ index 394443d00e661715439be1e56dddc129947699a4..480ad57a6b7b74e6b83e9c6ceb69ea1f public CrashReport(String title, Throwable exception) { io.papermc.paper.util.StacktraceDeobfuscator.INSTANCE.deobfuscateThrowable(exception); // Paper -diff --git a/net/minecraft/server/Main.java b/net/minecraft/server/Main.java -index 1485186d4989874ef89c4e83830f26358a43759c..b48fc9e0b95fe6c8f72c5501b8de374e6ac2e5d6 100644 ---- a/net/minecraft/server/Main.java -+++ b/net/minecraft/server/Main.java -@@ -62,6 +62,14 @@ import org.slf4j.Logger; - - public class Main { - private static final Logger LOGGER = LogUtils.getLogger(); -+ // DivineMC start - Log experimental warning -+ static { -+ io.papermc.paper.ServerBuildInfo info = io.papermc.paper.ServerBuildInfo.buildInfo(); -+ if (io.papermc.paper.ServerBuildInfoImpl.IS_EXPERIMENTAL) { -+ LOGGER.warn("Running an experimental version of {}, please proceed with caution.", info.brandName()); -+ } -+ } -+ // DivineMC end - Log experimental warning - - @SuppressForbidden( - reason = "System.out needed before bootstrap" diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java index 781030cb2e0316151c20351f04347c8db63f43e1..6bb8afb3b0e92c374474c92fa44dc7b80af0bd73 100644 --- a/net/minecraft/server/MinecraftServer.java diff --git a/divinemc-server/minecraft-patches/features/0003-Completely-remove-Mojang-profiler.patch b/divinemc-server/minecraft-patches/features/0003-Completely-remove-Mojang-profiler.patch index 6c3603c..ed5ce7b 100644 --- a/divinemc-server/minecraft-patches/features/0003-Completely-remove-Mojang-profiler.patch +++ b/divinemc-server/minecraft-patches/features/0003-Completely-remove-Mojang-profiler.patch @@ -1293,7 +1293,7 @@ index 6540b2d6a1062d883811ce240c49d30d1925b291..8055b8552b40160732953b15876dda79 } diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java -index 3c3e8e58cd2761ab2f0652e63f944a5c9a95dca8..3980e0dfb08a357384ea33670fb282a26a598f6e 100644 +index eb6286d34a68bf6eb57877a9cfc2be09615c7e83..f4b1f45f1dc86bd077860f088cdd6da78ecc6020 100644 --- a/net/minecraft/server/level/ServerLevel.java +++ b/net/minecraft/server/level/ServerLevel.java @@ -77,8 +77,6 @@ import net.minecraft.util.ProgressListener; @@ -1437,7 +1437,7 @@ index 3c3e8e58cd2761ab2f0652e63f944a5c9a95dca8..3980e0dfb08a357384ea33670fb282a2 } @VisibleForTesting -@@ -1330,17 +1296,13 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -1356,17 +1322,13 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe } // Paper end - log detailed entity tick information entity.setOldPosAndRot(); @@ -1455,7 +1455,7 @@ index 3c3e8e58cd2761ab2f0652e63f944a5c9a95dca8..3980e0dfb08a357384ea33670fb282a2 for (Entity entity1 : entity.getPassengers()) { this.tickPassenger(entity, entity1, isActive); // Paper - EAR 2 -@@ -1361,9 +1323,6 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -1387,9 +1349,6 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe passengerEntity.setOldPosAndRot(); passengerEntity.tickCount++; passengerEntity.totalEntityAge++; // Paper - age-like counter for all entities @@ -1465,7 +1465,7 @@ index 3c3e8e58cd2761ab2f0652e63f944a5c9a95dca8..3980e0dfb08a357384ea33670fb282a2 // Paper start - EAR 2 if (isActive) { passengerEntity.rideTick(); -@@ -1375,7 +1334,6 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -1401,7 +1360,6 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe ridingEntity.positionRider(passengerEntity); } // Paper end - EAR 2 @@ -1510,7 +1510,7 @@ index a9f723528dd05cb9583319edcb14143b784a2fd7..21d41b477cc0e8d2476d1e3141bdf23d this.stopUsingItem(); this.connection.send(new ClientboundPlayerAbilitiesPacket(this.getAbilities())); diff --git a/net/minecraft/server/network/ServerCommonPacketListenerImpl.java b/net/minecraft/server/network/ServerCommonPacketListenerImpl.java -index 398c1733824b689520170de0be94006731afa5cd..0c499eca39371993c46d510fa45298efdd67d20c 100644 +index c089a01765945277aafc62cb3566d81162c40c1d..801dd76a2c7f76fc6fdb7167cbf3ab1310be36c9 100644 --- a/net/minecraft/server/network/ServerCommonPacketListenerImpl.java +++ b/net/minecraft/server/network/ServerCommonPacketListenerImpl.java @@ -24,7 +24,6 @@ import net.minecraft.network.protocol.cookie.ServerboundCookieResponsePacket; @@ -1521,7 +1521,7 @@ index 398c1733824b689520170de0be94006731afa5cd..0c499eca39371993c46d510fa45298ef import org.slf4j.Logger; public abstract class ServerCommonPacketListenerImpl implements ServerCommonPacketListener, org.bukkit.craftbukkit.entity.CraftPlayer.TransferCookieConnection { // CraftBukkit -@@ -252,7 +251,6 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack +@@ -256,7 +255,6 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack } protected void keepConnectionAlive() { @@ -1529,7 +1529,7 @@ index 398c1733824b689520170de0be94006731afa5cd..0c499eca39371993c46d510fa45298ef long millis = Util.getMillis(); // Paper start - give clients a longer time to respond to pings as per pre 1.12.2 timings // This should effectively place the keepalive handling back to "as it was" before 1.12.2 -@@ -286,8 +284,6 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack +@@ -290,8 +288,6 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack this.send(new ClientboundKeepAlivePacket(this.keepAliveChallenge)); } } diff --git a/divinemc-server/minecraft-patches/features/0007-Multithreaded-Tracker.patch b/divinemc-server/minecraft-patches/features/0006-Multithreaded-Tracker.patch similarity index 88% rename from divinemc-server/minecraft-patches/features/0007-Multithreaded-Tracker.patch rename to divinemc-server/minecraft-patches/features/0006-Multithreaded-Tracker.patch index e2f1eb6..2f524d7 100644 --- a/divinemc-server/minecraft-patches/features/0007-Multithreaded-Tracker.patch +++ b/divinemc-server/minecraft-patches/features/0006-Multithreaded-Tracker.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Multithreaded Tracker diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java b/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java -index 8ffe79b81777015ff807538e461ec68463225557..b28083be4384d6c5efbdce898a0e9d7a2f5bd3d3 100644 +index dd2509996bfd08e8c3f9f2be042229eac6d7692d..8ef5a1aaac9c27873ce746eb281f77bb318a3c69 100644 --- a/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java +++ b/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java @@ -342,7 +342,11 @@ public final class RegionizedPlayerChunkLoader { @@ -22,32 +22,10 @@ index 8ffe79b81777015ff807538e461ec68463225557..b28083be4384d6c5efbdce898a0e9d7a private static final byte CHUNK_TICKET_STAGE_NONE = 0; private static final byte CHUNK_TICKET_STAGE_LOADING = 1; diff --git a/net/minecraft/server/level/ChunkMap.java b/net/minecraft/server/level/ChunkMap.java -index 72f019e3034d3268cf5526237ff0927eccc0c5bb..34c37abfe6c33ca1073450c8925f553d34be87a0 100644 +index d3d9926d504fa6b3384be5ae06b2843ebb7f807c..965899a98223b15bd770378c202873cbf15b714d 100644 --- a/net/minecraft/server/level/ChunkMap.java +++ b/net/minecraft/server/level/ChunkMap.java -@@ -262,9 +262,19 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - } - - final ServerPlayer[] backingSet = inRange.getRawDataUnchecked(); -- for (int i = 0, len = inRange.size(); i < len; i++) { -- ++(backingSet[i].mobCounts[index]); -+ // DivineMC start - Multithreaded tracker -+ if (org.bxteam.divinemc.DivineConfig.multithreadedEnabled) { -+ for (int i = 0, len = inRange.size(); i < len; i++) { -+ final ServerPlayer player = backingSet[i]; -+ if (player == null) continue; -+ ++(player.mobCounts[index]); -+ } -+ } else { -+ for (int i = 0, len = inRange.size(); i < len; i++) { -+ ++(backingSet[i].mobCounts[index]); -+ } - } -+ // DivineMC end - Multithreaded tracker - } - - // Paper start - per player mob count backoff -@@ -965,6 +975,13 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -951,6 +951,13 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider // Paper end - optimise entity tracker protected void tick() { @@ -61,7 +39,7 @@ index 72f019e3034d3268cf5526237ff0927eccc0c5bb..34c37abfe6c33ca1073450c8925f553d // Paper start - optimise entity tracker if (true) { this.newTrackerTick(); -@@ -1087,7 +1104,11 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1073,7 +1080,11 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider final Entity entity; private final int range; SectionPos lastSectionPos; @@ -74,7 +52,7 @@ index 72f019e3034d3268cf5526237ff0927eccc0c5bb..34c37abfe6c33ca1073450c8925f553d // Paper start - optimise entity tracker private long lastChunkUpdate = -1L; -@@ -1114,21 +1135,55 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1100,21 +1111,55 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider this.lastTrackedChunk = chunk; final ServerPlayer[] playersRaw = players.getRawDataUnchecked(); @@ -140,7 +118,7 @@ index 72f019e3034d3268cf5526237ff0927eccc0c5bb..34c37abfe6c33ca1073450c8925f553d } @Override -@@ -1190,7 +1245,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1176,7 +1221,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider } public void broadcast(Packet packet) { @@ -149,7 +127,7 @@ index 72f019e3034d3268cf5526237ff0927eccc0c5bb..34c37abfe6c33ca1073450c8925f553d serverPlayerConnection.send(packet); } } -@@ -1203,21 +1258,20 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1189,21 +1234,20 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider } public void broadcastRemoved() { @@ -230,10 +208,10 @@ index 0fb253aa55a24b56b17f524b3261c5b75c7d7e59..b6053158f5d9b6ad325ea075ab7c60f9 attributesToSync.clear(); diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java -index 9afd448ede87c9192dc576f66e08676a68b34d98..6e1ed33463c6280159d7f8187b9a9210d6b85e0c 100644 +index c229d69ce3d007e4cb57e559611b3fca7a03562f..ba5ca5213fafd60b2257409f334a7c6b28fe918a 100644 --- a/net/minecraft/server/level/ServerLevel.java +++ b/net/minecraft/server/level/ServerLevel.java -@@ -2471,7 +2471,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -2497,7 +2497,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe @Override public LevelEntityGetter getEntities() { @@ -242,8 +220,17 @@ index 9afd448ede87c9192dc576f66e08676a68b34d98..6e1ed33463c6280159d7f8187b9a9210 return this.moonrise$getEntityLookup(); // Paper - rewrite chunk system } +@@ -2724,7 +2724,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + } + + map.carriedByPlayers.remove(player); +- if (map.carriedBy.removeIf(holdingPlayer -> holdingPlayer.player == player)) { ++ if (map.carriedBy.removeIf(holdingPlayer -> holdingPlayer != null && holdingPlayer.player == player)) { // DivineMC - Multithreaded tracker + map.decorations.remove(player.getName().getString()); + } + } diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 420c9993df062466b85d60fe3fcc915e24d3da2a..19c7802969aa9d1e15b4c67ee5c97e73daf0a460 100644 +index 56a22d19a82a937c08cb4527b0f67f219a6bb8a0..f8c76bb2c9fa625e191036dc58ef3dfb1d4ee930 100644 --- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java @@ -1822,7 +1822,7 @@ public class ServerGamePacketListenerImpl @@ -293,3 +280,15 @@ index a25d74592e89e3d6339479c6dc2b6f45d1932cfc..621b183211b8148bb8db256d2119c82f private final AttributeSupplier supplier; private final net.minecraft.world.entity.LivingEntity entity; // Purpur - Ridables +diff --git a/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java b/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java +index 681dec447486138088fe5f705ef4fadab531139f..3d6aad86519be3e1449d3288369a41aebb924c90 100644 +--- a/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java ++++ b/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java +@@ -279,6 +279,7 @@ public class MapItemSavedData extends SavedData { + + for (int i = 0; i < this.carriedBy.size(); i++) { + MapItemSavedData.HoldingPlayer holdingPlayer1 = this.carriedBy.get(i); ++ if (holdingPlayer1 == null) continue; // DivineMC - Multithreaded tracker + Player player1 = holdingPlayer1.player; + String string = player1.getName().getString(); + if (!player1.isRemoved() && (player1.getInventory().contains(predicate) || mapStack.isFramed())) { diff --git a/divinemc-server/minecraft-patches/features/0006-Threaded-light-engine.patch b/divinemc-server/minecraft-patches/features/0006-Threaded-light-engine.patch deleted file mode 100644 index 612fc65..0000000 --- a/divinemc-server/minecraft-patches/features/0006-Threaded-light-engine.patch +++ /dev/null @@ -1,40 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> -Date: Tue, 28 Jan 2025 01:14:58 +0300 -Subject: [PATCH] Threaded light engine - - -diff --git a/net/minecraft/server/level/ChunkMap.java b/net/minecraft/server/level/ChunkMap.java -index d3d9926d504fa6b3384be5ae06b2843ebb7f807c..72f019e3034d3268cf5526237ff0927eccc0c5bb 100644 ---- a/net/minecraft/server/level/ChunkMap.java -+++ b/net/minecraft/server/level/ChunkMap.java -@@ -210,7 +210,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - ConsecutiveExecutor consecutiveExecutor = new ConsecutiveExecutor(dispatcher, "worldgen"); - this.progressListener = progressListener; - this.chunkStatusListener = chunkStatusListener; -- ConsecutiveExecutor consecutiveExecutor1 = new ConsecutiveExecutor(dispatcher, "light"); -+ ConsecutiveExecutor consecutiveExecutor1 = onLightExecutorInit(ConsecutiveExecutor::new); // DivineMC - Threaded light engine - // Paper - rewrite chunk system - this.lightEngine = new ThreadedLevelLightEngine( - lightChunk, this, this.level.dimensionType().hasSkyLight(), consecutiveExecutor1, null // Paper - rewrite chunk system -@@ -230,6 +230,20 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - this.worldGenContext = new WorldGenContext(level, generator, structureManager, this.lightEngine, null, this::setChunkUnsaved); // Paper - rewrite chunk system - } - -+ // DivineMC start - Threaded light engine -+ private java.util.concurrent.ExecutorService lightThread = null; -+ -+ private ConsecutiveExecutor onLightExecutorInit(java.util.function.BiFunction original) { -+ lightThread = new java.util.concurrent.ThreadPoolExecutor( -+ 1, 1, -+ 0, java.util.concurrent.TimeUnit.SECONDS, -+ new java.util.concurrent.LinkedBlockingQueue<>(), -+ new com.google.common.util.concurrent.ThreadFactoryBuilder().setPriority(Thread.NORM_PRIORITY - 1).setDaemon(true).setNameFormat(String.format("%s - Light", level.dimension().location().toDebugFileName())).build() -+ ); -+ return original.apply(lightThread, "light"); -+ } -+ // DivineMC end - Threaded light engine -+ - private void setChunkUnsaved(ChunkPos chunkPos) { - // Paper - rewrite chunk system - } diff --git a/divinemc-server/minecraft-patches/features/0008-Async-locate-command.patch b/divinemc-server/minecraft-patches/features/0007-Async-locate-command.patch similarity index 100% rename from divinemc-server/minecraft-patches/features/0008-Async-locate-command.patch rename to divinemc-server/minecraft-patches/features/0007-Async-locate-command.patch diff --git a/divinemc-server/minecraft-patches/features/0009-Parallel-world-ticking.patch b/divinemc-server/minecraft-patches/features/0008-Parallel-world-ticking.patch similarity index 65% rename from divinemc-server/minecraft-patches/features/0009-Parallel-world-ticking.patch rename to divinemc-server/minecraft-patches/features/0008-Parallel-world-ticking.patch index 893b3e8..7d2a317 100644 --- a/divinemc-server/minecraft-patches/features/0009-Parallel-world-ticking.patch +++ b/divinemc-server/minecraft-patches/features/0008-Parallel-world-ticking.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Parallel world ticking diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java -index b5817aa8f537593f6d9fc6b612c82ccccb250ac7..0c99bffa769d53562a10d23c4a9b37dc59c7f478 100644 +index b5817aa8f537593f6d9fc6b612c82ccccb250ac7..5683395e09a65b1b39748df5152fffef630ac083 100644 --- a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java +++ b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java @@ -1031,7 +1031,7 @@ public final class ChunkHolderManager { @@ -13,30 +13,36 @@ index b5817aa8f537593f6d9fc6b612c82ccccb250ac7..0c99bffa769d53562a10d23c4a9b37dc return; } - if (!TickThread.isTickThread()) { -+ if (!TickThread.isTickThreadFor(world)) { // DivineMC - parallel world ticking ++ if (org.bxteam.divinemc.DivineConfig.enableParallelWorldTicking && !TickThread.isTickThreadFor(world)) { // DivineMC - Parallel world ticking this.taskScheduler.scheduleChunkTask(() -> { final ArrayDeque pendingFullLoadUpdate = ChunkHolderManager.this.pendingFullLoadUpdate; for (int i = 0, len = changedFullStatus.size(); i < len; ++i) { -@@ -1057,7 +1057,7 @@ public final class ChunkHolderManager { +@@ -1057,7 +1057,13 @@ public final class ChunkHolderManager { // note: never call while inside the chunk system, this will absolutely break everything public void processUnloads() { - TickThread.ensureTickThread("Cannot unload chunks off-main"); -+ TickThread.ensureTickThread(world, "Cannot unload chunks off-main"); // DivineMC - parallel world ticking ++ // DivineMC start - Parallel world ticking ++ if (org.bxteam.divinemc.DivineConfig.enableParallelWorldTicking) { ++ TickThread.ensureTickThread(world, "Cannot unload chunks off-main"); ++ } else { ++ TickThread.ensureTickThread("Cannot unload chunks off-main"); ++ } ++ // DivineMC end - Parallel world ticking if (BLOCK_TICKET_UPDATES.get() == Boolean.TRUE) { throw new IllegalStateException("Cannot unload chunks recursively"); -@@ -1339,7 +1339,7 @@ public final class ChunkHolderManager { +@@ -1339,7 +1345,7 @@ public final class ChunkHolderManager { List changedFullStatus = null; - final boolean isTickThread = TickThread.isTickThread(); -+ final boolean isTickThread = TickThread.isTickThreadFor(world); // DivineMC - parallel world ticking ++ final boolean isTickThread = org.bxteam.divinemc.DivineConfig.enableParallelWorldTicking && TickThread.isTickThreadFor(world) || TickThread.isTickThread(); // DivineMC - Parallel world ticking boolean ret = false; final boolean canProcessFullUpdates = processFullUpdates & isTickThread; diff --git a/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java b/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java -index 4a881636ba21fae9e50950bbba2b4321b71d35ab..d019e6cd603c918b576b950a3c678862b2248c93 100644 +index 4a881636ba21fae9e50950bbba2b4321b71d35ab..ebcd52da2228893f7a2d96f61e71906f8d2783ba 100644 --- a/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java +++ b/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java @@ -46,7 +46,7 @@ public class BoatDispenseItemBehavior extends DefaultDispenseItemBehavior { @@ -44,12 +50,12 @@ index 4a881636ba21fae9e50950bbba2b4321b71d35ab..d019e6cd603c918b576b950a3c678862 org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(d1, d2 + d4, d3)); - if (!DispenserBlock.eventFired) { -+ if (!DispenserBlock.eventFired.get()) { // DivineMC - parallel world ticking ++ if (!DispenserBlock.getEventFiredTL()) { // DivineMC - Parallel world ticking serverLevel.getCraftServer().getPluginManager().callEvent(event); } diff --git a/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java b/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java -index bd5bbc7e55c6bea77991fe5a3c0c2580313d16c5..10ab4be4b8d7e488148bab395e344fca0d09fbb0 100644 +index bd5bbc7e55c6bea77991fe5a3c0c2580313d16c5..b822dd53138c9cde3e7248afb7589ca32a1b178a 100644 --- a/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java +++ b/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java @@ -78,7 +78,7 @@ public class DefaultDispenseItemBehavior implements DispenseItemBehavior { @@ -57,12 +63,12 @@ index bd5bbc7e55c6bea77991fe5a3c0c2580313d16c5..10ab4be4b8d7e488148bab395e344fca org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(block, craftItem.clone(), org.bukkit.craftbukkit.util.CraftVector.toBukkit(itemEntity.getDeltaMovement())); - if (!DispenserBlock.eventFired) { -+ if (!DispenserBlock.eventFired.get()) { // DivineMC - parallel world ticking ++ if (!DispenserBlock.getEventFiredTL()) { // DivineMC - Parallel world ticking level.getCraftServer().getPluginManager().callEvent(event); } diff --git a/net/minecraft/core/dispenser/DispenseItemBehavior.java b/net/minecraft/core/dispenser/DispenseItemBehavior.java -index f576449e8bc6fd92963cbe3954b0c853a02def3c..c8c8351f5645cf4041d26b0e02c072546ad329c6 100644 +index f576449e8bc6fd92963cbe3954b0c853a02def3c..075987ec1eabb7385918049c54d7210ff2b38847 100644 --- a/net/minecraft/core/dispenser/DispenseItemBehavior.java +++ b/net/minecraft/core/dispenser/DispenseItemBehavior.java @@ -89,7 +89,7 @@ public interface DispenseItemBehavior { @@ -70,7 +76,7 @@ index f576449e8bc6fd92963cbe3954b0c853a02def3c..c8c8351f5645cf4041d26b0e02c07254 org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(0, 0, 0)); - if (!DispenserBlock.eventFired) { -+ if (!DispenserBlock.eventFired.get()) { // DivineMC - parallel world ticking ++ if (!DispenserBlock.getEventFiredTL()) { // DivineMC - Parallel world ticking serverLevel.getCraftServer().getPluginManager().callEvent(event); } @@ -79,7 +85,7 @@ index f576449e8bc6fd92963cbe3954b0c853a02def3c..c8c8351f5645cf4041d26b0e02c07254 org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(0, 0, 0)); - if (!DispenserBlock.eventFired) { -+ if (!DispenserBlock.eventFired.get()) { // DivineMC - parallel world ticking ++ if (!DispenserBlock.getEventFiredTL()) { // DivineMC - Parallel world ticking serverLevel.getCraftServer().getPluginManager().callEvent(event); } @@ -88,7 +94,7 @@ index f576449e8bc6fd92963cbe3954b0c853a02def3c..c8c8351f5645cf4041d26b0e02c07254 org.bukkit.event.block.BlockDispenseArmorEvent event = new org.bukkit.event.block.BlockDispenseArmorEvent(block, craftItem.clone(), entitiesOfClass.get(0).getBukkitLivingEntity()); - if (!DispenserBlock.eventFired) { -+ if (!DispenserBlock.eventFired.get()) { // DivineMC - parallel world ticking ++ if (!DispenserBlock.getEventFiredTL()) { // DivineMC - Parallel world ticking world.getCraftServer().getPluginManager().callEvent(event); } @@ -97,7 +103,7 @@ index f576449e8bc6fd92963cbe3954b0c853a02def3c..c8c8351f5645cf4041d26b0e02c07254 org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(singleCopy); org.bukkit.event.block.BlockDispenseArmorEvent event = new org.bukkit.event.block.BlockDispenseArmorEvent(block, craftItem.clone(), abstractChestedHorse.getBukkitLivingEntity()); - if (!DispenserBlock.eventFired) { -+ if (!DispenserBlock.eventFired.get()) { // DivineMC - parallel world ticking ++ if (!DispenserBlock.getEventFiredTL()) { // DivineMC - Parallel world ticking world.getCraftServer().getPluginManager().callEvent(event); } @@ -106,7 +112,7 @@ index f576449e8bc6fd92963cbe3954b0c853a02def3c..c8c8351f5645cf4041d26b0e02c07254 org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(x, y, z)); - if (!DispenserBlock.eventFired) { -+ if (!DispenserBlock.eventFired.get()) { // DivineMC - parallel world ticking ++ if (!DispenserBlock.getEventFiredTL()) { // DivineMC - Parallel world ticking level.getCraftServer().getPluginManager().callEvent(event); } @@ -115,7 +121,7 @@ index f576449e8bc6fd92963cbe3954b0c853a02def3c..c8c8351f5645cf4041d26b0e02c07254 org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(bukkitBlock, craftItem.clone(), org.bukkit.craftbukkit.util.CraftVector.toBukkit(blockPos)); - if (!DispenserBlock.eventFired) { -+ if (!DispenserBlock.eventFired.get()) { // DivineMC - parallel world ticking ++ if (!DispenserBlock.getEventFiredTL()) { // DivineMC - Parallel world ticking levelAccessor.getMinecraftWorld().getCraftServer().getPluginManager().callEvent(event); } @@ -124,7 +130,7 @@ index f576449e8bc6fd92963cbe3954b0c853a02def3c..c8c8351f5645cf4041d26b0e02c07254 org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(bukkitBlock, craftItem.clone(), new org.bukkit.util.Vector(0, 0, 0)); - if (!DispenserBlock.eventFired) { -+ if (!DispenserBlock.eventFired.get()) { // DivineMC - parallel world ticking ++ if (!DispenserBlock.getEventFiredTL()) { // DivineMC - Parallel world ticking serverLevel.getCraftServer().getPluginManager().callEvent(event); } @@ -133,7 +139,7 @@ index f576449e8bc6fd92963cbe3954b0c853a02def3c..c8c8351f5645cf4041d26b0e02c07254 org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(0, 0, 0)); - if (!DispenserBlock.eventFired) { -+ if (!DispenserBlock.eventFired.get()) { // DivineMC - parallel world ticking ++ if (!DispenserBlock.getEventFiredTL()) { // DivineMC - Parallel world ticking level.getCraftServer().getPluginManager().callEvent(event); } @@ -143,10 +149,10 @@ index f576449e8bc6fd92963cbe3954b0c853a02def3c..c8c8351f5645cf4041d26b0e02c07254 if (level.capturedBlockStates.size() > 0) { - org.bukkit.TreeType treeType = net.minecraft.world.level.block.SaplingBlock.treeType; - net.minecraft.world.level.block.SaplingBlock.treeType = null; -+ // DivineMC start - parallel world ticking -+ org.bukkit.TreeType treeType = net.minecraft.world.level.block.SaplingBlock.treeTypeRT.get(); -+ net.minecraft.world.level.block.SaplingBlock.treeTypeRT.set(null); -+ // DivineMC end - parallel world ticking ++ // DivineMC start - Parallel world ticking ++ org.bukkit.TreeType treeType = net.minecraft.world.level.block.SaplingBlock.getTreeTypeTL(); ++ net.minecraft.world.level.block.SaplingBlock.setTreeTypeTL(null); ++ // DivineMC end - Parallel world ticking org.bukkit.Location location = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(blockPos, level.getWorld()); List blocks = new java.util.ArrayList<>(level.capturedBlockStates.values()); level.capturedBlockStates.clear(); @@ -155,7 +161,7 @@ index f576449e8bc6fd92963cbe3954b0c853a02def3c..c8c8351f5645cf4041d26b0e02c07254 org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector((double) blockPos.getX() + 0.5D, (double) blockPos.getY(), (double) blockPos.getZ() + 0.5D)); - if (!DispenserBlock.eventFired) { -+ if (!DispenserBlock.eventFired.get()) { // DivineMC - parallel world ticking ++ if (!DispenserBlock.getEventFiredTL()) { // DivineMC - Parallel world ticking level.getCraftServer().getPluginManager().callEvent(event); } @@ -164,7 +170,7 @@ index f576449e8bc6fd92963cbe3954b0c853a02def3c..c8c8351f5645cf4041d26b0e02c07254 org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(bukkitBlock, craftItem.clone(), org.bukkit.craftbukkit.util.CraftVector.toBukkit(blockPos)); - if (!DispenserBlock.eventFired) { -+ if (!DispenserBlock.eventFired.get()) { // DivineMC - parallel world ticking ++ if (!DispenserBlock.getEventFiredTL()) { // DivineMC - Parallel world ticking level.getCraftServer().getPluginManager().callEvent(event); } @@ -173,7 +179,7 @@ index f576449e8bc6fd92963cbe3954b0c853a02def3c..c8c8351f5645cf4041d26b0e02c07254 org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(bukkitBlock, craftItem.clone(), org.bukkit.craftbukkit.util.CraftVector.toBukkit(blockPos)); - if (!DispenserBlock.eventFired) { -+ if (!DispenserBlock.eventFired.get()) { // DivineMC - parallel world ticking ++ if (!DispenserBlock.getEventFiredTL()) { // DivineMC - Parallel world ticking level.getCraftServer().getPluginManager().callEvent(event); } @@ -182,7 +188,7 @@ index f576449e8bc6fd92963cbe3954b0c853a02def3c..c8c8351f5645cf4041d26b0e02c07254 org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(bukkitBlock, craftItem.clone(), org.bukkit.craftbukkit.util.CraftVector.toBukkit(blockPos)); - if (!DispenserBlock.eventFired) { -+ if (!DispenserBlock.eventFired.get()) { // DivineMC - parallel world ticking ++ if (!DispenserBlock.getEventFiredTL()) { // DivineMC - Parallel world ticking serverLevel.getCraftServer().getPluginManager().callEvent(event); } @@ -191,12 +197,12 @@ index f576449e8bc6fd92963cbe3954b0c853a02def3c..c8c8351f5645cf4041d26b0e02c07254 org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseArmorEvent(block, craftItem.clone(), entitiesOfClass.get(0).getBukkitLivingEntity()); - if (!DispenserBlock.eventFired) { -+ if (!DispenserBlock.eventFired.get()) { // DivineMC - parallel world ticking ++ if (!DispenserBlock.getEventFiredTL()) { // DivineMC - Parallel world ticking serverLevel.getCraftServer().getPluginManager().callEvent(event); } diff --git a/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java b/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java -index b91b2f5ea6a1da0477541dc65fdfbfa57b9af475..e2b7ee10569812c94a5ff6d6e731941f24527c55 100644 +index b91b2f5ea6a1da0477541dc65fdfbfa57b9af475..136c52cab0097fb68e8d12f653f1dab934de9bde 100644 --- a/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java +++ b/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java @@ -39,7 +39,7 @@ public class EquipmentDispenseItemBehavior extends DefaultDispenseItemBehavior { @@ -204,12 +210,12 @@ index b91b2f5ea6a1da0477541dc65fdfbfa57b9af475..e2b7ee10569812c94a5ff6d6e731941f org.bukkit.event.block.BlockDispenseArmorEvent event = new org.bukkit.event.block.BlockDispenseArmorEvent(block, craftItem.clone(), (org.bukkit.craftbukkit.entity.CraftLivingEntity) livingEntity.getBukkitEntity()); - if (!DispenserBlock.eventFired) { -+ if (!DispenserBlock.eventFired.get()) { // DivineMC - parallel world ticking ++ if (!DispenserBlock.getEventFiredTL()) { // DivineMC - Parallel world ticking world.getCraftServer().getPluginManager().callEvent(event); } diff --git a/net/minecraft/core/dispenser/MinecartDispenseItemBehavior.java b/net/minecraft/core/dispenser/MinecartDispenseItemBehavior.java -index 116395b6c00a0814922516707544a9ff26d68835..37f710bfa8a9388351d1d32f6f2eeac7c8bfd4bd 100644 +index 116395b6c00a0814922516707544a9ff26d68835..11d4da87aa80aa338d5f3048285079777bae7d3d 100644 --- a/net/minecraft/core/dispenser/MinecartDispenseItemBehavior.java +++ b/net/minecraft/core/dispenser/MinecartDispenseItemBehavior.java @@ -62,7 +62,7 @@ public class MinecartDispenseItemBehavior extends DefaultDispenseItemBehavior { @@ -217,12 +223,12 @@ index 116395b6c00a0814922516707544a9ff26d68835..37f710bfa8a9388351d1d32f6f2eeac7 org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(block2, craftItem.clone(), new org.bukkit.util.Vector(vec31.x, vec31.y, vec31.z)); - if (!DispenserBlock.eventFired) { -+ if (!DispenserBlock.eventFired.get()) { // DivineMC - parallel world ticking ++ if (!DispenserBlock.getEventFiredTL()) { // DivineMC - Parallel world ticking serverLevel.getCraftServer().getPluginManager().callEvent(event); } diff --git a/net/minecraft/core/dispenser/ProjectileDispenseBehavior.java b/net/minecraft/core/dispenser/ProjectileDispenseBehavior.java -index 449d9b72ff4650961daa9d1bd25940f3914a6b12..097528f85e5c0393c8b20a68aede99b4b949cb24 100644 +index 449d9b72ff4650961daa9d1bd25940f3914a6b12..ce12d4729d100be7dffaf4bdfc1c999d68c07bac 100644 --- a/net/minecraft/core/dispenser/ProjectileDispenseBehavior.java +++ b/net/minecraft/core/dispenser/ProjectileDispenseBehavior.java @@ -32,7 +32,7 @@ public class ProjectileDispenseBehavior extends DefaultDispenseItemBehavior { @@ -230,12 +236,12 @@ index 449d9b72ff4650961daa9d1bd25940f3914a6b12..097528f85e5c0393c8b20a68aede99b4 org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector((double) direction.getStepX(), (double) direction.getStepY(), (double) direction.getStepZ())); - if (!DispenserBlock.eventFired) { -+ if (!DispenserBlock.eventFired.get()) { // DivineMC - parallel world ticking ++ if (!DispenserBlock.getEventFiredTL()) { // DivineMC - Parallel world ticking serverLevel.getCraftServer().getPluginManager().callEvent(event); } diff --git a/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java b/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java -index 626e9feb6a6e7a2cbc7c63e30ba4fb6b923e85c7..6fad185f34c4614f16012ec008add241f188d462 100644 +index 626e9feb6a6e7a2cbc7c63e30ba4fb6b923e85c7..81b37cf681ff0220263628b390910d9597662a3a 100644 --- a/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java +++ b/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java @@ -25,7 +25,7 @@ public class ShearsDispenseItemBehavior extends OptionalDispenseItemBehavior { @@ -243,12 +249,12 @@ index 626e9feb6a6e7a2cbc7c63e30ba4fb6b923e85c7..6fad185f34c4614f16012ec008add241 org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(item); // Paper - ignore stack size on damageable items org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(bukkitBlock, craftItem.clone(), new org.bukkit.util.Vector(0, 0, 0)); - if (!DispenserBlock.eventFired) { -+ if (!DispenserBlock.eventFired.get()) { // DivineMC - parallel world ticking ++ if (!DispenserBlock.getEventFiredTL()) { // DivineMC - Parallel world ticking serverLevel.getCraftServer().getPluginManager().callEvent(event); } diff --git a/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java b/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java -index 5ab2c8333178335515e619b87ae420f948c83bd1..9d2bc41befd0f73b6a0f097d45fbe771e13be86f 100644 +index 5ab2c8333178335515e619b87ae420f948c83bd1..e3034132c9119956e15a6a0342173d70e44ad669 100644 --- a/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java +++ b/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java @@ -27,7 +27,7 @@ public class ShulkerBoxDispenseBehavior extends OptionalDispenseItemBehavior { @@ -256,19 +262,19 @@ index 5ab2c8333178335515e619b87ae420f948c83bd1..9d2bc41befd0f73b6a0f097d45fbe771 org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(bukkitBlock, craftItem.clone(), new org.bukkit.util.Vector(blockPos.getX(), blockPos.getY(), blockPos.getZ())); - if (!DispenserBlock.eventFired) { -+ if (!DispenserBlock.eventFired.get()) { // DivineMC - parallel world ticking ++ if (!DispenserBlock.getEventFiredTL()) { // DivineMC - Parallel world ticking blockSource.level().getCraftServer().getPluginManager().callEvent(event); } diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java -index 4cb26cb312802ffc00426293014ed3994793a391..9f7698f8ce56d5d89cf86f6ea2d5b4d51b18c9a2 100644 +index 4cb26cb312802ffc00426293014ed3994793a391..5a726da8535aa939f043829a3c60fdd9d4ed154a 100644 --- a/net/minecraft/server/MinecraftServer.java +++ b/net/minecraft/server/MinecraftServer.java @@ -288,6 +288,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop S spin(Function threadFunction) { ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.init(); // Paper - rewrite data converter system @@ -407,29 +413,29 @@ index 4cb26cb312802ffc00426293014ed3994793a391..9f7698f8ce56d5d89cf86f6ea2d5b4d5 Map, ServerLevel> oldLevels = this.levels; Map, ServerLevel> newLevels = Maps.newLinkedHashMap(oldLevels); newLevels.remove(level.dimension()); -+ level.tickExecutor.shutdown(); // DivineMC - parallel world ticking ++ if (org.bxteam.divinemc.DivineConfig.enableParallelWorldTicking) level.tickExecutor.shutdown(); // DivineMC - Parallel world ticking this.levels = Collections.unmodifiableMap(newLevels); } // CraftBukkit end diff --git a/net/minecraft/server/dedicated/DedicatedServer.java b/net/minecraft/server/dedicated/DedicatedServer.java -index 0d889730641fd88d4da0c9116226a4dae385e846..9fcdff2be139296f4e14b54c33cc795efdff0c7f 100644 +index 0d889730641fd88d4da0c9116226a4dae385e846..f7a061ad623fa909389c60c1d5b4be840f99e2bf 100644 --- a/net/minecraft/server/dedicated/DedicatedServer.java +++ b/net/minecraft/server/dedicated/DedicatedServer.java @@ -243,6 +243,12 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface io.papermc.paper.command.PaperCommands.registerCommands(this); // Paper - setup /paper command this.server.spark.registerCommandBeforePlugins(this.server); // Paper - spark com.destroystokyo.paper.Metrics.PaperMetrics.startMetrics(); // Paper - start metrics -+ // DivineMC start - parallel world ticking ++ // DivineMC start - Parallel world ticking + if (org.bxteam.divinemc.DivineConfig.enableParallelWorldTicking) { + serverLevelTickingSemaphore = new java.util.concurrent.Semaphore(org.bxteam.divinemc.DivineConfig.parallelThreadCount); -+ DedicatedServer.LOGGER.info("Using {} permits for parallel world ticking", serverLevelTickingSemaphore.availablePermits()); ++ DedicatedServer.LOGGER.info("Using {} permits for Parallel world ticking", serverLevelTickingSemaphore.availablePermits()); + } -+ // DivineMC end - parallel world ticking ++ // DivineMC end - Parallel world ticking /*// Purpur start - Purpur config files // Purpur - Configurable void damage height and damage try { org.purpurmc.purpur.PurpurConfig.init((java.io.File) options.valueOf("purpur-settings")); diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java -index 6e1ed33463c6280159d7f8187b9a9210d6b85e0c..973198ae0a73d6747e73548bdcbc1de46b6fb107 100644 +index ba5ca5213fafd60b2257409f334a7c6b28fe918a..52ba052fc1ff2a35786570c282a7de4e9dff99f5 100644 --- a/net/minecraft/server/level/ServerLevel.java +++ b/net/minecraft/server/level/ServerLevel.java @@ -182,7 +182,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe @@ -437,7 +443,7 @@ index 6e1ed33463c6280159d7f8187b9a9210d6b85e0c..973198ae0a73d6747e73548bdcbc1de4 public final net.minecraft.world.level.storage.PrimaryLevelData serverLevelData; // CraftBukkit - type private int lastSpawnChunkRadius; - final EntityTickList entityTickList = new EntityTickList(); -+ final EntityTickList entityTickList = new EntityTickList(this); // DivineMC - parallel world ticking ++ final EntityTickList entityTickList = new EntityTickList(this); // DivineMC - Parallel world ticking // Paper - rewrite chunk system private final GameEventDispatcher gameEventDispatcher; public boolean noSave; @@ -445,7 +451,7 @@ index 6e1ed33463c6280159d7f8187b9a9210d6b85e0c..973198ae0a73d6747e73548bdcbc1de4 private double preciseTime; // Purpur - Configurable daylight cycle private boolean forceTime; // Purpur - Configurable daylight cycle private final RandomSequences randomSequences; -+ public java.util.concurrent.ExecutorService tickExecutor; // DivineMC - parallel world ticking ++ public java.util.concurrent.ExecutorService tickExecutor; // DivineMC - Parallel world ticking // CraftBukkit start public final LevelStorageSource.LevelStorageAccess levelStorageAccess; @@ -453,20 +459,21 @@ index 6e1ed33463c6280159d7f8187b9a9210d6b85e0c..973198ae0a73d6747e73548bdcbc1de4 this.chunkDataController = new ca.spottedleaf.moonrise.patches.chunk_system.io.datacontroller.ChunkDataController((ServerLevel)(Object)this, this.chunkTaskScheduler); // Paper end - rewrite chunk system 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.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 } -@@ -1255,12 +1257,12 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -1281,12 +1283,12 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe if (fluidState.is(fluid)) { fluidState.tick(this, pos, blockState); } - // Paper start - rewrite chunk system - if ((++this.tickedBlocksOrFluids & 7L) != 0L) { +- ((ca.spottedleaf.moonrise.patches.chunk_system.server.ChunkSystemMinecraftServer)this.server).moonrise$executeMidTickTasks(); + // DivineMC start - Parallel world ticking + ++this.tickedBlocksOrFluids; + if (!org.bxteam.divinemc.DivineConfig.enableParallelWorldTicking && (this.tickedBlocksOrFluids & 7L) != 0L) { - ((ca.spottedleaf.moonrise.patches.chunk_system.server.ChunkSystemMinecraftServer)this.server).moonrise$executeMidTickTasks(); ++ this.server.moonrise$executeMidTickTasks(); } - // Paper end - rewrite chunk system - @@ -474,16 +481,17 @@ index 6e1ed33463c6280159d7f8187b9a9210d6b85e0c..973198ae0a73d6747e73548bdcbc1de4 } private void tickBlock(BlockPos pos, Block block) { -@@ -1268,12 +1270,12 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -1294,12 +1296,12 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe if (blockState.is(block)) { blockState.tick(this, pos, this.random); } - // Paper start - rewrite chunk system - if ((++this.tickedBlocksOrFluids & 7L) != 0L) { +- ((ca.spottedleaf.moonrise.patches.chunk_system.server.ChunkSystemMinecraftServer)this.server).moonrise$executeMidTickTasks(); + // DivineMC start - Parallel world ticking + ++this.tickedBlocksOrFluids; + if (!org.bxteam.divinemc.DivineConfig.enableParallelWorldTicking && (this.tickedBlocksOrFluids & 7L) != 0L) { - ((ca.spottedleaf.moonrise.patches.chunk_system.server.ChunkSystemMinecraftServer)this.server).moonrise$executeMidTickTasks(); ++ this.server.moonrise$executeMidTickTasks(); } - // Paper end - rewrite chunk system - @@ -491,32 +499,38 @@ index 6e1ed33463c6280159d7f8187b9a9210d6b85e0c..973198ae0a73d6747e73548bdcbc1de4 } // Paper start - log detailed entity tick information -@@ -1522,6 +1524,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -1548,6 +1550,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe } private void addPlayer(ServerPlayer player) { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this, "Cannot add player off-main"); // DivineMC - parallel world ticking (additional concurrency issues logs) ++ if (org.bxteam.divinemc.DivineConfig.enableParallelWorldTicking) ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this, "Cannot add player off-main"); // DivineMC - Parallel world ticking (additional concurrency issues logs) Entity entity = this.getEntities().get(player.getUUID()); if (entity != null) { LOGGER.warn("Force-added player with duplicate UUID {}", player.getUUID()); -@@ -1534,7 +1537,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -1560,7 +1563,13 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe // CraftBukkit start private boolean addEntity(Entity entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason) { - org.spigotmc.AsyncCatcher.catchOp("entity add"); // Spigot -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this, "Cannot add entity off-main"); // DivineMC - parallel world ticking (additional concurrency issues logs) ++ // DivineMC start - Parallel world ticking ++ if (org.bxteam.divinemc.DivineConfig.enableParallelWorldTicking) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this, "Cannot add entity off-main"); ++ } else { ++ org.spigotmc.AsyncCatcher.catchOp("entity add"); // Spigot ++ } ++ // DivineMC end - Parallel world ticking entity.generation = false; // Paper - Don't fire sync event during generation; Reset flag if it was added during a ServerLevel generation process // Paper start - extra debug info if (entity.valid) { diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java -index 21d41b477cc0e8d2476d1e3141bdf23d0c06f3e0..307cfbc754aa933d7bec51ac8b1b8ee46867fb41 100644 +index 21d41b477cc0e8d2476d1e3141bdf23d0c06f3e0..195d9dc696a9a7da0bb041612b73c0bbacf2919e 100644 --- a/net/minecraft/server/level/ServerPlayer.java +++ b/net/minecraft/server/level/ServerPlayer.java @@ -432,6 +432,7 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc return this.viewDistanceHolder; } // Paper end - rewrite chunk system -+ public boolean hasTickedAtLeastOnceInNewWorld = false; // DivineMC - parallel world ticking ++ public boolean hasTickedAtLeastOnceInNewWorld = false; // DivineMC - Parallel world ticking public ServerPlayer(MinecraftServer server, ServerLevel level, GameProfile gameProfile, ClientInformation clientInformation) { super(level, level.getSharedSpawnPos(), level.getSharedSpawnAngle(), gameProfile); @@ -524,7 +538,7 @@ index 21d41b477cc0e8d2476d1e3141bdf23d0c06f3e0..307cfbc754aa933d7bec51ac8b1b8ee4 @Override public void tick() { -+ hasTickedAtLeastOnceInNewWorld = true; // DivineMC - parallel world ticking ++ hasTickedAtLeastOnceInNewWorld = true; // DivineMC - Parallel world ticking // CraftBukkit start if (this.joining) { this.joining = false; @@ -532,7 +546,7 @@ index 21d41b477cc0e8d2476d1e3141bdf23d0c06f3e0..307cfbc754aa933d7bec51ac8b1b8ee4 return this; } else { // CraftBukkit start -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureOnlyTickThread("Cannot change dimension of a player off-main, from world " + serverLevel().getWorld().getName() + " to world " + level.getWorld().getName()); // DivineMC - parallel world ticking (additional concurrency issues logs) ++ if (org.bxteam.divinemc.DivineConfig.enableParallelWorldTicking) ca.spottedleaf.moonrise.common.util.TickThread.ensureOnlyTickThread("Cannot change dimension of a player off-main, from world " + serverLevel().getWorld().getName() + " to world " + level.getWorld().getName()); // DivineMC - Parallel world ticking (additional concurrency issues logs) /* this.isChangingDimension = true; LevelData levelData = level.getLevelData(); @@ -540,12 +554,12 @@ index 21d41b477cc0e8d2476d1e3141bdf23d0c06f3e0..307cfbc754aa933d7bec51ac8b1b8ee4 return OptionalInt.empty(); } else { // CraftBukkit start -+ // DivineMC start - parallel world ticking ++ // DivineMC start - Parallel world ticking + if (org.bxteam.divinemc.DivineConfig.enableParallelWorldTicking && !hasTickedAtLeastOnceInNewWorld) { + MinecraftServer.LOGGER.warn("Ignoring request to open container {} because we haven't ticked in the current world yet!", abstractContainerMenu, new Throwable()); + return OptionalInt.empty(); + } -+ // DivineMC end - parallel world ticking ++ // DivineMC end - Parallel world ticking this.containerMenu = abstractContainerMenu; // Moved up if (!this.isImmobile()) this.connection @@ -553,23 +567,23 @@ index 21d41b477cc0e8d2476d1e3141bdf23d0c06f3e0..307cfbc754aa933d7bec51ac8b1b8ee4 } @Override public void closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) { -+ // DivineMC start - parallel world ticking (debugging) ++ // DivineMC start - Parallel world ticking (debugging) + if (org.bxteam.divinemc.DivineConfig.logContainerCreationStacktraces) { + MinecraftServer.LOGGER.warn("Closing {} inventory that was created at", this.getBukkitEntity().getName(), this.containerMenu.containerCreationStacktrace); + } -+ // DivineMC end - parallel world ticking (debugging) ++ // DivineMC end - Parallel world ticking (debugging) org.bukkit.craftbukkit.event.CraftEventFactory.handleInventoryCloseEvent(this, reason); // CraftBukkit // Paper end - Inventory close reason this.connection.send(new ClientboundContainerClosePacket(this.containerMenu.containerId)); diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java -index 6f66c5a93383b2897425e117c32add9a34054add..3d8c1c5aa922ec0417388a1ff27d423cdb6d1351 100644 +index 6f66c5a93383b2897425e117c32add9a34054add..5ff0ce34cfacb745748d3dc627127842ac1df9fa 100644 --- a/net/minecraft/server/players/PlayerList.java +++ b/net/minecraft/server/players/PlayerList.java @@ -150,6 +150,7 @@ public abstract class PlayerList { abstract public void loadAndSaveFiles(); // Paper - fix converting txt to json file; moved from DedicatedPlayerList constructor public void placeNewPlayer(Connection connection, ServerPlayer player, CommonListenerCookie cookie) { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureOnlyTickThread("Cannot place new player off-main"); // DivineMC - parallel world ticking ++ if (org.bxteam.divinemc.DivineConfig.enableParallelWorldTicking) ca.spottedleaf.moonrise.common.util.TickThread.ensureOnlyTickThread("Cannot place new player off-main"); // DivineMC - Parallel world ticking player.isRealPlayer = true; // Paper player.loginTime = System.currentTimeMillis(); // Paper - Replace OfflinePlayer#getLastPlayed GameProfile gameProfile = player.getGameProfile(); @@ -577,14 +591,14 @@ index 6f66c5a93383b2897425e117c32add9a34054add..3d8c1c5aa922ec0417388a1ff27d423c return this.respawn(player, keepInventory, reason, eventReason, null); } public ServerPlayer respawn(ServerPlayer player, boolean keepInventory, Entity.RemovalReason reason, org.bukkit.event.player.PlayerRespawnEvent.RespawnReason eventReason, org.bukkit.Location location) { -+ // DivineMC start - parallel world ticking (additional concurrency issues logs) ++ // DivineMC start - Parallel world ticking (additional concurrency issues logs) + if (org.bxteam.divinemc.DivineConfig.enableParallelWorldTicking) { + if (location != null) + ca.spottedleaf.moonrise.common.util.TickThread.ensureOnlyTickThread("Cannot respawn player off-main, from world " + player.serverLevel().getWorld().getName() + " to world " + location.getWorld().getName()); + else + ca.spottedleaf.moonrise.common.util.TickThread.ensureOnlyTickThread("Cannot respawn player off-main, respawning in world " + player.serverLevel().getWorld().getName()); + } -+ // DivineMC end - parallel world ticking (additional concurrency issues logs) ++ // DivineMC end - Parallel world ticking (additional concurrency issues logs) this.players.remove(player); this.playersByName.remove(player.getScoreboardName().toLowerCase(java.util.Locale.ROOT)); // Spigot player.serverLevel().removePlayerImmediately(player, reason); @@ -592,74 +606,320 @@ index 6f66c5a93383b2897425e117c32add9a34054add..3d8c1c5aa922ec0417388a1ff27d423c ServerPlayer serverPlayer = player; Level fromWorld = player.level(); player.wonGame = false; -+ serverPlayer.hasTickedAtLeastOnceInNewWorld = false; // DivineMC - parallel world ticking ++ serverPlayer.hasTickedAtLeastOnceInNewWorld = false; // DivineMC - Parallel world ticking // CraftBukkit end serverPlayer.connection = player.connection; serverPlayer.restoreFrom(player, keepInventory); diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java -index 4c4bed85db28d92ca55593a19ac23616619e4bd1..258cb45f1f959b75c1bcdb130811af2f8fddf07d 100644 +index 4c4bed85db28d92ca55593a19ac23616619e4bd1..7e29f1f1972f2fb4f9a779653467bc85ce37c7bc 100644 --- a/net/minecraft/world/entity/Entity.java +++ b/net/minecraft/world/entity/Entity.java -@@ -848,7 +848,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - // CraftBukkit start - public void postTick() { - // No clean way to break out of ticking once the entity has been copied to a new world, so instead we move the portalling later in the tick cycle -- if (!(this instanceof ServerPlayer) && this.isAlive()) { // Paper - don't attempt to teleport dead entities -+ if (false && !(this instanceof ServerPlayer) && this.isAlive()) { // Paper - don't attempt to teleport dead entities // DivineMC - parallel world ticking - this.handlePortal(); - } - } -@@ -3851,6 +3851,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess +@@ -3314,14 +3314,34 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + if (this.portalProcess != null) { + if (this.portalProcess.processPortalTeleportation(serverLevel, this, this.canUsePortal(false))) { + this.setPortalCooldown(); +- TeleportTransition portalDestination = this.portalProcess.getPortalDestination(serverLevel, this); +- if (portalDestination != null) { +- ServerLevel level = portalDestination.newLevel(); +- if (this instanceof ServerPlayer // CraftBukkit - always call event for players +- || (level != null && (level.dimension() == serverLevel.dimension() || this.canTeleport(serverLevel, level)))) { // CraftBukkit +- this.teleport(portalDestination); ++ // DivineMC start - Parallel world ticking ++ java.util.function.Consumer portalEntityTask = entity -> { ++ assert entity.portalProcess != null; ++ ++ if (entity.portalProcess.isParallelCancelledByPlugin()) { ++ entity.portalProcess = null; ++ return; ++ } ++ ++ TeleportTransition portalDestination = entity.portalProcess.getPortalDestination(serverLevel, entity); ++ if (portalDestination != null) { ++ ServerLevel level = portalDestination.newLevel(); ++ if (entity instanceof ServerPlayer // CraftBukkit - always call event for players ++ || (level != null && (level.dimension() == serverLevel.dimension() || entity.canTeleport(serverLevel, level)))) { // CraftBukkit ++ entity.teleport(portalDestination); ++ } + } ++ if (this.portalProcess != null) ++ entity.portalProcess.confirmParallelAsHandled(); ++ }; ++ ++ if (org.bxteam.divinemc.DivineConfig.enableParallelWorldTicking) { ++ this.portalProcess.setParallelAsScheduled(); ++ this.getBukkitEntity().taskScheduler.schedule(portalEntityTask, entity -> {}, 0); ++ } else { ++ portalEntityTask.accept(this); + } ++ // DivineMC end - Parallel world ticking + } else if (this.portalProcess.hasExpired()) { + this.portalProcess = null; + } +@@ -3851,6 +3871,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess } private Entity teleportCrossDimension(ServerLevel level, TeleportTransition teleportTransition) { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(level, "Cannot teleport entity to another world off-main, from world " + this.level.getWorld().getName() + " to world " + level.getWorld().getName()); // DivineMC - parallel world ticking ++ if (org.bxteam.divinemc.DivineConfig.enableParallelWorldTicking) ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(level, "Cannot teleport entity to another world off-main, from world " + this.level.getWorld().getName() + " to world " + level.getWorld().getName()); // DivineMC - Parallel world ticking List passengers = this.getPassengers(); List list = new ArrayList<>(passengers.size()); this.ejectPassengers(); +diff --git a/net/minecraft/world/entity/PortalProcessor.java b/net/minecraft/world/entity/PortalProcessor.java +index 88b07fbb96b20124777889830afa480673629d43..be357d9e3c5327ceec12c31830551a564c8cea1b 100644 +--- a/net/minecraft/world/entity/PortalProcessor.java ++++ b/net/minecraft/world/entity/PortalProcessor.java +@@ -11,6 +11,7 @@ public class PortalProcessor { + private BlockPos entryPosition; + private int portalTime; + private boolean insidePortalThisTick; ++ private org.bxteam.divinemc.util.PWTTeleportState teleportState = org.bxteam.divinemc.util.PWTTeleportState.INACTIVE; // DivineMC - Parallel world ticking + + public PortalProcessor(Portal portal, BlockPos entryPosition) { + this.portal = portal; +@@ -19,6 +20,7 @@ public class PortalProcessor { + } + + public boolean processPortalTeleportation(ServerLevel level, Entity entity, boolean canChangeDimensions) { ++ if (this.isParallelTeleportScheduled()) return false; // DivineMC - Parallel world ticking + if (!this.insidePortalThisTick) { + this.decayTick(); + return false; +@@ -42,7 +44,7 @@ public class PortalProcessor { + } + + public boolean hasExpired() { +- return this.portalTime <= 0; ++ return !this.isParallelTeleportScheduled() && this.portalTime <= 0; // DivineMC - Parallel world ticking + } + + public BlockPos getEntryPosition() { +@@ -68,4 +70,30 @@ public class PortalProcessor { + public boolean isSamePortal(Portal portal) { + return this.portal == portal; + } ++ ++ // DivineMC start - Parallel world ticking ++ public boolean isParallelTeleportPending() { ++ return this.teleportState == org.bxteam.divinemc.util.PWTTeleportState.PENDING; ++ } ++ ++ public boolean isParallelTeleportScheduled() { ++ return this.teleportState != org.bxteam.divinemc.util.PWTTeleportState.INACTIVE; ++ } ++ ++ public boolean isParallelCancelledByPlugin() { ++ return this.teleportState == org.bxteam.divinemc.util.PWTTeleportState.CANCELLED; ++ } ++ ++ public void setParallelAsScheduled() { ++ this.teleportState = org.bxteam.divinemc.util.PWTTeleportState.PENDING; ++ } ++ ++ public void confirmParallelAsHandled() { ++ this.teleportState = org.bxteam.divinemc.util.PWTTeleportState.INACTIVE; ++ } ++ ++ public void setParallelAsCancelled() { ++ this.teleportState = org.bxteam.divinemc.util.PWTTeleportState.CANCELLED; ++ } ++ // DivineMC end - Parallel world ticking + } +diff --git a/net/minecraft/world/entity/ai/behavior/GoToPotentialJobSite.java b/net/minecraft/world/entity/ai/behavior/GoToPotentialJobSite.java +index 3614551856c594f3c0cfee984fcf03fad672b007..6add256046e392d8eb797e3fa9d1cbe7cca575df 100644 +--- a/net/minecraft/world/entity/ai/behavior/GoToPotentialJobSite.java ++++ b/net/minecraft/world/entity/ai/behavior/GoToPotentialJobSite.java +@@ -46,12 +46,22 @@ public class GoToPotentialJobSite extends Behavior { + BlockPos blockPos = globalPos.pos(); + ServerLevel level1 = level.getServer().getLevel(globalPos.dimension()); + if (level1 != null) { +- PoiManager poiManager = level1.getPoiManager(); +- if (poiManager.exists(blockPos, holder -> true)) { +- poiManager.release(blockPos); +- } ++ // DivineMC start - Parallel world ticking ++ Runnable releasePoiTask = () -> { ++ PoiManager poiManager = level1.getPoiManager(); ++ if (poiManager.exists(blockPos, holder -> true)) { ++ poiManager.release(blockPos); ++ } ++ ++ DebugPackets.sendPoiTicketCountPacket(level, blockPos); ++ }; + +- DebugPackets.sendPoiTicketCountPacket(level, blockPos); ++ if (org.bxteam.divinemc.DivineConfig.enableParallelWorldTicking) { ++ level.moonrise$getChunkTaskScheduler().scheduleChunkTask(0, 0, releasePoiTask, ca.spottedleaf.concurrentutil.util.Priority.BLOCKING); ++ } else { ++ releasePoiTask.run(); ++ } ++ // DivineMC end - Parallel world ticking + } + }); + entity.getBrain().eraseMemory(MemoryModuleType.POTENTIAL_JOB_SITE); +diff --git a/net/minecraft/world/entity/npc/Villager.java b/net/minecraft/world/entity/npc/Villager.java +index fec90e482c8935dfca609bbf90e67f86a1586221..4ee1791f293ba3abdbfe69824b85566fd9a36586 100644 +--- a/net/minecraft/world/entity/npc/Villager.java ++++ b/net/minecraft/world/entity/npc/Villager.java +@@ -803,13 +803,24 @@ public class Villager extends AbstractVillager implements ReputationEventHandler + this.brain.getMemory(moduleType).ifPresent(globalPos -> { + ServerLevel level = server.getLevel(globalPos.dimension()); + if (level != null) { +- PoiManager poiManager = level.getPoiManager(); +- Optional> type = poiManager.getType(globalPos.pos()); +- BiPredicate> biPredicate = POI_MEMORIES.get(moduleType); +- if (type.isPresent() && biPredicate.test(this, type.get())) { +- poiManager.release(globalPos.pos()); +- DebugPackets.sendPoiTicketCountPacket(level, globalPos.pos()); ++ // DivineMC start - Parallel world ticking ++ Runnable releasePoiTask = () -> { ++ PoiManager poiManager = level.getPoiManager(); ++ Optional> type = poiManager.getType(globalPos.pos()); ++ BiPredicate> biPredicate = POI_MEMORIES.get(moduleType); ++ if (type.isPresent() && biPredicate.test(this, type.get())) { ++ poiManager.release(globalPos.pos()); ++ DebugPackets.sendPoiTicketCountPacket(level, globalPos.pos()); ++ } ++ }; ++ ++ if (org.bxteam.divinemc.DivineConfig.enableParallelWorldTicking) { ++ level.moonrise$getChunkTaskScheduler().scheduleChunkTask(0, 0, releasePoiTask, ca.spottedleaf.concurrentutil.util.Priority.BLOCKING); ++ } ++ else { ++ releasePoiTask.run(); + } ++ // DivineMC end - Parallel world ticking + } + }); + } +diff --git a/net/minecraft/world/entity/projectile/ThrownEnderpearl.java b/net/minecraft/world/entity/projectile/ThrownEnderpearl.java +index d212f57c8c0b2086f567fd30237b110203d9e8cb..d59bdab65c034bab443ea9448930fdff85a85392 100644 +--- a/net/minecraft/world/entity/projectile/ThrownEnderpearl.java ++++ b/net/minecraft/world/entity/projectile/ThrownEnderpearl.java +@@ -126,40 +126,50 @@ public class ThrownEnderpearl extends ThrowableItemProjectile { + Vec3 vec3 = this.oldPosition(); + if (owner instanceof ServerPlayer serverPlayer) { + if (serverPlayer.connection.isAcceptingMessages()) { +- // CraftBukkit start +- ServerPlayer serverPlayer1 = serverPlayer.teleport(new TeleportTransition(serverLevel, vec3, Vec3.ZERO, 0.0F, 0.0F, Relative.union(Relative.ROTATION, Relative.DELTA), TeleportTransition.DO_NOTHING, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.ENDER_PEARL)); +- if (serverPlayer1 == null) { +- this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.HIT); +- return; +- } +- // CraftBukkit end +- if (this.random.nextFloat() < serverLevel.purpurConfig.enderPearlEndermiteChance && serverLevel.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING)) { // Purpur - Configurable Ender Pearl RNG +- Endermite endermite = EntityType.ENDERMITE.create(serverLevel, EntitySpawnReason.TRIGGERED); +- if (endermite != null) { +- endermite.setPlayerSpawned(true); // Purpur - Add back player spawned endermite API +- endermite.moveTo(owner.getX(), owner.getY(), owner.getZ(), owner.getYRot(), owner.getXRot()); +- serverLevel.addFreshEntity(endermite, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.ENDER_PEARL); ++ // DivineMC start - Parallel world ticking ++ java.util.function.Consumer teleportPlayerCrossDimensionTask = taskServerPlayer -> { ++ // CraftBukkit start ++ ServerPlayer serverPlayer1 = serverPlayer.teleport(new TeleportTransition(serverLevel, vec3, Vec3.ZERO, 0.0F, 0.0F, Relative.union(Relative.ROTATION, Relative.DELTA), TeleportTransition.DO_NOTHING, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.ENDER_PEARL)); ++ if (serverPlayer1 == null) { ++ this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.HIT); ++ return; ++ } ++ // CraftBukkit end ++ if (this.random.nextFloat() < serverLevel.purpurConfig.enderPearlEndermiteChance && serverLevel.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING)) { // Purpur - Configurable Ender Pearl RNG ++ Endermite endermite = EntityType.ENDERMITE.create(serverLevel, EntitySpawnReason.TRIGGERED); ++ if (endermite != null) { ++ endermite.setPlayerSpawned(true); // Purpur - Add back player spawned endermite API ++ endermite.moveTo(owner.getX(), owner.getY(), owner.getZ(), owner.getYRot(), owner.getXRot()); ++ serverLevel.addFreshEntity(endermite, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.ENDER_PEARL); ++ } + } +- } + +- if (this.isOnPortalCooldown()) { +- owner.setPortalCooldown(); +- } ++ if (this.isOnPortalCooldown()) { ++ owner.setPortalCooldown(); ++ } + +- // CraftBukkit start - moved up +- // ServerPlayer serverPlayer1 = serverPlayer.teleport( +- // new TeleportTransition( +- // serverLevel, vec3, Vec3.ZERO, 0.0F, 0.0F, Relative.union(Relative.ROTATION, Relative.DELTA), TeleportTransition.DO_NOTHING +- // ) +- // ); +- // CraftBukkit end - moved up +- if (serverPlayer1 != null) { +- serverPlayer1.resetFallDistance(); +- serverPlayer1.resetCurrentImpulseContext(); +- serverPlayer1.hurtServer(serverPlayer.serverLevel(), this.damageSources().enderPearl().eventEntityDamager(this), this.level().purpurConfig.enderPearlDamage); // CraftBukkit // Paper - fix DamageSource API // Purpur - Configurable Ender Pearl damage +- } ++ // CraftBukkit start - moved up ++ // ServerPlayer serverPlayer1 = serverPlayer.teleport( ++ // new TeleportTransition( ++ // serverLevel, vec3, Vec3.ZERO, 0.0F, 0.0F, Relative.union(Relative.ROTATION, Relative.DELTA), TeleportTransition.DO_NOTHING ++ // ) ++ // ); ++ // CraftBukkit end - moved up ++ if (serverPlayer1 != null) { ++ serverPlayer1.resetFallDistance(); ++ serverPlayer1.resetCurrentImpulseContext(); ++ serverPlayer1.hurtServer(serverPlayer.serverLevel(), this.damageSources().enderPearl().eventEntityDamager(this), this.level().purpurConfig.enderPearlDamage); // CraftBukkit // Paper - fix DamageSource API // Purpur - Configurable Ender Pearl damage ++ } + +- this.playSound(serverLevel, vec3); ++ this.playSound(serverLevel, vec3); ++ }; ++ ++ if (org.bxteam.divinemc.DivineConfig.enableParallelWorldTicking) { ++ serverPlayer.getBukkitEntity().taskScheduler.schedule(teleportPlayerCrossDimensionTask, entity -> {}, 0); ++ } else { ++ teleportPlayerCrossDimensionTask.accept(serverPlayer); ++ } ++ // DivineMC end - Parallel world ticking + } + } else { + Entity entity = owner.teleport( diff --git a/net/minecraft/world/inventory/AbstractContainerMenu.java b/net/minecraft/world/inventory/AbstractContainerMenu.java -index e6419715fab462b12790ecb175ce1e1a1fceed8f..40040b1e78765a4a686f9e430d16361c8c31fb14 100644 +index e6419715fab462b12790ecb175ce1e1a1fceed8f..b03068bb94c785db51da660361ce28b9f4b45898 100644 --- a/net/minecraft/world/inventory/AbstractContainerMenu.java +++ b/net/minecraft/world/inventory/AbstractContainerMenu.java @@ -92,8 +92,14 @@ public abstract class AbstractContainerMenu { } public void startOpen() {} // CraftBukkit end -+ public Throwable containerCreationStacktrace; // DivineMC - parallel world ticking ++ public Throwable containerCreationStacktrace; // DivineMC - Parallel world ticking protected AbstractContainerMenu(@Nullable MenuType menuType, int containerId) { -+ // DivineMC start - parallel world ticking (debugging) ++ // DivineMC start - Parallel world ticking (debugging) + if (org.bxteam.divinemc.DivineConfig.logContainerCreationStacktraces) { + this.containerCreationStacktrace = new Throwable(); + } -+ // DivineMC start - parallel world ticking (debugging) ++ // DivineMC start - Parallel world ticking (debugging) this.menuType = menuType; this.containerId = containerId; } diff --git a/net/minecraft/world/item/ItemStack.java b/net/minecraft/world/item/ItemStack.java -index 264b713e8b7c3d5f7d8e1facc90a60349f2cf414..f461b060e03edf4102290a424ab008b88d80bdc2 100644 +index 264b713e8b7c3d5f7d8e1facc90a60349f2cf414..596abdcda01e412d995d150ca9d056a5278ea691 100644 --- a/net/minecraft/world/item/ItemStack.java +++ b/net/minecraft/world/item/ItemStack.java -@@ -407,8 +407,8 @@ public final class ItemStack implements DataComponentHolder { +@@ -407,8 +407,10 @@ public final class ItemStack implements DataComponentHolder { if (interactionResult.consumesAction() && serverLevel.captureTreeGeneration && !serverLevel.capturedBlockStates.isEmpty()) { serverLevel.captureTreeGeneration = false; org.bukkit.Location location = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(clickedPos, serverLevel.getWorld()); - org.bukkit.TreeType treeType = net.minecraft.world.level.block.SaplingBlock.treeType; - net.minecraft.world.level.block.SaplingBlock.treeType = null; -+ org.bukkit.TreeType treeType = net.minecraft.world.level.block.SaplingBlock.treeTypeRT.get(); // DivineMC - parallel world ticking -+ net.minecraft.world.level.block.SaplingBlock.treeTypeRT.set(null); // DivineMC - parallel world ticking ++ // DivineMC start - Parallel world ticking ++ org.bukkit.TreeType treeType = net.minecraft.world.level.block.SaplingBlock.getTreeTypeTL(); ++ net.minecraft.world.level.block.SaplingBlock.setTreeTypeTL(null); ++ // DivineMC end - Parallel world ticking List blocks = new java.util.ArrayList<>(serverLevel.capturedBlockStates.values()); serverLevel.capturedBlockStates.clear(); org.bukkit.event.world.StructureGrowEvent structureEvent = null; diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java -index 0c58919e8242317ba276e103b81d07006c41deca..9608aa7231e1e19ac34f7fcf6cc4051da01f2f3e 100644 +index 0c58919e8242317ba276e103b81d07006c41deca..788e83078e9e430b84bf5dc9df877eb7ef46543b 100644 --- a/net/minecraft/world/level/Level.java +++ b/net/minecraft/world/level/Level.java @@ -170,6 +170,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl public final io.papermc.paper.antixray.ChunkPacketBlockController chunkPacketBlockController; // Paper - Anti-Xray public final org.purpurmc.purpur.PurpurWorldConfig purpurConfig; // Purpur - Purpur config files public final org.bxteam.divinemc.DivineWorldConfig divineConfig; // DivineMC - Configuration -+ public io.papermc.paper.redstone.RedstoneWireTurbo turbo = new io.papermc.paper.redstone.RedstoneWireTurbo((net.minecraft.world.level.block.RedStoneWireBlock) net.minecraft.world.level.block.Blocks.REDSTONE_WIRE); // DivineMC - parallel world ticking (moved to world) ++ public io.papermc.paper.redstone.RedstoneWireTurbo turbo = new io.papermc.paper.redstone.RedstoneWireTurbo((net.minecraft.world.level.block.RedStoneWireBlock) net.minecraft.world.level.block.Blocks.REDSTONE_WIRE); // DivineMC - Parallel world ticking (moved to world) public static BlockPos lastPhysicsProblem; // Spigot private org.spigotmc.TickLimiter entityLimiter; private org.spigotmc.TickLimiter tileLimiter; @@ -667,7 +927,7 @@ index 0c58919e8242317ba276e103b81d07006c41deca..9608aa7231e1e19ac34f7fcf6cc4051d @Override public boolean setBlock(BlockPos pos, BlockState state, int flags, int recursionLeft) { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread((ServerLevel)this, pos, "Updating block asynchronously"); // DivineMC - parallel world ticking (additional concurrency issues logs) ++ if (org.bxteam.divinemc.DivineConfig.enableParallelWorldTicking) ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread((ServerLevel)this, pos, "Updating block asynchronously"); // DivineMC - Parallel world ticking (additional concurrency issues logs) // CraftBukkit start - tree generation if (this.captureTreeGeneration) { // Paper start - Protect Bedrock and End Portal/Frames from being destroyed @@ -677,50 +937,55 @@ index 0c58919e8242317ba276e103b81d07006c41deca..9608aa7231e1e19ac34f7fcf6cc4051d tickingBlockEntity.tick(); - // Paper start - rewrite chunk system - if ((++tickedEntities & 7) == 0) { +- ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel)(Level)(Object)this).moonrise$midTickTasks(); + // DivineMC start - Parallel world ticking + ++tickedEntities; + if (!org.bxteam.divinemc.DivineConfig.enableParallelWorldTicking && (tickedEntities & 7) == 0) { - ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel)(Level)(Object)this).moonrise$midTickTasks(); ++ this.moonrise$midTickTasks(); } - // Paper end - rewrite chunk system + // DivineMC end - Parallel world ticking } } this.blockEntityTickers.removeAll(toRemove); // Paper - Fix MC-117075 -@@ -1553,7 +1556,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl +@@ -1553,7 +1556,11 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl entity.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD); // Paper end - Prevent block entity and entity crashes } - this.moonrise$midTickTasks(); // Paper - rewrite chunk system -+ // this.moonrise$midTickTasks(); // Paper - rewrite chunk system // DivineMC - Parallel world ticking - commented out ++ // DivineMC start - Parallel world ticking ++ if (!org.bxteam.divinemc.DivineConfig.enableParallelWorldTicking) { ++ this.moonrise$midTickTasks(); // Paper - rewrite chunk system ++ } ++ // DivineMC end - Parallel world ticking } // Paper start - Option to prevent armor stands from doing entity lookups -@@ -1696,6 +1699,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl +@@ -1696,6 +1703,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl @Nullable public BlockEntity getBlockEntity(BlockPos pos, boolean validate) { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThreadOrAsyncThread((ServerLevel) this, "Cannot read world asynchronously"); // DivineMC - parallel world ticking ++ if (org.bxteam.divinemc.DivineConfig.enableParallelWorldTicking) ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThreadOrAsyncThread((ServerLevel) this, "Cannot read world asynchronously"); // DivineMC - Parallel world ticking // Paper start - Perf: Optimize capturedTileEntities lookup net.minecraft.world.level.block.entity.BlockEntity blockEntity; if (!this.capturedTileEntities.isEmpty() && (blockEntity = this.capturedTileEntities.get(pos)) != null) { -@@ -1713,6 +1717,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl +@@ -1713,6 +1721,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl } public void setBlockEntity(BlockEntity blockEntity) { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread((ServerLevel) this, "Cannot modify world asynchronously"); // DivineMC - parallel world ticking ++ if (org.bxteam.divinemc.DivineConfig.enableParallelWorldTicking) ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread((ServerLevel) this, "Cannot modify world asynchronously"); // DivineMC - Parallel world ticking BlockPos blockPos = blockEntity.getBlockPos(); if (!this.isOutsideBuildHeight(blockPos)) { // CraftBukkit start -@@ -1797,6 +1802,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl +@@ -1797,6 +1806,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl @Override public List getEntities(@Nullable Entity entity, AABB boundingBox, Predicate predicate) { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread((ServerLevel)this, boundingBox, "Cannot getEntities asynchronously"); // DivineMC - parallel world ticking (additional concurrency issues logs) ++ if (org.bxteam.divinemc.DivineConfig.enableParallelWorldTicking) ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread((ServerLevel)this, boundingBox, "Cannot getEntities asynchronously"); // DivineMC - Parallel world ticking (additional concurrency issues logs) List list = Lists.newArrayList(); // Paper start - rewrite chunk system -@@ -2106,8 +2112,15 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl +@@ -2106,8 +2116,15 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl public abstract RecipeAccess recipeAccess(); public BlockPos getBlockRandomPos(int x, int y, int z, int yMask) { @@ -739,29 +1004,46 @@ index 0c58919e8242317ba276e103b81d07006c41deca..9608aa7231e1e19ac34f7fcf6cc4051d } diff --git a/net/minecraft/world/level/block/DispenserBlock.java b/net/minecraft/world/level/block/DispenserBlock.java -index e0a4d41e5bcf144ea4c10d6f633c3a95ed2c5aec..274f36581e7040c67bf8649258660228a3e8cce0 100644 +index e0a4d41e5bcf144ea4c10d6f633c3a95ed2c5aec..12ead3dc17828558b2e6b67a5f306a5948f7b2cf 100644 --- a/net/minecraft/world/level/block/DispenserBlock.java +++ b/net/minecraft/world/level/block/DispenserBlock.java -@@ -50,7 +50,7 @@ public class DispenserBlock extends BaseEntityBlock { - private static final DefaultDispenseItemBehavior DEFAULT_BEHAVIOR = new DefaultDispenseItemBehavior(); +@@ -51,6 +51,25 @@ public class DispenserBlock extends BaseEntityBlock { public static final Map DISPENSER_REGISTRY = new IdentityHashMap<>(); private static final int TRIGGER_DURATION = 4; -- public static boolean eventFired = false; // CraftBukkit -+ public static ThreadLocal eventFired = ThreadLocal.withInitial(() -> Boolean.FALSE); // DivineMC - parallel world ticking + public static boolean eventFired = false; // CraftBukkit ++ // DivineMC start - Parallel world ticking ++ public static ThreadLocal eventFiredTL = ThreadLocal.withInitial(() -> Boolean.FALSE); ++ ++ public static boolean getEventFiredTL() { ++ if (org.bxteam.divinemc.DivineConfig.enableParallelWorldTicking && eventFiredTL.get()) return true; ++ ++ synchronized (DispenserBlock.class) { ++ return eventFired; ++ } ++ } ++ ++ public static void setEventFiredTL(boolean value) { ++ if (org.bxteam.divinemc.DivineConfig.enableParallelWorldTicking) eventFiredTL.set(value); ++ ++ synchronized (DispenserBlock.class) { ++ eventFired = value; ++ } ++ } ++ // DivineMC end - Parallel world ticking @Override public MapCodec codec() { -@@ -96,7 +96,7 @@ public class DispenserBlock extends BaseEntityBlock { +@@ -96,7 +115,7 @@ public class DispenserBlock extends BaseEntityBlock { DispenseItemBehavior dispenseMethod = this.getDispenseMethod(level, item); if (dispenseMethod != DispenseItemBehavior.NOOP) { if (!org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockPreDispenseEvent(level, pos, item, randomSlot)) return; // Paper - Add BlockPreDispenseEvent - DispenserBlock.eventFired = false; // CraftBukkit - reset event status -+ DispenserBlock.eventFired.set(Boolean.FALSE); // CraftBukkit - reset event status // DivineMC - parallel world ticking ++ DispenserBlock.setEventFiredTL(Boolean.FALSE); // CraftBukkit - reset event status // DivineMC - Parallel world ticking dispenserBlockEntity.setItem(randomSlot, dispenseMethod.dispense(blockSource, item)); } } diff --git a/net/minecraft/world/level/block/FungusBlock.java b/net/minecraft/world/level/block/FungusBlock.java -index 85f0eac75784565c658c5178c544f969db3d6f54..22c527a7b44a434b66a2217ed88eca79703b2036 100644 +index 85f0eac75784565c658c5178c544f969db3d6f54..fc900a7c382f825110c561070155c1f6451557ed 100644 --- a/net/minecraft/world/level/block/FungusBlock.java +++ b/net/minecraft/world/level/block/FungusBlock.java @@ -76,9 +76,9 @@ public class FungusBlock extends BushBlock implements BonemealableBlock { @@ -769,15 +1051,15 @@ index 85f0eac75784565c658c5178c544f969db3d6f54..22c527a7b44a434b66a2217ed88eca79 .map((value) -> { if (this == Blocks.WARPED_FUNGUS) { - SaplingBlock.treeType = org.bukkit.TreeType.WARPED_FUNGUS; -+ SaplingBlock.treeTypeRT.set(org.bukkit.TreeType.WARPED_FUNGUS); // DivineMC - parallel world ticking ++ SaplingBlock.setTreeTypeTL(org.bukkit.TreeType.WARPED_FUNGUS); // DivineMC - Parallel world ticking } else if (this == Blocks.CRIMSON_FUNGUS) { - SaplingBlock.treeType = org.bukkit.TreeType.CRIMSON_FUNGUS; -+ SaplingBlock.treeTypeRT.set(org.bukkit.TreeType.CRIMSON_FUNGUS); // DivineMC - parallel world ticking ++ SaplingBlock.setTreeTypeTL(org.bukkit.TreeType.CRIMSON_FUNGUS); // DivineMC - Parallel world ticking } return value; }) diff --git a/net/minecraft/world/level/block/MushroomBlock.java b/net/minecraft/world/level/block/MushroomBlock.java -index 904369f4d7db41026183f2de7c96c2f0f4dc204d..1a3f07834fd1483aa77f7733512908b62583edda 100644 +index 904369f4d7db41026183f2de7c96c2f0f4dc204d..371334bc3a1ea505b3293f13542aef4cf8f2a2d2 100644 --- a/net/minecraft/world/level/block/MushroomBlock.java +++ b/net/minecraft/world/level/block/MushroomBlock.java @@ -94,7 +94,7 @@ public class MushroomBlock extends BushBlock implements BonemealableBlock { @@ -785,7 +1067,7 @@ index 904369f4d7db41026183f2de7c96c2f0f4dc204d..1a3f07834fd1483aa77f7733512908b6 } else { level.removeBlock(pos, false); - SaplingBlock.treeType = (this == Blocks.BROWN_MUSHROOM) ? org.bukkit.TreeType.BROWN_MUSHROOM : org.bukkit.TreeType.RED_MUSHROOM; // CraftBukkit -+ SaplingBlock.treeTypeRT.set((this == Blocks.BROWN_MUSHROOM) ? org.bukkit.TreeType.BROWN_MUSHROOM : org.bukkit.TreeType.RED_MUSHROOM); // CraftBukkit // DivineMC - parallel world ticking ++ SaplingBlock.setTreeTypeTL((this == Blocks.BROWN_MUSHROOM) ? org.bukkit.TreeType.BROWN_MUSHROOM : org.bukkit.TreeType.RED_MUSHROOM); // CraftBukkit // DivineMC - Parallel world ticking if (optional.get().value().place(level, level.getChunkSource().getGenerator(), random, pos)) { return true; } else { @@ -824,50 +1106,76 @@ index 12c9d60314c99fb65e640d255a2d0c6b7790ad4d..4293c5f11a17500353b63cad399727ec } } diff --git a/net/minecraft/world/level/block/SaplingBlock.java b/net/minecraft/world/level/block/SaplingBlock.java -index e014f052e9b0f5ca6b28044e2389782b7d0e0cb8..d10f8909fcfa930c726f2620e3ffc912baa40be7 100644 +index e014f052e9b0f5ca6b28044e2389782b7d0e0cb8..4ad0e278a348407beed1ec4381bfeacf4de32a0d 100644 --- a/net/minecraft/world/level/block/SaplingBlock.java +++ b/net/minecraft/world/level/block/SaplingBlock.java -@@ -26,7 +26,7 @@ public class SaplingBlock extends BushBlock implements BonemealableBlock { - protected static final float AABB_OFFSET = 6.0F; +@@ -27,6 +27,26 @@ public class SaplingBlock extends BushBlock implements BonemealableBlock { protected static final VoxelShape SHAPE = Block.box(2.0, 0.0, 2.0, 14.0, 12.0, 14.0); protected final TreeGrower treeGrower; -- public static org.bukkit.TreeType treeType; // CraftBukkit -+ public static final ThreadLocal treeTypeRT = new ThreadLocal<>(); // CraftBukkit // DivineMC - parallel world ticking (from Folia) + public static org.bukkit.TreeType treeType; // CraftBukkit ++ // DivineMC start - Parallel world ticking ++ public static final ThreadLocal treeTypeTL = new ThreadLocal<>(); ++ ++ public static org.bukkit.TreeType getTreeTypeTL() { ++ org.bukkit.TreeType treeTypeRTCopy; ++ if (org.bxteam.divinemc.DivineConfig.enableParallelWorldTicking && (treeTypeRTCopy = treeTypeTL.get()) != null) return treeTypeRTCopy; ++ ++ synchronized (SaplingBlock.class) { ++ return treeType; ++ } ++ } ++ ++ public static void setTreeTypeTL(org.bukkit.TreeType value) { ++ if (org.bxteam.divinemc.DivineConfig.enableParallelWorldTicking) treeTypeTL.set(value); ++ ++ synchronized (SaplingBlock.class) { ++ treeType = value; ++ } ++ } ++ // DivineMC end - Parallel world ticking @Override public MapCodec codec() { -@@ -63,8 +63,10 @@ public class SaplingBlock extends BushBlock implements BonemealableBlock { +@@ -63,14 +83,16 @@ public class SaplingBlock extends BushBlock implements BonemealableBlock { this.treeGrower.growTree(level, level.getChunkSource().getGenerator(), pos, state, random); level.captureTreeGeneration = false; if (!level.capturedBlockStates.isEmpty()) { - org.bukkit.TreeType treeType = SaplingBlock.treeType; - SaplingBlock.treeType = null; -+ // DivineMC start - parallel world ticking -+ org.bukkit.TreeType treeType = SaplingBlock.treeTypeRT.get(); -+ SaplingBlock.treeTypeRT.set(null); -+ // DivineMC end - parallel world ticking ++ // DivineMC start - Parallel world ticking ++ org.bukkit.TreeType treeTypeLocal = SaplingBlock.getTreeTypeTL(); ++ SaplingBlock.setTreeTypeTL(null); ++ // DivineMC end - Parallel world ticking org.bukkit.Location location = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(pos, level.getWorld()); java.util.List blocks = new java.util.ArrayList<>(level.capturedBlockStates.values()); level.capturedBlockStates.clear(); + org.bukkit.event.world.StructureGrowEvent event = null; +- if (treeType != null) { +- event = new org.bukkit.event.world.StructureGrowEvent(location, treeType, false, null, blocks); ++ if (treeTypeLocal != null) { // DivineMC - Parallel world ticking ++ event = new org.bukkit.event.world.StructureGrowEvent(location, treeTypeLocal, false, null, blocks); // DivineMC - Parallel world ticking + org.bukkit.Bukkit.getPluginManager().callEvent(event); + } + if (event == null || !event.isCancelled()) { diff --git a/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java b/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java -index 26db603ed681a6c302596627d4dd5bf8a9bafc4e..b761f12131699bdd075a39f19d96cb70ae8f4eda 100644 +index 26db603ed681a6c302596627d4dd5bf8a9bafc4e..20e8155c0c5f4bd60344c1bbd15f7555ad9ef554 100644 --- a/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java +++ b/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java @@ -77,6 +77,12 @@ public abstract class BaseContainerBlockEntity extends BlockEntity implements Co return canUnlock(player, code, displayName, null); } public static boolean canUnlock(Player player, LockCode code, Component displayName, @Nullable BlockEntity blockEntity) { -+ // DivineMC start - parallel world ticking ++ // DivineMC start - Parallel world ticking + if (org.bxteam.divinemc.DivineConfig.enableParallelWorldTicking && player instanceof net.minecraft.server.level.ServerPlayer serverPlayer && blockEntity != null && blockEntity.getLevel() != serverPlayer.serverLevel()) { + net.minecraft.server.MinecraftServer.LOGGER.warn("Player {} ({}) attempted to open a BlockEntity @ {} {}, {}, {} while they were in a different world {} than the block themselves!", serverPlayer.getScoreboardName(), serverPlayer.getStringUUID(), blockEntity.getLevel().getWorld().getName(), blockEntity.getBlockPos().getX(), blockEntity.getBlockPos().getY(), blockEntity.getBlockPos().getZ(), serverPlayer.level().getWorld().getName()); + return false; + } -+ // DivineMC end - parallel world ticking ++ // DivineMC end - Parallel world ticking if (player instanceof net.minecraft.server.level.ServerPlayer serverPlayer && blockEntity != null && blockEntity.getLevel() != null && blockEntity.getLevel().getBlockEntity(blockEntity.getBlockPos()) == blockEntity) { final org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(blockEntity.getLevel(), blockEntity.getBlockPos()); net.kyori.adventure.text.Component lockedMessage = net.kyori.adventure.text.Component.translatable("container.isLocked", io.papermc.paper.adventure.PaperAdventure.asAdventure(displayName)); diff --git a/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java b/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java -index 1638eccef431fb68775af624110f1968f0c6dabd..8f3eb265adcacf6d4f71db9a298ba8a7ce99c941 100644 +index 1638eccef431fb68775af624110f1968f0c6dabd..491ac8be8fdaa573fe1b3a234a14110d89b43345 100644 --- a/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java +++ b/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java @@ -43,9 +43,9 @@ public class SculkCatalystBlockEntity extends BlockEntity implements GameEventLi @@ -875,22 +1183,22 @@ index 1638eccef431fb68775af624110f1968f0c6dabd..8f3eb265adcacf6d4f71db9a298ba8a7 public static void serverTick(Level level, BlockPos pos, BlockState state, SculkCatalystBlockEntity sculkCatalyst) { - org.bukkit.craftbukkit.event.CraftEventFactory.sourceBlockOverride = sculkCatalyst.getBlockPos(); // CraftBukkit - SPIGOT-7068: Add source block override, not the most elegant way but better than passing down a BlockPosition up to five methods deep. -+ org.bukkit.craftbukkit.event.CraftEventFactory.sourceBlockOverrideRT.set(pos); // DivineMC - parallel world ticking // CraftBukkit - SPIGOT-7068: Add source block override, not the most elegant way but better than passing down a BlockPosition up to five methods deep. ++ org.bukkit.craftbukkit.event.CraftEventFactory.setSourceBlockOverrideTL(pos); // DivineMC - Parallel world ticking // CraftBukkit - SPIGOT-7068: Add source block override, not the most elegant way but better than passing down a BlockPosition up to five methods deep. sculkCatalyst.catalystListener.getSculkSpreader().updateCursors(level, pos, level.getRandom(), true); - org.bukkit.craftbukkit.event.CraftEventFactory.sourceBlockOverride = null; // CraftBukkit -+ org.bukkit.craftbukkit.event.CraftEventFactory.sourceBlockOverrideRT.set(null); // DivineMC - parallel world ticking // CraftBukkit ++ org.bukkit.craftbukkit.event.CraftEventFactory.setSourceBlockOverrideTL(null); // DivineMC - Parallel world ticking // CraftBukkit } @Override diff --git a/net/minecraft/world/level/block/grower/TreeGrower.java b/net/minecraft/world/level/block/grower/TreeGrower.java -index cf7311c507de09a8f89934e430b2201e8bdffe51..30bd72ad2f66616a89b78fb0677109b8341eb132 100644 +index cf7311c507de09a8f89934e430b2201e8bdffe51..e76cb020471f28c29f1b24addb5cd101ddc9630f 100644 --- a/net/minecraft/world/level/block/grower/TreeGrower.java +++ b/net/minecraft/world/level/block/grower/TreeGrower.java @@ -204,55 +204,59 @@ public final class TreeGrower { // CraftBukkit start private void setTreeType(Holder> holder) { ResourceKey> treeFeature = holder.unwrapKey().get(); -+ // DivineMC start - parallel world ticking ++ // DivineMC start - Parallel world ticking + org.bukkit.TreeType treeType; if (treeFeature == TreeFeatures.OAK || treeFeature == TreeFeatures.OAK_BEES_005) { - net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.TREE; @@ -964,60 +1272,59 @@ index cf7311c507de09a8f89934e430b2201e8bdffe51..30bd72ad2f66616a89b78fb0677109b8 } else { throw new IllegalArgumentException("Unknown tree generator " + treeFeature); } -+ net.minecraft.world.level.block.SaplingBlock.treeTypeRT.set(treeType); -+ // DivineMC end - parallel world ticking ++ net.minecraft.world.level.block.SaplingBlock.setTreeTypeTL(treeType); ++ // DivineMC end - Parallel world ticking } // CraftBukkit end } diff --git a/net/minecraft/world/level/chunk/LevelChunk.java b/net/minecraft/world/level/chunk/LevelChunk.java -index e3d71604c161373ef44164606602290af1bd1cb0..ba88a3aed79543f69a5bf30cfd03f30983d229cf 100644 +index e3d71604c161373ef44164606602290af1bd1cb0..b245f594ff91e2d29c83f56b9ed5165f37387e7e 100644 --- a/net/minecraft/world/level/chunk/LevelChunk.java +++ b/net/minecraft/world/level/chunk/LevelChunk.java @@ -365,6 +365,7 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p @Nullable public BlockState setBlockState(BlockPos pos, BlockState state, boolean isMoving, boolean doPlace) { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.level, pos, "Updating block asynchronously"); // DivineMC - parallel world ticking ++ if (org.bxteam.divinemc.DivineConfig.enableParallelWorldTicking) ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.level, pos, "Updating block asynchronously"); // DivineMC - Parallel world ticking // CraftBukkit end int y = pos.getY(); LevelChunkSection section = this.getSection(this.getSectionIndex(y)); diff --git a/net/minecraft/world/level/entity/EntityTickList.java b/net/minecraft/world/level/entity/EntityTickList.java -index c89701d7bdc9b889038d3c52f2232fb17624b113..3c6ec711bf9a75657c13da647e4ae7947257b627 100644 +index c89701d7bdc9b889038d3c52f2232fb17624b113..018a04674897cfcec0e8de5cb2ab06243a994ae3 100644 --- a/net/minecraft/world/level/entity/EntityTickList.java +++ b/net/minecraft/world/level/entity/EntityTickList.java -@@ -10,17 +10,27 @@ import net.minecraft.world.entity.Entity; +@@ -10,17 +10,26 @@ import net.minecraft.world.entity.Entity; public class EntityTickList { public final ca.spottedleaf.moonrise.common.list.IteratorSafeOrderedReferenceSet entities = new ca.spottedleaf.moonrise.common.list.IteratorSafeOrderedReferenceSet<>(); // Paper - rewrite chunk system -+ // DivineMC start - parallel world ticking -+ // Used to track async entity additions/removals/loops ++ // DivineMC start - Parallel world ticking + private final net.minecraft.server.level.ServerLevel serverLevel; + + public EntityTickList(net.minecraft.server.level.ServerLevel serverLevel) { + this.serverLevel = serverLevel; + } -+ // DivineMC end - parallel world ticking ++ // DivineMC end - Parallel world ticking private void ensureActiveIsNotIterated() { // Paper - rewrite chunk system } public void add(Entity entity) { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(entity, "Asynchronous entity ticklist addition"); // Paper // DivineMC - parallel world ticking ++ if (org.bxteam.divinemc.DivineConfig.enableParallelWorldTicking) ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(entity, "Asynchronous entity ticklist addition"); // Paper // DivineMC - Parallel world ticking this.ensureActiveIsNotIterated(); this.entities.add(entity); // Paper - rewrite chunk system } public void remove(Entity entity) { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(entity, "Asynchronous entity ticklist removal"); // Paper // DivineMC - parallel world ticking ++ if (org.bxteam.divinemc.DivineConfig.enableParallelWorldTicking) ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(entity, "Asynchronous entity ticklist removal"); // Paper // DivineMC - Parallel world ticking this.ensureActiveIsNotIterated(); this.entities.remove(entity); // Paper - rewrite chunk system } -@@ -30,6 +40,7 @@ public class EntityTickList { +@@ -30,6 +39,7 @@ public class EntityTickList { } public void forEach(Consumer entity) { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverLevel, "Asynchronous entity ticklist iteration"); // DivineMC - parallel world ticking ++ if (org.bxteam.divinemc.DivineConfig.enableParallelWorldTicking) ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverLevel, "Asynchronous entity ticklist iteration"); // DivineMC - Parallel world ticking // Paper start - rewrite chunk system // To ensure nothing weird happens with dimension travelling, do not iterate over new entries... // (by dfl iterator() is configured to not iterate over new entries) diff --git a/divinemc-server/minecraft-patches/features/0015-Some-optimizations.patch b/divinemc-server/minecraft-patches/features/0009-Misc-Optimizations.patch similarity index 67% rename from divinemc-server/minecraft-patches/features/0015-Some-optimizations.patch rename to divinemc-server/minecraft-patches/features/0009-Misc-Optimizations.patch index 0d09051..0f21d72 100644 --- a/divinemc-server/minecraft-patches/features/0015-Some-optimizations.patch +++ b/divinemc-server/minecraft-patches/features/0009-Misc-Optimizations.patch @@ -1,11 +1,11 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> -Date: Sat, 1 Feb 2025 15:59:29 +0300 -Subject: [PATCH] Some optimizations +Date: Fri, 31 Jan 2025 21:50:46 +0300 +Subject: [PATCH] Misc Optimizations diff --git a/com/mojang/brigadier/tree/CommandNode.java b/com/mojang/brigadier/tree/CommandNode.java -index 2ae5b80338282ac73c74765fc0729af2d54f6d6c..59c93bbf431836fd29101ff9e7e467d9ea3f5df9 100644 +index 2ae5b80338282ac73c74765fc0729af2d54f6d6c..00a63a6a5983e6a25f2a9014a2f9eefeda468cdf 100644 --- a/com/mojang/brigadier/tree/CommandNode.java +++ b/com/mojang/brigadier/tree/CommandNode.java @@ -24,7 +24,7 @@ import java.util.concurrent.CompletableFuture; @@ -13,10 +13,42 @@ index 2ae5b80338282ac73c74765fc0729af2d54f6d6c..59c93bbf431836fd29101ff9e7e467d9 public abstract class CommandNode implements Comparable> { - private final Map> children = new LinkedHashMap<>(); -+ private final Map> children = Collections.synchronizedMap(new LinkedHashMap<>()); // DivineMC - Some optimizations ++ private final Map> children = Collections.synchronizedMap(new LinkedHashMap<>()); // DivineMC - Misc Optimizations private final Map> literals = new LinkedHashMap<>(); private final Map> arguments = new LinkedHashMap<>(); public Predicate requirement; // Paper - public-f +diff --git a/com/mojang/math/OctahedralGroup.java b/com/mojang/math/OctahedralGroup.java +index 11902e7427761746ee098fea3276a34fef0096ba..3ba23fa243f7af712a41316066ca554f1c23b495 100644 +--- a/com/mojang/math/OctahedralGroup.java ++++ b/com/mojang/math/OctahedralGroup.java +@@ -112,6 +112,7 @@ public enum OctahedralGroup implements StringRepresentable { + this.transformation = new Matrix3f().scaling(invertX ? -1.0F : 1.0F, invertY ? -1.0F : 1.0F, invertZ ? -1.0F : 1.0F); + this.transformation.mul(permutation.transformation()); + this.initializeRotationDirections(); // Paper - Avoid Lazy Initialization for Enum Fields ++ this.rotate(Direction.UP); // DivineMC - Math Optimizations + } + + private BooleanList packInversions() { +diff --git a/com/mojang/math/Transformation.java b/com/mojang/math/Transformation.java +index aa755b8b7f8bc5910322e0c5b520f603da06a85a..e781dea43279aa77cc40a7afd2281c32cc8347a9 100644 +--- a/com/mojang/math/Transformation.java ++++ b/com/mojang/math/Transformation.java +@@ -51,6 +51,7 @@ public final class Transformation { + } else { + this.matrix = matrix; + } ++ ensureDecomposed(); // DivineMC - Math Optimizations + } + + public Transformation(@Nullable Vector3f translation, @Nullable Quaternionf leftRotation, @Nullable Vector3f scale, @Nullable Quaternionf rightRotation) { +@@ -60,6 +61,7 @@ public final class Transformation { + this.scale = scale != null ? scale : new Vector3f(1.0F, 1.0F, 1.0F); + this.rightRotation = rightRotation != null ? rightRotation : new Quaternionf(); + this.decomposed = true; ++ ensureDecomposed(); // DivineMC - Math Optimizations + } + + public static Transformation identity() { diff --git a/net/minecraft/core/MappedRegistry.java b/net/minecraft/core/MappedRegistry.java index 5f752603aa5611ce9d3dd44cc5b70c27ac46a86e..332122c0b700fb743f91f3fed16aade41dceec28 100644 --- a/net/minecraft/core/MappedRegistry.java @@ -62,7 +94,7 @@ index bee90335677f7d8b01589ce5cfd81a40fd422886..a5e488d14fd2016ee188b114d0e68156 public record Positioned(ChunkPos center, int viewDistance) implements ChunkTrackingView { diff --git a/net/minecraft/util/ClassInstanceMultiMap.java b/net/minecraft/util/ClassInstanceMultiMap.java -index 2a708ae0d5bb209650b525e3c56051f8b5655074..762cba15597623f95a242bdd44742d9b892ad042 100644 +index 2a708ae0d5bb209650b525e3c56051f8b5655074..4c7670224f0c90c1d0d833ff0b3d908846133b4a 100644 --- a/net/minecraft/util/ClassInstanceMultiMap.java +++ b/net/minecraft/util/ClassInstanceMultiMap.java @@ -14,9 +14,9 @@ import java.util.Map.Entry; @@ -70,10 +102,10 @@ index 2a708ae0d5bb209650b525e3c56051f8b5655074..762cba15597623f95a242bdd44742d9b public class ClassInstanceMultiMap extends AbstractCollection { - private final Map, List> byClass = Maps.newHashMap(); -+ private final Map, List> byClass = new it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap<>(); // DivineMC - Some optimizations ++ private final Map, List> byClass = new it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap<>(); // DivineMC - Misc Optimizations private final Class baseClass; - private final List allInstances = Lists.newArrayList(); -+ private final List allInstances = new it.unimi.dsi.fastutil.objects.ObjectArrayList<>(); // DivineMC - Some optimizations ++ private final List allInstances = new it.unimi.dsi.fastutil.objects.ObjectArrayList<>(); // DivineMC - Misc Optimizations public ClassInstanceMultiMap(Class baseClass) { this.baseClass = baseClass; @@ -109,7 +141,7 @@ index 2a708ae0d5bb209650b525e3c56051f8b5655074..762cba15597623f95a242bdd44742d9b @Override diff --git a/net/minecraft/util/CrudeIncrementalIntIdentityHashBiMap.java b/net/minecraft/util/CrudeIncrementalIntIdentityHashBiMap.java -index f28fbf81a417a678726d3f77b3999054676d522e..fbf17cc11fa8e56c5ada2f0f60c944b6b22591a5 100644 +index f28fbf81a417a678726d3f77b3999054676d522e..7ff32b1f93b31fafd13f4e0857d14d85ef1f28c7 100644 --- a/net/minecraft/util/CrudeIncrementalIntIdentityHashBiMap.java +++ b/net/minecraft/util/CrudeIncrementalIntIdentityHashBiMap.java @@ -52,23 +52,23 @@ public class CrudeIncrementalIntIdentityHashBiMap implements IdMap, ca.spo @@ -117,27 +149,27 @@ index f28fbf81a417a678726d3f77b3999054676d522e..fbf17cc11fa8e56c5ada2f0f60c944b6 @Nullable @Override - public K byId(int value) { -+ public synchronized K byId(int value) { // DivineMC - Some optimizations ++ public synchronized K byId(int value) { // DivineMC - Misc Optimizations return value >= 0 && value < this.byId.length ? this.byId[value] : null; } - private int getValue(int key) { -+ private synchronized int getValue(int key) { // DivineMC - Some optimizations ++ private synchronized int getValue(int key) { // DivineMC - Misc Optimizations return key == -1 ? -1 : this.values[key]; } - public boolean contains(K value) { -+ public synchronized boolean contains(K value) { // DivineMC - Some optimizations ++ public synchronized boolean contains(K value) { // DivineMC - Misc Optimizations return this.getId(value) != -1; } - public boolean contains(int value) { -+ public synchronized boolean contains(int value) { // DivineMC - Some optimizations ++ public synchronized boolean contains(int value) { // DivineMC - Misc Optimizations return this.byId(value) != null; } - public int add(K object) { -+ public synchronized int add(K object) { // DivineMC - Some optimizations ++ public synchronized int add(K object) { // DivineMC - Misc Optimizations int i = this.nextId(); this.addMapping(object, i); return i; @@ -146,7 +178,7 @@ index f28fbf81a417a678726d3f77b3999054676d522e..fbf17cc11fa8e56c5ada2f0f60c944b6 } - public void addMapping(K object, int intKey) { -+ public synchronized void addMapping(K object, int intKey) { // DivineMC - Some optimizations ++ public synchronized void addMapping(K object, int intKey) { // DivineMC - Misc Optimizations int max = Math.max(intKey, this.size + 1); if (max >= this.keys.length * 0.8F) { int i = this.keys.length << 1; @@ -155,12 +187,12 @@ index f28fbf81a417a678726d3f77b3999054676d522e..fbf17cc11fa8e56c5ada2f0f60c944b6 @Override - public Iterator iterator() { -+ public synchronized Iterator iterator() { // DivineMC - Some optimizations ++ public synchronized Iterator iterator() { // DivineMC - Misc Optimizations return Iterators.filter(Iterators.forArray(this.byId), Predicates.notNull()); } - public void clear() { -+ public synchronized void clear() { // DivineMC - Some optimizations ++ public synchronized void clear() { // DivineMC - Misc Optimizations Arrays.fill(this.keys, null); Arrays.fill(this.byId, null); this.nextId = 0; @@ -264,8 +296,110 @@ index f36f8f2d49d4eba5c80eb243883749d6f831eb8a..5abd899c88683cb79bb8f02e43c4bfbe + // DivineMC end - Some optimizations } } +diff --git a/net/minecraft/util/Mth.java b/net/minecraft/util/Mth.java +index ab3a221c115992d0f4ea921aa92cf0976b815ff4..076a931341da486162f289a5f19d3d6736df7768 100644 +--- a/net/minecraft/util/Mth.java ++++ b/net/minecraft/util/Mth.java +@@ -46,11 +46,11 @@ public class Mth { + private static final double[] COS_TAB = new double[257]; + + public static float sin(float value) { +- return SIN[(int)(value * 10430.378F) & 65535]; ++ return net.caffeinemc.mods.lithium.common.util.math.CompactSineLUT.sin(value); // DivineMC - Math Optimizations + } + + public static float cos(float value) { +- return SIN[(int)(value * 10430.378F + 16384.0F) & 65535]; ++ return net.caffeinemc.mods.lithium.common.util.math.CompactSineLUT.cos(value); // DivineMC - Math Optimizations + } + + public static float sqrt(float value) { +@@ -58,18 +58,15 @@ public class Mth { + } + + public static int floor(float value) { +- int i = (int)value; +- return value < i ? i - 1 : i; ++ return (int) Math.floor(value); // DivineMC - Math Optimizations + } + + public static int floor(double value) { +- int i = (int)value; +- return value < i ? i - 1 : i; ++ return (int) Math.floor(value); // DivineMC - Math Optimizations + } + + public static long lfloor(double value) { +- long l = (long)value; +- return value < l ? l - 1L : l; ++ return (long) Math.floor(value); // DivineMC - Math Optimizations + } + + public static float abs(float value) { +@@ -81,13 +78,11 @@ public class Mth { + } + + public static int ceil(float value) { +- int i = (int)value; +- return value > i ? i + 1 : i; ++ return (int) Math.ceil(value); // DivineMC - Math Optimizations + } + + public static int ceil(double value) { +- int i = (int)value; +- return value > i ? i + 1 : i; ++ return (int) Math.ceil(value); // DivineMC - Math Optimizations + } + + public static int clamp(int value, int min, int max) { +@@ -123,15 +118,7 @@ public class Mth { + } + + public static double absMax(double x, double y) { +- if (x < 0.0) { +- x = -x; +- } +- +- if (y < 0.0) { +- y = -y; +- } +- +- return Math.max(x, y); ++ return Math.max(Math.abs(x), Math.abs(y)); // DivineMC - Math Optimizations + } + + public static int floorDiv(int dividend, int divisor) { +@@ -162,14 +149,26 @@ public class Mth { + return Math.floorMod(x, y); + } + +- public static float positiveModulo(float numerator, float denominator) { ++ public static float positiveModuloForAnyDenominator(float numerator, float denominator) { // DivineMC - Math Optimizations + return (numerator % denominator + denominator) % denominator; + } + +- public static double positiveModulo(double numerator, double denominator) { ++ public static double positiveModuloForAnyDenominator(double numerator, double denominator) { // DivineMC - Math Optimizations + return (numerator % denominator + denominator) % denominator; + } + ++ // DivineMC start - Math Optimizations ++ public static float positiveModuloForPositiveIntegerDenominator(float numerator, float denominator) { ++ var modulo = numerator % denominator; ++ return modulo < 0 ? modulo + denominator : modulo; ++ } ++ ++ public static double positiveModuloForPositiveIntegerDenominator(double numerator, double denominator) { ++ var modulo = numerator % denominator; ++ return modulo < 0 ? modulo + denominator : modulo; ++ } ++ // DivineMC end - Math Optimizations ++ + public static boolean isMultipleOf(int number, int multiple) { + return number % multiple == 0; + } diff --git a/net/minecraft/util/RandomSource.java b/net/minecraft/util/RandomSource.java -index 98a54bc4de251014342cda6d0951b7fea79ce553..cd17a4c7f02abf16fcb3b793c10d8b86d47b7974 100644 +index 98a54bc4de251014342cda6d0951b7fea79ce553..663edee4dfa660e3d3a04c728fd764258867916d 100644 --- a/net/minecraft/util/RandomSource.java +++ b/net/minecraft/util/RandomSource.java @@ -12,7 +12,7 @@ public interface RandomSource { @@ -273,7 +407,7 @@ index 98a54bc4de251014342cda6d0951b7fea79ce553..cd17a4c7f02abf16fcb3b793c10d8b86 static RandomSource create() { - return create(RandomSupport.generateUniqueSeed()); -+ return createThreadSafe(); // DivineMC - Some optimizations ++ return createThreadSafe(); // DivineMC - Misc Optimizations } @Deprecated @@ -282,12 +416,12 @@ index 98a54bc4de251014342cda6d0951b7fea79ce553..cd17a4c7f02abf16fcb3b793c10d8b86 static RandomSource create(long seed) { - return new LegacyRandomSource(seed); -+ return new ThreadSafeLegacyRandomSource(seed); // DivineMC - Some optimizations ++ return new ThreadSafeLegacyRandomSource(seed); // DivineMC - Misc Optimizations } static RandomSource createNewThreadLocalInstance() { diff --git a/net/minecraft/util/debugchart/DebugSampleSubscriptionTracker.java b/net/minecraft/util/debugchart/DebugSampleSubscriptionTracker.java -index 15de39fa82c7aea18298509fe9587d027c30cc15..c199f99efe25737602a3565ca6f70177571ff886 100644 +index 15de39fa82c7aea18298509fe9587d027c30cc15..eb534ed5a7478fc632db096328e3582f4ec410b8 100644 --- a/net/minecraft/util/debugchart/DebugSampleSubscriptionTracker.java +++ b/net/minecraft/util/debugchart/DebugSampleSubscriptionTracker.java @@ -15,7 +15,7 @@ public class DebugSampleSubscriptionTracker { @@ -295,12 +429,12 @@ index 15de39fa82c7aea18298509fe9587d027c30cc15..c199f99efe25737602a3565ca6f70177 private final PlayerList playerList; private final EnumMap> subscriptions; - private final Queue subscriptionRequestQueue = new LinkedList<>(); -+ private final java.util.List subscriptionRequestQueue = java.util.Collections.synchronizedList(new LinkedList<>()); // DivineMC - Some optimizations ++ private final java.util.List subscriptionRequestQueue = java.util.Collections.synchronizedList(new LinkedList<>()); // DivineMC - Misc Optimizations public DebugSampleSubscriptionTracker(PlayerList playerList) { this.playerList = playerList; diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java -index 258cb45f1f959b75c1bcdb130811af2f8fddf07d..40641a649631cfe63b0077d66115aaa525377cf0 100644 +index 258cb45f1f959b75c1bcdb130811af2f8fddf07d..9c0e539f09bddac018f93d212e3cdbc446f3c672 100644 --- a/net/minecraft/world/entity/Entity.java +++ b/net/minecraft/world/entity/Entity.java @@ -143,7 +143,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess @@ -308,7 +442,7 @@ index 258cb45f1f959b75c1bcdb130811af2f8fddf07d..40641a649631cfe63b0077d66115aaa5 // Paper start - Share random for entities to make them more random - public static RandomSource SHARED_RANDOM = new RandomRandomSource(); -+ public static RandomSource SHARED_RANDOM = new net.minecraft.world.level.levelgen.ThreadSafeLegacyRandomSource(net.minecraft.world.level.levelgen.RandomSupport.generateUniqueSeed()); // DivineMC - Some optimizations ++ public static RandomSource SHARED_RANDOM = new net.minecraft.world.level.levelgen.ThreadSafeLegacyRandomSource(net.minecraft.world.level.levelgen.RandomSupport.generateUniqueSeed()); // DivineMC - Misc Optimizations // Paper start - replace random private static final class RandomRandomSource extends ca.spottedleaf.moonrise.common.util.ThreadUnsafeRandom { public RandomRandomSource() { @@ -359,7 +493,7 @@ index 5ded2f808a9fcb26856567de6bc56e206f948a84..02d64a5ea756b2c91a71b7a0fc0f2121 // Paper start - Perf: Use array for gamerule storage diff --git a/net/minecraft/world/level/LocalMobCapCalculator.java b/net/minecraft/world/level/LocalMobCapCalculator.java -index 9641219c190261dea0db5f95f040a705ba0a3ff9..91966607f8f48b56e2c7e9389bd7d8acda99a48d 100644 +index 9641219c190261dea0db5f95f040a705ba0a3ff9..a3fccdeb2c076e12b611683da55d45e00a166417 100644 --- a/net/minecraft/world/level/LocalMobCapCalculator.java +++ b/net/minecraft/world/level/LocalMobCapCalculator.java @@ -13,16 +13,24 @@ import net.minecraft.world.entity.MobCategory; @@ -367,7 +501,7 @@ index 9641219c190261dea0db5f95f040a705ba0a3ff9..91966607f8f48b56e2c7e9389bd7d8ac public class LocalMobCapCalculator { private final Long2ObjectMap> playersNearChunk = new Long2ObjectOpenHashMap<>(); - private final Map playerMobCounts = Maps.newHashMap(); -+ private final Map playerMobCounts = Maps.newConcurrentMap(); // DivineMC - Some optimizations ++ private final Map playerMobCounts = Maps.newConcurrentMap(); // DivineMC - Misc Optimizations private final ChunkMap chunkMap; public LocalMobCapCalculator(ChunkMap chunkMap) { @@ -395,28 +529,41 @@ index 9641219c190261dea0db5f95f040a705ba0a3ff9..91966607f8f48b56e2c7e9389bd7d8ac static class MobCounts { - private final Object2IntMap counts = new Object2IntOpenHashMap<>(MobCategory.values().length); -+ private final int[] spawnGroupDensities = new int[MobCategory.values().length]; // DivineMC - Some optimizations ++ private final int[] spawnGroupDensities = new int[MobCategory.values().length]; // DivineMC - Misc Optimizations public void add(MobCategory category) { - this.counts.computeInt(category, (key, value) -> value == null ? 1 : value + 1); -+ this.spawnGroupDensities[category.ordinal()] ++; // DivineMC - Some optimizations ++ this.spawnGroupDensities[category.ordinal()] ++; // DivineMC - Misc Optimizations } public boolean canSpawn(MobCategory category) { - return this.counts.getOrDefault(category, 0) < category.getMaxInstancesPerChunk(); -+ return this.spawnGroupDensities[category.ordinal()] < category.getMaxInstancesPerChunk(); // DivineMC - Some optimizations ++ return this.spawnGroupDensities[category.ordinal()] < category.getMaxInstancesPerChunk(); // DivineMC - Misc Optimizations } } } +diff --git a/net/minecraft/world/level/levelgen/blending/Blender.java b/net/minecraft/world/level/levelgen/blending/Blender.java +index 01e5b29d6e9a5c53c0e23b61ed0c1d7be1a0fe08..d80df05e40f3941ade5ed320e12f8dcf47e6b247 100644 +--- a/net/minecraft/world/level/levelgen/blending/Blender.java ++++ b/net/minecraft/world/level/levelgen/blending/Blender.java +@@ -144,7 +144,7 @@ public class Blender { + private static double heightToOffset(double height) { + double d = 1.0; + double d1 = height + 0.5; +- double d2 = Mth.positiveModulo(d1, 8.0); ++ double d2 = Mth.positiveModuloForPositiveIntegerDenominator(d1, 8.0); // DivineMC - Math optimizations + return 1.0 * (32.0 * (d1 - 128.0) - 3.0 * (d1 - 120.0) * d2 + 3.0 * d2 * d2) / (128.0 * (32.0 - 3.0 * d2)); + } + diff --git a/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java b/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java -index 681dec447486138088fe5f705ef4fadab531139f..c34515e0a2954730665acf429dfec6fc3069e7c4 100644 +index 681dec447486138088fe5f705ef4fadab531139f..12ea268eaec629fde20d55460e618fde3a3e006d 100644 --- a/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java +++ b/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java @@ -198,6 +198,7 @@ public class MapItemSavedData extends SavedData { } } -+ mapItemSavedData.setDirty(false); // DivineMC - Some optimizations ++ mapItemSavedData.setDirty(false); // DivineMC - Misc Optimizations return mapItemSavedData; } @@ -433,3 +580,71 @@ index d9a3b5a2e6495b7e22c114506c2bd1e406f58f8f..a6e03345afd6d8a38e06a43c59103209 private final DataFixer fixerUpper; private final HolderLookup.Provider registries; private final Path dataFolder; +diff --git a/net/minecraft/world/phys/AABB.java b/net/minecraft/world/phys/AABB.java +index c9c6e4e460ad8435f12761704bb9b0284d6aa708..54807bb4b4189ceaded1f78a1a9ab85ce40ab2b1 100644 +--- a/net/minecraft/world/phys/AABB.java ++++ b/net/minecraft/world/phys/AABB.java +@@ -189,13 +189,15 @@ public class AABB { + } + + public AABB intersect(AABB other) { +- double max = Math.max(this.minX, other.minX); +- double max1 = Math.max(this.minY, other.minY); +- double max2 = Math.max(this.minZ, other.minZ); +- double min = Math.min(this.maxX, other.maxX); +- double min1 = Math.min(this.maxY, other.maxY); +- double min2 = Math.min(this.maxZ, other.maxZ); +- return new AABB(max, max1, max2, min, min1, min2); ++ // DivineMC start - Math Optimizations ++ return new AABB( ++ this.minX > other.minX ? this.minX : other.minX, ++ this.minY > other.minY ? this.minY : other.minY, ++ this.minZ > other.minZ ? this.minZ : other.minZ, ++ this.maxX < other.maxX ? this.maxX : other.maxX, ++ this.maxY < other.maxY ? this.maxY : other.maxY, ++ this.maxZ < other.maxZ ? this.maxZ : other.maxZ ++ ); + } + + public AABB minmax(AABB other) { +@@ -227,16 +229,37 @@ public class AABB { + } + + public boolean intersects(AABB other) { +- return this.intersects(other.minX, other.minY, other.minZ, other.maxX, other.maxY, other.maxZ); ++ // DivineMC start - Math Optimizations ++ return this.minX < other.maxX && ++ this.maxX > other.minX && ++ this.minY < other.maxY && ++ this.maxY > other.minY && ++ this.minZ < other.maxZ && ++ this.maxZ > other.minZ; ++ // DivineMC end - Math Optimizations + } + + public boolean intersects(double x1, double y1, double z1, double x2, double y2, double z2) { +- return this.minX < x2 && this.maxX > x1 && this.minY < y2 && this.maxY > y1 && this.minZ < z2 && this.maxZ > z1; ++ // DivineMC start - Math Optimizations ++ return this.minX < x2 && ++ this.maxX > x1 && ++ this.minY < y2 && ++ this.maxY > y1 && ++ this.minZ < z2 && ++ this.maxZ > z1; ++ // DivineMC end - Math Optimizations + } + + public boolean intersects(Vec3 min, Vec3 max) { + return this.intersects( +- Math.min(min.x, max.x), Math.min(min.y, max.y), Math.min(min.z, max.z), Math.max(min.x, max.x), Math.max(min.y, max.y), Math.max(min.z, max.z) ++ // DivineMC start - Math Optimizations ++ min.x < max.x ? min.x : max.x, ++ min.y < max.y ? min.y : max.y, ++ min.z < max.z ? min.z : max.z, ++ min.x > max.x ? min.x : max.x, ++ min.y > max.y ? min.y : max.y, ++ min.z > max.z ? min.z : max.z ++ // DivineMC end - Math Optimizations + ); + } + diff --git a/divinemc-server/minecraft-patches/features/0010-Chunk-System-Optimizations.patch b/divinemc-server/minecraft-patches/features/0010-Chunk-System-Optimizations.patch new file mode 100644 index 0000000..a4b1721 --- /dev/null +++ b/divinemc-server/minecraft-patches/features/0010-Chunk-System-Optimizations.patch @@ -0,0 +1,3408 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Sat, 1 Feb 2025 00:09:39 +0300 +Subject: [PATCH] Chunk System Optimizations + + +diff --git a/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java b/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java +index 1b8193587814225c2ef2c5d9e667436eb50ff6c5..b588449cfe766c14a0cf4ea9640b04a51bbcf433 100644 +--- a/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java ++++ b/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java +@@ -59,12 +59,15 @@ public final class NearbyPlayers { + public static final int GENERAL_REALLY_SMALL_AREA_VIEW_DISTANCE_BLOCKS = (GENERAL_REALLY_SMALL_VIEW_DISTANCE << 4); + + private final ServerLevel world; +- private final Reference2ReferenceOpenHashMap players = new Reference2ReferenceOpenHashMap<>(); +- private final Long2ReferenceOpenHashMap byChunk = new Long2ReferenceOpenHashMap<>(); +- private final Long2ReferenceOpenHashMap>[] directByChunk = new Long2ReferenceOpenHashMap[TOTAL_MAP_TYPES]; ++ // DivineMC start - Chunk System optimization ++ private final Object callbackLock = new Object(); ++ private final it.unimi.dsi.fastutil.objects.Reference2ReferenceMap players = it.unimi.dsi.fastutil.objects.Reference2ReferenceMaps.synchronize(new Reference2ReferenceOpenHashMap<>()); ++ private final it.unimi.dsi.fastutil.longs.Long2ReferenceMap byChunk = it.unimi.dsi.fastutil.longs.Long2ReferenceMaps.synchronize(new Long2ReferenceOpenHashMap<>()); ++ private final it.unimi.dsi.fastutil.longs.Long2ReferenceMap>[] directByChunk = new it.unimi.dsi.fastutil.longs.Long2ReferenceMap[TOTAL_MAP_TYPES]; ++ // DivineMC end - Chunk System optimization + { + for (int i = 0; i < this.directByChunk.length; ++i) { +- this.directByChunk[i] = new Long2ReferenceOpenHashMap<>(); ++ this.directByChunk[i] = it.unimi.dsi.fastutil.longs.Long2ReferenceMaps.synchronize(new Long2ReferenceOpenHashMap<>()); // DivineMC - Chunk System optimization + } + } + +@@ -188,7 +191,10 @@ public final class NearbyPlayers { + final ReferenceList list = this.players[idx]; + if (list == null) { + ++this.nonEmptyLists; +- final ReferenceList players = (this.players[idx] = new ReferenceList<>(EMPTY_PLAYERS_ARRAY)); ++ // DivineMC start - Chunk System optimization ++ this.players[idx] = new ReferenceList<>(EMPTY_PLAYERS_ARRAY); ++ final ReferenceList players = this.players[idx]; ++ // DivineMC end - Chunk System optimization + this.nearbyPlayers.directByChunk[idx].put(this.chunkKey, players); + players.add(player); + return; +diff --git a/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/util/ZeroCollidingReferenceStateTable.java b/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/util/ZeroCollidingReferenceStateTable.java +index 866f38eb0f379ffbe2888023a7d1c290f521a231..08666b4aa1c7663861dc361f60e6f1cc46694521 100644 +--- a/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/util/ZeroCollidingReferenceStateTable.java ++++ b/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/util/ZeroCollidingReferenceStateTable.java +@@ -21,13 +21,15 @@ import net.minecraft.world.level.block.state.properties.Property; + + public final class ZeroCollidingReferenceStateTable { + +- private final Int2ObjectOpenHashMap propertyToIndexer; ++ private final it.unimi.dsi.fastutil.ints.Int2ObjectMap propertyToIndexer; // DivineMC - Chunk System optimization + private S[] lookup; + private final Collection> properties; + + public ZeroCollidingReferenceStateTable(final Collection> properties) { +- this.propertyToIndexer = new Int2ObjectOpenHashMap<>(properties.size()); +- this.properties = new ReferenceArrayList<>(properties); ++ // DivineMC start - Chunk System optimization ++ this.propertyToIndexer = it.unimi.dsi.fastutil.ints.Int2ObjectMaps.synchronize(new Int2ObjectOpenHashMap<>(properties.size())); ++ this.properties = it.unimi.dsi.fastutil.objects.ReferenceLists.synchronize(new ReferenceArrayList<>(properties)); ++ // DivineMC end - Chunk System optimization + + final List> sortedProperties = new ArrayList<>(properties); + +@@ -77,11 +79,11 @@ public final class ZeroCollidingReferenceStateTable { + return ret; + } + +- public boolean isLoaded() { ++ public synchronized boolean isLoaded() { // DivineMC - Chunk System optimization + return this.lookup != null; + } + +- public void loadInTable(final Map, Comparable>, S> universe) { ++ public synchronized void loadInTable(final Map, Comparable>, S> universe) { // DivineMC - Chunk System optimization + if (this.lookup != null) { + throw new IllegalStateException(); + } +@@ -117,7 +119,7 @@ public final class ZeroCollidingReferenceStateTable { + return ((PropertyAccess)property).moonrise$getById((int)modded); + } + +- public > S set(final long index, final Property property, final T with) { ++ public synchronized > S set(final long index, final Property property, final T with) { // DivineMC - Chunk System optimization + final int newValueId = ((PropertyAccess)property).moonrise$getIdFor(with); + if (newValueId < 0) { + return null; +@@ -139,7 +141,7 @@ public final class ZeroCollidingReferenceStateTable { + return this.lookup[(int)newIndex]; + } + +- public > S trySet(final long index, final Property property, final T with, final S dfl) { ++ public synchronized > S trySet(final long index, final Property property, final T with, final S dfl) { // DivineMC - Chunk System optimization + final Indexer indexer = this.propertyToIndexer.get(((PropertyAccess)property).moonrise$getId()); + if (indexer == null) { + return dfl; +diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java b/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java +index 8ef5a1aaac9c27873ce746eb281f77bb318a3c69..886825a10bd06b4b656d19a05624c74f2686feb3 100644 +--- a/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java ++++ b/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java +@@ -189,13 +189,13 @@ public final class RegionizedPlayerChunkLoader { + } + + if (((ChunkSystemServerPlayer)player).moonrise$getChunkLoader() != null) { +- throw new IllegalStateException("Player is already added to player chunk loader"); ++ return; // DivineMC - Chunk System optimization - already loaded + } + + final PlayerChunkLoaderData loader = new PlayerChunkLoaderData(this.world, player); + +- ((ChunkSystemServerPlayer)player).moonrise$setChunkLoader(loader); + loader.add(); ++ player.moonrise$setChunkLoader(loader); // DivineMC - Chunk System optimization + } + + public void updatePlayer(final ServerPlayer player) { +@@ -301,7 +301,7 @@ public final class RegionizedPlayerChunkLoader { + return false; + } + +- public void tick() { ++ public synchronized void tick() { // DivineMC - Chunk System optimization - synchronized + TickThread.ensureTickThread("Cannot tick player chunk loader async"); + long currTime = System.nanoTime(); + for (final ServerPlayer player : new java.util.ArrayList<>(this.world.players())) { +@@ -312,6 +312,7 @@ public final class RegionizedPlayerChunkLoader { + } + loader.update(); // can't invoke plugin logic + loader.updateQueues(currTime); ++ player.connection.resumeFlushing(); // DivineMC - Chunk System optimization + } + } + +@@ -362,7 +363,7 @@ public final class RegionizedPlayerChunkLoader { + GENERATED_TICKET_LEVEL, + TICK_TICKET_LEVEL + }; +- private final Long2ByteOpenHashMap chunkTicketStage = new Long2ByteOpenHashMap(); ++ private final it.unimi.dsi.fastutil.longs.Long2ByteMap chunkTicketStage = it.unimi.dsi.fastutil.longs.Long2ByteMaps.synchronize(new Long2ByteOpenHashMap()); // DivineMC - Chunk System optimization + { + this.chunkTicketStage.defaultReturnValue(CHUNK_TICKET_STAGE_NONE); + } +@@ -384,10 +385,19 @@ public final class RegionizedPlayerChunkLoader { + final int centerX = PlayerChunkLoaderData.this.lastChunkX; + final int centerZ = PlayerChunkLoaderData.this.lastChunkZ; + +- return Integer.compare( +- Math.abs(c1x - centerX) + Math.abs(c1z - centerZ), +- Math.abs(c2x - centerX) + Math.abs(c2z - centerZ) +- ); ++ // DivineMC start - Chunk Loading Priority Optimization ++ if (org.bxteam.divinemc.DivineConfig.chunkTaskPriority == org.bxteam.divinemc.server.chunk.ChunkTaskPriority.EUCLIDEAN_CIRCLE_PATTERN) { ++ return Integer.compare( ++ (c1x - centerX) * (c1x - centerX) + (c1z - centerZ) * (c1z - centerZ), ++ (c2x - centerX) * (c2x - centerX) + (c2z - centerZ) * (c2z - centerZ) ++ ); ++ } else { ++ return Integer.compare( ++ Math.abs(c1x - centerX) + Math.abs(c1z - centerZ), ++ Math.abs(c2x - centerX) + Math.abs(c2z - centerZ) ++ ); ++ } ++ // DivineMC end - Chunk Loading Priority Optimization + }; + private final LongHeapPriorityQueue sendQueue = new LongHeapPriorityQueue(CLOSEST_MANHATTAN_DIST); + private final LongHeapPriorityQueue tickingQueue = new LongHeapPriorityQueue(CLOSEST_MANHATTAN_DIST); +@@ -490,7 +500,7 @@ public final class RegionizedPlayerChunkLoader { + } + + @Override +- protected void removeCallback(final PlayerChunkLoaderData parameter, final int chunkX, final int chunkZ) { ++ protected synchronized void removeCallback(final PlayerChunkLoaderData parameter, final int chunkX, final int chunkZ) { // DivineMC - Chunk System optimization + final long chunk = CoordinateUtils.getChunkKey(chunkX, chunkZ); + // note: by the time this is called, the tick cleanup should have ran - so, if the chunk is at + // the tick stage it was deemed in range for loading. Thus, we need to move it to generated +@@ -624,7 +634,7 @@ public final class RegionizedPlayerChunkLoader { + return Math.max(Math.abs(dx), Math.abs(dz)) <= this.lastTickDistance; + } + +- private boolean areNeighboursGenerated(final int chunkX, final int chunkZ, final int radius) { ++ private synchronized boolean areNeighboursGenerated(final int chunkX, final int chunkZ, final int radius) { // DivineMC - Chunk System optimization + for (int dz = -radius; dz <= radius; ++dz) { + for (int dx = -radius; dx <= radius; ++dx) { + if ((dx | dz) == 0) { +@@ -643,10 +653,10 @@ public final class RegionizedPlayerChunkLoader { + return true; + } + +- void updateQueues(final long time) { ++ synchronized void updateQueues(final long time) { // DivineMC - Chunk System optimization + TickThread.ensureTickThread(this.player, "Cannot tick player chunk loader async"); + if (this.removed) { +- throw new IllegalStateException("Ticking removed player chunk loader"); ++ return; // DivineMC - Chunk System optimization - just return + } + // update rate limits + final double loadRate = this.getMaxChunkLoadRate(); +@@ -910,10 +920,10 @@ public final class RegionizedPlayerChunkLoader { + ); + } + +- void update() { ++ synchronized void update() { // DivineMC - Chunk System optimization + TickThread.ensureTickThread(this.player, "Cannot update player asynchronously"); + if (this.removed) { +- throw new IllegalStateException("Updating removed player chunk loader"); ++ return; // DivineMC - Chunk System optimization - just return + } + final ViewDistances playerDistances = ((ChunkSystemServerPlayer)this.player).moonrise$getViewDistanceHolder().getViewDistances(); + final ViewDistances worldDistances = ((ChunkSystemServerLevel)this.world).moonrise$getViewDistanceHolder().getViewDistances(); +@@ -1062,7 +1072,7 @@ public final class RegionizedPlayerChunkLoader { + this.flushDelayedTicketOps(); + } + +- void remove() { ++ synchronized void remove() { // DivineMC - Chunk System optimization + TickThread.ensureTickThread(this.player, "Cannot add player asynchronously"); + if (this.removed) { + throw new IllegalStateException("Removing removed player chunk loader"); +@@ -1090,7 +1100,7 @@ public final class RegionizedPlayerChunkLoader { + } + + public LongOpenHashSet getSentChunksRaw() { +- return this.sentChunks; ++ return new LongOpenHashSet(this.sentChunks); // DivineMC - Chunk System optimization + } + } + } +diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java +index 5683395e09a65b1b39748df5152fffef630ac083..6aea99f071bd1a6a1ea9507bb70739d56318eb22 100644 +--- a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java ++++ b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java +@@ -71,36 +71,49 @@ public final class ChunkHolderManager { + private static final long PROBE_MARKER = Long.MIN_VALUE + 1; + public final ReentrantAreaLock ticketLockArea; + +- private final ConcurrentLong2ReferenceChainedHashTable>> tickets = new ConcurrentLong2ReferenceChainedHashTable<>(); +- private final ConcurrentLong2ReferenceChainedHashTable sectionToChunkToExpireCount = new ConcurrentLong2ReferenceChainedHashTable<>(); ++ // DivineMC start - Chunk System optimization ++ private final ConcurrentLong2ReferenceChainedHashTable>> tickets = ConcurrentLong2ReferenceChainedHashTable.createWithCapacity(20, 0.9F); ++ private final ConcurrentLong2ReferenceChainedHashTable sectionToChunkToExpireCount = ConcurrentLong2ReferenceChainedHashTable.createWithCapacity(20, 0.9F); ++ // DivineMC end - Chunk System optimization + final ChunkUnloadQueue unloadQueue; + +- private final ConcurrentLong2ReferenceChainedHashTable chunkHolders = ConcurrentLong2ReferenceChainedHashTable.createWithCapacity(16384, 0.25f); ++ private final ConcurrentLong2ReferenceChainedHashTable chunkHolders = ConcurrentLong2ReferenceChainedHashTable.createWithCapacity(20, 0.9F); // DivineMC - Chunk System optimization + private final ServerLevel world; + private final ChunkTaskScheduler taskScheduler; + private long currentTick; + +- private final ArrayDeque pendingFullLoadUpdate = new ArrayDeque<>(); +- private final ObjectRBTreeSet autoSaveQueue = new ObjectRBTreeSet<>((final NewChunkHolder c1, final NewChunkHolder c2) -> { +- if (c1 == c2) { +- return 0; +- } ++ // DivineMC start - Chunk System optimization ++ public static class LevelHolderData { ++ private final java.util.concurrent.ConcurrentLinkedDeque pendingFullLoadUpdate = new java.util.concurrent.ConcurrentLinkedDeque<>(); ++ private final ObjectRBTreeSet autoSaveQueue = new ObjectRBTreeSet<>((final NewChunkHolder c1, final NewChunkHolder c2) -> { ++ if (c1 == c2) { ++ return 0; ++ } + +- final int saveTickCompare = Long.compare(c1.lastAutoSave, c2.lastAutoSave); ++ final int saveTickCompare = Long.compare(c1.lastAutoSave, c2.lastAutoSave); + +- if (saveTickCompare != 0) { +- return saveTickCompare; +- } ++ if (saveTickCompare != 0) { ++ return saveTickCompare; ++ } + +- final long coord1 = CoordinateUtils.getChunkKey(c1.chunkX, c1.chunkZ); +- final long coord2 = CoordinateUtils.getChunkKey(c2.chunkX, c2.chunkZ); ++ final long coord1 = CoordinateUtils.getChunkKey(c1.chunkX, c1.chunkZ); ++ final long coord2 = CoordinateUtils.getChunkKey(c2.chunkX, c2.chunkZ); + +- if (coord1 == coord2) { +- throw new IllegalStateException("Duplicate chunkholder in auto save queue"); +- } ++ if (coord1 == coord2) { ++ throw new IllegalStateException("Duplicate chunkholder in auto save queue"); ++ } + +- return Long.compare(coord1, coord2); +- }); ++ return Long.compare(coord1, coord2); ++ }); ++ } ++ ++ public LevelHolderData getData() { ++ if (this.world == null) { ++ throw new RuntimeException("World was null!"); ++ } ++ return world.chunkHolderData; ++ } ++ // DivineMC end - Chunk System optimization + + public ChunkHolderManager(final ServerLevel world, final ChunkTaskScheduler taskScheduler) { + this.world = world; +@@ -222,26 +235,29 @@ public final class ChunkHolderManager { + this.taskScheduler.setShutdown(true); + } + +- void ensureInAutosave(final NewChunkHolder holder) { +- if (!this.autoSaveQueue.contains(holder)) { ++ // DivineMC start - Chunk System optimization ++ synchronized void ensureInAutosave(final NewChunkHolder holder) { ++ final LevelHolderData data = getData(); ++ if (!data.autoSaveQueue.contains(holder)) { + holder.lastAutoSave = this.currentTick; +- this.autoSaveQueue.add(holder); ++ data.autoSaveQueue.add(holder); + } + } + +- public void autoSave() { ++ public synchronized void autoSave() { ++ final LevelHolderData data = getData(); + final List reschedule = new ArrayList<>(); + final long currentTick = this.currentTick; + final long maxSaveTime = currentTick - Math.max(1L, PlatformHooks.get().configAutoSaveInterval(this.world)); + final int maxToSave = PlatformHooks.get().configMaxAutoSavePerTick(this.world); +- for (int autoSaved = 0; autoSaved < maxToSave && !this.autoSaveQueue.isEmpty();) { +- final NewChunkHolder holder = this.autoSaveQueue.first(); ++ for (int autoSaved = 0; autoSaved < maxToSave && !data.autoSaveQueue.isEmpty();) { ++ final NewChunkHolder holder = data.autoSaveQueue.first(); + + if (holder.lastAutoSave > maxSaveTime) { + break; + } + +- this.autoSaveQueue.remove(holder); ++ data.autoSaveQueue.remove(holder); + + holder.lastAutoSave = currentTick; + if (holder.save(false) != null) { +@@ -255,10 +271,11 @@ public final class ChunkHolderManager { + + for (final NewChunkHolder holder : reschedule) { + if (holder.getChunkStatus().isOrAfter(FullChunkStatus.FULL)) { +- this.autoSaveQueue.add(holder); ++ data.autoSaveQueue.add(holder); + } + } + } ++ // DivineMC end - Chunk System optimization + + public void saveAllChunks(final boolean flush, final boolean shutdown, final boolean logProgress) { + final List holders = this.getChunkHolders(); +@@ -317,13 +334,9 @@ public final class ChunkHolderManager { + } + if (logProgress) { + final long currTime = System.nanoTime(); +- if ((currTime - lastLog) > TimeUnit.SECONDS.toNanos(10L)) { ++ if ((currTime - lastLog) > TimeUnit.SECONDS.toNanos(5L)) { // DivineMC - Log a bit more frequently + lastLog = currTime; +- LOGGER.info( +- "Saved " + savedChunk + " block chunks, " + savedEntity + " entity chunks, " + savedPoi +- + " poi chunks in world '" + WorldUtil.getWorldName(this.world) + "', progress: " +- + format.format((double)(i+1)/(double)len * 100.0) +- ); ++ LOGGER.info("Saved {} block chunks, {} entity chunks, {} poi chunks in world '{}', progress: {}", savedChunk, savedEntity, savedPoi, ca.spottedleaf.moonrise.common.util.WorldUtil.getWorldName(this.world), format.format((double) (i + 1) / (double) len * 100.0)); // DivineMC - Beautify log + } + } + } +@@ -425,8 +438,8 @@ public final class ChunkHolderManager { + final Long2ObjectOpenHashMap>> ret = new Long2ObjectOpenHashMap<>(); + final Long2ObjectOpenHashMap sections = new Long2ObjectOpenHashMap<>(); + final int sectionShift = this.taskScheduler.getChunkSystemLockShift(); +- for (final PrimitiveIterator.OfLong iterator = this.tickets.keyIterator(); iterator.hasNext();) { +- final long coord = iterator.nextLong(); ++ for (final Iterator iterator = this.tickets.keyIterator(); iterator.hasNext();) { // DivineMC - Chunk System optimization ++ final long coord = iterator.next(); // DivineMC - Chunk System optimization + sections.computeIfAbsent( + CoordinateUtils.getChunkKey( + CoordinateUtils.getChunkX(coord) >> sectionShift, +@@ -523,7 +536,7 @@ public final class ChunkHolderManager { + chunkZ >> sectionShift + ); + +- this.sectionToChunkToExpireCount.computeIfAbsent(sectionKey, (final long keyInMap) -> { ++ this.sectionToChunkToExpireCount.computeIfAbsent(sectionKey, (keyInMap) -> { // DivineMC - Chunk System optimization + return new Long2IntOpenHashMap(); + }).addTo(chunkKey, 1); + } +@@ -567,7 +580,7 @@ public final class ChunkHolderManager { + + final ReentrantAreaLock.Node ticketLock = lock ? this.ticketLockArea.lock(chunkX, chunkZ) : null; + try { +- final SortedArraySet> ticketsAtChunk = this.tickets.computeIfAbsent(chunk, (final long keyInMap) -> { ++ final SortedArraySet> ticketsAtChunk = this.tickets.computeIfAbsent(chunk, (keyInMap) -> { // DivineMC - Chunk System optimization + return SortedArraySet.create(4); + }); + +@@ -697,8 +710,8 @@ public final class ChunkHolderManager { + + final Long2ObjectOpenHashMap sections = new Long2ObjectOpenHashMap<>(); + final int sectionShift = this.taskScheduler.getChunkSystemLockShift(); +- for (final PrimitiveIterator.OfLong iterator = this.tickets.keyIterator(); iterator.hasNext();) { +- final long coord = iterator.nextLong(); ++ for (final Iterator iterator = this.tickets.keyIterator(); iterator.hasNext();) { // DivineMC - Chunk System optimization ++ final long coord = iterator.next(); // DivineMC - Chunk System optimization + sections.computeIfAbsent( + CoordinateUtils.getChunkKey( + CoordinateUtils.getChunkX(coord) >> sectionShift, +@@ -746,8 +759,8 @@ public final class ChunkHolderManager { + return removeDelay <= 0L; + }; + +- for (final PrimitiveIterator.OfLong iterator = this.sectionToChunkToExpireCount.keyIterator(); iterator.hasNext();) { +- final long sectionKey = iterator.nextLong(); ++ for (final Iterator iterator = this.sectionToChunkToExpireCount.keyIterator(); iterator.hasNext();) { // DivineMC - Chunk System optimization ++ final long sectionKey = iterator.next(); // DivineMC - Chunk System optimization + + if (!this.sectionToChunkToExpireCount.containsKey(sectionKey)) { + // removed concurrently +@@ -1033,7 +1046,7 @@ public final class ChunkHolderManager { + } + if (org.bxteam.divinemc.DivineConfig.enableParallelWorldTicking && !TickThread.isTickThreadFor(world)) { // DivineMC - Parallel world ticking + this.taskScheduler.scheduleChunkTask(() -> { +- final ArrayDeque pendingFullLoadUpdate = ChunkHolderManager.this.pendingFullLoadUpdate; ++ final java.util.Deque pendingFullLoadUpdate = ChunkHolderManager.this.getData().pendingFullLoadUpdate; // DivineMC - Chunk System optimization + for (int i = 0, len = changedFullStatus.size(); i < len; ++i) { + pendingFullLoadUpdate.add(changedFullStatus.get(i)); + } +@@ -1041,16 +1054,16 @@ public final class ChunkHolderManager { + ChunkHolderManager.this.processPendingFullUpdate(); + }, Priority.HIGHEST); + } else { +- final ArrayDeque pendingFullLoadUpdate = this.pendingFullLoadUpdate; ++ final java.util.Deque pendingFullLoadUpdate = this.getData().pendingFullLoadUpdate; // DivineMC - Chunk System optimization + for (int i = 0, len = changedFullStatus.size(); i < len; ++i) { + pendingFullLoadUpdate.add(changedFullStatus.get(i)); + } + } + } + +- private void removeChunkHolder(final NewChunkHolder holder) { ++ private synchronized void removeChunkHolder(final NewChunkHolder holder) { // DivineMC - Chunk System optimization + holder.onUnload(); +- this.autoSaveQueue.remove(holder); ++ this.getData().autoSaveQueue.remove(holder); // DivineMC - Chunk System optimization + PlatformHooks.get().onChunkHolderDelete(this.world, holder.vanillaChunkHolder); + this.chunkHolders.remove(CoordinateUtils.getChunkKey(holder.chunkX, holder.chunkZ)); + } +@@ -1214,6 +1227,27 @@ public final class ChunkHolderManager { + } + } + ++ // DivineMC start - Chunk System optimization ++ public final java.util.Set blockTickingChunkHolders = java.util.Collections.synchronizedSet(new org.agrona.collections.ObjectHashSet<>(10, 0.88f, true)); ++ public final java.util.Set entityTickingChunkHolders = java.util.Collections.synchronizedSet(new org.agrona.collections.ObjectHashSet<>(10, 0.88f, true)); ++ ++ public void markBlockTicking(@org.jetbrains.annotations.NotNull NewChunkHolder newChunkHolder) { ++ this.blockTickingChunkHolders.add(newChunkHolder.getCachedLongPos()); ++ } ++ ++ public void markNonBlockTickingIfPossible(@org.jetbrains.annotations.NotNull NewChunkHolder newChunkHolder) { ++ this.blockTickingChunkHolders.remove(newChunkHolder.getCachedLongPos()); ++ } ++ ++ public void markEntityTicking(@org.jetbrains.annotations.NotNull NewChunkHolder newChunkHolder) { ++ this.entityTickingChunkHolders.add(newChunkHolder.getCachedLongPos()); ++ } ++ ++ public void markNonEntityTickingIfPossible(@org.jetbrains.annotations.NotNull NewChunkHolder newChunkHolder) { ++ this.entityTickingChunkHolders.remove(newChunkHolder.getCachedLongPos()); ++ } ++ // DivineMC end - Chunk System optimization ++ + public enum TicketOperationType { + ADD, REMOVE, ADD_IF_REMOVED, ADD_AND_REMOVE + } +@@ -1387,7 +1421,7 @@ public final class ChunkHolderManager { + + // only call on tick thread + private boolean processPendingFullUpdate() { +- final ArrayDeque pendingFullLoadUpdate = this.pendingFullLoadUpdate; ++ final java.util.Deque pendingFullLoadUpdate = this.getData().pendingFullLoadUpdate; // DivineMC - Chunk System optimization + + boolean ret = false; + +@@ -1423,8 +1457,7 @@ public final class ChunkHolderManager { + final JsonArray allTicketsJson = new JsonArray(); + ret.add("tickets", allTicketsJson); + +- for (final Iterator>>> iterator = this.tickets.entryIterator(); +- iterator.hasNext();) { ++ for (final Iterator>>> iterator = this.tickets.entryIterator(); iterator.hasNext();) { // DivineMC - Chunk System optimization + 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..b5057ffac429ea2d1910082a83c13a5b7dc550c1 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 { + } + + public final ChunkHolder vanillaChunkHolder; ++ // DivineMC start - Chunk System optimization ++ private final long cachedLongPos; ++ ++ public long getCachedLongPos() { ++ return cachedLongPos; ++ } ++ // DivineMC end - Chunk System optimization + + public NewChunkHolder(final ServerLevel world, final int chunkX, final int chunkZ, final ChunkTaskScheduler scheduler) { + this.world = world; + this.chunkX = chunkX; + this.chunkZ = chunkZ; ++ this.cachedLongPos = ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(this.chunkX, this.chunkZ); // DivineMC - Chunk System optimization + this.scheduler = scheduler; + this.vanillaChunkHolder = new ChunkHolder( + new ChunkPos(chunkX, chunkZ), ChunkHolderManager.MAX_TICKET_LEVEL, world, +@@ -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 +- private UnloadTask chunkDataUnload; +- private UnloadTask entityDataUnload; +- private UnloadTask poiDataUnload; ++ // DivineMC start - Chunk System optimization ++ private volatile UnloadTask chunkDataUnload; ++ private volatile UnloadTask entityDataUnload; ++ private volatile UnloadTask poiDataUnload; ++ // DivineMC end - Chunk System optimization + + public static final record UnloadTask(CallbackCompletable completable, PrioritisedExecutor.PrioritisedTask task, +- 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) { +@@ -877,7 +887,11 @@ public final class NewChunkHolder { + MoonriseRegionFileIO.scheduleSave(this.world, this.chunkX, this.chunkZ, data, type); + } + +- this.getUnloadTask(type).completable().complete(data); ++ // DivineMC start - Chunk System optimization ++ UnloadTask task = this.getUnloadTask(type); ++ if (task == null) return; ++ task.completable().complete(data); ++ // DivineMC end - Chunk System optimization + final ReentrantAreaLock.Node schedulingLock = this.scheduler.schedulingLockArea.lock(this.chunkX, this.chunkZ); + try { + // can only write to these fields while holding the schedule lock +@@ -1190,6 +1204,7 @@ public final class NewChunkHolder { + for (int dz = -NEIGHBOUR_RADIUS; dz <= NEIGHBOUR_RADIUS; ++dz) { + for (int dx = -NEIGHBOUR_RADIUS; dx <= NEIGHBOUR_RADIUS; ++dx) { + final NewChunkHolder holder = (dx | dz) == 0 ? this : this.scheduler.chunkHolderManager.getChunkHolder(dx + this.chunkX, dz + this.chunkZ); ++ if (holder == null) continue; // DivineMC - Chunk System optimization + if (loaded) { + if (holder.setNeighbourFullLoaded(-dx, -dz)) { + changedFullStatus.add(holder); +@@ -1214,6 +1229,19 @@ public final class NewChunkHolder { + + private void updateCurrentState(final FullChunkStatus to) { + this.currentFullChunkStatus = to; ++ // DivineMC start - Chunk System optimization ++ if (to.isOrAfter(FullChunkStatus.BLOCK_TICKING)) { ++ this.world.moonrise$getChunkTaskScheduler().chunkHolderManager.markBlockTicking(this); ++ } else { ++ this.world.moonrise$getChunkTaskScheduler().chunkHolderManager.markNonBlockTickingIfPossible(this); ++ } ++ ++ if (to.isOrAfter(FullChunkStatus.ENTITY_TICKING)) { ++ this.world.moonrise$getChunkTaskScheduler().chunkHolderManager.markEntityTicking(this); ++ } else { ++ this.world.moonrise$getChunkTaskScheduler().chunkHolderManager.markNonEntityTickingIfPossible(this); ++ } ++ // DivineMC end - Chunk System optimization + } + + // only to be called on the main thread, no locks need to be held +@@ -1348,11 +1376,11 @@ public final class NewChunkHolder { + return this.requestedGenStatus; + } + +- private final Reference2ObjectOpenHashMap>> statusWaiters = new Reference2ObjectOpenHashMap<>(); ++ private final Map>> statusWaiters = new java.util.concurrent.ConcurrentHashMap<>(); // DivineMC - Chunk System optimization + + void addStatusConsumer(final ChunkStatus status, final Consumer consumer) { + this.statusWaiters.computeIfAbsent(status, (final ChunkStatus keyInMap) -> { +- return new ArrayList<>(4); ++ return new java.util.concurrent.CopyOnWriteArrayList<>(); // DivineMC - Chunk System optimization + }).add(consumer); + } + +@@ -1394,11 +1422,11 @@ public final class NewChunkHolder { + }, Priority.HIGHEST); + } + +- private final Reference2ObjectOpenHashMap>> fullStatusWaiters = new Reference2ObjectOpenHashMap<>(); ++ private final Map>> fullStatusWaiters = new java.util.concurrent.ConcurrentHashMap<>(); // DivineMC - Chunk System optimization + + void addFullStatusConsumer(final FullChunkStatus status, final Consumer consumer) { + this.fullStatusWaiters.computeIfAbsent(status, (final FullChunkStatus keyInMap) -> { +- return new ArrayList<>(4); ++ return new java.util.concurrent.CopyOnWriteArrayList<>(); // DivineMC - Chunk System optimization + }).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 ++++ b/ca/spottedleaf/moonrise/patches/chunk_tick_iteration/ChunkTickConstants.java +@@ -2,6 +2,6 @@ package ca.spottedleaf.moonrise.patches.chunk_tick_iteration; + + public final class ChunkTickConstants { + +- public static final int PLAYER_SPAWN_TRACK_RANGE = 8; ++ public static final int PLAYER_SPAWN_TRACK_RANGE = (int) Math.round(org.bxteam.divinemc.DivineConfig.playerNearChunkDetectionRange / 16.0); // DivineMC - Chunk System optimization + + } +diff --git a/ca/spottedleaf/moonrise/patches/starlight/light/SWMRNibbleArray.java b/ca/spottedleaf/moonrise/patches/starlight/light/SWMRNibbleArray.java +index 4ca68a903e67606fc4ef0bfa9862a73797121c8b..f94f443f862611f039454d1dc8ff2a4ba5f081d3 100644 +--- a/ca/spottedleaf/moonrise/patches/starlight/light/SWMRNibbleArray.java ++++ b/ca/spottedleaf/moonrise/patches/starlight/light/SWMRNibbleArray.java +@@ -325,7 +325,7 @@ public final class SWMRNibbleArray { + } + + // operation type: updating +- public boolean updateVisible() { ++ public synchronized boolean updateVisible() { // DivineMC - Chunk System optimization + 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/ChunkMap.java b/net/minecraft/server/level/ChunkMap.java +index 965899a98223b15bd770378c202873cbf15b714d..dccf4d4b1067e1b09e38cabeae82185929494b62 100644 +--- a/net/minecraft/server/level/ChunkMap.java ++++ b/net/minecraft/server/level/ChunkMap.java +@@ -127,8 +127,8 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + public final AtomicInteger tickingGenerated = new AtomicInteger(); // Paper - public + private final String storageName; + private final PlayerMap playerMap = new PlayerMap(); +- public final Int2ObjectMap entityMap = new Int2ObjectOpenHashMap<>(); +- private final Long2ByteMap chunkTypeCache = new Long2ByteOpenHashMap(); ++ public final Int2ObjectMap entityMap = new org.bxteam.divinemc.util.map.Int2ObjectConcurrentHashMap<>(); // DivineMC - Chunk System Optimizations ++ private final Long2ByteMap chunkTypeCache = it.unimi.dsi.fastutil.longs.Long2ByteMaps.synchronize(new Long2ByteOpenHashMap()); // DivineMC - Chunk System Optimizations + // Paper - rewrite chunk system + public int serverViewDistance; + public final WorldGenContext worldGenContext; // Paper - public +@@ -249,7 +249,11 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + + final ServerPlayer[] backingSet = inRange.getRawDataUnchecked(); + for (int i = 0, len = inRange.size(); i < len; i++) { +- ++(backingSet[i].mobCounts[index]); ++ // DivineMC start - Chunk System Optimizations ++ ServerPlayer player = backingSet[i]; ++ if (player == null) continue; ++ ++(player.mobCounts[index]); ++ // DivineMC end - Chunk System Optimizations + } + } + +@@ -266,7 +270,11 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + } + final ServerPlayer[] backingSet = inRange.getRawDataUnchecked(); + for (int i = 0, len = inRange.size(); i < len; i++) { +- ++(backingSet[i].mobBackoffCounts[idx]); ++ // DivineMC start - Chunk System Optimizations ++ ServerPlayer player = backingSet[i]; ++ if (player == null) continue; ++ ++(player.mobBackoffCounts[idx]); ++ // DivineMC end - Chunk System Optimizations + } + } + // Paper end - per player mob count backoff +@@ -720,27 +728,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + return false; + } + +- final ServerPlayer[] raw = players.getRawDataUnchecked(); +- final int len = players.size(); +- +- Objects.checkFromIndexSize(0, len, raw.length); +- for (int i = 0; i < len; ++i) { +- final ServerPlayer serverPlayer = raw[i]; +- // Paper start - PlayerNaturallySpawnCreaturesEvent +- com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent event; +- blockRange = 16384.0D; +- if (reducedRange) { +- event = serverPlayer.playerNaturallySpawnedEvent; +- if (event == null || event.isCancelled()) continue; +- blockRange = (double) ((event.getSpawnRadius() << 4) * (event.getSpawnRadius() << 4)); +- } +- // Paper end - PlayerNaturallySpawnCreaturesEvent +- if (this.playerIsCloseEnoughForSpawning(serverPlayer, chunkPos, blockRange)) { +- return true; +- } +- } +- +- return false; ++ return !players.isEmpty(); // DivineMC - Chunk System Optimizations + // Paper end - chunk tick iteration optimisation + } + +@@ -758,10 +746,10 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + final ServerPlayer[] raw = players.getRawDataUnchecked(); + final int len = players.size(); + +- Objects.checkFromIndexSize(0, len, raw.length); +- for (int i = 0; i < len; ++i) { ++ for (int i = 0; i < raw.length; ++i) { // DivineMC - Chunk System Optimizations + final ServerPlayer player = raw[i]; +- if (this.playerIsCloseEnoughForSpawning(player, chunkPos, 16384.0D)) { // Spigot ++ if (player == null) continue; // DivineMC - Chunk System Optimizations ++ if (this.playerIsCloseEnoughForSpawning(player, chunkPos, (org.bxteam.divinemc.DivineConfig.playerNearChunkDetectionRange^2))) { // Spigot // DivineMC - Chunk System Optimizations + if (ret == null) { + ret = new ArrayList<>(len - i); + ret.add(player); +@@ -1146,6 +1134,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + } else { + for (int i = 0, len = players.size(); i < len; ++i) { + final ServerPlayer player = playersRaw[i]; ++ if (player == null) continue; // DivineMC - Chunk System Optimizations + this.updatePlayer(player); + } + +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 ++++ b/net/minecraft/server/level/DistanceManager.java +@@ -178,15 +178,13 @@ public abstract class DistanceManager implements ca.spottedleaf.moonrise.patches + + public boolean inEntityTickingRange(long chunkPos) { + // Paper start - rewrite chunk system +- final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder chunkHolder = this.moonrise$getChunkHolderManager().getChunkHolder(chunkPos); +- return chunkHolder != null && chunkHolder.isEntityTickingReady(); ++ return this.moonrise$getChunkHolderManager().entityTickingChunkHolders.contains(chunkPos); // DivineMC - Chunk System optimization + // Paper end - rewrite chunk system + } + + public boolean inBlockTickingRange(long chunkPos) { + // Paper start - rewrite chunk system +- final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder chunkHolder = this.moonrise$getChunkHolderManager().getChunkHolder(chunkPos); +- return chunkHolder != null && chunkHolder.isTickingReady(); ++ return this.moonrise$getChunkHolderManager().blockTickingChunkHolders.contains(chunkPos); // DivineMC - Chunk System optimization + // Paper end - rewrite chunk system + } + +diff --git a/net/minecraft/server/level/ServerChunkCache.java b/net/minecraft/server/level/ServerChunkCache.java +index ab30af9cd58ff7310e05be87b08f42bacf69e11e..ae0e36d198ad8243920c8e8a55c0be4945542763 100644 +--- a/net/minecraft/server/level/ServerChunkCache.java ++++ b/net/minecraft/server/level/ServerChunkCache.java +@@ -439,8 +439,7 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon + + public boolean isPositionTicking(long chunkPos) { + // Paper start - rewrite chunk system +- final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder newChunkHolder = ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(chunkPos); +- return newChunkHolder != null && newChunkHolder.isTickingReady(); ++ return ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getChunkTaskScheduler().chunkHolderManager.blockTickingChunkHolders.contains(chunkPos); // DivineMC - Chunk System optimization + // Paper end - rewrite chunk system + } + +diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java +index 52ba052fc1ff2a35786570c282a7de4e9dff99f5..9298bdca9a653622f3625190e875e7b6c5e40023 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 + public final ServerChunkCache chunkSource; + private final MinecraftServer server; + public final net.minecraft.world.level.storage.PrimaryLevelData serverLevelData; // CraftBukkit - type ++ public final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkHolderManager.LevelHolderData chunkHolderData; // DivineMC - Chunk System optimization + private int lastSpawnChunkRadius; + final EntityTickList entityTickList = new EntityTickList(this); // DivineMC - Parallel world ticking + // Paper - rewrite chunk system +@@ -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); ++ this.chunkHolderData = new ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkHolderManager.LevelHolderData(); // DivineMC - Chunk System optimization + 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"), +@@ -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 +- final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder holder = this.moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(chunkPos); +- return holder != null && holder.isTickingReady(); ++ return this.moonrise$getChunkTaskScheduler().chunkHolderManager.blockTickingChunkHolders.contains(chunkPos); // DivineMC - Chunk System optimization + // Paper end - rewrite chunk system + } + +@@ -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(); +- final int minSection = ca.spottedleaf.moonrise.common.util.WorldUtil.getMinSection((ServerLevel)(Object)this); ++ final int minSection = ca.spottedleaf.moonrise.common.util.WorldUtil.getMinSection(this); // DivineMC - Chunk System optimization + final ca.spottedleaf.moonrise.common.util.SimpleThreadUnsafeRandom simpleRandom = this.simpleRandom; + final boolean doubleTickFluids = !ca.spottedleaf.moonrise.common.PlatformHooks.get().configFixMC224294(); + +@@ -897,42 +900,41 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + final int offsetX = cpos.x << 4; + final int offsetZ = cpos.z << 4; + ++ // DivineMC start - Chunk System optimization + for (int sectionIndex = 0, sectionsLen = sections.length; sectionIndex < sectionsLen; sectionIndex++) { +- final int offsetY = (sectionIndex + minSection) << 4; + final LevelChunkSection section = sections[sectionIndex]; +- final net.minecraft.world.level.chunk.PalettedContainer states = section.states; + if (!section.isRandomlyTickingBlocks()) { + continue; + } ++ final int offsetY = (sectionIndex + minSection) << 4; ++ final net.minecraft.world.level.chunk.PalettedContainer states = section.states; + +- final ca.spottedleaf.moonrise.common.list.ShortList tickList = ((ca.spottedleaf.moonrise.patches.block_counting.BlockCountingChunkSection)section).moonrise$getTickingBlockList(); ++ final ca.spottedleaf.moonrise.common.list.ShortList tickList = section.moonrise$getTickingBlockList(); + + for (int i = 0; i < tickSpeed; ++i) { +- final int tickingBlocks = tickList.size(); + final int index = simpleRandom.nextInt() & ((16 * 16 * 16) - 1); + +- if (index >= tickingBlocks) { ++ if (index >= tickList.size()) { + // most of the time we fall here + continue; + } + +- final int location = (int)tickList.getRaw(index) & 0xFFFF; ++ final int location = tickList.getRaw(index); + final BlockState state = states.get(location); + + // do not use a mutable pos, as some random tick implementations store the input without calling immutable()! +- final BlockPos pos = new BlockPos((location & 15) | offsetX, ((location >>> (4 + 4)) & 15) | offsetY, ((location >>> 4) & 15) | offsetZ); ++ final BlockPos pos = new BlockPos((location & 15) | offsetX, (location >>> (4 + 4)) | offsetY, ((location >>> 4) & 15) | offsetZ); + +- state.randomTick((ServerLevel)(Object)this, pos, simpleRandom); ++ state.randomTick(this, pos, simpleRandom); + if (doubleTickFluids) { + final FluidState fluidState = state.getFluidState(); + if (fluidState.isRandomlyTicking()) { +- fluidState.randomTick((ServerLevel)(Object)this, pos, simpleRandom); ++ fluidState.randomTick(this, pos, simpleRandom); + } + } + } + } +- +- return; ++ // DivineMC end - Chunk System optimization + } + // Paper end - optimise random ticking + +@@ -2557,30 +2559,25 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + + private boolean isPositionTickingWithEntitiesLoaded(long chunkPos) { + // Paper start - rewrite chunk system +- final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder chunkHolder = this.moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(chunkPos); +- // isTicking implies the chunk is loaded, and the chunk is loaded now implies the entities are loaded +- return chunkHolder != null && chunkHolder.isTickingReady(); ++ return this.moonrise$getChunkTaskScheduler().chunkHolderManager.blockTickingChunkHolders.contains(chunkPos); // DivineMC - Chunk System optimization + // Paper end - rewrite chunk system + } + + public boolean isPositionEntityTicking(BlockPos pos) { + // Paper start - rewrite chunk system +- final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder chunkHolder = this.moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(pos)); +- return chunkHolder != null && chunkHolder.isEntityTickingReady(); ++ return this.moonrise$getChunkTaskScheduler().chunkHolderManager.entityTickingChunkHolders.contains(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(pos)); // DivineMC - Chunk System optimization + // Paper end - rewrite chunk system + } + + public boolean isNaturalSpawningAllowed(BlockPos pos) { + // Paper start - rewrite chunk system +- final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder chunkHolder = this.moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(pos)); +- return chunkHolder != null && chunkHolder.isEntityTickingReady(); ++ return this.moonrise$getChunkTaskScheduler().chunkHolderManager.entityTickingChunkHolders.contains(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(pos)); // DivineMC - Chunk System optimization + // Paper end - rewrite chunk system + } + + public boolean isNaturalSpawningAllowed(ChunkPos chunkPos) { + // Paper start - rewrite chunk system +- final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder chunkHolder = this.moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(chunkPos)); +- return chunkHolder != null && chunkHolder.isEntityTickingReady(); ++ return this.moonrise$getChunkTaskScheduler().chunkHolderManager.entityTickingChunkHolders.contains(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(chunkPos)); // DivineMC - Chunk System optimization + // 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 ++++ b/net/minecraft/world/level/LevelReader.java +@@ -70,10 +70,27 @@ public interface LevelReader extends ca.spottedleaf.moonrise.patches.chunk_syste + + @Override + default Holder getNoiseBiome(int x, int y, int z) { +- ChunkAccess chunk = this.getChunk(QuartPos.toSection(x), QuartPos.toSection(z), ChunkStatus.BIOMES, false); ++ ChunkAccess chunk = this.fasterChunkAccess(this, QuartPos.toSection(x), QuartPos.toSection(z), ChunkStatus.BIOMES, false); // DivineMC - Chunk System optimization + return chunk != null ? chunk.getNoiseBiome(x, y, z) : this.getUncachedNoiseBiome(x, y, z); + } + ++ // DivineMC start - Chunk System optimization ++ private @Nullable ChunkAccess fasterChunkAccess(LevelReader instance, int x, int z, ChunkStatus chunkStatus, boolean create) { ++ if (!create && instance instanceof net.minecraft.server.level.ServerLevel world) { ++ final net.minecraft.server.level.ChunkHolder holder = (world.getChunkSource().chunkMap).getVisibleChunkIfPresent(ChunkPos.asLong(x, z)); ++ if (holder != null) { ++ final java.util.concurrent.CompletableFuture> future = holder.getFullChunkFuture(); ++ final net.minecraft.server.level.ChunkResult either = future.getNow(null); ++ if (either != null) { ++ final net.minecraft.world.level.chunk.LevelChunk chunk = either.orElse(null); ++ if (chunk != null) return chunk; ++ } ++ } ++ } ++ return instance.getChunk(x, z, chunkStatus, create); ++ } ++ // DivineMC end - Chunk System optimization ++ + Holder getUncachedNoiseBiome(int x, int y, int z); + + boolean isClientSide(); +diff --git a/net/minecraft/world/level/biome/BiomeManager.java b/net/minecraft/world/level/biome/BiomeManager.java +index 73962e79a0f3d892e3155443a1b84508b0f4042e..5622345280267792861c8d508d0366cfdfb2c17a 100644 +--- a/net/minecraft/world/level/biome/BiomeManager.java ++++ b/net/minecraft/world/level/biome/BiomeManager.java +@@ -14,6 +14,7 @@ public class BiomeManager { + private static final int ZOOM_MASK = 3; + private final BiomeManager.NoiseBiomeSource noiseBiomeSource; + private final long biomeZoomSeed; ++ private static final double maxOffset = 0.4500000001D; // DivineMC - Chunk System Optimizations + + public BiomeManager(BiomeManager.NoiseBiomeSource noiseBiomeSource, long biomeZoomSeed) { + this.noiseBiomeSource = noiseBiomeSource; +@@ -29,39 +30,65 @@ public class BiomeManager { + } + + public Holder getBiome(BlockPos pos) { +- int i = pos.getX() - 2; +- int i1 = pos.getY() - 2; +- int i2 = pos.getZ() - 2; +- int i3 = i >> 2; +- int i4 = i1 >> 2; +- int i5 = i2 >> 2; +- double d = (i & 3) / 4.0; +- double d1 = (i1 & 3) / 4.0; +- double d2 = (i2 & 3) / 4.0; +- int i6 = 0; +- double d3 = Double.POSITIVE_INFINITY; ++ // DivineMC start - Chunk System Optimizations ++ int xMinus2 = pos.getX() - 2; ++ int yMinus2 = pos.getY() - 2; ++ int zMinus2 = pos.getZ() - 2; ++ int x = xMinus2 >> 2; ++ int y = yMinus2 >> 2; ++ int z = zMinus2 >> 2; ++ double quartX = (double) (xMinus2 & 3) / 4.0; ++ double quartY = (double) (yMinus2 & 3) / 4.0; ++ double quartZ = (double) (zMinus2 & 3) / 4.0; ++ int smallestX = 0; ++ double smallestDist = Double.POSITIVE_INFINITY; ++ for (int biomeX = 0; biomeX < 8; ++biomeX) { ++ boolean everyOtherQuad = (biomeX & 4) == 0; ++ boolean everyOtherPair = (biomeX & 2) == 0; ++ boolean everyOther = (biomeX & 1) == 0; ++ double quartXX = everyOtherQuad ? quartX : quartX - 1.0; ++ double quartYY = everyOtherPair ? quartY : quartY - 1.0; ++ double quartZZ = everyOther ? quartZ : quartZ - 1.0; + +- for (int i7 = 0; i7 < 8; i7++) { +- boolean flag = (i7 & 4) == 0; +- boolean flag1 = (i7 & 2) == 0; +- boolean flag2 = (i7 & 1) == 0; +- int i8 = flag ? i3 : i3 + 1; +- int i9 = flag1 ? i4 : i4 + 1; +- int i10 = flag2 ? i5 : i5 + 1; +- double d4 = flag ? d : d - 1.0; +- double d5 = flag1 ? d1 : d1 - 1.0; +- double d6 = flag2 ? d2 : d2 - 1.0; +- double fiddledDistance = getFiddledDistance(this.biomeZoomSeed, i8, i9, i10, d4, d5, d6); +- if (d3 > fiddledDistance) { +- i6 = i7; +- d3 = fiddledDistance; ++ double maxQuartYY = 0.0, maxQuartZZ = 0.0; ++ if (biomeX != 0) { ++ maxQuartYY = Mth.square(Math.max(quartYY + maxOffset, Math.abs(quartYY - maxOffset))); ++ maxQuartZZ = Mth.square(Math.max(quartZZ + maxOffset, Math.abs(quartZZ - maxOffset))); ++ double maxQuartXX = Mth.square(Math.max(quartXX + maxOffset, Math.abs(quartXX - maxOffset))); ++ if (smallestDist < maxQuartXX + maxQuartYY + maxQuartZZ) continue; + } +- } ++ int xx = everyOtherQuad ? x : x + 1; ++ int yy = everyOtherPair ? y : y + 1; ++ int zz = everyOther ? z : z + 1; ++ ++ long seed = LinearCongruentialGenerator.next(this.biomeZoomSeed, xx); ++ seed = LinearCongruentialGenerator.next(seed, yy); ++ seed = LinearCongruentialGenerator.next(seed, zz); ++ seed = LinearCongruentialGenerator.next(seed, xx); ++ seed = LinearCongruentialGenerator.next(seed, yy); ++ seed = LinearCongruentialGenerator.next(seed, zz); ++ double offsetX = getFiddle(seed); ++ double sqrX = Mth.square(quartXX + offsetX); ++ if (biomeX != 0 && smallestDist < sqrX + maxQuartYY + maxQuartZZ) continue; ++ seed = LinearCongruentialGenerator.next(seed, this.biomeZoomSeed); ++ double offsetY = getFiddle(seed); ++ double sqrY = Mth.square(quartYY + offsetY); ++ if (biomeX != 0 && smallestDist < sqrX + sqrY + maxQuartZZ) continue; ++ seed = LinearCongruentialGenerator.next(seed, this.biomeZoomSeed); ++ double offsetZ = getFiddle(seed); ++ double biomeDist = sqrX + sqrY + Mth.square(quartZZ + offsetZ); + +- int i7x = (i6 & 4) == 0 ? i3 : i3 + 1; +- int i11 = (i6 & 2) == 0 ? i4 : i4 + 1; +- int i12 = (i6 & 1) == 0 ? i5 : i5 + 1; +- return this.noiseBiomeSource.getNoiseBiome(i7x, i11, i12); ++ if (smallestDist > biomeDist) { ++ smallestX = biomeX; ++ smallestDist = biomeDist; ++ } ++ } ++ return this.noiseBiomeSource.getNoiseBiome( ++ (smallestX & 4) == 0 ? x : x + 1, ++ (smallestX & 2) == 0 ? y : y + 1, ++ (smallestX & 1) == 0 ? z : z + 1 ++ ); ++ // DivineMC end - Chunk System Optimizations + } + + public Holder getNoiseBiomeAtPosition(double x, double y, double z) { +diff --git a/net/minecraft/world/level/biome/TheEndBiomeSource.java b/net/minecraft/world/level/biome/TheEndBiomeSource.java +index cf3172be76fa4c7987ed569138439ff42f92fa7f..ed3c470056855a520a110ac7014f7839bcc85b88 100644 +--- a/net/minecraft/world/level/biome/TheEndBiomeSource.java ++++ b/net/minecraft/world/level/biome/TheEndBiomeSource.java +@@ -27,6 +27,33 @@ public class TheEndBiomeSource extends BiomeSource { + private final Holder islands; + private final Holder barrens; + ++ // DivineMC start - Chunk System Optimizations ++ private Holder getBiomeForNoiseGenVanilla(int x, int y, int z, Climate.Sampler noise) { ++ int i = QuartPos.toBlock(x); ++ int j = QuartPos.toBlock(y); ++ int k = QuartPos.toBlock(z); ++ int l = SectionPos.blockToSectionCoord(i); ++ int m = SectionPos.blockToSectionCoord(k); ++ if ((long)l * (long)l + (long)m * (long)m <= 4096L) { ++ return this.end; ++ } else { ++ int n = (SectionPos.blockToSectionCoord(i) * 2 + 1) * 8; ++ int o = (SectionPos.blockToSectionCoord(k) * 2 + 1) * 8; ++ double d = noise.erosion().compute(new DensityFunction.SinglePointContext(n, j, o)); ++ if (d > 0.25D) { ++ return this.highlands; ++ } else if (d >= -0.0625D) { ++ return this.midlands; ++ } else { ++ return d < -0.21875D ? this.islands : this.barrens; ++ } ++ } ++ } ++ ++ private final ThreadLocal>> cache = ThreadLocal.withInitial(it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap::new); ++ private final int cacheCapacity = 1024; ++ // DivineMC end - Chunk System Optimizations ++ + public static TheEndBiomeSource create(HolderGetter biomeGetter) { + return new TheEndBiomeSource( + biomeGetter.getOrThrow(Biomes.THE_END), +@@ -55,26 +82,24 @@ public class TheEndBiomeSource extends BiomeSource { + return CODEC; + } + ++ // DivineMC start - Chunk System Optimizations + @Override +- public Holder getNoiseBiome(int x, int y, int z, Climate.Sampler sampler) { +- int blockPosX = QuartPos.toBlock(x); +- int blockPosY = QuartPos.toBlock(y); +- int blockPosZ = QuartPos.toBlock(z); +- int sectionPosX = SectionPos.blockToSectionCoord(blockPosX); +- int sectionPosZ = SectionPos.blockToSectionCoord(blockPosZ); +- if ((long)sectionPosX * sectionPosX + (long)sectionPosZ * sectionPosZ <= 4096L) { +- return this.end; ++ public Holder getNoiseBiome(int biomeX, int biomeY, int biomeZ, Climate.Sampler multiNoiseSampler) { ++ final long key = net.minecraft.world.level.ChunkPos.asLong(biomeX, biomeZ); ++ final it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap> cacheThreadLocal = cache.get(); ++ final Holder biome = cacheThreadLocal.get(key); ++ if (biome != null) { ++ return biome; + } else { +- int i = (SectionPos.blockToSectionCoord(blockPosX) * 2 + 1) * 8; +- int i1 = (SectionPos.blockToSectionCoord(blockPosZ) * 2 + 1) * 8; +- double d = sampler.erosion().compute(new DensityFunction.SinglePointContext(i, blockPosY, i1)); +- if (d > 0.25) { +- return this.highlands; +- } else if (d >= -0.0625) { +- return this.midlands; +- } else { +- return d < -0.21875 ? this.islands : this.barrens; ++ final Holder gennedBiome = getBiomeForNoiseGenVanilla(biomeX, biomeY, biomeZ, multiNoiseSampler); ++ cacheThreadLocal.put(key, gennedBiome); ++ if (cacheThreadLocal.size() > cacheCapacity) { ++ for (int i = 0; i < cacheCapacity / 16; i ++) { ++ cacheThreadLocal.removeFirst(); ++ } + } ++ return gennedBiome; + } + } ++ // DivineMC end - Chunk System Optimizations + } +diff --git a/net/minecraft/world/level/chunk/LevelChunkSection.java b/net/minecraft/world/level/chunk/LevelChunkSection.java +index c83d0667b19830304f22319a46a23422a8766790..aa10c5087a0fc9306b734f20ccbad73045a1b6d0 100644 +--- a/net/minecraft/world/level/chunk/LevelChunkSection.java ++++ b/net/minecraft/world/level/chunk/LevelChunkSection.java +@@ -23,6 +23,7 @@ public class LevelChunkSection implements ca.spottedleaf.moonrise.patches.block_ + public short tickingFluidCount; + public final PalettedContainer states; + private PalettedContainer> biomes; // CraftBukkit - read/write ++ private static final int sliceSize = 4; // DivineMC - Chunk System Optimizations + + // Paper start - block counting + private static final it.unimi.dsi.fastutil.shorts.ShortArrayList FULL_LIST = new it.unimi.dsi.fastutil.shorts.ShortArrayList(16*16*16); +@@ -296,7 +297,7 @@ public class LevelChunkSection implements ca.spottedleaf.moonrise.patches.block_ + } + + public boolean maybeHas(Predicate predicate) { +- return this.states.maybeHas(predicate); ++ return this.states.maybeHasOrCatch(predicate, Blocks.AIR.defaultBlockState()); // DivineMC - Chunk System Optimizations + } + + public Holder getNoiseBiome(int x, int y, int z) { +@@ -312,13 +313,15 @@ public class LevelChunkSection implements ca.spottedleaf.moonrise.patches.block_ + PalettedContainer> palettedContainer = this.biomes.recreate(); + int i = 4; + +- for (int i1 = 0; i1 < 4; i1++) { +- for (int i2 = 0; i2 < 4; i2++) { +- for (int i3 = 0; i3 < 4; i3++) { +- palettedContainer.getAndSetUnchecked(i1, i2, i3, biomeResolver.getNoiseBiome(x + i1, y + i2, z + i3, climateSampler)); ++ // DivineMC start - Chunk System Optimizations ++ for (int posY = 0; posY < sliceSize; ++posY) { ++ for (int posZ = 0; posZ < sliceSize; ++posZ) { ++ for (int posX = 0; posX < sliceSize; ++posX) { ++ palettedContainer.getAndSetUnchecked(posX, posY, posZ, biomeResolver.getNoiseBiome(x + posX, y + posY, z + posZ, climateSampler)); + } + } + } ++ // DivineMC end - Chunk System Optimizations + + this.biomes = palettedContainer; + } +diff --git a/net/minecraft/world/level/chunk/LinearPalette.java b/net/minecraft/world/level/chunk/LinearPalette.java +index 2073f6ff41aa570102621d183ee890b076267d54..25d46d1dff23a9bffd135d6954b551991e175cd4 100644 +--- a/net/minecraft/world/level/chunk/LinearPalette.java ++++ b/net/minecraft/world/level/chunk/LinearPalette.java +@@ -12,7 +12,7 @@ public class LinearPalette implements Palette, ca.spottedleaf.moonrise.pat + private final T[] values; + private final PaletteResize resizeHandler; + private final int bits; +- private int size; ++ private volatile int size; // DivineMC - Chunk System Optimizations + + // Paper start - optimise palette reads + @Override +@@ -49,11 +49,14 @@ public class LinearPalette implements Palette, ca.spottedleaf.moonrise.pat + + @Override + public int idFor(T state) { +- for (int i = 0; i < this.size; i++) { +- if (this.values[i] == state) { ++ // DivineMC start - Chunk System Optimizations ++ final T[] values = this.values; ++ for (int i = 0; i < values.length; i++) { ++ if (values[i] == state) { + return i; + } + } ++ // DivineMC end - Chunk System Optimizations + + int ix = this.size; + if (ix < this.values.length) { +@@ -67,17 +70,23 @@ public class LinearPalette implements Palette, ca.spottedleaf.moonrise.pat + + @Override + public boolean maybeHas(Predicate filter) { +- for (int i = 0; i < this.size; i++) { +- if (filter.test(this.values[i])) { ++ // DivineMC start - Chunk System Optimizations ++ final T[] values = this.values; ++ final int currentSize = this.size; ++ ++ for (int i = 0; i < currentSize; i++) { ++ T value = values[i]; ++ if (value != null && filter.test(value)) { + return true; + } + } ++ // DivineMC end - Chunk System Optimizations + + return false; + } + + @Override +- public T valueFor(int id) { ++ public synchronized T valueFor(int id) { // DivineMC - Chunk System Optimizations + if (id >= 0 && id < this.size) { + return this.values[id]; + } else { +diff --git a/net/minecraft/world/level/chunk/Palette.java b/net/minecraft/world/level/chunk/Palette.java +index a80b2e9dceea423180a9c390d1970317dff4f1b0..6d9dfc1837dccef2073da180aaaf68b07b04a8e3 100644 +--- a/net/minecraft/world/level/chunk/Palette.java ++++ b/net/minecraft/world/level/chunk/Palette.java +@@ -10,6 +10,12 @@ public interface Palette extends ca.spottedleaf.moonrise.patches.fast_palette + + boolean maybeHas(Predicate filter); + ++ // DivineMC start - Chunk System Optimizations ++ public default boolean maybeHasOrCatch(Predicate filter, T defaultValue) { ++ return this.maybeHas(filter); ++ } ++ // DivineMC end - Chunk System Optimizations ++ + T valueFor(int id); + + void read(FriendlyByteBuf buffer); +diff --git a/net/minecraft/world/level/chunk/PalettedContainer.java b/net/minecraft/world/level/chunk/PalettedContainer.java +index 230cb433c38f9b6ffb1adeaa8b6040490f13e826..712d3d1669aecd38934957c81835e1f38289539a 100644 +--- a/net/minecraft/world/level/chunk/PalettedContainer.java ++++ b/net/minecraft/world/level/chunk/PalettedContainer.java +@@ -393,6 +393,12 @@ public class PalettedContainer implements PaletteResize, PalettedContainer + return this.data.palette.maybeHas(predicate); + } + ++ // DivineMC start - Chunk System Optimizations ++ public boolean maybeHasOrCatch(Predicate predicate, @org.jetbrains.annotations.NotNull T defaultValue) { ++ return this.data.palette.maybeHasOrCatch(predicate, defaultValue); ++ } ++ // DivineMC end - Chunk System Optimizations ++ + @Override + public PalettedContainer copy() { + return new PalettedContainer<>(this, this.presetValues); // Paper - Anti-Xray - Add preset values +diff --git a/net/minecraft/world/level/chunk/ProtoChunk.java b/net/minecraft/world/level/chunk/ProtoChunk.java +index e66239e2da91bd3ddf358d239be796719c0da327..35e9d8cfe12252d3419626f1cefb64d30e20069e 100644 +--- a/net/minecraft/world/level/chunk/ProtoChunk.java ++++ b/net/minecraft/world/level/chunk/ProtoChunk.java +@@ -41,7 +41,7 @@ public class ProtoChunk extends ChunkAccess { + @Nullable + private volatile LevelLightEngine lightEngine; + private volatile ChunkStatus status = ChunkStatus.EMPTY; +- private final List entities = Lists.newArrayList(); ++ private final List entities = Collections.synchronizedList(Lists.newArrayList()); // DivineMC - Chunk System optimization + @Nullable + private CarvingMask carvingMask; + @Nullable +diff --git a/net/minecraft/world/level/chunk/SingleValuePalette.java b/net/minecraft/world/level/chunk/SingleValuePalette.java +index 2ffae24b0cb1a20c7d5a8520f1b5197c2cedea11..c3ec5e5645f680a915c95d833b589b680c82c35d 100644 +--- a/net/minecraft/world/level/chunk/SingleValuePalette.java ++++ b/net/minecraft/world/level/chunk/SingleValuePalette.java +@@ -11,7 +11,7 @@ import org.apache.commons.lang3.Validate; + public class SingleValuePalette implements Palette, ca.spottedleaf.moonrise.patches.fast_palette.FastPalette { // Paper - optimise palette reads + private final IdMap registry; + @Nullable +- private T value; ++ private volatile T value; // DivineMC - Chunk System Optimizations + private final PaletteResize resizeHandler; + + // Paper start - optimise palette reads +@@ -44,6 +44,7 @@ public class SingleValuePalette implements Palette, ca.spottedleaf.moonris + if (this.value != null && this.value != state) { + return this.resizeHandler.onResize(1, state); + } else { ++ if (state == null) throw new IllegalArgumentException("Null state not allowed"); // DivineMC - Chunk System Optimizations + this.value = state; + // Paper start - optimise palette reads + if (this.rawPalette != null) { +@@ -63,6 +64,19 @@ public class SingleValuePalette implements Palette, ca.spottedleaf.moonris + } + } + ++ // DivineMC start - Chunk System Optimizations ++ @Override ++ public boolean maybeHasOrCatch(final Predicate filter, final T defaultValue) { ++ if (this.value == null) { ++ if (defaultValue == null) throw new IllegalArgumentException("Default value for 'maybeHasOrCatch' cannot be null!"); ++ this.value = defaultValue; ++ return maybeHas(filter); ++ } else { ++ return filter.test(this.value); ++ } ++ } ++ // DivineMC end - Chunk System Optimizations ++ + @Override + public T valueFor(int id) { + if (this.value != null && id == 0) { +diff --git a/net/minecraft/world/level/chunk/storage/IOWorker.java b/net/minecraft/world/level/chunk/storage/IOWorker.java +index 2199a9e2a0141c646d108f2687a27f1d165453c5..c28c2583b257f92207b822a1fdde8f5b7e480992 100644 +--- a/net/minecraft/world/level/chunk/storage/IOWorker.java ++++ b/net/minecraft/world/level/chunk/storage/IOWorker.java +@@ -212,7 +212,38 @@ public class IOWorker implements ChunkScanAccess, AutoCloseable { + }); + } + ++ // DivineMC start - Chunk System optimization ++ private void checkHardLimit() { ++ if (this.pendingWrites.size() >= org.bxteam.divinemc.DivineConfig.chunkDataCacheLimit) { ++ LOGGER.warn("Chunk data cache size exceeded hard limit ({} >= {}), forcing writes to disk (you can increase chunkDataCacheLimit in c2me.toml)", this.pendingWrites.size(), org.bxteam.divinemc.DivineConfig.chunkDataCacheLimit); ++ while (this.pendingWrites.size() >= org.bxteam.divinemc.DivineConfig.chunkDataCacheSoftLimit * 0.75) { ++ writeResult0(); ++ } ++ } ++ } ++ ++ private void writeResult0() { ++ java.util.Iterator> iterator = this.pendingWrites.entrySet().iterator(); ++ if (iterator.hasNext()) { ++ java.util.Map.Entry entry = iterator.next(); ++ iterator.remove(); ++ this.runStore(entry.getKey(), entry.getValue()); ++ } ++ } ++ // DivineMC end - Chunk System optimization ++ + private void storePendingChunk() { ++ // DivineMC start - Chunk System optimization ++ if (!this.pendingWrites.isEmpty()) { ++ checkHardLimit(); ++ if (this.pendingWrites.size() >= org.bxteam.divinemc.DivineConfig.chunkDataCacheSoftLimit) { ++ int writeFrequency = Math.min(1, (this.pendingWrites.size() - (int) org.bxteam.divinemc.DivineConfig.chunkDataCacheSoftLimit) / 16); ++ for (int i = 0; i < writeFrequency; i++) { ++ writeResult0(); ++ } ++ } ++ } ++ // DivineMC end - Chunk System optimization + Entry entry = this.pendingWrites.pollFirstEntry(); + if (entry != null) { + this.runStore(entry.getKey(), entry.getValue()); +diff --git a/net/minecraft/world/level/chunk/storage/RegionFileStorage.java b/net/minecraft/world/level/chunk/storage/RegionFileStorage.java +index 6ebd1300c2561116b83cb2472ac7939ead36d576..16cd10ab8de69ca3d29c84cf93715645322fd72a 100644 +--- a/net/minecraft/world/level/chunk/storage/RegionFileStorage.java ++++ b/net/minecraft/world/level/chunk/storage/RegionFileStorage.java +@@ -244,7 +244,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise + + protected RegionFileStorage(RegionStorageInfo info, Path folder, boolean sync) { // Paper - protected + this.folder = folder; +- this.sync = sync; ++ this.sync = Boolean.parseBoolean(System.getProperty("com.ishland.c2me.chunkio.syncDiskWrites", String.valueOf(sync))); // DivineMC - C2ME: sync disk writes + this.info = info; + this.isChunkData = isChunkDataFolder(this.folder); // Paper - recalculate region file headers + } +diff --git a/net/minecraft/world/level/levelgen/Aquifer.java b/net/minecraft/world/level/levelgen/Aquifer.java +index c62a15ea4a1bb22e7bcc2fc544acf8a601892029..bc67039f8374ae4e471ca14e0c623e6bf334020f 100644 +--- a/net/minecraft/world/level/levelgen/Aquifer.java ++++ b/net/minecraft/world/level/levelgen/Aquifer.java +@@ -85,6 +85,15 @@ public interface Aquifer { + private final int minGridZ; + private final int gridSizeX; + private final int gridSizeZ; ++ // DivineMC start - Chunk System Optimizations ++ private int c2me$dist1; ++ private int c2me$dist2; ++ private int c2me$dist3; ++ private long c2me$pos1; ++ private long c2me$pos2; ++ private long c2me$pos3; ++ private double c2me$mutableDoubleThingy; ++ // DivineMC end - Chunk System Optimizations + private static final int[][] SURFACE_SAMPLING_OFFSETS_IN_CHUNKS = new int[][]{ + {0, 0}, {-2, -1}, {-1, -1}, {0, -1}, {1, -1}, {-3, 0}, {-2, 0}, {-1, 0}, {1, 0}, {-2, 1}, {-1, 1}, {0, 1}, {1, 1} + }; +@@ -120,6 +129,36 @@ public interface Aquifer { + this.aquiferCache = new Aquifer.FluidStatus[i4]; + this.aquiferLocationCache = new long[i4]; + Arrays.fill(this.aquiferLocationCache, Long.MAX_VALUE); ++ // DivineMC start - Chunk System Optimizations ++ if (this.aquiferLocationCache.length % (this.gridSizeX * this.gridSizeZ) != 0) { ++ throw new AssertionError("Array length"); ++ } ++ ++ int sizeY = this.aquiferLocationCache.length / (this.gridSizeX * this.gridSizeZ); ++ ++ final RandomSource random = org.bxteam.divinemc.util.RandomUtil.getRandom(this.positionalRandomFactory); ++ // index: y, z, x ++ for (int y = 0; y < sizeY; y++) { ++ for (int z = 0; z < this.gridSizeZ; z++) { ++ for (int x = 0; x < this.gridSizeX; x++) { ++ final int x1 = x + this.minGridX; ++ final int y1 = y + this.minGridY; ++ final int z1 = z + this.minGridZ; ++ org.bxteam.divinemc.util.RandomUtil.derive(this.positionalRandomFactory, random, x1, y1, z1); ++ int x2 = x1 * 16 + random.nextInt(10); ++ int y2 = y1 * 12 + random.nextInt(9); ++ int z2 = z1 * 16 + random.nextInt(10); ++ int index = this.getIndex(x1, y1, z1); ++ this.aquiferLocationCache[index] = BlockPos.asLong(x2, y2, z2); ++ } ++ } ++ } ++ for (long blockPosition : this.aquiferLocationCache) { ++ if (blockPosition == Long.MAX_VALUE) { ++ throw new AssertionError("Array initialization"); ++ } ++ } ++ // DivineMC end - Chunk System Optimizations + } + + private int getIndex(int gridX, int gridY, int gridZ) { +@@ -132,140 +171,24 @@ public interface Aquifer { + @Nullable + @Override + public BlockState computeSubstance(DensityFunction.FunctionContext context, double substance) { ++ // DivineMC start - Chunk System Optimizations + int i = context.blockX(); +- int i1 = context.blockY(); +- int i2 = context.blockZ(); ++ int j = context.blockY(); ++ int k = context.blockZ(); + if (substance > 0.0) { + this.shouldScheduleFluidUpdate = false; + return null; + } else { +- Aquifer.FluidStatus fluidStatus = this.globalFluidPicker.computeFluid(i, i1, i2); +- if (fluidStatus.at(i1).is(Blocks.LAVA)) { ++ Aquifer.FluidStatus fluidLevel = this.globalFluidPicker.computeFluid(i, j, k); ++ if (fluidLevel.at(j).is(Blocks.LAVA)) { + this.shouldScheduleFluidUpdate = false; + return Blocks.LAVA.defaultBlockState(); + } else { +- int i3 = Math.floorDiv(i - 5, 16); +- int i4 = Math.floorDiv(i1 + 1, 12); +- int i5 = Math.floorDiv(i2 - 5, 16); +- int i6 = Integer.MAX_VALUE; +- int i7 = Integer.MAX_VALUE; +- int i8 = Integer.MAX_VALUE; +- int i9 = Integer.MAX_VALUE; +- long l = 0L; +- long l1 = 0L; +- long l2 = 0L; +- long l3 = 0L; +- +- for (int i10 = 0; i10 <= 1; i10++) { +- for (int i11 = -1; i11 <= 1; i11++) { +- for (int i12 = 0; i12 <= 1; i12++) { +- int i13 = i3 + i10; +- int i14 = i4 + i11; +- int i15 = i5 + i12; +- int index = this.getIndex(i13, i14, i15); +- long l4 = this.aquiferLocationCache[index]; +- long l5; +- if (l4 != Long.MAX_VALUE) { +- l5 = l4; +- } else { +- RandomSource randomSource = this.positionalRandomFactory.at(i13, i14, i15); +- l5 = BlockPos.asLong( +- i13 * 16 + randomSource.nextInt(10), i14 * 12 + randomSource.nextInt(9), i15 * 16 + randomSource.nextInt(10) +- ); +- this.aquiferLocationCache[index] = l5; +- } +- +- int i16 = BlockPos.getX(l5) - i; +- int i17 = BlockPos.getY(l5) - i1; +- int i18 = BlockPos.getZ(l5) - i2; +- int i19 = i16 * i16 + i17 * i17 + i18 * i18; +- if (i6 >= i19) { +- l3 = l2; +- l2 = l1; +- l1 = l; +- l = l5; +- i9 = i8; +- i8 = i7; +- i7 = i6; +- i6 = i19; +- } else if (i7 >= i19) { +- l3 = l2; +- l2 = l1; +- l1 = l5; +- i9 = i8; +- i8 = i7; +- i7 = i19; +- } else if (i8 >= i19) { +- l3 = l2; +- l2 = l5; +- i9 = i8; +- i8 = i19; +- } else if (i9 >= i19) { +- l3 = l5; +- i9 = i19; +- } +- } +- } +- } +- +- Aquifer.FluidStatus aquiferStatus = this.getAquiferStatus(l); +- double d = similarity(i6, i7); +- BlockState blockState = aquiferStatus.at(i1); +- if (d <= 0.0) { +- if (d >= FLOWING_UPDATE_SIMULARITY) { +- Aquifer.FluidStatus aquiferStatus1 = this.getAquiferStatus(l1); +- this.shouldScheduleFluidUpdate = !aquiferStatus.equals(aquiferStatus1); +- } else { +- this.shouldScheduleFluidUpdate = false; +- } +- +- return blockState; +- } else if (blockState.is(Blocks.WATER) && this.globalFluidPicker.computeFluid(i, i1 - 1, i2).at(i1 - 1).is(Blocks.LAVA)) { +- this.shouldScheduleFluidUpdate = true; +- return blockState; +- } else { +- MutableDouble mutableDouble = new MutableDouble(Double.NaN); +- Aquifer.FluidStatus aquiferStatus2 = this.getAquiferStatus(l1); +- double d1 = d * this.calculatePressure(context, mutableDouble, aquiferStatus, aquiferStatus2); +- if (substance + d1 > 0.0) { +- this.shouldScheduleFluidUpdate = false; +- return null; +- } else { +- Aquifer.FluidStatus aquiferStatus3 = this.getAquiferStatus(l2); +- double d2 = similarity(i6, i8); +- if (d2 > 0.0) { +- double d3 = d * d2 * this.calculatePressure(context, mutableDouble, aquiferStatus, aquiferStatus3); +- if (substance + d3 > 0.0) { +- this.shouldScheduleFluidUpdate = false; +- return null; +- } +- } +- +- double d3 = similarity(i7, i8); +- if (d3 > 0.0) { +- double d4 = d * d3 * this.calculatePressure(context, mutableDouble, aquiferStatus2, aquiferStatus3); +- if (substance + d4 > 0.0) { +- this.shouldScheduleFluidUpdate = false; +- return null; +- } +- } +- +- boolean flag = !aquiferStatus.equals(aquiferStatus2); +- boolean flag1 = d3 >= FLOWING_UPDATE_SIMULARITY && !aquiferStatus2.equals(aquiferStatus3); +- boolean flag2 = d2 >= FLOWING_UPDATE_SIMULARITY && !aquiferStatus.equals(aquiferStatus3); +- if (!flag && !flag1 && !flag2) { +- this.shouldScheduleFluidUpdate = d2 >= FLOWING_UPDATE_SIMULARITY +- && similarity(i6, i9) >= FLOWING_UPDATE_SIMULARITY +- && !aquiferStatus.equals(this.getAquiferStatus(l3)); +- } else { +- this.shouldScheduleFluidUpdate = true; +- } +- +- return blockState; +- } +- } ++ aquiferExtracted$refreshDistPosIdx(i, j, k); ++ return aquiferExtracted$applyPost(context, substance, j, i, k); + } + } ++ // DivineMC end - Chunk System Optimizations + } + + @Override +@@ -278,65 +201,28 @@ public interface Aquifer { + return 1.0 - Math.abs(secondDistance - firstDistance) / 25.0; + } + ++ // DivineMC start - Chunk System Optimizations + private double calculatePressure( +- DensityFunction.FunctionContext context, MutableDouble substance, Aquifer.FluidStatus firstFluid, Aquifer.FluidStatus secondFluid ++ DensityFunction.FunctionContext context, MutableDouble substance, Aquifer.FluidStatus fluidLevel, Aquifer.FluidStatus fluidLevel2 // DivineMC - rename args + ) { + int i = context.blockY(); +- BlockState blockState = firstFluid.at(i); +- BlockState blockState1 = secondFluid.at(i); +- if ((!blockState.is(Blocks.LAVA) || !blockState1.is(Blocks.WATER)) && (!blockState.is(Blocks.WATER) || !blockState1.is(Blocks.LAVA))) { +- int abs = Math.abs(firstFluid.fluidLevel - secondFluid.fluidLevel); ++ BlockState blockState = fluidLevel.at(i); ++ BlockState blockState2 = fluidLevel2.at(i); ++ if ((!blockState.is(Blocks.LAVA) || !blockState2.is(Blocks.WATER)) && (!blockState.is(Blocks.WATER) || !blockState2.is(Blocks.LAVA))) { ++ int abs = Math.abs(fluidLevel.fluidLevel - fluidLevel2.fluidLevel); + if (abs == 0) { + return 0.0; + } else { +- double d = 0.5 * (firstFluid.fluidLevel + secondFluid.fluidLevel); +- double d1 = i + 0.5 - d; +- double d2 = abs / 2.0; +- double d3 = 0.0; +- double d4 = 2.5; +- double d5 = 1.5; +- double d6 = 3.0; +- double d7 = 10.0; +- double d8 = 3.0; +- double d9 = d2 - Math.abs(d1); +- double d11; +- if (d1 > 0.0) { +- double d10 = 0.0 + d9; +- if (d10 > 0.0) { +- d11 = d10 / 1.5; +- } else { +- d11 = d10 / 2.5; +- } +- } else { +- double d10 = 3.0 + d9; +- if (d10 > 0.0) { +- d11 = d10 / 3.0; +- } else { +- d11 = d10 / 10.0; +- } +- } +- +- double d10x = 2.0; +- double d12; +- if (!(d11 < -2.0) && !(d11 > 2.0)) { +- double value = substance.getValue(); +- if (Double.isNaN(value)) { +- double d13 = this.barrierNoise.compute(context); +- substance.setValue(d13); +- d12 = d13; +- } else { +- d12 = value; +- } +- } else { +- d12 = 0.0; +- } ++ double d = 0.5 * (double)(fluidLevel.fluidLevel + fluidLevel2.fluidLevel); ++ final double q = aquiferExtracted$getQ(i, d, abs); + +- return 2.0 * (d12 + d11); ++ return aquiferExtracted$postCalculateDensity(context, substance, q); + } + } else { + return 2.0; + } + } ++ // DivineMC end - Chunk System Optimizations + + private int gridX(int x) { + return Math.floorDiv(x, 16); +@@ -350,23 +236,25 @@ public interface Aquifer { + return Math.floorDiv(z, 16); + } + +- private Aquifer.FluidStatus getAquiferStatus(long packedPos) { +- int x = BlockPos.getX(packedPos); +- int y = BlockPos.getY(packedPos); +- int z = BlockPos.getZ(packedPos); +- int i = this.gridX(x); +- int i1 = this.gridY(y); +- int i2 = this.gridZ(z); +- int index = this.getIndex(i, i1, i2); +- Aquifer.FluidStatus fluidStatus = this.aquiferCache[index]; +- if (fluidStatus != null) { +- return fluidStatus; ++ // DivineMC start - Chunk System Optimizations ++ private Aquifer.FluidStatus getAquiferStatus(long pos) { ++ int i = BlockPos.getX(pos); ++ int j = BlockPos.getY(pos); ++ int k = BlockPos.getZ(pos); ++ int l = i >> 4; // C2ME - inline: floorDiv(i, 16) ++ int m = Math.floorDiv(j, 12); // C2ME - inline ++ int n = k >> 4; // C2ME - inline: floorDiv(k, 16) ++ int o = this.getIndex(l, m, n); ++ Aquifer.FluidStatus fluidLevel = this.aquiferCache[o]; ++ if (fluidLevel != null) { ++ return fluidLevel; + } else { +- Aquifer.FluidStatus fluidStatus1 = this.computeFluid(x, y, z); +- this.aquiferCache[index] = fluidStatus1; +- return fluidStatus1; ++ Aquifer.FluidStatus fluidLevel2 = this.computeFluid(i, j, k); ++ this.aquiferCache[o] = fluidLevel2; ++ return fluidLevel2; + } + } ++ // DivineMC end - Chunk System Optimizations + + private Aquifer.FluidStatus computeFluid(int x, int y, int z) { + Aquifer.FluidStatus fluidStatus = this.globalFluidPicker.computeFluid(x, y, z); +@@ -407,22 +295,21 @@ public interface Aquifer { + } + + private int computeSurfaceLevel(int x, int y, int z, Aquifer.FluidStatus fluidStatus, int maxSurfaceLevel, boolean fluidPresent) { +- DensityFunction.SinglePointContext singlePointContext = new DensityFunction.SinglePointContext(x, y, z); ++ // DivineMC start - Chunk System Optimizations ++ DensityFunction.SinglePointContext unblendedNoisePos = new DensityFunction.SinglePointContext(x, y, z); + double d; + double d1; +- if (OverworldBiomeBuilder.isDeepDarkRegion(this.erosion, this.depth, singlePointContext)) { ++ if (OverworldBiomeBuilder.isDeepDarkRegion(this.erosion, this.depth, unblendedNoisePos)) { + d = -1.0; + d1 = -1.0; + } else { + int i = maxSurfaceLevel + 8 - y; +- int i1 = 64; +- double d2 = fluidPresent ? Mth.clampedMap((double)i, 0.0, 64.0, 1.0, 0.0) : 0.0; +- double d3 = Mth.clamp(this.fluidLevelFloodednessNoise.compute(singlePointContext), -1.0, 1.0); +- double d4 = Mth.map(d2, 1.0, 0.0, -0.3, 0.8); +- double d5 = Mth.map(d2, 1.0, 0.0, -0.8, 0.4); +- d = d3 - d5; +- d1 = d3 - d4; ++ double f = fluidPresent ? Mth.clampedLerp(1.0, 0.0, ((double) i) / 64.0) : 0.0; // inline ++ double g = Mth.clamp(this.fluidLevelFloodednessNoise.compute(unblendedNoisePos), -1.0, 1.0); ++ d = g + 0.8 + (f - 1.0) * 1.2; // inline ++ d1 = g + 0.3 + (f - 1.0) * 1.1; // inline + } ++ // DivineMC end - Chunk System Optimizations + + int i; + if (d1 > 0.0) { +@@ -453,12 +340,12 @@ public interface Aquifer { + private BlockState computeFluidType(int x, int y, int z, Aquifer.FluidStatus fluidStatus, int surfaceLevel) { + BlockState blockState = fluidStatus.fluidType; + if (surfaceLevel <= -10 && surfaceLevel != DimensionType.WAY_BELOW_MIN_Y && fluidStatus.fluidType != Blocks.LAVA.defaultBlockState()) { +- int i = 64; +- int i1 = 40; +- int i2 = Math.floorDiv(x, 64); +- int i3 = Math.floorDiv(y, 40); +- int i4 = Math.floorDiv(z, 64); +- double d = this.lavaNoise.compute(new DensityFunction.SinglePointContext(i2, i3, i4)); ++ // DivineMC start - Chunk System Optimizations ++ int k = x >> 6; ++ int l = Math.floorDiv(y, 40); ++ int m = z >> 6; ++ double d = this.lavaNoise.compute(new DensityFunction.SinglePointContext(k, l, m)); ++ // DivineMC end - Chunk System Optimizations + if (Math.abs(d) > 0.3) { + blockState = Blocks.LAVA.defaultBlockState(); + } +@@ -466,5 +353,183 @@ public interface Aquifer { + + return blockState; + } ++ ++ // DivineMC start - Chunk System Optimizations ++ private @org.jetbrains.annotations.Nullable BlockState aquiferExtracted$applyPost(DensityFunction.FunctionContext pos, double density, int j, int i, int k) { ++ Aquifer.FluidStatus fluidLevel2 = this.getAquiferStatus(this.c2me$pos1); ++ double d = similarity(this.c2me$dist1, this.c2me$dist2); ++ BlockState blockState = fluidLevel2.at(j); ++ if (d <= 0.0) { ++ this.shouldScheduleFluidUpdate = d >= FLOWING_UPDATE_SIMULARITY; ++ return blockState; ++ } else if (blockState.is(Blocks.WATER) && this.globalFluidPicker.computeFluid(i, j - 1, k).at(j - 1).is(Blocks.LAVA)) { ++ this.shouldScheduleFluidUpdate = true; ++ return blockState; ++ } else { ++ this.c2me$mutableDoubleThingy = Double.NaN; ++ Aquifer.FluidStatus fluidLevel3 = this.getAquiferStatus(this.c2me$pos2); ++ double e = d * this.c2me$calculateDensityModified(pos, fluidLevel2, fluidLevel3); ++ if (density + e > 0.0) { ++ this.shouldScheduleFluidUpdate = false; ++ return null; ++ } else { ++ return aquiferExtracted$getFinalBlockState(pos, density, d, fluidLevel2, fluidLevel3, blockState); ++ } ++ } ++ } ++ ++ private BlockState aquiferExtracted$getFinalBlockState(DensityFunction.FunctionContext pos, double density, double d, Aquifer.FluidStatus fluidLevel2, Aquifer.FluidStatus fluidLevel3, BlockState blockState) { ++ Aquifer.FluidStatus fluidLevel4 = this.getAquiferStatus(this.c2me$pos3); ++ double f = similarity(this.c2me$dist1, this.c2me$dist3); ++ if (aquiferExtracted$extractedCheckFG(pos, density, d, fluidLevel2, f, fluidLevel4)) return null; ++ ++ double g = similarity(this.c2me$dist2, this.c2me$dist3); ++ if (aquiferExtracted$extractedCheckFG(pos, density, d, fluidLevel3, g, fluidLevel4)) return null; ++ ++ this.shouldScheduleFluidUpdate = true; ++ return blockState; ++ } ++ ++ private boolean aquiferExtracted$extractedCheckFG(DensityFunction.FunctionContext pos, double density, double d, Aquifer.FluidStatus fluidLevel2, double f, Aquifer.FluidStatus fluidLevel4) { ++ if (f > 0.0) { ++ double g = d * f * this.c2me$calculateDensityModified(pos, fluidLevel2, fluidLevel4); ++ if (density + g > 0.0) { ++ this.shouldScheduleFluidUpdate = false; ++ return true; ++ } ++ } ++ return false; ++ } ++ ++ private void aquiferExtracted$refreshDistPosIdx(int x, int y, int z) { ++ int gx = (x - 5) >> 4; ++ int gy = Math.floorDiv(y + 1, 12); ++ int gz = (z - 5) >> 4; ++ int dist1 = Integer.MAX_VALUE; ++ int dist2 = Integer.MAX_VALUE; ++ int dist3 = Integer.MAX_VALUE; ++ long pos1 = 0; ++ long pos2 = 0; ++ long pos3 = 0; ++ ++ for (int offY = -1; offY <= 1; ++offY) { ++ for (int offZ = 0; offZ <= 1; ++offZ) { ++ for (int offX = 0; offX <= 1; ++offX) { ++ int posIdx = this.getIndex(gx + offX, gy + offY, gz + offZ); ++ ++ long position = this.aquiferLocationCache[posIdx]; ++ ++ int dx = BlockPos.getX(position) - x; ++ int dy = BlockPos.getY(position) - y; ++ int dz = BlockPos.getZ(position) - z; ++ int dist = dx * dx + dy * dy + dz * dz; ++ ++ if (dist3 >= dist) { ++ pos3 = position; ++ dist3 = dist; ++ } ++ if (dist2 >= dist) { ++ pos3 = pos2; ++ dist3 = dist2; ++ pos2 = position; ++ dist2 = dist; ++ } ++ if (dist1 >= dist) { ++ pos2 = pos1; ++ dist2 = dist1; ++ pos1 = position; ++ dist1 = dist; ++ } ++ } ++ } ++ } ++ ++ this.c2me$dist1 = dist1; ++ this.c2me$dist2 = dist2; ++ this.c2me$dist3 = dist3; ++ this.c2me$pos1 = pos1; ++ this.c2me$pos2 = pos2; ++ this.c2me$pos3 = pos3; ++ } ++ ++ private double c2me$calculateDensityModified( ++ DensityFunction.FunctionContext pos, Aquifer.FluidStatus fluidLevel, Aquifer.FluidStatus fluidLevel2 ++ ) { ++ int i = pos.blockY(); ++ BlockState blockState = fluidLevel.at(i); ++ BlockState blockState2 = fluidLevel2.at(i); ++ if ((!blockState.is(Blocks.LAVA) || !blockState2.is(Blocks.WATER)) && (!blockState.is(Blocks.WATER) || !blockState2.is(Blocks.LAVA))) { ++ int j = Math.abs(fluidLevel.fluidLevel - fluidLevel2.fluidLevel); ++ if (j == 0) { ++ return 0.0; ++ } else { ++ double d = 0.5 * (double)(fluidLevel.fluidLevel + fluidLevel2.fluidLevel); ++ final double q = aquiferExtracted$getQ(i, d, j); ++ ++ return aquiferExtracted$postCalculateDensityModified(pos, q); ++ } ++ } else { ++ return 2.0; ++ } ++ } ++ ++ private double aquiferExtracted$postCalculateDensity(DensityFunction.FunctionContext pos, MutableDouble mutableDouble, double q) { ++ double r; ++ if (!(q < -2.0) && !(q > 2.0)) { ++ double s = mutableDouble.getValue(); ++ if (Double.isNaN(s)) { ++ double t = this.barrierNoise.compute(pos); ++ mutableDouble.setValue(t); ++ r = t; ++ } else { ++ r = s; ++ } ++ } else { ++ r = 0.0; ++ } ++ ++ return 2.0 * (r + q); ++ } ++ ++ private double aquiferExtracted$postCalculateDensityModified(DensityFunction.FunctionContext pos, double q) { ++ double r; ++ if (!(q < -2.0) && !(q > 2.0)) { ++ double s = this.c2me$mutableDoubleThingy; ++ if (Double.isNaN(s)) { ++ double t = this.barrierNoise.compute(pos); ++ this.c2me$mutableDoubleThingy = t; ++ r = t; ++ } else { ++ r = s; ++ } ++ } else { ++ r = 0.0; ++ } ++ ++ return 2.0 * (r + q); ++ } ++ ++ private static double aquiferExtracted$getQ(double i, double d, double j) { ++ double e = i + 0.5 - d; ++ double f = j / 2.0; ++ double o = f - Math.abs(e); ++ double q; ++ if (e > 0.0) { ++ if (o > 0.0) { ++ q = o / 1.5; ++ } else { ++ q = o / 2.5; ++ } ++ } else { ++ double p = 3.0 + o; ++ if (p > 0.0) { ++ q = p / 3.0; ++ } else { ++ q = p / 10.0; ++ } ++ } ++ return q; ++ } ++ // DivineMC end - Chunk System Optimizations + } + } +diff --git a/net/minecraft/world/level/levelgen/Beardifier.java b/net/minecraft/world/level/levelgen/Beardifier.java +index 131923282c9ecbcb1d7f45a826da907c02bd2716..1af75406ba69c5eec4a41fe7a8dce0d07c259099 100644 +--- a/net/minecraft/world/level/levelgen/Beardifier.java ++++ b/net/minecraft/world/level/levelgen/Beardifier.java +@@ -29,6 +29,17 @@ public class Beardifier implements DensityFunctions.BeardifierOrMarker { + }); + private final ObjectListIterator pieceIterator; + private final ObjectListIterator junctionIterator; ++ // DivineMC start - Chunk System Optimizations ++ private Beardifier.Rigid[] c2me$pieceArray; ++ private JigsawJunction[] c2me$junctionArray; ++ ++ private void c2me$initArrays() { ++ this.c2me$pieceArray = com.google.common.collect.Iterators.toArray(this.pieceIterator, Beardifier.Rigid.class); ++ this.pieceIterator.back(Integer.MAX_VALUE); ++ this.c2me$junctionArray = com.google.common.collect.Iterators.toArray(this.junctionIterator, JigsawJunction.class); ++ this.junctionIterator.back(Integer.MAX_VALUE); ++ } ++ // DivineMC end - Chunk System Optimizations + + public static Beardifier forStructuresInChunk(StructureManager structureManager, ChunkPos chunkPos) { + int minBlockX = chunkPos.getMinBlockX(); +@@ -76,50 +87,44 @@ public class Beardifier implements DensityFunctions.BeardifierOrMarker { + this.junctionIterator = junctionIterator; + } + ++ // DivineMC start - Chunk System Optimizations + @Override + public double compute(DensityFunction.FunctionContext context) { ++ if (this.c2me$pieceArray == null || this.c2me$junctionArray == null) { ++ this.c2me$initArrays(); ++ } + int i = context.blockX(); +- int i1 = context.blockY(); +- int i2 = context.blockZ(); ++ int j = context.blockY(); ++ int k = context.blockZ(); + double d = 0.0; + +- while (this.pieceIterator.hasNext()) { +- Beardifier.Rigid rigid = this.pieceIterator.next(); +- BoundingBox boundingBox = rigid.box(); +- int groundLevelDelta = rigid.groundLevelDelta(); +- int max = Math.max(0, Math.max(boundingBox.minX() - i, i - boundingBox.maxX())); +- int max1 = Math.max(0, Math.max(boundingBox.minZ() - i2, i2 - boundingBox.maxZ())); +- int i3 = boundingBox.minY() + groundLevelDelta; +- int i4 = i1 - i3; +- +- int i5 = switch (rigid.terrainAdjustment()) { +- case NONE -> 0; +- case BURY, BEARD_THIN -> i4; +- case BEARD_BOX -> Math.max(0, Math.max(i3 - i1, i1 - boundingBox.maxY())); +- case ENCAPSULATE -> Math.max(0, Math.max(boundingBox.minY() - i1, i1 - boundingBox.maxY())); +- }; ++ for (Beardifier.Rigid piece : this.c2me$pieceArray) { ++ BoundingBox blockBox = piece.box(); ++ int l = piece.groundLevelDelta(); ++ int m = Math.max(0, Math.max(blockBox.minX() - i, i - blockBox.maxX())); ++ int n = Math.max(0, Math.max(blockBox.minZ() - k, k - blockBox.maxZ())); ++ int o = blockBox.minY() + l; ++ int p = j - o; + +- d += switch (rigid.terrainAdjustment()) { ++ d += switch (piece.terrainAdjustment()) { // 2 switch statement merged + case NONE -> 0.0; +- case BURY -> getBuryContribution(max, i5 / 2.0, max1); +- case BEARD_THIN, BEARD_BOX -> getBeardContribution(max, i5, max1, i4) * 0.8; +- case ENCAPSULATE -> getBuryContribution(max / 2.0, i5 / 2.0, max1 / 2.0) * 0.8; ++ case BURY -> getBuryContribution(m, (double)p / 2.0, n); ++ case BEARD_THIN -> getBeardContribution(m, p, n, p) * 0.8; ++ case BEARD_BOX -> getBeardContribution(m, Math.max(0, Math.max(o - j, j - blockBox.maxY())), n, p) * 0.8; ++ case ENCAPSULATE -> getBuryContribution((double)m / 2.0, (double)Math.max(0, Math.max(blockBox.minY() - j, j - blockBox.maxY())) / 2.0, (double)n / 2.0) * 0.8; + }; + } + +- this.pieceIterator.back(Integer.MAX_VALUE); +- +- while (this.junctionIterator.hasNext()) { +- JigsawJunction jigsawJunction = this.junctionIterator.next(); +- int i6 = i - jigsawJunction.getSourceX(); +- int groundLevelDelta = i1 - jigsawJunction.getSourceGroundY(); +- int max = i2 - jigsawJunction.getSourceZ(); +- d += getBeardContribution(i6, groundLevelDelta, max, groundLevelDelta) * 0.4; ++ for (JigsawJunction jigsawJunction : this.c2me$junctionArray) { ++ int r = i - jigsawJunction.getSourceX(); ++ int l = j - jigsawJunction.getSourceGroundY(); ++ int m = k - jigsawJunction.getSourceZ(); ++ d += getBeardContribution(r, l, m, l) * 0.4; + } + +- this.junctionIterator.back(Integer.MAX_VALUE); + return d; + } ++ // DivineMC end - Chunk System Optimizations + + @Override + public double minValue() { +@@ -132,8 +137,14 @@ public class Beardifier implements DensityFunctions.BeardifierOrMarker { + } + + private static double getBuryContribution(double x, double y, double z) { +- double len = Mth.length(x, y, z); +- return Mth.clampedMap(len, 0.0, 6.0, 1.0, 0.0); ++ // DivineMC start - Chunk System Optimizations ++ double d = Math.sqrt(x * x + y * y + z * z); ++ if (d > 6.0) { ++ return 0.0; ++ } else { ++ return 1.0 - d / 6.0; ++ } ++ // DivineMC end - Chunk System Optimizations + } + + private static double getBeardContribution(int x, int y, int z, int height) { +diff --git a/net/minecraft/world/level/levelgen/BelowZeroRetrogen.java b/net/minecraft/world/level/levelgen/BelowZeroRetrogen.java +index 4993ace2b3d615570de3d4b6621aeba3a3e7fe99..1be79332446559c95ae3048a71a6634fd01cf2e2 100644 +--- a/net/minecraft/world/level/levelgen/BelowZeroRetrogen.java ++++ b/net/minecraft/world/level/levelgen/BelowZeroRetrogen.java +@@ -82,6 +82,7 @@ public final class BelowZeroRetrogen { + } + + public void applyBedrockMask(ProtoChunk chunk) { ++ if (org.bxteam.divinemc.DivineConfig.smoothBedrockLayer) return; // DivineMC - Smooth bedrock layer + LevelHeightAccessor heightAccessorForGeneration = chunk.getHeightAccessorForGeneration(); + int minY = heightAccessorForGeneration.getMinY(); + int maxY = heightAccessorForGeneration.getMaxY(); +diff --git a/net/minecraft/world/level/levelgen/Column.java b/net/minecraft/world/level/levelgen/Column.java +index 4a1df0f8578c9ee5538ed8c94d3c7911f36f83b8..716c2c69843234cdef34339d859babc95ffe318c 100644 +--- a/net/minecraft/world/level/levelgen/Column.java ++++ b/net/minecraft/world/level/levelgen/Column.java +@@ -156,7 +156,7 @@ public abstract class Column { + } + + public int height() { +- return this.ceiling - this.floor - 1; ++ return net.minecraft.util.Mth.abs(this.ceiling - this.floor - 1); // DivineMC - Chunk System optimization + } + + @Override +diff --git a/net/minecraft/world/level/levelgen/LegacyRandomSource.java b/net/minecraft/world/level/levelgen/LegacyRandomSource.java +index c67168517774a0ad9ca43422a79ef14a8ea0c2e8..026dfbbb6c3fd5cd274dcbf721e5cf3af889e3d9 100644 +--- a/net/minecraft/world/level/levelgen/LegacyRandomSource.java ++++ b/net/minecraft/world/level/levelgen/LegacyRandomSource.java +@@ -53,13 +53,7 @@ public class LegacyRandomSource implements BitRandomSource { + return this.gaussianSource.nextGaussian(); + } + +- public static class LegacyPositionalRandomFactory implements PositionalRandomFactory { +- private final long seed; +- +- public LegacyPositionalRandomFactory(long seed) { +- this.seed = seed; +- } +- ++ public record LegacyPositionalRandomFactory(long seed) implements PositionalRandomFactory { // DivineMC - make record + @Override + public RandomSource at(int x, int y, int z) { + long seed = Mth.getSeed(x, y, z); +diff --git a/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java b/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java +index 65728ef17e63d71833677fdcbd5bb90794b4822b..eb61a3c995afe5af1cd385826e882acf441e2785 100644 +--- a/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java ++++ b/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java +@@ -65,11 +65,13 @@ public final class NoiseBasedChunkGenerator extends ChunkGenerator { + } + + private static Aquifer.FluidPicker createFluidPicker(NoiseGeneratorSettings settings) { +- Aquifer.FluidStatus fluidStatus = new Aquifer.FluidStatus(-54, Blocks.LAVA.defaultBlockState()); +- int seaLevel = settings.seaLevel(); +- Aquifer.FluidStatus fluidStatus1 = new Aquifer.FluidStatus(seaLevel, settings.defaultFluid()); +- Aquifer.FluidStatus fluidStatus2 = new Aquifer.FluidStatus(DimensionType.MIN_Y * 2, Blocks.AIR.defaultBlockState()); +- return (x, y, z) -> y < Math.min(-54, seaLevel) ? fluidStatus : fluidStatus1; ++ // DivineMC start - Chunk System Optimizations ++ Aquifer.FluidStatus fluidLevel = new Aquifer.FluidStatus(-54, Blocks.LAVA.defaultBlockState()); ++ int i = settings.seaLevel(); ++ Aquifer.FluidStatus fluidLevel2 = new Aquifer.FluidStatus(i, settings.defaultFluid()); ++ final int min = Math.min(-54, i); ++ return (j, k, lx) -> k < min ? fluidLevel : fluidLevel2; ++ // DivineMC end - Chunk System Optimizations + } + + @Override +@@ -294,30 +296,32 @@ public final class NoiseBasedChunkGenerator extends ChunkGenerator { + public CompletableFuture fillFromNoise(Blender blender, RandomState randomState, StructureManager structureManager, ChunkAccess chunk) { + NoiseSettings noiseSettings = this.settings.value().noiseSettings().clampToHeightAccessor(chunk.getHeightAccessorForGeneration()); + int minY = noiseSettings.minY(); +- int i = Mth.floorDiv(minY, noiseSettings.getCellHeight()); +- int i1 = Mth.floorDiv(noiseSettings.height(), noiseSettings.getCellHeight()); +- return i1 <= 0 ? CompletableFuture.completedFuture(chunk) : CompletableFuture.supplyAsync(() -> { +- int sectionIndex = chunk.getSectionIndex(i1 * noiseSettings.getCellHeight() - 1 + minY); +- int sectionIndex1 = chunk.getSectionIndex(minY); +- Set set = Sets.newHashSet(); +- +- for (int i2 = sectionIndex; i2 >= sectionIndex1; i2--) { +- LevelChunkSection section = chunk.getSection(i2); +- section.acquire(); +- set.add(section); +- } ++ // DivineMC start - Optimize noise fill ++ int minYDiv = Mth.floorDiv(minY, noiseSettings.getCellHeight()); ++ int cellHeightDiv = Mth.floorDiv(noiseSettings.height(), noiseSettings.getCellHeight()); + +- ChunkAccess var20; +- try { +- var20 = this.doFill(blender, structureManager, randomState, chunk, i, i1); +- } finally { +- for (LevelChunkSection levelChunkSection1 : set) { +- levelChunkSection1.release(); +- } ++ if (cellHeightDiv <= 0) { ++ return CompletableFuture.completedFuture(chunk); ++ } ++ ++ try { ++ int startIndex = chunk.getSectionIndex(cellHeightDiv * noiseSettings.getCellHeight() - 1 + minY); ++ int minYIndex = chunk.getSectionIndex(minY); ++ LevelChunkSection[] sections = chunk.getSections(); ++ ++ for (int i = startIndex; i >= minYIndex; --i) { ++ sections[i].acquire(); + } + +- return var20; +- }, Runnable::run); // Paper - rewrite chunk system ++ ChunkAccess access = this.doFill(blender, structureManager, randomState, chunk, minYDiv, cellHeightDiv); ++ for (int i = startIndex; i >= minYIndex; --i) { ++ sections[i].release(); ++ } ++ return CompletableFuture.completedFuture(access); ++ } catch (Throwable throwable) { ++ throw new RuntimeException("Unexpected error when running noise fill", throwable); ++ } ++ // DivineMC end - Optimize noise fill + } + + private ChunkAccess doFill(Blender blender, StructureManager structureManager, RandomState random, ChunkAccess chunk, int minCellY, int cellCountY) { +@@ -375,7 +379,7 @@ public final class NoiseBasedChunkGenerator extends ChunkGenerator { + + interpolatedState = this.debugPreliminarySurfaceLevel(noiseChunk, i10, i7, i13, interpolatedState); + if (interpolatedState != AIR && !SharedConstants.debugVoidTerrain(chunk.getPos())) { +- section.setBlockState(i11, i8, i14, interpolatedState, false); ++ optimizedBlockSetOp(section, i11, i8, i14, interpolatedState, false); // DivineMC - Optimize noise fill + heightmapUnprimed.update(i11, i7, i14, interpolatedState); + heightmapUnprimed1.update(i11, i7, i14, interpolatedState); + if (aquifer.shouldScheduleFluidUpdate() && !interpolatedState.getFluidState().isEmpty()) { +@@ -396,6 +400,26 @@ public final class NoiseBasedChunkGenerator extends ChunkGenerator { + return chunk; + } + ++ // DivineMC start - Optimize noise fill ++ private void optimizedBlockSetOp(@org.jetbrains.annotations.NotNull LevelChunkSection chunkSection, int chunkSectionBlockPosX, int chunkSectionBlockPosY, int chunkSectionBlockPosZ, @org.jetbrains.annotations.NotNull BlockState blockState, boolean lock) { ++ chunkSection.nonEmptyBlockCount += 1; ++ ++ if (!blockState.getFluidState().isEmpty()) { ++ chunkSection.tickingFluidCount += 1; ++ } ++ ++ if (blockState.isRandomlyTicking()) { ++ chunkSection.tickingBlockCount += 1; ++ } ++ ++ var blockStateId = chunkSection.states.data.palette.idFor(blockState); ++ chunkSection.states.data.storage().set( ++ chunkSection.states.strategy.getIndex(chunkSectionBlockPosX, chunkSectionBlockPosY, ++ chunkSectionBlockPosZ ++ ), blockStateId); ++ } ++ // DivineMC end - Optimize noise fill ++ + private BlockState debugPreliminarySurfaceLevel(NoiseChunk chunk, int x, int y, int z, BlockState state) { + return state; + } +diff --git a/net/minecraft/world/level/levelgen/NoiseSettings.java b/net/minecraft/world/level/levelgen/NoiseSettings.java +index 4cf3a364595ba5f81f741295695cb9a449bdf672..44df2ac0bd972c4d97fc89cd0c2d2d83480ca3e1 100644 +--- a/net/minecraft/world/level/levelgen/NoiseSettings.java ++++ b/net/minecraft/world/level/levelgen/NoiseSettings.java +@@ -8,7 +8,7 @@ import net.minecraft.core.QuartPos; + import net.minecraft.world.level.LevelHeightAccessor; + import net.minecraft.world.level.dimension.DimensionType; + +-public record NoiseSettings(int minY, int height, int noiseSizeHorizontal, int noiseSizeVertical) { ++public record NoiseSettings(int minY, int height, int noiseSizeHorizontal, int noiseSizeVertical, int horizontalCellBlockCount, int verticalCellBlockCount) { // DivineMC - NoiseSettings optimizations + public static final Codec CODEC = RecordCodecBuilder.create( + instance -> instance.group( + Codec.intRange(DimensionType.MIN_Y, DimensionType.MAX_Y).fieldOf("min_y").forGetter(NoiseSettings::minY), +@@ -16,7 +16,10 @@ public record NoiseSettings(int minY, int height, int noiseSizeHorizontal, int n + Codec.intRange(1, 4).fieldOf("size_horizontal").forGetter(NoiseSettings::noiseSizeHorizontal), + Codec.intRange(1, 4).fieldOf("size_vertical").forGetter(NoiseSettings::noiseSizeVertical) + ) +- .apply(instance, NoiseSettings::new) ++ // DivineMC start - NoiseSettings optimizations ++ .apply(instance, (Integer minY1, Integer height1, Integer noiseSizeHorizontal1, Integer noiseSizeVertical1) -> new NoiseSettings(minY1, height1, noiseSizeHorizontal1, noiseSizeVertical1, ++ QuartPos.toBlock(noiseSizeHorizontal1), QuartPos.toBlock(noiseSizeVertical1))) ++ // DivineMC end - NoiseSettings optimizations + ) + .comapFlatMap(NoiseSettings::guardY, Function.identity()); + protected static final NoiseSettings OVERWORLD_NOISE_SETTINGS = create(-64, 384, 1, 2); +@@ -36,7 +39,7 @@ public record NoiseSettings(int minY, int height, int noiseSizeHorizontal, int n + } + + public static NoiseSettings create(int minY, int height, int noiseSizeHorizontal, int noiseSizeVertical) { +- NoiseSettings noiseSettings = new NoiseSettings(minY, height, noiseSizeHorizontal, noiseSizeVertical); ++ NoiseSettings noiseSettings = new NoiseSettings(minY, height, noiseSizeHorizontal, noiseSizeVertical, QuartPos.toBlock(noiseSizeHorizontal), QuartPos.toBlock(noiseSizeVertical)); // DivineMC - NoiseSettings optimizations + guardY(noiseSettings).error().ifPresent(error -> { + throw new IllegalStateException(error.message()); + }); +@@ -44,16 +47,16 @@ public record NoiseSettings(int minY, int height, int noiseSizeHorizontal, int n + } + + public int getCellHeight() { +- return QuartPos.toBlock(this.noiseSizeVertical()); ++ return verticalCellBlockCount(); // DivineMC - NoiseSettings optimizations + } + + public int getCellWidth() { +- return QuartPos.toBlock(this.noiseSizeHorizontal()); ++ return horizontalCellBlockCount(); // DivineMC - NoiseSettings optimizations + } + + public NoiseSettings clampToHeightAccessor(LevelHeightAccessor heightAccessor) { + int max = Math.max(this.minY, heightAccessor.getMinY()); + int i = Math.min(this.minY + this.height, heightAccessor.getMaxY() + 1) - max; +- return new NoiseSettings(max, i, this.noiseSizeHorizontal, this.noiseSizeVertical); ++ return new NoiseSettings(max, i, this.noiseSizeHorizontal, this.noiseSizeVertical, QuartPos.toBlock(this.noiseSizeHorizontal), QuartPos.toBlock(this.noiseSizeVertical)); // DivineMC - NoiseSettings optimizations + } + } +diff --git a/net/minecraft/world/level/levelgen/SurfaceRules.java b/net/minecraft/world/level/levelgen/SurfaceRules.java +index bbf2995d352c22b3f6fb0de40f2932af1771c504..af852376b61d127a5e251a7a6fcb35c25d1d4e1f 100644 +--- a/net/minecraft/world/level/levelgen/SurfaceRules.java ++++ b/net/minecraft/world/level/levelgen/SurfaceRules.java +@@ -185,7 +185,7 @@ public class SurfaceRules { + + @Override + protected boolean compute() { +- return this.context.biome.get().is(BiomeConditionSource.this.biomeNameTest); ++ return this.context.biome.is(BiomeConditionSource.this.biomeNameTest); // DivineMC - Chunk System Optimizations + } + } + +@@ -281,7 +281,7 @@ public class SurfaceRules { + private int minSurfaceLevel; + long lastUpdateY = -9223372036854775807L; + final BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos(); +- Supplier> biome; ++ Holder biome; // DivineMC - Chunk System Optimizations + public int blockY; + int waterHeight; + int stoneDepthBelow; +@@ -314,7 +314,10 @@ public class SurfaceRules { + + protected void updateY(int stoneDepthAbove, int stoneDepthBelow, int waterHeight, int blockX, int blockY, int blockZ) { + this.lastUpdateY++; +- this.biome = Suppliers.memoize(() -> this.biomeGetter.apply(this.pos.set(blockX, blockY, blockZ))); ++ // DivineMC start - Chunk System Optimizations ++ this.pos.set(blockX, blockY, blockZ); ++ this.biome = this.biomeGetter.apply(this.pos); ++ // DivineMC end - Chunk System Optimizations + this.blockY = blockY; + this.waterHeight = waterHeight; + this.stoneDepthBelow = stoneDepthBelow; +@@ -441,7 +444,6 @@ public class SurfaceRules { + protected boolean compute() { + return this.context + .biome +- .get() + .value() + .coldEnoughToSnow(this.context.pos.set(this.context.blockX, this.context.blockY, this.context.blockZ), this.context.getSeaLevel()); + } +diff --git a/net/minecraft/world/level/levelgen/WorldgenRandom.java b/net/minecraft/world/level/levelgen/WorldgenRandom.java +index c2d7cd788071e25b8ba2503c30ae80c7a9f353ed..0a2e13c4a3db6517267e1f9e74b6152c73e8351f 100644 +--- a/net/minecraft/world/level/levelgen/WorldgenRandom.java ++++ b/net/minecraft/world/level/levelgen/WorldgenRandom.java +@@ -73,7 +73,7 @@ public class WorldgenRandom extends LegacyRandomSource { + } + + public static enum Algorithm { +- LEGACY(LegacyRandomSource::new), ++ LEGACY(ThreadSafeLegacyRandomSource::new), // DivineMC - Chunk System optimization + XOROSHIRO(XoroshiroRandomSource::new); + + private final LongFunction constructor; +diff --git a/net/minecraft/world/level/levelgen/XoroshiroRandomSource.java b/net/minecraft/world/level/levelgen/XoroshiroRandomSource.java +index 9d3a9ca1e13cd80f468f1352bbb74345f03903dd..d97b9b43686bda0a95fc02f6ca31b2d07d603a32 100644 +--- a/net/minecraft/world/level/levelgen/XoroshiroRandomSource.java ++++ b/net/minecraft/world/level/levelgen/XoroshiroRandomSource.java +@@ -106,15 +106,7 @@ public class XoroshiroRandomSource implements RandomSource { + return this.randomNumberGenerator.nextLong() >>> 64 - bits; + } + +- public static class XoroshiroPositionalRandomFactory implements PositionalRandomFactory { +- private final long seedLo; +- private final long seedHi; +- +- public XoroshiroPositionalRandomFactory(long seedLo, long seedHi) { +- this.seedLo = seedLo; +- this.seedHi = seedHi; +- } +- ++ public record XoroshiroPositionalRandomFactory(long seedLo, long seedHi) implements PositionalRandomFactory { // DivineMC - make record + @Override + public RandomSource at(int x, int y, int z) { + long seed = Mth.getSeed(x, y, z); +diff --git a/net/minecraft/world/level/levelgen/feature/stateproviders/RandomizedIntStateProvider.java b/net/minecraft/world/level/levelgen/feature/stateproviders/RandomizedIntStateProvider.java +index f4009671880b00ecec98fe604215e2824e453cdf..7b00301da0b3fb6429d04e4d10cafa30e168aa62 100644 +--- a/net/minecraft/world/level/levelgen/feature/stateproviders/RandomizedIntStateProvider.java ++++ b/net/minecraft/world/level/levelgen/feature/stateproviders/RandomizedIntStateProvider.java +@@ -54,18 +54,21 @@ public class RandomizedIntStateProvider extends BlockStateProvider { + } + + @Override ++ @SuppressWarnings("DataFlowIssue") + public BlockState getState(RandomSource random, BlockPos pos) { +- BlockState state = this.source.getState(random, pos); +- if (this.property == null || !state.hasProperty(this.property)) { +- IntegerProperty integerProperty = findProperty(state, this.propertyName); +- if (integerProperty == null) { +- return state; ++ BlockState blockState = this.source.getState(random, pos); ++ IntegerProperty propertyLocal = this.property; ++ if (propertyLocal == null || !blockState.hasProperty(propertyLocal)) { ++ IntegerProperty intProperty = findProperty(blockState, this.propertyName); ++ if (intProperty == null) { ++ return blockState; + } + +- this.property = integerProperty; ++ propertyLocal = intProperty; ++ this.property = intProperty; + } + +- return state.setValue(this.property, Integer.valueOf(this.values.sample(random))); ++ return (BlockState)blockState.setValue(propertyLocal, this.values.sample(random)); + } + + @Nullable +diff --git a/net/minecraft/world/level/levelgen/structure/ScatteredFeaturePiece.java b/net/minecraft/world/level/levelgen/structure/ScatteredFeaturePiece.java +index d122221deefb218db962e97ba2d958c33d903b8a..56311b439ac22700593d2d31da3a4efe3a519d53 100644 +--- a/net/minecraft/world/level/levelgen/structure/ScatteredFeaturePiece.java ++++ b/net/minecraft/world/level/levelgen/structure/ScatteredFeaturePiece.java +@@ -12,7 +12,7 @@ public abstract class ScatteredFeaturePiece extends StructurePiece { + protected final int width; + protected final int height; + protected final int depth; +- protected int heightPosition = -1; ++ protected volatile int heightPosition = -1; // DivineMC - Chunk System optimization - make volatile + + protected ScatteredFeaturePiece(StructurePieceType type, int x, int y, int z, int width, int height, int depth, Direction orientation) { + super(type, 0, StructurePiece.makeBoundingBox(x, y, z, orientation, width, height, depth)); +diff --git a/net/minecraft/world/level/levelgen/structure/StructureCheck.java b/net/minecraft/world/level/levelgen/structure/StructureCheck.java +index f7dc4957b38878ddd3bfc7546be8a4e0af65c807..4023115e8120a6ccbc2a5bf7f2d17ffb2f808eb7 100644 +--- a/net/minecraft/world/level/levelgen/structure/StructureCheck.java ++++ b/net/minecraft/world/level/levelgen/structure/StructureCheck.java +@@ -46,6 +46,7 @@ public class StructureCheck { + private final LevelHeightAccessor heightAccessor; + private final BiomeSource biomeSource; + private final long seed; ++ private Object mapMutex = new Object(); // DivineMC - Chunk System Optimizations + private final DataFixer fixerUpper; + // Paper start - rewrite chunk system + // make sure to purge entries from the maps to prevent memory leaks +@@ -235,15 +236,13 @@ public class StructureCheck { + } + + private void storeFullResults(long chunkPos, Object2IntMap structureChunks) { +- // Paper start - rewrite chunk system ++ // DivineMC start - Chunk System Optimizations + this.loadedChunksSafe.put(chunkPos, deduplicateEmptyMap(structureChunks)); +- // once we insert into loadedChunks, we don't really need to be very careful about removing everything +- // from this map, as everything that checks this map uses loadedChunks first +- // so, one way or another it's a race condition that doesn't matter +- for (ca.spottedleaf.moonrise.common.map.SynchronisedLong2BooleanMap value : this.featureChecksSafe.values()) { +- value.remove(chunkPos); ++ ++ synchronized (this.mapMutex) { ++ this.featureChecksSafe.values().forEach((long2BooleanMap) -> long2BooleanMap.remove(chunkPos)); + } +- // Paper end - rewrite chunk system ++ // DivineMC end - Chunk System Optimizations + } + + public void incrementReference(ChunkPos pos, Structure structure) { +diff --git a/net/minecraft/world/level/levelgen/structure/StructureStart.java b/net/minecraft/world/level/levelgen/structure/StructureStart.java +index 4dafa79dd4ec55a443ba3731a79e7cd6e8052f48..8aeab4d773473ad20b1c64295c93d6fcb4ea02a1 100644 +--- a/net/minecraft/world/level/levelgen/structure/StructureStart.java ++++ b/net/minecraft/world/level/levelgen/structure/StructureStart.java +@@ -26,7 +26,7 @@ public final class StructureStart { + private final Structure structure; + private final PiecesContainer pieceContainer; + private final ChunkPos chunkPos; +- private int references; ++ private final java.util.concurrent.atomic.AtomicInteger references = new java.util.concurrent.atomic.AtomicInteger(); // DivineMC - Chunk System optimization + @Nullable + private volatile BoundingBox cachedBoundingBox; + +@@ -39,7 +39,7 @@ public final class StructureStart { + public StructureStart(Structure structure, ChunkPos chunkPos, int references, PiecesContainer pieceContainer) { + this.structure = structure; + this.chunkPos = chunkPos; +- this.references = references; ++ this.references.set(references); // DivineMC - Chunk System optimization + this.pieceContainer = pieceContainer; + } + +@@ -126,7 +126,7 @@ public final class StructureStart { + compoundTag.putString("id", context.registryAccess().lookupOrThrow(Registries.STRUCTURE).getKey(this.structure).toString()); + compoundTag.putInt("ChunkX", chunkPos.x); + compoundTag.putInt("ChunkZ", chunkPos.z); +- compoundTag.putInt("references", this.references); ++ compoundTag.putInt("references", this.references.get()); // DivineMC - Chunk System optimization + compoundTag.put("Children", this.pieceContainer.save(context)); + return compoundTag; + } else { +@@ -144,15 +144,15 @@ public final class StructureStart { + } + + public boolean canBeReferenced() { +- return this.references < this.getMaxReferences(); ++ return this.references.get() < this.getMaxReferences(); // DivineMC - Chunk System optimization + } + + public void addReference() { +- this.references++; ++ this.references.getAndIncrement(); // DivineMC - Chunk System optimization + } + + public int getReferences() { +- return this.references; ++ return this.references.get(); // DivineMC - Chunk System optimization + } + + protected int getMaxReferences() { +diff --git a/net/minecraft/world/level/levelgen/structure/pools/StructurePoolElement.java b/net/minecraft/world/level/levelgen/structure/pools/StructurePoolElement.java +index c84d865837e0f009fcde19e14a44fa43aefe660a..64d7adbd4aa398044a1d68d51e463b672ee81edf 100644 +--- a/net/minecraft/world/level/levelgen/structure/pools/StructurePoolElement.java ++++ b/net/minecraft/world/level/levelgen/structure/pools/StructurePoolElement.java +@@ -27,9 +27,9 @@ import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemp + import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager; + + public abstract class StructurePoolElement { +- public static final Codec CODEC = BuiltInRegistries.STRUCTURE_POOL_ELEMENT ++ public static final Codec CODEC = new org.bxteam.divinemc.util.SynchronizedCodec<>(BuiltInRegistries.STRUCTURE_POOL_ELEMENT // DivineMC - Chunk System Optimizations + .byNameCodec() +- .dispatch("element_type", StructurePoolElement::getType, StructurePoolElementType::codec); ++ .dispatch("element_type", StructurePoolElement::getType, StructurePoolElementType::codec)); // DivineMC - Chunk System Optimizations + private static final Holder EMPTY = Holder.direct(new StructureProcessorList(List.of())); + @Nullable + private volatile StructureTemplatePool.Projection projection; +diff --git a/net/minecraft/world/level/levelgen/structure/structures/DesertPyramidPiece.java b/net/minecraft/world/level/levelgen/structure/structures/DesertPyramidPiece.java +index e8c125ddb335bfe81b8f1dbb91f7f7ccec11d980..4b6b18b3d255f6a72e6be3b85260732b8eab533c 100644 +--- a/net/minecraft/world/level/levelgen/structure/structures/DesertPyramidPiece.java ++++ b/net/minecraft/world/level/levelgen/structure/structures/DesertPyramidPiece.java +@@ -23,29 +23,44 @@ import net.minecraft.world.level.storage.loot.BuiltInLootTables; + public class DesertPyramidPiece extends ScatteredFeaturePiece { + public static final int WIDTH = 21; + public static final int DEPTH = 21; +- private final boolean[] hasPlacedChest = new boolean[4]; +- private final List potentialSuspiciousSandWorldPositions = new ArrayList<>(); ++ private final java.util.concurrent.atomic.AtomicReferenceArray hasPlacedChestAtomic = new java.util.concurrent.atomic.AtomicReferenceArray<>(new Boolean[4]); // DivineMC - Chunk System Optimizations ++ private final java.util.Set potentialSuspiciousSandWorldPositions = com.google.common.collect.Sets.newConcurrentHashSet(); // DivineMC - Chunk System Optimizations + private BlockPos randomCollapsedRoofPos = BlockPos.ZERO; + ++ // DivineMC start - Chunk System Optimizations ++ private void init() { ++ for (int i = 0; i < this.hasPlacedChestAtomic.length(); ++i) { ++ if (this.hasPlacedChestAtomic.get(i) == null) { ++ this.hasPlacedChestAtomic.set(i, false); ++ } ++ } ++ } ++ // DivineMC end - Chunk System Optimizations ++ + public DesertPyramidPiece(RandomSource random, int x, int z) { + super(StructurePieceType.DESERT_PYRAMID_PIECE, x, 64, z, 21, 15, 21, getRandomHorizontalDirection(random)); ++ init(); // DivineMC - Chunk System Optimizations + } + + public DesertPyramidPiece(CompoundTag tag) { + super(StructurePieceType.DESERT_PYRAMID_PIECE, tag); +- this.hasPlacedChest[0] = tag.getBoolean("hasPlacedChest0"); +- this.hasPlacedChest[1] = tag.getBoolean("hasPlacedChest1"); +- this.hasPlacedChest[2] = tag.getBoolean("hasPlacedChest2"); +- this.hasPlacedChest[3] = tag.getBoolean("hasPlacedChest3"); ++ // DivineMC start - Chunk System Optimizations ++ this.hasPlacedChestAtomic.set(0, tag.getBoolean("hasPlacedChest0")); ++ this.hasPlacedChestAtomic.set(1, tag.getBoolean("hasPlacedChest1")); ++ this.hasPlacedChestAtomic.set(2, tag.getBoolean("hasPlacedChest2")); ++ this.hasPlacedChestAtomic.set(3, tag.getBoolean("hasPlacedChest3")); ++ // DivineMC end - Chunk System Optimizations + } + + @Override + protected void addAdditionalSaveData(StructurePieceSerializationContext context, CompoundTag tag) { + super.addAdditionalSaveData(context, tag); +- tag.putBoolean("hasPlacedChest0", this.hasPlacedChest[0]); +- tag.putBoolean("hasPlacedChest1", this.hasPlacedChest[1]); +- tag.putBoolean("hasPlacedChest2", this.hasPlacedChest[2]); +- tag.putBoolean("hasPlacedChest3", this.hasPlacedChest[3]); ++ // DivineMC start - Chunk System Optimizations ++ tag.putBoolean("hasPlacedChest0", this.hasPlacedChestAtomic.get(0)); ++ tag.putBoolean("hasPlacedChest1", this.hasPlacedChestAtomic.get(1)); ++ tag.putBoolean("hasPlacedChest2", this.hasPlacedChestAtomic.get(2)); ++ tag.putBoolean("hasPlacedChest3", this.hasPlacedChestAtomic.get(3)); ++ // DivineMC end - Chunk System Optimizations + } + + @Override +@@ -287,12 +302,12 @@ public class DesertPyramidPiece extends ScatteredFeaturePiece { + this.placeBlock(level, Blocks.CUT_SANDSTONE.defaultBlockState(), 10, -11, 13, box); + + for (Direction direction : Direction.Plane.HORIZONTAL) { +- if (!this.hasPlacedChest[direction.get2DDataValue()]) { ++ if (!this.hasPlacedChestAtomic.get(direction.get2DDataValue())) { // DivineMC - Chunk System Optimizations + int i4 = direction.getStepX() * 2; + int i5 = direction.getStepZ() * 2; +- this.hasPlacedChest[direction.get2DDataValue()] = this.createChest( ++ this.hasPlacedChestAtomic.set(direction.get2DDataValue(), this.createChest( // DivineMC - Chunk System Optimizations + level, box, random, 10 + i4, -11, 10 + i5, BuiltInLootTables.DESERT_PYRAMID +- ); ++ )); // DivineMC - Chunk System Optimizations + } + } + +@@ -419,7 +434,7 @@ public class DesertPyramidPiece extends ScatteredFeaturePiece { + this.randomCollapsedRoofPos = new BlockPos(this.getWorldX(i1, randomInt), this.getWorldY(y), this.getWorldZ(i1, randomInt)); + } + +- public List getPotentialSuspiciousSandWorldPositions() { ++ public java.util.Set getPotentialSuspiciousSandWorldPositions() { // DivineMC - Chunk System Optimizations + return this.potentialSuspiciousSandWorldPositions; + } + +diff --git a/net/minecraft/world/level/levelgen/structure/structures/JungleTemplePiece.java b/net/minecraft/world/level/levelgen/structure/structures/JungleTemplePiece.java +index 82600a247e13b82fc56273e1fd8483c8102a8d3d..cd9b24f017ebea5df55c7d15ee3d793ad612fbc6 100644 +--- a/net/minecraft/world/level/levelgen/structure/structures/JungleTemplePiece.java ++++ b/net/minecraft/world/level/levelgen/structure/structures/JungleTemplePiece.java +@@ -30,10 +30,12 @@ import net.minecraft.world.level.storage.loot.BuiltInLootTables; + public class JungleTemplePiece extends ScatteredFeaturePiece { + public static final int WIDTH = 12; + public static final int DEPTH = 15; +- private boolean placedMainChest; +- private boolean placedHiddenChest; +- private boolean placedTrap1; +- private boolean placedTrap2; ++ // DivineMC start - Chunk System Optimizations ++ private final java.util.concurrent.atomic.AtomicBoolean placedMainChest = new java.util.concurrent.atomic.AtomicBoolean(false); ++ private final java.util.concurrent.atomic.AtomicBoolean placedHiddenChest = new java.util.concurrent.atomic.AtomicBoolean(false); ++ private final java.util.concurrent.atomic.AtomicBoolean placedTrap1 = new java.util.concurrent.atomic.AtomicBoolean(false); ++ private final java.util.concurrent.atomic.AtomicBoolean placedTrap2 = new java.util.concurrent.atomic.AtomicBoolean(false); ++ // DivineMC end - Chunk System Optimizations + private static final JungleTemplePiece.MossStoneSelector STONE_SELECTOR = new JungleTemplePiece.MossStoneSelector(); + + public JungleTemplePiece(RandomSource random, int x, int z) { +@@ -42,19 +44,23 @@ public class JungleTemplePiece extends ScatteredFeaturePiece { + + public JungleTemplePiece(CompoundTag tag) { + super(StructurePieceType.JUNGLE_PYRAMID_PIECE, tag); +- this.placedMainChest = tag.getBoolean("placedMainChest"); +- this.placedHiddenChest = tag.getBoolean("placedHiddenChest"); +- this.placedTrap1 = tag.getBoolean("placedTrap1"); +- this.placedTrap2 = tag.getBoolean("placedTrap2"); ++ // DivineMC start - Chunk System Optimizations ++ this.placedMainChest.set(tag.getBoolean("placedMainChest")); ++ this.placedHiddenChest.set(tag.getBoolean("placedHiddenChest")); ++ this.placedTrap1.set(tag.getBoolean("placedTrap1")); ++ this.placedTrap2.set(tag.getBoolean("placedTrap2")); ++ // DivineMC end - Chunk System Optimizations + } + + @Override + protected void addAdditionalSaveData(StructurePieceSerializationContext context, CompoundTag tag) { + super.addAdditionalSaveData(context, tag); +- tag.putBoolean("placedMainChest", this.placedMainChest); +- tag.putBoolean("placedHiddenChest", this.placedHiddenChest); +- tag.putBoolean("placedTrap1", this.placedTrap1); +- tag.putBoolean("placedTrap2", this.placedTrap2); ++ // DivineMC start - Chunk System Optimizations ++ tag.putBoolean("placedMainChest", this.placedMainChest.get()); ++ tag.putBoolean("placedHiddenChest", this.placedHiddenChest.get()); ++ tag.putBoolean("placedTrap1", this.placedTrap1.get()); ++ tag.putBoolean("placedTrap2", this.placedTrap2.get()); ++ // DivineMC end - Chunk System Optimizations + } + + @Override +@@ -242,8 +248,8 @@ public class JungleTemplePiece extends ScatteredFeaturePiece { + box + ); + this.placeBlock(level, Blocks.MOSSY_COBBLESTONE.defaultBlockState(), 3, -3, 1, box); +- if (!this.placedTrap1) { +- this.placedTrap1 = this.createDispenser(level, box, random, 3, -2, 1, Direction.NORTH, BuiltInLootTables.JUNGLE_TEMPLE_DISPENSER); ++ if (!this.placedTrap1.get()) { // DivineMC - Chunk System Optimizations ++ this.placedTrap1.set(this.createDispenser(level, box, random, 3, -2, 1, Direction.NORTH, BuiltInLootTables.JUNGLE_TEMPLE_DISPENSER)); // DivineMC - Chunk System Optimizations + } + + this.placeBlock(level, Blocks.VINE.defaultBlockState().setValue(VineBlock.SOUTH, Boolean.valueOf(true)), 3, -2, 2, box); +@@ -340,14 +346,14 @@ public class JungleTemplePiece extends ScatteredFeaturePiece { + ); + this.placeBlock(level, Blocks.MOSSY_COBBLESTONE.defaultBlockState(), 9, -3, 4, box); + this.placeBlock(level, blockState4, 9, -2, 4, box); +- if (!this.placedTrap2) { +- this.placedTrap2 = this.createDispenser(level, box, random, 9, -2, 3, Direction.WEST, BuiltInLootTables.JUNGLE_TEMPLE_DISPENSER); ++ if (!this.placedTrap2.get()) { // DivineMC - Chunk System Optimizations ++ this.placedTrap2.set(this.createDispenser(level, box, random, 9, -2, 3, Direction.WEST, BuiltInLootTables.JUNGLE_TEMPLE_DISPENSER)); // DivineMC - Chunk System Optimizations + } + + this.placeBlock(level, Blocks.VINE.defaultBlockState().setValue(VineBlock.EAST, Boolean.valueOf(true)), 8, -1, 3, box); + this.placeBlock(level, Blocks.VINE.defaultBlockState().setValue(VineBlock.EAST, Boolean.valueOf(true)), 8, -2, 3, box); +- if (!this.placedMainChest) { +- this.placedMainChest = this.createChest(level, box, random, 8, -3, 3, BuiltInLootTables.JUNGLE_TEMPLE); ++ if (!this.placedMainChest.get()) { // DivineMC - Chunk System Optimizations ++ this.placedMainChest.set(this.createChest(level, box, random, 8, -3, 3, BuiltInLootTables.JUNGLE_TEMPLE)); // DivineMC - Chunk System Optimizations + } + + this.placeBlock(level, Blocks.MOSSY_COBBLESTONE.defaultBlockState(), 9, -3, 2, box); +@@ -390,8 +396,8 @@ public class JungleTemplePiece extends ScatteredFeaturePiece { + this.placeBlock(level, Blocks.STICKY_PISTON.defaultBlockState().setValue(PistonBaseBlock.FACING, Direction.WEST), 10, -2, 8, box); + this.placeBlock(level, Blocks.STICKY_PISTON.defaultBlockState().setValue(PistonBaseBlock.FACING, Direction.WEST), 10, -1, 8, box); + this.placeBlock(level, Blocks.REPEATER.defaultBlockState().setValue(RepeaterBlock.FACING, Direction.NORTH), 10, -2, 10, box); +- if (!this.placedHiddenChest) { +- this.placedHiddenChest = this.createChest(level, box, random, 9, -3, 10, BuiltInLootTables.JUNGLE_TEMPLE); ++ if (!this.placedHiddenChest.get()) { // DivineMC - Chunk System Optimizations ++ this.placedHiddenChest.set(this.createChest(level, box, random, 9, -3, 10, BuiltInLootTables.JUNGLE_TEMPLE)); // DivineMC - Chunk System Optimizations + } + } + } +diff --git a/net/minecraft/world/level/levelgen/structure/structures/MineshaftPieces.java b/net/minecraft/world/level/levelgen/structure/structures/MineshaftPieces.java +index 1acd506f7c0679fa9f69b6ab221002b28d00c3e5..52237559477e1965cb13a94ee4e6b4ff2ee99b03 100644 +--- a/net/minecraft/world/level/levelgen/structure/structures/MineshaftPieces.java ++++ b/net/minecraft/world/level/levelgen/structure/structures/MineshaftPieces.java +@@ -95,7 +95,7 @@ public class MineshaftPieces { + public static class MineShaftCorridor extends MineshaftPieces.MineShaftPiece { + private final boolean hasRails; + private final boolean spiderCorridor; +- private boolean hasPlacedSpider; ++ private volatile boolean hasPlacedSpider; // DivineMC - Chunk System Optimizations + private final int numSections; + + public MineShaftCorridor(CompoundTag tag) { +@@ -954,7 +954,7 @@ public class MineshaftPieces { + } + + public static class MineShaftRoom extends MineshaftPieces.MineShaftPiece { +- private final List childEntranceBoxes = Lists.newLinkedList(); ++ private final List childEntranceBoxes = java.util.Collections.synchronizedList(Lists.newLinkedList()); // DivineMC - Chunk System Optimizations + + public MineShaftRoom(int genDepth, RandomSource random, int x, int z, MineshaftStructure.Type type) { + super( +diff --git a/net/minecraft/world/level/levelgen/structure/structures/NetherFortressPieces.java b/net/minecraft/world/level/levelgen/structure/structures/NetherFortressPieces.java +index 20ddc6403823f72ef15d10e3d53b5f4d13e82724..081fbe004cac5bca67210a1abcca7fa44642f417 100644 +--- a/net/minecraft/world/level/levelgen/structure/structures/NetherFortressPieces.java ++++ b/net/minecraft/world/level/levelgen/structure/structures/NetherFortressPieces.java +@@ -1301,7 +1301,7 @@ public class NetherFortressPieces { + int i = 0; + + for (NetherFortressPieces.PieceWeight pieceWeight : weights) { +- if (pieceWeight.maxPlaceCount > 0 && pieceWeight.placeCount < pieceWeight.maxPlaceCount) { ++ if (pieceWeight.maxPlaceCount > 0 && pieceWeight.placeCount.get() < pieceWeight.maxPlaceCount) { // DivineMC - Chunk System Optimizations + flag = true; + } + +@@ -1341,7 +1341,7 @@ public class NetherFortressPieces { + pieceWeight, pieces, random, x, y, z, orientation, genDepth + ); + if (netherBridgePiece != null) { +- pieceWeight.placeCount++; ++ pieceWeight.placeCount.set(pieceWeight.placeCount.get() + 1); // DivineMC - Chunk System Optimizations + startPiece.previousPiece = pieceWeight; + if (!pieceWeight.isValid()) { + weights.remove(pieceWeight); +@@ -1576,7 +1576,7 @@ public class NetherFortressPieces { + static class PieceWeight { + public final Class pieceClass; + public final int weight; +- public int placeCount; ++ public final ThreadLocal placeCount = ThreadLocal.withInitial(() -> 0); // DivineMC - Chunk System Optimizations + public final int maxPlaceCount; + public final boolean allowInRow; + +@@ -1592,11 +1592,11 @@ public class NetherFortressPieces { + } + + public boolean doPlace(int genDepth) { +- return this.maxPlaceCount == 0 || this.placeCount < this.maxPlaceCount; ++ return this.maxPlaceCount == 0 || this.placeCount.get() < this.maxPlaceCount; // DivineMC - Chunk System Optimizations + } + + public boolean isValid() { +- return this.maxPlaceCount == 0 || this.placeCount < this.maxPlaceCount; ++ return this.maxPlaceCount == 0 || this.placeCount.get() < this.maxPlaceCount; // DivineMC - Chunk System Optimizations + } + } + +@@ -1746,24 +1746,24 @@ public class NetherFortressPieces { + } + + public static class StartPiece extends NetherFortressPieces.BridgeCrossing { +- public NetherFortressPieces.PieceWeight previousPiece; ++ public volatile NetherFortressPieces.PieceWeight previousPiece; // DivineMC - Chunk System Optimizations + public List availableBridgePieces; + public List availableCastlePieces; + public final List pendingChildren = Lists.newArrayList(); + + public StartPiece(RandomSource random, int x, int z) { + super(x, z, getRandomHorizontalDirection(random)); +- this.availableBridgePieces = Lists.newArrayList(); ++ this.availableBridgePieces = java.util.Collections.synchronizedList(Lists.newArrayList()); // DivineMC - Chunk System Optimizations + + for (NetherFortressPieces.PieceWeight pieceWeight : NetherFortressPieces.BRIDGE_PIECE_WEIGHTS) { +- pieceWeight.placeCount = 0; ++ pieceWeight.placeCount.remove(); // DivineMC - Chunk System Optimizations + this.availableBridgePieces.add(pieceWeight); + } + +- this.availableCastlePieces = Lists.newArrayList(); ++ this.availableCastlePieces = java.util.Collections.synchronizedList(Lists.newArrayList()); // DivineMC - Chunk System Optimizations + + for (NetherFortressPieces.PieceWeight pieceWeight : NetherFortressPieces.CASTLE_PIECE_WEIGHTS) { +- pieceWeight.placeCount = 0; ++ pieceWeight.placeCount.remove(); // DivineMC - Chunk System Optimizations + this.availableCastlePieces.add(pieceWeight); + } + } +diff --git a/net/minecraft/world/level/levelgen/structure/structures/StrongholdPieces.java b/net/minecraft/world/level/levelgen/structure/structures/StrongholdPieces.java +index b28829043c558af04511108691a4e9042a5afc62..b9d9fe3affd1f991ea3a742bc48a518f19819dfd 100644 +--- a/net/minecraft/world/level/levelgen/structure/structures/StrongholdPieces.java ++++ b/net/minecraft/world/level/levelgen/structure/structures/StrongholdPieces.java +@@ -63,32 +63,36 @@ public class StrongholdPieces { + } + } + }; +- private static List currentPieces; +- static Class imposedPiece; +- private static int totalWeight; ++ // DivineMC start - Chunk System Optimizations ++ private static final ThreadLocal> currentPieces = new ThreadLocal>(); ++ static final ThreadLocal> imposedPiece = new ThreadLocal>(); ++ private static final ThreadLocal totalWeight = ThreadLocal.withInitial(() -> 0); ++ // DivineMC end - Chunk System Optimizations + static final StrongholdPieces.SmoothStoneSelector SMOOTH_STONE_SELECTOR = new StrongholdPieces.SmoothStoneSelector(); + + public static void resetPieces() { +- currentPieces = Lists.newArrayList(); ++ // DivineMC start - Chunk System Optimizations ++ currentPieces.set(Lists.newArrayList()); + + for (StrongholdPieces.PieceWeight pieceWeight : STRONGHOLD_PIECE_WEIGHTS) { +- pieceWeight.placeCount = 0; +- currentPieces.add(pieceWeight); ++ pieceWeight.placeCount.set(0); ++ currentPieces.get().add(pieceWeight); + } + +- imposedPiece = null; ++ imposedPiece.set(null); ++ // DivineMC end - Chunk System Optimizations + } + + private static boolean updatePieceWeight() { + boolean flag = false; +- totalWeight = 0; ++ totalWeight.set(0); // DivineMC - Chunk System Optimizations + +- for (StrongholdPieces.PieceWeight pieceWeight : currentPieces) { +- if (pieceWeight.maxPlaceCount > 0 && pieceWeight.placeCount < pieceWeight.maxPlaceCount) { ++ for (StrongholdPieces.PieceWeight pieceWeight : currentPieces.get()) { // DivineMC - Chunk System Optimizations ++ if (pieceWeight.maxPlaceCount > 0 && pieceWeight.placeCount.get() < pieceWeight.maxPlaceCount) { // DivineMC - Chunk System Optimizations + flag = true; + } + +- totalWeight = totalWeight + pieceWeight.weight; ++ totalWeight.set(totalWeight.get() + pieceWeight.weight); // DivineMC - Chunk System Optimizations + } + + return flag; +@@ -138,9 +142,11 @@ public class StrongholdPieces { + if (!updatePieceWeight()) { + return null; + } else { +- if (imposedPiece != null) { +- StrongholdPieces.StrongholdPiece strongholdPiece = findAndCreatePieceFactory(imposedPiece, pieces, random, x, y, z, direction, genDepth); +- imposedPiece = null; ++ // DivineMC start - Chunk System Optimizations ++ if (imposedPiece.get() != null) { ++ StrongholdPieces.StrongholdPiece strongholdPiece = findAndCreatePieceFactory(imposedPiece.get(), pieces, random, x, y, z, direction, genDepth); ++ imposedPiece.set(null); ++ // DivineMC end - Chunk System Optimizations + if (strongholdPiece != null) { + return strongholdPiece; + } +@@ -150,9 +156,9 @@ public class StrongholdPieces { + + while (i < 5) { + i++; +- int randomInt = random.nextInt(totalWeight); ++ int randomInt = random.nextInt(totalWeight.get()); // DivineMC - Chunk System Optimizations + +- for (StrongholdPieces.PieceWeight pieceWeight : currentPieces) { ++ for (StrongholdPieces.PieceWeight pieceWeight : currentPieces.get()) { // DivineMC - Chunk System Optimizations + randomInt -= pieceWeight.weight; + if (randomInt < 0) { + if (!pieceWeight.doPlace(genDepth) || pieceWeight == piece.previousPiece) { +@@ -163,10 +169,10 @@ public class StrongholdPieces { + pieceWeight.pieceClass, pieces, random, x, y, z, direction, genDepth + ); + if (strongholdPiece1 != null) { +- pieceWeight.placeCount++; ++ pieceWeight.placeCount.set(pieceWeight.placeCount.get() + 1); // DivineMC - Chunk System Optimizations + piece.previousPiece = pieceWeight; + if (!pieceWeight.isValid()) { +- currentPieces.remove(pieceWeight); ++ currentPieces.get().remove(pieceWeight); // DivineMC - Chunk System Optimizations + } + + return strongholdPiece1; +@@ -202,7 +208,7 @@ public class StrongholdPieces { + private static final int WIDTH = 5; + private static final int HEIGHT = 5; + private static final int DEPTH = 7; +- private boolean hasPlacedChest; ++ private volatile boolean hasPlacedChest; // DivineMC - Chunk System Optimizations + + public ChestCorridor(int genDepth, RandomSource random, BoundingBox box, Direction orientation) { + super(StructurePieceType.STRONGHOLD_CHEST_CORRIDOR, genDepth, box); +@@ -723,7 +729,7 @@ public class StrongholdPieces { + static class PieceWeight { + public final Class pieceClass; + public final int weight; +- public int placeCount; ++ public final ThreadLocal placeCount = ThreadLocal.withInitial(() -> 0); // DivineMC - Chunk System Optimizations + public final int maxPlaceCount; + + public PieceWeight(Class pieceClass, int weight, int maxPlaceCount) { +@@ -733,11 +739,11 @@ public class StrongholdPieces { + } + + public boolean doPlace(int genDepth) { +- return this.maxPlaceCount == 0 || this.placeCount < this.maxPlaceCount; ++ return this.maxPlaceCount == 0 || this.placeCount.get() < this.maxPlaceCount; // DivineMC - Chunk System Optimizations + } + + public boolean isValid() { +- return this.maxPlaceCount == 0 || this.placeCount < this.maxPlaceCount; ++ return this.maxPlaceCount == 0 || this.placeCount.get() < this.maxPlaceCount; // DivineMC - Chunk System Optimizations + } + } + +@@ -745,7 +751,7 @@ public class StrongholdPieces { + protected static final int WIDTH = 11; + protected static final int HEIGHT = 8; + protected static final int DEPTH = 16; +- private boolean hasPlacedSpawner; ++ private volatile boolean hasPlacedSpawner; // DivineMC - Chunk System Optimizations + + public PortalRoom(int genDepth, BoundingBox box, Direction orientation) { + super(StructurePieceType.STRONGHOLD_PORTAL_ROOM, genDepth, box); +@@ -1273,7 +1279,7 @@ public class StrongholdPieces { + @Override + public void addChildren(StructurePiece piece, StructurePieceAccessor pieces, RandomSource random) { + if (this.isSource) { +- StrongholdPieces.imposedPiece = StrongholdPieces.FiveCrossing.class; ++ StrongholdPieces.imposedPiece.set(StrongholdPieces.FiveCrossing.class); + } + + this.generateSmallDoorChildForward((StrongholdPieces.StartPiece)piece, pieces, random, 1, 1); +@@ -1322,10 +1328,10 @@ public class StrongholdPieces { + } + + public static class StartPiece extends StrongholdPieces.StairsDown { +- public StrongholdPieces.PieceWeight previousPiece; ++ public volatile StrongholdPieces.PieceWeight previousPiece; // DivineMC - Chunk System Optimizations + @Nullable +- public StrongholdPieces.PortalRoom portalRoomPiece; +- public final List pendingChildren = Lists.newArrayList(); ++ public volatile StrongholdPieces.PortalRoom portalRoomPiece; // DivineMC - Chunk System Optimizations ++ public final List pendingChildren = java.util.Collections.synchronizedList(Lists.newArrayList()); // DivineMC - Chunk System Optimizations + + public StartPiece(RandomSource random, int x, int z) { + super(StructurePieceType.STRONGHOLD_START, 0, x, z, getRandomHorizontalDirection(random)); +diff --git a/net/minecraft/world/level/levelgen/structure/structures/WoodlandMansionPieces.java b/net/minecraft/world/level/levelgen/structure/structures/WoodlandMansionPieces.java +index 6c5b6ba8973c88e959d57fec38abcef5f097e9b3..ad44f4915e7a9609445b62664d2a47581de8c169 100644 +--- a/net/minecraft/world/level/levelgen/structure/structures/WoodlandMansionPieces.java ++++ b/net/minecraft/world/level/levelgen/structure/structures/WoodlandMansionPieces.java +@@ -126,7 +126,7 @@ public class WoodlandMansionPieces { + int i = 11; + this.entranceX = 7; + this.entranceY = 4; +- this.baseGrid = new WoodlandMansionPieces.SimpleGrid(11, 11, 5); ++ this.baseGrid = new org.bxteam.divinemc.util.ConcurrentFlagMatrix(11, 11, 5); // DivineMC - Chunk System Optimizations + this.baseGrid.set(this.entranceX, this.entranceY, this.entranceX + 1, this.entranceY + 1, 3); + this.baseGrid.set(this.entranceX - 1, this.entranceY, this.entranceX - 1, this.entranceY + 1, 2); + this.baseGrid.set(this.entranceX + 2, this.entranceY - 2, this.entranceX + 3, this.entranceY + 3, 5); +@@ -145,14 +145,16 @@ public class WoodlandMansionPieces { + } + + this.floorRooms = new WoodlandMansionPieces.SimpleGrid[3]; +- this.floorRooms[0] = new WoodlandMansionPieces.SimpleGrid(11, 11, 5); +- this.floorRooms[1] = new WoodlandMansionPieces.SimpleGrid(11, 11, 5); +- this.floorRooms[2] = new WoodlandMansionPieces.SimpleGrid(11, 11, 5); ++ // DivineMC start - Chunk System Optimizations ++ this.floorRooms[0] = new org.bxteam.divinemc.util.ConcurrentFlagMatrix(11, 11, 5); ++ this.floorRooms[1] = new org.bxteam.divinemc.util.ConcurrentFlagMatrix(11, 11, 5); ++ this.floorRooms[2] = new org.bxteam.divinemc.util.ConcurrentFlagMatrix(11, 11, 5); ++ // DivineMC end - Chunk System Optimizations + this.identifyRooms(this.baseGrid, this.floorRooms[0]); + this.identifyRooms(this.baseGrid, this.floorRooms[1]); + this.floorRooms[0].set(this.entranceX + 1, this.entranceY, this.entranceX + 1, this.entranceY + 1, 8388608); + this.floorRooms[1].set(this.entranceX + 1, this.entranceY, this.entranceX + 1, this.entranceY + 1, 8388608); +- this.thirdFloorGrid = new WoodlandMansionPieces.SimpleGrid(this.baseGrid.width, this.baseGrid.height, 5); ++ this.thirdFloorGrid = new org.bxteam.divinemc.util.ConcurrentFlagMatrix(this.baseGrid.width, this.baseGrid.height, 5); // DivineMC - Chunk System Optimizations + this.setupThirdFloor(); + this.identifyRooms(this.thirdFloorGrid, this.floorRooms[2]); + } +@@ -1139,9 +1141,11 @@ public class WoodlandMansionPieces { + } + + static class PlacementData { +- public Rotation rotation; +- public BlockPos position; +- public String wallType; ++ // DivineMC start - Chunk System Optimizations ++ public volatile Rotation rotation; ++ public volatile BlockPos position; ++ public volatile String wallType; ++ // DivineMC end - Chunk System Optimizations + } + + static class SecondFloorRoomCollection extends WoodlandMansionPieces.FloorRoomCollection { +diff --git a/net/minecraft/world/level/levelgen/structure/templatesystem/StructurePlaceSettings.java b/net/minecraft/world/level/levelgen/structure/templatesystem/StructurePlaceSettings.java +index 05027cc20d174d78bef118cd2ba545ac56e1559c..32bbfe48dee44b0b491aa369dec59cbf0772c4b5 100644 +--- a/net/minecraft/world/level/levelgen/structure/templatesystem/StructurePlaceSettings.java ++++ b/net/minecraft/world/level/levelgen/structure/templatesystem/StructurePlaceSettings.java +@@ -22,7 +22,7 @@ public class StructurePlaceSettings { + @Nullable + private RandomSource random; + public int palette = -1; // CraftBukkit - Set initial value so we know if the palette has been set forcefully +- private final List processors = Lists.newArrayList(); ++ private final List processors = java.util.Collections.synchronizedList(Lists.newArrayList()); // DivineMC - Chunk System Optimizations + private boolean knownShape; + private boolean finalizeEntities; + +diff --git a/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java b/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java +index ab1dcbe416e2c3c94cfddf04b7ed053425a71806..a37eb2e29b4577ebc711e8ef7b47fbbc3bc66897 100644 +--- a/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java ++++ b/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java +@@ -71,8 +71,8 @@ public class StructureTemplate { + public static final String ENTITY_TAG_BLOCKPOS = "blockPos"; + public static final String ENTITY_TAG_NBT = "nbt"; + public static final String SIZE_TAG = "size"; +- public final List palettes = Lists.newArrayList(); +- public final List entityInfoList = Lists.newArrayList(); ++ public final List palettes = java.util.Collections.synchronizedList(Lists.newArrayList()); // DivineMC - Chunk System Optimizations ++ public final List entityInfoList = java.util.Collections.synchronizedList(Lists.newArrayList()); // DivineMC - Chunk System Optimizations + private Vec3i size = Vec3i.ZERO; + private String author = "?"; + // CraftBukkit start - data containers +diff --git a/net/minecraft/world/ticks/LevelChunkTicks.java b/net/minecraft/world/ticks/LevelChunkTicks.java +index faf45ac459f7c25309d6ef6dce371d484a0dae7b..0e862e9c0d9d562e77ce02a35deb761c28fa2f90 100644 +--- a/net/minecraft/world/ticks/LevelChunkTicks.java ++++ b/net/minecraft/world/ticks/LevelChunkTicks.java +@@ -18,10 +18,10 @@ import net.minecraft.nbt.ListTag; + import net.minecraft.world.level.ChunkPos; + + public class LevelChunkTicks implements SerializableTickContainer, TickContainerAccess, ca.spottedleaf.moonrise.patches.chunk_system.ticks.ChunkSystemLevelChunkTicks { // Paper - rewrite chunk system +- private final Queue> tickQueue = new PriorityQueue<>(ScheduledTick.DRAIN_ORDER); ++ private final Queue> tickQueue = new java.util.concurrent.PriorityBlockingQueue<>(11, ScheduledTick.DRAIN_ORDER); // DivineMC - Chunk System Optimizations + @Nullable + private List> pendingTicks; +- private final Set> ticksPerPosition = new ObjectOpenCustomHashSet<>(ScheduledTick.UNIQUE_TICK_HASH); ++ private final Set> ticksPerPosition = it.unimi.dsi.fastutil.objects.ObjectSets.synchronize(new ObjectOpenCustomHashSet<>(ScheduledTick.UNIQUE_TICK_HASH)); // DivineMC - Chunk System Optimizations + @Nullable + private BiConsumer, ScheduledTick> onTickAdded; + +@@ -71,10 +71,18 @@ public class LevelChunkTicks implements SerializableTickContainer, TickCon + + @Nullable + public ScheduledTick poll() { +- ScheduledTick scheduledTick = this.tickQueue.poll(); +- if (scheduledTick != null) { +- this.ticksPerPosition.remove(scheduledTick); this.dirty = true; // Paper - rewrite chunk system ++ // DivineMC start - Chunk System Optimizations ++ ScheduledTick scheduledTick = null; ++ try { ++ scheduledTick = this.tickQueue.poll(); ++ if (scheduledTick != null) { ++ this.ticksPerPosition.remove(scheduledTick); this.dirty = true; // Paper - rewrite chunk system ++ } ++ } catch (Exception e) { ++ net.minecraft.server.MinecraftServer.LOGGER.error("Encountered caught exception when polling chunk ticks, blocking and returning null.", e); ++ return null; + } ++ // DivineMC end - Chunk System Optimizations + + return scheduledTick; + } +@@ -87,6 +95,7 @@ public class LevelChunkTicks implements SerializableTickContainer, TickCon + } + + private void scheduleUnchecked(ScheduledTick tick) { ++ if (tick == null) return; // DivineMC - Chunk System Optimizations + this.tickQueue.add(tick); + if (this.onTickAdded != null) { + this.onTickAdded.accept(this, tick); +@@ -127,6 +136,7 @@ public class LevelChunkTicks implements SerializableTickContainer, TickCon + } + + for (ScheduledTick scheduledTick : this.tickQueue) { ++ if (scheduledTick == null) continue; // DivineMC - Chunk System Optimizations - fix NPE + list.add(scheduledTick.toSavedTick(gametime)); + } + +diff --git a/net/minecraft/world/ticks/LevelTicks.java b/net/minecraft/world/ticks/LevelTicks.java +index 0a9805d42142678ca5213c511235daa6505ddbf3..cd73f1a3ccc80e4ab1766147fccc67d81eeb4cc3 100644 +--- a/net/minecraft/world/ticks/LevelTicks.java ++++ b/net/minecraft/world/ticks/LevelTicks.java +@@ -30,17 +30,18 @@ public class LevelTicks implements LevelTickAccess { + private static final Comparator> CONTAINER_DRAIN_ORDER = (levelChunkTicks, levelChunkTicks1) -> ScheduledTick.INTRA_TICK_DRAIN_ORDER + .compare(levelChunkTicks.peek(), levelChunkTicks1.peek()); + private final LongPredicate tickCheck; +- private final Long2ObjectMap> allContainers = new Long2ObjectOpenHashMap<>(); +- private final Long2LongMap nextTickForContainer = Util.make(new Long2LongOpenHashMap(), map -> map.defaultReturnValue(Long.MAX_VALUE)); +- private final Queue> containersToTick = new PriorityQueue<>(CONTAINER_DRAIN_ORDER); +- private final Queue> toRunThisTick = new ArrayDeque<>(); ++ private final Long2ObjectMap> allContainers = it.unimi.dsi.fastutil.longs.Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap<>()); // DivineMC - Chunk System Optimizations ++ private final java.util.Map nextTickForContainer = new java.util.concurrent.ConcurrentHashMap<>(); // DivineMC - Chunk System Optimizations ++ private final Queue> containersToTick = new java.util.concurrent.PriorityBlockingQueue<>(11, CONTAINER_DRAIN_ORDER); // DivineMC - Chunk System Optimizations ++ private final Queue> toRunThisTick = new java.util.concurrent.ConcurrentLinkedQueue<>(); // DivineMC - Chunk System Optimizations + private final List> alreadyRunThisTick = new ArrayList<>(); +- private final Set> toRunThisTickSet = new ObjectOpenCustomHashSet<>(ScheduledTick.UNIQUE_TICK_HASH); ++ private final Set> toRunThisTickSet = com.google.common.collect.Sets.newConcurrentHashSet(); // DivineMC - Chunk System Optimizations + private final BiConsumer, ScheduledTick> chunkScheduleUpdater = (levelChunkTicks, scheduledTick) -> { + if (scheduledTick.equals(levelChunkTicks.peek())) { + this.updateContainerScheduling(scheduledTick); + } + }; ++ private final java.util.concurrent.atomic.AtomicInteger toRunThisTickCount = new java.util.concurrent.atomic.AtomicInteger(0); // DivineMC - Chunk System Optimizations + + public LevelTicks(LongPredicate tickCheck) { + this.tickCheck = tickCheck; +@@ -90,12 +91,14 @@ public class LevelTicks implements LevelTickAccess { + } + + private void sortContainersToTick(long gameTime) { +- ObjectIterator objectIterator = Long2LongMaps.fastIterator(this.nextTickForContainer); ++ java.util.Iterator> objectIterator = this.nextTickForContainer.entrySet().iterator(); // DivineMC - Chunk System Optimizations + + while (objectIterator.hasNext()) { +- Entry entry = objectIterator.next(); +- long longKey = entry.getLongKey(); +- long longValue = entry.getLongValue(); ++ // DivineMC start - Chunk System Optimizations ++ java.util.Map.Entry entry = objectIterator.next(); ++ long longKey = entry.getKey(); ++ long longValue = entry.getValue(); ++ // DivineMC end - Chunk System Optimizations + if (longValue <= gameTime) { + LevelChunkTicks levelChunkTicks = this.allContainers.get(longKey); + if (levelChunkTicks == null) { +@@ -162,16 +165,19 @@ public class LevelTicks implements LevelTickAccess { + } + + private void scheduleForThisTick(ScheduledTick tick) { ++ if (tick == null) return; // DivineMC - Chunk System Optimizations + this.toRunThisTick.add(tick); ++ this.toRunThisTickCount.incrementAndGet(); // DivineMC - Chunk System Optimizations + } + + private boolean canScheduleMoreTicks(int maxAllowedTicks) { +- return this.toRunThisTick.size() < maxAllowedTicks; ++ return this.toRunThisTickCount.get() < maxAllowedTicks; // DivineMC - Chunk System Optimizations + } + + private void runCollectedTicks(BiConsumer ticker) { + while (!this.toRunThisTick.isEmpty()) { + ScheduledTick scheduledTick = this.toRunThisTick.poll(); ++ this.toRunThisTickCount.decrementAndGet(); // DivineMC - Chunk System Optimizations + if (!this.toRunThisTickSet.isEmpty()) { + this.toRunThisTickSet.remove(scheduledTick); + } +@@ -183,6 +189,7 @@ public class LevelTicks implements LevelTickAccess { + + private void cleanupAfterTick() { + this.toRunThisTick.clear(); ++ this.toRunThisTickCount.set(0); // DivineMC - Chunk System Optimizations + this.containersToTick.clear(); + this.alreadyRunThisTick.clear(); + this.toRunThisTickSet.clear(); diff --git a/divinemc-server/minecraft-patches/features/0010-Math-Optimizations.patch b/divinemc-server/minecraft-patches/features/0010-Math-Optimizations.patch deleted file mode 100644 index 54f44f6..0000000 --- a/divinemc-server/minecraft-patches/features/0010-Math-Optimizations.patch +++ /dev/null @@ -1,329 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> -Date: Fri, 31 Jan 2025 21:50:46 +0300 -Subject: [PATCH] Math Optimizations - - -diff --git a/com/mojang/math/OctahedralGroup.java b/com/mojang/math/OctahedralGroup.java -index 11902e7427761746ee098fea3276a34fef0096ba..3ba23fa243f7af712a41316066ca554f1c23b495 100644 ---- a/com/mojang/math/OctahedralGroup.java -+++ b/com/mojang/math/OctahedralGroup.java -@@ -112,6 +112,7 @@ public enum OctahedralGroup implements StringRepresentable { - this.transformation = new Matrix3f().scaling(invertX ? -1.0F : 1.0F, invertY ? -1.0F : 1.0F, invertZ ? -1.0F : 1.0F); - this.transformation.mul(permutation.transformation()); - this.initializeRotationDirections(); // Paper - Avoid Lazy Initialization for Enum Fields -+ this.rotate(Direction.UP); // DivineMC - Math Optimizations - } - - private BooleanList packInversions() { -diff --git a/com/mojang/math/Transformation.java b/com/mojang/math/Transformation.java -index aa755b8b7f8bc5910322e0c5b520f603da06a85a..e781dea43279aa77cc40a7afd2281c32cc8347a9 100644 ---- a/com/mojang/math/Transformation.java -+++ b/com/mojang/math/Transformation.java -@@ -51,6 +51,7 @@ public final class Transformation { - } else { - this.matrix = matrix; - } -+ ensureDecomposed(); // DivineMC - Math Optimizations - } - - public Transformation(@Nullable Vector3f translation, @Nullable Quaternionf leftRotation, @Nullable Vector3f scale, @Nullable Quaternionf rightRotation) { -@@ -60,6 +61,7 @@ public final class Transformation { - this.scale = scale != null ? scale : new Vector3f(1.0F, 1.0F, 1.0F); - this.rightRotation = rightRotation != null ? rightRotation : new Quaternionf(); - this.decomposed = true; -+ ensureDecomposed(); // DivineMC - Math Optimizations - } - - public static Transformation identity() { -diff --git a/net/minecraft/util/Mth.java b/net/minecraft/util/Mth.java -index ab3a221c115992d0f4ea921aa92cf0976b815ff4..076a931341da486162f289a5f19d3d6736df7768 100644 ---- a/net/minecraft/util/Mth.java -+++ b/net/minecraft/util/Mth.java -@@ -46,11 +46,11 @@ public class Mth { - private static final double[] COS_TAB = new double[257]; - - public static float sin(float value) { -- return SIN[(int)(value * 10430.378F) & 65535]; -+ return net.caffeinemc.mods.lithium.common.util.math.CompactSineLUT.sin(value); // DivineMC - Math Optimizations - } - - public static float cos(float value) { -- return SIN[(int)(value * 10430.378F + 16384.0F) & 65535]; -+ return net.caffeinemc.mods.lithium.common.util.math.CompactSineLUT.cos(value); // DivineMC - Math Optimizations - } - - public static float sqrt(float value) { -@@ -58,18 +58,15 @@ public class Mth { - } - - public static int floor(float value) { -- int i = (int)value; -- return value < i ? i - 1 : i; -+ return (int) Math.floor(value); // DivineMC - Math Optimizations - } - - public static int floor(double value) { -- int i = (int)value; -- return value < i ? i - 1 : i; -+ return (int) Math.floor(value); // DivineMC - Math Optimizations - } - - public static long lfloor(double value) { -- long l = (long)value; -- return value < l ? l - 1L : l; -+ return (long) Math.floor(value); // DivineMC - Math Optimizations - } - - public static float abs(float value) { -@@ -81,13 +78,11 @@ public class Mth { - } - - public static int ceil(float value) { -- int i = (int)value; -- return value > i ? i + 1 : i; -+ return (int) Math.ceil(value); // DivineMC - Math Optimizations - } - - public static int ceil(double value) { -- int i = (int)value; -- return value > i ? i + 1 : i; -+ return (int) Math.ceil(value); // DivineMC - Math Optimizations - } - - public static int clamp(int value, int min, int max) { -@@ -123,15 +118,7 @@ public class Mth { - } - - public static double absMax(double x, double y) { -- if (x < 0.0) { -- x = -x; -- } -- -- if (y < 0.0) { -- y = -y; -- } -- -- return Math.max(x, y); -+ return Math.max(Math.abs(x), Math.abs(y)); // DivineMC - Math Optimizations - } - - public static int floorDiv(int dividend, int divisor) { -@@ -162,14 +149,26 @@ public class Mth { - return Math.floorMod(x, y); - } - -- public static float positiveModulo(float numerator, float denominator) { -+ public static float positiveModuloForAnyDenominator(float numerator, float denominator) { // DivineMC - Math Optimizations - return (numerator % denominator + denominator) % denominator; - } - -- public static double positiveModulo(double numerator, double denominator) { -+ public static double positiveModuloForAnyDenominator(double numerator, double denominator) { // DivineMC - Math Optimizations - return (numerator % denominator + denominator) % denominator; - } - -+ // DivineMC start - Math Optimizations -+ public static float positiveModuloForPositiveIntegerDenominator(float numerator, float denominator) { -+ var modulo = numerator % denominator; -+ return modulo < 0 ? modulo + denominator : modulo; -+ } -+ -+ public static double positiveModuloForPositiveIntegerDenominator(double numerator, double denominator) { -+ var modulo = numerator % denominator; -+ return modulo < 0 ? modulo + denominator : modulo; -+ } -+ // DivineMC end - Math Optimizations -+ - public static boolean isMultipleOf(int number, int multiple) { - return number % multiple == 0; - } -diff --git a/net/minecraft/world/level/biome/BiomeManager.java b/net/minecraft/world/level/biome/BiomeManager.java -index 73962e79a0f3d892e3155443a1b84508b0f4042e..db400d7b25e454b4a1ac8d09a590c3c7d2504052 100644 ---- a/net/minecraft/world/level/biome/BiomeManager.java -+++ b/net/minecraft/world/level/biome/BiomeManager.java -@@ -14,6 +14,7 @@ public class BiomeManager { - private static final int ZOOM_MASK = 3; - private final BiomeManager.NoiseBiomeSource noiseBiomeSource; - private final long biomeZoomSeed; -+ private static final double maxOffset = 0.4500000001D; // DivineMC - Math Optimizations - - public BiomeManager(BiomeManager.NoiseBiomeSource noiseBiomeSource, long biomeZoomSeed) { - this.noiseBiomeSource = noiseBiomeSource; -@@ -29,39 +30,65 @@ public class BiomeManager { - } - - public Holder getBiome(BlockPos pos) { -- int i = pos.getX() - 2; -- int i1 = pos.getY() - 2; -- int i2 = pos.getZ() - 2; -- int i3 = i >> 2; -- int i4 = i1 >> 2; -- int i5 = i2 >> 2; -- double d = (i & 3) / 4.0; -- double d1 = (i1 & 3) / 4.0; -- double d2 = (i2 & 3) / 4.0; -- int i6 = 0; -- double d3 = Double.POSITIVE_INFINITY; -+ // DivineMC start - Math Optimizations -+ int xMinus2 = pos.getX() - 2; -+ int yMinus2 = pos.getY() - 2; -+ int zMinus2 = pos.getZ() - 2; -+ int x = xMinus2 >> 2; -+ int y = yMinus2 >> 2; -+ int z = zMinus2 >> 2; -+ double quartX = (double) (xMinus2 & 3) / 4.0; -+ double quartY = (double) (yMinus2 & 3) / 4.0; -+ double quartZ = (double) (zMinus2 & 3) / 4.0; -+ int smallestX = 0; -+ double smallestDist = Double.POSITIVE_INFINITY; -+ for (int biomeX = 0; biomeX < 8; ++biomeX) { -+ boolean everyOtherQuad = (biomeX & 4) == 0; -+ boolean everyOtherPair = (biomeX & 2) == 0; -+ boolean everyOther = (biomeX & 1) == 0; -+ double quartXX = everyOtherQuad ? quartX : quartX - 1.0; -+ double quartYY = everyOtherPair ? quartY : quartY - 1.0; -+ double quartZZ = everyOther ? quartZ : quartZ - 1.0; - -- for (int i7 = 0; i7 < 8; i7++) { -- boolean flag = (i7 & 4) == 0; -- boolean flag1 = (i7 & 2) == 0; -- boolean flag2 = (i7 & 1) == 0; -- int i8 = flag ? i3 : i3 + 1; -- int i9 = flag1 ? i4 : i4 + 1; -- int i10 = flag2 ? i5 : i5 + 1; -- double d4 = flag ? d : d - 1.0; -- double d5 = flag1 ? d1 : d1 - 1.0; -- double d6 = flag2 ? d2 : d2 - 1.0; -- double fiddledDistance = getFiddledDistance(this.biomeZoomSeed, i8, i9, i10, d4, d5, d6); -- if (d3 > fiddledDistance) { -- i6 = i7; -- d3 = fiddledDistance; -+ double maxQuartYY = 0.0, maxQuartZZ = 0.0; -+ if (biomeX != 0) { -+ maxQuartYY = Mth.square(Math.max(quartYY + maxOffset, Math.abs(quartYY - maxOffset))); -+ maxQuartZZ = Mth.square(Math.max(quartZZ + maxOffset, Math.abs(quartZZ - maxOffset))); -+ double maxQuartXX = Mth.square(Math.max(quartXX + maxOffset, Math.abs(quartXX - maxOffset))); -+ if (smallestDist < maxQuartXX + maxQuartYY + maxQuartZZ) continue; - } -- } -+ int xx = everyOtherQuad ? x : x + 1; -+ int yy = everyOtherPair ? y : y + 1; -+ int zz = everyOther ? z : z + 1; -+ -+ long seed = LinearCongruentialGenerator.next(this.biomeZoomSeed, xx); -+ seed = LinearCongruentialGenerator.next(seed, yy); -+ seed = LinearCongruentialGenerator.next(seed, zz); -+ seed = LinearCongruentialGenerator.next(seed, xx); -+ seed = LinearCongruentialGenerator.next(seed, yy); -+ seed = LinearCongruentialGenerator.next(seed, zz); -+ double offsetX = getFiddle(seed); -+ double sqrX = Mth.square(quartXX + offsetX); -+ if (biomeX != 0 && smallestDist < sqrX + maxQuartYY + maxQuartZZ) continue; -+ seed = LinearCongruentialGenerator.next(seed, this.biomeZoomSeed); -+ double offsetY = getFiddle(seed); -+ double sqrY = Mth.square(quartYY + offsetY); -+ if (biomeX != 0 && smallestDist < sqrX + sqrY + maxQuartZZ) continue; -+ seed = LinearCongruentialGenerator.next(seed, this.biomeZoomSeed); -+ double offsetZ = getFiddle(seed); -+ double biomeDist = sqrX + sqrY + Mth.square(quartZZ + offsetZ); - -- int i7x = (i6 & 4) == 0 ? i3 : i3 + 1; -- int i11 = (i6 & 2) == 0 ? i4 : i4 + 1; -- int i12 = (i6 & 1) == 0 ? i5 : i5 + 1; -- return this.noiseBiomeSource.getNoiseBiome(i7x, i11, i12); -+ if (smallestDist > biomeDist) { -+ smallestX = biomeX; -+ smallestDist = biomeDist; -+ } -+ } -+ return this.noiseBiomeSource.getNoiseBiome( -+ (smallestX & 4) == 0 ? x : x + 1, -+ (smallestX & 2) == 0 ? y : y + 1, -+ (smallestX & 1) == 0 ? z : z + 1 -+ ); -+ // DivineMC end - Math Optimizations - } - - public Holder getNoiseBiomeAtPosition(double x, double y, double z) { -diff --git a/net/minecraft/world/level/levelgen/blending/Blender.java b/net/minecraft/world/level/levelgen/blending/Blender.java -index 01e5b29d6e9a5c53c0e23b61ed0c1d7be1a0fe08..d80df05e40f3941ade5ed320e12f8dcf47e6b247 100644 ---- a/net/minecraft/world/level/levelgen/blending/Blender.java -+++ b/net/minecraft/world/level/levelgen/blending/Blender.java -@@ -144,7 +144,7 @@ public class Blender { - private static double heightToOffset(double height) { - double d = 1.0; - double d1 = height + 0.5; -- double d2 = Mth.positiveModulo(d1, 8.0); -+ double d2 = Mth.positiveModuloForPositiveIntegerDenominator(d1, 8.0); // DivineMC - Math optimizations - return 1.0 * (32.0 * (d1 - 128.0) - 3.0 * (d1 - 120.0) * d2 + 3.0 * d2 * d2) / (128.0 * (32.0 - 3.0 * d2)); - } - -diff --git a/net/minecraft/world/phys/AABB.java b/net/minecraft/world/phys/AABB.java -index c9c6e4e460ad8435f12761704bb9b0284d6aa708..54807bb4b4189ceaded1f78a1a9ab85ce40ab2b1 100644 ---- a/net/minecraft/world/phys/AABB.java -+++ b/net/minecraft/world/phys/AABB.java -@@ -189,13 +189,15 @@ public class AABB { - } - - public AABB intersect(AABB other) { -- double max = Math.max(this.minX, other.minX); -- double max1 = Math.max(this.minY, other.minY); -- double max2 = Math.max(this.minZ, other.minZ); -- double min = Math.min(this.maxX, other.maxX); -- double min1 = Math.min(this.maxY, other.maxY); -- double min2 = Math.min(this.maxZ, other.maxZ); -- return new AABB(max, max1, max2, min, min1, min2); -+ // DivineMC start - Math Optimizations -+ return new AABB( -+ this.minX > other.minX ? this.minX : other.minX, -+ this.minY > other.minY ? this.minY : other.minY, -+ this.minZ > other.minZ ? this.minZ : other.minZ, -+ this.maxX < other.maxX ? this.maxX : other.maxX, -+ this.maxY < other.maxY ? this.maxY : other.maxY, -+ this.maxZ < other.maxZ ? this.maxZ : other.maxZ -+ ); - } - - public AABB minmax(AABB other) { -@@ -227,16 +229,37 @@ public class AABB { - } - - public boolean intersects(AABB other) { -- return this.intersects(other.minX, other.minY, other.minZ, other.maxX, other.maxY, other.maxZ); -+ // DivineMC start - Math Optimizations -+ return this.minX < other.maxX && -+ this.maxX > other.minX && -+ this.minY < other.maxY && -+ this.maxY > other.minY && -+ this.minZ < other.maxZ && -+ this.maxZ > other.minZ; -+ // DivineMC end - Math Optimizations - } - - public boolean intersects(double x1, double y1, double z1, double x2, double y2, double z2) { -- return this.minX < x2 && this.maxX > x1 && this.minY < y2 && this.maxY > y1 && this.minZ < z2 && this.maxZ > z1; -+ // DivineMC start - Math Optimizations -+ return this.minX < x2 && -+ this.maxX > x1 && -+ this.minY < y2 && -+ this.maxY > y1 && -+ this.minZ < z2 && -+ this.maxZ > z1; -+ // DivineMC end - Math Optimizations - } - - public boolean intersects(Vec3 min, Vec3 max) { - return this.intersects( -- Math.min(min.x, max.x), Math.min(min.y, max.y), Math.min(min.z, max.z), Math.max(min.x, max.x), Math.max(min.y, max.y), Math.max(min.z, max.z) -+ // DivineMC start - Math Optimizations -+ min.x < max.x ? min.x : max.x, -+ min.y < max.y ? min.y : max.y, -+ min.z < max.z ? min.z : max.z, -+ min.x > max.x ? min.x : max.x, -+ min.y > max.y ? min.y : max.y, -+ min.z > max.z ? min.z : max.z -+ // DivineMC end - Math Optimizations - ); - } - diff --git a/divinemc-server/minecraft-patches/features/0016-Optimize-entity-stupid-brain.patch b/divinemc-server/minecraft-patches/features/0012-Optimize-entity-stupid-brain.patch similarity index 100% rename from divinemc-server/minecraft-patches/features/0016-Optimize-entity-stupid-brain.patch rename to divinemc-server/minecraft-patches/features/0012-Optimize-entity-stupid-brain.patch diff --git a/divinemc-server/minecraft-patches/features/0012-World-and-Noise-gen-optimizations.patch b/divinemc-server/minecraft-patches/features/0012-World-and-Noise-gen-optimizations.patch deleted file mode 100644 index 7d2dcd3..0000000 --- a/divinemc-server/minecraft-patches/features/0012-World-and-Noise-gen-optimizations.patch +++ /dev/null @@ -1,1056 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> -Date: Sat, 1 Feb 2025 00:09:39 +0300 -Subject: [PATCH] World and Noise gen optimizations - - -diff --git a/net/minecraft/world/level/ChunkPos.java b/net/minecraft/world/level/ChunkPos.java -index 55ce935a2fab7e32904d9ff599867269035d703f..4fa84743ba7f570f11a4979b7e5381478c844aef 100644 ---- a/net/minecraft/world/level/ChunkPos.java -+++ b/net/minecraft/world/level/ChunkPos.java -@@ -110,7 +110,12 @@ public class ChunkPos { - - @Override - public boolean equals(Object other) { -- return this == other || other instanceof ChunkPos chunkPos && this.x == chunkPos.x && this.z == chunkPos.z; -+ // DivineMC start - Use standard equals -+ if (other == this) return true; -+ if (other == null || other.getClass() != this.getClass()) return false; -+ ChunkPos thatPos = (ChunkPos) other; -+ return this.x == thatPos.x && this.z == thatPos.z; -+ // DivineMC end - Use standard equals - } - - public int getMiddleBlockX() { -diff --git a/net/minecraft/world/level/biome/TheEndBiomeSource.java b/net/minecraft/world/level/biome/TheEndBiomeSource.java -index cf3172be76fa4c7987ed569138439ff42f92fa7f..bfc65a4d8d1e64f42ff13508020e5e0260e83b98 100644 ---- a/net/minecraft/world/level/biome/TheEndBiomeSource.java -+++ b/net/minecraft/world/level/biome/TheEndBiomeSource.java -@@ -27,6 +27,33 @@ public class TheEndBiomeSource extends BiomeSource { - private final Holder islands; - private final Holder barrens; - -+ // DivineMC start - World gen optimizations -+ private Holder getBiomeForNoiseGenVanilla(int x, int y, int z, Climate.Sampler noise) { -+ int i = QuartPos.toBlock(x); -+ int j = QuartPos.toBlock(y); -+ int k = QuartPos.toBlock(z); -+ int l = SectionPos.blockToSectionCoord(i); -+ int m = SectionPos.blockToSectionCoord(k); -+ if ((long)l * (long)l + (long)m * (long)m <= 4096L) { -+ return this.end; -+ } else { -+ int n = (SectionPos.blockToSectionCoord(i) * 2 + 1) * 8; -+ int o = (SectionPos.blockToSectionCoord(k) * 2 + 1) * 8; -+ double d = noise.erosion().compute(new DensityFunction.SinglePointContext(n, j, o)); -+ if (d > 0.25D) { -+ return this.highlands; -+ } else if (d >= -0.0625D) { -+ return this.midlands; -+ } else { -+ return d < -0.21875D ? this.islands : this.barrens; -+ } -+ } -+ } -+ -+ private final ThreadLocal>> cache = ThreadLocal.withInitial(it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap::new); -+ private final int cacheCapacity = 1024; -+ // DivineMC end - World gen optimizations -+ - public static TheEndBiomeSource create(HolderGetter biomeGetter) { - return new TheEndBiomeSource( - biomeGetter.getOrThrow(Biomes.THE_END), -@@ -55,26 +82,24 @@ public class TheEndBiomeSource extends BiomeSource { - return CODEC; - } - -+ // DivineMC start - World gen optimizations - @Override -- public Holder getNoiseBiome(int x, int y, int z, Climate.Sampler sampler) { -- int blockPosX = QuartPos.toBlock(x); -- int blockPosY = QuartPos.toBlock(y); -- int blockPosZ = QuartPos.toBlock(z); -- int sectionPosX = SectionPos.blockToSectionCoord(blockPosX); -- int sectionPosZ = SectionPos.blockToSectionCoord(blockPosZ); -- if ((long)sectionPosX * sectionPosX + (long)sectionPosZ * sectionPosZ <= 4096L) { -- return this.end; -+ public Holder getNoiseBiome(int biomeX, int biomeY, int biomeZ, Climate.Sampler multiNoiseSampler) { -+ final long key = net.minecraft.world.level.ChunkPos.asLong(biomeX, biomeZ); -+ final it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap> cacheThreadLocal = cache.get(); -+ final Holder biome = cacheThreadLocal.get(key); -+ if (biome != null) { -+ return biome; - } else { -- int i = (SectionPos.blockToSectionCoord(blockPosX) * 2 + 1) * 8; -- int i1 = (SectionPos.blockToSectionCoord(blockPosZ) * 2 + 1) * 8; -- double d = sampler.erosion().compute(new DensityFunction.SinglePointContext(i, blockPosY, i1)); -- if (d > 0.25) { -- return this.highlands; -- } else if (d >= -0.0625) { -- return this.midlands; -- } else { -- return d < -0.21875 ? this.islands : this.barrens; -+ final Holder gennedBiome = getBiomeForNoiseGenVanilla(biomeX, biomeY, biomeZ, multiNoiseSampler); -+ cacheThreadLocal.put(key, gennedBiome); -+ if (cacheThreadLocal.size() > cacheCapacity) { -+ for (int i = 0; i < cacheCapacity / 16; i ++) { -+ cacheThreadLocal.removeFirst(); -+ } - } -+ return gennedBiome; - } - } -+ // DivineMC end - World gen optimizations - } -diff --git a/net/minecraft/world/level/chunk/ChunkGenerator.java b/net/minecraft/world/level/chunk/ChunkGenerator.java -index 7e0b602e9fd9e3b3f60014ab179b3a82e3bf5c2a..a440b90e203ab6521bc45dbb1931e6a737c979fa 100644 ---- a/net/minecraft/world/level/chunk/ChunkGenerator.java -+++ b/net/minecraft/world/level/chunk/ChunkGenerator.java -@@ -116,7 +116,7 @@ public abstract class ChunkGenerator { - return CompletableFuture.supplyAsync(() -> { - chunk.fillBiomesFromNoise(this.biomeSource, randomState.sampler()); - return chunk; -- }, Runnable::run); // Paper - rewrite chunk system -+ }, net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator.EXECUTOR); // Paper - rewrite chunk system // DivineMC - Optimize noise fill - } - - public abstract void applyCarvers( -diff --git a/net/minecraft/world/level/chunk/LevelChunkSection.java b/net/minecraft/world/level/chunk/LevelChunkSection.java -index c83d0667b19830304f22319a46a23422a8766790..5bc74d860923d6485593cacb67d4c18e20db2634 100644 ---- a/net/minecraft/world/level/chunk/LevelChunkSection.java -+++ b/net/minecraft/world/level/chunk/LevelChunkSection.java -@@ -23,6 +23,7 @@ public class LevelChunkSection implements ca.spottedleaf.moonrise.patches.block_ - public short tickingFluidCount; - public final PalettedContainer states; - private PalettedContainer> biomes; // CraftBukkit - read/write -+ private static final int sliceSize = 4; // DivineMC - World and Noise gen optimizations - - // Paper start - block counting - private static final it.unimi.dsi.fastutil.shorts.ShortArrayList FULL_LIST = new it.unimi.dsi.fastutil.shorts.ShortArrayList(16*16*16); -@@ -312,13 +313,15 @@ public class LevelChunkSection implements ca.spottedleaf.moonrise.patches.block_ - PalettedContainer> palettedContainer = this.biomes.recreate(); - int i = 4; - -- for (int i1 = 0; i1 < 4; i1++) { -- for (int i2 = 0; i2 < 4; i2++) { -- for (int i3 = 0; i3 < 4; i3++) { -- palettedContainer.getAndSetUnchecked(i1, i2, i3, biomeResolver.getNoiseBiome(x + i1, y + i2, z + i3, climateSampler)); -+ // DivineMC start - World and Noise gen optimizations -+ for (int posY = 0; posY < sliceSize; ++posY) { -+ for (int posZ = 0; posZ < sliceSize; ++posZ) { -+ for (int posX = 0; posX < sliceSize; ++posX) { -+ palettedContainer.getAndSetUnchecked(posX, posY, posZ, biomeResolver.getNoiseBiome(x + posX, y + posY, z + posZ, climateSampler)); - } - } - } -+ // DivineMC end - World and Noise gen optimizations - - this.biomes = palettedContainer; - } -diff --git a/net/minecraft/world/level/levelgen/Aquifer.java b/net/minecraft/world/level/levelgen/Aquifer.java -index c62a15ea4a1bb22e7bcc2fc544acf8a601892029..43dd5f63fe7834d41874ea30651f3fb738d88ba6 100644 ---- a/net/minecraft/world/level/levelgen/Aquifer.java -+++ b/net/minecraft/world/level/levelgen/Aquifer.java -@@ -85,6 +85,15 @@ public interface Aquifer { - private final int minGridZ; - private final int gridSizeX; - private final int gridSizeZ; -+ // DivineMC start - World gen optimizations -+ private int c2me$dist1; -+ private int c2me$dist2; -+ private int c2me$dist3; -+ private long c2me$pos1; -+ private long c2me$pos2; -+ private long c2me$pos3; -+ private double c2me$mutableDoubleThingy; -+ // DivineMC end - World gen optimizations - private static final int[][] SURFACE_SAMPLING_OFFSETS_IN_CHUNKS = new int[][]{ - {0, 0}, {-2, -1}, {-1, -1}, {0, -1}, {1, -1}, {-3, 0}, {-2, 0}, {-1, 0}, {1, 0}, {-2, 1}, {-1, 1}, {0, 1}, {1, 1} - }; -@@ -120,6 +129,36 @@ public interface Aquifer { - this.aquiferCache = new Aquifer.FluidStatus[i4]; - this.aquiferLocationCache = new long[i4]; - Arrays.fill(this.aquiferLocationCache, Long.MAX_VALUE); -+ // DivineMC start - World gen optimizations -+ if (this.aquiferLocationCache.length % (this.gridSizeX * this.gridSizeZ) != 0) { -+ throw new AssertionError("Array length"); -+ } -+ -+ int sizeY = this.aquiferLocationCache.length / (this.gridSizeX * this.gridSizeZ); -+ -+ final RandomSource random = org.bxteam.divinemc.util.RandomUtil.getRandom(this.positionalRandomFactory); -+ // index: y, z, x -+ for (int y = 0; y < sizeY; y++) { -+ for (int z = 0; z < this.gridSizeZ; z++) { -+ for (int x = 0; x < this.gridSizeX; x++) { -+ final int x1 = x + this.minGridX; -+ final int y1 = y + this.minGridY; -+ final int z1 = z + this.minGridZ; -+ org.bxteam.divinemc.util.RandomUtil.derive(this.positionalRandomFactory, random, x1, y1, z1); -+ int x2 = x1 * 16 + random.nextInt(10); -+ int y2 = y1 * 12 + random.nextInt(9); -+ int z2 = z1 * 16 + random.nextInt(10); -+ int index = this.getIndex(x1, y1, z1); -+ this.aquiferLocationCache[index] = BlockPos.asLong(x2, y2, z2); -+ } -+ } -+ } -+ for (long blockPosition : this.aquiferLocationCache) { -+ if (blockPosition == Long.MAX_VALUE) { -+ throw new AssertionError("Array initialization"); -+ } -+ } -+ // DivineMC end - World gen optimizations - } - - private int getIndex(int gridX, int gridY, int gridZ) { -@@ -132,140 +171,24 @@ public interface Aquifer { - @Nullable - @Override - public BlockState computeSubstance(DensityFunction.FunctionContext context, double substance) { -+ // DivineMC start - World gen optimizations - int i = context.blockX(); -- int i1 = context.blockY(); -- int i2 = context.blockZ(); -+ int j = context.blockY(); -+ int k = context.blockZ(); - if (substance > 0.0) { - this.shouldScheduleFluidUpdate = false; - return null; - } else { -- Aquifer.FluidStatus fluidStatus = this.globalFluidPicker.computeFluid(i, i1, i2); -- if (fluidStatus.at(i1).is(Blocks.LAVA)) { -+ Aquifer.FluidStatus fluidLevel = this.globalFluidPicker.computeFluid(i, j, k); -+ if (fluidLevel.at(j).is(Blocks.LAVA)) { - this.shouldScheduleFluidUpdate = false; - return Blocks.LAVA.defaultBlockState(); - } else { -- int i3 = Math.floorDiv(i - 5, 16); -- int i4 = Math.floorDiv(i1 + 1, 12); -- int i5 = Math.floorDiv(i2 - 5, 16); -- int i6 = Integer.MAX_VALUE; -- int i7 = Integer.MAX_VALUE; -- int i8 = Integer.MAX_VALUE; -- int i9 = Integer.MAX_VALUE; -- long l = 0L; -- long l1 = 0L; -- long l2 = 0L; -- long l3 = 0L; -- -- for (int i10 = 0; i10 <= 1; i10++) { -- for (int i11 = -1; i11 <= 1; i11++) { -- for (int i12 = 0; i12 <= 1; i12++) { -- int i13 = i3 + i10; -- int i14 = i4 + i11; -- int i15 = i5 + i12; -- int index = this.getIndex(i13, i14, i15); -- long l4 = this.aquiferLocationCache[index]; -- long l5; -- if (l4 != Long.MAX_VALUE) { -- l5 = l4; -- } else { -- RandomSource randomSource = this.positionalRandomFactory.at(i13, i14, i15); -- l5 = BlockPos.asLong( -- i13 * 16 + randomSource.nextInt(10), i14 * 12 + randomSource.nextInt(9), i15 * 16 + randomSource.nextInt(10) -- ); -- this.aquiferLocationCache[index] = l5; -- } -- -- int i16 = BlockPos.getX(l5) - i; -- int i17 = BlockPos.getY(l5) - i1; -- int i18 = BlockPos.getZ(l5) - i2; -- int i19 = i16 * i16 + i17 * i17 + i18 * i18; -- if (i6 >= i19) { -- l3 = l2; -- l2 = l1; -- l1 = l; -- l = l5; -- i9 = i8; -- i8 = i7; -- i7 = i6; -- i6 = i19; -- } else if (i7 >= i19) { -- l3 = l2; -- l2 = l1; -- l1 = l5; -- i9 = i8; -- i8 = i7; -- i7 = i19; -- } else if (i8 >= i19) { -- l3 = l2; -- l2 = l5; -- i9 = i8; -- i8 = i19; -- } else if (i9 >= i19) { -- l3 = l5; -- i9 = i19; -- } -- } -- } -- } -- -- Aquifer.FluidStatus aquiferStatus = this.getAquiferStatus(l); -- double d = similarity(i6, i7); -- BlockState blockState = aquiferStatus.at(i1); -- if (d <= 0.0) { -- if (d >= FLOWING_UPDATE_SIMULARITY) { -- Aquifer.FluidStatus aquiferStatus1 = this.getAquiferStatus(l1); -- this.shouldScheduleFluidUpdate = !aquiferStatus.equals(aquiferStatus1); -- } else { -- this.shouldScheduleFluidUpdate = false; -- } -- -- return blockState; -- } else if (blockState.is(Blocks.WATER) && this.globalFluidPicker.computeFluid(i, i1 - 1, i2).at(i1 - 1).is(Blocks.LAVA)) { -- this.shouldScheduleFluidUpdate = true; -- return blockState; -- } else { -- MutableDouble mutableDouble = new MutableDouble(Double.NaN); -- Aquifer.FluidStatus aquiferStatus2 = this.getAquiferStatus(l1); -- double d1 = d * this.calculatePressure(context, mutableDouble, aquiferStatus, aquiferStatus2); -- if (substance + d1 > 0.0) { -- this.shouldScheduleFluidUpdate = false; -- return null; -- } else { -- Aquifer.FluidStatus aquiferStatus3 = this.getAquiferStatus(l2); -- double d2 = similarity(i6, i8); -- if (d2 > 0.0) { -- double d3 = d * d2 * this.calculatePressure(context, mutableDouble, aquiferStatus, aquiferStatus3); -- if (substance + d3 > 0.0) { -- this.shouldScheduleFluidUpdate = false; -- return null; -- } -- } -- -- double d3 = similarity(i7, i8); -- if (d3 > 0.0) { -- double d4 = d * d3 * this.calculatePressure(context, mutableDouble, aquiferStatus2, aquiferStatus3); -- if (substance + d4 > 0.0) { -- this.shouldScheduleFluidUpdate = false; -- return null; -- } -- } -- -- boolean flag = !aquiferStatus.equals(aquiferStatus2); -- boolean flag1 = d3 >= FLOWING_UPDATE_SIMULARITY && !aquiferStatus2.equals(aquiferStatus3); -- boolean flag2 = d2 >= FLOWING_UPDATE_SIMULARITY && !aquiferStatus.equals(aquiferStatus3); -- if (!flag && !flag1 && !flag2) { -- this.shouldScheduleFluidUpdate = d2 >= FLOWING_UPDATE_SIMULARITY -- && similarity(i6, i9) >= FLOWING_UPDATE_SIMULARITY -- && !aquiferStatus.equals(this.getAquiferStatus(l3)); -- } else { -- this.shouldScheduleFluidUpdate = true; -- } -- -- return blockState; -- } -- } -+ aquiferExtracted$refreshDistPosIdx(i, j, k); -+ return aquiferExtracted$applyPost(context, substance, j, i, k); - } - } -+ // DivineMC end - World gen optimizations - } - - @Override -@@ -278,65 +201,28 @@ public interface Aquifer { - return 1.0 - Math.abs(secondDistance - firstDistance) / 25.0; - } - -+ // DivineMC start - World gen optimizations - private double calculatePressure( -- DensityFunction.FunctionContext context, MutableDouble substance, Aquifer.FluidStatus firstFluid, Aquifer.FluidStatus secondFluid -+ DensityFunction.FunctionContext context, MutableDouble substance, Aquifer.FluidStatus fluidLevel, Aquifer.FluidStatus fluidLevel2 // DivineMC - rename args - ) { - int i = context.blockY(); -- BlockState blockState = firstFluid.at(i); -- BlockState blockState1 = secondFluid.at(i); -- if ((!blockState.is(Blocks.LAVA) || !blockState1.is(Blocks.WATER)) && (!blockState.is(Blocks.WATER) || !blockState1.is(Blocks.LAVA))) { -- int abs = Math.abs(firstFluid.fluidLevel - secondFluid.fluidLevel); -+ BlockState blockState = fluidLevel.at(i); -+ BlockState blockState2 = fluidLevel2.at(i); -+ if ((!blockState.is(Blocks.LAVA) || !blockState2.is(Blocks.WATER)) && (!blockState.is(Blocks.WATER) || !blockState2.is(Blocks.LAVA))) { -+ int abs = Math.abs(fluidLevel.fluidLevel - fluidLevel2.fluidLevel); - if (abs == 0) { - return 0.0; - } else { -- double d = 0.5 * (firstFluid.fluidLevel + secondFluid.fluidLevel); -- double d1 = i + 0.5 - d; -- double d2 = abs / 2.0; -- double d3 = 0.0; -- double d4 = 2.5; -- double d5 = 1.5; -- double d6 = 3.0; -- double d7 = 10.0; -- double d8 = 3.0; -- double d9 = d2 - Math.abs(d1); -- double d11; -- if (d1 > 0.0) { -- double d10 = 0.0 + d9; -- if (d10 > 0.0) { -- d11 = d10 / 1.5; -- } else { -- d11 = d10 / 2.5; -- } -- } else { -- double d10 = 3.0 + d9; -- if (d10 > 0.0) { -- d11 = d10 / 3.0; -- } else { -- d11 = d10 / 10.0; -- } -- } -- -- double d10x = 2.0; -- double d12; -- if (!(d11 < -2.0) && !(d11 > 2.0)) { -- double value = substance.getValue(); -- if (Double.isNaN(value)) { -- double d13 = this.barrierNoise.compute(context); -- substance.setValue(d13); -- d12 = d13; -- } else { -- d12 = value; -- } -- } else { -- d12 = 0.0; -- } -+ double d = 0.5 * (double)(fluidLevel.fluidLevel + fluidLevel2.fluidLevel); -+ final double q = aquiferExtracted$getQ(i, d, abs); - -- return 2.0 * (d12 + d11); -+ return aquiferExtracted$postCalculateDensity(context, substance, q); - } - } else { - return 2.0; - } - } -+ // DivineMC end - World gen optimizations - - private int gridX(int x) { - return Math.floorDiv(x, 16); -@@ -350,23 +236,25 @@ public interface Aquifer { - return Math.floorDiv(z, 16); - } - -- private Aquifer.FluidStatus getAquiferStatus(long packedPos) { -- int x = BlockPos.getX(packedPos); -- int y = BlockPos.getY(packedPos); -- int z = BlockPos.getZ(packedPos); -- int i = this.gridX(x); -- int i1 = this.gridY(y); -- int i2 = this.gridZ(z); -- int index = this.getIndex(i, i1, i2); -- Aquifer.FluidStatus fluidStatus = this.aquiferCache[index]; -- if (fluidStatus != null) { -- return fluidStatus; -+ // DivineMC start - World gen optimizations -+ private Aquifer.FluidStatus getAquiferStatus(long pos) { -+ int i = BlockPos.getX(pos); -+ int j = BlockPos.getY(pos); -+ int k = BlockPos.getZ(pos); -+ int l = i >> 4; // C2ME - inline: floorDiv(i, 16) -+ int m = Math.floorDiv(j, 12); // C2ME - inline -+ int n = k >> 4; // C2ME - inline: floorDiv(k, 16) -+ int o = this.getIndex(l, m, n); -+ Aquifer.FluidStatus fluidLevel = this.aquiferCache[o]; -+ if (fluidLevel != null) { -+ return fluidLevel; - } else { -- Aquifer.FluidStatus fluidStatus1 = this.computeFluid(x, y, z); -- this.aquiferCache[index] = fluidStatus1; -- return fluidStatus1; -+ Aquifer.FluidStatus fluidLevel2 = this.computeFluid(i, j, k); -+ this.aquiferCache[o] = fluidLevel2; -+ return fluidLevel2; - } - } -+ // DivineMC end - World gen optimizations - - private Aquifer.FluidStatus computeFluid(int x, int y, int z) { - Aquifer.FluidStatus fluidStatus = this.globalFluidPicker.computeFluid(x, y, z); -@@ -406,23 +294,22 @@ public interface Aquifer { - return new Aquifer.FluidStatus(i7, this.computeFluidType(x, y, z, fluidStatus, i7)); - } - -+ // DivineMC start - World gen optimizations - private int computeSurfaceLevel(int x, int y, int z, Aquifer.FluidStatus fluidStatus, int maxSurfaceLevel, boolean fluidPresent) { -- DensityFunction.SinglePointContext singlePointContext = new DensityFunction.SinglePointContext(x, y, z); -+ DensityFunction.SinglePointContext unblendedNoisePos = new DensityFunction.SinglePointContext(x, y, z); - double d; - double d1; -- if (OverworldBiomeBuilder.isDeepDarkRegion(this.erosion, this.depth, singlePointContext)) { -+ if (OverworldBiomeBuilder.isDeepDarkRegion(this.erosion, this.depth, unblendedNoisePos)) { - d = -1.0; - d1 = -1.0; - } else { - int i = maxSurfaceLevel + 8 - y; -- int i1 = 64; -- double d2 = fluidPresent ? Mth.clampedMap((double)i, 0.0, 64.0, 1.0, 0.0) : 0.0; -- double d3 = Mth.clamp(this.fluidLevelFloodednessNoise.compute(singlePointContext), -1.0, 1.0); -- double d4 = Mth.map(d2, 1.0, 0.0, -0.3, 0.8); -- double d5 = Mth.map(d2, 1.0, 0.0, -0.8, 0.4); -- d = d3 - d5; -- d1 = d3 - d4; -+ double f = fluidPresent ? Mth.clampedLerp(1.0, 0.0, ((double) i) / 64.0) : 0.0; // inline -+ double g = Mth.clamp(this.fluidLevelFloodednessNoise.compute(unblendedNoisePos), -1.0, 1.0); -+ d = g + 0.8 + (f - 1.0) * 1.2; // inline -+ d1 = g + 0.3 + (f - 1.0) * 1.1; // inline - } -+ // DivineMC end - World gen optimizations - - int i; - if (d1 > 0.0) { -@@ -466,5 +353,183 @@ public interface Aquifer { - - return blockState; - } -+ -+ // DivineMC start - World gen optimizations -+ private @org.jetbrains.annotations.Nullable BlockState aquiferExtracted$applyPost(DensityFunction.FunctionContext pos, double density, int j, int i, int k) { -+ Aquifer.FluidStatus fluidLevel2 = this.getAquiferStatus(this.c2me$pos1); -+ double d = similarity(this.c2me$dist1, this.c2me$dist2); -+ BlockState blockState = fluidLevel2.at(j); -+ if (d <= 0.0) { -+ this.shouldScheduleFluidUpdate = d >= FLOWING_UPDATE_SIMULARITY; -+ return blockState; -+ } else if (blockState.is(Blocks.WATER) && this.globalFluidPicker.computeFluid(i, j - 1, k).at(j - 1).is(Blocks.LAVA)) { -+ this.shouldScheduleFluidUpdate = true; -+ return blockState; -+ } else { -+ this.c2me$mutableDoubleThingy = Double.NaN; -+ Aquifer.FluidStatus fluidLevel3 = this.getAquiferStatus(this.c2me$pos2); -+ double e = d * this.c2me$calculateDensityModified(pos, fluidLevel2, fluidLevel3); -+ if (density + e > 0.0) { -+ this.shouldScheduleFluidUpdate = false; -+ return null; -+ } else { -+ return aquiferExtracted$getFinalBlockState(pos, density, d, fluidLevel2, fluidLevel3, blockState); -+ } -+ } -+ } -+ -+ private BlockState aquiferExtracted$getFinalBlockState(DensityFunction.FunctionContext pos, double density, double d, Aquifer.FluidStatus fluidLevel2, Aquifer.FluidStatus fluidLevel3, BlockState blockState) { -+ Aquifer.FluidStatus fluidLevel4 = this.getAquiferStatus(this.c2me$pos3); -+ double f = similarity(this.c2me$dist1, this.c2me$dist3); -+ if (aquiferExtracted$extractedCheckFG(pos, density, d, fluidLevel2, f, fluidLevel4)) return null; -+ -+ double g = similarity(this.c2me$dist2, this.c2me$dist3); -+ if (aquiferExtracted$extractedCheckFG(pos, density, d, fluidLevel3, g, fluidLevel4)) return null; -+ -+ this.shouldScheduleFluidUpdate = true; -+ return blockState; -+ } -+ -+ private boolean aquiferExtracted$extractedCheckFG(DensityFunction.FunctionContext pos, double density, double d, Aquifer.FluidStatus fluidLevel2, double f, Aquifer.FluidStatus fluidLevel4) { -+ if (f > 0.0) { -+ double g = d * f * this.c2me$calculateDensityModified(pos, fluidLevel2, fluidLevel4); -+ if (density + g > 0.0) { -+ this.shouldScheduleFluidUpdate = false; -+ return true; -+ } -+ } -+ return false; -+ } -+ -+ private void aquiferExtracted$refreshDistPosIdx(int x, int y, int z) { -+ int gx = (x - 5) >> 4; -+ int gy = Math.floorDiv(y + 1, 12); -+ int gz = (z - 5) >> 4; -+ int dist1 = Integer.MAX_VALUE; -+ int dist2 = Integer.MAX_VALUE; -+ int dist3 = Integer.MAX_VALUE; -+ long pos1 = 0; -+ long pos2 = 0; -+ long pos3 = 0; -+ -+ for (int offY = -1; offY <= 1; ++offY) { -+ for (int offZ = 0; offZ <= 1; ++offZ) { -+ for (int offX = 0; offX <= 1; ++offX) { -+ int posIdx = this.getIndex(gx + offX, gy + offY, gz + offZ); -+ -+ long position = this.aquiferLocationCache[posIdx]; -+ -+ int dx = BlockPos.getX(position) - x; -+ int dy = BlockPos.getY(position) - y; -+ int dz = BlockPos.getZ(position) - z; -+ int dist = dx * dx + dy * dy + dz * dz; -+ -+ if (dist3 >= dist) { -+ pos3 = position; -+ dist3 = dist; -+ } -+ if (dist2 >= dist) { -+ pos3 = pos2; -+ dist3 = dist2; -+ pos2 = position; -+ dist2 = dist; -+ } -+ if (dist1 >= dist) { -+ pos2 = pos1; -+ dist2 = dist1; -+ pos1 = position; -+ dist1 = dist; -+ } -+ } -+ } -+ } -+ -+ this.c2me$dist1 = dist1; -+ this.c2me$dist2 = dist2; -+ this.c2me$dist3 = dist3; -+ this.c2me$pos1 = pos1; -+ this.c2me$pos2 = pos2; -+ this.c2me$pos3 = pos3; -+ } -+ -+ private double c2me$calculateDensityModified( -+ DensityFunction.FunctionContext pos, Aquifer.FluidStatus fluidLevel, Aquifer.FluidStatus fluidLevel2 -+ ) { -+ int i = pos.blockY(); -+ BlockState blockState = fluidLevel.at(i); -+ BlockState blockState2 = fluidLevel2.at(i); -+ if ((!blockState.is(Blocks.LAVA) || !blockState2.is(Blocks.WATER)) && (!blockState.is(Blocks.WATER) || !blockState2.is(Blocks.LAVA))) { -+ int j = Math.abs(fluidLevel.fluidLevel - fluidLevel2.fluidLevel); -+ if (j == 0) { -+ return 0.0; -+ } else { -+ double d = 0.5 * (double)(fluidLevel.fluidLevel + fluidLevel2.fluidLevel); -+ final double q = aquiferExtracted$getQ(i, d, j); -+ -+ return aquiferExtracted$postCalculateDensityModified(pos, q); -+ } -+ } else { -+ return 2.0; -+ } -+ } -+ -+ private double aquiferExtracted$postCalculateDensity(DensityFunction.FunctionContext pos, MutableDouble mutableDouble, double q) { -+ double r; -+ if (!(q < -2.0) && !(q > 2.0)) { -+ double s = mutableDouble.getValue(); -+ if (Double.isNaN(s)) { -+ double t = this.barrierNoise.compute(pos); -+ mutableDouble.setValue(t); -+ r = t; -+ } else { -+ r = s; -+ } -+ } else { -+ r = 0.0; -+ } -+ -+ return 2.0 * (r + q); -+ } -+ -+ private double aquiferExtracted$postCalculateDensityModified(DensityFunction.FunctionContext pos, double q) { -+ double r; -+ if (!(q < -2.0) && !(q > 2.0)) { -+ double s = this.c2me$mutableDoubleThingy; -+ if (Double.isNaN(s)) { -+ double t = this.barrierNoise.compute(pos); -+ this.c2me$mutableDoubleThingy = t; -+ r = t; -+ } else { -+ r = s; -+ } -+ } else { -+ r = 0.0; -+ } -+ -+ return 2.0 * (r + q); -+ } -+ -+ private static double aquiferExtracted$getQ(double i, double d, double j) { -+ double e = i + 0.5 - d; -+ double f = j / 2.0; -+ double o = f - Math.abs(e); -+ double q; -+ if (e > 0.0) { -+ if (o > 0.0) { -+ q = o / 1.5; -+ } else { -+ q = o / 2.5; -+ } -+ } else { -+ double p = 3.0 + o; -+ if (p > 0.0) { -+ q = p / 3.0; -+ } else { -+ q = p / 10.0; -+ } -+ } -+ return q; -+ } -+ // DivineMC end - World gen optimizations - } - } -diff --git a/net/minecraft/world/level/levelgen/Beardifier.java b/net/minecraft/world/level/levelgen/Beardifier.java -index 131923282c9ecbcb1d7f45a826da907c02bd2716..36dd3eb0cb29d546531aec91a9c486be09975797 100644 ---- a/net/minecraft/world/level/levelgen/Beardifier.java -+++ b/net/minecraft/world/level/levelgen/Beardifier.java -@@ -29,6 +29,17 @@ public class Beardifier implements DensityFunctions.BeardifierOrMarker { - }); - private final ObjectListIterator pieceIterator; - private final ObjectListIterator junctionIterator; -+ // DivineMC start - World gen optimizations -+ private Beardifier.Rigid[] c2me$pieceArray; -+ private JigsawJunction[] c2me$junctionArray; -+ -+ private void c2me$initArrays() { -+ this.c2me$pieceArray = com.google.common.collect.Iterators.toArray(this.pieceIterator, Beardifier.Rigid.class); -+ this.pieceIterator.back(Integer.MAX_VALUE); -+ this.c2me$junctionArray = com.google.common.collect.Iterators.toArray(this.junctionIterator, JigsawJunction.class); -+ this.junctionIterator.back(Integer.MAX_VALUE); -+ } -+ // DivineMC end - World gen optimizations - - public static Beardifier forStructuresInChunk(StructureManager structureManager, ChunkPos chunkPos) { - int minBlockX = chunkPos.getMinBlockX(); -@@ -76,50 +87,44 @@ public class Beardifier implements DensityFunctions.BeardifierOrMarker { - this.junctionIterator = junctionIterator; - } - -+ // DivineMC start - World gen optimizations - @Override - public double compute(DensityFunction.FunctionContext context) { -+ if (this.c2me$pieceArray == null || this.c2me$junctionArray == null) { -+ this.c2me$initArrays(); -+ } - int i = context.blockX(); -- int i1 = context.blockY(); -- int i2 = context.blockZ(); -+ int j = context.blockY(); -+ int k = context.blockZ(); - double d = 0.0; - -- while (this.pieceIterator.hasNext()) { -- Beardifier.Rigid rigid = this.pieceIterator.next(); -- BoundingBox boundingBox = rigid.box(); -- int groundLevelDelta = rigid.groundLevelDelta(); -- int max = Math.max(0, Math.max(boundingBox.minX() - i, i - boundingBox.maxX())); -- int max1 = Math.max(0, Math.max(boundingBox.minZ() - i2, i2 - boundingBox.maxZ())); -- int i3 = boundingBox.minY() + groundLevelDelta; -- int i4 = i1 - i3; -- -- int i5 = switch (rigid.terrainAdjustment()) { -- case NONE -> 0; -- case BURY, BEARD_THIN -> i4; -- case BEARD_BOX -> Math.max(0, Math.max(i3 - i1, i1 - boundingBox.maxY())); -- case ENCAPSULATE -> Math.max(0, Math.max(boundingBox.minY() - i1, i1 - boundingBox.maxY())); -- }; -+ for (Beardifier.Rigid piece : this.c2me$pieceArray) { -+ BoundingBox blockBox = piece.box(); -+ int l = piece.groundLevelDelta(); -+ int m = Math.max(0, Math.max(blockBox.minX() - i, i - blockBox.maxX())); -+ int n = Math.max(0, Math.max(blockBox.minZ() - k, k - blockBox.maxZ())); -+ int o = blockBox.minY() + l; -+ int p = j - o; - -- d += switch (rigid.terrainAdjustment()) { -+ d += switch (piece.terrainAdjustment()) { // 2 switch statement merged - case NONE -> 0.0; -- case BURY -> getBuryContribution(max, i5 / 2.0, max1); -- case BEARD_THIN, BEARD_BOX -> getBeardContribution(max, i5, max1, i4) * 0.8; -- case ENCAPSULATE -> getBuryContribution(max / 2.0, i5 / 2.0, max1 / 2.0) * 0.8; -+ case BURY -> getBuryContribution(m, (double)p / 2.0, n); -+ case BEARD_THIN -> getBeardContribution(m, p, n, p) * 0.8; -+ case BEARD_BOX -> getBeardContribution(m, Math.max(0, Math.max(o - j, j - blockBox.maxY())), n, p) * 0.8; -+ case ENCAPSULATE -> getBuryContribution((double)m / 2.0, (double)Math.max(0, Math.max(blockBox.minY() - j, j - blockBox.maxY())) / 2.0, (double)n / 2.0) * 0.8; - }; - } - -- this.pieceIterator.back(Integer.MAX_VALUE); -- -- while (this.junctionIterator.hasNext()) { -- JigsawJunction jigsawJunction = this.junctionIterator.next(); -- int i6 = i - jigsawJunction.getSourceX(); -- int groundLevelDelta = i1 - jigsawJunction.getSourceGroundY(); -- int max = i2 - jigsawJunction.getSourceZ(); -- d += getBeardContribution(i6, groundLevelDelta, max, groundLevelDelta) * 0.4; -+ for (JigsawJunction jigsawJunction : this.c2me$junctionArray) { -+ int r = i - jigsawJunction.getSourceX(); -+ int l = j - jigsawJunction.getSourceGroundY(); -+ int m = k - jigsawJunction.getSourceZ(); -+ d += getBeardContribution(r, l, m, l) * 0.4; - } - -- this.junctionIterator.back(Integer.MAX_VALUE); - return d; - } -+ // DivineMC end - World gen optimizations - - @Override - public double minValue() { -@@ -132,8 +137,14 @@ public class Beardifier implements DensityFunctions.BeardifierOrMarker { - } - - private static double getBuryContribution(double x, double y, double z) { -- double len = Mth.length(x, y, z); -- return Mth.clampedMap(len, 0.0, 6.0, 1.0, 0.0); -+ // DivineMC start - World gen optimizations -+ double d = Math.sqrt(x * x + y * y + z * z); -+ if (d > 6.0) { -+ return 0.0; -+ } else { -+ return 1.0 - d / 6.0; -+ } -+ // DivineMC end - World gen optimizations - } - - private static double getBeardContribution(int x, int y, int z, int height) { -diff --git a/net/minecraft/world/level/levelgen/BelowZeroRetrogen.java b/net/minecraft/world/level/levelgen/BelowZeroRetrogen.java -index 4993ace2b3d615570de3d4b6621aeba3a3e7fe99..1be79332446559c95ae3048a71a6634fd01cf2e2 100644 ---- a/net/minecraft/world/level/levelgen/BelowZeroRetrogen.java -+++ b/net/minecraft/world/level/levelgen/BelowZeroRetrogen.java -@@ -82,6 +82,7 @@ public final class BelowZeroRetrogen { - } - - public void applyBedrockMask(ProtoChunk chunk) { -+ if (org.bxteam.divinemc.DivineConfig.smoothBedrockLayer) return; // DivineMC - Smooth bedrock layer - LevelHeightAccessor heightAccessorForGeneration = chunk.getHeightAccessorForGeneration(); - int minY = heightAccessorForGeneration.getMinY(); - int maxY = heightAccessorForGeneration.getMaxY(); -diff --git a/net/minecraft/world/level/levelgen/LegacyRandomSource.java b/net/minecraft/world/level/levelgen/LegacyRandomSource.java -index c67168517774a0ad9ca43422a79ef14a8ea0c2e8..026dfbbb6c3fd5cd274dcbf721e5cf3af889e3d9 100644 ---- a/net/minecraft/world/level/levelgen/LegacyRandomSource.java -+++ b/net/minecraft/world/level/levelgen/LegacyRandomSource.java -@@ -53,13 +53,7 @@ public class LegacyRandomSource implements BitRandomSource { - return this.gaussianSource.nextGaussian(); - } - -- public static class LegacyPositionalRandomFactory implements PositionalRandomFactory { -- private final long seed; -- -- public LegacyPositionalRandomFactory(long seed) { -- this.seed = seed; -- } -- -+ public record LegacyPositionalRandomFactory(long seed) implements PositionalRandomFactory { // DivineMC - make record - @Override - public RandomSource at(int x, int y, int z) { - long seed = Mth.getSeed(x, y, z); -diff --git a/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java b/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java -index 65728ef17e63d71833677fdcbd5bb90794b4822b..f79db926dc154dae3dd5405aee4582f9b10e873e 100644 ---- a/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java -+++ b/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java -@@ -57,6 +57,11 @@ public final class NoiseBasedChunkGenerator extends ChunkGenerator { - private static final BlockState AIR = Blocks.AIR.defaultBlockState(); - public final Holder settings; - private final Supplier globalFluidPicker; -+ // DivineMC start - Optimize noise fill -+ public static final java.util.concurrent.Executor EXECUTOR = org.bxteam.divinemc.DivineConfig.enableAsyncNoiseFill -+ ? java.util.concurrent.Executors.newThreadPerTaskExecutor(Thread.ofVirtual().name("worldgen_fill").factory()) -+ : Runnable::run; -+ // DivineMC end - Optimize noise fill - - public NoiseBasedChunkGenerator(BiomeSource biomeSource, Holder settings) { - super(biomeSource); -@@ -65,11 +70,13 @@ public final class NoiseBasedChunkGenerator extends ChunkGenerator { - } - - private static Aquifer.FluidPicker createFluidPicker(NoiseGeneratorSettings settings) { -- Aquifer.FluidStatus fluidStatus = new Aquifer.FluidStatus(-54, Blocks.LAVA.defaultBlockState()); -- int seaLevel = settings.seaLevel(); -- Aquifer.FluidStatus fluidStatus1 = new Aquifer.FluidStatus(seaLevel, settings.defaultFluid()); -- Aquifer.FluidStatus fluidStatus2 = new Aquifer.FluidStatus(DimensionType.MIN_Y * 2, Blocks.AIR.defaultBlockState()); -- return (x, y, z) -> y < Math.min(-54, seaLevel) ? fluidStatus : fluidStatus1; -+ // DivineMC start - World gen optimizations -+ Aquifer.FluidStatus fluidLevel = new Aquifer.FluidStatus(-54, Blocks.LAVA.defaultBlockState()); -+ int i = settings.seaLevel(); -+ Aquifer.FluidStatus fluidLevel2 = new Aquifer.FluidStatus(i, settings.defaultFluid()); -+ final int min = Math.min(-54, i); -+ return (j, k, lx) -> k < min ? fluidLevel : fluidLevel2; -+ // DivineMC end - World gen optimizations - } - - @Override -@@ -77,7 +84,7 @@ public final class NoiseBasedChunkGenerator extends ChunkGenerator { - return CompletableFuture.supplyAsync(() -> { - this.doCreateBiomes(blender, randomState, structureManager, chunk); - return chunk; -- }, Runnable::run); // Paper - rewrite chunk system -+ }, EXECUTOR); // Paper - rewrite chunk system // DivineMC - Optimize noise fill - } - - private void doCreateBiomes(Blender blender, RandomState random, StructureManager structureManager, ChunkAccess chunk) { -@@ -294,30 +301,35 @@ public final class NoiseBasedChunkGenerator extends ChunkGenerator { - public CompletableFuture fillFromNoise(Blender blender, RandomState randomState, StructureManager structureManager, ChunkAccess chunk) { - NoiseSettings noiseSettings = this.settings.value().noiseSettings().clampToHeightAccessor(chunk.getHeightAccessorForGeneration()); - int minY = noiseSettings.minY(); -- int i = Mth.floorDiv(minY, noiseSettings.getCellHeight()); -- int i1 = Mth.floorDiv(noiseSettings.height(), noiseSettings.getCellHeight()); -- return i1 <= 0 ? CompletableFuture.completedFuture(chunk) : CompletableFuture.supplyAsync(() -> { -- int sectionIndex = chunk.getSectionIndex(i1 * noiseSettings.getCellHeight() - 1 + minY); -- int sectionIndex1 = chunk.getSectionIndex(minY); -- Set set = Sets.newHashSet(); -- -- for (int i2 = sectionIndex; i2 >= sectionIndex1; i2--) { -- LevelChunkSection section = chunk.getSection(i2); -- section.acquire(); -- set.add(section); -- } -+ // DivineMC start - Optimize noise fill -+ int minYDiv = Mth.floorDiv(minY, noiseSettings.getCellHeight()); -+ int cellHeightDiv = Mth.floorDiv(noiseSettings.height(), noiseSettings.getCellHeight()); -+ -+ if (cellHeightDiv <= 0) { -+ return CompletableFuture.completedFuture(chunk); -+ } - -- ChunkAccess var20; -+ return CompletableFuture.supplyAsync(() -> { - try { -- var20 = this.doFill(blender, structureManager, randomState, chunk, i, i1); -- } finally { -- for (LevelChunkSection levelChunkSection1 : set) { -- levelChunkSection1.release(); -+ int startIndex = chunk.getSectionIndex(cellHeightDiv * noiseSettings.getCellHeight() - 1 + minY); -+ int minYIndex = chunk.getSectionIndex(minY); -+ LevelChunkSection[] sections = chunk.getSections(); -+ -+ for (int i = startIndex; i >= minYIndex; --i) { -+ sections[i].acquire(); -+ } -+ -+ ChunkAccess access = this.doFill(blender, structureManager, randomState, chunk, minYDiv, cellHeightDiv); -+ for (int i = startIndex; i >= minYIndex; --i) { -+ sections[i].release(); - } -- } - -- return var20; -- }, Runnable::run); // Paper - rewrite chunk system -+ return access; -+ } catch (Throwable throwable) { -+ throw new RuntimeException("Unexpected error when running noise fill", throwable); -+ } -+ }, EXECUTOR); // Paper - rewrite chunk system -+ // DivineMC end - Optimize noise fill - } - - private ChunkAccess doFill(Blender blender, StructureManager structureManager, RandomState random, ChunkAccess chunk, int minCellY, int cellCountY) { -@@ -375,7 +387,7 @@ public final class NoiseBasedChunkGenerator extends ChunkGenerator { - - interpolatedState = this.debugPreliminarySurfaceLevel(noiseChunk, i10, i7, i13, interpolatedState); - if (interpolatedState != AIR && !SharedConstants.debugVoidTerrain(chunk.getPos())) { -- section.setBlockState(i11, i8, i14, interpolatedState, false); -+ optimizedBlockSetOp(section, i11, i8, i14, interpolatedState, false); // DivineMC - Optimize noise fill - heightmapUnprimed.update(i11, i7, i14, interpolatedState); - heightmapUnprimed1.update(i11, i7, i14, interpolatedState); - if (aquifer.shouldScheduleFluidUpdate() && !interpolatedState.getFluidState().isEmpty()) { -@@ -396,6 +408,26 @@ public final class NoiseBasedChunkGenerator extends ChunkGenerator { - return chunk; - } - -+ // DivineMC start - Optimize noise fill -+ private void optimizedBlockSetOp(@org.jetbrains.annotations.NotNull LevelChunkSection chunkSection, int chunkSectionBlockPosX, int chunkSectionBlockPosY, int chunkSectionBlockPosZ, @org.jetbrains.annotations.NotNull BlockState blockState, boolean lock) { -+ chunkSection.nonEmptyBlockCount += 1; -+ -+ if (!blockState.getFluidState().isEmpty()) { -+ chunkSection.tickingFluidCount += 1; -+ } -+ -+ if (blockState.isRandomlyTicking()) { -+ chunkSection.tickingBlockCount += 1; -+ } -+ -+ var blockStateId = chunkSection.states.data.palette.idFor(blockState); -+ chunkSection.states.data.storage().set( -+ chunkSection.states.strategy.getIndex(chunkSectionBlockPosX, chunkSectionBlockPosY, -+ chunkSectionBlockPosZ -+ ), blockStateId); -+ } -+ // DivineMC end - Optimize noise fill -+ - private BlockState debugPreliminarySurfaceLevel(NoiseChunk chunk, int x, int y, int z, BlockState state) { - return state; - } -diff --git a/net/minecraft/world/level/levelgen/NoiseSettings.java b/net/minecraft/world/level/levelgen/NoiseSettings.java -index 4cf3a364595ba5f81f741295695cb9a449bdf672..44df2ac0bd972c4d97fc89cd0c2d2d83480ca3e1 100644 ---- a/net/minecraft/world/level/levelgen/NoiseSettings.java -+++ b/net/minecraft/world/level/levelgen/NoiseSettings.java -@@ -8,7 +8,7 @@ import net.minecraft.core.QuartPos; - import net.minecraft.world.level.LevelHeightAccessor; - import net.minecraft.world.level.dimension.DimensionType; - --public record NoiseSettings(int minY, int height, int noiseSizeHorizontal, int noiseSizeVertical) { -+public record NoiseSettings(int minY, int height, int noiseSizeHorizontal, int noiseSizeVertical, int horizontalCellBlockCount, int verticalCellBlockCount) { // DivineMC - NoiseSettings optimizations - public static final Codec CODEC = RecordCodecBuilder.create( - instance -> instance.group( - Codec.intRange(DimensionType.MIN_Y, DimensionType.MAX_Y).fieldOf("min_y").forGetter(NoiseSettings::minY), -@@ -16,7 +16,10 @@ public record NoiseSettings(int minY, int height, int noiseSizeHorizontal, int n - Codec.intRange(1, 4).fieldOf("size_horizontal").forGetter(NoiseSettings::noiseSizeHorizontal), - Codec.intRange(1, 4).fieldOf("size_vertical").forGetter(NoiseSettings::noiseSizeVertical) - ) -- .apply(instance, NoiseSettings::new) -+ // DivineMC start - NoiseSettings optimizations -+ .apply(instance, (Integer minY1, Integer height1, Integer noiseSizeHorizontal1, Integer noiseSizeVertical1) -> new NoiseSettings(minY1, height1, noiseSizeHorizontal1, noiseSizeVertical1, -+ QuartPos.toBlock(noiseSizeHorizontal1), QuartPos.toBlock(noiseSizeVertical1))) -+ // DivineMC end - NoiseSettings optimizations - ) - .comapFlatMap(NoiseSettings::guardY, Function.identity()); - protected static final NoiseSettings OVERWORLD_NOISE_SETTINGS = create(-64, 384, 1, 2); -@@ -36,7 +39,7 @@ public record NoiseSettings(int minY, int height, int noiseSizeHorizontal, int n - } - - public static NoiseSettings create(int minY, int height, int noiseSizeHorizontal, int noiseSizeVertical) { -- NoiseSettings noiseSettings = new NoiseSettings(minY, height, noiseSizeHorizontal, noiseSizeVertical); -+ NoiseSettings noiseSettings = new NoiseSettings(minY, height, noiseSizeHorizontal, noiseSizeVertical, QuartPos.toBlock(noiseSizeHorizontal), QuartPos.toBlock(noiseSizeVertical)); // DivineMC - NoiseSettings optimizations - guardY(noiseSettings).error().ifPresent(error -> { - throw new IllegalStateException(error.message()); - }); -@@ -44,16 +47,16 @@ public record NoiseSettings(int minY, int height, int noiseSizeHorizontal, int n - } - - public int getCellHeight() { -- return QuartPos.toBlock(this.noiseSizeVertical()); -+ return verticalCellBlockCount(); // DivineMC - NoiseSettings optimizations - } - - public int getCellWidth() { -- return QuartPos.toBlock(this.noiseSizeHorizontal()); -+ return horizontalCellBlockCount(); // DivineMC - NoiseSettings optimizations - } - - public NoiseSettings clampToHeightAccessor(LevelHeightAccessor heightAccessor) { - int max = Math.max(this.minY, heightAccessor.getMinY()); - int i = Math.min(this.minY + this.height, heightAccessor.getMaxY() + 1) - max; -- return new NoiseSettings(max, i, this.noiseSizeHorizontal, this.noiseSizeVertical); -+ return new NoiseSettings(max, i, this.noiseSizeHorizontal, this.noiseSizeVertical, QuartPos.toBlock(this.noiseSizeHorizontal), QuartPos.toBlock(this.noiseSizeVertical)); // DivineMC - NoiseSettings optimizations - } - } -diff --git a/net/minecraft/world/level/levelgen/XoroshiroRandomSource.java b/net/minecraft/world/level/levelgen/XoroshiroRandomSource.java -index 9d3a9ca1e13cd80f468f1352bbb74345f03903dd..d97b9b43686bda0a95fc02f6ca31b2d07d603a32 100644 ---- a/net/minecraft/world/level/levelgen/XoroshiroRandomSource.java -+++ b/net/minecraft/world/level/levelgen/XoroshiroRandomSource.java -@@ -106,15 +106,7 @@ public class XoroshiroRandomSource implements RandomSource { - return this.randomNumberGenerator.nextLong() >>> 64 - bits; - } - -- public static class XoroshiroPositionalRandomFactory implements PositionalRandomFactory { -- private final long seedLo; -- private final long seedHi; -- -- public XoroshiroPositionalRandomFactory(long seedLo, long seedHi) { -- this.seedLo = seedLo; -- this.seedHi = seedHi; -- } -- -+ public record XoroshiroPositionalRandomFactory(long seedLo, long seedHi) implements PositionalRandomFactory { // DivineMC - make record - @Override - public RandomSource at(int x, int y, int z) { - long seed = Mth.getSeed(x, y, z); -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/0013-Chunk-System-optimization.patch b/divinemc-server/minecraft-patches/features/0013-Chunk-System-optimization.patch deleted file mode 100644 index c120f72..0000000 --- a/divinemc-server/minecraft-patches/features/0013-Chunk-System-optimization.patch +++ /dev/null @@ -1,985 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> -Date: Sat, 1 Feb 2025 00:33:03 +0300 -Subject: [PATCH] Chunk System optimization - - -diff --git a/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java b/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java -index 1b8193587814225c2ef2c5d9e667436eb50ff6c5..b588449cfe766c14a0cf4ea9640b04a51bbcf433 100644 ---- a/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java -+++ b/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java -@@ -59,12 +59,15 @@ public final class NearbyPlayers { - public static final int GENERAL_REALLY_SMALL_AREA_VIEW_DISTANCE_BLOCKS = (GENERAL_REALLY_SMALL_VIEW_DISTANCE << 4); - - private final ServerLevel world; -- private final Reference2ReferenceOpenHashMap players = new Reference2ReferenceOpenHashMap<>(); -- private final Long2ReferenceOpenHashMap byChunk = new Long2ReferenceOpenHashMap<>(); -- private final Long2ReferenceOpenHashMap>[] directByChunk = new Long2ReferenceOpenHashMap[TOTAL_MAP_TYPES]; -+ // DivineMC start - Chunk System optimization -+ private final Object callbackLock = new Object(); -+ private final it.unimi.dsi.fastutil.objects.Reference2ReferenceMap players = it.unimi.dsi.fastutil.objects.Reference2ReferenceMaps.synchronize(new Reference2ReferenceOpenHashMap<>()); -+ private final it.unimi.dsi.fastutil.longs.Long2ReferenceMap byChunk = it.unimi.dsi.fastutil.longs.Long2ReferenceMaps.synchronize(new Long2ReferenceOpenHashMap<>()); -+ private final it.unimi.dsi.fastutil.longs.Long2ReferenceMap>[] directByChunk = new it.unimi.dsi.fastutil.longs.Long2ReferenceMap[TOTAL_MAP_TYPES]; -+ // DivineMC end - Chunk System optimization - { - for (int i = 0; i < this.directByChunk.length; ++i) { -- this.directByChunk[i] = new Long2ReferenceOpenHashMap<>(); -+ this.directByChunk[i] = it.unimi.dsi.fastutil.longs.Long2ReferenceMaps.synchronize(new Long2ReferenceOpenHashMap<>()); // DivineMC - Chunk System optimization - } - } - -@@ -188,7 +191,10 @@ public final class NearbyPlayers { - final ReferenceList list = this.players[idx]; - if (list == null) { - ++this.nonEmptyLists; -- final ReferenceList players = (this.players[idx] = new ReferenceList<>(EMPTY_PLAYERS_ARRAY)); -+ // DivineMC start - Chunk System optimization -+ this.players[idx] = new ReferenceList<>(EMPTY_PLAYERS_ARRAY); -+ final ReferenceList players = this.players[idx]; -+ // DivineMC end - Chunk System optimization - this.nearbyPlayers.directByChunk[idx].put(this.chunkKey, players); - players.add(player); - return; -diff --git a/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/util/ZeroCollidingReferenceStateTable.java b/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/util/ZeroCollidingReferenceStateTable.java -index 866f38eb0f379ffbe2888023a7d1c290f521a231..08666b4aa1c7663861dc361f60e6f1cc46694521 100644 ---- a/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/util/ZeroCollidingReferenceStateTable.java -+++ b/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/util/ZeroCollidingReferenceStateTable.java -@@ -21,13 +21,15 @@ import net.minecraft.world.level.block.state.properties.Property; - - public final class ZeroCollidingReferenceStateTable { - -- private final Int2ObjectOpenHashMap propertyToIndexer; -+ private final it.unimi.dsi.fastutil.ints.Int2ObjectMap propertyToIndexer; // DivineMC - Chunk System optimization - private S[] lookup; - private final Collection> properties; - - public ZeroCollidingReferenceStateTable(final Collection> properties) { -- this.propertyToIndexer = new Int2ObjectOpenHashMap<>(properties.size()); -- this.properties = new ReferenceArrayList<>(properties); -+ // DivineMC start - Chunk System optimization -+ this.propertyToIndexer = it.unimi.dsi.fastutil.ints.Int2ObjectMaps.synchronize(new Int2ObjectOpenHashMap<>(properties.size())); -+ this.properties = it.unimi.dsi.fastutil.objects.ReferenceLists.synchronize(new ReferenceArrayList<>(properties)); -+ // DivineMC end - Chunk System optimization - - final List> sortedProperties = new ArrayList<>(properties); - -@@ -77,11 +79,11 @@ public final class ZeroCollidingReferenceStateTable { - return ret; - } - -- public boolean isLoaded() { -+ public synchronized boolean isLoaded() { // DivineMC - Chunk System optimization - return this.lookup != null; - } - -- public void loadInTable(final Map, Comparable>, S> universe) { -+ public synchronized void loadInTable(final Map, Comparable>, S> universe) { // DivineMC - Chunk System optimization - if (this.lookup != null) { - throw new IllegalStateException(); - } -@@ -117,7 +119,7 @@ public final class ZeroCollidingReferenceStateTable { - return ((PropertyAccess)property).moonrise$getById((int)modded); - } - -- public > S set(final long index, final Property property, final T with) { -+ public synchronized > S set(final long index, final Property property, final T with) { // DivineMC - Chunk System optimization - final int newValueId = ((PropertyAccess)property).moonrise$getIdFor(with); - if (newValueId < 0) { - return null; -@@ -139,7 +141,7 @@ public final class ZeroCollidingReferenceStateTable { - return this.lookup[(int)newIndex]; - } - -- public > S trySet(final long index, final Property property, final T with, final S dfl) { -+ public synchronized > S trySet(final long index, final Property property, final T with, final S dfl) { // DivineMC - Chunk System optimization - final Indexer indexer = this.propertyToIndexer.get(((PropertyAccess)property).moonrise$getId()); - if (indexer == null) { - return dfl; -diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java b/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java -index 8ef5a1aaac9c27873ce746eb281f77bb318a3c69..76b8d42ae530b59cdaba0583365a557da6b90ede 100644 ---- a/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java -+++ b/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java -@@ -301,7 +301,7 @@ public final class RegionizedPlayerChunkLoader { - return false; - } - -- public void tick() { -+ public synchronized void tick() { // DivineMC - Chunk System optimization - synchronized - TickThread.ensureTickThread("Cannot tick player chunk loader async"); - long currTime = System.nanoTime(); - for (final ServerPlayer player : new java.util.ArrayList<>(this.world.players())) { -@@ -312,6 +312,7 @@ public final class RegionizedPlayerChunkLoader { - } - loader.update(); // can't invoke plugin logic - loader.updateQueues(currTime); -+ player.connection.resumeFlushing(); // DivineMC - Chunk System optimization - } - } - -@@ -362,7 +363,7 @@ public final class RegionizedPlayerChunkLoader { - GENERATED_TICKET_LEVEL, - TICK_TICKET_LEVEL - }; -- private final Long2ByteOpenHashMap chunkTicketStage = new Long2ByteOpenHashMap(); -+ private final it.unimi.dsi.fastutil.longs.Long2ByteMap chunkTicketStage = it.unimi.dsi.fastutil.longs.Long2ByteMaps.synchronize(new Long2ByteOpenHashMap()); // DivineMC - Chunk System optimization - { - this.chunkTicketStage.defaultReturnValue(CHUNK_TICKET_STAGE_NONE); - } -@@ -384,10 +385,19 @@ public final class RegionizedPlayerChunkLoader { - final int centerX = PlayerChunkLoaderData.this.lastChunkX; - final int centerZ = PlayerChunkLoaderData.this.lastChunkZ; - -- return Integer.compare( -- Math.abs(c1x - centerX) + Math.abs(c1z - centerZ), -- Math.abs(c2x - centerX) + Math.abs(c2z - centerZ) -- ); -+ // DivineMC start - Chunk Loading Priority Optimization -+ if (org.bxteam.divinemc.DivineConfig.chunkTaskPriority == org.bxteam.divinemc.server.chunk.ChunkTaskPriority.EUCLIDEAN_CIRCLE_PATTERN) { -+ return Integer.compare( -+ (c1x - centerX) * (c1x - centerX) + (c1z - centerZ) * (c1z - centerZ), -+ (c2x - centerX) * (c2x - centerX) + (c2z - centerZ) * (c2z - centerZ) -+ ); -+ } else { -+ return Integer.compare( -+ Math.abs(c1x - centerX) + Math.abs(c1z - centerZ), -+ Math.abs(c2x - centerX) + Math.abs(c2z - centerZ) -+ ); -+ } -+ // DivineMC end - Chunk Loading Priority Optimization - }; - private final LongHeapPriorityQueue sendQueue = new LongHeapPriorityQueue(CLOSEST_MANHATTAN_DIST); - private final LongHeapPriorityQueue tickingQueue = new LongHeapPriorityQueue(CLOSEST_MANHATTAN_DIST); -@@ -490,7 +500,7 @@ public final class RegionizedPlayerChunkLoader { - } - - @Override -- protected void removeCallback(final PlayerChunkLoaderData parameter, final int chunkX, final int chunkZ) { -+ protected synchronized void removeCallback(final PlayerChunkLoaderData parameter, final int chunkX, final int chunkZ) { // DivineMC - Chunk System optimization - synchronized - final long chunk = CoordinateUtils.getChunkKey(chunkX, chunkZ); - // note: by the time this is called, the tick cleanup should have ran - so, if the chunk is at - // the tick stage it was deemed in range for loading. Thus, we need to move it to generated -@@ -624,7 +634,7 @@ public final class RegionizedPlayerChunkLoader { - return Math.max(Math.abs(dx), Math.abs(dz)) <= this.lastTickDistance; - } - -- private boolean areNeighboursGenerated(final int chunkX, final int chunkZ, final int radius) { -+ private synchronized boolean areNeighboursGenerated(final int chunkX, final int chunkZ, final int radius) { // DivineMC - Chunk System optimization - synchronized - for (int dz = -radius; dz <= radius; ++dz) { - for (int dx = -radius; dx <= radius; ++dx) { - if ((dx | dz) == 0) { -@@ -643,19 +653,11 @@ public final class RegionizedPlayerChunkLoader { - return true; - } - -- void updateQueues(final long time) { -+ synchronized void updateQueues(final long time) { // DivineMC - Chunk System optimization - synchronized - TickThread.ensureTickThread(this.player, "Cannot tick player chunk loader async"); - if (this.removed) { - throw new IllegalStateException("Ticking removed player chunk loader"); - } -- // update rate limits -- final double loadRate = this.getMaxChunkLoadRate(); -- final double genRate = this.getMaxChunkGenRate(); -- final double sendRate = this.getMaxChunkSendRate(); -- -- this.chunkLoadTicketLimiter.tickAllocation(time, loadRate, loadRate); -- this.chunkGenerateTicketLimiter.tickAllocation(time, genRate, genRate); -- this.chunkSendLimiter.tickAllocation(time, sendRate, sendRate); - - // try to progress chunk loads - while (!this.loadingQueue.isEmpty()) { -@@ -682,8 +684,7 @@ public final class RegionizedPlayerChunkLoader { - } - - // try to push more chunk loads -- final long maxLoads = Math.max(0L, Math.min(MAX_RATE, Math.min(this.loadQueue.size(), this.getMaxChunkLoads()))); -- final int maxLoadsThisTick = (int)this.chunkLoadTicketLimiter.takeAllocation(time, loadRate, maxLoads); -+ final int maxLoadsThisTick = this.loadQueue.size(); // DivineMC - Chunk System optimization - if (maxLoadsThisTick > 0) { - final LongArrayList chunks = new LongArrayList(maxLoadsThisTick); - for (int i = 0; i < maxLoadsThisTick; ++i) { -@@ -758,9 +759,7 @@ public final class RegionizedPlayerChunkLoader { - } - - // try to push more chunk generations -- final long maxGens = Math.max(0L, Math.min(MAX_RATE, Math.min(this.genQueue.size(), this.getMaxChunkGenerates()))); -- // preview the allocations, as we may not actually utilise all of them -- final long maxGensThisTick = this.chunkGenerateTicketLimiter.previewAllocation(time, genRate, maxGens); -+ final long maxGensThisTick = this.genQueue.size(); // DivineMC - Chunk System optimization - long ratedGensThisTick = 0L; - while (!this.genQueue.isEmpty()) { - final long chunkKey = this.genQueue.firstLong(); -@@ -790,8 +789,6 @@ public final class RegionizedPlayerChunkLoader { - ); - this.generatingQueue.enqueue(chunkKey); - } -- // take the allocations we actually used -- this.chunkGenerateTicketLimiter.takeAllocation(time, genRate, ratedGensThisTick); - - // try to pull ticking chunks - while (!this.tickingQueue.isEmpty()) { -@@ -821,10 +818,10 @@ public final class RegionizedPlayerChunkLoader { - } - - // try to pull sending chunks -- final long maxSends = Math.max(0L, Math.min(MAX_RATE, Integer.MAX_VALUE)); // note: no logic to track concurrent sends -- final int maxSendsThisTick = Math.min((int)this.chunkSendLimiter.takeAllocation(time, sendRate, maxSends), this.sendQueue.size()); -+ final int maxSendsThisTick = this.sendQueue.size(); // DivineMC - Chunk System optimization - // we do not return sends that we took from the allocation back because we want to limit the max send rate, not target it - for (int i = 0; i < maxSendsThisTick; ++i) { -+ if (this.sendQueue.isEmpty()) break; // DivineMC - Chunk System optimization - final long pendingSend = this.sendQueue.firstLong(); - final int pendingSendX = CoordinateUtils.getChunkX(pendingSend); - final int pendingSendZ = CoordinateUtils.getChunkZ(pendingSend); -@@ -889,9 +886,6 @@ public final class RegionizedPlayerChunkLoader { - - // reset limiters, they will start at a zero allocation - final long time = System.nanoTime(); -- this.chunkLoadTicketLimiter.reset(time); -- this.chunkGenerateTicketLimiter.reset(time); -- this.chunkSendLimiter.reset(time); - - // now we can update - this.update(); -@@ -910,10 +904,10 @@ public final class RegionizedPlayerChunkLoader { - ); - } - -- void update() { -+ synchronized void update() { // DivineMC - Chunk System optimization - synchronized - TickThread.ensureTickThread(this.player, "Cannot update player asynchronously"); - if (this.removed) { -- throw new IllegalStateException("Updating removed player chunk loader"); -+ return; // DivineMC - Chunk System optimization - } - final ViewDistances playerDistances = ((ChunkSystemServerPlayer)this.player).moonrise$getViewDistanceHolder().getViewDistances(); - final ViewDistances worldDistances = ((ChunkSystemServerLevel)this.world).moonrise$getViewDistanceHolder().getViewDistances(); -@@ -1062,7 +1056,7 @@ public final class RegionizedPlayerChunkLoader { - this.flushDelayedTicketOps(); - } - -- void remove() { -+ synchronized void remove() { // DivineMC - Chunk System optimization - synchronized - TickThread.ensureTickThread(this.player, "Cannot add player asynchronously"); - if (this.removed) { - throw new IllegalStateException("Removing removed player chunk loader"); -@@ -1090,7 +1084,7 @@ public final class RegionizedPlayerChunkLoader { - } - - public LongOpenHashSet getSentChunksRaw() { -- return this.sentChunks; -+ return new LongOpenHashSet(this.sentChunks); // DivineMC - Chunk System optimization - } - } - } -diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java -index 0c99bffa769d53562a10d23c4a9b37dc59c7f478..f4fcc3b2676b17ebc276dcb177285f18a0cdfe99 100644 ---- a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java -+++ b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java -@@ -71,36 +71,49 @@ public final class ChunkHolderManager { - private static final long PROBE_MARKER = Long.MIN_VALUE + 1; - public final ReentrantAreaLock ticketLockArea; - -- private final ConcurrentLong2ReferenceChainedHashTable>> tickets = new ConcurrentLong2ReferenceChainedHashTable<>(); -- private final ConcurrentLong2ReferenceChainedHashTable sectionToChunkToExpireCount = new ConcurrentLong2ReferenceChainedHashTable<>(); -+ // DivineMC start - Chunk System optimization -+ private final ConcurrentLong2ReferenceChainedHashTable>> tickets = ConcurrentLong2ReferenceChainedHashTable.createWithCapacity(20, 0.9F); -+ private final ConcurrentLong2ReferenceChainedHashTable sectionToChunkToExpireCount = ConcurrentLong2ReferenceChainedHashTable.createWithCapacity(20, 0.9F); -+ // DivineMC end - Chunk System optimization - final ChunkUnloadQueue unloadQueue; - -- private final ConcurrentLong2ReferenceChainedHashTable chunkHolders = ConcurrentLong2ReferenceChainedHashTable.createWithCapacity(16384, 0.25f); -+ private final ConcurrentLong2ReferenceChainedHashTable chunkHolders = ConcurrentLong2ReferenceChainedHashTable.createWithCapacity(20, 0.9F); // DivineMC - Chunk System optimization - private final ServerLevel world; - private final ChunkTaskScheduler taskScheduler; - private long currentTick; - -- private final ArrayDeque pendingFullLoadUpdate = new ArrayDeque<>(); -- private final ObjectRBTreeSet autoSaveQueue = new ObjectRBTreeSet<>((final NewChunkHolder c1, final NewChunkHolder c2) -> { -- if (c1 == c2) { -- return 0; -- } -+ // DivineMC start - Chunk System optimization -+ public static class LevelHolderData { -+ private final java.util.concurrent.ConcurrentLinkedDeque pendingFullLoadUpdate = new java.util.concurrent.ConcurrentLinkedDeque<>(); -+ private final ObjectRBTreeSet autoSaveQueue = new ObjectRBTreeSet<>((final NewChunkHolder c1, final NewChunkHolder c2) -> { -+ if (c1 == c2) { -+ return 0; -+ } - -- final int saveTickCompare = Long.compare(c1.lastAutoSave, c2.lastAutoSave); -+ final int saveTickCompare = Long.compare(c1.lastAutoSave, c2.lastAutoSave); - -- if (saveTickCompare != 0) { -- return saveTickCompare; -- } -+ if (saveTickCompare != 0) { -+ return saveTickCompare; -+ } - -- final long coord1 = CoordinateUtils.getChunkKey(c1.chunkX, c1.chunkZ); -- final long coord2 = CoordinateUtils.getChunkKey(c2.chunkX, c2.chunkZ); -+ final long coord1 = CoordinateUtils.getChunkKey(c1.chunkX, c1.chunkZ); -+ final long coord2 = CoordinateUtils.getChunkKey(c2.chunkX, c2.chunkZ); - -- if (coord1 == coord2) { -- throw new IllegalStateException("Duplicate chunkholder in auto save queue"); -- } -+ if (coord1 == coord2) { -+ throw new IllegalStateException("Duplicate chunkholder in auto save queue"); -+ } - -- return Long.compare(coord1, coord2); -- }); -+ return Long.compare(coord1, coord2); -+ }); -+ } -+ -+ public LevelHolderData getData() { -+ if (this.world == null) { -+ throw new RuntimeException("World was null!"); -+ } -+ return world.chunkHolderData; -+ } -+ // DivineMC end - Chunk System optimization - - public ChunkHolderManager(final ServerLevel world, final ChunkTaskScheduler taskScheduler) { - this.world = world; -@@ -222,26 +235,29 @@ public final class ChunkHolderManager { - this.taskScheduler.setShutdown(true); - } - -- void ensureInAutosave(final NewChunkHolder holder) { -- if (!this.autoSaveQueue.contains(holder)) { -+ // DivineMC start - Chunk System optimization -+ synchronized void ensureInAutosave(final NewChunkHolder holder) { -+ final LevelHolderData data = getData(); -+ if (!data.autoSaveQueue.contains(holder)) { - holder.lastAutoSave = this.currentTick; -- this.autoSaveQueue.add(holder); -+ data.autoSaveQueue.add(holder); - } - } - -- public void autoSave() { -+ public synchronized void autoSave() { -+ final LevelHolderData data = getData(); - final List reschedule = new ArrayList<>(); - final long currentTick = this.currentTick; - final long maxSaveTime = currentTick - Math.max(1L, PlatformHooks.get().configAutoSaveInterval(this.world)); - final int maxToSave = PlatformHooks.get().configMaxAutoSavePerTick(this.world); -- for (int autoSaved = 0; autoSaved < maxToSave && !this.autoSaveQueue.isEmpty();) { -- final NewChunkHolder holder = this.autoSaveQueue.first(); -+ for (int autoSaved = 0; autoSaved < maxToSave && !data.autoSaveQueue.isEmpty();) { -+ final NewChunkHolder holder = data.autoSaveQueue.first(); - - if (holder.lastAutoSave > maxSaveTime) { - break; - } - -- this.autoSaveQueue.remove(holder); -+ data.autoSaveQueue.remove(holder); - - holder.lastAutoSave = currentTick; - if (holder.save(false) != null) { -@@ -255,10 +271,11 @@ public final class ChunkHolderManager { - - for (final NewChunkHolder holder : reschedule) { - if (holder.getChunkStatus().isOrAfter(FullChunkStatus.FULL)) { -- this.autoSaveQueue.add(holder); -+ data.autoSaveQueue.add(holder); - } - } - } -+ // DivineMC end - Chunk System optimization - - public void saveAllChunks(final boolean flush, final boolean shutdown, final boolean logProgress) { - final List holders = this.getChunkHolders(); -@@ -317,13 +334,9 @@ public final class ChunkHolderManager { - } - if (logProgress) { - final long currTime = System.nanoTime(); -- if ((currTime - lastLog) > TimeUnit.SECONDS.toNanos(10L)) { -+ if ((currTime - lastLog) > TimeUnit.SECONDS.toNanos(5L)) { // DivineMC - Log a bit more frequently - lastLog = currTime; -- LOGGER.info( -- "Saved " + savedChunk + " block chunks, " + savedEntity + " entity chunks, " + savedPoi -- + " poi chunks in world '" + WorldUtil.getWorldName(this.world) + "', progress: " -- + format.format((double)(i+1)/(double)len * 100.0) -- ); -+ LOGGER.info("Saved {} block chunks, {} entity chunks, {} poi chunks in world '{}', progress: {}", savedChunk, savedEntity, savedPoi, ca.spottedleaf.moonrise.common.util.WorldUtil.getWorldName(this.world), format.format((double) (i + 1) / (double) len * 100.0)); // DivineMC - Beautify log - } - } - } -@@ -425,8 +438,8 @@ public final class ChunkHolderManager { - final Long2ObjectOpenHashMap>> ret = new Long2ObjectOpenHashMap<>(); - final Long2ObjectOpenHashMap sections = new Long2ObjectOpenHashMap<>(); - final int sectionShift = this.taskScheduler.getChunkSystemLockShift(); -- for (final PrimitiveIterator.OfLong iterator = this.tickets.keyIterator(); iterator.hasNext();) { -- final long coord = iterator.nextLong(); -+ for (final Iterator iterator = this.tickets.keyIterator(); iterator.hasNext();) { // DivineMC - Chunk System optimization -+ final long coord = iterator.next(); // DivineMC - Chunk System optimization - sections.computeIfAbsent( - CoordinateUtils.getChunkKey( - CoordinateUtils.getChunkX(coord) >> sectionShift, -@@ -523,7 +536,7 @@ public final class ChunkHolderManager { - chunkZ >> sectionShift - ); - -- this.sectionToChunkToExpireCount.computeIfAbsent(sectionKey, (final long keyInMap) -> { -+ this.sectionToChunkToExpireCount.computeIfAbsent(sectionKey, (keyInMap) -> { // DivineMC - Chunk System optimization - return new Long2IntOpenHashMap(); - }).addTo(chunkKey, 1); - } -@@ -567,7 +580,7 @@ public final class ChunkHolderManager { - - final ReentrantAreaLock.Node ticketLock = lock ? this.ticketLockArea.lock(chunkX, chunkZ) : null; - try { -- final SortedArraySet> ticketsAtChunk = this.tickets.computeIfAbsent(chunk, (final long keyInMap) -> { -+ final SortedArraySet> ticketsAtChunk = this.tickets.computeIfAbsent(chunk, (keyInMap) -> { // DivineMC - Chunk System optimization - return SortedArraySet.create(4); - }); - -@@ -697,8 +710,8 @@ public final class ChunkHolderManager { - - final Long2ObjectOpenHashMap sections = new Long2ObjectOpenHashMap<>(); - final int sectionShift = this.taskScheduler.getChunkSystemLockShift(); -- for (final PrimitiveIterator.OfLong iterator = this.tickets.keyIterator(); iterator.hasNext();) { -- final long coord = iterator.nextLong(); -+ for (final Iterator iterator = this.tickets.keyIterator(); iterator.hasNext();) { // DivineMC - Chunk System optimization -+ final long coord = iterator.next(); // DivineMC - Chunk System optimization - sections.computeIfAbsent( - CoordinateUtils.getChunkKey( - CoordinateUtils.getChunkX(coord) >> sectionShift, -@@ -746,8 +759,8 @@ public final class ChunkHolderManager { - return removeDelay <= 0L; - }; - -- for (final PrimitiveIterator.OfLong iterator = this.sectionToChunkToExpireCount.keyIterator(); iterator.hasNext();) { -- final long sectionKey = iterator.nextLong(); -+ for (final Iterator iterator = this.sectionToChunkToExpireCount.keyIterator(); iterator.hasNext();) { // DivineMC - Chunk System optimization -+ final long sectionKey = iterator.next(); // DivineMC - Chunk System optimization - - if (!this.sectionToChunkToExpireCount.containsKey(sectionKey)) { - // removed concurrently -@@ -1033,7 +1046,7 @@ public final class ChunkHolderManager { - } - if (!TickThread.isTickThreadFor(world)) { // DivineMC - parallel world ticking - this.taskScheduler.scheduleChunkTask(() -> { -- final ArrayDeque pendingFullLoadUpdate = ChunkHolderManager.this.pendingFullLoadUpdate; -+ final java.util.Deque pendingFullLoadUpdate = ChunkHolderManager.this.getData().pendingFullLoadUpdate; // DivineMC - Chunk System optimization - for (int i = 0, len = changedFullStatus.size(); i < len; ++i) { - pendingFullLoadUpdate.add(changedFullStatus.get(i)); - } -@@ -1041,16 +1054,16 @@ public final class ChunkHolderManager { - ChunkHolderManager.this.processPendingFullUpdate(); - }, Priority.HIGHEST); - } else { -- final ArrayDeque pendingFullLoadUpdate = this.pendingFullLoadUpdate; -+ final java.util.Deque pendingFullLoadUpdate = this.getData().pendingFullLoadUpdate; // DivineMC - Chunk System optimization - for (int i = 0, len = changedFullStatus.size(); i < len; ++i) { - pendingFullLoadUpdate.add(changedFullStatus.get(i)); - } - } - } - -- private void removeChunkHolder(final NewChunkHolder holder) { -+ private synchronized void removeChunkHolder(final NewChunkHolder holder) { // DivineMC - Chunk System optimization - holder.onUnload(); -- this.autoSaveQueue.remove(holder); -+ this.getData().autoSaveQueue.remove(holder); // DivineMC - Chunk System optimization - PlatformHooks.get().onChunkHolderDelete(this.world, holder.vanillaChunkHolder); - this.chunkHolders.remove(CoordinateUtils.getChunkKey(holder.chunkX, holder.chunkZ)); - } -@@ -1208,6 +1221,27 @@ public final class ChunkHolderManager { - } - } - -+ // DivineMC start - Chunk System optimization -+ public final java.util.Set blockTickingChunkHolders = java.util.Collections.synchronizedSet(new org.agrona.collections.ObjectHashSet<>(10, 0.88f, true)); -+ public final java.util.Set entityTickingChunkHolders = java.util.Collections.synchronizedSet(new org.agrona.collections.ObjectHashSet<>(10, 0.88f, true)); -+ -+ public void markBlockTicking(@org.jetbrains.annotations.NotNull NewChunkHolder newChunkHolder) { -+ this.blockTickingChunkHolders.add(newChunkHolder.getCachedLongPos()); -+ } -+ -+ public void markNonBlockTickingIfPossible(@org.jetbrains.annotations.NotNull NewChunkHolder newChunkHolder) { -+ this.blockTickingChunkHolders.remove(newChunkHolder.getCachedLongPos()); -+ } -+ -+ public void markEntityTicking(@org.jetbrains.annotations.NotNull NewChunkHolder newChunkHolder) { -+ this.entityTickingChunkHolders.add(newChunkHolder.getCachedLongPos()); -+ } -+ -+ public void markNonEntityTickingIfPossible(@org.jetbrains.annotations.NotNull NewChunkHolder newChunkHolder) { -+ this.entityTickingChunkHolders.remove(newChunkHolder.getCachedLongPos()); -+ } -+ // DivineMC end - Chunk System optimization -+ - public enum TicketOperationType { - ADD, REMOVE, ADD_IF_REMOVED, ADD_AND_REMOVE - } -@@ -1381,7 +1415,7 @@ public final class ChunkHolderManager { - - // only call on tick thread - private boolean processPendingFullUpdate() { -- final ArrayDeque pendingFullLoadUpdate = this.pendingFullLoadUpdate; -+ final java.util.Deque pendingFullLoadUpdate = this.getData().pendingFullLoadUpdate; // DivineMC - Chunk System optimization - - boolean ret = false; - -@@ -1417,8 +1451,7 @@ public final class ChunkHolderManager { - final JsonArray allTicketsJson = new JsonArray(); - ret.add("tickets", allTicketsJson); - -- for (final Iterator>>> iterator = this.tickets.entryIterator(); -- iterator.hasNext();) { -+ for (final Iterator>>> iterator = this.tickets.entryIterator(); iterator.hasNext();) { // DivineMC - Chunk System optimization - 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/NewChunkHolder.java b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/NewChunkHolder.java -index e4a5fa25ed368fc4662c30934da2963ef446d782..6da0ea5cd83a00578223e0a19f952c917bcbcdae 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 { - } - - public final ChunkHolder vanillaChunkHolder; -+ // DivineMC start - Chunk System optimization -+ private final long cachedLongPos; -+ -+ public long getCachedLongPos() { -+ return cachedLongPos; -+ } -+ // DivineMC end - Chunk System optimization - - public NewChunkHolder(final ServerLevel world, final int chunkX, final int chunkZ, final ChunkTaskScheduler scheduler) { - this.world = world; - this.chunkX = chunkX; - this.chunkZ = chunkZ; -+ this.cachedLongPos = ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(this.chunkX, this.chunkZ); // DivineMC - Chunk System optimization - this.scheduler = scheduler; - this.vanillaChunkHolder = new ChunkHolder( - new ChunkPos(chunkX, chunkZ), ChunkHolderManager.MAX_TICKET_LEVEL, world, -@@ -790,9 +798,11 @@ 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 -- private UnloadTask chunkDataUnload; -- private UnloadTask entityDataUnload; -- private UnloadTask poiDataUnload; -+ // DivineMC start - Chunk System optimization -+ private volatile UnloadTask chunkDataUnload; -+ private volatile UnloadTask entityDataUnload; -+ private volatile UnloadTask poiDataUnload; -+ // DivineMC end - Chunk System optimization - - public static final record UnloadTask(CallbackCompletable completable, PrioritisedExecutor.PrioritisedTask task, - LazyRunnable toRun) {} -@@ -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) { - final NewChunkHolder holder = (dx | dz) == 0 ? this : this.scheduler.chunkHolderManager.getChunkHolder(dx + this.chunkX, dz + this.chunkZ); -+ if (holder == null) continue; // DivineMC - Chunk System optimization - if (loaded) { - if (holder.setNeighbourFullLoaded(-dx, -dz)) { - changedFullStatus.add(holder); -@@ -1214,6 +1225,19 @@ public final class NewChunkHolder { - - private void updateCurrentState(final FullChunkStatus to) { - this.currentFullChunkStatus = to; -+ // DivineMC start - Chunk System optimization -+ if (to.isOrAfter(FullChunkStatus.BLOCK_TICKING)) { -+ this.world.moonrise$getChunkTaskScheduler().chunkHolderManager.markBlockTicking(this); -+ } else { -+ this.world.moonrise$getChunkTaskScheduler().chunkHolderManager.markNonBlockTickingIfPossible(this); -+ } -+ -+ if (to.isOrAfter(FullChunkStatus.ENTITY_TICKING)) { -+ this.world.moonrise$getChunkTaskScheduler().chunkHolderManager.markEntityTicking(this); -+ } else { -+ this.world.moonrise$getChunkTaskScheduler().chunkHolderManager.markNonEntityTickingIfPossible(this); -+ } -+ // DivineMC end - Chunk System optimization - } - - // only to be called on the main thread, no locks need to be held -@@ -1348,11 +1372,11 @@ public final class NewChunkHolder { - return this.requestedGenStatus; - } - -- private final Reference2ObjectOpenHashMap>> statusWaiters = new Reference2ObjectOpenHashMap<>(); -+ private final Map>> statusWaiters = new java.util.concurrent.ConcurrentHashMap<>(); // DivineMC - Chunk System optimization - - void addStatusConsumer(final ChunkStatus status, final Consumer consumer) { - this.statusWaiters.computeIfAbsent(status, (final ChunkStatus keyInMap) -> { -- return new ArrayList<>(4); -+ return new java.util.concurrent.CopyOnWriteArrayList<>(); // DivineMC - Chunk System optimization - }).add(consumer); - } - -@@ -1394,11 +1418,11 @@ public final class NewChunkHolder { - }, Priority.HIGHEST); - } - -- private final Reference2ObjectOpenHashMap>> fullStatusWaiters = new Reference2ObjectOpenHashMap<>(); -+ private final Map>> fullStatusWaiters = new java.util.concurrent.ConcurrentHashMap<>(); - - void addFullStatusConsumer(final FullChunkStatus status, final Consumer consumer) { - this.fullStatusWaiters.computeIfAbsent(status, (final FullChunkStatus keyInMap) -> { -- return new ArrayList<>(4); -+ return new java.util.concurrent.CopyOnWriteArrayList<>(); // DivineMC - Chunk System optimization - }).add(consumer); - } - -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 -+++ b/ca/spottedleaf/moonrise/patches/chunk_tick_iteration/ChunkTickConstants.java -@@ -2,6 +2,6 @@ package ca.spottedleaf.moonrise.patches.chunk_tick_iteration; - - public final class ChunkTickConstants { - -- public static final int PLAYER_SPAWN_TRACK_RANGE = 8; -+ public static final int PLAYER_SPAWN_TRACK_RANGE = (int) Math.round(org.bxteam.divinemc.DivineConfig.playerNearChunkDetectionRange / 16.0); // DivineMC - Chunk System optimization - - } -diff --git a/ca/spottedleaf/moonrise/patches/starlight/light/SWMRNibbleArray.java b/ca/spottedleaf/moonrise/patches/starlight/light/SWMRNibbleArray.java -index 4ca68a903e67606fc4ef0bfa9862a73797121c8b..f94f443f862611f039454d1dc8ff2a4ba5f081d3 100644 ---- a/ca/spottedleaf/moonrise/patches/starlight/light/SWMRNibbleArray.java -+++ b/ca/spottedleaf/moonrise/patches/starlight/light/SWMRNibbleArray.java -@@ -325,7 +325,7 @@ public final class SWMRNibbleArray { - } - - // operation type: updating -- public boolean updateVisible() { -+ public synchronized boolean updateVisible() { // DivineMC - Chunk System optimization - if (!this.isDirty()) { - return false; - } -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 -+++ b/net/minecraft/server/level/DistanceManager.java -@@ -178,15 +178,13 @@ public abstract class DistanceManager implements ca.spottedleaf.moonrise.patches - - public boolean inEntityTickingRange(long chunkPos) { - // Paper start - rewrite chunk system -- final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder chunkHolder = this.moonrise$getChunkHolderManager().getChunkHolder(chunkPos); -- return chunkHolder != null && chunkHolder.isEntityTickingReady(); -+ return this.moonrise$getChunkHolderManager().entityTickingChunkHolders.contains(chunkPos); // DivineMC - Chunk System optimization - // Paper end - rewrite chunk system - } - - public boolean inBlockTickingRange(long chunkPos) { - // Paper start - rewrite chunk system -- final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder chunkHolder = this.moonrise$getChunkHolderManager().getChunkHolder(chunkPos); -- return chunkHolder != null && chunkHolder.isTickingReady(); -+ return this.moonrise$getChunkHolderManager().blockTickingChunkHolders.contains(chunkPos); // DivineMC - Chunk System optimization - // Paper end - rewrite chunk system - } - -diff --git a/net/minecraft/server/level/ServerChunkCache.java b/net/minecraft/server/level/ServerChunkCache.java -index ab30af9cd58ff7310e05be87b08f42bacf69e11e..ae0e36d198ad8243920c8e8a55c0be4945542763 100644 ---- a/net/minecraft/server/level/ServerChunkCache.java -+++ b/net/minecraft/server/level/ServerChunkCache.java -@@ -439,8 +439,7 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon - - public boolean isPositionTicking(long chunkPos) { - // Paper start - rewrite chunk system -- final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder newChunkHolder = ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(chunkPos); -- return newChunkHolder != null && newChunkHolder.isTickingReady(); -+ return ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getChunkTaskScheduler().chunkHolderManager.blockTickingChunkHolders.contains(chunkPos); // DivineMC - Chunk System optimization - // Paper end - rewrite chunk system - } - -diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java -index 973198ae0a73d6747e73548bdcbc1de46b6fb107..1581bf8ff425c6e0df28d107300d7266e9f87ed5 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 - public final ServerChunkCache chunkSource; - private final MinecraftServer server; - public final net.minecraft.world.level.storage.PrimaryLevelData serverLevelData; // CraftBukkit - type -+ public final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkHolderManager.LevelHolderData chunkHolderData; // DivineMC - Chunk System optimization - 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 - // 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); -+ this.chunkHolderData = new ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkHolderManager.LevelHolderData(); // DivineMC - Chunk System optimization - 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 - @Override - public boolean shouldTickBlocksAt(long chunkPos) { - // Paper start - rewrite chunk system -- final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder holder = this.moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(chunkPos); -- return holder != null && holder.isTickingReady(); -+ return this.moonrise$getChunkTaskScheduler().chunkHolderManager.blockTickingChunkHolders.contains(chunkPos); // DivineMC - Chunk System optimization - // Paper end - rewrite chunk system - } - -@@ -2525,30 +2526,25 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe - - private boolean isPositionTickingWithEntitiesLoaded(long chunkPos) { - // Paper start - rewrite chunk system -- final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder chunkHolder = this.moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(chunkPos); -- // isTicking implies the chunk is loaded, and the chunk is loaded now implies the entities are loaded -- return chunkHolder != null && chunkHolder.isTickingReady(); -+ return this.moonrise$getChunkTaskScheduler().chunkHolderManager.blockTickingChunkHolders.contains(chunkPos); // DivineMC - Chunk System optimization - // Paper end - rewrite chunk system - } - - public boolean isPositionEntityTicking(BlockPos pos) { - // Paper start - rewrite chunk system -- final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder chunkHolder = this.moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(pos)); -- return chunkHolder != null && chunkHolder.isEntityTickingReady(); -+ return this.moonrise$getChunkTaskScheduler().chunkHolderManager.entityTickingChunkHolders.contains(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(pos)); // DivineMC - Chunk System optimization - // Paper end - rewrite chunk system - } - - public boolean isNaturalSpawningAllowed(BlockPos pos) { - // Paper start - rewrite chunk system -- final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder chunkHolder = this.moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(pos)); -- return chunkHolder != null && chunkHolder.isEntityTickingReady(); -+ return this.moonrise$getChunkTaskScheduler().chunkHolderManager.entityTickingChunkHolders.contains(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(pos)); // DivineMC - Chunk System optimization - // Paper end - rewrite chunk system - } - - public boolean isNaturalSpawningAllowed(ChunkPos chunkPos) { - // Paper start - rewrite chunk system -- final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder chunkHolder = this.moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(chunkPos)); -- return chunkHolder != null && chunkHolder.isEntityTickingReady(); -+ return this.moonrise$getChunkTaskScheduler().chunkHolderManager.entityTickingChunkHolders.contains(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(chunkPos)); // DivineMC - Chunk System optimization - // Paper end - rewrite chunk system - } - -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 -+++ b/net/minecraft/world/level/LevelReader.java -@@ -70,10 +70,27 @@ public interface LevelReader extends ca.spottedleaf.moonrise.patches.chunk_syste - - @Override - default Holder getNoiseBiome(int x, int y, int z) { -- ChunkAccess chunk = this.getChunk(QuartPos.toSection(x), QuartPos.toSection(z), ChunkStatus.BIOMES, false); -+ ChunkAccess chunk = this.fasterChunkAccess(this, QuartPos.toSection(x), QuartPos.toSection(z), ChunkStatus.BIOMES, false); // DivineMC - Chunk System optimization - return chunk != null ? chunk.getNoiseBiome(x, y, z) : this.getUncachedNoiseBiome(x, y, z); - } - -+ // DivineMC start - Chunk System optimization -+ private @Nullable ChunkAccess fasterChunkAccess(LevelReader instance, int x, int z, ChunkStatus chunkStatus, boolean create) { -+ if (!create && instance instanceof net.minecraft.server.level.ServerLevel world) { -+ final net.minecraft.server.level.ChunkHolder holder = (world.getChunkSource().chunkMap).getVisibleChunkIfPresent(ChunkPos.asLong(x, z)); -+ if (holder != null) { -+ final java.util.concurrent.CompletableFuture> future = holder.getFullChunkFuture(); -+ final net.minecraft.server.level.ChunkResult either = future.getNow(null); -+ if (either != null) { -+ final net.minecraft.world.level.chunk.LevelChunk chunk = either.orElse(null); -+ if (chunk != null) return chunk; -+ } -+ } -+ } -+ return instance.getChunk(x, z, chunkStatus, create); -+ } -+ // DivineMC end - Chunk System optimization -+ - Holder getUncachedNoiseBiome(int x, int y, int z); - - boolean isClientSide(); -diff --git a/net/minecraft/world/level/chunk/ProtoChunk.java b/net/minecraft/world/level/chunk/ProtoChunk.java -index e66239e2da91bd3ddf358d239be796719c0da327..35e9d8cfe12252d3419626f1cefb64d30e20069e 100644 ---- a/net/minecraft/world/level/chunk/ProtoChunk.java -+++ b/net/minecraft/world/level/chunk/ProtoChunk.java -@@ -41,7 +41,7 @@ public class ProtoChunk extends ChunkAccess { - @Nullable - private volatile LevelLightEngine lightEngine; - private volatile ChunkStatus status = ChunkStatus.EMPTY; -- private final List entities = Lists.newArrayList(); -+ private final List entities = Collections.synchronizedList(Lists.newArrayList()); // DivineMC - Chunk System optimization - @Nullable - private CarvingMask carvingMask; - @Nullable -diff --git a/net/minecraft/world/level/chunk/storage/IOWorker.java b/net/minecraft/world/level/chunk/storage/IOWorker.java -index 2199a9e2a0141c646d108f2687a27f1d165453c5..c28c2583b257f92207b822a1fdde8f5b7e480992 100644 ---- a/net/minecraft/world/level/chunk/storage/IOWorker.java -+++ b/net/minecraft/world/level/chunk/storage/IOWorker.java -@@ -212,7 +212,38 @@ public class IOWorker implements ChunkScanAccess, AutoCloseable { - }); - } - -+ // DivineMC start - Chunk System optimization -+ private void checkHardLimit() { -+ if (this.pendingWrites.size() >= org.bxteam.divinemc.DivineConfig.chunkDataCacheLimit) { -+ LOGGER.warn("Chunk data cache size exceeded hard limit ({} >= {}), forcing writes to disk (you can increase chunkDataCacheLimit in c2me.toml)", this.pendingWrites.size(), org.bxteam.divinemc.DivineConfig.chunkDataCacheLimit); -+ while (this.pendingWrites.size() >= org.bxteam.divinemc.DivineConfig.chunkDataCacheSoftLimit * 0.75) { -+ writeResult0(); -+ } -+ } -+ } -+ -+ private void writeResult0() { -+ java.util.Iterator> iterator = this.pendingWrites.entrySet().iterator(); -+ if (iterator.hasNext()) { -+ java.util.Map.Entry entry = iterator.next(); -+ iterator.remove(); -+ this.runStore(entry.getKey(), entry.getValue()); -+ } -+ } -+ // DivineMC end - Chunk System optimization -+ - private void storePendingChunk() { -+ // DivineMC start - Chunk System optimization -+ if (!this.pendingWrites.isEmpty()) { -+ checkHardLimit(); -+ if (this.pendingWrites.size() >= org.bxteam.divinemc.DivineConfig.chunkDataCacheSoftLimit) { -+ int writeFrequency = Math.min(1, (this.pendingWrites.size() - (int) org.bxteam.divinemc.DivineConfig.chunkDataCacheSoftLimit) / 16); -+ for (int i = 0; i < writeFrequency; i++) { -+ writeResult0(); -+ } -+ } -+ } -+ // DivineMC end - Chunk System optimization - Entry entry = this.pendingWrites.pollFirstEntry(); - if (entry != null) { - this.runStore(entry.getKey(), entry.getValue()); -diff --git a/net/minecraft/world/level/chunk/storage/RegionFileStorage.java b/net/minecraft/world/level/chunk/storage/RegionFileStorage.java -index 6ebd1300c2561116b83cb2472ac7939ead36d576..16cd10ab8de69ca3d29c84cf93715645322fd72a 100644 ---- a/net/minecraft/world/level/chunk/storage/RegionFileStorage.java -+++ b/net/minecraft/world/level/chunk/storage/RegionFileStorage.java -@@ -244,7 +244,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise - - protected RegionFileStorage(RegionStorageInfo info, Path folder, boolean sync) { // Paper - protected - this.folder = folder; -- this.sync = sync; -+ this.sync = Boolean.parseBoolean(System.getProperty("com.ishland.c2me.chunkio.syncDiskWrites", String.valueOf(sync))); // DivineMC - C2ME: sync disk writes - this.info = info; - this.isChunkData = isChunkDataFolder(this.folder); // Paper - recalculate region file headers - } -diff --git a/net/minecraft/world/level/levelgen/Column.java b/net/minecraft/world/level/levelgen/Column.java -index 4a1df0f8578c9ee5538ed8c94d3c7911f36f83b8..716c2c69843234cdef34339d859babc95ffe318c 100644 ---- a/net/minecraft/world/level/levelgen/Column.java -+++ b/net/minecraft/world/level/levelgen/Column.java -@@ -156,7 +156,7 @@ public abstract class Column { - } - - public int height() { -- return this.ceiling - this.floor - 1; -+ return net.minecraft.util.Mth.abs(this.ceiling - this.floor - 1); // DivineMC - Chunk System optimization - } - - @Override -diff --git a/net/minecraft/world/level/levelgen/WorldgenRandom.java b/net/minecraft/world/level/levelgen/WorldgenRandom.java -index c2d7cd788071e25b8ba2503c30ae80c7a9f353ed..0a2e13c4a3db6517267e1f9e74b6152c73e8351f 100644 ---- a/net/minecraft/world/level/levelgen/WorldgenRandom.java -+++ b/net/minecraft/world/level/levelgen/WorldgenRandom.java -@@ -73,7 +73,7 @@ public class WorldgenRandom extends LegacyRandomSource { - } - - public static enum Algorithm { -- LEGACY(LegacyRandomSource::new), -+ LEGACY(ThreadSafeLegacyRandomSource::new), // DivineMC - Chunk System optimization - XOROSHIRO(XoroshiroRandomSource::new); - - private final LongFunction constructor; -diff --git a/net/minecraft/world/level/levelgen/feature/stateproviders/RandomizedIntStateProvider.java b/net/minecraft/world/level/levelgen/feature/stateproviders/RandomizedIntStateProvider.java -index f4009671880b00ecec98fe604215e2824e453cdf..c607c801223ea71343ece67b81151e31362f1c31 100644 ---- a/net/minecraft/world/level/levelgen/feature/stateproviders/RandomizedIntStateProvider.java -+++ b/net/minecraft/world/level/levelgen/feature/stateproviders/RandomizedIntStateProvider.java -@@ -55,17 +55,21 @@ public class RandomizedIntStateProvider extends BlockStateProvider { - - @Override - public BlockState getState(RandomSource random, BlockPos pos) { -- BlockState state = this.source.getState(random, pos); -- if (this.property == null || !state.hasProperty(this.property)) { -- IntegerProperty integerProperty = findProperty(state, this.propertyName); -- if (integerProperty == null) { -- return state; -+ // DivineMC start - Chunk System optimization -+ BlockState blockState = this.source.getState(random, pos); -+ IntegerProperty propertyLocal = this.property; -+ if (propertyLocal == null || !blockState.hasProperty(propertyLocal)) { -+ IntegerProperty intProperty = findProperty(blockState, this.propertyName); -+ if (intProperty == null) { -+ return blockState; - } - -- this.property = integerProperty; -+ propertyLocal = intProperty; -+ this.property = intProperty; - } - -- return state.setValue(this.property, Integer.valueOf(this.values.sample(random))); -+ return (BlockState)blockState.setValue(propertyLocal, this.values.sample(random)); -+ // DivineMC end - Chunk System optimization - } - - @Nullable -diff --git a/net/minecraft/world/level/levelgen/structure/ScatteredFeaturePiece.java b/net/minecraft/world/level/levelgen/structure/ScatteredFeaturePiece.java -index d122221deefb218db962e97ba2d958c33d903b8a..56311b439ac22700593d2d31da3a4efe3a519d53 100644 ---- a/net/minecraft/world/level/levelgen/structure/ScatteredFeaturePiece.java -+++ b/net/minecraft/world/level/levelgen/structure/ScatteredFeaturePiece.java -@@ -12,7 +12,7 @@ public abstract class ScatteredFeaturePiece extends StructurePiece { - protected final int width; - protected final int height; - protected final int depth; -- protected int heightPosition = -1; -+ protected volatile int heightPosition = -1; // DivineMC - Chunk System optimization - make volatile - - protected ScatteredFeaturePiece(StructurePieceType type, int x, int y, int z, int width, int height, int depth, Direction orientation) { - super(type, 0, StructurePiece.makeBoundingBox(x, y, z, orientation, width, height, depth)); -diff --git a/net/minecraft/world/level/levelgen/structure/StructureStart.java b/net/minecraft/world/level/levelgen/structure/StructureStart.java -index 4dafa79dd4ec55a443ba3731a79e7cd6e8052f48..8aeab4d773473ad20b1c64295c93d6fcb4ea02a1 100644 ---- a/net/minecraft/world/level/levelgen/structure/StructureStart.java -+++ b/net/minecraft/world/level/levelgen/structure/StructureStart.java -@@ -26,7 +26,7 @@ public final class StructureStart { - private final Structure structure; - private final PiecesContainer pieceContainer; - private final ChunkPos chunkPos; -- private int references; -+ private final java.util.concurrent.atomic.AtomicInteger references = new java.util.concurrent.atomic.AtomicInteger(); // DivineMC - Chunk System optimization - @Nullable - private volatile BoundingBox cachedBoundingBox; - -@@ -39,7 +39,7 @@ public final class StructureStart { - public StructureStart(Structure structure, ChunkPos chunkPos, int references, PiecesContainer pieceContainer) { - this.structure = structure; - this.chunkPos = chunkPos; -- this.references = references; -+ this.references.set(references); // DivineMC - Chunk System optimization - this.pieceContainer = pieceContainer; - } - -@@ -126,7 +126,7 @@ public final class StructureStart { - compoundTag.putString("id", context.registryAccess().lookupOrThrow(Registries.STRUCTURE).getKey(this.structure).toString()); - compoundTag.putInt("ChunkX", chunkPos.x); - compoundTag.putInt("ChunkZ", chunkPos.z); -- compoundTag.putInt("references", this.references); -+ compoundTag.putInt("references", this.references.get()); // DivineMC - Chunk System optimization - compoundTag.put("Children", this.pieceContainer.save(context)); - return compoundTag; - } else { -@@ -144,15 +144,15 @@ public final class StructureStart { - } - - public boolean canBeReferenced() { -- return this.references < this.getMaxReferences(); -+ return this.references.get() < this.getMaxReferences(); // DivineMC - Chunk System optimization - } - - public void addReference() { -- this.references++; -+ this.references.getAndIncrement(); // DivineMC - Chunk System optimization - } - - public int getReferences() { -- return this.references; -+ return this.references.get(); // DivineMC - Chunk System optimization - } - - protected int getMaxReferences() { diff --git a/divinemc-server/minecraft-patches/features/0017-Clump-experience-orbs.patch b/divinemc-server/minecraft-patches/features/0013-Clump-experience-orbs.patch similarity index 100% rename from divinemc-server/minecraft-patches/features/0017-Clump-experience-orbs.patch rename to divinemc-server/minecraft-patches/features/0013-Clump-experience-orbs.patch diff --git a/divinemc-server/minecraft-patches/features/0018-Optimize-explosions.patch b/divinemc-server/minecraft-patches/features/0014-Optimize-explosions.patch similarity index 100% rename from divinemc-server/minecraft-patches/features/0018-Optimize-explosions.patch rename to divinemc-server/minecraft-patches/features/0014-Optimize-explosions.patch diff --git a/divinemc-server/minecraft-patches/features/0014-Optimize-hoppers.patch b/divinemc-server/minecraft-patches/features/0014-Optimize-hoppers.patch deleted file mode 100644 index e13bcae..0000000 --- a/divinemc-server/minecraft-patches/features/0014-Optimize-hoppers.patch +++ /dev/null @@ -1,682 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> -Date: Sat, 1 Feb 2025 00:55:34 +0300 -Subject: [PATCH] Optimize hoppers - - -diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java -index 9f7698f8ce56d5d89cf86f6ea2d5b4d51b18c9a2..351b035d1f3025af28b5147b95b912e0e2ab9212 100644 ---- a/net/minecraft/server/MinecraftServer.java -+++ b/net/minecraft/server/MinecraftServer.java -@@ -1746,7 +1746,6 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0; // Paper - BlockPhysicsEvent - serverLevel.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - Add EntityMoveEvent - serverLevel.updateLagCompensationTick(); // Paper - lag compensation -- net.minecraft.world.level.block.entity.HopperBlockEntity.skipHopperEvents = serverLevel.paperConfig().hopper.disableMoveEvent || org.bukkit.event.inventory.InventoryMoveItemEvent.getHandlerList().getRegisteredListeners().length == 0; // Paper - Perf: Optimize Hoppers - serverLevel.hasRidableMoveEvent = org.purpurmc.purpur.event.entity.RidableMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Purpur - Ridables - 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 1581bf8ff425c6e0df28d107300d7266e9f87ed5..8c136d77451c17613834c4a7dab6ab6c2fe4d716 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 - private final LevelTicks fluidTicks = new LevelTicks<>(this::isPositionTickingWithEntitiesLoaded); - private final PathTypeCache pathTypesByPosCache = new PathTypeCache(); - final Set navigatingMobs = new ObjectOpenHashSet<>(); -- volatile boolean isUpdatingNavigations; -+ final java.util.concurrent.atomic.AtomicBoolean isUpdatingNavigations = new java.util.concurrent.atomic.AtomicBoolean(false); // DivineMC - Optimize Hoppers - protected final Raids raids; - private final ObjectLinkedOpenHashSet blockEvents = new ObjectLinkedOpenHashSet<>(); - private final List blockEventsToReschedule = new ArrayList<>(64); -@@ -1729,7 +1729,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe - - @Override - public void sendBlockUpdated(BlockPos pos, BlockState oldState, BlockState newState, int flags) { -- if (this.isUpdatingNavigations) { -+ if (this.isUpdatingNavigations.get() && false) { // DivineMC - String string = "recursive call to sendBlockUpdated"; - Util.logAndPauseIfInIde("recursive call to sendBlockUpdated", new IllegalStateException("recursive call to sendBlockUpdated")); - } -@@ -1760,13 +1760,13 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe - // Paper end - catch CME see below why - - try { -- this.isUpdatingNavigations = true; -+ this.isUpdatingNavigations.set(true); // DivineMC - - for (PathNavigation pathNavigation : list) { - pathNavigation.recomputePath(); - } - } finally { -- this.isUpdatingNavigations = false; -+ this.isUpdatingNavigations.set(false); // DivineMC - } - } - } // Paper - option to disable pathfinding updates -@@ -2653,7 +2653,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe - } - - if (entity instanceof Mob mob) { -- if (false && ServerLevel.this.isUpdatingNavigations) { // Paper - Remove unnecessary onTrackingStart during navigation warning -+ if (false && ServerLevel.this.isUpdatingNavigations.get()) { // Paper - Remove unnecessary onTrackingStart during navigation warning // DivineMC - String string = "onTrackingStart called during navigation iteration"; - Util.logAndPauseIfInIde( - "onTrackingStart called during navigation iteration", new IllegalStateException("onTrackingStart called during navigation iteration") -@@ -2723,7 +2723,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe - } - - if (entity instanceof Mob mob) { -- if (false && ServerLevel.this.isUpdatingNavigations) { // Paper - Remove unnecessary onTrackingStart during navigation warning -+ if (false && ServerLevel.this.isUpdatingNavigations.get()) { // Paper - Remove unnecessary onTrackingStart during navigation warning // DivineMC - String string = "onTrackingStart called during navigation iteration"; - Util.logAndPauseIfInIde( - "onTrackingStart called during navigation iteration", new IllegalStateException("onTrackingStart called during navigation iteration") -diff --git a/net/minecraft/world/level/block/entity/HopperBlockEntity.java b/net/minecraft/world/level/block/entity/HopperBlockEntity.java -index 5cd1326ad5d046c88b2b3449d610a78fa880b4cd..a0ee6ad6e7a6791605191d20d742e16cc9857a60 100644 ---- a/net/minecraft/world/level/block/entity/HopperBlockEntity.java -+++ b/net/minecraft/world/level/block/entity/HopperBlockEntity.java -@@ -139,56 +139,18 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen - } - } - -- // Paper start - Perf: Optimize Hoppers -- private static final int HOPPER_EMPTY = 0; -- private static final int HOPPER_HAS_ITEMS = 1; -- private static final int HOPPER_IS_FULL = 2; -- -- private static int getFullState(final HopperBlockEntity hopper) { -- hopper.unpackLootTable(null); -- -- final List hopperItems = hopper.items; -- -- boolean empty = true; -- boolean full = true; -- -- for (int i = 0, len = hopperItems.size(); i < len; ++i) { -- final ItemStack stack = hopperItems.get(i); -- if (stack.isEmpty()) { -- full = false; -- continue; -- } -- -- if (!full) { -- // can't be full -- return HOPPER_HAS_ITEMS; -- } -- -- empty = false; -- -- if (stack.getCount() != stack.getMaxStackSize()) { -- // can't be full or empty -- return HOPPER_HAS_ITEMS; -- } -- } -- -- return empty ? HOPPER_EMPTY : (full ? HOPPER_IS_FULL : HOPPER_HAS_ITEMS); -- } -- // Paper end - Perf: Optimize Hoppers -- - private static boolean tryMoveItems(Level level, BlockPos pos, BlockState state, HopperBlockEntity blockEntity, BooleanSupplier validator) { - if (level.isClientSide) { - return false; - } else { - if (!blockEntity.isOnCooldown() && state.getValue(HopperBlock.ENABLED)) { - boolean flag = false; -- final int fullState = getFullState(blockEntity); // Paper - Perf: Optimize Hoppers -- if (fullState != HOPPER_EMPTY) { // Paper - Perf: Optimize Hoppers -+ if (!blockEntity.isEmpty()) { // DivineMC - Optimize hoppers - flag = ejectItems(level, pos, blockEntity); - } - -- if (fullState != HOPPER_IS_FULL || flag) { // Paper - Perf: Optimize Hoppers -- flag |= validator.getAsBoolean(); // Paper - note: this is not a validator, it's what adds/sucks in items -+ if (!blockEntity.inventoryFull()) { // DivineMC - Optimize hoppers -+ flag |= validator.getAsBoolean(); // DivineMC - Optimize hoppers - } - - if (flag) { -@@ -212,206 +174,6 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen - return true; - } - -- // Paper start - Perf: Optimize Hoppers -- public static boolean skipHopperEvents; -- private static boolean skipPullModeEventFire; -- private static boolean skipPushModeEventFire; -- -- private static boolean hopperPush(final Level level, final Container destination, final Direction direction, final HopperBlockEntity hopper) { -- skipPushModeEventFire = skipHopperEvents; -- boolean foundItem = false; -- for (int i = 0; i < hopper.getContainerSize(); ++i) { -- final ItemStack item = hopper.getItem(i); -- if (!item.isEmpty()) { -- foundItem = true; -- ItemStack origItemStack = item; -- ItemStack movedItem = origItemStack; -- -- final int originalItemCount = origItemStack.getCount(); -- final int movedItemCount = Math.min(level.spigotConfig.hopperAmount, originalItemCount); -- origItemStack.setCount(movedItemCount); -- -- // We only need to fire the event once to give protection plugins a chance to cancel this event -- // Because nothing uses getItem, every event call should end up the same result. -- if (!skipPushModeEventFire) { -- movedItem = callPushMoveEvent(destination, movedItem, hopper); -- if (movedItem == null) { // cancelled -- origItemStack.setCount(originalItemCount); -- return false; -- } -- } -- -- final ItemStack remainingItem = addItem(hopper, destination, movedItem, direction); -- final int remainingItemCount = remainingItem.getCount(); -- if (remainingItemCount != movedItemCount) { -- origItemStack = origItemStack.copy(true); -- origItemStack.setCount(originalItemCount); -- if (!origItemStack.isEmpty()) { -- origItemStack.setCount(originalItemCount - movedItemCount + remainingItemCount); -- } -- hopper.setItem(i, origItemStack); -- destination.setChanged(); -- return true; -- } -- origItemStack.setCount(originalItemCount); -- } -- } -- if (foundItem && level.paperConfig().hopper.cooldownWhenFull) { // Inventory was full - cooldown -- hopper.setCooldown(level.spigotConfig.hopperTransfer); -- } -- return false; -- } -- -- private static boolean hopperPull(final Level level, final Hopper hopper, final Container container, ItemStack origItemStack, final int i) { -- ItemStack movedItem = origItemStack; -- final int originalItemCount = origItemStack.getCount(); -- final int movedItemCount = Math.min(level.spigotConfig.hopperAmount, originalItemCount); -- container.setChanged(); // original logic always marks source inv as changed even if no move happens. -- movedItem.setCount(movedItemCount); -- -- if (!skipPullModeEventFire) { -- movedItem = callPullMoveEvent(hopper, container, movedItem); -- if (movedItem == null) { // cancelled -- origItemStack.setCount(originalItemCount); -- // Drastically improve performance by returning true. -- // No plugin could have relied on the behavior of false as the other call -- // site for IMIE did not exhibit the same behavior -- return true; -- } -- } -- -- final ItemStack remainingItem = addItem(container, hopper, movedItem, null); -- final int remainingItemCount = remainingItem.getCount(); -- if (remainingItemCount != movedItemCount) { -- origItemStack = origItemStack.copy(true); -- origItemStack.setCount(originalItemCount); -- if (!origItemStack.isEmpty()) { -- origItemStack.setCount(originalItemCount - movedItemCount + remainingItemCount); -- } -- -- ignoreBlockEntityUpdates = true; -- container.setItem(i, origItemStack); -- ignoreBlockEntityUpdates = false; -- container.setChanged(); -- return true; -- } -- origItemStack.setCount(originalItemCount); -- -- if (level.paperConfig().hopper.cooldownWhenFull) { -- applyCooldown(hopper); -- } -- -- return false; -- } -- -- @Nullable -- private static ItemStack callPushMoveEvent(Container destination, ItemStack itemStack, HopperBlockEntity hopper) { -- final org.bukkit.inventory.Inventory destinationInventory = getInventory(destination); -- final io.papermc.paper.event.inventory.PaperInventoryMoveItemEvent event = new io.papermc.paper.event.inventory.PaperInventoryMoveItemEvent( -- hopper.getOwner(false).getInventory(), -- org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemStack), -- destinationInventory, -- true -- ); -- final boolean result = event.callEvent(); -- if (!event.calledGetItem && !event.calledSetItem) { -- skipPushModeEventFire = true; -- } -- if (!result) { -- applyCooldown(hopper); -- return null; -- } -- -- if (event.calledSetItem) { -- return org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItem()); -- } else { -- return itemStack; -- } -- } -- -- @Nullable -- private static ItemStack callPullMoveEvent(final Hopper hopper, final Container container, final ItemStack itemstack) { -- final org.bukkit.inventory.Inventory sourceInventory = getInventory(container); -- final org.bukkit.inventory.Inventory destination = getInventory(hopper); -- -- // Mirror is safe as no plugins ever use this item -- final io.papermc.paper.event.inventory.PaperInventoryMoveItemEvent event = new io.papermc.paper.event.inventory.PaperInventoryMoveItemEvent(sourceInventory, org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack), destination, false); -- final boolean result = event.callEvent(); -- if (!event.calledGetItem && !event.calledSetItem) { -- skipPullModeEventFire = true; -- } -- if (!result) { -- applyCooldown(hopper); -- return null; -- } -- -- if (event.calledSetItem) { -- return org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItem()); -- } else { -- return itemstack; -- } -- } -- -- private static org.bukkit.inventory.Inventory getInventory(final Container container) { -- final org.bukkit.inventory.Inventory sourceInventory; -- if (container instanceof net.minecraft.world.CompoundContainer compoundContainer) { -- // Have to special-case large chests as they work oddly -- sourceInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest(compoundContainer); -- } else if (container instanceof BlockEntity blockEntity) { -- sourceInventory = blockEntity.getOwner(false).getInventory(); -- } else if (container.getOwner() != null) { -- sourceInventory = container.getOwner().getInventory(); -- } else { -- sourceInventory = new org.bukkit.craftbukkit.inventory.CraftInventory(container); -- } -- return sourceInventory; -- } -- -- private static void applyCooldown(final Hopper hopper) { -- if (hopper instanceof HopperBlockEntity blockEntity && blockEntity.getLevel() != null) { -- blockEntity.setCooldown(blockEntity.getLevel().spigotConfig.hopperTransfer); -- } -- } -- -- private static boolean allMatch(Container container, Direction direction, java.util.function.BiPredicate test) { -- if (container instanceof WorldlyContainer) { -- for (int slot : ((WorldlyContainer) container).getSlotsForFace(direction)) { -- if (!test.test(container.getItem(slot), slot)) { -- return false; -- } -- } -- } else { -- int size = container.getContainerSize(); -- for (int slot = 0; slot < size; slot++) { -- if (!test.test(container.getItem(slot), slot)) { -- return false; -- } -- } -- } -- return true; -- } -- -- private static boolean anyMatch(Container container, Direction direction, java.util.function.BiPredicate test) { -- if (container instanceof WorldlyContainer) { -- for (int slot : ((WorldlyContainer) container).getSlotsForFace(direction)) { -- if (test.test(container.getItem(slot), slot)) { -- return true; -- } -- } -- } else { -- int size = container.getContainerSize(); -- for (int slot = 0; slot < size; slot++) { -- if (test.test(container.getItem(slot), slot)) { -- return true; -- } -- } -- } -- return true; -- } -- private static final java.util.function.BiPredicate STACK_SIZE_TEST = (itemStack, i) -> itemStack.getCount() >= itemStack.getMaxStackSize(); -- private static final java.util.function.BiPredicate IS_EMPTY_TEST = (itemStack, i) -> itemStack.isEmpty(); -- // Paper end - Perf: Optimize Hoppers -- - private static boolean ejectItems(Level level, BlockPos pos, HopperBlockEntity blockEntity) { - Container attachedContainer = getAttachedContainer(level, pos, blockEntity); - if (attachedContainer == null) { -@@ -421,60 +183,59 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen - if (isFullContainer(attachedContainer, opposite)) { - return false; - } else { -- // Paper start - Perf: Optimize Hoppers -- return hopperPush(level, attachedContainer, opposite, blockEntity); -- //for (int i = 0; i < blockEntity.getContainerSize(); i++) { -- // ItemStack item = blockEntity.getItem(i); -- // if (!item.isEmpty()) { -- // int count = item.getCount(); -- // // CraftBukkit start - Call event when pushing items into other inventories -- // ItemStack original = item.copy(); -- // org.bukkit.craftbukkit.inventory.CraftItemStack oitemstack = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror( -- // blockEntity.removeItem(i, level.spigotConfig.hopperAmount) -- // ); // Spigot -- -- // org.bukkit.inventory.Inventory destinationInventory; -- // // Have to special case large chests as they work oddly -- // if (attachedContainer instanceof final net.minecraft.world.CompoundContainer compoundContainer) { -- // destinationInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest(compoundContainer); -- // } else if (attachedContainer.getOwner() != null) { -- // destinationInventory = attachedContainer.getOwner().getInventory(); -- // } else { -- // destinationInventory = new org.bukkit.craftbukkit.inventory.CraftInventory(attachedContainer); -- // } -- -- // org.bukkit.event.inventory.InventoryMoveItemEvent event = new org.bukkit.event.inventory.InventoryMoveItemEvent( -- // blockEntity.getOwner().getInventory(), -- // oitemstack, -- // destinationInventory, -- // true -- // ); -- // if (!event.callEvent()) { -- // blockEntity.setItem(i, original); -- // blockEntity.setCooldown(level.spigotConfig.hopperTransfer); // Delay hopper checks // Spigot -- // return false; -- // } -- // int origCount = event.getItem().getAmount(); // Spigot -- // ItemStack itemStack = HopperBlockEntity.addItem(blockEntity, attachedContainer, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItem()), opposite); -- // // CraftBukkit end -- -- // if (itemStack.isEmpty()) { -- // attachedContainer.setChanged(); -- // return true; -- // } -- -- // item.setCount(count); -- // // Spigot start -- // item.shrink(origCount - itemStack.getCount()); -- // if (count <= level.spigotConfig.hopperAmount) { -- // // Spigot end -- // blockEntity.setItem(i, item); -- // } -- // } -- //} -- -- //return false; -- // Paper end - Perf: Optimize Hoppers -+ // DivineMC start - Optimize hoppers -+ for (int i = 0; i < blockEntity.getContainerSize(); i++) { -+ ItemStack item = blockEntity.getItem(i); -+ if (!item.isEmpty()) { -+ int count = item.getCount(); -+ // CraftBukkit start - Call event when pushing items into other inventories -+ ItemStack original = item.copy(); -+ org.bukkit.craftbukkit.inventory.CraftItemStack oitemstack = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror( -+ blockEntity.removeItem(i, level.spigotConfig.hopperAmount) -+ ); // Spigot -+ -+ org.bukkit.inventory.Inventory destinationInventory; -+ // Have to special case large chests as they work oddly -+ if (attachedContainer instanceof final net.minecraft.world.CompoundContainer compoundContainer) { -+ destinationInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest(compoundContainer); -+ } else if (attachedContainer.getOwner() != null) { -+ destinationInventory = attachedContainer.getOwner().getInventory(); -+ } else { -+ destinationInventory = new org.bukkit.craftbukkit.inventory.CraftInventory(attachedContainer); -+ } -+ -+ org.bukkit.event.inventory.InventoryMoveItemEvent event = new org.bukkit.event.inventory.InventoryMoveItemEvent( -+ blockEntity.getOwner().getInventory(), -+ oitemstack, -+ destinationInventory, -+ true -+ ); -+ if (!event.callEvent()) { -+ blockEntity.setItem(i, original); -+ blockEntity.setCooldown(level.spigotConfig.hopperTransfer); // Delay hopper checks // Spigot -+ return false; -+ } -+ int origCount = event.getItem().getAmount(); // Spigot -+ ItemStack itemStack = HopperBlockEntity.addItem(blockEntity, attachedContainer, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItem()), opposite); -+ // CraftBukkit end -+ -+ if (itemStack.isEmpty()) { -+ attachedContainer.setChanged(); -+ return true; -+ } -+ -+ item.setCount(count); -+ // Spigot start -+ item.shrink(origCount - itemStack.getCount()); -+ if (count <= level.spigotConfig.hopperAmount) { -+ // Spigot end -+ blockEntity.setItem(i, item); -+ } -+ } -+ } -+ -+ return false; -+ // DivineMC end - Optimize hoppers - } - } - } -@@ -529,7 +290,6 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen - Container sourceContainer = getSourceContainer(level, hopper, blockPos, blockState); - if (sourceContainer != null) { - Direction direction = Direction.DOWN; -- skipPullModeEventFire = skipHopperEvents; // Paper - Perf: Optimize Hoppers - - for (int i : getSlots(sourceContainer, direction)) { - if (tryTakeInItemFromSlot(hopper, sourceContainer, i, direction, level)) { // Spigot -@@ -555,59 +315,58 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen - private static boolean tryTakeInItemFromSlot(Hopper hopper, Container container, int slot, Direction direction, Level level) { // Spigot - ItemStack item = container.getItem(slot); - if (!item.isEmpty() && canTakeItemFromContainer(hopper, container, item, slot, direction)) { -- // Paper start - Perf: Optimize Hoppers -- return hopperPull(level, hopper, container, item, slot); -- //int count = item.getCount(); -- //// CraftBukkit start - Call event on collection of items from inventories into the hopper -- //ItemStack original = item.copy(); -- //org.bukkit.craftbukkit.inventory.CraftItemStack oitemstack = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror( -- // container.removeItem(slot, level.spigotConfig.hopperAmount) // Spigot -- //); -- -- //org.bukkit.inventory.Inventory sourceInventory; -- //// Have to special case large chests as they work oddly -- //if (container instanceof final net.minecraft.world.CompoundContainer compoundContainer) { -- // sourceInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest(compoundContainer); -- //} else if (container.getOwner() != null) { -- // sourceInventory = container.getOwner().getInventory(); -- //} else { -- // sourceInventory = new org.bukkit.craftbukkit.inventory.CraftInventory(container); -- //} -- -- //org.bukkit.event.inventory.InventoryMoveItemEvent event = new org.bukkit.event.inventory.InventoryMoveItemEvent( -- // sourceInventory, -- // oitemstack, -- // hopper.getOwner().getInventory(), -- // false -- //); -- -- //if (!event.callEvent()) { -- // container.setItem(slot, original); -- -- // if (hopper instanceof final HopperBlockEntity hopperBlockEntity) { -- // hopperBlockEntity.setCooldown(level.spigotConfig.hopperTransfer); // Spigot -- // } -- -- // return false; -- //} -- //int origCount = event.getItem().getAmount(); // Spigot -- //ItemStack itemStack = HopperBlockEntity.addItem(container, hopper, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItem()), null); -- //// CraftBukkit end -- -- //if (itemStack.isEmpty()) { -- // container.setChanged(); -- // return true; -- //} -- -- //item.setCount(count); -- //// Spigot start -- //item.shrink(origCount - itemStack.getCount()); -- //if (count <= level.spigotConfig.hopperAmount) { -- // // Spigot end -- // container.setItem(slot, item); -- //} -- // Paper end - Perf: Optimize Hoppers -+ // DivineMC start - Optimize hoppers -+ int count = item.getCount(); -+ // CraftBukkit start - Call event on collection of items from inventories into the hopper -+ ItemStack original = item.copy(); -+ org.bukkit.craftbukkit.inventory.CraftItemStack oitemstack = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror( -+ container.removeItem(slot, level.spigotConfig.hopperAmount) // Spigot -+ ); -+ -+ org.bukkit.inventory.Inventory sourceInventory; -+ // Have to special case large chests as they work oddly -+ if (container instanceof final net.minecraft.world.CompoundContainer compoundContainer) { -+ sourceInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest(compoundContainer); -+ } else if (container.getOwner() != null) { -+ sourceInventory = container.getOwner().getInventory(); -+ } else { -+ sourceInventory = new org.bukkit.craftbukkit.inventory.CraftInventory(container); -+ } -+ -+ org.bukkit.event.inventory.InventoryMoveItemEvent event = new org.bukkit.event.inventory.InventoryMoveItemEvent( -+ sourceInventory, -+ oitemstack, -+ hopper.getOwner().getInventory(), -+ false -+ ); -+ -+ if (!event.callEvent()) { -+ container.setItem(slot, original); -+ -+ if (hopper instanceof final HopperBlockEntity hopperBlockEntity) { -+ hopperBlockEntity.setCooldown(level.spigotConfig.hopperTransfer); // Spigot -+ } -+ -+ return false; -+ } -+ int origCount = event.getItem().getAmount(); // Spigot -+ ItemStack itemStack = HopperBlockEntity.addItem(container, hopper, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItem()), null); -+ // CraftBukkit end -+ -+ if (itemStack.isEmpty()) { -+ container.setChanged(); -+ return true; -+ } -+ -+ item.setCount(count); -+ // Spigot start -+ item.shrink(origCount - itemStack.getCount()); -+ if (count <= level.spigotConfig.hopperAmount) { -+ // Spigot end -+ container.setItem(slot, item); -+ } - } -+ // DivineMC end - Optimize hoppers - - return false; - } -@@ -615,15 +374,13 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen - public static boolean addItem(Container container, ItemEntity item) { - boolean flag = false; - // CraftBukkit start -- if (org.bukkit.event.inventory.InventoryPickupItemEvent.getHandlerList().getRegisteredListeners().length > 0) { // Paper - optimize hoppers - org.bukkit.event.inventory.InventoryPickupItemEvent event = new org.bukkit.event.inventory.InventoryPickupItemEvent( -- getInventory(container), (org.bukkit.entity.Item) item.getBukkitEntity() // Paper - Perf: Optimize Hoppers; use getInventory() to avoid snapshot creation -+ container.getOwner().getInventory(), (org.bukkit.entity.Item) item.getBukkitEntity() // DivineMC - Optimize hoppers - ); - if (!event.callEvent()) { - return false; - } - // CraftBukkit end -- } // Paper - Perf: Optimize Hoppers - ItemStack itemStack = item.getItem().copy(); - ItemStack itemStack1 = addItem(null, container, itemStack, null); - if (itemStack1.isEmpty()) { -@@ -678,9 +435,7 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen - stack = stack.split(destination.getMaxStackSize()); - } - // Spigot end -- ignoreBlockEntityUpdates = true; // Paper - Perf: Optimize Hoppers - destination.setItem(slot, stack); -- ignoreBlockEntityUpdates = false; // Paper - Perf: Optimize Hoppers - stack = leftover; // Paper - Make hoppers respect inventory max stack size - flag = true; - } else if (canMergeItems(item, stack)) { -@@ -768,19 +523,13 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen - - @Nullable - public static Container getContainerAt(Level level, BlockPos pos) { -- return getContainerAt(level, pos, level.getBlockState(pos), pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5, true); // Paper - Optimize hoppers -+ return getContainerAt(level, pos, level.getBlockState(pos), pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5); // DivineMC - Optimize hoppers - } - - @Nullable - private static Container getContainerAt(Level level, BlockPos pos, BlockState state, double x, double y, double z) { -- // Paper start - Perf: Optimize Hoppers -- return HopperBlockEntity.getContainerAt(level, pos, state, x, y, z, false); -- } -- @Nullable -- private static Container getContainerAt(Level level, BlockPos pos, BlockState state, double x, double y, double z, final boolean optimizeEntities) { -- // Paper end - Perf: Optimize Hoppers - Container blockContainer = getBlockContainer(level, pos, state); -- if (blockContainer == null && (!optimizeEntities || !level.paperConfig().hopper.ignoreOccludingBlocks || !state.getBukkitMaterial().isOccluding())) { // Paper - Perf: Optimize Hoppers -+ if (blockContainer == null) { // DivineMC - Optimize hoppers - blockContainer = getEntityContainer(level, x, y, z); - } - -@@ -806,14 +555,14 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen - - @Nullable - private static Container getEntityContainer(Level level, double x, double y, double z) { -- List entities = level.getEntitiesOfClass( -- (Class) Container.class, new AABB(x - 0.5, y - 0.5, z - 0.5, x + 0.5, y + 0.5, z + 0.5), EntitySelector.CONTAINER_ENTITY_SELECTOR // Paper - Perf: Optimize hoppers -- ); -+ List entities = level.getEntities( -+ (Entity)null, new AABB(x - 0.5, y - 0.5, z - 0.5, x + 0.5, y + 0.5, z + 0.5), EntitySelector.CONTAINER_ENTITY_SELECTOR -+ ); // DivineMC - Optimize hoppers - return !entities.isEmpty() ? (Container)entities.get(level.random.nextInt(entities.size())) : null; - } - - private static boolean canMergeItems(ItemStack stack1, ItemStack stack2) { -- return stack1.getCount() < stack1.getMaxStackSize() && ItemStack.isSameItemSameComponents(stack1, stack2); // Paper - Perf: Optimize Hoppers; used to return true for full itemstacks?! -+ return stack1.getCount() <= stack1.getMaxStackSize() && ItemStack.isSameItemSameComponents(stack1, stack2); // DivineMC - Optimize hoppers - } - - @Override -diff --git a/net/minecraft/world/ticks/LevelChunkTicks.java b/net/minecraft/world/ticks/LevelChunkTicks.java -index faf45ac459f7c25309d6ef6dce371d484a0dae7b..6f0d1b28a45b93c51c5476283f1629a86e3420d1 100644 ---- a/net/minecraft/world/ticks/LevelChunkTicks.java -+++ b/net/minecraft/world/ticks/LevelChunkTicks.java -@@ -17,7 +17,8 @@ import net.minecraft.core.BlockPos; - import net.minecraft.nbt.ListTag; - import net.minecraft.world.level.ChunkPos; - --public class LevelChunkTicks implements SerializableTickContainer, TickContainerAccess, ca.spottedleaf.moonrise.patches.chunk_system.ticks.ChunkSystemLevelChunkTicks { // Paper - rewrite chunk system -+public class LevelChunkTicks implements SerializableTickContainer, TickContainerAccess, ca.spottedleaf.moonrise.patches.chunk_system.ticks.ChunkSystemLevelChunkTicks { // DivineMC -+ private static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.LogManager.getLogger(LevelChunkTicks.class); // Paper - rewrite chunk system - private final Queue> tickQueue = new PriorityQueue<>(ScheduledTick.DRAIN_ORDER); - @Nullable - private List> pendingTicks; -@@ -71,10 +72,18 @@ public class LevelChunkTicks implements SerializableTickContainer, TickCon - - @Nullable - public ScheduledTick poll() { -- ScheduledTick scheduledTick = this.tickQueue.poll(); -- if (scheduledTick != null) { -- this.ticksPerPosition.remove(scheduledTick); this.dirty = true; // Paper - rewrite chunk system -+ // DivineMC start - catch exceptions when polling chunk ticks -+ ScheduledTick scheduledTick = null; -+ try { -+ scheduledTick = this.tickQueue.poll(); -+ if (scheduledTick != null) { -+ this.ticksPerPosition.remove(scheduledTick); this.dirty = true; // Paper - rewrite chunk system -+ } -+ } catch (Exception e) { -+ log.error("Encountered caught exception when polling chunk ticks, blocking and returning null.", e); -+ return null; - } -+ // DivineMC end - catch exceptions when polling chunk ticks - - return scheduledTick; - } diff --git a/divinemc-server/minecraft-patches/features/0019-Option-to-allow-weird-movement-and-disable-teleporti.patch b/divinemc-server/minecraft-patches/features/0015-Option-to-allow-weird-movement-and-disable-teleporti.patch similarity index 98% rename from divinemc-server/minecraft-patches/features/0019-Option-to-allow-weird-movement-and-disable-teleporti.patch rename to divinemc-server/minecraft-patches/features/0015-Option-to-allow-weird-movement-and-disable-teleporti.patch index 8d692e9..e2ea80b 100644 --- a/divinemc-server/minecraft-patches/features/0019-Option-to-allow-weird-movement-and-disable-teleporti.patch +++ b/divinemc-server/minecraft-patches/features/0015-Option-to-allow-weird-movement-and-disable-teleporti.patch @@ -6,7 +6,7 @@ Subject: [PATCH] Option to allow weird movement and disable teleporting diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 19c7802969aa9d1e15b4c67ee5c97e73daf0a460..b2881d1b0cc2bc172dd21349e07b7e6f89bd996c 100644 +index f8c76bb2c9fa625e191036dc58ef3dfb1d4ee930..5b8031a45b1f4f6d0e86c2a839d173a6e3f4f697 100644 --- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java @@ -574,7 +574,7 @@ public class ServerGamePacketListenerImpl diff --git a/divinemc-server/minecraft-patches/features/0020-Lag-compensation.patch b/divinemc-server/minecraft-patches/features/0016-Lag-compensation.patch similarity index 96% rename from divinemc-server/minecraft-patches/features/0020-Lag-compensation.patch rename to divinemc-server/minecraft-patches/features/0016-Lag-compensation.patch index d1f8dc5..b19fcea 100644 --- a/divinemc-server/minecraft-patches/features/0020-Lag-compensation.patch +++ b/divinemc-server/minecraft-patches/features/0016-Lag-compensation.patch @@ -5,13 +5,13 @@ Subject: [PATCH] Lag compensation diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java -index 351b035d1f3025af28b5147b95b912e0e2ab9212..1bcccba4df407ec4d53f49c3c2c7493db87b2240 100644 +index 5a726da8535aa939f043829a3c60fdd9d4ed154a..0aebdd1cb0e1b63ff9a867bdcda7cbdc3c194b56 100644 --- a/net/minecraft/server/MinecraftServer.java +++ b/net/minecraft/server/MinecraftServer.java @@ -289,6 +289,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop S spin(Function threadFunction) { @@ -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 8c136d77451c17613834c4a7dab6ab6c2fe4d716..f7f7a553d52f42cbbc5d839b850d65ddf97c3ed1 100644 +index a70007d324e2169d1b2a1a11046500196b8b5660..53b606c34f65b80a676ddd26d64028dbdebecb43 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 8c136d77451c17613834c4a7dab6ab6c2fe4d716..f7f7a553d52f42cbbc5d839b850d65dd 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 8c136d77451c17613834c4a7dab6ab6c2fe4d716..f7f7a553d52f42cbbc5d839b850d65dd 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 @@ -96,10 +96,10 @@ index 04a1c17a34b495b71eb1b0ccd597cef83480464d..edb128a3f454e7b3009ca360509decd7 protected float getBlockSpeedFactor() { return Mth.lerp((float)this.getAttributeValue(Attributes.MOVEMENT_EFFICIENCY), super.getBlockSpeedFactor(), 1.0F); diff --git a/net/minecraft/world/entity/PortalProcessor.java b/net/minecraft/world/entity/PortalProcessor.java -index 88b07fbb96b20124777889830afa480673629d43..d2661ea79536010414f77256332f214d19106dd9 100644 +index be357d9e3c5327ceec12c31830551a564c8cea1b..f77da420bc88df6ec304086fc1eba0690fe278b1 100644 --- a/net/minecraft/world/entity/PortalProcessor.java +++ b/net/minecraft/world/entity/PortalProcessor.java -@@ -24,10 +24,20 @@ public class PortalProcessor { +@@ -26,10 +26,20 @@ public class PortalProcessor { return false; } else { this.insidePortalThisTick = false; @@ -244,7 +244,7 @@ index b631e35e965b1914cdeeddab8bd6bdbfd2465079..bb7dab597850fba8f0dff4461fc518e0 } diff --git a/net/minecraft/world/level/chunk/LevelChunk.java b/net/minecraft/world/level/chunk/LevelChunk.java -index ba88a3aed79543f69a5bf30cfd03f30983d229cf..0337f4b9ca3c9c9a1e2a7cf19fcbad5e78b949dc 100644 +index b245f594ff91e2d29c83f56b9ed5165f37387e7e..534384727e852dc8ea822f49f182af49eb3a40c1 100644 --- a/net/minecraft/world/level/chunk/LevelChunk.java +++ b/net/minecraft/world/level/chunk/LevelChunk.java @@ -907,6 +907,19 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p diff --git a/divinemc-server/minecraft-patches/features/0021-MSPT-Tracking-for-each-world.patch b/divinemc-server/minecraft-patches/features/0017-MSPT-Tracking-for-each-world.patch similarity index 89% rename from divinemc-server/minecraft-patches/features/0021-MSPT-Tracking-for-each-world.patch rename to divinemc-server/minecraft-patches/features/0017-MSPT-Tracking-for-each-world.patch index 69a1fdc..a9a4bf9 100644 --- a/divinemc-server/minecraft-patches/features/0021-MSPT-Tracking-for-each-world.patch +++ b/divinemc-server/minecraft-patches/features/0017-MSPT-Tracking-for-each-world.patch @@ -5,7 +5,7 @@ Subject: [PATCH] MSPT Tracking for each world diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java -index 1bcccba4df407ec4d53f49c3c2c7493db87b2240..54c8605a4e36605208344e39726a4c3bbe972076 100644 +index 11b89a625b942f5f2f882c54dbfc08c16e983425..fc9de137ed1681afd1ef51391cf8f30fd4c61c4b 100644 --- a/net/minecraft/server/MinecraftServer.java +++ b/net/minecraft/server/MinecraftServer.java @@ -1684,7 +1684,15 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop entitiesWithScheduledTasks = java.util.concurrent.ConcurrentHashMap.newKeySet(); // DivineMC - Skip EntityScheduler's executeTick checks if there isn't any tasks to be run diff --git a/divinemc-server/minecraft-patches/features/0023-Carpet-Fixes-RecipeManager-Optimize.patch b/divinemc-server/minecraft-patches/features/0019-Carpet-Fixes-RecipeManager-Optimize.patch similarity index 96% rename from divinemc-server/minecraft-patches/features/0023-Carpet-Fixes-RecipeManager-Optimize.patch rename to divinemc-server/minecraft-patches/features/0019-Carpet-Fixes-RecipeManager-Optimize.patch index 3edd5a6..8611fdb 100644 --- a/divinemc-server/minecraft-patches/features/0023-Carpet-Fixes-RecipeManager-Optimize.patch +++ b/divinemc-server/minecraft-patches/features/0019-Carpet-Fixes-RecipeManager-Optimize.patch @@ -9,7 +9,7 @@ This is a fully vanilla optimization. Improves: [Blast]Furnace/Campfire/Smoker/S This was mostly made for the auto crafting table, since the performance boost is much more visible while using that mod diff --git a/net/minecraft/world/item/crafting/RecipeManager.java b/net/minecraft/world/item/crafting/RecipeManager.java -index f679b7f4f152faf0255b8776c91dfff1be2851c8..300d9564602126a2e8a94774dc3a0f7c21d75e17 100644 +index c7d91a38fc3b5cf2487e09719a1f9bc39ca5d20e..875e9711c9dacaf73636c7c7ce1cf740a23f006f 100644 --- a/net/minecraft/world/item/crafting/RecipeManager.java +++ b/net/minecraft/world/item/crafting/RecipeManager.java @@ -166,7 +166,7 @@ public class RecipeManager extends SimplePreparableReloadListener imp diff --git a/divinemc-server/minecraft-patches/features/0024-Snowball-and-Egg-knockback.patch b/divinemc-server/minecraft-patches/features/0020-Snowball-and-Egg-knockback.patch similarity index 100% rename from divinemc-server/minecraft-patches/features/0024-Snowball-and-Egg-knockback.patch rename to divinemc-server/minecraft-patches/features/0020-Snowball-and-Egg-knockback.patch diff --git a/divinemc-server/minecraft-patches/features/0025-Block-Log4Shell-exploit.patch b/divinemc-server/minecraft-patches/features/0021-Block-Log4Shell-exploit.patch similarity index 95% rename from divinemc-server/minecraft-patches/features/0025-Block-Log4Shell-exploit.patch rename to divinemc-server/minecraft-patches/features/0021-Block-Log4Shell-exploit.patch index dba6bf5..f848bca 100644 --- a/divinemc-server/minecraft-patches/features/0025-Block-Log4Shell-exploit.patch +++ b/divinemc-server/minecraft-patches/features/0021-Block-Log4Shell-exploit.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Block Log4Shell exploit diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index b2881d1b0cc2bc172dd21349e07b7e6f89bd996c..c96c13ff7e2f0f62166e429dcb66ff5217604c32 100644 +index 5b8031a45b1f4f6d0e86c2a839d173a6e3f4f697..a3ec8c92dae7735bb0b1ececc9851c829c486a53 100644 --- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java @@ -2425,6 +2425,7 @@ public class ServerGamePacketListenerImpl diff --git a/divinemc-server/minecraft-patches/features/0026-Re-Fix-MC-117075.patch b/divinemc-server/minecraft-patches/features/0022-Re-Fix-MC-117075.patch similarity index 67% rename from divinemc-server/minecraft-patches/features/0026-Re-Fix-MC-117075.patch rename to divinemc-server/minecraft-patches/features/0022-Re-Fix-MC-117075.patch index b82b489..aee90a0 100644 --- a/divinemc-server/minecraft-patches/features/0026-Re-Fix-MC-117075.patch +++ b/divinemc-server/minecraft-patches/features/0022-Re-Fix-MC-117075.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Re-Fix MC-117075 diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java -index 9608aa7231e1e19ac34f7fcf6cc4051da01f2f3e..440d890d32a6705aa5ebc84040c4290634bbae9b 100644 +index 9608aa7231e1e19ac34f7fcf6cc4051da01f2f3e..92fc936c6bdbda909b9cdf157ec9d46a4872098c 100644 --- a/net/minecraft/world/level/Level.java +++ b/net/minecraft/world/level/Level.java @@ -113,7 +113,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl @@ -17,7 +17,14 @@ index 9608aa7231e1e19ac34f7fcf6cc4051da01f2f3e..440d890d32a6705aa5ebc84040c42906 protected final NeighborUpdater neighborUpdater; private final List pendingBlockEntityTickers = Lists.newArrayList(); private boolean tickingBlockEntities; -@@ -1528,7 +1528,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl +@@ -1521,14 +1521,12 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + boolean runsNormally = this.tickRateManager().runsNormally(); + + int tickedEntities = 0; // Paper - rewrite chunk system +- var toRemove = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet(); // Paper - Fix MC-117075; use removeAll +- toRemove.add(null); // Paper - Fix MC-117075 + for (tileTickPosition = 0; tileTickPosition < this.blockEntityTickers.size(); tileTickPosition++) { // Paper - Disable tick limiters + this.tileTickPosition = (this.tileTickPosition < this.blockEntityTickers.size()) ? this.tileTickPosition : 0; TickingBlockEntity tickingBlockEntity = this.blockEntityTickers.get(this.tileTickPosition); // Spigot end if (tickingBlockEntity.isRemoved()) { @@ -26,11 +33,12 @@ index 9608aa7231e1e19ac34f7fcf6cc4051da01f2f3e..440d890d32a6705aa5ebc84040c42906 } else if (runsNormally && this.shouldTickBlocksAt(tickingBlockEntity.getPos())) { tickingBlockEntity.tick(); // DivineMC start - Parallel world ticking -@@ -1541,6 +1541,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl +@@ -1539,7 +1537,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + // DivineMC end - Parallel world ticking + } } - this.blockEntityTickers.removeAll(toRemove); // Paper - Fix MC-117075 - +- this.blockEntityTickers.removeAll(toRemove); // Paper - Fix MC-117075 + this.blockEntityTickers.removeMarkedEntries(); // DivineMC - optimize block entity removals - Fix MC-117075 + this.tickingBlockEntities = false; this.spigotConfig.currentPrimedTnt = 0; // Spigot - } diff --git a/divinemc-server/minecraft-patches/features/0027-Skip-distanceToSqr-call-in-ServerEntity-sendChanges-.patch b/divinemc-server/minecraft-patches/features/0023-Skip-distanceToSqr-call-in-ServerEntity-sendChanges-.patch similarity index 100% rename from divinemc-server/minecraft-patches/features/0027-Skip-distanceToSqr-call-in-ServerEntity-sendChanges-.patch rename to divinemc-server/minecraft-patches/features/0023-Skip-distanceToSqr-call-in-ServerEntity-sendChanges-.patch diff --git a/divinemc-server/minecraft-patches/features/0028-Optimize-canSee-checks.patch b/divinemc-server/minecraft-patches/features/0024-Optimize-canSee-checks.patch similarity index 87% rename from divinemc-server/minecraft-patches/features/0028-Optimize-canSee-checks.patch rename to divinemc-server/minecraft-patches/features/0024-Optimize-canSee-checks.patch index e509d00..395c9e8 100644 --- a/divinemc-server/minecraft-patches/features/0028-Optimize-canSee-checks.patch +++ b/divinemc-server/minecraft-patches/features/0024-Optimize-canSee-checks.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Optimize canSee checks diff --git a/net/minecraft/server/level/ChunkMap.java b/net/minecraft/server/level/ChunkMap.java -index 34c37abfe6c33ca1073450c8925f553d34be87a0..5f754dedc2fcbcc67b20c7069b8c5fccd68a7361 100644 +index dccf4d4b1067e1b09e38cabeae82185929494b62..55da7420dc3dfff468529a0f101f864dbefe3c7c 100644 --- a/net/minecraft/server/level/ChunkMap.java +++ b/net/minecraft/server/level/ChunkMap.java -@@ -1293,7 +1293,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1258,7 +1258,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider flag = flag && this.entity.broadcastToPlayer(player) && ChunkMap.this.isChunkTracked(player, this.entity.chunkPosition().x, this.entity.chunkPosition().z); // Paper end - Configurable entity tracking range by Y // CraftBukkit start - respect vanish API diff --git a/divinemc-server/minecraft-patches/features/0029-Implement-NoChatReports.patch b/divinemc-server/minecraft-patches/features/0025-Implement-NoChatReports.patch similarity index 98% rename from divinemc-server/minecraft-patches/features/0029-Implement-NoChatReports.patch rename to divinemc-server/minecraft-patches/features/0025-Implement-NoChatReports.patch index 5b36ed7..0f7ab96 100644 --- a/divinemc-server/minecraft-patches/features/0029-Implement-NoChatReports.patch +++ b/divinemc-server/minecraft-patches/features/0025-Implement-NoChatReports.patch @@ -218,10 +218,10 @@ index 9fcdff2be139296f4e14b54c33cc795efdff0c7f..64c3bbe540599e5195f0cc89635bff2c // Paper start - Add setting for proxy online mode status return properties.enforceSecureProfile diff --git a/net/minecraft/server/network/ServerCommonPacketListenerImpl.java b/net/minecraft/server/network/ServerCommonPacketListenerImpl.java -index 0c499eca39371993c46d510fa45298efdd67d20c..2eccb615dfebf64ca76d2cce1876d6ec2afe518d 100644 +index 801dd76a2c7f76fc6fdb7167cbf3ab1310be36c9..4fa55fac5dab26a505cba2c1876e9459a582da12 100644 --- a/net/minecraft/server/network/ServerCommonPacketListenerImpl.java +++ b/net/minecraft/server/network/ServerCommonPacketListenerImpl.java -@@ -308,10 +308,64 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack +@@ -312,10 +312,64 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack } public void send(Packet packet) { diff --git a/divinemc-server/minecraft-patches/features/0030-Optimize-Structure-Generation.patch b/divinemc-server/minecraft-patches/features/0026-Optimize-Structure-Generation.patch similarity index 99% rename from divinemc-server/minecraft-patches/features/0030-Optimize-Structure-Generation.patch rename to divinemc-server/minecraft-patches/features/0026-Optimize-Structure-Generation.patch index 5a6a5d6..a0f56cc 100644 --- a/divinemc-server/minecraft-patches/features/0030-Optimize-Structure-Generation.patch +++ b/divinemc-server/minecraft-patches/features/0026-Optimize-Structure-Generation.patch @@ -231,7 +231,7 @@ index 4a6da3648c513a6cce16cf71246937d2d0ad014d..6af4c9026e814dee1ed4c7593ad00b5f + // Quiil end - Optimize Structure Generation } diff --git a/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java b/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java -index ab1dcbe416e2c3c94cfddf04b7ed053425a71806..985d11f0b72858d66ad011d83106730b07e25242 100644 +index a37eb2e29b4577ebc711e8ef7b47fbbc3bc66897..45d6fbeeaac577bb35adb69fd344b9486953ea03 100644 --- a/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java +++ b/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java @@ -249,6 +249,12 @@ public class StructureTemplate { diff --git a/divinemc-server/minecraft-patches/features/0031-Verify-Minecraft-EULA-earlier.patch b/divinemc-server/minecraft-patches/features/0027-Verify-Minecraft-EULA-earlier.patch similarity index 92% rename from divinemc-server/minecraft-patches/features/0031-Verify-Minecraft-EULA-earlier.patch rename to divinemc-server/minecraft-patches/features/0027-Verify-Minecraft-EULA-earlier.patch index dd5ff10..bd22335 100644 --- a/divinemc-server/minecraft-patches/features/0031-Verify-Minecraft-EULA-earlier.patch +++ b/divinemc-server/minecraft-patches/features/0027-Verify-Minecraft-EULA-earlier.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Verify Minecraft EULA earlier diff --git a/net/minecraft/server/Main.java b/net/minecraft/server/Main.java -index b48fc9e0b95fe6c8f72c5501b8de374e6ac2e5d6..44e98037c986dec845613fa24f9664ef1803b96c 100644 +index 1485186d4989874ef89c4e83830f26358a43759c..c4f9b67c92e4c7e8015f637fe633a9e8da276e5c 100644 --- a/net/minecraft/server/Main.java +++ b/net/minecraft/server/Main.java -@@ -131,7 +131,6 @@ public class Main { +@@ -123,7 +123,6 @@ public class Main { dedicatedServerSettings.forceSave(); RegionFileVersion.configure(dedicatedServerSettings.getProperties().regionFileComression); Path path2 = Paths.get("eula.txt"); @@ -16,7 +16,7 @@ index b48fc9e0b95fe6c8f72c5501b8de374e6ac2e5d6..44e98037c986dec845613fa24f9664ef // Paper start - load config files early for access below if needed org.bukkit.configuration.file.YamlConfiguration bukkitConfiguration = io.papermc.paper.configuration.PaperConfigurations.loadLegacyConfigFile((File) optionSet.valueOf("bukkit-settings")); org.bukkit.configuration.file.YamlConfiguration spigotConfiguration = io.papermc.paper.configuration.PaperConfigurations.loadLegacyConfigFile((File) optionSet.valueOf("spigot-settings")); -@@ -154,19 +153,6 @@ public class Main { +@@ -146,19 +145,6 @@ public class Main { return; } diff --git a/divinemc-server/minecraft-patches/features/0032-Configurable-MC-67.patch b/divinemc-server/minecraft-patches/features/0028-Configurable-MC-67.patch similarity index 85% rename from divinemc-server/minecraft-patches/features/0032-Configurable-MC-67.patch rename to divinemc-server/minecraft-patches/features/0028-Configurable-MC-67.patch index 225e18a..0804d57 100644 --- a/divinemc-server/minecraft-patches/features/0032-Configurable-MC-67.patch +++ b/divinemc-server/minecraft-patches/features/0028-Configurable-MC-67.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Configurable MC-67 diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java -index 258cb45f1f959b75c1bcdb130811af2f8fddf07d..64ba7d3573461a97c842849ee80642b0d43ee1c8 100644 +index 3943789e241f6bb6bc165099b1fb585463cf3d86..07e8bda8eb200d5a7554e0319e1a00dc85454e1a 100644 --- a/net/minecraft/world/entity/Entity.java +++ b/net/minecraft/world/entity/Entity.java -@@ -3997,6 +3997,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess +@@ -4017,6 +4017,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess } public boolean canTeleport(Level fromLevel, Level toLevel) { diff --git a/divinemc-server/minecraft-patches/features/0033-Option-to-disable-saving-of-snowball-and-firework.patch b/divinemc-server/minecraft-patches/features/0029-Option-to-disable-saving-of-snowball-and-firework.patch similarity index 95% rename from divinemc-server/minecraft-patches/features/0033-Option-to-disable-saving-of-snowball-and-firework.patch rename to divinemc-server/minecraft-patches/features/0029-Option-to-disable-saving-of-snowball-and-firework.patch index 8e0bd33..79a0955 100644 --- a/divinemc-server/minecraft-patches/features/0033-Option-to-disable-saving-of-snowball-and-firework.patch +++ b/divinemc-server/minecraft-patches/features/0029-Option-to-disable-saving-of-snowball-and-firework.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Option to disable saving of snowball and firework diff --git a/net/minecraft/world/entity/projectile/FireworkRocketEntity.java b/net/minecraft/world/entity/projectile/FireworkRocketEntity.java -index 774ca9e0b56fd175ae246051de762d0c4256ca58..3a380d038ef1231624a646c38b60a4344694e321 100644 +index c7ae41b2cbc1eb85a6eb9c16813bd326fb8f49f0..e6764ee35f9d9d20a7693d50ea61b703867e3c79 100644 --- a/net/minecraft/world/entity/projectile/FireworkRocketEntity.java +++ b/net/minecraft/world/entity/projectile/FireworkRocketEntity.java @@ -364,4 +364,14 @@ public class FireworkRocketEntity extends Projectile implements ItemSupplier { diff --git a/divinemc-server/minecraft-patches/features/0034-Catch-update-suppressors.patch b/divinemc-server/minecraft-patches/features/0030-Catch-update-suppressors.patch similarity index 98% rename from divinemc-server/minecraft-patches/features/0034-Catch-update-suppressors.patch rename to divinemc-server/minecraft-patches/features/0030-Catch-update-suppressors.patch index 8336629..093c181 100644 --- a/divinemc-server/minecraft-patches/features/0034-Catch-update-suppressors.patch +++ b/divinemc-server/minecraft-patches/features/0030-Catch-update-suppressors.patch @@ -20,7 +20,7 @@ index 4535858701b2bb232b9d2feb2af6551526232ddc..aa4dd7517e8be167aef1eaf7aa907e3c if (var4 instanceof ReportedException reportedException && reportedException.getCause() instanceof OutOfMemoryError) { throw makeReportedException(var4, packet, processor); diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java -index 72ab570ca8b532a1f55408515f34a02aa0c79a6a..fe2d9328a7e9365f8c7e24e862038bc94ddfe1ca 100644 +index 804de864da13ae0be6a1caee88e95a19e35d08c0..5a0c8791495aa522e511918ad0a24d9bbe6b5877 100644 --- a/net/minecraft/server/MinecraftServer.java +++ b/net/minecraft/server/MinecraftServer.java @@ -1694,6 +1694,10 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop(), new com.google.common.util.concurrent.ThreadFactoryBuilder() diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java -index fe2d9328a7e9365f8c7e24e862038bc94ddfe1ca..daf6141a6aed6baf7b8de4030324703a0fe872d3 100644 +index 5a0c8791495aa522e511918ad0a24d9bbe6b5877..9d6fa3bced0ba5ab3443bdf4ce306598a8e4a31f 100644 --- a/net/minecraft/server/MinecraftServer.java +++ b/net/minecraft/server/MinecraftServer.java -@@ -2709,8 +2709,11 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop implements PaletteResize, PalettedContainer diff --git a/divinemc-server/minecraft-patches/features/0042-Command-block-parse-results-caching.patch b/divinemc-server/minecraft-patches/features/0038-Command-block-parse-results-caching.patch similarity index 100% rename from divinemc-server/minecraft-patches/features/0042-Command-block-parse-results-caching.patch rename to divinemc-server/minecraft-patches/features/0038-Command-block-parse-results-caching.patch diff --git a/divinemc-server/minecraft-patches/features/0043-Linear-region-file-format.patch b/divinemc-server/minecraft-patches/features/0039-Linear-region-file-format.patch similarity index 76% rename from divinemc-server/minecraft-patches/features/0043-Linear-region-file-format.patch rename to divinemc-server/minecraft-patches/features/0039-Linear-region-file-format.patch index c00a8f6..bb7a6fd 100644 --- a/divinemc-server/minecraft-patches/features/0043-Linear-region-file-format.patch +++ b/divinemc-server/minecraft-patches/features/0039-Linear-region-file-format.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Linear region file format diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/io/ChunkSystemRegionFileStorage.java b/ca/spottedleaf/moonrise/patches/chunk_system/io/ChunkSystemRegionFileStorage.java -index a814512fcfb85312474ae2c2c21443843bf57831..c21e04c1d43797db221e4712fcc987a44eaa983c 100644 +index a814512fcfb85312474ae2c2c21443843bf57831..fdccc27c528b01b16a72e614ffd96523aa6f50d2 100644 --- a/ca/spottedleaf/moonrise/patches/chunk_system/io/ChunkSystemRegionFileStorage.java +++ b/ca/spottedleaf/moonrise/patches/chunk_system/io/ChunkSystemRegionFileStorage.java @@ -8,9 +8,9 @@ public interface ChunkSystemRegionFileStorage { @@ -13,15 +13,15 @@ index a814512fcfb85312474ae2c2c21443843bf57831..c21e04c1d43797db221e4712fcc987a4 public boolean moonrise$doesRegionFileNotExistNoIO(final int chunkX, final int chunkZ); - public RegionFile moonrise$getRegionFileIfLoaded(final int chunkX, final int chunkZ); -+ public org.stupidcraft.linearpaper.region.IRegionFile moonrise$getRegionFileIfLoaded(final int chunkX, final int chunkZ); // DivineMC - Linear region file format ++ public org.bxteam.divinemc.region.IRegionFile moonrise$getRegionFileIfLoaded(final int chunkX, final int chunkZ); // DivineMC - Linear region file format - public RegionFile moonrise$getRegionFileIfExists(final int chunkX, final int chunkZ) throws IOException; -+ public org.stupidcraft.linearpaper.region.IRegionFile moonrise$getRegionFileIfExists(final int chunkX, final int chunkZ) throws IOException; // DivineMC - Linear region file format ++ public org.bxteam.divinemc.region.IRegionFile moonrise$getRegionFileIfExists(final int chunkX, final int chunkZ) throws IOException; // DivineMC - Linear region file format public MoonriseRegionFileIO.RegionDataController.WriteData moonrise$startWrite( final int chunkX, final int chunkZ, final CompoundTag compound diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/io/MoonriseRegionFileIO.java b/ca/spottedleaf/moonrise/patches/chunk_system/io/MoonriseRegionFileIO.java -index 98fbc5c8044bd945d64569f13412a6e7e49a4e7f..57ae23fdd5a95e7670eb4dfaca0d290edfc1d25c 100644 +index 98fbc5c8044bd945d64569f13412a6e7e49a4e7f..34682217252cb98a70511a8cb25f077ec9f872b8 100644 --- a/ca/spottedleaf/moonrise/patches/chunk_system/io/MoonriseRegionFileIO.java +++ b/ca/spottedleaf/moonrise/patches/chunk_system/io/MoonriseRegionFileIO.java @@ -1260,7 +1260,7 @@ public final class MoonriseRegionFileIO { @@ -29,7 +29,7 @@ index 98fbc5c8044bd945d64569f13412a6e7e49a4e7f..57ae23fdd5a95e7670eb4dfaca0d290e // Paper start - flush regionfiles on save if (this.world.paperConfig().chunks.flushRegionsOnSave) { - final RegionFile regionFile = this.regionDataController.getCache().moonrise$getRegionFileIfLoaded(this.chunkX, this.chunkZ); -+ final org.stupidcraft.linearpaper.region.IRegionFile regionFile = this.regionDataController.getCache().moonrise$getRegionFileIfLoaded(this.chunkX, this.chunkZ); // DivineMC - Linear region file format ++ final org.bxteam.divinemc.region.IRegionFile regionFile = this.regionDataController.getCache().moonrise$getRegionFileIfLoaded(this.chunkX, this.chunkZ); // DivineMC - Linear region file format if (regionFile != null) { regionFile.flush(); } // else: evicted from cache, which should have called flush @@ -38,12 +38,12 @@ index 98fbc5c8044bd945d64569f13412a6e7e49a4e7f..57ae23fdd5a95e7670eb4dfaca0d290e public static interface IORunnable { - public void run(final RegionFile regionFile) throws IOException; -+ public void run(final org.stupidcraft.linearpaper.region.IRegionFile regionFile) throws IOException; // DivineMC - Linear region file format ++ public void run(final org.bxteam.divinemc.region.IRegionFile regionFile) throws IOException; // DivineMC - Linear region file format } } diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/storage/ChunkSystemChunkBuffer.java b/ca/spottedleaf/moonrise/patches/chunk_system/storage/ChunkSystemChunkBuffer.java -index 51c126735ace8fdde89ad97b5cab62f244212db0..c466ce5c669e4b5ed835b13584a5ae5afe08fa11 100644 +index 51c126735ace8fdde89ad97b5cab62f244212db0..8713d00d767c9225a0823d2fdbb0b479005738d7 100644 --- a/ca/spottedleaf/moonrise/patches/chunk_system/storage/ChunkSystemChunkBuffer.java +++ b/ca/spottedleaf/moonrise/patches/chunk_system/storage/ChunkSystemChunkBuffer.java @@ -8,5 +8,5 @@ public interface ChunkSystemChunkBuffer { @@ -51,10 +51,10 @@ index 51c126735ace8fdde89ad97b5cab62f244212db0..c466ce5c669e4b5ed835b13584a5ae5a public void moonrise$setWriteOnClose(final boolean value); - public void moonrise$write(final RegionFile regionFile) throws IOException; -+ public void moonrise$write(final org.stupidcraft.linearpaper.region.IRegionFile regionFile) throws IOException; // DivineMC - Linear region file format ++ public void moonrise$write(final org.bxteam.divinemc.region.IRegionFile regionFile) throws IOException; // DivineMC - Linear region file format } diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java -index daf6141a6aed6baf7b8de4030324703a0fe872d3..6182c6f7cc6f5199897a5e227dabe9f9738733a5 100644 +index 26a4a279122bb03a83dd4a34e780d6a4567ba3a8..51b79f614417f231951e9ba05b29ff0044e081e7 100644 --- a/net/minecraft/server/MinecraftServer.java +++ b/net/minecraft/server/MinecraftServer.java @@ -947,10 +947,10 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop list1 = Lists.newArrayList(); - try (RegionFile regionFile = new RegionFile(regionStorageInfo, file.toPath(), path, true)) { -+ try (org.stupidcraft.linearpaper.region.IRegionFile regionFile = org.stupidcraft.linearpaper.region.IRegionFileFactory.getAbstractRegionFile(regionStorageInfo, file.toPath(), path, true)) { // DivineMC - Linear region file format ++ try (org.bxteam.divinemc.region.IRegionFile regionFile = org.bxteam.divinemc.region.RegionFileFactory.getAbstractRegionFile(regionStorageInfo, file.toPath(), path, true)) { // DivineMC - Linear region file format for (int i2 = 0; i2 < 32; i2++) { for (int i3 = 0; i3 < 32; i3++) { ChunkPos chunkPos = new ChunkPos(i2 + i, i3 + i1); @@ -106,7 +106,7 @@ index e0bcda2ddea0d6633445a7440fbf0d18e50a7653..2ab8e8d9db5ea390a1334c28b97ef6d0 protected abstract boolean tryProcessOnePosition(T chunkStorage, ChunkPos chunkPos, ResourceKey dimension); - private void onFileFinished(RegionFile regionFile) { -+ private void onFileFinished(org.stupidcraft.linearpaper.region.IRegionFile regionFile) { // DivineMC - Linear region file format ++ private void onFileFinished(org.bxteam.divinemc.region.IRegionFile regionFile) { // DivineMC - Linear region file format if (WorldUpgrader.this.recreateRegionFiles) { if (this.previousWriteFuture != null) { this.previousWriteFuture.join(); @@ -115,12 +115,12 @@ index e0bcda2ddea0d6633445a7440fbf0d18e50a7653..2ab8e8d9db5ea390a1334c28b97ef6d0 } - record FileToUpgrade(RegionFile file, List chunksToUpgrade) { -+ record FileToUpgrade(org.stupidcraft.linearpaper.region.IRegionFile file, List chunksToUpgrade) { // DivineMC - Linear region file format ++ record FileToUpgrade(org.bxteam.divinemc.region.IRegionFile file, List chunksToUpgrade) { // DivineMC - Linear region file format } class PoiUpgrader extends WorldUpgrader.SimpleRegionStorageUpgrader { diff --git a/net/minecraft/world/level/chunk/storage/RegionFile.java b/net/minecraft/world/level/chunk/storage/RegionFile.java -index c72494e757a9dc50e053dbc873f7b30e83d5cb8c..5d29e3046485dcdda9356f6bfe6f6463e9b9bef4 100644 +index c72494e757a9dc50e053dbc873f7b30e83d5cb8c..2988084cf2d72557dd304dcb904d09441e79863d 100644 --- a/net/minecraft/world/level/chunk/storage/RegionFile.java +++ b/net/minecraft/world/level/chunk/storage/RegionFile.java @@ -22,7 +22,7 @@ import net.minecraft.util.profiling.jfr.JvmProfiler; @@ -128,7 +128,7 @@ index c72494e757a9dc50e053dbc873f7b30e83d5cb8c..5d29e3046485dcdda9356f6bfe6f6463 import org.slf4j.Logger; -public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patches.chunk_system.storage.ChunkSystemRegionFile { // Paper - rewrite chunk system -+public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patches.chunk_system.storage.ChunkSystemRegionFile, org.stupidcraft.linearpaper.region.IRegionFile { // Paper - rewrite chunk system // DivineMC - Linear region file format ++public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patches.chunk_system.storage.ChunkSystemRegionFile, org.bxteam.divinemc.region.IRegionFile { // Paper - rewrite chunk system // DivineMC - Linear region file format private static final Logger LOGGER = LogUtils.getLogger(); public static final int MAX_CHUNK_SIZE = 500 * 1024 * 1024; // Paper - don't write garbage data to disk if writing serialization fails private static final int SECTOR_BYTES = 4096; @@ -137,12 +137,12 @@ index c72494e757a9dc50e053dbc873f7b30e83d5cb8c..5d29e3046485dcdda9356f6bfe6f6463 @Override - public final void moonrise$write(final RegionFile regionFile) throws IOException { -+ public final void moonrise$write(final org.stupidcraft.linearpaper.region.IRegionFile regionFile) throws IOException { // DivineMC - Linear region file format ++ public final void moonrise$write(final org.bxteam.divinemc.region.IRegionFile regionFile) throws IOException { // DivineMC - Linear region file format regionFile.write(this.pos, ByteBuffer.wrap(this.buf, 0, this.count)); } // Paper end - rewrite chunk system diff --git a/net/minecraft/world/level/chunk/storage/RegionFileStorage.java b/net/minecraft/world/level/chunk/storage/RegionFileStorage.java -index 16cd10ab8de69ca3d29c84cf93715645322fd72a..1061d1480af0d4947d0f7f1b0028b4196cf25588 100644 +index 16cd10ab8de69ca3d29c84cf93715645322fd72a..9fd987078625bee796b381108ae7dbfb1f14d256 100644 --- a/net/minecraft/world/level/chunk/storage/RegionFileStorage.java +++ b/net/minecraft/world/level/chunk/storage/RegionFileStorage.java @@ -18,7 +18,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise @@ -150,7 +150,7 @@ index 16cd10ab8de69ca3d29c84cf93715645322fd72a..1061d1480af0d4947d0f7f1b0028b419 public static final String ANVIL_EXTENSION = ".mca"; private static final int MAX_CACHE_SIZE = 256; - public final Long2ObjectLinkedOpenHashMap regionCache = new Long2ObjectLinkedOpenHashMap<>(); -+ public final Long2ObjectLinkedOpenHashMap regionCache = new Long2ObjectLinkedOpenHashMap<>(); // DivineMC - Linear region file format ++ public final Long2ObjectLinkedOpenHashMap regionCache = new Long2ObjectLinkedOpenHashMap<>(); // DivineMC - Linear region file format private final RegionStorageInfo info; private final Path folder; private final boolean sync; @@ -168,7 +168,7 @@ index 16cd10ab8de69ca3d29c84cf93715645322fd72a..1061d1480af0d4947d0f7f1b0028b419 private final it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet nonExistingRegionFiles = new it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet(); private static String getRegionFileName(final int chunkX, final int chunkZ) { + // DivineMC start - Linear region file format -+ if (org.bxteam.divinemc.DivineConfig.regionFormatTypeName == org.stupidcraft.linearpaper.region.EnumRegionFileExtension.LINEAR) { ++ if (org.bxteam.divinemc.DivineConfig.regionFormatTypeName == org.bxteam.divinemc.region.RegionFileFormat.LINEAR) { + return "r." + (chunkX >> REGION_SHIFT) + "." + (chunkZ >> REGION_SHIFT) + ".linear"; + } + // DivineMC end - Linear region file format @@ -181,17 +181,17 @@ index 16cd10ab8de69ca3d29c84cf93715645322fd72a..1061d1480af0d4947d0f7f1b0028b419 @Override - public synchronized final RegionFile moonrise$getRegionFileIfLoaded(final int chunkX, final int chunkZ) { -+ public synchronized final org.stupidcraft.linearpaper.region.IRegionFile moonrise$getRegionFileIfLoaded(final int chunkX, final int chunkZ) { // DivineMC - Linear region file format ++ public synchronized final org.bxteam.divinemc.region.IRegionFile moonrise$getRegionFileIfLoaded(final int chunkX, final int chunkZ) { // DivineMC - Linear region file format return this.regionCache.getAndMoveToFirst(ChunkPos.asLong(chunkX >> REGION_SHIFT, chunkZ >> REGION_SHIFT)); } @Override - public synchronized final RegionFile moonrise$getRegionFileIfExists(final int chunkX, final int chunkZ) throws IOException { -+ public synchronized final org.stupidcraft.linearpaper.region.IRegionFile moonrise$getRegionFileIfExists(final int chunkX, final int chunkZ) throws IOException { // DivineMC - Linear region file format ++ public synchronized final org.bxteam.divinemc.region.IRegionFile moonrise$getRegionFileIfExists(final int chunkX, final int chunkZ) throws IOException { // DivineMC - Linear region file format final long key = ChunkPos.asLong(chunkX >> REGION_SHIFT, chunkZ >> REGION_SHIFT); - RegionFile ret = this.regionCache.getAndMoveToFirst(key); -+ org.stupidcraft.linearpaper.region.IRegionFile ret = this.regionCache.getAndMoveToFirst(key); // DivineMC - Linear region file format ++ org.bxteam.divinemc.region.IRegionFile ret = this.regionCache.getAndMoveToFirst(key); // DivineMC - Linear region file format if (ret != null) { return ret; } @@ -200,7 +200,7 @@ index 16cd10ab8de69ca3d29c84cf93715645322fd72a..1061d1480af0d4947d0f7f1b0028b419 FileUtil.createDirectoriesSafe(this.folder); - ret = new RegionFile(this.info, regionPath, this.folder, this.sync); -+ ret = org.stupidcraft.linearpaper.region.IRegionFileFactory.getAbstractRegionFile(this.info, regionPath, this.folder, this.sync); // DivineMC - Linear region file format ++ ret = org.bxteam.divinemc.region.RegionFileFactory.getAbstractRegionFile(this.info, regionPath, this.folder, this.sync); // DivineMC - Linear region file format this.regionCache.putAndMoveToFirst(key, ret); @@ -209,7 +209,7 @@ index 16cd10ab8de69ca3d29c84cf93715645322fd72a..1061d1480af0d4947d0f7f1b0028b419 final ChunkPos pos = new ChunkPos(chunkX, chunkZ); - final RegionFile regionFile = this.getRegionFile(pos); -+ final org.stupidcraft.linearpaper.region.IRegionFile regionFile = this.getRegionFile(pos); // DivineMC - Linear region file format ++ final org.bxteam.divinemc.region.IRegionFile regionFile = this.getRegionFile(pos); // DivineMC - Linear region file format // note: not required to keep regionfile loaded after this call, as the write param takes a regionfile as input // (and, the regionfile parameter is unused for writing until the write call) @@ -223,7 +223,7 @@ index 16cd10ab8de69ca3d29c84cf93715645322fd72a..1061d1480af0d4947d0f7f1b0028b419 final ChunkPos pos = new ChunkPos(chunkX, chunkZ); if (writeData.result() == ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.WriteData.WriteResult.DELETE) { - final RegionFile regionFile = this.moonrise$getRegionFileIfExists(chunkX, chunkZ); -+ final org.stupidcraft.linearpaper.region.IRegionFile regionFile = this.moonrise$getRegionFileIfExists(chunkX, chunkZ); // DivineMC - Linear region file format ++ final org.bxteam.divinemc.region.IRegionFile regionFile = this.moonrise$getRegionFileIfExists(chunkX, chunkZ); // DivineMC - Linear region file format if (regionFile != null) { regionFile.clear(pos); } // else: didn't exist @@ -232,7 +232,7 @@ index 16cd10ab8de69ca3d29c84cf93715645322fd72a..1061d1480af0d4947d0f7f1b0028b419 final int chunkX, final int chunkZ ) throws IOException { - final RegionFile regionFile = this.moonrise$getRegionFileIfExists(chunkX, chunkZ); -+ final org.stupidcraft.linearpaper.region.IRegionFile regionFile = this.moonrise$getRegionFileIfExists(chunkX, chunkZ); // DivineMC - Linear region file format ++ final org.bxteam.divinemc.region.IRegionFile regionFile = this.moonrise$getRegionFileIfExists(chunkX, chunkZ); // DivineMC - Linear region file format final DataInputStream input = regionFile == null ? null : regionFile.getChunkDataInputStream(new ChunkPos(chunkX, chunkZ)); @@ -241,7 +241,7 @@ index 16cd10ab8de69ca3d29c84cf93715645322fd72a..1061d1480af0d4947d0f7f1b0028b419 // Paper end - rewrite chunk system // Paper start - rewrite chunk system - public RegionFile getRegionFile(ChunkPos chunkcoordintpair) throws IOException { -+ public org.stupidcraft.linearpaper.region.IRegionFile getRegionFile(ChunkPos chunkcoordintpair) throws IOException { // DivineMC - Linear region file format ++ public org.bxteam.divinemc.region.IRegionFile getRegionFile(ChunkPos chunkcoordintpair) throws IOException { // DivineMC - Linear region file format return this.getRegionFile(chunkcoordintpair, false); } // Paper end - rewrite chunk system @@ -250,7 +250,7 @@ index 16cd10ab8de69ca3d29c84cf93715645322fd72a..1061d1480af0d4947d0f7f1b0028b419 } - @org.jetbrains.annotations.Contract("_, false -> !null") @Nullable private RegionFile getRegionFile(ChunkPos chunkPos, boolean existingOnly) throws IOException { // CraftBukkit -+ @org.jetbrains.annotations.Contract("_, false -> !null") @Nullable private org.stupidcraft.linearpaper.region.IRegionFile getRegionFile(ChunkPos chunkPos, boolean existingOnly) throws IOException { // CraftBukkit // DivineMC - Linear region file format ++ @org.jetbrains.annotations.Contract("_, false -> !null") @Nullable private org.bxteam.divinemc.region.IRegionFile getRegionFile(ChunkPos chunkPos, boolean existingOnly) throws IOException { // CraftBukkit // DivineMC - Linear region file format // Paper start - rewrite chunk system if (existingOnly) { return this.moonrise$getRegionFileIfExists(chunkPos.x, chunkPos.z); @@ -259,7 +259,7 @@ index 16cd10ab8de69ca3d29c84cf93715645322fd72a..1061d1480af0d4947d0f7f1b0028b419 final long key = ChunkPos.asLong(chunkPos.x >> REGION_SHIFT, chunkPos.z >> REGION_SHIFT); - RegionFile ret = this.regionCache.getAndMoveToFirst(key); -+ org.stupidcraft.linearpaper.region.IRegionFile ret = this.regionCache.getAndMoveToFirst(key); // DivineMC - Linear region file format ++ org.bxteam.divinemc.region.IRegionFile ret = this.regionCache.getAndMoveToFirst(key); // DivineMC - Linear region file format if (ret != null) { return ret; } @@ -268,7 +268,7 @@ index 16cd10ab8de69ca3d29c84cf93715645322fd72a..1061d1480af0d4947d0f7f1b0028b419 FileUtil.createDirectoriesSafe(this.folder); - ret = new RegionFile(this.info, regionPath, this.folder, this.sync); -+ ret = org.stupidcraft.linearpaper.region.IRegionFileFactory.getAbstractRegionFile(this.info, regionPath, this.folder, this.sync); // DivineMC - Linear region file format ++ ret = org.bxteam.divinemc.region.RegionFileFactory.getAbstractRegionFile(this.info, regionPath, this.folder, this.sync); // DivineMC - Linear region file format this.regionCache.putAndMoveToFirst(key, ret); @@ -277,7 +277,7 @@ index 16cd10ab8de69ca3d29c84cf93715645322fd72a..1061d1480af0d4947d0f7f1b0028b419 } - private static CompoundTag readOversizedChunk(RegionFile regionfile, ChunkPos chunkCoordinate) throws IOException { -+ private static CompoundTag readOversizedChunk(org.stupidcraft.linearpaper.region.IRegionFile regionfile, ChunkPos chunkCoordinate) throws IOException { // DivineMC - Linear region file format ++ private static CompoundTag readOversizedChunk(org.bxteam.divinemc.region.IRegionFile regionfile, ChunkPos chunkCoordinate) throws IOException { // DivineMC - Linear region file format synchronized (regionfile) { try (DataInputStream datainputstream = regionfile.getChunkDataInputStream(chunkCoordinate)) { CompoundTag oversizedData = regionfile.getOversizedData(chunkCoordinate.x, chunkCoordinate.z); @@ -286,7 +286,7 @@ index 16cd10ab8de69ca3d29c84cf93715645322fd72a..1061d1480af0d4947d0f7f1b0028b419 public CompoundTag read(ChunkPos chunkPos) throws IOException { // CraftBukkit start - SPIGOT-5680: There's no good reason to preemptively create files on read, save that for writing - RegionFile regionFile = this.getRegionFile(chunkPos, true); -+ org.stupidcraft.linearpaper.region.IRegionFile regionFile = this.getRegionFile(chunkPos, true); // DivineMC - Linear region file format ++ org.bxteam.divinemc.region.IRegionFile regionFile = this.getRegionFile(chunkPos, true); // DivineMC - Linear region file format if (regionFile == null) { return null; } @@ -295,7 +295,7 @@ index 16cd10ab8de69ca3d29c84cf93715645322fd72a..1061d1480af0d4947d0f7f1b0028b419 public void scanChunk(ChunkPos chunkPos, StreamTagVisitor visitor) throws IOException { // CraftBukkit start - SPIGOT-5680: There's no good reason to preemptively create files on read, save that for writing - RegionFile regionFile = this.getRegionFile(chunkPos, true); -+ org.stupidcraft.linearpaper.region.IRegionFile regionFile = this.getRegionFile(chunkPos, true); // DivineMC - Linear region file format ++ org.bxteam.divinemc.region.IRegionFile regionFile = this.getRegionFile(chunkPos, true); // DivineMC - Linear region file format if (regionFile == null) { return; } @@ -304,7 +304,7 @@ index 16cd10ab8de69ca3d29c84cf93715645322fd72a..1061d1480af0d4947d0f7f1b0028b419 public void write(ChunkPos chunkPos, @Nullable CompoundTag chunkData) throws IOException { // Paper - rewrite chunk system - public - RegionFile regionFile = this.getRegionFile(chunkPos, chunkData == null); // CraftBukkit // Paper - rewrite chunk system -+ org.stupidcraft.linearpaper.region.IRegionFile regionFile = this.getRegionFile(chunkPos, chunkData == null); // CraftBukkit // Paper - rewrite chunk system // DivineMC - Linear region file format ++ org.bxteam.divinemc.region.IRegionFile regionFile = this.getRegionFile(chunkPos, chunkData == null); // CraftBukkit // Paper - rewrite chunk system // DivineMC - Linear region file format // Paper start - rewrite chunk system if (regionFile == null) { // if the RegionFile doesn't exist, no point in deleting from it @@ -313,7 +313,7 @@ index 16cd10ab8de69ca3d29c84cf93715645322fd72a..1061d1480af0d4947d0f7f1b0028b419 synchronized (this) { final ExceptionCollector exceptionCollector = new ExceptionCollector<>(); - for (final RegionFile regionFile : this.regionCache.values()) { -+ for (final org.stupidcraft.linearpaper.region.IRegionFile regionFile : this.regionCache.values()) { // DivineMC - Linear region file format ++ for (final org.bxteam.divinemc.region.IRegionFile regionFile : this.regionCache.values()) { // DivineMC - Linear region file format try { regionFile.close(); } catch (final IOException ex) { @@ -322,7 +322,7 @@ index 16cd10ab8de69ca3d29c84cf93715645322fd72a..1061d1480af0d4947d0f7f1b0028b419 synchronized (this) { final ExceptionCollector exceptionCollector = new ExceptionCollector<>(); - for (final RegionFile regionFile : this.regionCache.values()) { -+ for (final org.stupidcraft.linearpaper.region.IRegionFile regionFile : this.regionCache.values()) { // DivineMC - Linear region file format ++ for (final org.bxteam.divinemc.region.IRegionFile regionFile : this.regionCache.values()) { // DivineMC - Linear region file format try { regionFile.flush(); } catch (final IOException ex) { diff --git a/divinemc-server/minecraft-patches/features/0044-Async-mob-spawning.patch b/divinemc-server/minecraft-patches/features/0040-Async-mob-spawning.patch similarity index 96% rename from divinemc-server/minecraft-patches/features/0044-Async-mob-spawning.patch rename to divinemc-server/minecraft-patches/features/0040-Async-mob-spawning.patch index f64855c..ccd9bae 100644 --- a/divinemc-server/minecraft-patches/features/0044-Async-mob-spawning.patch +++ b/divinemc-server/minecraft-patches/features/0040-Async-mob-spawning.patch @@ -5,11 +5,11 @@ Subject: [PATCH] Async mob spawning diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java -index 6182c6f7cc6f5199897a5e227dabe9f9738733a5..272a87eaa7a6406d0b059c18d7a7aa8c945dffa0 100644 +index 51b79f614417f231951e9ba05b29ff0044e081e7..dee93ae262a2a06e68dfe8ae1b7193172c436990 100644 --- a/net/minecraft/server/MinecraftServer.java +++ b/net/minecraft/server/MinecraftServer.java @@ -291,6 +291,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop entitiesWithScheduledTasks = java.util.concurrent.ConcurrentHashMap.newKeySet(); // DivineMC - Skip EntityScheduler's executeTick checks if there isn't any tasks to be run + public org.bxteam.divinemc.util.AsyncProcessor mobSpawnExecutor = new org.bxteam.divinemc.util.AsyncProcessor("mob_spawning"); // DivineMC - Async mob spawning @@ -173,7 +173,7 @@ index d3f5242fc66529bf3137da4d505a6cf55e749e43..650dfce05bfc68d4c664471b430bd5c0 } } diff --git a/net/minecraft/world/level/entity/EntityTickList.java b/net/minecraft/world/level/entity/EntityTickList.java -index 3c6ec711bf9a75657c13da647e4ae7947257b627..564a00938ef45837b1f8fa90504c54a6dc9bb383 100644 +index 018a04674897cfcec0e8de5cb2ab06243a994ae3..8c1de4654a3a29e75716a03efd476b8a3b7fe9e9 100644 --- a/net/minecraft/world/level/entity/EntityTickList.java +++ b/net/minecraft/world/level/entity/EntityTickList.java @@ -9,7 +9,7 @@ import javax.annotation.Nullable; @@ -182,10 +182,10 @@ index 3c6ec711bf9a75657c13da647e4ae7947257b627..564a00938ef45837b1f8fa90504c54a6 public class EntityTickList { - public final ca.spottedleaf.moonrise.common.list.IteratorSafeOrderedReferenceSet entities = new ca.spottedleaf.moonrise.common.list.IteratorSafeOrderedReferenceSet<>(); // Paper - rewrite chunk system + public final java.util.concurrent.ConcurrentLinkedQueue entities = new java.util.concurrent.ConcurrentLinkedQueue<>(); // Paper - rewrite chunk system // DivineMC - Async mob spawning - // DivineMC start - parallel world ticking - // Used to track async entity additions/removals/loops + // DivineMC start - Parallel world ticking private final net.minecraft.server.level.ServerLevel serverLevel; -@@ -44,13 +44,13 @@ public class EntityTickList { + +@@ -43,13 +43,13 @@ public class EntityTickList { // Paper start - rewrite chunk system // To ensure nothing weird happens with dimension travelling, do not iterate over new entries... // (by dfl iterator() is configured to not iterate over new entries) diff --git a/divinemc-server/minecraft-patches/features/0045-Dynamic-Activation-of-Brain.patch b/divinemc-server/minecraft-patches/features/0041-Dynamic-Activation-of-Brain.patch similarity index 98% rename from divinemc-server/minecraft-patches/features/0045-Dynamic-Activation-of-Brain.patch rename to divinemc-server/minecraft-patches/features/0041-Dynamic-Activation-of-Brain.patch index c2e49b8..48e1f64 100644 --- a/divinemc-server/minecraft-patches/features/0045-Dynamic-Activation-of-Brain.patch +++ b/divinemc-server/minecraft-patches/features/0041-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 fdaf752b4d39402de504cc8dfb6f0593f9b19d9a..74d6cf9b0fa0e47d5c94a16ce54f32810afb2da5 100644 +index 561066a2cf769e13ef3cea0881f7a2010ecbf2ec..5fe908ce51f95e1eab024dcd41ed108373f17fea 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 -> { @@ -43,7 +43,7 @@ index fdaf752b4d39402de504cc8dfb6f0593f9b19d9a..74d6cf9b0fa0e47d5c94a16ce54f3281 if (!tickRateManager.isEntityFrozen(entity)) { entity.checkDespawn(); diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java -index 96775eadaac9afb600b5c1b553db40970d9d7a3d..b6becf5e14abddcb60b1f4c4babecfe81cf5c09c 100644 +index 04ae7636d14a40a427b5d9b746632b0c489efa21..f1cd66d7d96771bc4967e214f70c756fec30efe5 100644 --- a/net/minecraft/world/entity/Entity.java +++ b/net/minecraft/world/entity/Entity.java @@ -336,6 +336,8 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess diff --git a/divinemc-server/minecraft-patches/features/0046-Configurable-movement-speed-for-entities.patch b/divinemc-server/minecraft-patches/features/0042-Configurable-movement-speed-for-entities.patch similarity index 100% rename from divinemc-server/minecraft-patches/features/0046-Configurable-movement-speed-for-entities.patch rename to divinemc-server/minecraft-patches/features/0042-Configurable-movement-speed-for-entities.patch diff --git a/divinemc-server/minecraft-patches/features/0047-Paper-PR-Add-FillBottleEvents-for-player-and-dispens.patch b/divinemc-server/minecraft-patches/features/0043-Paper-PR-Add-FillBottleEvents-for-player-and-dispens.patch similarity index 100% rename from divinemc-server/minecraft-patches/features/0047-Paper-PR-Add-FillBottleEvents-for-player-and-dispens.patch rename to divinemc-server/minecraft-patches/features/0043-Paper-PR-Add-FillBottleEvents-for-player-and-dispens.patch diff --git a/divinemc-server/minecraft-patches/features/0048-Leaf-Improve-BlockEntity-ticking-isRemoved-check.patch b/divinemc-server/minecraft-patches/features/0044-Leaf-Improve-BlockEntity-ticking-isRemoved-check.patch similarity index 100% rename from divinemc-server/minecraft-patches/features/0048-Leaf-Improve-BlockEntity-ticking-isRemoved-check.patch rename to divinemc-server/minecraft-patches/features/0044-Leaf-Improve-BlockEntity-ticking-isRemoved-check.patch diff --git a/divinemc-server/minecraft-patches/features/0045-Paper-PR-Throttle-failed-spawn-attempts.patch b/divinemc-server/minecraft-patches/features/0045-Paper-PR-Throttle-failed-spawn-attempts.patch new file mode 100644 index 0000000..7adef18 --- /dev/null +++ b/divinemc-server/minecraft-patches/features/0045-Paper-PR-Throttle-failed-spawn-attempts.patch @@ -0,0 +1,331 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Thu, 27 Mar 2025 00:04:19 +0300 +Subject: [PATCH] Paper PR: Throttle failed spawn attempts + +Original license: GPLv3 +Original project: https://github.com/PaperMC/Paper +Paper pull request: https://github.com/PaperMC/Paper/pull/11099 + +Example config in paper-world-defaults.yml: +``` + spawning-throttle: + failed-attempts-threshold: 1200 + throttled-ticks-per-spawn: + ambient: 10 # default value in bukkit.yml tickers-per * 10 + axolotls: 10 + creature: 4000 + monster: 10 + underground_water_creature: 10 + water_ambient: 10 + water_creature: 10 +``` + +diff --git a/net/minecraft/world/level/NaturalSpawner.java b/net/minecraft/world/level/NaturalSpawner.java +index 650dfce05bfc68d4c664471b430bd5c0f9629283..3e9ab446632ffe56de45f7622db44070e1cbaf1f 100644 +--- a/net/minecraft/world/level/NaturalSpawner.java ++++ b/net/minecraft/world/level/NaturalSpawner.java +@@ -175,29 +175,52 @@ public final class NaturalSpawner { + // Copied from getFilteredSpawningCategories + int limit = mobCategory.getMaxInstancesPerChunk(); + SpawnCategory spawnCategory = CraftSpawnCategory.toBukkit(mobCategory); ++ // Paper start - throttle failed spawn attempts ++ boolean spawnThisTick = true; ++ long ticksPerSpawn = level.ticksPerSpawnCategory.getLong(spawnCategory); ++ long ticksPerSpawnTmp = ticksPerSpawn; ++ io.papermc.paper.configuration.WorldConfiguration.Entities.Spawning.SpawningThrottle spawningThrottle = level.paperConfig().entities.spawning.spawningThrottle; ++ if (spawningThrottle.failedAttemptsThreshold.test(threshold -> chunk.failedSpawnAttempts[mobCategory.ordinal()] > threshold)) { ++ ticksPerSpawn = Math.max(ticksPerSpawn, spawningThrottle.throttledTicksPerSpawn.getOrDefault(mobCategory, -1)); ++ } ++ // Paper end - throttle failed spawn attempts + if (CraftSpawnCategory.isValidForLimits(spawnCategory)) { ++ spawnThisTick = ticksPerSpawnTmp != 0 && level.getGameTime() % ticksPerSpawn == 0; // Paper - throttle failed spawn attempts + limit = level.getWorld().getSpawnLimit(spawnCategory); + } + +- // Apply per-player limit +- int minDiff = Integer.MAX_VALUE; +- final ca.spottedleaf.moonrise.common.list.ReferenceList inRange = +- level.moonrise$getNearbyPlayers().getPlayers(chunk.getPos(), ca.spottedleaf.moonrise.common.misc.NearbyPlayers.NearbyMapType.TICK_VIEW_DISTANCE); +- if (inRange != null) { +- final net.minecraft.server.level.ServerPlayer[] backingSet = inRange.getRawDataUnchecked(); +- for (int k = 0, len = inRange.size(); k < len; k++) { +- minDiff = Math.min(limit - level.getChunkSource().chunkMap.getMobCountNear(backingSet[k], mobCategory), minDiff); ++ // Paper start - throttle failed spawn attempts ++ if (!spawningThrottle.failedAttemptsThreshold.enabled() || spawnThisTick) { ++ // Apply per-player limit ++ int minDiff = Integer.MAX_VALUE; ++ final ca.spottedleaf.moonrise.common.list.ReferenceList inRange = ++ level.moonrise$getNearbyPlayers().getPlayers(chunk.getPos(), ca.spottedleaf.moonrise.common.misc.NearbyPlayers.NearbyMapType.TICK_VIEW_DISTANCE); ++ if (inRange != null) { ++ final net.minecraft.server.level.ServerPlayer[] backingSet = inRange.getRawDataUnchecked(); ++ for (int k = 0, len = inRange.size(); k < len; k++) { ++ minDiff = Math.min(limit - level.getChunkSource().chunkMap.getMobCountNear(backingSet[k], mobCategory), minDiff); ++ } + } +- } + +- maxSpawns = (minDiff == Integer.MAX_VALUE) ? 0 : minDiff; +- canSpawn = maxSpawns > 0; ++ maxSpawns = (minDiff == Integer.MAX_VALUE) ? 0 : minDiff; ++ canSpawn = maxSpawns > 0; ++ } else { ++ canSpawn = false; ++ } ++ // Paper end - throttle failed spawn attempts + } else { + canSpawn = spawnState.canSpawnForCategoryLocal(mobCategory, chunk.getPos()); + } + if (canSpawn) { +- spawnCategoryForChunk(mobCategory, level, chunk, spawnState::canSpawn, spawnState::afterSpawn, +- maxSpawns, level.paperConfig().entities.spawning.perPlayerMobSpawns ? level.getChunkSource().chunkMap::updatePlayerMobTypeMap : null); ++ // Paper start - throttle failed spawn attempts ++ int spawnCount = spawnCategoryForChunk(mobCategory, level, chunk, spawnState::canSpawn, spawnState::afterSpawn, ++ maxSpawns, level.paperConfig().entities.spawning.perPlayerMobSpawns ? level.getChunkSource().chunkMap::updatePlayerMobTypeMap : null, false); ++ if (spawnCount == 0) { ++ chunk.failedSpawnAttempts[mobCategory.ordinal()]++; ++ } else { ++ chunk.failedSpawnAttempts[mobCategory.ordinal()] = 0; ++ } ++ // Paper end - throttle failed spawn attempts + // Paper end - Optional per player mob spawns + } + } +@@ -221,12 +244,21 @@ public final class NaturalSpawner { + } + public static void spawnCategoryForChunk( + MobCategory category, ServerLevel level, LevelChunk chunk, NaturalSpawner.SpawnPredicate filter, NaturalSpawner.AfterSpawnCallback callback, final int maxSpawns, final Consumer trackEntity ++ // Paper start - throttle failed spawn attempts ++ ) { ++ spawnCategoryForChunk(category, level, chunk, filter, callback, maxSpawns, trackEntity, false); ++ } ++ public static int spawnCategoryForChunk( ++ MobCategory category, ServerLevel level, LevelChunk chunk, NaturalSpawner.SpawnPredicate filter, NaturalSpawner.AfterSpawnCallback callback, final int maxSpawns, final Consumer trackEntity, final boolean nothing ++ // Paper end - throttle failed spawn attempts + ) { + // Paper end - Optional per player mob spawns + BlockPos randomPosWithin = getRandomPosWithin(level, chunk); + if (randomPosWithin.getY() >= level.getMinY() + 1) { +- spawnCategoryForPosition(category, level, chunk, randomPosWithin, filter, callback, maxSpawns, trackEntity); // Paper - Optional per player mob spawns ++ return spawnCategoryForPosition(category, level, chunk, randomPosWithin, filter, callback, maxSpawns, trackEntity, false); // Paper - Optional per player mob spawns // Paper - throttle failed spawn attempts + } ++ ++ return 0; // Paper - throttle failed spawn attempts + } + + @VisibleForDebug +@@ -246,16 +278,22 @@ public final class NaturalSpawner { + } + public static void spawnCategoryForPosition( + MobCategory category, ServerLevel level, ChunkAccess chunk, BlockPos pos, NaturalSpawner.SpawnPredicate filter, NaturalSpawner.AfterSpawnCallback callback, final int maxSpawns, final @Nullable Consumer trackEntity ++ // Paper start - throttle failed spawn attempts ++ ) { ++ spawnCategoryForPosition(category, level, chunk, pos, filter, callback, maxSpawns, trackEntity, false); ++ } ++ public static int spawnCategoryForPosition( ++ MobCategory category, ServerLevel level, ChunkAccess chunk, BlockPos pos, NaturalSpawner.SpawnPredicate filter, NaturalSpawner.AfterSpawnCallback callback, final int maxSpawns, final @Nullable Consumer trackEntity, final boolean nothing ++ // Paper end - throttle failed spawn attempts + ) { + // Paper end - Optional per player mob spawns + StructureManager structureManager = level.structureManager(); + ChunkGenerator generator = level.getChunkSource().getGenerator(); + int y = pos.getY(); ++ int i = 0; // Paper - throttle failed spawn attempts + BlockState blockState = level.getBlockStateIfLoadedAndInBounds(pos); // Paper - don't load chunks for mob spawn + if (blockState != null && !blockState.isRedstoneConductor(chunk, pos)) { // Paper - don't load chunks for mob spawn + BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos(); +- int i = 0; +- + for (int i1 = 0; i1 < 3; i1++) { + int x = pos.getX(); + int z = pos.getZ(); +@@ -295,13 +333,13 @@ public final class NaturalSpawner { + } + // Paper end - per player mob count backoff + if (doSpawning == PreSpawnStatus.ABORT) { +- return; ++ return i; // Paper - throttle failed spawn attempts + } + if (doSpawning == PreSpawnStatus.SUCCESS && filter.test(spawnerData.type, mutableBlockPos, chunk)) { + // Paper end - PreCreatureSpawnEvent + Mob mobForSpawn = getMobForSpawn(level, spawnerData.type); + if (mobForSpawn == null) { +- return; ++ return i; // Paper - throttle failed spawn attempts + } + + mobForSpawn.moveTo(d, y, d1, level.random.nextFloat() * 360.0F, 0.0F); +@@ -324,7 +362,7 @@ public final class NaturalSpawner { + } + // CraftBukkit end + if (i >= mobForSpawn.getMaxSpawnClusterSize() || i >= maxSpawns) { // Paper - Optional per player mob spawns +- return; ++ return i; // Paper - throttle failed spawn attempts + } + + if (mobForSpawn.isMaxGroupSizeReached(i3)) { +@@ -337,6 +375,8 @@ public final class NaturalSpawner { + } + } + } ++ ++ return i; // Paper - throttle failed spawn attempts + } + + private static boolean isRightDistanceToPlayerAndSpawnPoint(ServerLevel level, ChunkAccess chunk, BlockPos.MutableBlockPos pos, double distance) { +diff --git a/net/minecraft/world/level/chunk/ChunkAccess.java b/net/minecraft/world/level/chunk/ChunkAccess.java +index 80a0f5524e91e55d716e93c29e199d9816b0072a..3ba674ca0656d42b7d9e445cf25166253bf11f2e 100644 +--- a/net/minecraft/world/level/chunk/ChunkAccess.java ++++ b/net/minecraft/world/level/chunk/ChunkAccess.java +@@ -91,6 +91,7 @@ public abstract class ChunkAccess implements BiomeManager.NoiseBiomeSource, Ligh + public org.bukkit.craftbukkit.persistence.DirtyCraftPersistentDataContainer persistentDataContainer = new org.bukkit.craftbukkit.persistence.DirtyCraftPersistentDataContainer(ChunkAccess.DATA_TYPE_REGISTRY); + // CraftBukkit end + public final Registry biomeRegistry; // CraftBukkit ++ public final long[] failedSpawnAttempts = new long[net.minecraft.world.entity.MobCategory.values().length]; // Paper - throttle failed spawn attempts + + // Paper start - rewrite chunk system + private volatile ca.spottedleaf.moonrise.patches.starlight.light.SWMRNibbleArray[] blockNibbles; +diff --git a/net/minecraft/world/level/chunk/storage/SerializableChunkData.java b/net/minecraft/world/level/chunk/storage/SerializableChunkData.java +index 6b6aaeca14178b5b709e20ae13552d42217f15c0..56ed001b8ce9273bdc7afd8228f69e69e71f45ff 100644 +--- a/net/minecraft/world/level/chunk/storage/SerializableChunkData.java ++++ b/net/minecraft/world/level/chunk/storage/SerializableChunkData.java +@@ -92,6 +92,7 @@ public record SerializableChunkData( + List blockEntities, + CompoundTag structureData + , @Nullable net.minecraft.nbt.Tag persistentDataContainer // CraftBukkit - persistentDataContainer ++ , @Nullable long[] failedSpawnAttempts // Paper - throttle failed spawn attempts + ) { + public static final Codec> BLOCK_STATE_CODEC = PalettedContainer.codecRW( + Block.BLOCK_STATE_REGISTRY, BlockState.CODEC, PalettedContainer.Strategy.SECTION_STATES, Blocks.AIR.defaultBlockState(), null // Paper - Anti-Xray +@@ -216,6 +217,19 @@ public record SerializableChunkData( + lists[i] = list4; + } + ++ // Paper start - throttle failed spawn attempts ++ long[] failedSpawnAttemptsData = null; ++ if (tag.contains("Paper.FailedSpawnAttempts", net.minecraft.nbt.Tag.TAG_COMPOUND)) { ++ failedSpawnAttemptsData = new long[net.minecraft.world.entity.MobCategory.values().length]; ++ CompoundTag failedSpawnAttemptsTag = tag.getCompound("Paper.FailedSpawnAttempts"); ++ for (net.minecraft.world.entity.MobCategory mobCategory : net.minecraft.world.level.NaturalSpawner.SPAWNING_CATEGORIES) { ++ if (failedSpawnAttemptsTag.contains(mobCategory.getSerializedName(), net.minecraft.nbt.Tag.TAG_LONG)) { ++ failedSpawnAttemptsData[mobCategory.ordinal()] = failedSpawnAttemptsTag.getLong(mobCategory.getSerializedName()); ++ } ++ } ++ } ++ // Paper end - throttle failed spawn attempts ++ + List list5 = Lists.transform(tag.getList("entities", 10), tag1 -> (CompoundTag)tag1); + List list6 = Lists.transform(tag.getList("block_entities", 10), tag1 -> (CompoundTag)tag1); + CompoundTag compound1 = tag.getCompound("structures"); +@@ -294,6 +308,7 @@ public record SerializableChunkData( + list6, + compound1 + , tag.get("ChunkBukkitValues") // CraftBukkit - ChunkBukkitValues ++ , failedSpawnAttemptsData // Paper - throttle failed spawn attempts + ); + } + } +@@ -450,6 +465,15 @@ public record SerializableChunkData( + chunkAccess.addPackedPostProcess(this.postProcessingSections[i], i); + } + ++ // Paper start - throttle failed spawn attempts ++ long[] failedSpawnAttemptsData = this.failedSpawnAttempts; ++ if (failedSpawnAttemptsData != null) { ++ for (net.minecraft.world.entity.MobCategory mobCategory : net.minecraft.world.entity.MobCategory.values()) { ++ System.arraycopy(failedSpawnAttemptsData, 0, chunkAccess.failedSpawnAttempts, 0, failedSpawnAttemptsData.length); ++ } ++ } ++ // Paper end - throttle failed spawn attempts ++ + if (chunkType == ChunkType.LEVELCHUNK) { + return this.loadStarlightLightData(level, new ImposterProtoChunk((LevelChunk)chunkAccess, false)); // Paper - starlight + } else { +@@ -587,6 +611,7 @@ public record SerializableChunkData( + persistentDataContainer = chunk.persistentDataContainer.toTagCompound(); + } + // CraftBukkit end ++ final long[] failedSpawnAttemptsData = chunk.failedSpawnAttempts; // Paper - throttle failed spawn attempts + return new SerializableChunkData( + level.registryAccess().lookupOrThrow(Registries.BIOME), + pos, +@@ -607,6 +632,7 @@ public record SerializableChunkData( + list1, + compoundTag + , persistentDataContainer // CraftBukkit - persistentDataContainer ++ , failedSpawnAttemptsData // Paper - throttle failed spawn attempts + ); + } + } +@@ -703,6 +729,21 @@ public record SerializableChunkData( + compoundTag.put("ChunkBukkitValues", this.persistentDataContainer); + } + // CraftBukkit end ++ // Paper start - throttle failed spawn attempts ++ CompoundTag failedSpawnAttemptsTag = new CompoundTag(); ++ long[] failedSpawnAttemptsData = this.failedSpawnAttempts; ++ if (failedSpawnAttemptsData != null) { ++ for (net.minecraft.world.entity.MobCategory mobCategory : net.minecraft.world.entity.MobCategory.values()) { ++ long failedAttempts = failedSpawnAttemptsData[mobCategory.ordinal()]; ++ if (failedAttempts > 0) { ++ failedSpawnAttemptsTag.putLong(mobCategory.getSerializedName(), failedAttempts); ++ } ++ } ++ } ++ if (!failedSpawnAttemptsTag.isEmpty()) { ++ compoundTag.put("Paper.FailedSpawnAttempts", failedSpawnAttemptsTag); ++ } ++ // Paper end - throttle failed spawn attempts + // Paper start - starlight + if (this.lightCorrect && !this.chunkStatus.isBefore(net.minecraft.world.level.chunk.status.ChunkStatus.LIGHT)) { + // clobber vanilla value to force vanilla to relight +@@ -931,4 +972,49 @@ public record SerializableChunkData( + } + // Paper end - starlight - convert from record + } ++ ++ // Paper start - throttle failed spawn attempts - for plugin compatibility ++ public SerializableChunkData( ++ Registry biomeRegistry, ++ ChunkPos chunkPos, ++ int minSectionY, ++ long lastUpdateTime, ++ long inhabitedTime, ++ ChunkStatus chunkStatus, ++ @Nullable BlendingData.Packed blendingData, ++ @Nullable BelowZeroRetrogen belowZeroRetrogen, ++ UpgradeData upgradeData, ++ @Nullable long[] carvingMask, ++ Map heightmaps, ++ ChunkAccess.PackedTicks packedTicks, ++ ShortList[] postProcessingSections, ++ boolean lightCorrect, ++ List sectionData, ++ List entities, ++ List blockEntities, ++ CompoundTag structureData, ++ @Nullable net.minecraft.nbt.Tag persistentDataContainer // CraftBukkit - persistentDataContainer ++ ) { ++ this(biomeRegistry, ++ chunkPos, ++ minSectionY, ++ lastUpdateTime, ++ inhabitedTime, ++ chunkStatus, ++ blendingData, ++ belowZeroRetrogen, ++ upgradeData, ++ carvingMask, ++ heightmaps, ++ packedTicks, ++ postProcessingSections, ++ lightCorrect, ++ sectionData, ++ entities, ++ blockEntities, ++ structureData, ++ persistentDataContainer, ++ null); ++ } ++ // Paper end - throttle failed spawn attempts + } diff --git a/divinemc-server/minecraft-patches/features/0046-Optimize-Raids.patch b/divinemc-server/minecraft-patches/features/0046-Optimize-Raids.patch new file mode 100644 index 0000000..c380052 --- /dev/null +++ b/divinemc-server/minecraft-patches/features/0046-Optimize-Raids.patch @@ -0,0 +1,137 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Sun, 6 Apr 2025 20:53:48 +0300 +Subject: [PATCH] Optimize Raids + + +diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java +index 92e35158b68dcb8d1f34fb1b748c12d1d39468c7..c87d1f81bd1b4b9756bd2f4c1dbc58a2dc85c63b 100644 +--- a/net/minecraft/server/level/ServerLevel.java ++++ b/net/minecraft/server/level/ServerLevel.java +@@ -219,6 +219,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + private final alternate.current.wire.WireHandler wireHandler = new alternate.current.wire.WireHandler(this); // Paper - optimize redstone (Alternate Current) + public boolean hasRidableMoveEvent = false; // Purpur - Ridables + public org.bxteam.divinemc.util.tps.TPSCalculator tpsCalculator = new org.bxteam.divinemc.util.tps.TPSCalculator(); // DivineMC - Lag Compensation ++ public net.minecraft.world.item.ItemStack ominousBanner; // DivineMC - Optimize Raids + + public LevelChunk getChunkIfLoaded(int x, int z) { + return this.chunkSource.getChunkAtIfLoadedImmediately(x, z); // Paper - Use getChunkIfLoadedImmediately +@@ -714,6 +715,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + 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 ++ this.ominousBanner = Objects.requireNonNullElse(this.registryAccess(), net.minecraft.core.RegistryAccess.EMPTY).lookup(Registries.BANNER_PATTERN).map(Raid::getOminousBannerInstance).orElse(null); // DivineMC - Optimize Raids + } + + // Paper start +diff --git a/net/minecraft/world/entity/raid/Raid.java b/net/minecraft/world/entity/raid/Raid.java +index 41b0db439b425b052bd1469daa6620a435ca852b..4e53cb7ad7c787fd7581763ae3e77c988277887c 100644 +--- a/net/minecraft/world/entity/raid/Raid.java ++++ b/net/minecraft/world/entity/raid/Raid.java +@@ -109,6 +109,7 @@ public class Raid { + private static final org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry PDC_TYPE_REGISTRY = new org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry(); + public final org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer persistentDataContainer = new org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer(PDC_TYPE_REGISTRY); + // Paper end ++ private boolean isBarDirty; // DivineMC - Optimize Raids + + public Raid(int id, ServerLevel level, BlockPos center) { + this.id = id; +@@ -263,6 +264,12 @@ public class Raid { + } + + public void tick() { ++ // DivineMC start - Optimize Raids ++ if (this.isBarDirty) { ++ this.raidEvent.setProgress(Mth.clamp(this.getHealthOfLivingRaiders() / this.totalHealth, 0.0F, 1.0F)); ++ this.isBarDirty = false; ++ } ++ // DivineMC end - Optimize Raids + if (!this.isStopped()) { + if (this.status == Raid.RaidStatus.ONGOING) { + boolean flag = this.active; +@@ -581,7 +588,7 @@ public class Raid { + } + + public void updateBossbar() { +- this.raidEvent.setProgress(Mth.clamp(this.getHealthOfLivingRaiders() / this.totalHealth, 0.0F, 1.0F)); ++ this.isBarDirty = true; // DivineMC - Optimize Raids + } + + public float getHealthOfLivingRaiders() { +diff --git a/net/minecraft/world/entity/raid/Raider.java b/net/minecraft/world/entity/raid/Raider.java +index c06b589e669b055a26f662df60070d5908256220..7f954bab6e8a1b25abcb3aa6c2d26315dacec930 100644 +--- a/net/minecraft/world/entity/raid/Raider.java ++++ b/net/minecraft/world/entity/raid/Raider.java +@@ -42,9 +42,25 @@ import net.minecraft.world.phys.Vec3; + + public abstract class Raider extends PatrollingMonster { + protected static final EntityDataAccessor IS_CELEBRATING = SynchedEntityData.defineId(Raider.class, EntityDataSerializers.BOOLEAN); +- static final Predicate ALLOWED_ITEMS = item -> !item.hasPickUpDelay() +- && item.isAlive() +- && ItemStack.matches(item.getItem(), Raid.getOminousBannerInstance(item.registryAccess().lookupOrThrow(Registries.BANNER_PATTERN))); ++ // DivineMC start - Optimize Raids ++ static final Predicate ALLOWED_ITEMS = (itemEntity) -> { ++ ItemStack ominousBanner = ((ServerLevel) itemEntity.level()).ominousBanner; ++ if (ominousBanner == null) { ++ ominousBanner = Raid.getOminousBannerInstance(itemEntity.registryAccess().lookupOrThrow(Registries.BANNER_PATTERN)); ++ } ++ ++ return !itemEntity.hasPickUpDelay() && itemEntity.isAlive() && ++ ItemStack.matches(itemEntity.getItem(), ominousBanner); ++ }; ++ ++ private ItemStack getOminousBanner(net.minecraft.core.HolderGetter bannerPatternLookup) { ++ ItemStack ominousBanner = ((ServerLevel) this.level()).ominousBanner; ++ if (ominousBanner == null) { ++ ominousBanner = Raid.getOminousBannerInstance(bannerPatternLookup); ++ } ++ return ominousBanner; ++ } ++ // DivineMC end - Optimize Raids + @Nullable + protected Raid raid; + private int wave; +@@ -147,7 +163,7 @@ public abstract class Raider extends PatrollingMonster { + public boolean isCaptain() { + ItemStack itemBySlot = this.getItemBySlot(EquipmentSlot.HEAD); + boolean flag = !itemBySlot.isEmpty() +- && ItemStack.matches(itemBySlot, Raid.getOminousBannerInstance(this.registryAccess().lookupOrThrow(Registries.BANNER_PATTERN))); ++ && ItemStack.matches(itemBySlot, getOminousBanner(this.registryAccess().lookupOrThrow(Registries.BANNER_PATTERN))); // DivineMC - Optimize Raids + boolean isPatrolLeader = this.isPatrolLeader(); + return flag && isPatrolLeader; + } +@@ -211,7 +227,7 @@ public abstract class Raider extends PatrollingMonster { + boolean flag = this.hasActiveRaid() && this.getCurrentRaid().getLeader(this.getWave()) != null; + if (this.hasActiveRaid() + && !flag +- && ItemStack.matches(item, Raid.getOminousBannerInstance(this.registryAccess().lookupOrThrow(Registries.BANNER_PATTERN)))) { ++ && ItemStack.matches(item, getOminousBanner(this.registryAccess().lookupOrThrow(Registries.BANNER_PATTERN)))) { // DivineMC - Optimize Raids + // Paper start - EntityPickupItemEvent fixes + if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPickupItemEvent(this, entity, 0, false).isCancelled()) { + return; +@@ -398,6 +414,16 @@ public abstract class Raider extends PatrollingMonster { + && !this.cannotPickUpBanner(); + } + ++ // DivineMC start - Optimize Raids ++ private ItemStack getOminousBanner(net.minecraft.core.HolderGetter bannerPatternLookup) { ++ ItemStack ominousBanner = ((ServerLevel) this.mob.level()).ominousBanner; ++ if (ominousBanner == null) { ++ ominousBanner = Raid.getOminousBannerInstance(bannerPatternLookup); ++ } ++ return ominousBanner; ++ } ++ // DivineMC end - Optimize Raids ++ + private boolean cannotPickUpBanner() { + if (!this.mob.level().purpurConfig.pillagerBypassMobGriefing == !getServerLevel(this.mob).getGameRules().getBoolean(net.minecraft.world.level.GameRules.RULE_MOBGRIEFING) || !this.mob.canPickUpLoot()) return false; // Paper - respect game and entity rules for picking up items // Purpur - Add mobGriefing bypass to everything affected + if (!this.mob.hasActiveRaid()) { +@@ -407,7 +433,7 @@ public abstract class Raider extends PatrollingMonster { + } else if (!this.mob.canBeLeader()) { + return true; + } else if (ItemStack.matches( +- this.mob.getItemBySlot(EquipmentSlot.HEAD), Raid.getOminousBannerInstance(this.mob.registryAccess().lookupOrThrow(Registries.BANNER_PATTERN)) ++ this.mob.getItemBySlot(EquipmentSlot.HEAD), getOminousBanner(this.mob.registryAccess().lookupOrThrow(Registries.BANNER_PATTERN)) // DivineMC - Optimize Raids + )) { + return true; + } else { diff --git a/divinemc-server/minecraft-patches/features/0047-SparklyPaper-Allow-throttling-hopper-checks-if-the-t.patch b/divinemc-server/minecraft-patches/features/0047-SparklyPaper-Allow-throttling-hopper-checks-if-the-t.patch new file mode 100644 index 0000000..d4445d0 --- /dev/null +++ b/divinemc-server/minecraft-patches/features/0047-SparklyPaper-Allow-throttling-hopper-checks-if-the-t.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Sat, 12 Apr 2025 17:40:53 +0300 +Subject: [PATCH] SparklyPaper: Allow throttling hopper checks if the target + container is full + +Original project: https://github.com/SparklyPower/SparklyPaper + +diff --git a/net/minecraft/world/level/block/entity/HopperBlockEntity.java b/net/minecraft/world/level/block/entity/HopperBlockEntity.java +index 5cd1326ad5d046c88b2b3449d610a78fa880b4cd..6ad3388ac45a4d2ef85fc0fafece7de6e387f738 100644 +--- a/net/minecraft/world/level/block/entity/HopperBlockEntity.java ++++ b/net/minecraft/world/level/block/entity/HopperBlockEntity.java +@@ -419,6 +419,11 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + } else { + Direction opposite = blockEntity.facing.getOpposite(); + if (isFullContainer(attachedContainer, opposite)) { ++ // DivineMC start - SparklyPaper: Allow throttling hopper checks if the target container is full ++ if (org.bxteam.divinemc.DivineConfig.hopperThrottleWhenFull && org.bxteam.divinemc.DivineConfig.hopperThrottleSkipTicks > 0) { ++ blockEntity.setCooldown(org.bxteam.divinemc.DivineConfig.hopperThrottleSkipTicks); ++ } ++ // DivineMC end - SparklyPaper: Allow throttling hopper checks if the target container is full + return false; + } else { + // Paper start - Perf: Optimize Hoppers diff --git a/divinemc-server/paper-patches/features/0001-Rebrand.patch b/divinemc-server/paper-patches/features/0001-Rebrand.patch index 4c4ac97..cf01468 100644 --- a/divinemc-server/paper-patches/features/0001-Rebrand.patch +++ b/divinemc-server/paper-patches/features/0001-Rebrand.patch @@ -315,6 +315,13 @@ index 776bc01784b53e3f1d9a35046109c3b9ee4f0882..3731ca80ed58cd385cd66ffbe67f2eea } logger.log(Level.SEVERE, "------------------------------"); +diff --git a/src/main/resources/META-INF/services/ca.spottedleaf.moonrise.common.PlatformHooks b/src/main/resources/META-INF/services/ca.spottedleaf.moonrise.common.PlatformHooks +index e57c3ca79677b1dfe7cf3db36f0406de7ea5bd0a..fc80cbc5dbb49e7a8e251a47c9c05e0c2b20e8d0 100644 +--- a/src/main/resources/META-INF/services/ca.spottedleaf.moonrise.common.PlatformHooks ++++ b/src/main/resources/META-INF/services/ca.spottedleaf.moonrise.common.PlatformHooks +@@ -1 +1 @@ +-ca.spottedleaf.moonrise.paper.PaperHooks ++org.bxteam.divinemc.DivineHooks diff --git a/src/main/resources/logo.png b/src/main/resources/logo.png index 518591dd83289e041a16e2c2e7d7e7640d4b2e1b..f54753531b3bf2e8b5377f342465e727c7da98f2 100644 GIT binary patch diff --git a/divinemc-server/paper-patches/features/0005-Parallel-world-ticking.patch b/divinemc-server/paper-patches/features/0005-Parallel-world-ticking.patch index 9133d9d..c282fb3 100644 --- a/divinemc-server/paper-patches/features/0005-Parallel-world-ticking.patch +++ b/divinemc-server/paper-patches/features/0005-Parallel-world-ticking.patch @@ -221,89 +221,126 @@ index 69cdd304d255d52c9b7dc9b6a33ffdb630b79abe..c153e79ebe1f2338f0d0ca6b45b39279 } + // DivineMC end - Parallel world ticking } +diff --git a/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java b/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java +index d7398b1ecf2660c29fb7d106b48fe02d3736603e..124715b53090085fc0a9f50bb2df196d31d89bed 100644 +--- a/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java ++++ b/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java +@@ -28,6 +28,7 @@ import java.util.logging.Level; + class PaperEventManager { + + private final Server server; ++ private final org.purpurmc.purpur.util.MinecraftInternalPlugin minecraftInternalPlugin = new org.purpurmc.purpur.util.MinecraftInternalPlugin(); // DivineMC - Parallel world ticking + + public PaperEventManager(Server server) { + this.server = server; +@@ -40,6 +41,12 @@ class PaperEventManager { + if (listeners.length == 0) return; + // DivineMC end - Skip event if no listeners + if (event.isAsynchronous() && this.server.isPrimaryThread()) { ++ // DivineMC start - Parallel world ticking ++ if (org.bxteam.divinemc.DivineConfig.enableParallelWorldTicking && org.bxteam.divinemc.DivineConfig.pwtCompatabilityMode) { ++ org.bukkit.Bukkit.getAsyncScheduler().runNow(minecraftInternalPlugin, task -> event.callEvent()); ++ return; ++ } ++ // DivineMC end - Parallel world ticking + throw new IllegalStateException(event.getEventName() + " may only be triggered asynchronously."); + } else if (!event.isAsynchronous() && !this.server.isPrimaryThread() && !this.server.isStopping()) { + throw new IllegalStateException(event.getEventName() + " may only be triggered synchronously."); diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index bc2522968205d0c701a2fa23f29565a500881492..fc4fd93632cbeea929ee866673d721cf4ef1f418 100644 +index bc2522968205d0c701a2fa23f29565a500881492..312018206729b623a7c854af1c11a2d0bc888372 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -446,7 +446,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { +@@ -446,7 +446,13 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override public boolean unloadChunkRequest(int x, int z) { - org.spigotmc.AsyncCatcher.catchOp("chunk unload"); // Spigot -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, x, z, "Cannot unload chunk asynchronously"); // DivineMC - parallel world ticking (additional concurrency issues logs) ++ // DivineMC start - Parallel world ticking ++ if (org.bxteam.divinemc.DivineConfig.enableParallelWorldTicking) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, x, z, "Cannot unload chunk asynchronously"); ++ } else { ++ org.spigotmc.AsyncCatcher.catchOp("chunk unload"); // Spigot ++ } ++ // DivineMC end - Parallel world ticking if (this.isChunkLoaded(x, z)) { this.world.getChunkSource().removeRegionTicket(TicketType.PLUGIN, new ChunkPos(x, z), 1, Unit.INSTANCE); } -@@ -471,6 +471,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { +@@ -471,6 +477,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override public boolean refreshChunk(int x, int z) { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, x, z, "Cannot refresh chunk asynchronously"); // DivineMC - parallel world ticking (additional concurrency issues logs) ++ if (org.bxteam.divinemc.DivineConfig.enableParallelWorldTicking) ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, x, z, "Cannot refresh chunk asynchronously"); // DivineMC - Parallel world ticking ChunkHolder playerChunk = this.world.getChunkSource().chunkMap.getVisibleChunkIfPresent(ChunkPos.asLong(x, z)); if (playerChunk == null) return false; -@@ -521,7 +522,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { +@@ -521,7 +528,13 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override public boolean loadChunk(int x, int z, boolean generate) { - org.spigotmc.AsyncCatcher.catchOp("chunk load"); // Spigot -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.getHandle(), x, z, "May not sync load chunks asynchronously"); // DivineMC - parallel world ticking (additional concurrency issues logs) ++ // DivineMC start - Parallel world ticking ++ if (org.bxteam.divinemc.DivineConfig.enableParallelWorldTicking) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.getHandle(), x, z, "May not sync load chunks asynchronously"); ++ } else { ++ org.spigotmc.AsyncCatcher.catchOp("chunk load"); // Spigot ++ } ++ // DivineMC end - Parallel world ticking warnUnsafeChunk("loading a faraway chunk", x, z); // Paper ChunkAccess chunk = this.world.getChunkSource().getChunk(x, z, generate || isChunkGenerated(x, z) ? ChunkStatus.FULL : ChunkStatus.EMPTY, true); // Paper -@@ -749,6 +750,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { +@@ -749,6 +762,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override public boolean generateTree(Location loc, TreeType type, BlockChangeDelegate delegate) { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, loc.getX(), loc.getZ(), "Cannot generate tree asynchronously"); // DivineMC - parallel world ticking (additional concurrency issues logs) ++ if (org.bxteam.divinemc.DivineConfig.enableParallelWorldTicking) ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, loc.getX(), loc.getZ(), "Cannot generate tree asynchronously"); // DivineMC - Parallel world ticking this.world.captureTreeGeneration = true; this.world.captureBlockStates = true; boolean grownTree = this.generateTree(loc, type); -@@ -864,6 +866,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { +@@ -864,6 +878,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { } public boolean createExplosion(double x, double y, double z, float power, boolean setFire, boolean breakBlocks, Entity source, Consumer configurator) { // Paper end - expand explosion API -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, x, z, "Cannot create explosion asynchronously"); // DivineMC - parallel world ticking (additional concurrency issues logs) ++ if (org.bxteam.divinemc.DivineConfig.enableParallelWorldTicking) ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, x, z, "Cannot create explosion asynchronously"); // DivineMC - Parallel world ticking net.minecraft.world.level.Level.ExplosionInteraction explosionType; if (!breakBlocks) { explosionType = net.minecraft.world.level.Level.ExplosionInteraction.NONE; // Don't break blocks -@@ -955,6 +958,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { +@@ -955,6 +970,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override public int getHighestBlockYAt(int x, int z, org.bukkit.HeightMap heightMap) { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, x >> 4, z >> 4, "Cannot retrieve chunk asynchronously"); // DivineMC - parallel world ticking (additional concurrency issues logs) ++ if (org.bxteam.divinemc.DivineConfig.enableParallelWorldTicking) ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, x >> 4, z >> 4, "Cannot retrieve chunk asynchronously"); // DivineMC - Parallel world ticking warnUnsafeChunk("getting a faraway chunk", x >> 4, z >> 4); // Paper // Transient load for this tick return this.world.getChunk(x >> 4, z >> 4).getHeight(CraftHeightMap.toNMS(heightMap), x, z); -@@ -985,6 +989,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { +@@ -985,6 +1001,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override public void setBiome(int x, int y, int z, Holder bb) { BlockPos pos = new BlockPos(x, 0, z); -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, pos, "Cannot retrieve chunk asynchronously"); // DivineMC - parallel world ticking (additional concurrency issues logs) ++ if (org.bxteam.divinemc.DivineConfig.enableParallelWorldTicking) ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, pos, "Cannot retrieve chunk asynchronously"); // DivineMC - Parallel world ticking if (this.world.hasChunkAt(pos)) { net.minecraft.world.level.chunk.LevelChunk chunk = this.world.getChunkAt(pos); -@@ -2288,6 +2293,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { +@@ -2288,6 +2305,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override public void sendGameEvent(Entity sourceEntity, org.bukkit.GameEvent gameEvent, Vector position) { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, position.getX(), position.getZ(), "Cannot send game event asynchronously"); // DivineMC - parallel world ticking (additional concurrency issues logs) ++ if (org.bxteam.divinemc.DivineConfig.enableParallelWorldTicking) ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, position.getX(), position.getZ(), "Cannot send game event asynchronously"); // DivineMC - Parallel world ticking getHandle().gameEvent(sourceEntity != null ? ((CraftEntity) sourceEntity).getHandle(): null, net.minecraft.core.registries.BuiltInRegistries.GAME_EVENT.get(org.bukkit.craftbukkit.util.CraftNamespacedKey.toMinecraft(gameEvent.getKey())).orElseThrow(), org.bukkit.craftbukkit.util.CraftVector.toBlockPos(position)); } // Paper end diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -index 5cb69d0b822e11a99a96aef4f59986d083b079f4..1bfcb513f2d9a9b86a3833a7f57700b330450fbc 100644 +index 5cb69d0b822e11a99a96aef4f59986d083b079f4..0e47184336f63123211e24a966908a16aa27d6c6 100644 --- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java @@ -75,6 +75,11 @@ public class CraftBlock implements Block { } public net.minecraft.world.level.block.state.BlockState getNMS() { -+ // DivineMC start - parallel world ticking -+ if (world instanceof ServerLevel serverWorld) { ++ // DivineMC start - Parallel world ticking ++ if (org.bxteam.divinemc.DivineConfig.enableParallelWorldTicking && world instanceof ServerLevel serverWorld) { + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot read world asynchronously"); + } -+ // DivineMC end - parallel world ticking ++ // DivineMC end - Parallel world ticking return this.world.getBlockState(this.position); } @@ -311,11 +348,11 @@ index 5cb69d0b822e11a99a96aef4f59986d083b079f4..1bfcb513f2d9a9b86a3833a7f57700b3 } private void setData(final byte data, int flag) { -+ // DivineMC start - parallel world ticking -+ if (world instanceof ServerLevel serverWorld) { ++ // DivineMC start - Parallel world ticking ++ if (org.bxteam.divinemc.DivineConfig.enableParallelWorldTicking && world instanceof ServerLevel serverWorld) { + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot modify world asynchronously"); + } -+ // DivineMC end - parallel world ticking ++ // DivineMC end - Parallel world ticking this.world.setBlock(this.position, CraftMagicNumbers.getBlock(this.getType(), data), flag); } @@ -323,11 +360,11 @@ index 5cb69d0b822e11a99a96aef4f59986d083b079f4..1bfcb513f2d9a9b86a3833a7f57700b3 } public static boolean setTypeAndData(LevelAccessor world, BlockPos position, net.minecraft.world.level.block.state.BlockState old, net.minecraft.world.level.block.state.BlockState blockData, boolean applyPhysics) { -+ // DivineMC start - parallel world ticking -+ if (world instanceof ServerLevel serverWorld) { ++ // DivineMC start - Parallel world ticking ++ if (org.bxteam.divinemc.DivineConfig.enableParallelWorldTicking && world instanceof ServerLevel serverWorld) { + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot modify world asynchronously"); + } -+ // DivineMC end - parallel world ticking ++ // DivineMC end - Parallel world ticking // SPIGOT-611: need to do this to prevent glitchiness. Easier to handle this here (like /setblock) than to fix weirdness in tile entity cleanup if (old.hasBlockEntity() && blockData.getBlock() != old.getBlock()) { // SPIGOT-3725 remove old tile entity if block changes // SPIGOT-4612: faster - just clear tile @@ -335,33 +372,33 @@ index 5cb69d0b822e11a99a96aef4f59986d083b079f4..1bfcb513f2d9a9b86a3833a7f57700b3 @Override public Biome getBiome() { -+ // DivineMC start - parallel world ticking -+ if (world instanceof ServerLevel serverWorld) { ++ // DivineMC start - Parallel world ticking ++ if (org.bxteam.divinemc.DivineConfig.enableParallelWorldTicking && world instanceof ServerLevel serverWorld) { + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot read world asynchronously"); + } -+ // DivineMC end - parallel world ticking ++ // DivineMC end - Parallel world ticking return this.getWorld().getBiome(this.getX(), this.getY(), this.getZ()); } // Paper start @Override public Biome getComputedBiome() { -+ // DivineMC start - parallel world ticking -+ if (world instanceof ServerLevel serverWorld) { ++ // DivineMC start - Parallel world ticking ++ if (org.bxteam.divinemc.DivineConfig.enableParallelWorldTicking && world instanceof ServerLevel serverWorld) { + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot read world asynchronously"); + } -+ // DivineMC end - parallel world ticking ++ // DivineMC end - Parallel world ticking return this.getWorld().getComputedBiome(this.getX(), this.getY(), this.getZ()); } // Paper end @Override public void setBiome(Biome bio) { -+ // DivineMC start - parallel world ticking -+ if (world instanceof ServerLevel serverWorld) { ++ // DivineMC start - Parallel world ticking ++ if (org.bxteam.divinemc.DivineConfig.enableParallelWorldTicking && world instanceof ServerLevel serverWorld) { + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot modify world asynchronously"); + } -+ // DivineMC end - parallel world ticking ++ // DivineMC end - Parallel world ticking this.getWorld().setBiome(this.getX(), this.getY(), this.getZ(), bio); } @@ -369,11 +406,11 @@ index 5cb69d0b822e11a99a96aef4f59986d083b079f4..1bfcb513f2d9a9b86a3833a7f57700b3 @Override public boolean isBlockFaceIndirectlyPowered(BlockFace face) { -+ // DivineMC start - parallel world ticking -+ if (world instanceof ServerLevel serverWorld) { ++ // DivineMC start - Parallel world ticking ++ if (org.bxteam.divinemc.DivineConfig.enableParallelWorldTicking && world instanceof ServerLevel serverWorld) { + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot read world asynchronously"); + } -+ // DivineMC end - parallel world ticking ++ // DivineMC end - Parallel world ticking int power = this.world.getMinecraftWorld().getSignal(this.position, CraftBlock.blockFaceToNotch(face)); Block relative = this.getRelative(face); @@ -381,11 +418,11 @@ index 5cb69d0b822e11a99a96aef4f59986d083b079f4..1bfcb513f2d9a9b86a3833a7f57700b3 @Override public int getBlockPower(BlockFace face) { -+ // DivineMC start - parallel world ticking -+ if (world instanceof ServerLevel serverWorld) { ++ // DivineMC start - Parallel world ticking ++ if (org.bxteam.divinemc.DivineConfig.enableParallelWorldTicking && world instanceof ServerLevel serverWorld) { + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot read world asynchronously"); + } -+ // DivineMC end - parallel world ticking ++ // DivineMC end - Parallel world ticking int power = 0; net.minecraft.world.level.Level world = this.world.getMinecraftWorld(); int x = this.getX(); @@ -393,11 +430,11 @@ index 5cb69d0b822e11a99a96aef4f59986d083b079f4..1bfcb513f2d9a9b86a3833a7f57700b3 @Override public boolean breakNaturally() { -+ // DivineMC start - parallel world ticking -+ if (world instanceof ServerLevel serverWorld) { ++ // DivineMC start - Parallel world ticking ++ if (org.bxteam.divinemc.DivineConfig.enableParallelWorldTicking && world instanceof ServerLevel serverWorld) { + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot modify world asynchronously"); + } -+ // DivineMC end - parallel world ticking ++ // DivineMC end - Parallel world ticking return this.breakNaturally(null); } @@ -405,11 +442,11 @@ index 5cb69d0b822e11a99a96aef4f59986d083b079f4..1bfcb513f2d9a9b86a3833a7f57700b3 @Override public boolean applyBoneMeal(BlockFace face) { -+ // DivineMC start - parallel world ticking -+ if (world instanceof ServerLevel serverWorld) { ++ // DivineMC start - Parallel world ticking ++ if (org.bxteam.divinemc.DivineConfig.enableParallelWorldTicking && world instanceof ServerLevel serverWorld) { + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot modify world asynchronously"); + } -+ // DivineMC end - parallel world ticking ++ // DivineMC end - Parallel world ticking Direction direction = CraftBlock.blockFaceToNotch(face); BlockFertilizeEvent event = null; ServerLevel world = this.getCraftWorld().getHandle(); @@ -419,10 +456,10 @@ index 5cb69d0b822e11a99a96aef4f59986d083b079f4..1bfcb513f2d9a9b86a3833a7f57700b3 if (world.capturedBlockStates.size() > 0) { - TreeType treeType = SaplingBlock.treeType; - SaplingBlock.treeType = null; -+ // DivineMC start - parallel world ticking -+ TreeType treeType = SaplingBlock.treeTypeRT.get(); -+ SaplingBlock.treeTypeRT.set(null); -+ // DivineMC end - parallel world ticking ++ // DivineMC start - Parallel world ticking ++ TreeType treeType = SaplingBlock.getTreeTypeTL(); ++ SaplingBlock.setTreeTypeTL(null); ++ // DivineMC end - Parallel world ticking List blocks = new ArrayList<>(world.capturedBlockStates.values()); world.capturedBlockStates.clear(); StructureGrowEvent structureEvent = null; @@ -430,11 +467,11 @@ index 5cb69d0b822e11a99a96aef4f59986d083b079f4..1bfcb513f2d9a9b86a3833a7f57700b3 @Override public RayTraceResult rayTrace(Location start, Vector direction, double maxDistance, FluidCollisionMode fluidCollisionMode) { -+ // DivineMC start - parallel world ticking -+ if (world instanceof ServerLevel serverWorld) { ++ // DivineMC start - Parallel world ticking ++ if (org.bxteam.divinemc.DivineConfig.enableParallelWorldTicking && world instanceof ServerLevel serverWorld) { + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot read world asynchronously"); + } -+ // DivineMC end - parallel world ticking ++ // DivineMC end - Parallel world ticking Preconditions.checkArgument(start != null, "Location start cannot be null"); Preconditions.checkArgument(this.getWorld().equals(start.getWorld()), "Location start cannot be a different world"); start.checkFinite(); @@ -442,11 +479,11 @@ index 5cb69d0b822e11a99a96aef4f59986d083b079f4..1bfcb513f2d9a9b86a3833a7f57700b3 @Override public boolean canPlace(BlockData data) { -+ // DivineMC start - parallel world ticking -+ if (world instanceof ServerLevel serverWorld) { ++ // DivineMC start - Parallel world ticking ++ if (org.bxteam.divinemc.DivineConfig.enableParallelWorldTicking && world instanceof ServerLevel serverWorld) { + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot read world asynchronously"); + } -+ // DivineMC end - parallel world ticking ++ // DivineMC end - Parallel world ticking Preconditions.checkArgument(data != null, "BlockData cannot be null"); net.minecraft.world.level.block.state.BlockState iblockdata = ((CraftBlockData) data).getState(); net.minecraft.world.level.Level world = this.world.getMinecraftWorld(); @@ -454,53 +491,70 @@ index 5cb69d0b822e11a99a96aef4f59986d083b079f4..1bfcb513f2d9a9b86a3833a7f57700b3 @Override public void tick() { -+ // DivineMC start - parallel world ticking -+ if (world instanceof ServerLevel serverWorld) { ++ // DivineMC start - Parallel world ticking ++ if (org.bxteam.divinemc.DivineConfig.enableParallelWorldTicking && world instanceof ServerLevel serverWorld) { + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot modify world asynchronously"); + } -+ // DivineMC end - parallel world ticking ++ // DivineMC end - Parallel world ticking final ServerLevel level = this.world.getMinecraftWorld(); this.getNMS().tick(level, this.position, level.random); } diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java -index 768d3f93da2522d467183654260a8bd8653588b1..762bb2827dc1c0c0649a4cb3d8b0c8c0c9ea95d1 100644 +index 768d3f93da2522d467183654260a8bd8653588b1..dd3c9e214a59d20c2b5e8556951687e2aba2d116 100644 --- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java +++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java -@@ -25,7 +25,7 @@ public abstract class CraftBlockEntityState extends Craft - private final T tileEntity; +@@ -26,6 +26,25 @@ public abstract class CraftBlockEntityState extends Craft private final T snapshot; public boolean snapshotDisabled; // Paper -- public static boolean DISABLE_SNAPSHOT = false; // Paper -+ public static ThreadLocal DISABLE_SNAPSHOT = ThreadLocal.withInitial(() -> Boolean.FALSE); // DivineMC - parallel world ticking + public static boolean DISABLE_SNAPSHOT = false; // Paper ++ // DivineMC start - Parallel world ticking ++ public static ThreadLocal DISABLE_SNAPSHOT_TL = ThreadLocal.withInitial(() -> Boolean.FALSE); ++ ++ public static boolean getDisableSnapshotTL() { ++ if (org.bxteam.divinemc.DivineConfig.enableParallelWorldTicking && DISABLE_SNAPSHOT_TL.get()) return true; ++ ++ synchronized (CraftBlockEntityState.class) { ++ return DISABLE_SNAPSHOT; ++ } ++ } ++ ++ public static void setDisableSnapshotTL(boolean value) { ++ if (org.bxteam.divinemc.DivineConfig.enableParallelWorldTicking) DISABLE_SNAPSHOT_TL.set(value); ++ ++ synchronized (CraftBlockEntityState.class) { ++ DISABLE_SNAPSHOT = value; ++ } ++ } ++ // DivineMC end - Parallel world ticking public CraftBlockEntityState(World world, T tileEntity) { super(world, tileEntity.getBlockPos(), tileEntity.getBlockState()); -@@ -34,8 +34,10 @@ public abstract class CraftBlockEntityState extends Craft +@@ -34,8 +53,10 @@ public abstract class CraftBlockEntityState extends Craft try { // Paper - Show blockstate location if we failed to read it // Paper start - this.snapshotDisabled = DISABLE_SNAPSHOT; - if (DISABLE_SNAPSHOT) { -+ // DivineMC start - parallel world ticking -+ this.snapshotDisabled = DISABLE_SNAPSHOT.get(); -+ if (DISABLE_SNAPSHOT.get()) { -+ // DivineMC end - parallel world ticking ++ // DivineMC start - Parallel world ticking ++ this.snapshotDisabled = getDisableSnapshotTL(); ++ if (snapshotDisabled) { ++ // DivineMC end - Parallel world ticking this.snapshot = this.tileEntity; } else { this.snapshot = this.createSnapshot(tileEntity); diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java -index fa63a6cfcfcc4eee4503a82d85333c139c8c8b2b..951e47811e861dffd59cc39e2dcd6fd68900fc72 100644 +index fa63a6cfcfcc4eee4503a82d85333c139c8c8b2b..cddb460892f1756faa4b58ae53406058acd9803d 100644 --- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java +++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java @@ -215,6 +215,12 @@ public class CraftBlockState implements BlockState { LevelAccessor access = this.getWorldHandle(); CraftBlock block = this.getBlock(); -+ // DivineMC start - parallel world ticking -+ if (access instanceof net.minecraft.server.level.ServerLevel serverWorld) { ++ // DivineMC start - Parallel world ticking ++ if (org.bxteam.divinemc.DivineConfig.enableParallelWorldTicking && access instanceof net.minecraft.server.level.ServerLevel serverWorld) { + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot modify world asynchronously"); + } -+ // DivineMC end - parallel world ticking ++ // DivineMC end - Parallel world ticking + if (block.getType() != this.getType()) { if (!force) { @@ -509,12 +563,12 @@ index fa63a6cfcfcc4eee4503a82d85333c139c8c8b2b..951e47811e861dffd59cc39e2dcd6fd6 @Override public java.util.Collection getDrops(org.bukkit.inventory.ItemStack item, org.bukkit.entity.Entity entity) { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(world.getHandle(), position, "Cannot modify world asynchronously"); // DivineMC - parallel world ticking ++ if (org.bxteam.divinemc.DivineConfig.enableParallelWorldTicking) ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(world.getHandle(), position, "Cannot modify world asynchronously"); // DivineMC - Parallel world ticking this.requirePlaced(); net.minecraft.world.item.ItemStack nms = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(item); diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java -index 56453454cbd4b9e9270fc833f8ab38d5fa7a3763..a8e740b255336c2d611e44129418b5fb29792592 100644 +index 56453454cbd4b9e9270fc833f8ab38d5fa7a3763..c3cc5012cf460f57495d8867f198007676bae5bf 100644 --- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java +++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java @@ -249,8 +249,10 @@ public final class CraftBlockStates { @@ -524,8 +578,8 @@ index 56453454cbd4b9e9270fc833f8ab38d5fa7a3763..a8e740b255336c2d611e44129418b5fb - boolean prev = CraftBlockEntityState.DISABLE_SNAPSHOT; - CraftBlockEntityState.DISABLE_SNAPSHOT = !useSnapshot; + // DivineMC start - Parallel world ticking -+ boolean prev = CraftBlockEntityState.DISABLE_SNAPSHOT.get(); -+ CraftBlockEntityState.DISABLE_SNAPSHOT.set(!useSnapshot); ++ boolean prev = CraftBlockEntityState.getDisableSnapshotTL(); ++ CraftBlockEntityState.setDisableSnapshotTL(!useSnapshot); + // DivineMC end - Parallel world ticking try { // Paper end @@ -535,38 +589,59 @@ index 56453454cbd4b9e9270fc833f8ab38d5fa7a3763..a8e740b255336c2d611e44129418b5fb // Paper start } finally { - CraftBlockEntityState.DISABLE_SNAPSHOT = prev; -+ CraftBlockEntityState.DISABLE_SNAPSHOT.set(prev); // DivineMC - parallel world ticking ++ CraftBlockEntityState.setDisableSnapshotTL(prev); // DivineMC - Parallel world ticking } // Paper end } diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index 4df143dfa4c01dc70e496ec8dc44fdde00ab40c6..1a398376298fbc5a247d6645e733f7c543106fb1 100644 +index 4df143dfa4c01dc70e496ec8dc44fdde00ab40c6..59ee059f8d2d96b5e5ae507f209d267da24c9fa1 100644 --- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -960,7 +960,7 @@ public class CraftEventFactory { - return CraftEventFactory.handleBlockSpreadEvent(world, source, target, block, 2); +@@ -961,6 +961,26 @@ public class CraftEventFactory { } -- public static BlockPos sourceBlockOverride = null; // SPIGOT-7068: Add source block override, not the most elegant way but better than passing down a BlockPosition up to five methods deep. -+ public static final ThreadLocal sourceBlockOverrideRT = new ThreadLocal<>(); // SPIGOT-7068: Add source block override, not the most elegant way but better than passing down a BlockPosition up to five methods deep. // DivineMC - parallel world ticking (this is from Folia, fixes concurrency bugs with sculk catalysts) + public static BlockPos sourceBlockOverride = null; // SPIGOT-7068: Add source block override, not the most elegant way but better than passing down a BlockPosition up to five methods deep. ++ // DivineMC start - Parallel world ticking ++ public static final ThreadLocal sourceBlockOverrideTL = new ThreadLocal<>(); ++ ++ public static BlockPos getSourceBlockOverrideTL() { ++ BlockPos sourceBlockOverrideRTCopy; ++ if (org.bxteam.divinemc.DivineConfig.enableParallelWorldTicking && (sourceBlockOverrideRTCopy = sourceBlockOverrideTL.get()) != null) return sourceBlockOverrideRTCopy; ++ ++ synchronized (CraftEventFactory.class) { ++ return sourceBlockOverride; ++ } ++ } ++ ++ public static void setSourceBlockOverrideTL(BlockPos value) { ++ if (org.bxteam.divinemc.DivineConfig.enableParallelWorldTicking) sourceBlockOverrideTL.set(value); ++ ++ synchronized (CraftEventFactory.class) { ++ sourceBlockOverride = value; ++ } ++ } ++ // DivineMC end - Parallel world ticking public static boolean handleBlockSpreadEvent(LevelAccessor world, BlockPos source, BlockPos target, net.minecraft.world.level.block.state.BlockState block, int flag) { // Suppress during worldgen -@@ -972,7 +972,7 @@ public class CraftEventFactory { +@@ -972,7 +992,10 @@ public class CraftEventFactory { CraftBlockState state = CraftBlockStates.getBlockState(world, target, flag); state.setData(block); - BlockSpreadEvent event = new BlockSpreadEvent(state.getBlock(), CraftBlock.at(world, CraftEventFactory.sourceBlockOverride != null ? CraftEventFactory.sourceBlockOverride : source), state); -+ BlockSpreadEvent event = new BlockSpreadEvent(state.getBlock(), CraftBlock.at(world, CraftEventFactory.sourceBlockOverrideRT.get() != null ? CraftEventFactory.sourceBlockOverrideRT.get() : source), state); // DivineMC - parallel world ticking ++ // DivineMC start - Parallel world ticking ++ final BlockPos sourceBlockOverrideRTSnap = getSourceBlockOverrideTL(); ++ BlockSpreadEvent event = new BlockSpreadEvent(state.getBlock(), CraftBlock.at(world, sourceBlockOverrideRTSnap != null ? sourceBlockOverrideRTSnap : source), state); ++ // DivineMC end - Parallel world ticking Bukkit.getPluginManager().callEvent(event); if (!event.isCancelled()) { -@@ -2245,7 +2245,7 @@ public class CraftEventFactory { +@@ -2245,7 +2268,7 @@ public class CraftEventFactory { CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemStack.copyWithCount(1)); org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(bukkitBlock, craftItem.clone(), CraftVector.toBukkit(to)); - if (!net.minecraft.world.level.block.DispenserBlock.eventFired) { -+ if (!net.minecraft.world.level.block.DispenserBlock.eventFired.get()) { // DivineMC - parallel world ticking ++ if (!net.minecraft.world.level.block.DispenserBlock.getEventFiredTL()) { // DivineMC - Parallel world ticking if (!event.callEvent()) { return itemStack; } 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..e044381 --- /dev/null +++ b/divinemc-server/paper-patches/features/0006-Chunk-System-Optimizations.patch @@ -0,0 +1,686 @@ +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/misc/Delayed26WayDistancePropagator3D.java b/src/main/java/ca/spottedleaf/moonrise/common/misc/Delayed26WayDistancePropagator3D.java +index 460e27ab0506c83a28934800ee74ee886d4b025e..49e53bf85352f4d1b9997fa3c30e37e40c0ff01c 100644 +--- a/src/main/java/ca/spottedleaf/moonrise/common/misc/Delayed26WayDistancePropagator3D.java ++++ b/src/main/java/ca/spottedleaf/moonrise/common/misc/Delayed26WayDistancePropagator3D.java +@@ -15,7 +15,7 @@ public final class Delayed26WayDistancePropagator3D { + + // Generally updates to positions are made close to other updates, so we link to decrease cache misses when + // propagating updates +- protected final LongLinkedOpenHashSet updatedSources = new LongLinkedOpenHashSet(); ++ protected final it.unimi.dsi.fastutil.longs.LongSet updatedSources = it.unimi.dsi.fastutil.longs.LongSets.synchronize(new LongLinkedOpenHashSet()); // DivineMC - Chunk System Optimizations + + @FunctionalInterface + public static interface LevelChangeCallback { +@@ -94,29 +94,29 @@ public final class Delayed26WayDistancePropagator3D { + + protected final void addToIncreaseWorkQueue(final long coordinate, final byte level) { + final Delayed8WayDistancePropagator2D.WorkQueue queue = this.levelIncreaseWorkQueues[level]; +- queue.queuedCoordinates.enqueue(coordinate); +- queue.queuedLevels.enqueue(level); ++ queue.queuedCoordinates.add(coordinate); // DivineMC - Chunk System Optimizations ++ queue.queuedLevels.add(level); // DivineMC - Chunk System Optimizations + + this.levelIncreaseWorkQueueBitset |= (1L << level); + } + + protected final void addToIncreaseWorkQueue(final long coordinate, final byte index, final byte level) { + final Delayed8WayDistancePropagator2D.WorkQueue queue = this.levelIncreaseWorkQueues[index]; +- queue.queuedCoordinates.enqueue(coordinate); +- queue.queuedLevels.enqueue(level); ++ queue.queuedCoordinates.add(coordinate); // DivineMC - Chunk System Optimizations ++ queue.queuedLevels.add(level); // DivineMC - Chunk System Optimizations + + this.levelIncreaseWorkQueueBitset |= (1L << index); + } + + protected final void addToRemoveWorkQueue(final long coordinate, final byte level) { + final Delayed8WayDistancePropagator2D.WorkQueue queue = this.levelRemoveWorkQueues[level]; +- queue.queuedCoordinates.enqueue(coordinate); +- queue.queuedLevels.enqueue(level); ++ queue.queuedCoordinates.add(coordinate); // DivineMC - Chunk System Optimizations ++ queue.queuedLevels.add(level); // DivineMC - Chunk System Optimizations + + this.levelRemoveWorkQueueBitset |= (1L << level); + } + +- public boolean propagateUpdates() { ++ public synchronized boolean propagateUpdates() { // DivineMC - Chunk System Optimizations + if (this.updatedSources.isEmpty()) { + return false; + } +@@ -157,15 +157,15 @@ public final class Delayed26WayDistancePropagator3D { + return ret; + } + +- protected void propagateIncreases() { ++ protected synchronized void propagateIncreases() { // DivineMC - Chunk System Optimizations + for (int queueIndex = 63 ^ Long.numberOfLeadingZeros(this.levelIncreaseWorkQueueBitset); + this.levelIncreaseWorkQueueBitset != 0L; + this.levelIncreaseWorkQueueBitset ^= (1L << queueIndex), queueIndex = 63 ^ Long.numberOfLeadingZeros(this.levelIncreaseWorkQueueBitset)) { + + final Delayed8WayDistancePropagator2D.WorkQueue queue = this.levelIncreaseWorkQueues[queueIndex]; + while (!queue.queuedLevels.isEmpty()) { +- final long coordinate = queue.queuedCoordinates.removeFirstLong(); +- byte level = queue.queuedLevels.removeFirstByte(); ++ final long coordinate = queue.queuedCoordinates.removeFirst(); // DivineMC - Chunk System Optimizations ++ byte level = queue.queuedLevels.removeFirst(); // DivineMC - Chunk System Optimizations + + final boolean neighbourCheck = level < 0; + +@@ -226,15 +226,15 @@ public final class Delayed26WayDistancePropagator3D { + } + } + +- protected void propagateDecreases() { ++ protected synchronized void propagateDecreases() { // DivineMC - Chunk System Optimizations + for (int queueIndex = 63 ^ Long.numberOfLeadingZeros(this.levelRemoveWorkQueueBitset); + this.levelRemoveWorkQueueBitset != 0L; + this.levelRemoveWorkQueueBitset ^= (1L << queueIndex), queueIndex = 63 ^ Long.numberOfLeadingZeros(this.levelRemoveWorkQueueBitset)) { + + final Delayed8WayDistancePropagator2D.WorkQueue queue = this.levelRemoveWorkQueues[queueIndex]; + while (!queue.queuedLevels.isEmpty()) { +- final long coordinate = queue.queuedCoordinates.removeFirstLong(); +- final byte level = queue.queuedLevels.removeFirstByte(); ++ final long coordinate = queue.queuedCoordinates.removeFirst(); // DivineMC - Chunk System Optimizations ++ final byte level = queue.queuedLevels.removeFirst(); // DivineMC - Chunk System Optimizations + + final byte currentLevel = this.levels.removeIfGreaterOrEqual(coordinate, level); + if (currentLevel == 0) { +diff --git a/src/main/java/ca/spottedleaf/moonrise/common/misc/Delayed8WayDistancePropagator2D.java b/src/main/java/ca/spottedleaf/moonrise/common/misc/Delayed8WayDistancePropagator2D.java +index ab2fa1563d5e32a5313dfcc1da411cab45fb5ca0..1bababc2515d8c805e4ee0c943065f451a38b7b8 100644 +--- a/src/main/java/ca/spottedleaf/moonrise/common/misc/Delayed8WayDistancePropagator2D.java ++++ b/src/main/java/ca/spottedleaf/moonrise/common/misc/Delayed8WayDistancePropagator2D.java +@@ -356,24 +356,24 @@ public final class Delayed8WayDistancePropagator2D { + + protected final void addToIncreaseWorkQueue(final long coordinate, final byte level) { + final WorkQueue queue = this.levelIncreaseWorkQueues[level]; +- queue.queuedCoordinates.enqueue(coordinate); +- queue.queuedLevels.enqueue(level); ++ queue.queuedCoordinates.add(coordinate); // DivineMC - Chunk System Optimizations ++ queue.queuedLevels.add(level); // DivineMC - Chunk System Optimizations + + this.levelIncreaseWorkQueueBitset |= (1L << level); + } + + protected final void addToIncreaseWorkQueue(final long coordinate, final byte index, final byte level) { + final WorkQueue queue = this.levelIncreaseWorkQueues[index]; +- queue.queuedCoordinates.enqueue(coordinate); +- queue.queuedLevels.enqueue(level); ++ queue.queuedCoordinates.add(coordinate); // DivineMC - Chunk System Optimizations ++ queue.queuedLevels.add(level); // DivineMC - Chunk System Optimizations + + this.levelIncreaseWorkQueueBitset |= (1L << index); + } + + protected final void addToRemoveWorkQueue(final long coordinate, final byte level) { + final WorkQueue queue = this.levelRemoveWorkQueues[level]; +- queue.queuedCoordinates.enqueue(coordinate); +- queue.queuedLevels.enqueue(level); ++ queue.queuedCoordinates.add(coordinate); // DivineMC - Chunk System Optimizations ++ queue.queuedLevels.add(level); // DivineMC - Chunk System Optimizations + + this.levelRemoveWorkQueueBitset |= (1L << level); + } +@@ -426,8 +426,8 @@ public final class Delayed8WayDistancePropagator2D { + + final WorkQueue queue = this.levelIncreaseWorkQueues[queueIndex]; + while (!queue.queuedLevels.isEmpty()) { +- final long coordinate = queue.queuedCoordinates.removeFirstLong(); +- byte level = queue.queuedLevels.removeFirstByte(); ++ final long coordinate = queue.queuedCoordinates.removeFirst(); // DivineMC - Chunk System Optimizations ++ byte level = queue.queuedLevels.removeFirst(); // DivineMC - Chunk System Optimizations + + final boolean neighbourCheck = level < 0; + +@@ -492,8 +492,8 @@ public final class Delayed8WayDistancePropagator2D { + + final WorkQueue queue = this.levelRemoveWorkQueues[queueIndex]; + while (!queue.queuedLevels.isEmpty()) { +- final long coordinate = queue.queuedCoordinates.removeFirstLong(); +- final byte level = queue.queuedLevels.removeFirstByte(); ++ final long coordinate = queue.queuedCoordinates.removeFirst(); // DivineMC - Chunk System Optimizations ++ final byte level = queue.queuedLevels.removeFirst(); // DivineMC - Chunk System Optimizations + + final byte currentLevel = this.levels.removeIfGreaterOrEqual(coordinate, level); + if (currentLevel == 0) { +@@ -561,7 +561,7 @@ public final class Delayed8WayDistancePropagator2D { + } + + // copied from superclass +- private int find(final long k) { ++ private synchronized int find(final long k) { // DivineMC - Chunk System Optimizations + if (k == 0L) { + return this.containsNullKey ? this.n : -(this.n + 1); + } else { +@@ -585,7 +585,7 @@ public final class Delayed8WayDistancePropagator2D { + } + + // copied from superclass +- private void insert(final int pos, final long k, final byte v) { ++ private synchronized void insert(final int pos, final long k, final byte v) { // DivineMC - Chunk System Optimizations + if (pos == this.n) { + this.containsNullKey = true; + } +@@ -598,7 +598,7 @@ public final class Delayed8WayDistancePropagator2D { + } + + // copied from superclass +- public byte putIfGreater(final long key, final byte value) { ++ public synchronized byte putIfGreater(final long key, final byte value) { // DivineMC - Chunk System Optimizations + final int pos = this.find(key); + if (pos < 0) { + if (this.defRetValue < value) { +@@ -616,7 +616,7 @@ public final class Delayed8WayDistancePropagator2D { + } + + // copied from superclass +- private void removeEntry(final int pos) { ++ private synchronized void removeEntry(final int pos) { // DivineMC - Chunk System Optimizations + --this.size; + this.shiftKeys(pos); + if (this.n > this.minN && this.size < this.maxFill / 4 && this.n > 16) { +@@ -625,7 +625,7 @@ public final class Delayed8WayDistancePropagator2D { + } + + // copied from superclass +- private void removeNullEntry() { ++ private synchronized void removeNullEntry() { // DivineMC - Chunk System Optimizations + this.containsNullKey = false; + --this.size; + if (this.n > this.minN && this.size < this.maxFill / 4 && this.n > 16) { +@@ -634,7 +634,7 @@ public final class Delayed8WayDistancePropagator2D { + } + + // copied from superclass +- public byte removeIfGreaterOrEqual(final long key, final byte value) { ++ public synchronized byte removeIfGreaterOrEqual(final long key, final byte value) { // DivineMC - Chunk System Optimizations + if (key == 0L) { + if (!this.containsNullKey) { + return this.defRetValue; +@@ -679,8 +679,8 @@ public final class Delayed8WayDistancePropagator2D { + + protected static final class WorkQueue { + +- public final NoResizeLongArrayFIFODeque queuedCoordinates = new NoResizeLongArrayFIFODeque(); +- public final NoResizeByteArrayFIFODeque queuedLevels = new NoResizeByteArrayFIFODeque(); ++ public final java.util.concurrent.ConcurrentLinkedDeque queuedCoordinates = new java.util.concurrent.ConcurrentLinkedDeque<>(); ++ public final java.util.concurrent.ConcurrentLinkedDeque queuedLevels = new java.util.concurrent.ConcurrentLinkedDeque<>(); + + } + +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 91% rename from divinemc-server/paper-patches/features/0007-Optimize-canSee-checks.patch rename to divinemc-server/paper-patches/features/0008-Optimize-canSee-checks.patch index 0dc2611..b311a4b 100644 --- a/divinemc-server/paper-patches/features/0007-Optimize-canSee-checks.patch +++ b/divinemc-server/paper-patches/features/0008-Optimize-canSee-checks.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Optimize canSee checks diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index db2cd4603c26bca59654f0a5225b18c446a7f612..fcc124e1005f4ddda10b9d08226e6d64693f9009 100644 +index d2010fc46215c37c3ef1d8a75cc39bce655d2c3f..3cb2cd294874ece5fbefd0618b4db27701ef118a 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -215,7 +215,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { +@@ -210,7 +210,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { private boolean hasPlayedBefore = false; private final ConversationTracker conversationTracker = new ConversationTracker(); private final Set channels = new HashSet(); @@ -17,7 +17,7 @@ index db2cd4603c26bca59654f0a5225b18c446a7f612..fcc124e1005f4ddda10b9d08226e6d64 private final Set unlistedEntities = new HashSet<>(); // Paper - Add Listing API for Player private static final WeakHashMap> pluginWeakReferences = new WeakHashMap<>(); private int hash = 0; -@@ -2272,9 +2272,15 @@ public class CraftPlayer extends CraftHumanEntity implements Player { +@@ -2267,9 +2267,15 @@ public class CraftPlayer extends CraftHumanEntity implements Player { @Override public boolean canSee(org.bukkit.entity.Entity entity) { 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/features/0011-Multithreaded-Tracker.patch b/divinemc-server/paper-patches/features/0011-Multithreaded-Tracker.patch index 620a8ce..075937c 100644 --- a/divinemc-server/paper-patches/features/0011-Multithreaded-Tracker.patch +++ b/divinemc-server/paper-patches/features/0011-Multithreaded-Tracker.patch @@ -5,11 +5,11 @@ Subject: [PATCH] Multithreaded Tracker diff --git a/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java b/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java -index d7398b1ecf2660c29fb7d106b48fe02d3736603e..a9ec83b5bcb329bf3d2f3fb0e502685a37f9dcbc 100644 +index 124715b53090085fc0a9f50bb2df196d31d89bed..adf526bcb5b4df3b798a8b80ad11b7b2c30775d7 100644 --- a/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java +++ b/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java -@@ -42,6 +42,12 @@ class PaperEventManager { - if (event.isAsynchronous() && this.server.isPrimaryThread()) { +@@ -49,6 +49,12 @@ class PaperEventManager { + // DivineMC end - Parallel world ticking throw new IllegalStateException(event.getEventName() + " may only be triggered asynchronously."); } else if (!event.isAsynchronous() && !this.server.isPrimaryThread() && !this.server.isStopping()) { + // DivineMC start - Multithreaded Tracker @@ -22,10 +22,10 @@ index d7398b1ecf2660c29fb7d106b48fe02d3736603e..a9ec83b5bcb329bf3d2f3fb0e502685a } diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index 1a398376298fbc5a247d6645e733f7c543106fb1..b761e416d3844063efd66d7ec8519f548995aacd 100644 +index 59ee059f8d2d96b5e5ae507f209d267da24c9fa1..68c529473e9ce24cb6c6108cb65100757d1d8759 100644 --- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -1973,6 +1973,26 @@ public class CraftEventFactory { +@@ -1996,6 +1996,26 @@ public class CraftEventFactory { } public static boolean handleBlockFormEvent(Level world, BlockPos pos, net.minecraft.world.level.block.state.BlockState block, int flag, @Nullable Entity entity) { diff --git a/divinemc-server/paper-patches/features/0012-Paper-PR-Add-FillBottleEvents-for-player-and-dispens.patch b/divinemc-server/paper-patches/features/0012-Paper-PR-Add-FillBottleEvents-for-player-and-dispens.patch index f662895..59136d6 100644 --- a/divinemc-server/paper-patches/features/0012-Paper-PR-Add-FillBottleEvents-for-player-and-dispens.patch +++ b/divinemc-server/paper-patches/features/0012-Paper-PR-Add-FillBottleEvents-for-player-and-dispens.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Paper PR: Add FillBottleEvents for player and dispenser diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index b761e416d3844063efd66d7ec8519f548995aacd..036913b57313ac437115698282c6d76eb749cf52 100644 +index 68c529473e9ce24cb6c6108cb65100757d1d8759..ba5adea7a791a9539ae485ef1c1641a56ca19236 100644 --- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -2307,4 +2307,18 @@ public class CraftEventFactory { +@@ -2330,4 +2330,18 @@ public class CraftEventFactory { return event; } // Paper end - add EntityFertilizeEggEvent diff --git a/divinemc-server/paper-patches/features/0014-Paper-PR-Throttle-failed-spawn-attempts.patch b/divinemc-server/paper-patches/features/0014-Paper-PR-Throttle-failed-spawn-attempts.patch new file mode 100644 index 0000000..3c66bce --- /dev/null +++ b/divinemc-server/paper-patches/features/0014-Paper-PR-Throttle-failed-spawn-attempts.patch @@ -0,0 +1,45 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Thu, 27 Mar 2025 00:04:19 +0300 +Subject: [PATCH] Paper PR: Throttle failed spawn attempts + +Original license: GPLv3 +Original project: https://github.com/PaperMC/Paper +Paper pull request: https://github.com/PaperMC/Paper/pull/11099 + +Example config in paper-world-defaults.yml: +``` + spawning-throttle: + failed-attempts-threshold: 1200 + throttled-ticks-per-spawn: + ambient: 10 # default value in bukkit.yml tickers-per * 10 + axolotls: 10 + creature: 4000 + monster: 10 + underground_water_creature: 10 + water_ambient: 10 + water_creature: 10 +``` + +diff --git a/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java b/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java +index 7bfa7aa30c1181587c7632f920f48348d2493ea4..d838c90f98c6593404c77d0aab8655c0d15905c4 100644 +--- a/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java ++++ b/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java +@@ -181,6 +181,17 @@ public class WorldConfiguration extends ConfigurationPart { + @MergeMap + public Reference2IntMap ticksPerSpawn = Util.make(new Reference2IntOpenHashMap<>(NaturalSpawner.SPAWNING_CATEGORIES.length), map -> Arrays.stream(NaturalSpawner.SPAWNING_CATEGORIES).forEach(mobCategory -> map.put(mobCategory, -1))); + ++ // Paper start - throttle failed spawn attempts ++ public SpawningThrottle spawningThrottle; ++ ++ public class SpawningThrottle extends ConfigurationPart { ++ public IntOr.Disabled failedAttemptsThreshold = IntOr.Disabled.DISABLED; ++ ++ @MergeMap ++ public Reference2IntMap throttledTicksPerSpawn = Util.make(new Reference2IntOpenHashMap<>(NaturalSpawner.SPAWNING_CATEGORIES.length), map -> Arrays.stream(NaturalSpawner.SPAWNING_CATEGORIES).forEach(mobCategory -> map.put(mobCategory, -1))); ++ } ++ // Paper end - throttle failed spawn attempts ++ + @ConfigSerializable + public record DespawnRangePair(@Required DespawnRange hard, @Required DespawnRange soft) { + public static DespawnRangePair createDefault() { 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/purpur-patches/features/0002-Add-missing-purpur-config-options.patch b/divinemc-server/purpur-patches/features/0002-Add-missing-purpur-config-options.patch index d2bafb0..edebe96 100644 --- a/divinemc-server/purpur-patches/features/0002-Add-missing-purpur-config-options.patch +++ b/divinemc-server/purpur-patches/features/0002-Add-missing-purpur-config-options.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Add missing purpur config options diff --git a/src/main/java/org/purpurmc/purpur/PurpurConfig.java b/src/main/java/org/purpurmc/purpur/PurpurConfig.java -index 168361632d4af27c2144864ade539ce7ea217217..48248e1a7eb9f63220dd619c1868dcca3150c345 100644 +index 554ae05a1a7f7dbe91455cb14b1d9a02f3b7d288..2c32f98e4f3ac2705ae6e95a8e8965b4b9d9a0d5 100644 --- a/src/main/java/org/purpurmc/purpur/PurpurConfig.java +++ b/src/main/java/org/purpurmc/purpur/PurpurConfig.java @@ -327,6 +327,7 @@ public class PurpurConfig { @@ -16,7 +16,7 @@ index 168361632d4af27c2144864ade539ce7ea217217..48248e1a7eb9f63220dd619c1868dcca public static boolean enderChestSixRows = false; public static boolean enderChestPermissionRows = false; public static boolean cryingObsidianValidForPortalFrame = false; -@@ -369,6 +370,7 @@ public class PurpurConfig { +@@ -370,6 +371,7 @@ public class PurpurConfig { case 1 -> 9; default -> 27; }); 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/org/bxteam/divinemc/util/Assertions.java b/divinemc-server/src/main/java/com/ishland/flowsched/util/Assertions.java similarity index 95% rename from divinemc-server/src/main/java/org/bxteam/divinemc/util/Assertions.java rename to divinemc-server/src/main/java/com/ishland/flowsched/util/Assertions.java index 98975c5..c3ee6c4 100644 --- a/divinemc-server/src/main/java/org/bxteam/divinemc/util/Assertions.java +++ b/divinemc-server/src/main/java/com/ishland/flowsched/util/Assertions.java @@ -1,4 +1,4 @@ -package org.bxteam.divinemc.util; +package com.ishland.flowsched.util; public final class Assertions { public static void assertTrue(boolean value, String message) { diff --git a/divinemc-server/src/main/java/gg/pufferfish/pufferfish/sentry/PufferfishSentryAppender.java b/divinemc-server/src/main/java/gg/pufferfish/pufferfish/sentry/PufferfishSentryAppender.java index fbc67c6..595320c 100644 --- a/divinemc-server/src/main/java/gg/pufferfish/pufferfish/sentry/PufferfishSentryAppender.java +++ b/divinemc-server/src/main/java/gg/pufferfish/pufferfish/sentry/PufferfishSentryAppender.java @@ -20,7 +20,7 @@ import org.bxteam.divinemc.DivineConfig; import java.util.Map; public class PufferfishSentryAppender extends AbstractAppender { - private static final org.apache.logging.log4j.Logger LOGGER = LogManager.getLogger(PufferfishSentryAppender.class.getSimpleName()); + private static final org.apache.logging.log4j.Logger LOGGER = LogManager.getLogger("PufferfishSentryAppender"); private static final Gson GSON = new Gson(); private final Level logLevel; diff --git a/divinemc-server/src/main/java/gg/pufferfish/pufferfish/sentry/SentryManager.java b/divinemc-server/src/main/java/gg/pufferfish/pufferfish/sentry/SentryManager.java index 6b2b5a7..e6cdd4d 100644 --- a/divinemc-server/src/main/java/gg/pufferfish/pufferfish/sentry/SentryManager.java +++ b/divinemc-server/src/main/java/gg/pufferfish/pufferfish/sentry/SentryManager.java @@ -7,7 +7,7 @@ import org.apache.logging.log4j.Logger; import org.bxteam.divinemc.DivineConfig; public class SentryManager { - private static final Logger LOGGER = LogManager.getLogger(SentryManager.class); + private static final Logger LOGGER = LogManager.getLogger("SentryManager"); private static boolean initialized = false; private SentryManager() { diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/DivineBootstrap.java b/divinemc-server/src/main/java/org/bxteam/divinemc/DivineBootstrap.java index 63bb374..cc9e5dd 100644 --- a/divinemc-server/src/main/java/org/bxteam/divinemc/DivineBootstrap.java +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/DivineBootstrap.java @@ -18,6 +18,12 @@ public class DivineBootstrap { public static void boot(final OptionSet options) { SharedConstants.tryDetectVersion(); + + io.papermc.paper.ServerBuildInfo info = io.papermc.paper.ServerBuildInfo.buildInfo(); + if (io.papermc.paper.ServerBuildInfoImpl.IS_EXPERIMENTAL) { + LOGGER.warn("Running an experimental version of {}, please proceed with caution.", info.brandName()); + } + Path path2 = Paths.get("eula.txt"); Eula eula = new Eula(path2); boolean eulaAgreed = Boolean.getBoolean("com.mojang.eula.agree"); diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/DivineConfig.java b/divinemc-server/src/main/java/org/bxteam/divinemc/DivineConfig.java index 8cf2ad4..d4ad79c 100644 --- a/divinemc-server/src/main/java/org/bxteam/divinemc/DivineConfig.java +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/DivineConfig.java @@ -10,13 +10,14 @@ import org.apache.logging.log4j.Logger; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.MemoryConfiguration; import org.bxteam.divinemc.entity.pathfinding.PathfindTaskRejectPolicy; +import org.bxteam.divinemc.region.LinearImplementation; import org.bxteam.divinemc.server.chunk.ChunkSystemAlgorithms; import org.bxteam.divinemc.server.chunk.ChunkTaskPriority; import org.jetbrains.annotations.Nullable; import org.simpleyaml.configuration.comments.CommentType; import org.simpleyaml.configuration.file.YamlFile; import org.simpleyaml.exceptions.InvalidConfigurationException; -import org.stupidcraft.linearpaper.region.EnumRegionFileExtension; +import org.bxteam.divinemc.region.RegionFileFormat; import java.io.File; import java.io.IOException; @@ -185,7 +186,6 @@ public class DivineConfig { public static ChunkSystemAlgorithms chunkWorkerAlgorithm = ChunkSystemAlgorithms.C2ME; public static ChunkTaskPriority chunkTaskPriority = ChunkTaskPriority.EUCLIDEAN_CIRCLE_PATTERN; public static int threadPoolPriority = Thread.NORM_PRIORITY + 1; - public static boolean enableAsyncNoiseFill = false; public static boolean asyncChunkSendingEnabled = true; public static boolean enableSecureSeed = false; public static boolean smoothBedrockLayer = false; @@ -223,8 +223,6 @@ public class DivineConfig { " - DEFAULT_DIAMOND_PATTERN: Default one, chunk priorities will be ordered in a diamond pattern")); threadPoolPriority = getInt("settings.chunks.thread-pool-priority", threadPoolPriority, "Sets the priority of the thread pool used for chunk generation"); - enableAsyncNoiseFill = getBoolean("settings.chunks.enable-async-noise-fill", enableAsyncNoiseFill, - "Runs noise filling and biome populating in a virtual thread executor. If disabled, it will run sync."); enableSecureSeed = getBoolean("settings.chunks.enable-secure-seed", enableSecureSeed, "This feature is based on Secure Seed mod by Earthcomputer.", @@ -282,6 +280,8 @@ public class DivineConfig { public static boolean disableLeafDecay = false; public static boolean commandBlockParseResultsCaching = true; public static boolean enableAsyncSpawning = true; + public static boolean hopperThrottleWhenFull = false; + public static int hopperThrottleSkipTicks = 0; private static void miscSettings() { skipUselessSecondaryPoiSensor = getBoolean("settings.misc.skip-useless-secondary-poi-sensor", skipUselessSecondaryPoiSensor); clumpOrbs = getBoolean("settings.misc.clump-orbs", clumpOrbs, @@ -303,6 +303,11 @@ public class DivineConfig { "Caches the parse results of command blocks, can significantly reduce performance impact."); enableAsyncSpawning = getBoolean("settings.misc.enable-async-spawning", enableAsyncSpawning, "Enables optimization that will offload much of the computational effort involved with spawning new mobs to a different thread."); + + hopperThrottleWhenFull = getBoolean("settings.misc.hopper-throttle-when-full.enabled", hopperThrottleWhenFull, + "When enabled, hoppers will throttle if target container is full."); + hopperThrottleSkipTicks = getInt("settings.misc.hopper-throttle-when-full.skip-ticks", hopperThrottleSkipTicks, + "The amount of ticks to skip when the hopper is throttled."); } public static String sentryDsn = ""; @@ -522,32 +527,50 @@ public class DivineConfig { if (asyncEntityTrackerQueueSize <= 0) asyncEntityTrackerQueueSize = asyncEntityTrackerMaxThreads * 384; } - public static EnumRegionFileExtension regionFormatTypeName = EnumRegionFileExtension.MCA; + public static RegionFileFormat regionFormatTypeName = RegionFileFormat.ANVIL; + public static LinearImplementation linearImplementation = LinearImplementation.V2; + public static int linearFlushMaxThreads = 4; + public static int linearFlushDelay = 100; + public static boolean linearUseVirtualThread = false; public static int linearCompressionLevel = 1; - public static int linearFlushFrequency = 5; private static void linearRegionFormat() { - regionFormatTypeName = EnumRegionFileExtension.fromName(getString("settings.linear-region-format.type", regionFormatTypeName.name(), + regionFormatTypeName = RegionFileFormat.fromName(getString("settings.linear-region-format.type", regionFormatTypeName.name(), "The type of region file format to use for storing chunk data.", "Valid values:", " - LINEAR: Linear region file format", - " - MCA: Anvil region file format (default)")); + " - ANVIL: Anvil region file format (default)")); + linearImplementation = LinearImplementation.valueOf(getString("settings.linear-region-format.implementation", linearImplementation.name(), + "The implementation of the linear region file format to use.", + "Valid values:", + " - V1: Basic and default linear implementation", + " - V2: Introduces a grid-based compression scheme for better data management and flexibility (default)")); + + linearFlushMaxThreads = getInt("settings.linear-region-format.flush-max-threads", linearFlushMaxThreads, + "The maximum number of threads to use for flushing linear region files.", + "If this value is less than or equal to 0, it will be set to the number of available processors + this value."); + linearFlushDelay = getInt("settings.linear-region-format.flush-delay", linearFlushDelay, + "The delay in milliseconds to wait before flushing next files."); + linearUseVirtualThread = getBoolean("settings.linear-region-format.use-virtual-thread", linearUseVirtualThread, + "Whether to use virtual threads for flushing."); linearCompressionLevel = getInt("settings.linear-region-format.compression-level", linearCompressionLevel, "The compression level to use for the linear region file format."); - linearFlushFrequency = getInt("settings.linear-region-format.flush-frequency", linearFlushFrequency, - "The frequency in seconds to flush the linear region file format."); setComment("settings.linear-region-format", - "The linear region file format is a custom region file format that is designed to be more efficient than the MCA format.", + "The linear region file format is a custom region file format that is designed to be more efficient than the ANVIL format.", "It uses uses ZSTD compression instead of ZLIB. This format saves about 50% of disk space.", "Read more information about linear region format at https://github.com/xymb-endcrystalme/LinearRegionFileFormatTools", "WARNING: If you are want to use this format, make sure to create backup of your world before switching to it, there is potential risk to lose chunk data."); - if (regionFormatTypeName == EnumRegionFileExtension.UNKNOWN) { - LOGGER.error("Unknown region file type: {}, falling back to MCA format.", regionFormatTypeName); - regionFormatTypeName = EnumRegionFileExtension.MCA; + if (regionFormatTypeName == RegionFileFormat.UNKNOWN) { + LOGGER.error("Unknown region file type: {}, falling back to ANVIL format.", regionFormatTypeName); + regionFormatTypeName = RegionFileFormat.ANVIL; } - if (linearCompressionLevel > 23 || linearCompressionLevel < 1) { + if (linearFlushMaxThreads <= 0) { + linearFlushMaxThreads = Math.max(Runtime.getRuntime().availableProcessors() + linearFlushMaxThreads, 1); + } + + if (linearCompressionLevel > 22 || linearCompressionLevel < 1) { LOGGER.warn("Invalid linear compression level: {}, resetting to default (1)", playerNearChunkDetectionRange); linearCompressionLevel = 1; } diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/DivineHooks.java b/divinemc-server/src/main/java/org/bxteam/divinemc/DivineHooks.java new file mode 100644 index 0000000..66146d0 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/DivineHooks.java @@ -0,0 +1,10 @@ +package org.bxteam.divinemc; + +import ca.spottedleaf.moonrise.paper.PaperHooks; + +public final class DivineHooks extends PaperHooks { + @Override + public String getBrand() { + return "DivineMC"; + } +} diff --git a/divinemc-server/src/main/java/org/stupidcraft/linearpaper/region/IRegionFile.java b/divinemc-server/src/main/java/org/bxteam/divinemc/region/IRegionFile.java similarity index 96% rename from divinemc-server/src/main/java/org/stupidcraft/linearpaper/region/IRegionFile.java rename to divinemc-server/src/main/java/org/bxteam/divinemc/region/IRegionFile.java index a09223d..94ecd4e 100644 --- a/divinemc-server/src/main/java/org/stupidcraft/linearpaper/region/IRegionFile.java +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/region/IRegionFile.java @@ -1,4 +1,4 @@ -package org.stupidcraft.linearpaper.region; +package org.bxteam.divinemc.region; import ca.spottedleaf.moonrise.patches.chunk_system.storage.ChunkSystemRegionFile; import net.minecraft.nbt.CompoundTag; diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/region/LinearImplementation.java b/divinemc-server/src/main/java/org/bxteam/divinemc/region/LinearImplementation.java new file mode 100644 index 0000000..e521593 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/region/LinearImplementation.java @@ -0,0 +1,6 @@ +package org.bxteam.divinemc.region; + +public enum LinearImplementation { + V1, + V2 +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/region/LinearRegionFile.java b/divinemc-server/src/main/java/org/bxteam/divinemc/region/LinearRegionFile.java new file mode 100644 index 0000000..706f80f --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/region/LinearRegionFile.java @@ -0,0 +1,659 @@ +package org.bxteam.divinemc.region; + +import ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO; +import com.github.luben.zstd.ZstdInputStream; +import com.github.luben.zstd.ZstdOutputStream; +import com.mojang.logging.LogUtils; +import net.jpountz.lz4.LZ4Compressor; +import net.jpountz.lz4.LZ4Factory; +import net.jpountz.lz4.LZ4FastDecompressor; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.level.ChunkPos; +import net.openhft.hashing.LongHashFunction; +import org.bxteam.divinemc.DivineConfig; +import org.bxteam.divinemc.spark.ThreadDumperRegistry; +import org.jspecify.annotations.Nullable; +import org.slf4j.Logger; + +import java.io.BufferedOutputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.ArrayList; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.LockSupport; + +public class LinearRegionFile implements IRegionFile { + public static final int MAX_CHUNK_SIZE = 500 * 1024 * 1024; + private static final Object SAVE_LOCK = new Object(); + private static final long SUPERBLOCK = 0xc3ff13183cca9d9aL; + private static final Logger LOGGER = LogUtils.getLogger(); + + private static final byte V1_VERSION = 2; + private static final byte V2_VERSION = 3; + + private byte[][] bucketBuffers; + private final byte[][] chunkCompressedBuffers = new byte[1024][]; + private final int[] chunkUncompressedSizes = new int[1024]; + private final long[] chunkTimestamps = new long[1024]; + + private final Object markedToSaveLock = new Object(); + private boolean markedToSave = false; + + private final LZ4Compressor compressor; + private final LZ4FastDecompressor decompressor; + + private volatile boolean regionFileOpen = false; + private volatile boolean close = false; + + private final Path regionFilePath; + private final int gridSizeDefault = 8; + private int gridSize = gridSizeDefault; + private int bucketSize = 4; + private final int compressionLevel; + private final LinearImplementation linearImpl; + private final Thread schedulingThread; + + private static int activeSaveThreads = 0; + + public LinearRegionFile(Path path, LinearImplementation linearImplementation, int compressionLevel) { + this.regionFilePath = path; + this.linearImpl = linearImplementation; + this.compressionLevel = compressionLevel; + this.compressor = LZ4Factory.fastestInstance().fastCompressor(); + this.decompressor = LZ4Factory.fastestInstance().fastDecompressor(); + + Runnable flushCheck = () -> { + while (!close) { + synchronized (SAVE_LOCK) { + if (markedToSave && activeSaveThreads < DivineConfig.linearFlushMaxThreads) { + activeSaveThreads++; + Runnable flushOperation = () -> { + try { + flush(); + } catch (IOException ex) { + LOGGER.error("Region file {} flush failed", regionFilePath.toAbsolutePath(), ex); + } finally { + synchronized (SAVE_LOCK) { + activeSaveThreads--; + } + } + }; + Thread saveThread = DivineConfig.linearUseVirtualThread + ? Thread.ofVirtual().name("Linear IO - " + this.hashCode()).unstarted(flushOperation) + : Thread.ofPlatform().name("Linear IO - " + this.hashCode()).unstarted(flushOperation); + saveThread.setPriority(Thread.NORM_PRIORITY - 3); + saveThread.start(); + ThreadDumperRegistry.REGISTRY.add(saveThread.getName()); + } + } + LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(DivineConfig.linearFlushDelay)); + } + }; + this.schedulingThread = DivineConfig.linearUseVirtualThread + ? Thread.ofVirtual().unstarted(flushCheck) + : Thread.ofPlatform().unstarted(flushCheck); + this.schedulingThread.setName("Linear IO Schedule - " + this.hashCode()); + ThreadDumperRegistry.REGISTRY.add(this.schedulingThread.getName()); + } + + private synchronized void openRegionFile() { + if (regionFileOpen) return; + regionFileOpen = true; + + File file = regionFilePath.toFile(); + if (!file.canRead()) { + schedulingThread.start(); + return; + } + + try { + byte[] fileContent = Files.readAllBytes(regionFilePath); + ByteBuffer byteBuffer = ByteBuffer.wrap(fileContent); + + long superBlock = byteBuffer.getLong(); + if (superBlock != SUPERBLOCK) { + throw new RuntimeException("Invalid superblock: " + superBlock + " file " + regionFilePath); + } + + byte version = byteBuffer.get(); + if (version == V1_VERSION) { + parseLinearV1(byteBuffer); + } else if (version == V2_VERSION) { + parseLinearV2(byteBuffer); + } else { + throw new RuntimeException("Invalid version: " + version + " file " + regionFilePath); + } + + schedulingThread.start(); + } catch (IOException e) { + throw new RuntimeException("Failed to open region file " + regionFilePath, e); + } + } + + private void parseLinearV1(ByteBuffer buffer) throws IOException { + final int HEADER_SIZE = 32; + final int FOOTER_SIZE = 8; + buffer.position(buffer.position() + 11); + + int dataCount = buffer.getInt(); + long fileLength = regionFilePath.toFile().length(); + if (fileLength != HEADER_SIZE + dataCount + FOOTER_SIZE) { + throw new IOException("Invalid file length: " + regionFilePath + " " + fileLength + " expected " + (HEADER_SIZE + dataCount + FOOTER_SIZE)); + } + + buffer.position(buffer.position() + 8); + + byte[] rawCompressed = new byte[dataCount]; + buffer.get(rawCompressed); + + try (ByteArrayInputStream bais = new ByteArrayInputStream(rawCompressed); + ZstdInputStream zstdIn = new ZstdInputStream(bais)) { + ByteBuffer decompressedBuffer = ByteBuffer.wrap(zstdIn.readAllBytes()); + int[] starts = new int[1024]; + for (int i = 0; i < 1024; i++) { + starts[i] = decompressedBuffer.getInt(); + decompressedBuffer.getInt(); + } + + for (int i = 0; i < 1024; i++) { + if (starts[i] > 0) { + int size = starts[i]; + byte[] chunkData = new byte[size]; + decompressedBuffer.get(chunkData); + + int maxCompressedLength = compressor.maxCompressedLength(size); + byte[] compressed = new byte[maxCompressedLength]; + int compressedLength = compressor.compress(chunkData, 0, size, compressed, 0, maxCompressedLength); + byte[] finalCompressed = new byte[compressedLength]; + System.arraycopy(compressed, 0, finalCompressed, 0, compressedLength); + + chunkCompressedBuffers[i] = finalCompressed; + chunkUncompressedSizes[i] = size; + chunkTimestamps[i] = currentTimestamp(); + } + } + } + } + + private void parseLinearV2(ByteBuffer buffer) throws IOException { + buffer.getLong(); + gridSize = buffer.get(); + if (!(gridSize == 1 || gridSize == 2 || gridSize == 4 || gridSize == 8 || gridSize == 16 || gridSize == 32)) { + throw new RuntimeException("Invalid grid size: " + gridSize + " file " + regionFilePath); + } + bucketSize = 32 / gridSize; + + buffer.getInt(); + buffer.getInt(); + + boolean[] chunkExistenceBitmap = deserializeExistenceBitmap(buffer); + + while (true) { + byte featureNameLength = buffer.get(); + if (featureNameLength == 0) break; + byte[] featureNameBytes = new byte[featureNameLength]; + buffer.get(featureNameBytes); + String featureName = new String(featureNameBytes); + int featureValue = buffer.getInt(); + } + + int bucketCount = gridSize * gridSize; + int[] bucketSizes = new int[bucketCount]; + byte[] bucketCompressionLevels = new byte[bucketCount]; + long[] bucketHashes = new long[bucketCount]; + + for (int i = 0; i < bucketCount; i++) { + bucketSizes[i] = buffer.getInt(); + bucketCompressionLevels[i] = buffer.get(); + bucketHashes[i] = buffer.getLong(); + } + + bucketBuffers = new byte[bucketCount][]; + for (int i = 0; i < bucketCount; i++) { + if (bucketSizes[i] > 0) { + bucketBuffers[i] = new byte[bucketSizes[i]]; + buffer.get(bucketBuffers[i]); + long rawHash = LongHashFunction.xx().hashBytes(bucketBuffers[i]); + if (rawHash != bucketHashes[i]) { + throw new IOException("Region file hash incorrect " + regionFilePath); + } + } + } + + long footerSuperBlock = buffer.getLong(); + if (footerSuperBlock != SUPERBLOCK) { + throw new IOException("Footer superblock invalid " + regionFilePath); + } + } + + private synchronized void markToSave() { + synchronized (markedToSaveLock) { + markedToSave = true; + } + } + + private synchronized boolean isMarkedToSave() { + synchronized (markedToSaveLock) { + if (markedToSave) { + markedToSave = false; + return true; + } + return false; + } + } + + @Override + public synchronized boolean doesChunkExist(ChunkPos pos) { + openRegionFile(); + return hasChunk(pos); + } + + @Override + public synchronized boolean hasChunk(ChunkPos pos) { + openRegionFile(); + openBucketForChunk(pos.x, pos.z); + int index = getChunkIndex(pos.x, pos.z); + return chunkUncompressedSizes[index] > 0; + } + + @Override + public synchronized void flush() throws IOException { + if (!isMarkedToSave()) return; + openRegionFile(); + if (linearImpl == LinearImplementation.V1) { + flushLinearV1(); + } else if (linearImpl == LinearImplementation.V2) { + flushLinearV2(); + } + } + + private void flushLinearV1() throws IOException { + long timestamp = currentTimestamp(); + short chunkCount = 0; + File tempFile = new File(regionFilePath.toString() + ".tmp"); + + try (FileOutputStream fos = new FileOutputStream(tempFile); + ByteArrayOutputStream zstdBAOS = new ByteArrayOutputStream(); + ZstdOutputStream zstdOut = new ZstdOutputStream(zstdBAOS, compressionLevel); + DataOutputStream zstdDataOut = new DataOutputStream(zstdOut); + DataOutputStream fileDataOut = new DataOutputStream(fos)) { + + fileDataOut.writeLong(SUPERBLOCK); + fileDataOut.writeByte(V1_VERSION); + fileDataOut.writeLong(timestamp); + fileDataOut.writeByte(compressionLevel); + + ArrayList decompressedChunks = new ArrayList<>(1024); + for (int i = 0; i < 1024; i++) { + if (chunkUncompressedSizes[i] != 0) { + chunkCount++; + byte[] decompressed = new byte[chunkUncompressedSizes[i]]; + decompressor.decompress(chunkCompressedBuffers[i], 0, decompressed, 0, chunkUncompressedSizes[i]); + decompressedChunks.add(decompressed); + } else { + decompressedChunks.add(null); + } + } + + for (int i = 0; i < 1024; i++) { + zstdDataOut.writeInt(chunkUncompressedSizes[i]); + zstdDataOut.writeInt((int) chunkTimestamps[i]); + } + + for (int i = 0; i < 1024; i++) { + if (decompressedChunks.get(i) != null) { + zstdDataOut.write(decompressedChunks.get(i)); + } + } + zstdDataOut.close(); + + fileDataOut.writeShort(chunkCount); + byte[] compressedZstdData = zstdBAOS.toByteArray(); + fileDataOut.writeInt(compressedZstdData.length); + fileDataOut.writeLong(0); + fileDataOut.write(compressedZstdData); + fileDataOut.writeLong(SUPERBLOCK); + + fileDataOut.flush(); + fos.getFD().sync(); + fos.getChannel().force(true); + } + Files.move(tempFile.toPath(), regionFilePath, StandardCopyOption.REPLACE_EXISTING); + } + + private void flushLinearV2() throws IOException { + long timestamp = currentTimestamp(); + File tempFile = new File(regionFilePath.toString() + ".tmp"); + + try (FileOutputStream fos = new FileOutputStream(tempFile); + DataOutputStream dataOut = new DataOutputStream(fos)) { + + dataOut.writeLong(SUPERBLOCK); + dataOut.writeByte(V2_VERSION); + dataOut.writeLong(timestamp); + dataOut.writeByte(gridSize); + + int[] regionCoords = parseRegionCoordinates(regionFilePath.getFileName().toString()); + dataOut.writeInt(regionCoords[0]); + dataOut.writeInt(regionCoords[1]); + + boolean[] chunkExistence = new boolean[1024]; + for (int i = 0; i < 1024; i++) { + chunkExistence[i] = (chunkUncompressedSizes[i] > 0); + } + writeExistenceBitmap(dataOut, chunkExistence); + + writeNBTFeatures(dataOut); + + byte[][] buckets = buildBuckets(); + + int bucketCount = gridSize * gridSize; + for (int i = 0; i < bucketCount; i++) { + dataOut.writeInt(buckets[i] != null ? buckets[i].length : 0); + dataOut.writeByte(compressionLevel); + long bucketHash = buckets[i] != null ? LongHashFunction.xx().hashBytes(buckets[i]) : 0; + dataOut.writeLong(bucketHash); + } + for (int i = 0; i < bucketCount; i++) { + if (buckets[i] != null) { + dataOut.write(buckets[i]); + } + } + dataOut.writeLong(SUPERBLOCK); + + dataOut.flush(); + fos.getFD().sync(); + fos.getChannel().force(true); + } + Files.move(tempFile.toPath(), regionFilePath, StandardCopyOption.REPLACE_EXISTING); + } + + private void writeNBTFeatures(DataOutputStream dataOut) throws IOException { + dataOut.writeByte(0); + } + + private byte[][] buildBuckets() throws IOException { + int bucketCount = gridSize * gridSize; + byte[][] buckets = new byte[bucketCount][]; + + for (int bx = 0; bx < gridSize; bx++) { + for (int bz = 0; bz < gridSize; bz++) { + int bucketIdx = bx * gridSize + bz; + if (bucketBuffers != null && bucketBuffers[bucketIdx] != null) { + buckets[bucketIdx] = bucketBuffers[bucketIdx]; + continue; + } + + try (ByteArrayOutputStream bucketBAOS = new ByteArrayOutputStream(); + ZstdOutputStream bucketZstdOut = new ZstdOutputStream(bucketBAOS, compressionLevel); + DataOutputStream bucketDataOut = new DataOutputStream(bucketZstdOut)) { + + boolean hasData = false; + int cellCount = 32 / gridSize; + for (int cx = 0; cx < cellCount; cx++) { + for (int cz = 0; cz < cellCount; cz++) { + int chunkIndex = (bx * cellCount + cx) + (bz * cellCount + cz) * 32; + if (chunkUncompressedSizes[chunkIndex] > 0) { + hasData = true; + byte[] chunkData = new byte[chunkUncompressedSizes[chunkIndex]]; + decompressor.decompress(chunkCompressedBuffers[chunkIndex], 0, chunkData, 0, chunkUncompressedSizes[chunkIndex]); + bucketDataOut.writeInt(chunkData.length + 8); + bucketDataOut.writeLong(chunkTimestamps[chunkIndex]); + bucketDataOut.write(chunkData); + } else { + bucketDataOut.writeInt(0); + bucketDataOut.writeLong(chunkTimestamps[chunkIndex]); + } + } + } + bucketDataOut.close(); + if (hasData) { + buckets[bucketIdx] = bucketBAOS.toByteArray(); + } + } + } + } + return buckets; + } + + private void openBucketForChunk(int chunkX, int chunkZ) { + int modX = Math.floorMod(chunkX, 32); + int modZ = Math.floorMod(chunkZ, 32); + int bucketIdx = chunkToBucketIndex(modX, modZ); + if (bucketBuffers == null || bucketBuffers[bucketIdx] == null) { + return; + } + + try (ByteArrayInputStream bucketBAIS = new ByteArrayInputStream(bucketBuffers[bucketIdx]); + ZstdInputStream bucketZstdIn = new ZstdInputStream(bucketBAIS)) { + + ByteBuffer bucketBuffer = ByteBuffer.wrap(bucketZstdIn.readAllBytes()); + int cellsPerBucket = 32 / gridSize; + int bx = modX / bucketSize, bz = modZ / bucketSize; + for (int cx = 0; cx < cellsPerBucket; cx++) { + for (int cz = 0; cz < cellsPerBucket; cz++) { + int chunkIndex = (bx * cellsPerBucket + cx) + (bz * cellsPerBucket + cz) * 32; + int chunkSize = bucketBuffer.getInt(); + long timestamp = bucketBuffer.getLong(); + chunkTimestamps[chunkIndex] = timestamp; + + if (chunkSize > 0) { + byte[] chunkData = new byte[chunkSize - 8]; + bucketBuffer.get(chunkData); + + int maxCompressedLength = compressor.maxCompressedLength(chunkData.length); + byte[] compressed = new byte[maxCompressedLength]; + int compressedLength = compressor.compress(chunkData, 0, chunkData.length, compressed, 0, maxCompressedLength); + byte[] finalCompressed = new byte[compressedLength]; + System.arraycopy(compressed, 0, finalCompressed, 0, compressedLength); + + chunkCompressedBuffers[chunkIndex] = finalCompressed; + chunkUncompressedSizes[chunkIndex] = chunkData.length; + } + } + } + } catch (IOException ex) { + throw new RuntimeException("Region file corrupted: " + regionFilePath + " bucket: " + bucketIdx, ex); + } + bucketBuffers[bucketIdx] = null; + } + + @Override + public synchronized void write(ChunkPos pos, ByteBuffer buffer) { + openRegionFile(); + openBucketForChunk(pos.x, pos.z); + try { + byte[] rawData = toByteArray(new ByteArrayInputStream(buffer.array())); + int uncompressedSize = rawData.length; + if (uncompressedSize > MAX_CHUNK_SIZE) { + LOGGER.error("Chunk dupe attempt {}", regionFilePath); + clear(pos); + } else { + int maxCompressedLength = compressor.maxCompressedLength(uncompressedSize); + byte[] compressed = new byte[maxCompressedLength]; + int compressedLength = compressor.compress(rawData, 0, uncompressedSize, compressed, 0, maxCompressedLength); + byte[] finalCompressed = new byte[compressedLength]; + System.arraycopy(compressed, 0, finalCompressed, 0, compressedLength); + + int index = getChunkIndex(pos.x, pos.z); + chunkCompressedBuffers[index] = finalCompressed; + chunkTimestamps[index] = currentTimestamp(); + chunkUncompressedSizes[index] = uncompressedSize; + } + } catch (IOException e) { + LOGGER.error("Chunk write IOException {} {}", e, regionFilePath); + } + markToSave(); + } + + @Override + public DataOutputStream getChunkDataOutputStream(ChunkPos pos) { + openRegionFile(); + openBucketForChunk(pos.x, pos.z); + return new DataOutputStream(new BufferedOutputStream(new ChunkBuffer(pos))); + } + + @Override + public MoonriseRegionFileIO.RegionDataController.WriteData moonrise$startWrite(CompoundTag data, ChunkPos pos) { + DataOutputStream out = getChunkDataOutputStream(pos); + return new ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.WriteData( + data, + ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.WriteData.WriteResult.WRITE, + out, + regionFile -> { + try { + out.close(); + } catch (IOException e) { + LOGGER.error("Failed to close region file stream", e); + } + } + ); + } + + private class ChunkBuffer extends ByteArrayOutputStream { + private final ChunkPos pos; + public ChunkBuffer(ChunkPos pos) { + super(); + this.pos = pos; + } + @Override + public void close() { + ByteBuffer byteBuffer = ByteBuffer.wrap(this.buf, 0, this.count); + LinearRegionFile.this.write(this.pos, byteBuffer); + } + } + + private byte[] toByteArray(InputStream in) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + byte[] tempBuffer = new byte[4096]; + int length; + while ((length = in.read(tempBuffer)) >= 0) { + out.write(tempBuffer, 0, length); + } + return out.toByteArray(); + } + + @Nullable + @Override + public synchronized DataInputStream getChunkDataInputStream(ChunkPos pos) { + openRegionFile(); + openBucketForChunk(pos.x, pos.z); + int index = getChunkIndex(pos.x, pos.z); + if (chunkUncompressedSizes[index] != 0) { + byte[] decompressed = new byte[chunkUncompressedSizes[index]]; + decompressor.decompress(chunkCompressedBuffers[index], 0, decompressed, 0, chunkUncompressedSizes[index]); + return new DataInputStream(new ByteArrayInputStream(decompressed)); + } + return null; + } + + @Override + public synchronized void clear(ChunkPos pos) { + openRegionFile(); + openBucketForChunk(pos.x, pos.z); + int index = getChunkIndex(pos.x, pos.z); + chunkCompressedBuffers[index] = null; + chunkUncompressedSizes[index] = 0; + chunkTimestamps[index] = 0; + markToSave(); + } + + @Override + public synchronized void close() throws IOException { + openRegionFile(); + close = true; + try { + flush(); + } catch (IOException e) { + throw new IOException("Region flush IOException " + e + " " + regionFilePath, e); + } + } + + private static int getChunkIndex(int x, int z) { + return (x & 31) + ((z & 31) << 5); + } + + private static int currentTimestamp() { + return (int) (System.currentTimeMillis() / 1000L); + } + + @Override + public boolean recalculateHeader() { + return false; + } + + @Override + public void setOversized(int x, int z, boolean something) { + // stub + } + + @Override + public CompoundTag getOversizedData(int x, int z) throws IOException { + throw new IOException("getOversizedData is a stub " + regionFilePath); + } + + @Override + public boolean isOversized(int x, int z) { + return false; + } + + @Override + public Path getPath() { + return regionFilePath; + } + + private boolean[] deserializeExistenceBitmap(ByteBuffer buffer) { + boolean[] result = new boolean[1024]; + for (int i = 0; i < 128; i++) { + byte b = buffer.get(); + for (int j = 0; j < 8; j++) { + result[i * 8 + j] = ((b >> (7 - j)) & 1) == 1; + } + } + return result; + } + + private void writeExistenceBitmap(DataOutputStream out, boolean[] bitmap) throws IOException { + for (int i = 0; i < 128; i++) { + byte b = 0; + for (int j = 0; j < 8; j++) { + if (bitmap[i * 8 + j]) { + b |= (1 << (7 - j)); + } + } + out.writeByte(b); + } + } + + private int chunkToBucketIndex(int chunkX, int chunkZ) { + int bx = chunkX / bucketSize, bz = chunkZ / bucketSize; + return bx * gridSize + bz; + } + + private int[] parseRegionCoordinates(String fileName) { + int regionX = 0; + int regionZ = 0; + String[] parts = fileName.split("\\."); + if (parts.length >= 4) { + try { + regionX = Integer.parseInt(parts[1]); + regionZ = Integer.parseInt(parts[2]); + } catch (NumberFormatException e) { + LOGGER.error("Failed to parse region coordinates from file name: {}", fileName, e); + } + } else { + LOGGER.warn("Unexpected file name format: {}", fileName); + } + return new int[]{regionX, regionZ}; + } +} diff --git a/divinemc-server/src/main/java/org/stupidcraft/linearpaper/region/IRegionFileFactory.java b/divinemc-server/src/main/java/org/bxteam/divinemc/region/RegionFileFactory.java similarity index 54% rename from divinemc-server/src/main/java/org/stupidcraft/linearpaper/region/IRegionFileFactory.java rename to divinemc-server/src/main/java/org/bxteam/divinemc/region/RegionFileFactory.java index 3252852..fbcdd68 100644 --- a/divinemc-server/src/main/java/org/stupidcraft/linearpaper/region/IRegionFileFactory.java +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/region/RegionFileFactory.java @@ -1,4 +1,4 @@ -package org.stupidcraft.linearpaper.region; +package org.bxteam.divinemc.region; import net.minecraft.world.level.chunk.storage.RegionFile; import net.minecraft.world.level.chunk.storage.RegionFileVersion; @@ -10,34 +10,20 @@ import org.jetbrains.annotations.NotNull; import java.io.IOException; import java.nio.file.Path; -public class IRegionFileFactory { +public class RegionFileFactory { @Contract("_, _, _, _ -> new") public static @NotNull IRegionFile getAbstractRegionFile(RegionStorageInfo storageKey, Path directory, Path path, boolean dsync) throws IOException { return getAbstractRegionFile(storageKey, directory, path, RegionFileVersion.getCompressionFormat(), dsync); } @Contract("_, _, _, _, _ -> new") - public static @NotNull IRegionFile getAbstractRegionFile(RegionStorageInfo storageKey, Path directory, Path path, boolean dsync, boolean canRecalcHeader) throws IOException { - return getAbstractRegionFile(storageKey, directory, path, RegionFileVersion.getCompressionFormat(), dsync, canRecalcHeader); - } - - @Contract("_, _, _, _, _ -> new") - public static @NotNull IRegionFile getAbstractRegionFile(RegionStorageInfo storageKey, Path path, Path directory, RegionFileVersion compressionFormat, boolean dsync) throws IOException { - return getAbstractRegionFile(storageKey, path, directory, compressionFormat, dsync, true); - } - - @Contract("_, _, _, _, _, _ -> new") - public static @NotNull IRegionFile getAbstractRegionFile(RegionStorageInfo storageKey, @NotNull Path path, Path directory, RegionFileVersion compressionFormat, boolean dsync, boolean canRecalcHeader) throws IOException { + public static @NotNull IRegionFile getAbstractRegionFile(RegionStorageInfo storageKey, @NotNull Path path, Path directory, RegionFileVersion compressionFormat, boolean dsync) throws IOException { final String fullFileName = path.getFileName().toString(); final String[] fullNameSplit = fullFileName.split("\\."); final String extensionName = fullNameSplit[fullNameSplit.length - 1]; - switch (EnumRegionFileExtension.fromExtension(extensionName)) { - case UNKNOWN -> { - return new RegionFile(storageKey, path, directory, compressionFormat, dsync); - } - + switch (RegionFileFormat.fromExtension(extensionName)) { case LINEAR -> { - return new LinearRegionFile(path, DivineConfig.linearCompressionLevel); + return new LinearRegionFile(path, DivineConfig.linearImplementation, DivineConfig.linearCompressionLevel); } default -> { diff --git a/divinemc-server/src/main/java/org/stupidcraft/linearpaper/region/EnumRegionFileExtension.java b/divinemc-server/src/main/java/org/bxteam/divinemc/region/RegionFileFormat.java similarity index 53% rename from divinemc-server/src/main/java/org/stupidcraft/linearpaper/region/EnumRegionFileExtension.java rename to divinemc-server/src/main/java/org/bxteam/divinemc/region/RegionFileFormat.java index 2440bd3..30a54e5 100644 --- a/divinemc-server/src/main/java/org/stupidcraft/linearpaper/region/EnumRegionFileExtension.java +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/region/RegionFileFormat.java @@ -1,46 +1,46 @@ -package org.stupidcraft.linearpaper.region; +package org.bxteam.divinemc.region; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; - import java.util.Locale; -public enum EnumRegionFileExtension { +public enum RegionFileFormat { LINEAR(".linear"), - MCA(".mca"), + ANVIL(".mca"), UNKNOWN(null); - private final String extensionName; + private final String extension; - EnumRegionFileExtension(String extensionName) { - this.extensionName = extensionName; + RegionFileFormat(String extension) { + this.extension = extension; } public String getExtensionName() { - return this.extensionName; + return this.extension; } @Contract(pure = true) - public static EnumRegionFileExtension fromName(@NotNull String name) { + public static RegionFileFormat fromName(@NotNull String name) { switch (name.toUpperCase(Locale.ROOT)) { - case "MCA" -> { - return MCA; + case "MCA", "ANVIL" -> { + return ANVIL; } case "LINEAR" -> { return LINEAR; } + default -> { - return UNKNOWN; + throw new IllegalArgumentException("Unknown region file format: " + name); } } } @Contract(pure = true) - public static EnumRegionFileExtension fromExtension(@NotNull String name) { + public static RegionFileFormat fromExtension(@NotNull String name) { switch (name.toLowerCase()) { - case "mca" -> { - return MCA; + case "mca", "anvil" -> { + return ANVIL; } case "linear" -> { 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/CollectionWrapperUtil.java b/divinemc-server/src/main/java/org/bxteam/divinemc/util/CollectionWrapperUtil.java new file mode 100644 index 0000000..1edb1f2 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/util/CollectionWrapperUtil.java @@ -0,0 +1,744 @@ +package org.bxteam.divinemc.util; + +import it.unimi.dsi.fastutil.bytes.ByteIterator; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.IntCollection; +import it.unimi.dsi.fastutil.ints.IntIterator; +import it.unimi.dsi.fastutil.ints.IntSet; +import it.unimi.dsi.fastutil.longs.LongIterator; +import it.unimi.dsi.fastutil.longs.LongListIterator; +import it.unimi.dsi.fastutil.objects.ObjectCollection; +import it.unimi.dsi.fastutil.objects.ObjectIterator; +import it.unimi.dsi.fastutil.objects.ObjectSet; +import it.unimi.dsi.fastutil.objects.ReferenceSet; +import it.unimi.dsi.fastutil.shorts.ShortIterator; +import org.apache.commons.lang3.ArrayUtils; +import org.jetbrains.annotations.NotNull; + +import java.util.Collection; +import java.util.Iterator; +import java.util.ListIterator; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; + +public final class CollectionWrapperUtil { + private CollectionWrapperUtil() { + throw new IllegalStateException("This class cannot be instantiated"); + } + + private static Int2ObjectMap.@NotNull Entry intEntryForwards(Map.Entry entry) { + return new Int2ObjectMap.Entry<>() { + @Override + public T getValue() { + return entry.getValue(); + } + + @Override + public T setValue(T value) { + return entry.setValue(value); + } + + @Override + public int getIntKey() { + return entry.getKey(); + } + + @Override + public boolean equals(Object obj) { + if (obj == entry) { + return true; + } + return super.equals(obj); + } + + @Override + public int hashCode() { + return entry.hashCode(); + } + }; + } + + private static Map.Entry intEntryBackwards(Int2ObjectMap.Entry entry) { + return entry; + } + + public static ObjectSet> entrySetIntWrap(Map map) { + return new ConvertingObjectSet<>( + map.entrySet(), + CollectionWrapperUtil::intEntryForwards, + CollectionWrapperUtil::intEntryBackwards + ); + } + + public static IntSet wrapIntSet(Set intset) { + return new WrappingIntSet(intset); + } + + public static ByteIterator itrByteWrap(Iterator backing) { + return new WrappingByteIterator(backing); + } + + public static ByteIterator itrByteWrap(Iterable backing) { + return itrByteWrap(backing.iterator()); + } + + public static IntIterator itrIntWrap(Iterator backing) { + return new WrappingIntIterator(backing); + } + + public static IntIterator itrIntWrap(Iterable backing) { + return itrIntWrap(backing.iterator()); + } + + public static LongIterator itrLongWrap(Iterator backing) { + return new WrappingLongIterator(backing); + } + + public static LongIterator itrLongWrap(Iterable backing) { + return itrLongWrap(backing.iterator()); + } + + public static ShortIterator itrShortWrap(Iterator backing) { + return new WrappingShortIterator(backing); + } + + public static ShortIterator itrShortWrap(Iterable backing) { + return itrShortWrap(backing.iterator()); + } + + public static LongListIterator wrap(ListIterator c) { + return new WrappingLongListIterator(c); + } + + public static LongListIterator wrap(Iterator c) { + return new SlimWrappingLongListIterator(c); + } + + public static ObjectCollection wrap(Collection c) { + return new WrappingObjectCollection<>(c); + } + + public static ReferenceSet wrap(Set s) { + return new WrappingRefSet<>(s); + } + + public static ObjectIterator itrWrap(Iterable in) { + return new WrapperObjectIterator<>(in.iterator()); + } + + public static class ConvertingObjectSet implements ObjectSet { + private final Set backing; + private final Function forward; + private final Function back; + + public ConvertingObjectSet(Set backing, Function forward, Function back) { + this.backing = Objects.requireNonNull(backing, "Backing set cannot be null"); + this.forward = Objects.requireNonNull(forward, "Forward function cannot be null"); + this.back = Objects.requireNonNull(back, "Backward function cannot be null"); + } + + @Override + public int size() { + return backing.size(); + } + + @Override + public boolean isEmpty() { + return backing.isEmpty(); + } + + @SuppressWarnings("unchecked") + @Override + public boolean contains(Object o) { + try { + return backing.contains(back.apply((T) o)); + } catch (ClassCastException cce) { + return false; + } + } + + @Override + public Object[] toArray() { + return backing.stream().map(forward).toArray(); + } + + @Override + public R[] toArray(R[] a) { + return backing.stream().map(forward).collect(Collectors.toSet()).toArray(a); + } + + @Override + public boolean add(T e) { + return backing.add(back.apply(e)); + } + + @SuppressWarnings("unchecked") + @Override + public boolean remove(Object o) { + try { + return backing.remove(back.apply((T) o)); + } catch (ClassCastException cce) { + return false; + } + } + + @SuppressWarnings("unchecked") + @Override + public boolean containsAll(Collection c) { + try { + return backing.containsAll(c.stream() + .map(i -> back.apply((T) i)) + .collect(Collectors.toSet())); + } catch (ClassCastException cce) { + return false; + } + } + + @Override + public boolean addAll(Collection c) { + return backing.addAll(c.stream().map(back).collect(Collectors.toSet())); + } + + @SuppressWarnings("unchecked") + @Override + public boolean removeAll(Collection c) { + try { + return backing.removeAll(c.stream() + .map(i -> back.apply((T) i)) + .collect(Collectors.toSet())); + } catch (ClassCastException cce) { + return false; + } + } + + @SuppressWarnings("unchecked") + @Override + public boolean retainAll(Collection c) { + try { + return backing.retainAll(c.stream() + .map(i -> back.apply((T) i)) + .collect(Collectors.toSet())); + } catch (ClassCastException cce) { + return false; + } + } + + @Override + public void clear() { + backing.clear(); + } + + @Override + public ObjectIterator iterator() { + return new ObjectIterator<>() { + private final Iterator backg = backing.iterator(); + + @Override + public boolean hasNext() { + return backg.hasNext(); + } + + @Override + public T next() { + return forward.apply(backg.next()); + } + + @Override + public void remove() { + backg.remove(); + } + }; + } + } + + static class WrappingIntIterator implements IntIterator { + private final Iterator backing; + + WrappingIntIterator(Iterator backing) { + this.backing = Objects.requireNonNull(backing); + } + + @Override + public boolean hasNext() { + return backing.hasNext(); + } + + @Override + public int nextInt() { + return backing.next(); + } + + @Override + public Integer next() { + return backing.next(); + } + + @Override + public void remove() { + backing.remove(); + } + } + + static class WrappingLongIterator implements LongIterator { + private final Iterator backing; + + WrappingLongIterator(Iterator backing) { + this.backing = Objects.requireNonNull(backing); + } + + @Override + public boolean hasNext() { + return backing.hasNext(); + } + + @Override + public long nextLong() { + return backing.next(); + } + + @Override + public Long next() { + return backing.next(); + } + + @Override + public void remove() { + backing.remove(); + } + } + + static class WrappingShortIterator implements ShortIterator { + private final Iterator backing; + + WrappingShortIterator(Iterator backing) { + this.backing = Objects.requireNonNull(backing); + } + + @Override + public boolean hasNext() { + return backing.hasNext(); + } + + @Override + public short nextShort() { + return backing.next(); + } + + @Override + public Short next() { + return backing.next(); + } + + @Override + public void remove() { + backing.remove(); + } + } + + static class WrappingByteIterator implements ByteIterator { + private final Iterator backing; + + WrappingByteIterator(Iterator backing) { + this.backing = Objects.requireNonNull(backing); + } + + @Override + public boolean hasNext() { + return backing.hasNext(); + } + + @Override + public byte nextByte() { + return next(); + } + + @Override + public Byte next() { + return backing.next(); + } + + @Override + public void remove() { + backing.remove(); + } + } + + public static class WrappingIntSet implements IntSet { + private final Set backing; + + public WrappingIntSet(Set backing) { + this.backing = Objects.requireNonNull(backing); + } + + @Override + public boolean add(int key) { + return backing.add(key); + } + + @Override + public boolean contains(int key) { + return backing.contains(key); + } + + @Override + public int[] toIntArray() { + return backing.stream().mapToInt(Integer::intValue).toArray(); + } + + @Override + public int[] toArray(int[] a) { + return ArrayUtils.toPrimitive(backing.toArray(new Integer[0])); + } + + @Override + public boolean addAll(IntCollection c) { + return backing.addAll(c); + } + + @Override + public boolean containsAll(IntCollection c) { + return backing.containsAll(c); + } + + @Override + public boolean removeAll(IntCollection c) { + return backing.removeAll(c); + } + + @Override + public boolean retainAll(IntCollection c) { + return backing.retainAll(c); + } + + @Override + public int size() { + return backing.size(); + } + + @Override + public boolean isEmpty() { + return backing.isEmpty(); + } + + @Override + public Object[] toArray() { + return backing.toArray(); + } + + @Override + public T[] toArray(T[] a) { + return backing.toArray(a); + } + + @Override + public boolean containsAll(Collection c) { + return backing.containsAll(c); + } + + @Override + public boolean addAll(Collection c) { + return backing.addAll(c); + } + + @Override + public boolean removeAll(Collection c) { + return backing.removeAll(c); + } + + @Override + public boolean retainAll(Collection c) { + return backing.retainAll(c); + } + + @Override + public void clear() { + backing.clear(); + } + + @Override + public IntIterator iterator() { + return new WrappingIntIterator(backing.iterator()); + } + + @Override + public boolean remove(int k) { + return backing.remove(k); + } + } + + public static class WrappingRefSet implements ReferenceSet { + private final Set backing; + + public WrappingRefSet(Set backing) { + this.backing = Objects.requireNonNull(backing); + } + + @Override + public boolean add(V key) { + return backing.add(key); + } + + @Override + public boolean remove(final Object o) { + return backing.remove(o); + } + + @Override + public boolean containsAll(@NotNull final Collection c) { + return backing.containsAll(c); + } + + @Override + public boolean addAll(@NotNull final Collection c) { + return backing.addAll(c); + } + + @Override + public boolean removeAll(@NotNull final Collection c) { + return backing.removeAll(c); + } + + @Override + public boolean retainAll(@NotNull final Collection c) { + return backing.retainAll(c); + } + + @Override + public void clear() { + this.backing.clear(); + } + + @Override + public int size() { + return backing.size(); + } + + @Override + public boolean isEmpty() { + return backing.isEmpty(); + } + + @Override + public boolean contains(final Object o) { + return backing.contains(o); + } + + @Override + public ObjectIterator iterator() { + return null; + } + + @Override + public @NotNull Object[] toArray() { + return this.backing.toArray(); + } + + @Override + public @NotNull T[] toArray(@NotNull final T[] a) { + return this.backing.toArray(a); + } + } + + public static class WrappingLongListIterator implements LongListIterator { + private final ListIterator backing; + + WrappingLongListIterator(ListIterator backing) { + this.backing = Objects.requireNonNull(backing); + } + + @Override + public long previousLong() { + return backing.previous(); + } + + @Override + public long nextLong() { + return backing.next(); + } + + @Override + public boolean hasNext() { + return backing.hasNext(); + } + + @Override + public boolean hasPrevious() { + return backing.hasPrevious(); + } + + @Override + public int nextIndex() { + return backing.nextIndex(); + } + + @Override + public int previousIndex() { + return backing.previousIndex(); + } + + @Override + public void add(long k) { + backing.add(k); + } + + @Override + public void remove() { + backing.remove(); + } + + @Override + public void set(long k) { + backing.set(k); + } + } + + public static class SlimWrappingLongListIterator implements LongListIterator { + private final Iterator backing; + + SlimWrappingLongListIterator(Iterator backing) { + this.backing = Objects.requireNonNull(backing); + } + + @Override + public long previousLong() { + throw new UnsupportedOperationException(); + } + + @Override + public long nextLong() { + return backing.next(); + } + + @Override + public boolean hasNext() { + return backing.hasNext(); + } + + @Override + public boolean hasPrevious() { + throw new UnsupportedOperationException(); + } + + @Override + public int nextIndex() { + throw new UnsupportedOperationException(); + } + + @Override + public int previousIndex() { + throw new UnsupportedOperationException(); + } + + @Override + public void add(long k) { + throw new UnsupportedOperationException(); + } + + @Override + public void remove() { + backing.remove(); + } + + @Override + public void set(long k) { + throw new UnsupportedOperationException(); + } + } + + public static class WrappingObjectCollection implements ObjectCollection { + private final Collection backing; + + public WrappingObjectCollection(Collection backing) { + this.backing = Objects.requireNonNull(backing); + } + + @Override + public int size() { + return backing.size(); + } + + @Override + public boolean isEmpty() { + return backing.isEmpty(); + } + + @Override + public boolean contains(Object o) { + return backing.contains(o); + } + + @Override + public Object[] toArray() { + return backing.toArray(); + } + + @Override + public T[] toArray(T[] a) { + return backing.toArray(a); + } + + @Override + public boolean add(V e) { + return backing.add(e); + } + + @Override + public boolean remove(Object o) { + return backing.remove(o); + } + + @Override + public boolean containsAll(Collection c) { + return backing.containsAll(c); + } + + @Override + public boolean addAll(Collection c) { + return backing.addAll(c); + } + + @Override + public boolean removeAll(Collection c) { + return backing.removeAll(c); + } + + @Override + public boolean retainAll(Collection c) { + return backing.retainAll(c); + } + + @Override + public void clear() { + backing.clear(); + } + + @Override + public ObjectIterator iterator() { + return itrWrap(backing); + } + } + + private record WrapperObjectIterator(Iterator parent) implements ObjectIterator { + private WrapperObjectIterator(Iterator parent) { + this.parent = Objects.requireNonNull(parent); + } + + @Override + public boolean hasNext() { + return parent.hasNext(); + } + + @Override + public T next() { + return parent.next(); + } + + @Override + public void remove() { + parent.remove(); + } + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/util/ConcurrentFlagMatrix.java b/divinemc-server/src/main/java/org/bxteam/divinemc/util/ConcurrentFlagMatrix.java new file mode 100644 index 0000000..ed26ad6 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/util/ConcurrentFlagMatrix.java @@ -0,0 +1,64 @@ +package org.bxteam.divinemc.util; + +import java.util.concurrent.locks.ReentrantReadWriteLock; +import net.minecraft.world.level.levelgen.structure.structures.WoodlandMansionPieces; + +public class ConcurrentFlagMatrix extends WoodlandMansionPieces.SimpleGrid { + private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock(); + + public ConcurrentFlagMatrix(int rows, int columns, int fallbackValue) { + super(rows, columns, fallbackValue); + } + + public void set(int row, int column, int value) { + this.readWriteLock.writeLock().lock(); + + try { + super.set(row, column, value); + } finally { + this.readWriteLock.writeLock().unlock(); + } + } + + public void set(int startRow, int startColumn, int endRow, int endColumn, int value) { + this.readWriteLock.writeLock().lock(); + + try { + super.set(startRow, startColumn, endRow, endColumn, value); + } finally { + this.readWriteLock.writeLock().unlock(); + } + } + + public int get(int row, int column) { + this.readWriteLock.readLock().lock(); + + int result; + try { + result = super.get(row, column); + } finally { + this.readWriteLock.readLock().unlock(); + } + + return result; + } + + public void setIf(int row, int column, int expectedValue, int newValue) { + if (this.get(row, column) == expectedValue) { + this.set(row, column, newValue); + } + } + + public boolean edgesTo(int row, int column, int value) { + this.readWriteLock.readLock().lock(); + + boolean result; + try { + result = super.edgesTo(row, column, value); + } finally { + this.readWriteLock.readLock().unlock(); + } + + return result; + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/util/Files.java b/divinemc-server/src/main/java/org/bxteam/divinemc/util/Files.java deleted file mode 100644 index 7d02eb2..0000000 --- a/divinemc-server/src/main/java/org/bxteam/divinemc/util/Files.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.bxteam.divinemc.util; - -import java.io.File; -import java.io.IOException; - -public final class Files { - public static void deleteRecursively(File dir) throws IOException { - if (dir == null || !dir.isDirectory()) { - return; - } - - try { - File[] files = dir.listFiles(); - if (files == null) { - throw new IOException("Error enumerating directory during recursive delete operation: " + dir.getAbsolutePath()); - } - - for (File child : files) { - if (child.isDirectory()) { - Files.deleteRecursively(child); - } else if (child.isFile()) { - if (!child.delete()) { - throw new IOException("Error deleting file during recursive delete operation: " + child.getAbsolutePath()); - } - } - } - - if (!dir.delete()) { - throw new IOException("Error deleting directory during recursive delete operation: " + dir.getAbsolutePath()); - } - } catch (SecurityException ex) { - throw new IOException("Security error during recursive delete operation", ex); - } - } -} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/util/MemoryUtil.java b/divinemc-server/src/main/java/org/bxteam/divinemc/util/MemoryUtil.java deleted file mode 100644 index 1c84493..0000000 --- a/divinemc-server/src/main/java/org/bxteam/divinemc/util/MemoryUtil.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.bxteam.divinemc.util; - -public final class MemoryUtil { - public static int[] byte2int(byte[] data) { - if (data == null) return null; - int[] ints = new int[data.length]; - for (int i = 0; i < data.length; i++) { - ints[i] = data[i] & 0xff; - } - return ints; - } -} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/util/PWTTeleportState.java b/divinemc-server/src/main/java/org/bxteam/divinemc/util/PWTTeleportState.java new file mode 100644 index 0000000..21b3c37 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/util/PWTTeleportState.java @@ -0,0 +1,7 @@ +package org.bxteam.divinemc.util; + +public enum PWTTeleportState { + INACTIVE, + PENDING, + CANCELLED +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/util/SynchronizedCodec.java b/divinemc-server/src/main/java/org/bxteam/divinemc/util/SynchronizedCodec.java new file mode 100644 index 0000000..3fca958 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/util/SynchronizedCodec.java @@ -0,0 +1,40 @@ +package org.bxteam.divinemc.util; + +import com.mojang.datafixers.util.Pair; +import com.mojang.serialization.Codec; +import com.mojang.serialization.DataResult; +import com.mojang.serialization.DynamicOps; +import java.util.concurrent.locks.ReentrantLock; + +public class SynchronizedCodec implements Codec { + private final ReentrantLock lock = new ReentrantLock(false); + private final Codec delegate; + + public SynchronizedCodec(Codec delegate) { + this.delegate = delegate; + } + + @Override + public DataResult> decode(DynamicOps ops, T input) { + try { + lock.lockInterruptibly(); + return this.delegate.decode(ops, input); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } finally { + if (lock.isHeldByCurrentThread()) lock.unlock(); + } + } + + @Override + public DataResult encode(A input, DynamicOps ops, T prefix) { + try { + lock.lockInterruptibly(); + return this.delegate.encode(input, ops, prefix); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } finally { + if (lock.isHeldByCurrentThread()) lock.unlock(); + } + } +} 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(); + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/util/map/Int2ObjectConcurrentHashMap.java b/divinemc-server/src/main/java/org/bxteam/divinemc/util/map/Int2ObjectConcurrentHashMap.java new file mode 100644 index 0000000..cac8e64 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/util/map/Int2ObjectConcurrentHashMap.java @@ -0,0 +1,160 @@ +package org.bxteam.divinemc.util.map; + +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.IntSet; +import it.unimi.dsi.fastutil.objects.ObjectCollection; +import it.unimi.dsi.fastutil.objects.ObjectSet; +import org.bxteam.divinemc.util.CollectionWrapperUtil; + +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; + +public final class Int2ObjectConcurrentHashMap implements Int2ObjectMap { + private final ConcurrentHashMap backing; + private V defaultReturnValue; + + public Int2ObjectConcurrentHashMap() { + this(16); + } + + public Int2ObjectConcurrentHashMap(int initialCapacity) { + if (initialCapacity < 0) { + throw new IllegalArgumentException("Initial capacity cannot be negative: " + initialCapacity); + } + + this.backing = new ConcurrentHashMap<>(initialCapacity); + } + + public Int2ObjectConcurrentHashMap(Map map) { + this(Math.max(16, map.size())); + putAll(Objects.requireNonNull(map, "Source map cannot be null")); + } + + @Override + public V get(int key) { + V value = backing.get(key); + return value != null ? value : defaultReturnValue; + } + + @Override + public V get(Object key) { + V value = backing.get(key); + return value != null ? value : defaultReturnValue; + } + + @Override + public boolean isEmpty() { + return backing.isEmpty(); + } + + @Override + public boolean containsValue(Object value) { + return backing.containsValue(value); + } + + @Override + public void putAll(Map m) { + Objects.requireNonNull(m, "Source map cannot be null"); + backing.putAll(m); + } + + @Override + public int size() { + return backing.size(); + } + + @Override + public void defaultReturnValue(V rv) { + this.defaultReturnValue = rv; + } + + @Override + public V defaultReturnValue() { + return defaultReturnValue; + } + + @Override + public ObjectSet> int2ObjectEntrySet() { + return CollectionWrapperUtil.entrySetIntWrap(backing); + } + + @Override + public IntSet keySet() { + return CollectionWrapperUtil.wrapIntSet(backing.keySet()); + } + + @Override + public ObjectCollection values() { + return CollectionWrapperUtil.wrap(backing.values()); + } + + @Override + public boolean containsKey(int key) { + return backing.containsKey(key); + } + + @Override + public V put(int key, V value) { + return backing.put(key, value); + } + + @Override + public V remove(int key) { + return backing.remove(key); + } + + @Override + public void clear() { + backing.clear(); + } + + public V compute(int key, java.util.function.BiFunction remappingFunction) { + Objects.requireNonNull(remappingFunction); + return backing.compute(key, remappingFunction); + } + + public ConcurrentHashMap concurrentView() { + return backing; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Int2ObjectMap that)) return false; + + if (size() != that.size()) return false; + return int2ObjectEntrySet().containsAll(that.int2ObjectEntrySet()); + } + + @Override + public int hashCode() { + return backing.hashCode(); + } + + @Override + public String toString() { + return backing.toString(); + } + + public V getOrDefault(int key, V defaultValue) { + V value = get(key); + return value != null ? value : defaultValue; + } + + public V putIfAbsent(int key, V value) { + return backing.putIfAbsent(key, value); + } + + public boolean remove(int key, Object value) { + return backing.remove(key, value); + } + + public boolean replace(int key, V oldValue, V newValue) { + return backing.replace(key, oldValue, newValue); + } + + public V replace(int key, V value) { + return backing.replace(key, value); + } +} diff --git a/divinemc-server/src/main/java/org/stupidcraft/linearpaper/region/LinearRegionFile.java b/divinemc-server/src/main/java/org/stupidcraft/linearpaper/region/LinearRegionFile.java deleted file mode 100644 index 2ec67ce..0000000 --- a/divinemc-server/src/main/java/org/stupidcraft/linearpaper/region/LinearRegionFile.java +++ /dev/null @@ -1,308 +0,0 @@ -package org.stupidcraft.linearpaper.region; - -import ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO; -import com.github.luben.zstd.ZstdInputStream; -import com.github.luben.zstd.ZstdOutputStream; -import com.mojang.logging.LogUtils; -import net.jpountz.lz4.LZ4Compressor; -import net.jpountz.lz4.LZ4Factory; -import net.jpountz.lz4.LZ4FastDecompressor; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.world.level.ChunkPos; -import org.bxteam.divinemc.DivineConfig; -import org.slf4j.Logger; - -import java.io.BufferedOutputStream; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.nio.ByteBuffer; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardCopyOption; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.TimeUnit; -import javax.annotation.Nullable; - -public class LinearRegionFile implements IRegionFile { - private static final long SUPERBLOCK = -4323716122432332390L; - private static final byte VERSION = 2; - private static final int HEADER_SIZE = 32; - private static final int FOOTER_SIZE = 8; - private static final Logger LOGGER = LogUtils.getLogger(); - private static final List SUPPORTED_VERSIONS = Arrays.asList((byte) 1, (byte) 2); - private final byte[][] buffer = new byte[1024][]; - private final int[] bufferUncompressedSize = new int[1024]; - private final int[] chunkTimestamps = new int[1024]; - private final LZ4Compressor compressor; - private final LZ4FastDecompressor decompressor; - private final int compressionLevel; - public boolean closed = false; - public Path path; - private volatile long lastFlushed = System.nanoTime(); - - public LinearRegionFile(Path file, int compression) throws IOException { - this.path = file; - this.compressionLevel = compression; - this.compressor = LZ4Factory.fastestInstance().fastCompressor(); - this.decompressor = LZ4Factory.fastestInstance().fastDecompressor(); - - File regionFile = new File(this.path.toString()); - - Arrays.fill(this.bufferUncompressedSize, 0); - - if (!regionFile.canRead()) return; - - try (FileInputStream fileStream = new FileInputStream(regionFile); - DataInputStream rawDataStream = new DataInputStream(fileStream)) { - - long superBlock = rawDataStream.readLong(); - if (superBlock != SUPERBLOCK) - throw new RuntimeException("Invalid superblock: " + superBlock + " in " + file); - - byte version = rawDataStream.readByte(); - if (!SUPPORTED_VERSIONS.contains(version)) - throw new RuntimeException("Invalid version: " + version + " in " + file); - - rawDataStream.skipBytes(11); - - int dataCount = rawDataStream.readInt(); - long fileLength = file.toFile().length(); - if (fileLength != HEADER_SIZE + dataCount + FOOTER_SIZE) - throw new IOException("Invalid file length: " + this.path + " " + fileLength + " " + (HEADER_SIZE + dataCount + FOOTER_SIZE)); - - rawDataStream.skipBytes(8); - - byte[] rawCompressed = new byte[dataCount]; - rawDataStream.readFully(rawCompressed, 0, dataCount); - - superBlock = rawDataStream.readLong(); - if (superBlock != SUPERBLOCK) - throw new IOException("Footer superblock invalid " + this.path); - - try (DataInputStream dataStream = new DataInputStream(new ZstdInputStream(new ByteArrayInputStream(rawCompressed)))) { - - int[] starts = new int[1024]; - for (int i = 0; i < 1024; i++) { - starts[i] = dataStream.readInt(); - dataStream.skipBytes(4); - } - - for (int i = 0; i < 1024; i++) { - if (starts[i] > 0) { - int size = starts[i]; - byte[] b = new byte[size]; - dataStream.readFully(b, 0, size); - - int maxCompressedLength = this.compressor.maxCompressedLength(size); - byte[] compressed = new byte[maxCompressedLength]; - int compressedLength = this.compressor.compress(b, 0, size, compressed, 0, maxCompressedLength); - b = new byte[compressedLength]; - System.arraycopy(compressed, 0, b, 0, compressedLength); - - this.buffer[i] = b; - this.bufferUncompressedSize[i] = size; - } - } - } - } - } - - private static int getChunkIndex(int x, int z) { - return (x & 31) + ((z & 31) << 5); - } - - private static int getTimestamp() { - return (int) (System.currentTimeMillis() / 1000L); - } - - public void flush() throws IOException { - flushWrapper(); - } - - public void flushWrapper() { - try { - save(); - } catch (IOException e) { - LOGGER.error("Failed to flush region file {}", path.toAbsolutePath(), e); - } - } - - public boolean doesChunkExist(ChunkPos pos) throws Exception { - throw new Exception("doesChunkExist is a stub"); - } - - private synchronized void save() throws IOException { - long timestamp = getTimestamp(); - short chunkCount = 0; - - File tempFile = new File(path.toString() + ".tmp"); - - try (FileOutputStream fileStream = new FileOutputStream(tempFile); - ByteArrayOutputStream zstdByteArray = new ByteArrayOutputStream(); - ZstdOutputStream zstdStream = new ZstdOutputStream(zstdByteArray, this.compressionLevel); - DataOutputStream zstdDataStream = new DataOutputStream(zstdStream); - DataOutputStream dataStream = new DataOutputStream(fileStream)) { - - dataStream.writeLong(SUPERBLOCK); - dataStream.writeByte(VERSION); - dataStream.writeLong(timestamp); - dataStream.writeByte(this.compressionLevel); - - ArrayList byteBuffers = new ArrayList<>(); - for (int i = 0; i < 1024; i++) { - if (this.bufferUncompressedSize[i] != 0) { - chunkCount += 1; - byte[] content = new byte[bufferUncompressedSize[i]]; - this.decompressor.decompress(buffer[i], 0, content, 0, bufferUncompressedSize[i]); - - byteBuffers.add(content); - } else byteBuffers.add(null); - } - for (int i = 0; i < 1024; i++) { - zstdDataStream.writeInt(this.bufferUncompressedSize[i]); - zstdDataStream.writeInt(this.chunkTimestamps[i]); - } - for (int i = 0; i < 1024; i++) { - if (byteBuffers.get(i) != null) - zstdDataStream.write(byteBuffers.get(i), 0, byteBuffers.get(i).length); - } - zstdDataStream.close(); - - dataStream.writeShort(chunkCount); - - byte[] compressed = zstdByteArray.toByteArray(); - - dataStream.writeInt(compressed.length); - dataStream.writeLong(0); - - dataStream.write(compressed, 0, compressed.length); - dataStream.writeLong(SUPERBLOCK); - - dataStream.flush(); - fileStream.getFD().sync(); - fileStream.getChannel().force(true); - } - Files.move(tempFile.toPath(), this.path, StandardCopyOption.REPLACE_EXISTING); - this.lastFlushed = System.nanoTime(); - } - - public synchronized void write(ChunkPos pos, ByteBuffer buffer) { - try { - byte[] b = toByteArray(new ByteArrayInputStream(buffer.array())); - int uncompressedSize = b.length; - - int maxCompressedLength = this.compressor.maxCompressedLength(b.length); - byte[] compressed = new byte[maxCompressedLength]; - int compressedLength = this.compressor.compress(b, 0, b.length, compressed, 0, maxCompressedLength); - b = new byte[compressedLength]; - System.arraycopy(compressed, 0, b, 0, compressedLength); - - int index = getChunkIndex(pos.x, pos.z); - this.buffer[index] = b; - this.chunkTimestamps[index] = getTimestamp(); - this.bufferUncompressedSize[getChunkIndex(pos.x, pos.z)] = uncompressedSize; - } catch (IOException e) { - LOGGER.error("Chunk write IOException {} {}", e, this.path); - } - - if ((System.nanoTime() - this.lastFlushed) >= TimeUnit.NANOSECONDS.toSeconds(DivineConfig.linearFlushFrequency)) { - this.flushWrapper(); - } - } - - public DataOutputStream getChunkDataOutputStream(ChunkPos pos) { - return new DataOutputStream(new BufferedOutputStream(new ChunkBuffer(pos))); - } - - @Override - public MoonriseRegionFileIO.RegionDataController.WriteData moonrise$startWrite(CompoundTag data, ChunkPos pos) { - final DataOutputStream out = this.getChunkDataOutputStream(pos); - - return new ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.WriteData( - data, ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.WriteData.WriteResult.WRITE, - out, regionFile -> out.close() - ); - } - - private byte[] toByteArray(InputStream in) throws IOException { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - byte[] tempBuffer = new byte[4096]; - - int length; - while ((length = in.read(tempBuffer)) >= 0) { - out.write(tempBuffer, 0, length); - } - - return out.toByteArray(); - } - - @Nullable - public synchronized DataInputStream getChunkDataInputStream(ChunkPos pos) { - if (this.bufferUncompressedSize[getChunkIndex(pos.x, pos.z)] != 0) { - byte[] content = new byte[bufferUncompressedSize[getChunkIndex(pos.x, pos.z)]]; - this.decompressor.decompress(this.buffer[getChunkIndex(pos.x, pos.z)], 0, content, 0, bufferUncompressedSize[getChunkIndex(pos.x, pos.z)]); - return new DataInputStream(new ByteArrayInputStream(content)); - } - return null; - } - - public void clear(ChunkPos pos) { - int i = getChunkIndex(pos.x, pos.z); - this.buffer[i] = null; - this.bufferUncompressedSize[i] = 0; - this.chunkTimestamps[i] = getTimestamp(); - this.flushWrapper(); - } - - public Path getPath() { - return this.path; - } - - public boolean hasChunk(ChunkPos pos) { - return this.bufferUncompressedSize[getChunkIndex(pos.x, pos.z)] > 0; - } - - public void close() throws IOException { - if (closed) return; - closed = true; - flush(); - } - - public boolean recalculateHeader() { - return false; - } - - public void setOversized(int x, int z, boolean something) { - } - - public CompoundTag getOversizedData(int x, int z) throws IOException { - throw new IOException("getOversizedData is a stub " + this.path); - } - - public boolean isOversized(int x, int z) { - return false; - } - - private class ChunkBuffer extends ByteArrayOutputStream { - private final ChunkPos pos; - - public ChunkBuffer(ChunkPos chunkcoordintpair) { - super(); - this.pos = chunkcoordintpair; - } - - public void close() { - ByteBuffer bytebuffer = ByteBuffer.wrap(this.buf, 0, this.count); - LinearRegionFile.this.write(this.pos, bytebuffer); - } - } -} diff --git a/gradle.properties b/gradle.properties index 04cdb81..5e8c97a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,7 +2,7 @@ group = org.bxteam.divinemc version=1.21.5-R0.1-SNAPSHOT mcVersion=1.21.5 -purpurRef=f1e732aa3a02167f8eef3ba5c440000453e175d1 +purpurRef=bdeba761ac3db3184ab23a0b7badfa963ea1ce2e experimental=true org.gradle.configuration-cache=true