diff --git a/lophine-server/minecraft-patches/features/0137-Anonymous-Object-RegionizedTaskQueue-queue-TTL-optim.patch b/lophine-server/minecraft-patches/features/0137-Anonymous-Object-RegionizedTaskQueue-queue-TTL-optim.patch new file mode 100644 index 0000000..879e58b --- /dev/null +++ b/lophine-server/minecraft-patches/features/0137-Anonymous-Object-RegionizedTaskQueue-queue-TTL-optim.patch @@ -0,0 +1,146 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Helvetica Volubi +Date: Sat, 1 Aug 2026 16:30:53 +0800 +Subject: [PATCH] Anonymous Object: RegionizedTaskQueue queue TTL optimization + +As requested by the original author, this patch will be anonymous and hide the original commit address. +This patch is used with permission from the original author. +Licensed under: GPL-3.0 (https://www.gnu.org/licenses/gpl-3.0.html) + +diff --git a/io/papermc/paper/threadedregions/RegionizedTaskQueue.java b/io/papermc/paper/threadedregions/RegionizedTaskQueue.java +index fe0a6bd90cba4869a0ee11723832f2dee5039f62..53579b6faa9d55485088d59ec2fd1e4251c2fb01 100644 +--- a/io/papermc/paper/threadedregions/RegionizedTaskQueue.java ++++ b/io/papermc/paper/threadedregions/RegionizedTaskQueue.java +@@ -23,6 +23,7 @@ import java.util.concurrent.atomic.AtomicLong; + public final class RegionizedTaskQueue { + + private static final TicketType TASK_QUEUE_TICKET = ChunkSystemTicketType.create("task_queue_ticket", Long::compareTo); ++ private static final long QUEUE_MAX_TTL_TICKS = 5L; // Anonymous - RegionizedTaskQueue queue TTL optimization + + public PrioritisedExecutor.PrioritisedTask createChunkTask(final ServerLevel world, final int chunkX, final int chunkZ, + final Runnable run) { +@@ -155,6 +156,58 @@ public final class RegionizedTaskQueue { + } + } + ++ // Anonymous start - RegionizedTaskQueue queue TTL optimization ++ public void tickQueueReferenceTTL() { ++ final ThreadedRegionizer.ThreadedRegion currentRegion ++ = io.papermc.paper.threadedregions.TickRegionScheduler.getCurrentRegion(); ++ if (currentRegion == null) { ++ return; ++ } ++ ++ final ReferenceCountData[] toRemoveTicket = new ReferenceCountData[1]; ++ ++ for (ConcurrentChainedLong2ReferenceHashTable.TableEntry counterEntry : this.referenceCounters.entrySet()) { ++ final long coord = counterEntry.getKey(); ++ final ReferenceCountData counterData = counterEntry.getValue(); ++ ++ // only tick for our region ++ if (currentRegion == this.world.regioniser.getRegionAtUnsynchronised(CoordinateUtils.getChunkX(coord), CoordinateUtils.getChunkZ(coord))) { ++ long curr = counterData.referenceTTL.get(); ++ // successfully decreased ttl ++ if (curr == (curr = counterData.referenceTTL.compareAndExchange(curr, curr - 1))) { ++ if (counterData.referenceCount.get() != 0L) { ++ // still has reference, pump back ++ counterData.referenceTTL.set(QUEUE_MAX_TTL_TICKS); ++ continue; ++ } ++ ++ // dead ++ if (curr <= 0) { ++ // parsed from decrementReference ++ this.referenceCounters.computeIfPresent(coord, (final long keyInMap, final ReferenceCountData valueInMap) -> { ++ // might be increased again ++ if (valueInMap.referenceCount.get() != 0L) { ++ valueInMap.referenceTTL.set(QUEUE_MAX_TTL_TICKS); // still has reference, pump back ++ return valueInMap; // directly, the ttl was already charged in add logic ++ } ++ ++ // note: valueInMap may not be referenceCountData ++ toRemoveTicket[0] = valueInMap; ++ ++ return null; ++ }); ++ ++ if (toRemoveTicket[0] != null) { ++ this.removeTicket(coord, toRemoveTicket[0].id); ++ toRemoveTicket[0] = null; ++ } ++ } ++ } ++ } ++ } ++ } ++ // Anonymous end - RegionizedTaskQueue queue TTL optimization ++ + private void decrementReference(final ReferenceCountData referenceCountData, final long coord) { + if (!referenceCountData.decreaseReferenceCount()) { + return; +@@ -212,9 +265,10 @@ public final class RegionizedTaskQueue { + private final long id = ID_GENERATOR.getAndIncrement(); + + public final AtomicLong referenceCount = new AtomicLong(1L); ++ public final AtomicLong referenceTTL = new AtomicLong(QUEUE_MAX_TTL_TICKS); // Anonymous - RegionizedTaskQueue queue TTL optimization + public volatile boolean addedTicket; + +- // returns false if reference count is 0, otherwise increments ref count ++ // returns false if reference count or ttl is 0, otherwise increments ref count // Anonymous - RegionizedTaskQueue queue TTL optimization + public boolean addCount() { + int failures = 0; + for (long curr = this.referenceCount.get();;) { +@@ -227,6 +281,27 @@ public final class RegionizedTaskQueue { + } + + if (curr == (curr = this.referenceCount.compareAndExchange(curr, curr + 1L))) { ++ // Anonymous start - RegionizedTaskQueue queue TTL optimization ++ // now force charge back the ttl to max ++ int ttlFailures = 0; ++ for (long currTTL = this.referenceTTL.get();;) { ++ for (int i = 0; i < ttlFailures; i++) { ++ Thread.onSpinWait(); ++ } ++ ++ // add failed, rollback (ttl reached) ++ if (currTTL <= 0) { ++ this.referenceCount.decrementAndGet(); // revert the addition ++ return false; ++ } ++ ++ if (currTTL == (currTTL = this.referenceTTL.compareAndExchange(currTTL, QUEUE_MAX_TTL_TICKS))) { ++ break; ++ } ++ ++ ++ttlFailures; ++ } ++ // Anonymous end - RegionizedTaskQueue queue TTL optimization + return true; + } + +@@ -234,11 +309,11 @@ public final class RegionizedTaskQueue { + } + } + +- // returns true if new reference count is 0 ++ // returns true if new reference count and ttl is 0 // Anonymous - RegionizedTaskQueue queue TTL optimization + public boolean decreaseReferenceCount() { + final long res = this.referenceCount.decrementAndGet(); + if (res >= 0L) { +- return res == 0L; ++ return res == 0L && this.referenceTTL.get() <= 0L; // Anonymous - RegionizedTaskQueue queue TTL optimization + } else { + throw new IllegalStateException("Negative reference count"); + } +diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java +index 6c5cdfebb3a362c51b3b940397e10536dc67afc8..4a00b2399a190b609f7b0944e407f3b087e617b5 100644 +--- a/net/minecraft/server/MinecraftServer.java ++++ b/net/minecraft/server/MinecraftServer.java +@@ -1756,6 +1756,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop +Date: Sat, 1 Aug 2026 16:31:57 +0800 +Subject: [PATCH] Anonymous Object: Reduce ticket operations in nether portal + searching + +As requested by the original author, this patch will be anonymous and hide the original commit address. +This patch is used with permission from the original author. +Licensed under: GPL-3.0 (https://www.gnu.org/licenses/gpl-3.0.html) + +diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java +index 132122f23a697109696852cf383fa1b0186e0210..619521d8ff74c92a822bde9c160931cdfdb7da43 100644 +--- a/net/minecraft/server/level/ServerLevel.java ++++ b/net/minecraft/server/level/ServerLevel.java +@@ -221,6 +221,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet + private final StructureCheck structureCheck; + public final boolean tickTime; // Folia - region threading + private final LevelDebugSynchronizers debugSynchronizers = new LevelDebugSynchronizers(this); ++ public final io.anonymous.anonymous.utils.ReferenceCountingChunkLoader portalSearchingChunkLoader = new io.anonymous.anonymous.utils.ReferenceCountingChunkLoader(this, net.minecraft.world.level.chunk.status.ChunkStatus.EMPTY); // Anonymous - Reduce ticket operations in nether portal searching + // Luminol start - Level schedulers + private static final java.util.concurrent.atomic.AtomicInteger UNLOADER_ID_GEN = new java.util.concurrent.atomic.AtomicInteger(0); + public final me.earthme.luminol.utils.thread.LevelAwareRegionScheduler levelScheduler = new me.earthme.luminol.utils.thread.LevelAwareRegionScheduler(io.papermc.paper.threadedregions.TickRegions.getScheduler().scheduler); +@@ -1004,6 +1005,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet + + this.debugSynchronizers.tick(this.server.debugSubscribers()); + profiler.pop(); ++ this.portalSearchingChunkLoader.tickChunkReferenceTTL(); // Anonymous - Reduce ticket operations in nether portal searching + } + + // Folia start - region threading +diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java +index 489011f9af49fa3407038bd2f00439443307d179..340e6ad83ba93f3927a08a107e3c8dbafa546112 100644 +--- a/net/minecraft/world/entity/Entity.java ++++ b/net/minecraft/world/entity/Entity.java +@@ -4870,10 +4870,10 @@ public abstract class Entity + ); + + // kick off search for existing portal or creation +- destination.moonrise$loadChunksAsync( ++ destination.portalSearchingChunkLoader.loadChunksAsync( // Anonymous - Reduce ticket operations in nether portal searching + // add 32 so that the final search for a portal frame doesn't load any chunks + targetPos, portalSearchRadius + 32, +- net.minecraft.world.level.chunk.status.ChunkStatus.EMPTY, ++ //net.minecraft.world.level.chunk.status.ChunkStatus.EMPTY, // Anonymous - Reduce ticket operations in nether portal searching + ca.spottedleaf.concurrentutil.util.Priority.HIGH, + (chunks) -> { + BlockUtil.FoundRectangle portal = diff --git a/lophine-server/src/main/java/dev/kaiijumc/kaiiju/KaiijuEntityLimits.java b/lophine-server/src/main/java/dev/kaiijumc/kaiiju/KaiijuEntityLimits.java index 079bdd0..331e6f9 100644 --- a/lophine-server/src/main/java/dev/kaiijumc/kaiiju/KaiijuEntityLimits.java +++ b/lophine-server/src/main/java/dev/kaiijumc/kaiiju/KaiijuEntityLimits.java @@ -37,7 +37,7 @@ import java.util.logging.Level; @SuppressWarnings("unused") public class KaiijuEntityLimits { private static final Logger LOGGER = LogUtils.getLogger(); - private static final File CONFIG_FOLDER = new File("shiroha_config"); + private static final File CONFIG_FOLDER = new File("luminol_config"); protected static final String HEADER = "Per region entity limits for Kaiiju.\n" diff --git a/lophine-server/src/main/java/io/anonymous/anonymous/utils/ReferenceCountingChunkLoader.java b/lophine-server/src/main/java/io/anonymous/anonymous/utils/ReferenceCountingChunkLoader.java new file mode 100644 index 0000000..f3fe6cd --- /dev/null +++ b/lophine-server/src/main/java/io/anonymous/anonymous/utils/ReferenceCountingChunkLoader.java @@ -0,0 +1,312 @@ +package io.anonymous.anonymous.utils; + +import ca.spottedleaf.concurrentutil.completable.CallbackCompletable; +import ca.spottedleaf.concurrentutil.map.concurrent.longs.ConcurrentChainedLong2ReferenceHashTable; +import ca.spottedleaf.concurrentutil.util.Priority; +import ca.spottedleaf.moonrise.common.util.CoordinateUtils; +import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler; +import io.papermc.paper.threadedregions.RegionizedServer; +import io.papermc.paper.threadedregions.ThreadedRegionizer; +import io.papermc.paper.threadedregions.TickRegions; +import it.unimi.dsi.fastutil.Pair; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.status.ChunkStatus; +import org.jspecify.annotations.NonNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Consumer; + +/** + * 一个简单的基于folia的RegionizedTaskQueue引用计数的轻量ticket区块加载器 + * 用于在folia传送门搜索的高频率scheduleChunkLoad的调用下减少ticket操作从而减轻锁负载 + * 大部分内容物均取自folia的RegionizedTaskQueue(引用计数), ttl机制取自我的优化 + * + * @see io.papermc.paper.threadedregions.RegionizedTaskQueue + * @see Entity#findOrCreatePortalAsync(ServerLevel, BlockPos, ServerLevel, Entity.PortalType, CallbackCompletable) + */ +public class ReferenceCountingChunkLoader { + private static final long MAX_CHUNK_TTL_TICKS = 2L; + + private final ServerLevel world; + private final ChunkStatus targetStatus; + private final ConcurrentChainedLong2ReferenceHashTable referenceCounters = new ConcurrentChainedLong2ReferenceHashTable<>(); + + public ReferenceCountingChunkLoader(ServerLevel world, ChunkStatus targetStatus) { + this.world = world; + this.targetStatus = targetStatus; + } + + private void decrementReference(final @NonNull ReferenceCountData referenceCountData, final long coord) { + if (!referenceCountData.decreaseReferenceCount()) { + return; + } + + final ReferenceCountData[] toRemoveTicket = new ReferenceCountData[1]; + + this.referenceCounters.computeIfPresent(coord, (final long keyInMap, final ReferenceCountData valueInMap) -> { + if (valueInMap.referenceCount.get() != 0L) { + return valueInMap; + } + + toRemoveTicket[0] = valueInMap; + + return null; + }); + + if (toRemoveTicket[0] != null) { + this.removeTicket(coord, toRemoveTicket[0].id); + } + } + + private void removeTicket(final long coord, final long id) { + this.world.moonrise$getChunkTaskScheduler().chunkHolderManager.removeTicketAtLevel( + ChunkTaskScheduler.CHUNK_LOAD, coord, ChunkTaskScheduler.getTicketLevel(this.targetStatus), Long.valueOf(id) + ); + } + + private void addTicket(final long coord, final long id) { + this.world.moonrise$getChunkTaskScheduler().chunkHolderManager.addTicketAtLevel( + ChunkTaskScheduler.CHUNK_LOAD, coord, ChunkTaskScheduler.getTicketLevel(this.targetStatus), Long.valueOf(id) + ); + } + + private void processTicketUpdates(final long coord) { + this.world.moonrise$getChunkTaskScheduler().chunkHolderManager.processTicketUpdates(CoordinateUtils.getChunkX(coord), CoordinateUtils.getChunkZ(coord)); + } + + private void ensureTicketAdded(final long coord, final @NonNull ReferenceCountData referenceCountData) { + if (!referenceCountData.addedTicket) { + this.addTicket(coord, referenceCountData.id); + this.processTicketUpdates(coord); + referenceCountData.addedTicket = true; + } + } + + public final void loadChunksAsync(final @NonNull BlockPos pos, final int radiusBlocks, + final Priority priority, + final Consumer> onLoad) { + this.loadChunksAsync( + (pos.getX() - radiusBlocks) >> 4, + (pos.getX() + radiusBlocks) >> 4, + (pos.getZ() - radiusBlocks) >> 4, + (pos.getZ() + radiusBlocks) >> 4, + priority, onLoad + ); + } + + public final void loadChunksAsync(final int minChunkX, final int maxChunkX, final int minChunkZ, final int maxChunkZ, + final Priority priority, + final Consumer> onLoad) { + this.loadChunksAsync(minChunkX, maxChunkX, minChunkZ, maxChunkZ, priority, onLoad, null); + } + + + public final void loadChunksAsync(final int minChunkX, final int maxChunkX, final int minChunkZ, final int maxChunkZ, + final Priority priority, + final Consumer> onLoad, final Consumer onEachLoad) { + final int requiredChunks = (maxChunkX - minChunkX + 1) * (maxChunkZ - minChunkZ + 1); + final AtomicInteger loadedChunks = new AtomicInteger(); + + final List> ret = new ArrayList<>(requiredChunks); + + final Consumer consumer = (final ChunkAccess chunk) -> { + + if (chunk != null) { + final long pos = chunk.getPos().longKey(); + + synchronized (ret) { + ret.add(Pair.of(this.incrementReference(pos), chunk)); + } + } + + if (onEachLoad != null) { + onEachLoad.accept(chunk); + } + + if (loadedChunks.incrementAndGet() == requiredChunks) { + try { + if (onLoad != null) { + final List processed = new ArrayList<>(ret.size()); + + for (Pair result : ret) { + processed.add(result.right()); + } + + onLoad.accept(processed); + } + } finally { + for (Pair extraRefEntry : ret) { + this.decrementReference(extraRefEntry.left(), extraRefEntry.right().getPos().longKey()); + } + } + } + }; + + for (int cx = minChunkX; cx <= maxChunkX; ++cx) { + for (int cz = minChunkZ; cz <= maxChunkZ; ++cz) { + this.loadAsync(cx, cz, consumer, priority); + } + } + } + + public void loadAsync(int chunkX, int chunkZ, Consumer callback, Priority priority) { + final long coord = CoordinateUtils.getChunkKey(chunkX, chunkZ); + final ReferenceCountData increased = this.incrementReference(coord); + + final ChunkAccess cached = increased.cached; + if (cached != null) { + RegionizedServer.getInstance().taskQueue.queueChunkTask(this.world, chunkX, chunkZ, () -> { + try { + callback.accept(cached); + }finally { + this.decrementReference(increased, coord); + } + }, priority); + + return; + } + + this.world.moonrise$getChunkTaskScheduler().scheduleChunkLoad( + chunkX, chunkZ, this.targetStatus, true, priority, + chunk -> { + try { + increased.cached = chunk; + + callback.accept(chunk); + }finally { + this.decrementReference(increased, coord); + } + } + ); + } + + private ReferenceCountData incrementReference(final long coord) { + ReferenceCountData referenceCountData = this.referenceCounters.get(coord); + + if (referenceCountData != null && referenceCountData.addCount()) { + this.ensureTicketAdded(coord, referenceCountData); + return referenceCountData; + } + + referenceCountData = this.referenceCounters.compute(coord, (final long keyInMap, final ReferenceCountData valueInMap) -> { + if (valueInMap == null) { + return new ReferenceCountData(); + } + + valueInMap.referenceCount.getAndIncrement(); + + return valueInMap; + }); + + this.ensureTicketAdded(coord, referenceCountData); + + return referenceCountData; + } + + public void tickChunkReferenceTTL() { + final ThreadedRegionizer.ThreadedRegion currentRegion + = io.papermc.paper.threadedregions.TickRegionScheduler.getCurrentRegion(); + if (currentRegion == null) { + return; + } + + final ReferenceCountData[] toRemoveTicket = new ReferenceCountData[1]; + + for (ConcurrentChainedLong2ReferenceHashTable.TableEntry counterEntry : this.referenceCounters.entrySet()) { + final long coord = counterEntry.getKey(); + final ReferenceCountData counterData = counterEntry.getValue(); + + if (currentRegion == this.world.regioniser.getRegionAtUnsynchronised(CoordinateUtils.getChunkX(coord), CoordinateUtils.getChunkZ(coord))) { + long curr = counterData.referenceTTL.get(); + if (curr == (curr = counterData.referenceTTL.compareAndExchange(curr, curr - 1))) { + if (counterData.referenceCount.get() != 0L) { + counterData.referenceTTL.set(MAX_CHUNK_TTL_TICKS); + continue; + } + + if (curr <= 0) { + this.referenceCounters.computeIfPresent(coord, (final long keyInMap, final ReferenceCountData valueInMap) -> { + if (valueInMap.referenceCount.get() != 0L) { + valueInMap.referenceTTL.set(MAX_CHUNK_TTL_TICKS); + return valueInMap; + } + + toRemoveTicket[0] = valueInMap; + + return null; + }); + + if (toRemoveTicket[0] != null) { + this.removeTicket(coord, toRemoveTicket[0].id); + toRemoveTicket[0] = null; + } + } + } + } + } + } + + private static final class ReferenceCountData { + + private static final AtomicLong ID_GENERATOR = new AtomicLong(); + + private final long id = ID_GENERATOR.getAndIncrement(); + + public final AtomicLong referenceCount = new AtomicLong(1L); + public final AtomicLong referenceTTL = new AtomicLong(MAX_CHUNK_TTL_TICKS); + + public volatile ChunkAccess cached; + public volatile boolean addedTicket; + + public boolean addCount() { + int failures = 0; + for (long curr = this.referenceCount.get();;) { + for (int i = 0; i < failures; ++i) { + Thread.onSpinWait(); + } + + if (curr == 0L) { + return false; + } + + if (curr == (curr = this.referenceCount.compareAndExchange(curr, curr + 1L))) { + int ttlFailures = 0; + for (long currTTL = this.referenceTTL.get();;) { + for (int i = 0; i < ttlFailures; i++) { + Thread.onSpinWait(); + } + + if (currTTL <= 0) { + this.referenceCount.decrementAndGet(); + return false; + } + + if (currTTL == (currTTL = this.referenceTTL.compareAndExchange(currTTL, MAX_CHUNK_TTL_TICKS))) { + break; + } + + ++ttlFailures; + } + return true; + } + + ++failures; + } + } + + public boolean decreaseReferenceCount() { + final long res = this.referenceCount.decrementAndGet(); + if (res >= 0L) { + return res == 0L && this.referenceTTL.get() <= 0L; + } else { + throw new IllegalStateException("Negative reference count"); + } + } + } +}