From 38428c0d6cf9aeea31fffe8f63b8a92c982c8a4f Mon Sep 17 00:00:00 2001 From: Spottedleaf Date: Wed, 19 Jun 2024 10:29:03 -0700 Subject: [PATCH] Cleanup MCUtils patch for chunk system Remove utilities that are unused, as well as replacing the full chunk map with a concurrentutil implementation. Additionally, fix the addition/removal of chunks to/from the full chunk map so that getChunkIfLoaded correctly returns a non-null chunk when calling the load or unload events. --- leaf_notes.txt | 9 - patches/server/0009-MC-Utils.patch | 2347 +---------------- patches/server/0010-Adventure.patch | 6 +- ...oleAppender-for-console-improvements.patch | 4 +- patches/server/0020-Plugin-remapping.patch | 2 +- patches/server/0023-Timings-v2.patch | 30 +- ...025-Further-improve-server-tick-loop.patch | 2 +- ...0034-Expose-server-build-information.patch | 4 +- ...vent-block-entity-and-entity-crashes.patch | 4 +- patches/server/0044-Optimize-explosions.patch | 4 +- .../0063-Add-exception-reporting-event.patch | 6 +- ...ckPhysicsEvent-if-a-plugin-has-a-lis.patch | 2 +- ...80-Configurable-Chunk-Inhabited-Time.patch | 4 +- .../0089-Configurable-Player-Collision.patch | 2 +- ...-possibility-for-getServer-singleton.patch | 4 +- .../0098-Async-GameProfileCache-saving.patch | 2 +- ...le-async-calls-to-restart-the-server.patch | 2 +- .../server/0137-Basic-PlayerProfile-API.patch | 2 +- ...5-PlayerNaturallySpawnCreaturesEvent.patch | 10 +- ...nt-extended-PaperServerListPingEvent.patch | 2 +- ...ies-option-to-debug-dupe-uuid-issues.patch | 6 +- ...dd-Early-Warning-Feature-to-WatchDog.patch | 2 +- ...er-Thread-Pool-and-Thread-Priorities.patch | 2 +- .../0264-Optimize-World-Time-Updates.patch | 2 +- .../0277-Async-command-map-building.patch | 2 +- patches/server/0286-Server-Tick-Events.patch | 2 +- .../0312-Tracking-Range-Improvements.patch | 4 +- ...e-getChunkAt-calls-for-loaded-chunks.patch | 6 +- .../0316-Add-debug-for-sync-chunk-loads.patch | 4 +- .../server/0326-Optimise-Chunk-getFluid.patch | 6 +- ...-Add-tick-times-API-and-mspt-command.patch | 4 +- ...-PlayerChunkMap-adds-crashing-server.patch | 4 +- ...nEvent-when-Player-is-actually-ready.patch | 4 +- .../server/0354-misc-debugging-dumps.patch | 2 +- ...Wait-for-Async-Tasks-during-shutdown.patch | 2 +- ...ld-Difficulty-Remembering-Difficulty.patch | 6 +- .../0410-Cache-block-data-strings.patch | 4 +- ...-non-whitelisted-player-when-white-l.patch | 4 +- ...465-Add-ServerResourcesReloadedEvent.patch | 6 +- patches/server/0489-Add-EntityMoveEvent.patch | 2 +- ...telist-use-configurable-kick-message.patch | 4 +- .../0548-Add-PlayerKickEvent-causes.patch | 4 +- ...d-getChunkAt-has-inlined-logic-for-l.patch | 16 +- ...primise-map-impl-for-tracked-players.patch | 4 +- ...vanilla-BiomeProvider-from-WorldInfo.patch | 2 +- ...sks-fairly-for-worlds-while-waiting-.patch | 2 +- ...efault-CustomSpawners-in-custom-worl.patch | 2 +- ...o-worldlist-before-initing-the-world.patch | 2 +- patches/server/0674-Custom-Potion-Mixes.patch | 4 +- ...n-on-world-create-while-being-ticked.patch | 4 +- ...-on-plugins-accessing-faraway-chunks.patch | 2 +- ...ix-plugin-loggers-on-server-shutdown.patch | 2 +- .../0751-Fix-a-bunch-of-vanilla-bugs.patch | 4 +- ...x-premature-player-kicks-on-shutdown.patch | 4 +- .../0780-Player-Entity-Tracking-Events.patch | 4 +- .../server/0826-Fix-block-place-logic.patch | 4 +- ...Folia-scheduler-and-owned-region-API.patch | 2 +- ...0855-Only-capture-actual-tree-growth.patch | 4 +- ...ntity-tracking-range-by-Y-coordinate.patch | 4 +- ...k-if-we-can-see-non-visible-entities.patch | 4 +- ...n-t-fire-sync-events-during-worldgen.patch | 2 +- ...e-experience-dropping-on-block-break.patch | 4 +- ...llocation-of-Vec3D-by-entity-tracker.patch | 4 +- ...ing-message-for-initial-server-start.patch | 2 +- ...invalid-block-entity-during-world-ge.patch | 4 +- ...5-Per-world-ticks-per-spawn-settings.patch | 2 +- ...and-End-Portal-Frames-from-being-des.patch | 4 +- .../0975-Brigadier-based-command-API.patch | 6 +- ...ing-BlockPlaceEvent-calling-onRemove.patch | 6 +- ...Chunk-System-Starlight-from-Moonrise.patch | 237 +- ...ldBounds-and-getBlockState-for-inlin.patch | 2 +- ...cing-for-EntityLiving-hasLineOfSight.patch | 2 +- .../1000-Entity-Activation-Range-2.0.patch | 2 +- .../1001-Optional-per-player-mob-spawns.patch | 8 +- patches/server/1002-Anti-Xray.patch | 6 +- ...nate-Current-redstone-implementation.patch | 4 +- ...ng-PreCreatureSpawnEvent-with-per-pl.patch | 8 +- patches/server/1008-Optimize-Hoppers.patch | 2 +- .../1023-Improved-Watchdog-Support.patch | 12 +- 79 files changed, 365 insertions(+), 2552 deletions(-) diff --git a/leaf_notes.txt b/leaf_notes.txt index 7c8d0c37cc..4bd9fdf957 100644 --- a/leaf_notes.txt +++ b/leaf_notes.txt @@ -1,13 +1,4 @@ - note: for paper, the chunk debug command -- rebase IntervalledCounter into util patch - mcutil diff - paper debug chunks --async in DedicatedServer - TODO keep around region file lock? -- mcutil#getTicketLevelFor is wrong, just delete it later - -on another note, clean up mcutils... - -apply todo in levelmixin - -to fix later: -- Change loadedChunkMap in ServerChunkCache to use concurrent long map diff --git a/patches/server/0009-MC-Utils.patch b/patches/server/0009-MC-Utils.patch index 1d50fab8f5..c12a8e7c5c 100644 --- a/patches/server/0009-MC-Utils.patch +++ b/patches/server/0009-MC-Utils.patch @@ -12,456 +12,6 @@ public net.minecraft.server.level.ServerChunkCache mainThreadProcessor public net.minecraft.server.level.ServerChunkCache$MainThreadExecutor public net.minecraft.world.level.chunk.LevelChunkSection states -diff --git a/src/main/java/com/destroystokyo/paper/util/concurrent/WeakSeqLock.java b/src/main/java/com/destroystokyo/paper/util/concurrent/WeakSeqLock.java -new file mode 100644 -index 0000000000000000000000000000000000000000..4029dc68cf35d63aa70c4a76c35bf65a7fc6358f ---- /dev/null -+++ b/src/main/java/com/destroystokyo/paper/util/concurrent/WeakSeqLock.java -@@ -0,0 +1,68 @@ -+package com.destroystokyo.paper.util.concurrent; -+ -+import java.util.concurrent.atomic.AtomicLong; -+ -+/** -+ * copied from https://github.com/Spottedleaf/ConcurrentUtil/blob/master/src/main/java/ca/spottedleaf/concurrentutil/lock/WeakSeqLock.java -+ * @author Spottedleaf -+ */ -+public final class WeakSeqLock { -+ // TODO when the switch to J11 is made, nuke this class from orbit -+ -+ protected final AtomicLong lock = new AtomicLong(); -+ -+ public WeakSeqLock() { -+ //VarHandle.storeStoreFence(); // warn: usages must be checked to ensure this behaviour isn't needed -+ } -+ -+ public void acquireWrite() { -+ // must be release-type write -+ this.lock.lazySet(this.lock.get() + 1); -+ } -+ -+ public boolean canRead(final long read) { -+ return (read & 1) == 0; -+ } -+ -+ public boolean tryAcquireWrite() { -+ this.acquireWrite(); -+ return true; -+ } -+ -+ public void releaseWrite() { -+ // must be acquire-type write -+ final long lock = this.lock.get(); // volatile here acts as store-store -+ this.lock.lazySet(lock + 1); -+ } -+ -+ public void abortWrite() { -+ // must be acquire-type write -+ final long lock = this.lock.get(); // volatile here acts as store-store -+ this.lock.lazySet(lock ^ 1); -+ } -+ -+ public long acquireRead() { -+ int failures = 0; -+ long curr; -+ -+ for (curr = this.lock.get(); !this.canRead(curr); curr = this.lock.get()) { -+ // without j11, our only backoff is the yield() call... -+ -+ if (++failures > 5_000) { /* TODO determine a threshold */ -+ Thread.yield(); -+ } -+ /* Better waiting is beyond the scope of this lock; if it is needed the lock is being misused */ -+ } -+ -+ //VarHandle.loadLoadFence(); // volatile acts as the load-load barrier -+ return curr; -+ } -+ -+ public boolean tryReleaseRead(final long read) { -+ return this.lock.get() == read; // volatile acts as the load-load barrier -+ } -+ -+ public long getSequentialCounter() { -+ return this.lock.get(); -+ } -+} -diff --git a/src/main/java/com/destroystokyo/paper/util/map/QueuedChangesMapLong2Int.java b/src/main/java/com/destroystokyo/paper/util/map/QueuedChangesMapLong2Int.java -new file mode 100644 -index 0000000000000000000000000000000000000000..59868f37d14bbc0ece0836095cdad148778995e6 ---- /dev/null -+++ b/src/main/java/com/destroystokyo/paper/util/map/QueuedChangesMapLong2Int.java -@@ -0,0 +1,162 @@ -+package com.destroystokyo.paper.util.map; -+ -+import com.destroystokyo.paper.util.concurrent.WeakSeqLock; -+import it.unimi.dsi.fastutil.longs.Long2IntMap; -+import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; -+import it.unimi.dsi.fastutil.longs.LongIterator; -+import it.unimi.dsi.fastutil.longs.LongOpenHashSet; -+import it.unimi.dsi.fastutil.objects.ObjectIterator; -+ -+/** -+ * @author Spottedleaf -+ */ -+public class QueuedChangesMapLong2Int { -+ -+ protected final Long2IntOpenHashMap updatingMap; -+ protected final Long2IntOpenHashMap visibleMap; -+ protected final Long2IntOpenHashMap queuedPuts; -+ protected final LongOpenHashSet queuedRemove; -+ -+ protected int queuedDefaultReturnValue; -+ -+ // we use a seqlock as writes are not common. -+ protected final WeakSeqLock updatingMapSeqLock = new WeakSeqLock(); -+ -+ public QueuedChangesMapLong2Int() { -+ this(16, 0.75f); -+ } -+ -+ public QueuedChangesMapLong2Int(final int capacity, final float loadFactor) { -+ this.updatingMap = new Long2IntOpenHashMap(capacity, loadFactor); -+ this.visibleMap = new Long2IntOpenHashMap(capacity, loadFactor); -+ this.queuedPuts = new Long2IntOpenHashMap(); -+ this.queuedRemove = new LongOpenHashSet(); -+ } -+ -+ public void queueDefaultReturnValue(final int dfl) { -+ this.queuedDefaultReturnValue = dfl; -+ this.updatingMap.defaultReturnValue(dfl); -+ } -+ -+ public int queueUpdate(final long k, final int v) { -+ this.queuedRemove.remove(k); -+ this.queuedPuts.put(k, v); -+ -+ return this.updatingMap.put(k, v); -+ } -+ -+ public int queueRemove(final long k) { -+ this.queuedPuts.remove(k); -+ this.queuedRemove.add(k); -+ -+ return this.updatingMap.remove(k); -+ } -+ -+ public int getUpdating(final long k) { -+ return this.updatingMap.get(k); -+ } -+ -+ public int getVisible(final long k) { -+ return this.visibleMap.get(k); -+ } -+ -+ public int getVisibleAsync(final long k) { -+ long readlock; -+ int ret = 0; -+ -+ do { -+ readlock = this.updatingMapSeqLock.acquireRead(); -+ try { -+ ret = this.visibleMap.get(k); -+ } catch (final Throwable thr) { -+ if (thr instanceof ThreadDeath) { -+ throw (ThreadDeath)thr; -+ } -+ // ignore... -+ continue; -+ } -+ -+ } while (!this.updatingMapSeqLock.tryReleaseRead(readlock)); -+ -+ return ret; -+ } -+ -+ public boolean performUpdates() { -+ this.updatingMapSeqLock.acquireWrite(); -+ this.visibleMap.defaultReturnValue(this.queuedDefaultReturnValue); -+ this.updatingMapSeqLock.releaseWrite(); -+ -+ if (this.queuedPuts.isEmpty() && this.queuedRemove.isEmpty()) { -+ return false; -+ } -+ -+ // update puts -+ final ObjectIterator iterator0 = this.queuedPuts.long2IntEntrySet().fastIterator(); -+ while (iterator0.hasNext()) { -+ final Long2IntMap.Entry entry = iterator0.next(); -+ final long key = entry.getLongKey(); -+ final int val = entry.getIntValue(); -+ -+ this.updatingMapSeqLock.acquireWrite(); -+ try { -+ this.visibleMap.put(key, val); -+ } finally { -+ this.updatingMapSeqLock.releaseWrite(); -+ } -+ } -+ -+ this.queuedPuts.clear(); -+ -+ final LongIterator iterator1 = this.queuedRemove.iterator(); -+ while (iterator1.hasNext()) { -+ final long key = iterator1.nextLong(); -+ -+ this.updatingMapSeqLock.acquireWrite(); -+ try { -+ this.visibleMap.remove(key); -+ } finally { -+ this.updatingMapSeqLock.releaseWrite(); -+ } -+ } -+ -+ this.queuedRemove.clear(); -+ -+ return true; -+ } -+ -+ public boolean performUpdatesLockMap() { -+ this.updatingMapSeqLock.acquireWrite(); -+ try { -+ this.visibleMap.defaultReturnValue(this.queuedDefaultReturnValue); -+ -+ if (this.queuedPuts.isEmpty() && this.queuedRemove.isEmpty()) { -+ return false; -+ } -+ -+ // update puts -+ final ObjectIterator iterator0 = this.queuedPuts.long2IntEntrySet().fastIterator(); -+ while (iterator0.hasNext()) { -+ final Long2IntMap.Entry entry = iterator0.next(); -+ final long key = entry.getLongKey(); -+ final int val = entry.getIntValue(); -+ -+ this.visibleMap.put(key, val); -+ } -+ -+ this.queuedPuts.clear(); -+ -+ final LongIterator iterator1 = this.queuedRemove.iterator(); -+ while (iterator1.hasNext()) { -+ final long key = iterator1.nextLong(); -+ -+ this.visibleMap.remove(key); -+ } -+ -+ this.queuedRemove.clear(); -+ -+ return true; -+ } finally { -+ this.updatingMapSeqLock.releaseWrite(); -+ } -+ } -+} -diff --git a/src/main/java/com/destroystokyo/paper/util/map/QueuedChangesMapLong2Object.java b/src/main/java/com/destroystokyo/paper/util/map/QueuedChangesMapLong2Object.java -new file mode 100644 -index 0000000000000000000000000000000000000000..7bab31a312463cc963d9621cdc543a281459bd32 ---- /dev/null -+++ b/src/main/java/com/destroystokyo/paper/util/map/QueuedChangesMapLong2Object.java -@@ -0,0 +1,202 @@ -+package com.destroystokyo.paper.util.map; -+ -+import com.destroystokyo.paper.util.concurrent.WeakSeqLock; -+import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; -+import it.unimi.dsi.fastutil.longs.Long2ObjectMap; -+import it.unimi.dsi.fastutil.objects.ObjectBidirectionalIterator; -+import java.util.ArrayList; -+import java.util.Collection; -+import java.util.List; -+ -+/** -+ * @author Spottedleaf -+ */ -+public class QueuedChangesMapLong2Object { -+ -+ protected static final Object REMOVED = new Object(); -+ -+ protected final Long2ObjectLinkedOpenHashMap updatingMap; -+ protected final Long2ObjectLinkedOpenHashMap visibleMap; -+ protected final Long2ObjectLinkedOpenHashMap queuedChanges; -+ -+ // we use a seqlock as writes are not common. -+ protected final WeakSeqLock updatingMapSeqLock = new WeakSeqLock(); -+ -+ public QueuedChangesMapLong2Object() { -+ this(16, 0.75f); // dfl for fastutil -+ } -+ -+ public QueuedChangesMapLong2Object(final int capacity, final float loadFactor) { -+ this.updatingMap = new Long2ObjectLinkedOpenHashMap<>(capacity, loadFactor); -+ this.visibleMap = new Long2ObjectLinkedOpenHashMap<>(capacity, loadFactor); -+ this.queuedChanges = new Long2ObjectLinkedOpenHashMap<>(); -+ } -+ -+ public V queueUpdate(final long k, final V value) { -+ this.queuedChanges.put(k, value); -+ return this.updatingMap.put(k, value); -+ } -+ -+ public V queueRemove(final long k) { -+ this.queuedChanges.put(k, REMOVED); -+ return this.updatingMap.remove(k); -+ } -+ -+ public V getUpdating(final long k) { -+ return this.updatingMap.get(k); -+ } -+ -+ public boolean updatingContainsKey(final long k) { -+ return this.updatingMap.containsKey(k); -+ } -+ -+ public V getVisible(final long k) { -+ return this.visibleMap.get(k); -+ } -+ -+ public boolean visibleContainsKey(final long k) { -+ return this.visibleMap.containsKey(k); -+ } -+ -+ public V getVisibleAsync(final long k) { -+ long readlock; -+ V ret = null; -+ -+ do { -+ readlock = this.updatingMapSeqLock.acquireRead(); -+ -+ try { -+ ret = this.visibleMap.get(k); -+ } catch (final Throwable thr) { -+ if (thr instanceof ThreadDeath) { -+ throw (ThreadDeath)thr; -+ } -+ // ignore... -+ continue; -+ } -+ -+ } while (!this.updatingMapSeqLock.tryReleaseRead(readlock)); -+ -+ return ret; -+ } -+ -+ public boolean visibleContainsKeyAsync(final long k) { -+ long readlock; -+ boolean ret = false; -+ -+ do { -+ readlock = this.updatingMapSeqLock.acquireRead(); -+ -+ try { -+ ret = this.visibleMap.containsKey(k); -+ } catch (final Throwable thr) { -+ if (thr instanceof ThreadDeath) { -+ throw (ThreadDeath)thr; -+ } -+ // ignore... -+ continue; -+ } -+ -+ } while (!this.updatingMapSeqLock.tryReleaseRead(readlock)); -+ -+ return ret; -+ } -+ -+ public Long2ObjectLinkedOpenHashMap getVisibleMap() { -+ return this.visibleMap; -+ } -+ -+ public Long2ObjectLinkedOpenHashMap getUpdatingMap() { -+ return this.updatingMap; -+ } -+ -+ public int getVisibleSize() { -+ return this.visibleMap.size(); -+ } -+ -+ public int getVisibleSizeAsync() { -+ long readlock; -+ int ret; -+ -+ do { -+ readlock = this.updatingMapSeqLock.acquireRead(); -+ ret = this.visibleMap.size(); -+ } while (!this.updatingMapSeqLock.tryReleaseRead(readlock)); -+ -+ return ret; -+ } -+ -+ // unlike mojang's impl this cannot be used async since it's not a view of an immutable map -+ public Collection getUpdatingValues() { -+ return this.updatingMap.values(); -+ } -+ -+ public List getUpdatingValuesCopy() { -+ return new ArrayList<>(this.updatingMap.values()); -+ } -+ -+ // unlike mojang's impl this cannot be used async since it's not a view of an immutable map -+ public Collection getVisibleValues() { -+ return this.visibleMap.values(); -+ } -+ -+ public List getVisibleValuesCopy() { -+ return new ArrayList<>(this.visibleMap.values()); -+ } -+ -+ public boolean performUpdates() { -+ if (this.queuedChanges.isEmpty()) { -+ return false; -+ } -+ -+ final ObjectBidirectionalIterator> iterator = this.queuedChanges.long2ObjectEntrySet().fastIterator(); -+ while (iterator.hasNext()) { -+ final Long2ObjectMap.Entry entry = iterator.next(); -+ final long key = entry.getLongKey(); -+ final Object val = entry.getValue(); -+ -+ this.updatingMapSeqLock.acquireWrite(); -+ try { -+ if (val == REMOVED) { -+ this.visibleMap.remove(key); -+ } else { -+ this.visibleMap.put(key, (V)val); -+ } -+ } finally { -+ this.updatingMapSeqLock.releaseWrite(); -+ } -+ } -+ -+ this.queuedChanges.clear(); -+ return true; -+ } -+ -+ public boolean performUpdatesLockMap() { -+ if (this.queuedChanges.isEmpty()) { -+ return false; -+ } -+ -+ final ObjectBidirectionalIterator> iterator = this.queuedChanges.long2ObjectEntrySet().fastIterator(); -+ -+ try { -+ this.updatingMapSeqLock.acquireWrite(); -+ -+ while (iterator.hasNext()) { -+ final Long2ObjectMap.Entry entry = iterator.next(); -+ final long key = entry.getLongKey(); -+ final Object val = entry.getValue(); -+ -+ if (val == REMOVED) { -+ this.visibleMap.remove(key); -+ } else { -+ this.visibleMap.put(key, (V)val); -+ } -+ } -+ } finally { -+ this.updatingMapSeqLock.releaseWrite(); -+ } -+ -+ this.queuedChanges.clear(); -+ return true; -+ } -+} diff --git a/src/main/java/com/destroystokyo/paper/util/maplist/ChunkList.java b/src/main/java/com/destroystokyo/paper/util/maplist/ChunkList.java new file mode 100644 index 0000000000000000000000000000000000000000..554f4d4e63c1431721989e6f502a32ccc53a8807 @@ -2178,495 +1728,12 @@ index 46cab7a8c7b87ab01b26074b04f5a02b3907cfc4..49019b4a9bc4e634d54a9b0acaf9229a + } + // Paper end } -diff --git a/src/main/java/io/papermc/paper/chunk/SingleThreadChunkRegionManager.java b/src/main/java/io/papermc/paper/chunk/SingleThreadChunkRegionManager.java -new file mode 100644 -index 0000000000000000000000000000000000000000..a5f706d6f716b2a463ae58adcde69d9e665c7733 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/chunk/SingleThreadChunkRegionManager.java -@@ -0,0 +1,477 @@ -+package io.papermc.paper.chunk; -+ -+import io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet; -+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; -+import it.unimi.dsi.fastutil.objects.ReferenceLinkedOpenHashSet; -+import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; -+import io.papermc.paper.util.MCUtil; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.world.level.ChunkPos; -+import java.util.ArrayList; -+import java.util.Arrays; -+import java.util.Iterator; -+import java.util.List; -+import java.util.function.Supplier; -+ -+public final class SingleThreadChunkRegionManager { -+ -+ protected final int regionSectionMergeRadius; -+ protected final int regionSectionChunkSize; -+ public final int regionChunkShift; // log2(REGION_CHUNK_SIZE) -+ -+ public final ServerLevel world; -+ public final String name; -+ -+ protected final Long2ObjectOpenHashMap regionsBySection = new Long2ObjectOpenHashMap<>(); -+ protected final ReferenceLinkedOpenHashSet needsRecalculation = new ReferenceLinkedOpenHashSet<>(); -+ protected final int minSectionRecalcCount; -+ protected final double maxDeadRegionPercent; -+ protected final Supplier regionDataSupplier; -+ protected final Supplier regionSectionDataSupplier; -+ -+ public SingleThreadChunkRegionManager(final ServerLevel world, final int minSectionRecalcCount, -+ final double maxDeadRegionPercent, final int sectionMergeRadius, -+ final int regionSectionChunkShift, -+ final String name, final Supplier regionDataSupplier, -+ final Supplier regionSectionDataSupplier) { -+ this.regionSectionMergeRadius = sectionMergeRadius; -+ this.regionSectionChunkSize = 1 << regionSectionChunkShift; -+ this.regionChunkShift = regionSectionChunkShift; -+ this.world = world; -+ this.name = name; -+ this.minSectionRecalcCount = Math.max(2, minSectionRecalcCount); -+ this.maxDeadRegionPercent = maxDeadRegionPercent; -+ this.regionDataSupplier = regionDataSupplier; -+ this.regionSectionDataSupplier = regionSectionDataSupplier; -+ } -+ -+ // tested via https://gist.github.com/Spottedleaf/aa7ade3451c37b4cac061fc77074db2f -+ -+ /* -+ protected void check() { -+ ReferenceOpenHashSet> checked = new ReferenceOpenHashSet<>(); -+ -+ for (RegionSection section : this.regionsBySection.values()) { -+ if (!checked.add(section.region)) { -+ section.region.check(); -+ } -+ } -+ for (Region region : this.needsRecalculation) { -+ region.check(); -+ } -+ } -+ */ -+ -+ protected void addToRecalcQueue(final Region region) { -+ this.needsRecalculation.add(region); -+ } -+ -+ protected void removeFromRecalcQueue(final Region region) { -+ this.needsRecalculation.remove(region); -+ } -+ -+ public RegionSection getRegionSection(final int chunkX, final int chunkZ) { -+ return this.regionsBySection.get(MCUtil.getCoordinateKey(chunkX >> this.regionChunkShift, chunkZ >> this.regionChunkShift)); -+ } -+ -+ public Region getRegion(final int chunkX, final int chunkZ) { -+ final RegionSection section = this.regionsBySection.get(MCUtil.getCoordinateKey(chunkX >> regionChunkShift, chunkZ >> regionChunkShift)); -+ return section != null ? section.region : null; -+ } -+ -+ private final List toMerge = new ArrayList<>(); -+ -+ protected RegionSection getOrCreateAndMergeSection(final int sectionX, final int sectionZ, final RegionSection force) { -+ final long sectionKey = MCUtil.getCoordinateKey(sectionX, sectionZ); -+ -+ if (force == null) { -+ RegionSection region = this.regionsBySection.get(sectionKey); -+ if (region != null) { -+ return region; -+ } -+ } -+ -+ int mergeCandidateSectionSize = -1; -+ Region mergeIntoCandidate = null; -+ -+ // find optimal candidate to merge into -+ -+ final int minX = sectionX - this.regionSectionMergeRadius; -+ final int maxX = sectionX + this.regionSectionMergeRadius; -+ final int minZ = sectionZ - this.regionSectionMergeRadius; -+ final int maxZ = sectionZ + this.regionSectionMergeRadius; -+ for (int currX = minX; currX <= maxX; ++currX) { -+ for (int currZ = minZ; currZ <= maxZ; ++currZ) { -+ final RegionSection section = this.regionsBySection.get(MCUtil.getCoordinateKey(currX, currZ)); -+ if (section == null) { -+ continue; -+ } -+ final Region region = section.region; -+ if (region.dead) { -+ throw new IllegalStateException("Dead region should not be in live region manager state: " + region); -+ } -+ final int sections = region.sections.size(); -+ -+ if (sections > mergeCandidateSectionSize) { -+ mergeCandidateSectionSize = sections; -+ mergeIntoCandidate = region; -+ } -+ this.toMerge.add(region); -+ } -+ } -+ -+ // merge -+ if (mergeIntoCandidate != null) { -+ for (int i = 0; i < this.toMerge.size(); ++i) { -+ final Region region = this.toMerge.get(i); -+ if (region.dead || mergeIntoCandidate == region) { -+ continue; -+ } -+ region.mergeInto(mergeIntoCandidate); -+ } -+ this.toMerge.clear(); -+ } else { -+ mergeIntoCandidate = new Region(this); -+ } -+ -+ final RegionSection section; -+ if (force == null) { -+ this.regionsBySection.put(sectionKey, section = new RegionSection(sectionKey, this)); -+ } else { -+ final RegionSection existing = this.regionsBySection.putIfAbsent(sectionKey, force); -+ if (existing != null) { -+ throw new IllegalStateException("Attempting to override section '" + existing.toStringWithRegion() + -+ ", with " + force.toStringWithRegion()); -+ } -+ -+ section = force; -+ } -+ -+ mergeIntoCandidate.addRegionSection(section); -+ //mergeIntoCandidate.check(); -+ //this.check(); -+ -+ return section; -+ } -+ -+ public void addChunk(final int chunkX, final int chunkZ) { -+ this.getOrCreateAndMergeSection(chunkX >> this.regionChunkShift, chunkZ >> this.regionChunkShift, null).addChunk(chunkX, chunkZ); -+ } -+ -+ public void removeChunk(final int chunkX, final int chunkZ) { -+ final RegionSection section = this.regionsBySection.get( -+ MCUtil.getCoordinateKey(chunkX >> this.regionChunkShift, chunkZ >> this.regionChunkShift) -+ ); -+ if (section != null) { -+ section.removeChunk(chunkX, chunkZ); -+ } else { -+ throw new IllegalStateException("Cannot remove chunk at (" + chunkX + "," + chunkZ + ") from region state, section does not exist"); -+ } -+ } -+ -+ public void recalculateRegions() { -+ for (int i = 0, len = this.needsRecalculation.size(); i < len; ++i) { -+ final Region region = this.needsRecalculation.removeFirst(); -+ -+ this.recalculateRegion(region); -+ //this.check(); -+ } -+ } -+ -+ protected void recalculateRegion(final Region region) { -+ region.markedForRecalc = false; -+ //region.check(); -+ // clear unused regions -+ for (final Iterator iterator = region.deadSections.iterator(); iterator.hasNext();) { -+ final RegionSection deadSection = iterator.next(); -+ -+ if (deadSection.hasChunks()) { -+ throw new IllegalStateException("Dead section '" + deadSection.toStringWithRegion() + "' is marked dead but has chunks!"); -+ } -+ if (!region.removeRegionSection(deadSection)) { -+ throw new IllegalStateException("Region " + region + " has inconsistent state, it should contain section " + deadSection); -+ } -+ if (!this.regionsBySection.remove(deadSection.regionCoordinate, deadSection)) { -+ throw new IllegalStateException("Cannot remove dead section '" + -+ deadSection.toStringWithRegion() + "' from section state! State at section coordinate: " + -+ this.regionsBySection.get(deadSection.regionCoordinate)); -+ } -+ } -+ region.deadSections.clear(); -+ -+ // implicitly cover cases where size == 0 -+ if (region.sections.size() < this.minSectionRecalcCount) { -+ //region.check(); -+ return; -+ } -+ -+ // run a test to see if we actually need to recalculate -+ // TODO -+ -+ // destroy and rebuild the region -+ region.dead = true; -+ -+ // destroy region state -+ for (final Iterator iterator = region.sections.unsafeIterator(IteratorSafeOrderedReferenceSet.ITERATOR_FLAG_SEE_ADDITIONS); iterator.hasNext();) { -+ final RegionSection aliveSection = iterator.next(); -+ if (!aliveSection.hasChunks()) { -+ throw new IllegalStateException("Alive section '" + aliveSection.toStringWithRegion() + "' has no chunks!"); -+ } -+ if (!this.regionsBySection.remove(aliveSection.regionCoordinate, aliveSection)) { -+ throw new IllegalStateException("Cannot remove alive section '" + -+ aliveSection.toStringWithRegion() + "' from section state! State at section coordinate: " + -+ this.regionsBySection.get(aliveSection.regionCoordinate)); -+ } -+ } -+ -+ // rebuild regions -+ for (final Iterator iterator = region.sections.unsafeIterator(IteratorSafeOrderedReferenceSet.ITERATOR_FLAG_SEE_ADDITIONS); iterator.hasNext();) { -+ final RegionSection aliveSection = iterator.next(); -+ this.getOrCreateAndMergeSection(aliveSection.getSectionX(), aliveSection.getSectionZ(), aliveSection); -+ } -+ } -+ -+ public static final class Region { -+ protected final IteratorSafeOrderedReferenceSet sections = new IteratorSafeOrderedReferenceSet<>(); -+ protected final ReferenceOpenHashSet deadSections = new ReferenceOpenHashSet<>(16, 0.7f); -+ protected boolean dead; -+ protected boolean markedForRecalc; -+ -+ public final SingleThreadChunkRegionManager regionManager; -+ public final RegionData regionData; -+ -+ protected Region(final SingleThreadChunkRegionManager regionManager) { -+ this.regionManager = regionManager; -+ this.regionData = regionManager.regionDataSupplier.get(); -+ } -+ -+ public IteratorSafeOrderedReferenceSet.Iterator getSections() { -+ return this.sections.iterator(IteratorSafeOrderedReferenceSet.ITERATOR_FLAG_SEE_ADDITIONS); -+ } -+ -+ protected final double getDeadSectionPercent() { -+ return (double)this.deadSections.size() / (double)this.sections.size(); -+ } -+ -+ /* -+ protected void check() { -+ if (this.dead) { -+ throw new IllegalStateException("Dead region!"); -+ } -+ for (final Iterator> iterator = this.sections.unsafeIterator(IteratorSafeOrderedReferenceSet.ITERATOR_FLAG_SEE_ADDITIONS); iterator.hasNext();) { -+ final RegionSection section = iterator.next(); -+ if (section.region != this) { -+ throw new IllegalStateException("Region section must point to us!"); -+ } -+ if (this.regionManager.regionsBySection.get(section.regionCoordinate) != section) { -+ throw new IllegalStateException("Region section must match the regionmanager state!"); -+ } -+ } -+ } -+ */ -+ -+ // note: it is not true that the region at this point is not in any region. use the region field on the section -+ // to see if it is currently in another region. -+ protected final boolean addRegionSection(final RegionSection section) { -+ if (!this.sections.add(section)) { -+ return false; -+ } -+ -+ section.sectionData.addToRegion(section, section.region, this); -+ -+ section.region = this; -+ return true; -+ } -+ -+ protected final boolean removeRegionSection(final RegionSection section) { -+ if (!this.sections.remove(section)) { -+ return false; -+ } -+ -+ section.sectionData.removeFromRegion(section, this); -+ -+ return true; -+ } -+ -+ protected void mergeInto(final Region mergeTarget) { -+ if (this == mergeTarget) { -+ throw new IllegalStateException("Cannot merge a region onto itself"); -+ } -+ if (this.dead) { -+ throw new IllegalStateException("Source region is dead! Source " + this + ", target " + mergeTarget); -+ } else if (mergeTarget.dead) { -+ throw new IllegalStateException("Target region is dead! Source " + this + ", target " + mergeTarget); -+ } -+ this.dead = true; -+ if (this.markedForRecalc) { -+ this.regionManager.removeFromRecalcQueue(this); -+ } -+ -+ for (final Iterator iterator = this.sections.unsafeIterator(IteratorSafeOrderedReferenceSet.ITERATOR_FLAG_SEE_ADDITIONS); iterator.hasNext();) { -+ final RegionSection section = iterator.next(); -+ -+ if (!mergeTarget.addRegionSection(section)) { -+ throw new IllegalStateException("Target cannot contain source's sections! Source " + this + ", target " + mergeTarget); -+ } -+ } -+ -+ for (final RegionSection deadSection : this.deadSections) { -+ if (!this.sections.contains(deadSection)) { -+ throw new IllegalStateException("Source region does not even contain its own dead sections! Missing " + deadSection + " from region " + this); -+ } -+ mergeTarget.deadSections.add(deadSection); -+ } -+ //mergeTarget.check(); -+ } -+ -+ protected void markSectionAlive(final RegionSection section) { -+ this.deadSections.remove(section); -+ if (this.markedForRecalc && (this.sections.size() < this.regionManager.minSectionRecalcCount || this.getDeadSectionPercent() < this.regionManager.maxDeadRegionPercent)) { -+ this.regionManager.removeFromRecalcQueue(this); -+ this.markedForRecalc = false; -+ } -+ } -+ -+ protected void markSectionDead(final RegionSection section) { -+ this.deadSections.add(section); -+ if (!this.markedForRecalc && (this.sections.size() >= this.regionManager.minSectionRecalcCount || this.sections.size() == this.deadSections.size()) && this.getDeadSectionPercent() >= this.regionManager.maxDeadRegionPercent) { -+ this.regionManager.addToRecalcQueue(this); -+ this.markedForRecalc = true; -+ } -+ } -+ -+ @Override -+ public String toString() { -+ final StringBuilder ret = new StringBuilder(128); -+ -+ ret.append("Region{"); -+ ret.append("dead=").append(this.dead).append(','); -+ ret.append("markedForRecalc=").append(this.markedForRecalc).append(','); -+ -+ ret.append("sectionCount=").append(this.sections.size()).append(','); -+ ret.append("sections=["); -+ for (final Iterator iterator = this.sections.unsafeIterator(IteratorSafeOrderedReferenceSet.ITERATOR_FLAG_SEE_ADDITIONS); iterator.hasNext();) { -+ final RegionSection section = iterator.next(); -+ ret.append(section); -+ if (iterator.hasNext()) { -+ ret.append(','); -+ } -+ } -+ ret.append(']'); -+ -+ ret.append('}'); -+ return ret.toString(); -+ } -+ } -+ -+ public static final class RegionSection { -+ protected final long regionCoordinate; -+ protected final long[] chunksBitset; -+ protected int chunkCount; -+ protected Region region; -+ -+ public final SingleThreadChunkRegionManager regionManager; -+ public final RegionSectionData sectionData; -+ -+ protected RegionSection(final long regionCoordinate, final SingleThreadChunkRegionManager regionManager) { -+ this.regionCoordinate = regionCoordinate; -+ this.regionManager = regionManager; -+ this.chunksBitset = new long[Math.max(1, regionManager.regionSectionChunkSize * regionManager.regionSectionChunkSize / Long.SIZE)]; -+ this.sectionData = regionManager.regionSectionDataSupplier.get(); -+ } -+ -+ public int getSectionX() { -+ return MCUtil.getCoordinateX(this.regionCoordinate); -+ } -+ -+ public int getSectionZ() { -+ return MCUtil.getCoordinateZ(this.regionCoordinate); -+ } -+ -+ public Region getRegion() { -+ return this.region; -+ } -+ -+ private int getChunkIndex(final int chunkX, final int chunkZ) { -+ return (chunkX & (this.regionManager.regionSectionChunkSize - 1)) | ((chunkZ & (this.regionManager.regionSectionChunkSize - 1)) << this.regionManager.regionChunkShift); -+ } -+ -+ protected boolean hasChunks() { -+ return this.chunkCount != 0; -+ } -+ -+ protected void addChunk(final int chunkX, final int chunkZ) { -+ final int index = this.getChunkIndex(chunkX, chunkZ); -+ final long bitset = this.chunksBitset[index >>> 6]; // index / Long.SIZE -+ final long after = this.chunksBitset[index >>> 6] = bitset | (1L << (index & (Long.SIZE - 1))); -+ if (after == bitset) { -+ throw new IllegalStateException("Cannot add a chunk to a section which already has the chunk! RegionSection: " + this + ", global chunk: " + new ChunkPos(chunkX, chunkZ).toString()); -+ } -+ if (++this.chunkCount != 1) { -+ return; -+ } -+ this.region.markSectionAlive(this); -+ } -+ -+ protected void removeChunk(final int chunkX, final int chunkZ) { -+ final int index = this.getChunkIndex(chunkX, chunkZ); -+ final long before = this.chunksBitset[index >>> 6]; // index / Long.SIZE -+ final long bitset = this.chunksBitset[index >>> 6] = before & ~(1L << (index & (Long.SIZE - 1))); -+ if (before == bitset) { -+ throw new IllegalStateException("Cannot remove a chunk from a section which does not have that chunk! RegionSection: " + this + ", global chunk: " + new ChunkPos(chunkX, chunkZ).toString()); -+ } -+ if (--this.chunkCount != 0) { -+ return; -+ } -+ this.region.markSectionDead(this); -+ } -+ -+ @Override -+ public String toString() { -+ return "RegionSection{" + -+ "regionCoordinate=" + new ChunkPos(this.regionCoordinate).toString() + "," + -+ "chunkCount=" + this.chunkCount + "," + -+ "chunksBitset=" + toString(this.chunksBitset) + "," + -+ "hash=" + this.hashCode() + -+ "}"; -+ } -+ -+ public String toStringWithRegion() { -+ return "RegionSection{" + -+ "regionCoordinate=" + new ChunkPos(this.regionCoordinate).toString() + "," + -+ "chunkCount=" + this.chunkCount + "," + -+ "chunksBitset=" + toString(this.chunksBitset) + "," + -+ "hash=" + this.hashCode() + "," + -+ "region=" + this.region + -+ "}"; -+ } -+ -+ private static String toString(final long[] array) { -+ final StringBuilder ret = new StringBuilder(); -+ for (final long value : array) { -+ // zero pad the hex string -+ final char[] zeros = new char[Long.SIZE / 4]; -+ Arrays.fill(zeros, '0'); -+ final String string = Long.toHexString(value); -+ System.arraycopy(string.toCharArray(), 0, zeros, zeros.length - string.length(), string.length()); -+ -+ ret.append(zeros); -+ } -+ -+ return ret.toString(); -+ } -+ } -+ -+ public static interface RegionData { -+ -+ } -+ -+ public static interface RegionSectionData { -+ -+ public void removeFromRegion(final RegionSection section, final Region from); -+ -+ // removal from the old region is handled via removeFromRegion -+ public void addToRegion(final RegionSection section, final Region oldRegion, final Region newRegion); -+ -+ } -+} diff --git a/src/main/java/io/papermc/paper/chunk/system/ChunkSystem.java b/src/main/java/io/papermc/paper/chunk/system/ChunkSystem.java new file mode 100644 -index 0000000000000000000000000000000000000000..0d0cb3e63acd5156b6f9d6d78cc949b0af36a77b +index 0000000000000000000000000000000000000000..a79abe9b26f68d573812e91554124783075ae17a --- /dev/null +++ b/src/main/java/io/papermc/paper/chunk/system/ChunkSystem.java -@@ -0,0 +1,303 @@ +@@ -0,0 +1,297 @@ +package io.papermc.paper.chunk.system; + +import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; @@ -2720,12 +1787,16 @@ index 0000000000000000000000000000000000000000..0d0cb3e63acd5156b6f9d6d78cc949b0 + } + scheduleChunkLoad(level, chunkX, chunkZ, ChunkStatus.EMPTY, addTicket, priority, (final ChunkAccess chunk) -> { + if (chunk == null) { -+ onComplete.accept(null); ++ if (onComplete != null) { ++ onComplete.accept(null); ++ } + } else { + if (chunk.getPersistedStatus().isOrAfter(toStatus)) { + scheduleChunkLoad(level, chunkX, chunkZ, toStatus, addTicket, priority, onComplete); + } else { -+ onComplete.accept(null); ++ if (onComplete != null) { ++ onComplete.accept(null); ++ } + } + } + }); @@ -2757,8 +1828,6 @@ index 0000000000000000000000000000000000000000..0d0cb3e63acd5156b6f9d6d78cc949b0 + if (onComplete != null) { + onComplete.accept(chunk); + } -+ } catch (final ThreadDeath death) { -+ throw death; + } catch (final Throwable thr) { + LOGGER.error("Exception handling chunk load callback", thr); + SneakyThrow.sneaky(thr); @@ -2825,8 +1894,6 @@ index 0000000000000000000000000000000000000000..0d0cb3e63acd5156b6f9d6d78cc949b0 + if (onComplete != null) { + onComplete.accept(chunk); + } -+ } catch (final ThreadDeath death) { -+ throw death; + } catch (final Throwable thr) { + LOGGER.error("Exception handling chunk load callback", thr); + SneakyThrow.sneaky(thr); @@ -2905,21 +1972,15 @@ index 0000000000000000000000000000000000000000..0d0cb3e63acd5156b6f9d6d78cc949b0 + } + + public static void onChunkHolderCreate(final ServerLevel level, final ChunkHolder holder) { -+ final ChunkMap chunkMap = level.chunkSource.chunkMap; -+ for (int index = 0, len = chunkMap.regionManagers.size(); index < len; ++index) { -+ chunkMap.regionManagers.get(index).addChunk(holder.getPos().x, holder.getPos().z); -+ } ++ + } + + public static void onChunkHolderDelete(final ServerLevel level, final ChunkHolder holder) { -+ final ChunkMap chunkMap = level.chunkSource.chunkMap; -+ for (int index = 0, len = chunkMap.regionManagers.size(); index < len; ++index) { -+ chunkMap.regionManagers.get(index).removeChunk(holder.getPos().x, holder.getPos().z); -+ } ++ + } + + public static void onChunkBorder(final LevelChunk chunk, final ChunkHolder holder) { -+ chunk.playerChunk = holder; ++ + } + + public static void onChunkNotBorder(final LevelChunk chunk, final ChunkHolder holder) { @@ -3368,14 +2429,16 @@ index 0000000000000000000000000000000000000000..16785bd5c0524f6bad0691ca7ecd4514 +} diff --git a/src/main/java/io/papermc/paper/util/IntervalledCounter.java b/src/main/java/io/papermc/paper/util/IntervalledCounter.java new file mode 100644 -index 0000000000000000000000000000000000000000..cea9c098ade00ee87b8efc8164ab72f5279758f0 +index 0000000000000000000000000000000000000000..c90acc3bde887b9c8f8d49fcc3195657c721bc14 --- /dev/null +++ b/src/main/java/io/papermc/paper/util/IntervalledCounter.java -@@ -0,0 +1,115 @@ +@@ -0,0 +1,128 @@ +package io.papermc.paper.util; + +public final class IntervalledCounter { + ++ private static final int INITIAL_SIZE = 8; ++ + protected long[] times; + protected long[] counts; + protected final long interval; @@ -3385,8 +2448,8 @@ index 0000000000000000000000000000000000000000..cea9c098ade00ee87b8efc8164ab72f5 + protected int tail; // exclusive + + public IntervalledCounter(final long interval) { -+ this.times = new long[8]; -+ this.counts = new long[8]; ++ this.times = new long[INITIAL_SIZE]; ++ this.counts = new long[INITIAL_SIZE]; + this.interval = interval; + } + @@ -3441,13 +2504,13 @@ index 0000000000000000000000000000000000000000..cea9c098ade00ee87b8efc8164ab72f5 + this.tail = nextTail; + } + -+ public void updateAndAdd(final int count) { ++ public void updateAndAdd(final long count) { + final long currTime = System.nanoTime(); + this.updateCurrentTime(currTime); + this.addTime(currTime, count); + } + -+ public void updateAndAdd(final int count, final long currTime) { ++ public void updateAndAdd(final long count, final long currTime) { + this.updateCurrentTime(currTime); + this.addTime(currTime, count); + } @@ -3467,9 +2530,12 @@ index 0000000000000000000000000000000000000000..cea9c098ade00ee87b8efc8164ab72f5 + this.tail = size; + + if (tail >= head) { ++ // sequentially ordered from [head, tail) + System.arraycopy(oldElements, head, newElements, 0, size); + System.arraycopy(oldCounts, head, newCounts, 0, size); + } else { ++ // ordered from [head, length) ++ // then followed by [0, tail) + System.arraycopy(oldElements, head, newElements, 0, oldElements.length - head); + System.arraycopy(oldElements, 0, newElements, oldElements.length - head, tail); + @@ -3480,19 +2546,27 @@ index 0000000000000000000000000000000000000000..cea9c098ade00ee87b8efc8164ab72f5 + + // returns in units per second + public double getRate() { -+ return this.size() / (this.interval * 1.0e-9); ++ return (double)this.sum / ((double)this.interval * 1.0E-9); + } + -+ public long size() { ++ public long getInterval() { ++ return this.interval; ++ } ++ ++ public long getSum() { + return this.sum; + } ++ ++ public int totalDataPoints() { ++ return this.tail >= this.head ? (this.tail - this.head) : (this.tail + (this.counts.length - this.head)); ++ } +} diff --git a/src/main/java/io/papermc/paper/util/MCUtil.java b/src/main/java/io/papermc/paper/util/MCUtil.java new file mode 100644 -index 0000000000000000000000000000000000000000..eb36bef19e6729c1cc44aefa927317963aba929e +index 0000000000000000000000000000000000000000..c6c723d9378c593c8608d5940f63c98dff097cd0 --- /dev/null +++ b/src/main/java/io/papermc/paper/util/MCUtil.java -@@ -0,0 +1,554 @@ +@@ -0,0 +1,550 @@ +package io.papermc.paper.util; + +import com.google.common.collect.ImmutableList; @@ -4020,10 +3094,6 @@ index 0000000000000000000000000000000000000000..eb36bef19e6729c1cc44aefa92731796 + } + } + -+ public static int getTicketLevelFor(net.minecraft.world.level.chunk.status.ChunkStatus status) { -+ return net.minecraft.server.level.ChunkMap.MAX_VIEW_DISTANCE + io.papermc.paper.chunk.system.ChunkSystem.getDistance(status); -+ } -+ + @NotNull + public static List copyListAndAdd(@NotNull final List original, + @NotNull final T newElement) { @@ -4469,1033 +3539,6 @@ index 0000000000000000000000000000000000000000..0fd814f1d65c111266a2b20f86561839 + } + } +} -diff --git a/src/main/java/io/papermc/paper/util/misc/Delayed26WayDistancePropagator3D.java b/src/main/java/io/papermc/paper/util/misc/Delayed26WayDistancePropagator3D.java -new file mode 100644 -index 0000000000000000000000000000000000000000..470402573bc31106d5a63e415b958fb7f9c36aa9 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/util/misc/Delayed26WayDistancePropagator3D.java -@@ -0,0 +1,297 @@ -+package io.papermc.paper.util.misc; -+ -+import io.papermc.paper.util.CoordinateUtils; -+import it.unimi.dsi.fastutil.longs.Long2ByteOpenHashMap; -+import it.unimi.dsi.fastutil.longs.LongIterator; -+import it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet; -+ -+public final class Delayed26WayDistancePropagator3D { -+ -+ // this map is considered "stale" unless updates are propagated. -+ protected final Delayed8WayDistancePropagator2D.LevelMap levels = new Delayed8WayDistancePropagator2D.LevelMap(8192*2, 0.6f); -+ -+ // this map is never stale -+ protected final Long2ByteOpenHashMap sources = new Long2ByteOpenHashMap(4096, 0.6f); -+ -+ // 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(); -+ -+ @FunctionalInterface -+ public static interface LevelChangeCallback { -+ -+ /** -+ * This can be called for intermediate updates. So do not rely on newLevel being close to or -+ * the exact level that is expected after a full propagation has occured. -+ */ -+ public void onLevelUpdate(final long coordinate, final byte oldLevel, final byte newLevel); -+ -+ } -+ -+ protected final LevelChangeCallback changeCallback; -+ -+ public Delayed26WayDistancePropagator3D() { -+ this(null); -+ } -+ -+ public Delayed26WayDistancePropagator3D(final LevelChangeCallback changeCallback) { -+ this.changeCallback = changeCallback; -+ } -+ -+ public int getLevel(final long pos) { -+ return this.levels.get(pos); -+ } -+ -+ public int getLevel(final int x, final int y, final int z) { -+ return this.levels.get(CoordinateUtils.getChunkSectionKey(x, y, z)); -+ } -+ -+ public void setSource(final int x, final int y, final int z, final int level) { -+ this.setSource(CoordinateUtils.getChunkSectionKey(x, y, z), level); -+ } -+ -+ public void setSource(final long coordinate, final int level) { -+ if ((level & 63) != level || level == 0) { -+ throw new IllegalArgumentException("Level must be in (0, 63], not " + level); -+ } -+ -+ final byte byteLevel = (byte)level; -+ final byte oldLevel = this.sources.put(coordinate, byteLevel); -+ -+ if (oldLevel == byteLevel) { -+ return; // nothing to do -+ } -+ -+ // queue to update later -+ this.updatedSources.add(coordinate); -+ } -+ -+ public void removeSource(final int x, final int y, final int z) { -+ this.removeSource(CoordinateUtils.getChunkSectionKey(x, y, z)); -+ } -+ -+ public void removeSource(final long coordinate) { -+ if (this.sources.remove(coordinate) != 0) { -+ this.updatedSources.add(coordinate); -+ } -+ } -+ -+ // queues used for BFS propagating levels -+ protected final Delayed8WayDistancePropagator2D.WorkQueue[] levelIncreaseWorkQueues = new Delayed8WayDistancePropagator2D.WorkQueue[64]; -+ { -+ for (int i = 0; i < this.levelIncreaseWorkQueues.length; ++i) { -+ this.levelIncreaseWorkQueues[i] = new Delayed8WayDistancePropagator2D.WorkQueue(); -+ } -+ } -+ protected final Delayed8WayDistancePropagator2D.WorkQueue[] levelRemoveWorkQueues = new Delayed8WayDistancePropagator2D.WorkQueue[64]; -+ { -+ for (int i = 0; i < this.levelRemoveWorkQueues.length; ++i) { -+ this.levelRemoveWorkQueues[i] = new Delayed8WayDistancePropagator2D.WorkQueue(); -+ } -+ } -+ protected long levelIncreaseWorkQueueBitset; -+ protected long levelRemoveWorkQueueBitset; -+ -+ 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); -+ -+ 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); -+ -+ 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); -+ -+ this.levelRemoveWorkQueueBitset |= (1L << level); -+ } -+ -+ public boolean propagateUpdates() { -+ if (this.updatedSources.isEmpty()) { -+ return false; -+ } -+ -+ boolean ret = false; -+ -+ for (final LongIterator iterator = this.updatedSources.iterator(); iterator.hasNext();) { -+ final long coordinate = iterator.nextLong(); -+ -+ final byte currentLevel = this.levels.get(coordinate); -+ final byte updatedSource = this.sources.get(coordinate); -+ -+ if (currentLevel == updatedSource) { -+ continue; -+ } -+ ret = true; -+ -+ if (updatedSource > currentLevel) { -+ // level increase -+ this.addToIncreaseWorkQueue(coordinate, updatedSource); -+ } else { -+ // level decrease -+ this.addToRemoveWorkQueue(coordinate, currentLevel); -+ // if the current coordinate is a source, then the decrease propagation will detect that and queue -+ // the source propagation -+ } -+ } -+ -+ this.updatedSources.clear(); -+ -+ // propagate source level increases first for performance reasons (in crowded areas hopefully the additions -+ // make the removes remove less) -+ this.propagateIncreases(); -+ -+ // now we propagate the decreases (which will then re-propagate clobbered sources) -+ this.propagateDecreases(); -+ -+ return ret; -+ } -+ -+ protected void propagateIncreases() { -+ 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 boolean neighbourCheck = level < 0; -+ -+ final byte currentLevel; -+ if (neighbourCheck) { -+ level = (byte)-level; -+ currentLevel = this.levels.get(coordinate); -+ } else { -+ currentLevel = this.levels.putIfGreater(coordinate, level); -+ } -+ -+ if (neighbourCheck) { -+ // used when propagating from decrease to indicate that this level needs to check its neighbours -+ // this means the level at coordinate could be equal, but would still need neighbours checked -+ -+ if (currentLevel != level) { -+ // something caused the level to change, which means something propagated to it (which means -+ // us propagating here is redundant), or something removed the level (which means we -+ // cannot propagate further) -+ continue; -+ } -+ } else if (currentLevel >= level) { -+ // something higher/equal propagated -+ continue; -+ } -+ if (this.changeCallback != null) { -+ this.changeCallback.onLevelUpdate(coordinate, currentLevel, level); -+ } -+ -+ if (level == 1) { -+ // can't propagate 0 to neighbours -+ continue; -+ } -+ -+ // propagate to neighbours -+ final byte neighbourLevel = (byte)(level - 1); -+ final int x = CoordinateUtils.getChunkSectionX(coordinate); -+ final int y = CoordinateUtils.getChunkSectionY(coordinate); -+ final int z = CoordinateUtils.getChunkSectionZ(coordinate); -+ -+ for (int dy = -1; dy <= 1; ++dy) { -+ for (int dz = -1; dz <= 1; ++dz) { -+ for (int dx = -1; dx <= 1; ++dx) { -+ if ((dy | dz | dx) == 0) { -+ // already propagated to coordinate -+ continue; -+ } -+ -+ // sure we can check the neighbour level in the map right now and avoid a propagation, -+ // but then we would still have to recheck it when popping the value off of the queue! -+ // so just avoid the double lookup -+ final long neighbourCoordinate = CoordinateUtils.getChunkSectionKey(dx + x, dy + y, dz + z); -+ this.addToIncreaseWorkQueue(neighbourCoordinate, neighbourLevel); -+ } -+ } -+ } -+ } -+ } -+ } -+ -+ protected void propagateDecreases() { -+ 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 byte currentLevel = this.levels.removeIfGreaterOrEqual(coordinate, level); -+ if (currentLevel == 0) { -+ // something else removed -+ continue; -+ } -+ -+ if (currentLevel > level) { -+ // something higher propagated here or we hit the propagation of another source -+ // in the second case we need to re-propagate because we could have just clobbered another source's -+ // propagation -+ this.addToIncreaseWorkQueue(coordinate, currentLevel, (byte)-currentLevel); // indicate to the increase code that the level's neighbours need checking -+ continue; -+ } -+ -+ if (this.changeCallback != null) { -+ this.changeCallback.onLevelUpdate(coordinate, currentLevel, (byte)0); -+ } -+ -+ final byte source = this.sources.get(coordinate); -+ if (source != 0) { -+ // must re-propagate source later -+ this.addToIncreaseWorkQueue(coordinate, source); -+ } -+ -+ if (level == 0) { -+ // can't propagate -1 to neighbours -+ // we have to check neighbours for removing 1 just in case the neighbour is 2 -+ continue; -+ } -+ -+ // propagate to neighbours -+ final byte neighbourLevel = (byte)(level - 1); -+ final int x = CoordinateUtils.getChunkSectionX(coordinate); -+ final int y = CoordinateUtils.getChunkSectionY(coordinate); -+ final int z = CoordinateUtils.getChunkSectionZ(coordinate); -+ -+ for (int dy = -1; dy <= 1; ++dy) { -+ for (int dz = -1; dz <= 1; ++dz) { -+ for (int dx = -1; dx <= 1; ++dx) { -+ if ((dy | dz | dx) == 0) { -+ // already propagated to coordinate -+ continue; -+ } -+ -+ // sure we can check the neighbour level in the map right now and avoid a propagation, -+ // but then we would still have to recheck it when popping the value off of the queue! -+ // so just avoid the double lookup -+ final long neighbourCoordinate = CoordinateUtils.getChunkSectionKey(dx + x, dy + y, dz + z); -+ this.addToRemoveWorkQueue(neighbourCoordinate, neighbourLevel); -+ } -+ } -+ } -+ } -+ } -+ -+ // propagate sources we clobbered in the process -+ this.propagateIncreases(); -+ } -+} -diff --git a/src/main/java/io/papermc/paper/util/misc/Delayed8WayDistancePropagator2D.java b/src/main/java/io/papermc/paper/util/misc/Delayed8WayDistancePropagator2D.java -new file mode 100644 -index 0000000000000000000000000000000000000000..808d1449ac44ae86a650932365081fbaf178d141 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/util/misc/Delayed8WayDistancePropagator2D.java -@@ -0,0 +1,718 @@ -+package io.papermc.paper.util.misc; -+ -+import it.unimi.dsi.fastutil.HashCommon; -+import it.unimi.dsi.fastutil.bytes.ByteArrayFIFOQueue; -+import it.unimi.dsi.fastutil.longs.Long2ByteOpenHashMap; -+import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue; -+import it.unimi.dsi.fastutil.longs.LongIterator; -+import it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet; -+import io.papermc.paper.util.MCUtil; -+ -+public final class Delayed8WayDistancePropagator2D { -+ -+ // Test -+ /* -+ protected static void test(int x, int z, com.destroystokyo.paper.util.misc.DistanceTrackingAreaMap reference, Delayed8WayDistancePropagator2D test) { -+ int got = test.getLevel(x, z); -+ -+ int expect = 0; -+ Object[] nearest = reference.getObjectsInRange(x, z) == null ? null : reference.getObjectsInRange(x, z).getBackingSet(); -+ if (nearest != null) { -+ for (Object _obj : nearest) { -+ if (_obj instanceof Ticket) { -+ Ticket ticket = (Ticket)_obj; -+ long ticketCoord = reference.getLastCoordinate(ticket); -+ int viewDistance = reference.getLastViewDistance(ticket); -+ int distance = Math.max(com.destroystokyo.paper.util.math.IntegerUtil.branchlessAbs(MCUtil.getCoordinateX(ticketCoord) - x), -+ com.destroystokyo.paper.util.math.IntegerUtil.branchlessAbs(MCUtil.getCoordinateZ(ticketCoord) - z)); -+ int level = viewDistance - distance; -+ if (level > expect) { -+ expect = level; -+ } -+ } -+ } -+ } -+ -+ if (expect != got) { -+ throw new IllegalStateException("Expected " + expect + " at pos (" + x + "," + z + ") but got " + got); -+ } -+ } -+ -+ static class Ticket { -+ -+ int x; -+ int z; -+ -+ final com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet empty -+ = new com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<>(this); -+ -+ } -+ -+ public static void main(final String[] args) { -+ com.destroystokyo.paper.util.misc.DistanceTrackingAreaMap reference = new com.destroystokyo.paper.util.misc.DistanceTrackingAreaMap() { -+ @Override -+ protected com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet getEmptySetFor(Ticket object) { -+ return object.empty; -+ } -+ }; -+ Delayed8WayDistancePropagator2D test = new Delayed8WayDistancePropagator2D(); -+ -+ final int maxDistance = 64; -+ // test origin -+ { -+ Ticket originTicket = new Ticket(); -+ int originDistance = 31; -+ // test single source -+ reference.add(originTicket, 0, 0, originDistance); -+ test.setSource(0, 0, originDistance); test.propagateUpdates(); // set and propagate -+ for (int dx = -originDistance; dx <= originDistance; ++dx) { -+ for (int dz = -originDistance; dz <= originDistance; ++dz) { -+ test(dx, dz, reference, test); -+ } -+ } -+ // test single source decrease -+ reference.update(originTicket, 0, 0, originDistance/2); -+ test.setSource(0, 0, originDistance/2); test.propagateUpdates(); // set and propagate -+ for (int dx = -originDistance; dx <= originDistance; ++dx) { -+ for (int dz = -originDistance; dz <= originDistance; ++dz) { -+ test(dx, dz, reference, test); -+ } -+ } -+ // test source increase -+ originDistance = 2*originDistance; -+ reference.update(originTicket, 0, 0, originDistance); -+ test.setSource(0, 0, originDistance); test.propagateUpdates(); // set and propagate -+ for (int dx = -4*originDistance; dx <= 4*originDistance; ++dx) { -+ for (int dz = -4*originDistance; dz <= 4*originDistance; ++dz) { -+ test(dx, dz, reference, test); -+ } -+ } -+ -+ reference.remove(originTicket); -+ test.removeSource(0, 0); test.propagateUpdates(); -+ } -+ -+ // test multiple sources at origin -+ { -+ int originDistance = 31; -+ java.util.List list = new java.util.ArrayList<>(); -+ for (int i = 0; i < 10; ++i) { -+ Ticket a = new Ticket(); -+ list.add(a); -+ a.x = (i & 1) == 1 ? -i : i; -+ a.z = (i & 1) == 1 ? -i : i; -+ } -+ for (Ticket ticket : list) { -+ reference.add(ticket, ticket.x, ticket.z, originDistance); -+ test.setSource(ticket.x, ticket.z, originDistance); -+ } -+ test.propagateUpdates(); -+ -+ for (int dx = -8*originDistance; dx <= 8*originDistance; ++dx) { -+ for (int dz = -8*originDistance; dz <= 8*originDistance; ++dz) { -+ test(dx, dz, reference, test); -+ } -+ } -+ -+ // test ticket level decrease -+ -+ for (Ticket ticket : list) { -+ reference.update(ticket, ticket.x, ticket.z, originDistance/2); -+ test.setSource(ticket.x, ticket.z, originDistance/2); -+ } -+ test.propagateUpdates(); -+ -+ for (int dx = -8*originDistance; dx <= 8*originDistance; ++dx) { -+ for (int dz = -8*originDistance; dz <= 8*originDistance; ++dz) { -+ test(dx, dz, reference, test); -+ } -+ } -+ -+ // test ticket level increase -+ -+ for (Ticket ticket : list) { -+ reference.update(ticket, ticket.x, ticket.z, originDistance*2); -+ test.setSource(ticket.x, ticket.z, originDistance*2); -+ } -+ test.propagateUpdates(); -+ -+ for (int dx = -16*originDistance; dx <= 16*originDistance; ++dx) { -+ for (int dz = -16*originDistance; dz <= 16*originDistance; ++dz) { -+ test(dx, dz, reference, test); -+ } -+ } -+ -+ // test ticket remove -+ for (int i = 0, len = list.size(); i < len; ++i) { -+ if ((i & 3) != 0) { -+ continue; -+ } -+ Ticket ticket = list.get(i); -+ reference.remove(ticket); -+ test.removeSource(ticket.x, ticket.z); -+ } -+ test.propagateUpdates(); -+ -+ for (int dx = -16*originDistance; dx <= 16*originDistance; ++dx) { -+ for (int dz = -16*originDistance; dz <= 16*originDistance; ++dz) { -+ test(dx, dz, reference, test); -+ } -+ } -+ } -+ -+ // now test at coordinate offsets -+ // test offset -+ { -+ Ticket originTicket = new Ticket(); -+ int originDistance = 31; -+ int offX = 54432; -+ int offZ = -134567; -+ // test single source -+ reference.add(originTicket, offX, offZ, originDistance); -+ test.setSource(offX, offZ, originDistance); test.propagateUpdates(); // set and propagate -+ for (int dx = -originDistance; dx <= originDistance; ++dx) { -+ for (int dz = -originDistance; dz <= originDistance; ++dz) { -+ test(dx + offX, dz + offZ, reference, test); -+ } -+ } -+ // test single source decrease -+ reference.update(originTicket, offX, offZ, originDistance/2); -+ test.setSource(offX, offZ, originDistance/2); test.propagateUpdates(); // set and propagate -+ for (int dx = -originDistance; dx <= originDistance; ++dx) { -+ for (int dz = -originDistance; dz <= originDistance; ++dz) { -+ test(dx + offX, dz + offZ, reference, test); -+ } -+ } -+ // test source increase -+ originDistance = 2*originDistance; -+ reference.update(originTicket, offX, offZ, originDistance); -+ test.setSource(offX, offZ, originDistance); test.propagateUpdates(); // set and propagate -+ for (int dx = -4*originDistance; dx <= 4*originDistance; ++dx) { -+ for (int dz = -4*originDistance; dz <= 4*originDistance; ++dz) { -+ test(dx + offX, dz + offZ, reference, test); -+ } -+ } -+ -+ reference.remove(originTicket); -+ test.removeSource(offX, offZ); test.propagateUpdates(); -+ } -+ -+ // test multiple sources at origin -+ { -+ int originDistance = 31; -+ int offX = 54432; -+ int offZ = -134567; -+ java.util.List list = new java.util.ArrayList<>(); -+ for (int i = 0; i < 10; ++i) { -+ Ticket a = new Ticket(); -+ list.add(a); -+ a.x = offX + ((i & 1) == 1 ? -i : i); -+ a.z = offZ + ((i & 1) == 1 ? -i : i); -+ } -+ for (Ticket ticket : list) { -+ reference.add(ticket, ticket.x, ticket.z, originDistance); -+ test.setSource(ticket.x, ticket.z, originDistance); -+ } -+ test.propagateUpdates(); -+ -+ for (int dx = -8*originDistance; dx <= 8*originDistance; ++dx) { -+ for (int dz = -8*originDistance; dz <= 8*originDistance; ++dz) { -+ test(dx, dz, reference, test); -+ } -+ } -+ -+ // test ticket level decrease -+ -+ for (Ticket ticket : list) { -+ reference.update(ticket, ticket.x, ticket.z, originDistance/2); -+ test.setSource(ticket.x, ticket.z, originDistance/2); -+ } -+ test.propagateUpdates(); -+ -+ for (int dx = -8*originDistance; dx <= 8*originDistance; ++dx) { -+ for (int dz = -8*originDistance; dz <= 8*originDistance; ++dz) { -+ test(dx, dz, reference, test); -+ } -+ } -+ -+ // test ticket level increase -+ -+ for (Ticket ticket : list) { -+ reference.update(ticket, ticket.x, ticket.z, originDistance*2); -+ test.setSource(ticket.x, ticket.z, originDistance*2); -+ } -+ test.propagateUpdates(); -+ -+ for (int dx = -16*originDistance; dx <= 16*originDistance; ++dx) { -+ for (int dz = -16*originDistance; dz <= 16*originDistance; ++dz) { -+ test(dx, dz, reference, test); -+ } -+ } -+ -+ // test ticket remove -+ for (int i = 0, len = list.size(); i < len; ++i) { -+ if ((i & 3) != 0) { -+ continue; -+ } -+ Ticket ticket = list.get(i); -+ reference.remove(ticket); -+ test.removeSource(ticket.x, ticket.z); -+ } -+ test.propagateUpdates(); -+ -+ for (int dx = -16*originDistance; dx <= 16*originDistance; ++dx) { -+ for (int dz = -16*originDistance; dz <= 16*originDistance; ++dz) { -+ test(dx, dz, reference, test); -+ } -+ } -+ } -+ } -+ */ -+ -+ // this map is considered "stale" unless updates are propagated. -+ protected final LevelMap levels = new LevelMap(8192*2, 0.6f); -+ -+ // this map is never stale -+ protected final Long2ByteOpenHashMap sources = new Long2ByteOpenHashMap(4096, 0.6f); -+ -+ // 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(); -+ -+ @FunctionalInterface -+ public static interface LevelChangeCallback { -+ -+ /** -+ * This can be called for intermediate updates. So do not rely on newLevel being close to or -+ * the exact level that is expected after a full propagation has occured. -+ */ -+ public void onLevelUpdate(final long coordinate, final byte oldLevel, final byte newLevel); -+ -+ } -+ -+ protected final LevelChangeCallback changeCallback; -+ -+ public Delayed8WayDistancePropagator2D() { -+ this(null); -+ } -+ -+ public Delayed8WayDistancePropagator2D(final LevelChangeCallback changeCallback) { -+ this.changeCallback = changeCallback; -+ } -+ -+ public int getLevel(final long pos) { -+ return this.levels.get(pos); -+ } -+ -+ public int getLevel(final int x, final int z) { -+ return this.levels.get(MCUtil.getCoordinateKey(x, z)); -+ } -+ -+ public void setSource(final int x, final int z, final int level) { -+ this.setSource(MCUtil.getCoordinateKey(x, z), level); -+ } -+ -+ public void setSource(final long coordinate, final int level) { -+ if ((level & 63) != level || level == 0) { -+ throw new IllegalArgumentException("Level must be in (0, 63], not " + level); -+ } -+ -+ final byte byteLevel = (byte)level; -+ final byte oldLevel = this.sources.put(coordinate, byteLevel); -+ -+ if (oldLevel == byteLevel) { -+ return; // nothing to do -+ } -+ -+ // queue to update later -+ this.updatedSources.add(coordinate); -+ } -+ -+ public void removeSource(final int x, final int z) { -+ this.removeSource(MCUtil.getCoordinateKey(x, z)); -+ } -+ -+ public void removeSource(final long coordinate) { -+ if (this.sources.remove(coordinate) != 0) { -+ this.updatedSources.add(coordinate); -+ } -+ } -+ -+ // queues used for BFS propagating levels -+ protected final WorkQueue[] levelIncreaseWorkQueues = new WorkQueue[64]; -+ { -+ for (int i = 0; i < this.levelIncreaseWorkQueues.length; ++i) { -+ this.levelIncreaseWorkQueues[i] = new WorkQueue(); -+ } -+ } -+ protected final WorkQueue[] levelRemoveWorkQueues = new WorkQueue[64]; -+ { -+ for (int i = 0; i < this.levelRemoveWorkQueues.length; ++i) { -+ this.levelRemoveWorkQueues[i] = new WorkQueue(); -+ } -+ } -+ protected long levelIncreaseWorkQueueBitset; -+ protected long levelRemoveWorkQueueBitset; -+ -+ protected final void addToIncreaseWorkQueue(final long coordinate, final byte level) { -+ final WorkQueue queue = this.levelIncreaseWorkQueues[level]; -+ queue.queuedCoordinates.enqueue(coordinate); -+ queue.queuedLevels.enqueue(level); -+ -+ 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); -+ -+ 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); -+ -+ this.levelRemoveWorkQueueBitset |= (1L << level); -+ } -+ -+ public boolean propagateUpdates() { -+ if (this.updatedSources.isEmpty()) { -+ return false; -+ } -+ -+ boolean ret = false; -+ -+ for (final LongIterator iterator = this.updatedSources.iterator(); iterator.hasNext();) { -+ final long coordinate = iterator.nextLong(); -+ -+ final byte currentLevel = this.levels.get(coordinate); -+ final byte updatedSource = this.sources.get(coordinate); -+ -+ if (currentLevel == updatedSource) { -+ continue; -+ } -+ ret = true; -+ -+ if (updatedSource > currentLevel) { -+ // level increase -+ this.addToIncreaseWorkQueue(coordinate, updatedSource); -+ } else { -+ // level decrease -+ this.addToRemoveWorkQueue(coordinate, currentLevel); -+ // if the current coordinate is a source, then the decrease propagation will detect that and queue -+ // the source propagation -+ } -+ } -+ -+ this.updatedSources.clear(); -+ -+ // propagate source level increases first for performance reasons (in crowded areas hopefully the additions -+ // make the removes remove less) -+ this.propagateIncreases(); -+ -+ // now we propagate the decreases (which will then re-propagate clobbered sources) -+ this.propagateDecreases(); -+ -+ return ret; -+ } -+ -+ protected void propagateIncreases() { -+ for (int queueIndex = 63 ^ Long.numberOfLeadingZeros(this.levelIncreaseWorkQueueBitset); -+ this.levelIncreaseWorkQueueBitset != 0L; -+ this.levelIncreaseWorkQueueBitset ^= (1L << queueIndex), queueIndex = 63 ^ Long.numberOfLeadingZeros(this.levelIncreaseWorkQueueBitset)) { -+ -+ final WorkQueue queue = this.levelIncreaseWorkQueues[queueIndex]; -+ while (!queue.queuedLevels.isEmpty()) { -+ final long coordinate = queue.queuedCoordinates.removeFirstLong(); -+ byte level = queue.queuedLevels.removeFirstByte(); -+ -+ final boolean neighbourCheck = level < 0; -+ -+ final byte currentLevel; -+ if (neighbourCheck) { -+ level = (byte)-level; -+ currentLevel = this.levels.get(coordinate); -+ } else { -+ currentLevel = this.levels.putIfGreater(coordinate, level); -+ } -+ -+ if (neighbourCheck) { -+ // used when propagating from decrease to indicate that this level needs to check its neighbours -+ // this means the level at coordinate could be equal, but would still need neighbours checked -+ -+ if (currentLevel != level) { -+ // something caused the level to change, which means something propagated to it (which means -+ // us propagating here is redundant), or something removed the level (which means we -+ // cannot propagate further) -+ continue; -+ } -+ } else if (currentLevel >= level) { -+ // something higher/equal propagated -+ continue; -+ } -+ if (this.changeCallback != null) { -+ this.changeCallback.onLevelUpdate(coordinate, currentLevel, level); -+ } -+ -+ if (level == 1) { -+ // can't propagate 0 to neighbours -+ continue; -+ } -+ -+ // propagate to neighbours -+ final byte neighbourLevel = (byte)(level - 1); -+ final int x = (int)coordinate; -+ final int z = (int)(coordinate >>> 32); -+ -+ for (int dx = -1; dx <= 1; ++dx) { -+ for (int dz = -1; dz <= 1; ++dz) { -+ if ((dx | dz) == 0) { -+ // already propagated to coordinate -+ continue; -+ } -+ -+ // sure we can check the neighbour level in the map right now and avoid a propagation, -+ // but then we would still have to recheck it when popping the value off of the queue! -+ // so just avoid the double lookup -+ final long neighbourCoordinate = MCUtil.getCoordinateKey(x + dx, z + dz); -+ this.addToIncreaseWorkQueue(neighbourCoordinate, neighbourLevel); -+ } -+ } -+ } -+ } -+ } -+ -+ protected void propagateDecreases() { -+ for (int queueIndex = 63 ^ Long.numberOfLeadingZeros(this.levelRemoveWorkQueueBitset); -+ this.levelRemoveWorkQueueBitset != 0L; -+ this.levelRemoveWorkQueueBitset ^= (1L << queueIndex), queueIndex = 63 ^ Long.numberOfLeadingZeros(this.levelRemoveWorkQueueBitset)) { -+ -+ final WorkQueue queue = this.levelRemoveWorkQueues[queueIndex]; -+ while (!queue.queuedLevels.isEmpty()) { -+ final long coordinate = queue.queuedCoordinates.removeFirstLong(); -+ final byte level = queue.queuedLevels.removeFirstByte(); -+ -+ final byte currentLevel = this.levels.removeIfGreaterOrEqual(coordinate, level); -+ if (currentLevel == 0) { -+ // something else removed -+ continue; -+ } -+ -+ if (currentLevel > level) { -+ // something higher propagated here or we hit the propagation of another source -+ // in the second case we need to re-propagate because we could have just clobbered another source's -+ // propagation -+ this.addToIncreaseWorkQueue(coordinate, currentLevel, (byte)-currentLevel); // indicate to the increase code that the level's neighbours need checking -+ continue; -+ } -+ -+ if (this.changeCallback != null) { -+ this.changeCallback.onLevelUpdate(coordinate, currentLevel, (byte)0); -+ } -+ -+ final byte source = this.sources.get(coordinate); -+ if (source != 0) { -+ // must re-propagate source later -+ this.addToIncreaseWorkQueue(coordinate, source); -+ } -+ -+ if (level == 0) { -+ // can't propagate -1 to neighbours -+ // we have to check neighbours for removing 1 just in case the neighbour is 2 -+ continue; -+ } -+ -+ // propagate to neighbours -+ final byte neighbourLevel = (byte)(level - 1); -+ final int x = (int)coordinate; -+ final int z = (int)(coordinate >>> 32); -+ -+ for (int dx = -1; dx <= 1; ++dx) { -+ for (int dz = -1; dz <= 1; ++dz) { -+ if ((dx | dz) == 0) { -+ // already propagated to coordinate -+ continue; -+ } -+ -+ // sure we can check the neighbour level in the map right now and avoid a propagation, -+ // but then we would still have to recheck it when popping the value off of the queue! -+ // so just avoid the double lookup -+ final long neighbourCoordinate = MCUtil.getCoordinateKey(x + dx, z + dz); -+ this.addToRemoveWorkQueue(neighbourCoordinate, neighbourLevel); -+ } -+ } -+ } -+ } -+ -+ // propagate sources we clobbered in the process -+ this.propagateIncreases(); -+ } -+ -+ protected static final class LevelMap extends Long2ByteOpenHashMap { -+ public LevelMap() { -+ super(); -+ } -+ -+ public LevelMap(final int expected, final float loadFactor) { -+ super(expected, loadFactor); -+ } -+ -+ // copied from superclass -+ private int find(final long k) { -+ if (k == 0L) { -+ return this.containsNullKey ? this.n : -(this.n + 1); -+ } else { -+ final long[] key = this.key; -+ long curr; -+ int pos; -+ if ((curr = key[pos = (int)HashCommon.mix(k) & this.mask]) == 0L) { -+ return -(pos + 1); -+ } else if (k == curr) { -+ return pos; -+ } else { -+ while((curr = key[pos = pos + 1 & this.mask]) != 0L) { -+ if (k == curr) { -+ return pos; -+ } -+ } -+ -+ return -(pos + 1); -+ } -+ } -+ } -+ -+ // copied from superclass -+ private void insert(final int pos, final long k, final byte v) { -+ if (pos == this.n) { -+ this.containsNullKey = true; -+ } -+ -+ this.key[pos] = k; -+ this.value[pos] = v; -+ if (this.size++ >= this.maxFill) { -+ this.rehash(HashCommon.arraySize(this.size + 1, this.f)); -+ } -+ } -+ -+ // copied from superclass -+ public byte putIfGreater(final long key, final byte value) { -+ final int pos = this.find(key); -+ if (pos < 0) { -+ if (this.defRetValue < value) { -+ this.insert(-pos - 1, key, value); -+ } -+ return this.defRetValue; -+ } else { -+ final byte curr = this.value[pos]; -+ if (value > curr) { -+ this.value[pos] = value; -+ return curr; -+ } -+ return curr; -+ } -+ } -+ -+ // copied from superclass -+ private void removeEntry(final int pos) { -+ --this.size; -+ this.shiftKeys(pos); -+ if (this.n > this.minN && this.size < this.maxFill / 4 && this.n > 16) { -+ this.rehash(this.n / 2); -+ } -+ } -+ -+ // copied from superclass -+ private void removeNullEntry() { -+ this.containsNullKey = false; -+ --this.size; -+ if (this.n > this.minN && this.size < this.maxFill / 4 && this.n > 16) { -+ this.rehash(this.n / 2); -+ } -+ } -+ -+ // copied from superclass -+ public byte removeIfGreaterOrEqual(final long key, final byte value) { -+ if (key == 0L) { -+ if (!this.containsNullKey) { -+ return this.defRetValue; -+ } -+ final byte current = this.value[this.n]; -+ if (value >= current) { -+ this.removeNullEntry(); -+ return current; -+ } -+ return current; -+ } else { -+ long[] keys = this.key; -+ byte[] values = this.value; -+ long curr; -+ int pos; -+ if ((curr = keys[pos = (int)HashCommon.mix(key) & this.mask]) == 0L) { -+ return this.defRetValue; -+ } else if (key == curr) { -+ final byte current = values[pos]; -+ if (value >= current) { -+ this.removeEntry(pos); -+ return current; -+ } -+ return current; -+ } else { -+ while((curr = keys[pos = pos + 1 & this.mask]) != 0L) { -+ if (key == curr) { -+ final byte current = values[pos]; -+ if (value >= current) { -+ this.removeEntry(pos); -+ return current; -+ } -+ return current; -+ } -+ } -+ -+ return this.defRetValue; -+ } -+ } -+ } -+ } -+ -+ protected static final class WorkQueue { -+ -+ public final NoResizeLongArrayFIFODeque queuedCoordinates = new NoResizeLongArrayFIFODeque(); -+ public final NoResizeByteArrayFIFODeque queuedLevels = new NoResizeByteArrayFIFODeque(); -+ -+ } -+ -+ protected static final class NoResizeLongArrayFIFODeque extends LongArrayFIFOQueue { -+ -+ /** -+ * Assumes non-empty. If empty, undefined behaviour. -+ */ -+ public long removeFirstLong() { -+ // copied from superclass -+ long t = this.array[this.start]; -+ if (++this.start == this.length) { -+ this.start = 0; -+ } -+ -+ return t; -+ } -+ } -+ -+ protected static final class NoResizeByteArrayFIFODeque extends ByteArrayFIFOQueue { -+ -+ /** -+ * Assumes non-empty. If empty, undefined behaviour. -+ */ -+ public byte removeFirstByte() { -+ // copied from superclass -+ byte t = this.array[this.start]; -+ if (++this.start == this.length) { -+ this.start = 0; -+ } -+ -+ return t; -+ } -+ } -+} diff --git a/src/main/java/io/papermc/paper/util/player/NearbyPlayers.java b/src/main/java/io/papermc/paper/util/player/NearbyPlayers.java new file mode 100644 index 0000000000000000000000000000000000000000..c3ce8a42dddd76b7189ad5685b23f9d9f8ccadb3 @@ -6081,7 +4124,7 @@ index 3e5a85a7ad6149b04622c254fbc2e174896a4128..3f662692ed4846e026a9d48595e7b3b2 + } diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 40adb6117b9e0d5f70103113202a07715e403e2a..b1325e090f2c7aff31d27fc38ca7173efe31ed7c 100644 +index 40adb6117b9e0d5f70103113202a07715e403e2a..9eb987f9d86396d6b7e9d4f3834bea3326640ac7 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -310,6 +310,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop regionManagers = new java.util.ArrayList<>(); -+ public final io.papermc.paper.chunk.SingleThreadChunkRegionManager dataRegionManager; -+ -+ public static final class DataRegionData implements io.papermc.paper.chunk.SingleThreadChunkRegionManager.RegionData { -+ } -+ -+ public static final class DataRegionSectionData implements io.papermc.paper.chunk.SingleThreadChunkRegionManager.RegionSectionData { -+ -+ @Override -+ public void removeFromRegion(final io.papermc.paper.chunk.SingleThreadChunkRegionManager.RegionSection section, -+ final io.papermc.paper.chunk.SingleThreadChunkRegionManager.Region from) { -+ final DataRegionSectionData sectionData = (DataRegionSectionData)section.sectionData; -+ final DataRegionData fromData = (DataRegionData)from.regionData; -+ } -+ -+ @Override -+ public void addToRegion(final io.papermc.paper.chunk.SingleThreadChunkRegionManager.RegionSection section, -+ final io.papermc.paper.chunk.SingleThreadChunkRegionManager.Region oldRegion, -+ final io.papermc.paper.chunk.SingleThreadChunkRegionManager.Region newRegion) { -+ final DataRegionSectionData sectionData = (DataRegionSectionData)section.sectionData; -+ final DataRegionData oldRegionData = oldRegion == null ? null : (DataRegionData)oldRegion.regionData; -+ final DataRegionData newRegionData = (DataRegionData)newRegion.regionData; -+ } -+ } -+ + public final ChunkHolder getUnloadingChunkHolder(int chunkX, int chunkZ) { + return this.pendingUnloads.get(io.papermc.paper.util.CoordinateUtils.getChunkKey(chunkX, chunkZ)); + } @@ -6413,13 +4419,11 @@ index 5b920beb39dad8d392b4e5e12a89880720e41942..319f51eb8adde7584c74780ac0539f4b public ChunkMap(ServerLevel world, LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, StructureTemplateManager structureTemplateManager, Executor executor, BlockableEventLoop mainThreadExecutor, LightChunkGetter chunkProvider, ChunkGenerator chunkGenerator, ChunkProgressListener worldGenerationProgressListener, ChunkStatusUpdateListener chunkStatusChangeListener, Supplier persistentStateManagerFactory, int viewDistance, boolean dsync) { super(new RegionStorageInfo(session.getLevelId(), world.dimension(), "chunk"), session.getDimensionPath(world.dimension()).resolve("region"), dataFixer, dsync); this.visibleChunkMap = this.updatingChunkMap.clone(); -@@ -221,8 +277,24 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -221,8 +252,22 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider this.poiManager = new PoiManager(new RegionStorageInfo(session.getLevelId(), world.dimension(), "poi"), path.resolve("poi"), dataFixer, dsync, iregistrycustom, world.getServer(), world); this.setServerViewDistance(viewDistance); this.worldGenContext = new WorldGenContext(world, chunkGenerator, structureTemplateManager, this.lightEngine, this.mainThreadMailbox); + // Paper start -+ this.dataRegionManager = new io.papermc.paper.chunk.SingleThreadChunkRegionManager(this.level, 2, (1.0 / 3.0), 1, 6, "Data", DataRegionData::new, DataRegionSectionData::new); -+ this.regionManagers.add(this.dataRegionManager); + this.nearbyPlayers = new io.papermc.paper.util.player.NearbyPlayers(this.level); + // Paper end + } @@ -6438,7 +4442,7 @@ index 5b920beb39dad8d392b4e5e12a89880720e41942..319f51eb8adde7584c74780ac0539f4b protected ChunkGenerator generator() { return this.worldGenContext.generator(); } -@@ -378,9 +450,9 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -378,9 +423,9 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider }; stringbuilder.append("Updating:").append(System.lineSeparator()); @@ -6450,7 +4454,7 @@ index 5b920beb39dad8d392b4e5e12a89880720e41942..319f51eb8adde7584c74780ac0539f4b CrashReport crashreport = CrashReport.forThrowable(exception, "Chunk loading"); CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Chunk loading"); -@@ -422,8 +494,14 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -422,8 +467,14 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider holder.setTicketLevel(level); } else { holder = new ChunkHolder(new ChunkPos(pos), level, this.level, this.lightEngine, this.queueSorter, this); @@ -6465,7 +4469,7 @@ index 5b920beb39dad8d392b4e5e12a89880720e41942..319f51eb8adde7584c74780ac0539f4b this.updatingChunkMap.put(pos, holder); this.modified = true; } -@@ -445,7 +523,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -445,7 +496,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider protected void saveAllChunks(boolean flush) { if (flush) { @@ -6474,7 +4478,7 @@ index 5b920beb39dad8d392b4e5e12a89880720e41942..319f51eb8adde7584c74780ac0539f4b MutableBoolean mutableboolean = new MutableBoolean(); do { -@@ -468,7 +546,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -468,7 +519,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider }); this.flushWorker(); } else { @@ -6483,7 +4487,7 @@ index 5b920beb39dad8d392b4e5e12a89880720e41942..319f51eb8adde7584c74780ac0539f4b } } -@@ -487,7 +565,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -487,7 +538,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider } public boolean hasWork() { @@ -6492,7 +4496,7 @@ index 5b920beb39dad8d392b4e5e12a89880720e41942..319f51eb8adde7584c74780ac0539f4b } private void processUnloads(BooleanSupplier shouldKeepTicking) { -@@ -504,6 +582,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -504,6 +555,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider } this.updatingChunkMap.remove(j); @@ -6500,7 +4504,7 @@ index 5b920beb39dad8d392b4e5e12a89880720e41942..319f51eb8adde7584c74780ac0539f4b this.pendingUnloads.put(j, playerchunk); this.modified = true; ++i; -@@ -523,7 +602,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -523,7 +575,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider } int l = 0; @@ -6509,7 +4513,7 @@ index 5b920beb39dad8d392b4e5e12a89880720e41942..319f51eb8adde7584c74780ac0539f4b while (l < 20 && shouldKeepTicking.getAsBoolean() && objectiterator.hasNext()) { if (this.saveChunkIfNeeded((ChunkHolder) objectiterator.next())) { -@@ -541,7 +620,11 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -541,7 +593,11 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider } else { ChunkAccess ichunkaccess = holder.getLatestChunk(); @@ -6522,7 +4526,7 @@ index 5b920beb39dad8d392b4e5e12a89880720e41942..319f51eb8adde7584c74780ac0539f4b LevelChunk chunk; if (ichunkaccess instanceof LevelChunk) { -@@ -559,7 +642,9 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -559,7 +615,9 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider this.lightEngine.tryScheduleUpdate(); this.progressListener.onStatusChange(ichunkaccess.getPos(), (ChunkStatus) null); this.chunkSaveCooldowns.remove(ichunkaccess.getPos().toLong()); @@ -6533,7 +4537,7 @@ index 5b920beb39dad8d392b4e5e12a89880720e41942..319f51eb8adde7584c74780ac0539f4b } }; -@@ -896,7 +981,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -896,7 +954,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider } } @@ -6542,7 +4546,7 @@ index 5b920beb39dad8d392b4e5e12a89880720e41942..319f51eb8adde7584c74780ac0539f4b int j = Mth.clamp(watchDistance, 2, 32); if (j != this.serverViewDistance) { -@@ -913,7 +998,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -913,7 +971,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider } @@ -6551,7 +4555,7 @@ index 5b920beb39dad8d392b4e5e12a89880720e41942..319f51eb8adde7584c74780ac0539f4b return Mth.clamp(player.requestedViewDistance(), 2, this.serverViewDistance); } -@@ -942,7 +1027,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -942,7 +1000,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider } public int size() { @@ -6560,7 +4564,7 @@ index 5b920beb39dad8d392b4e5e12a89880720e41942..319f51eb8adde7584c74780ac0539f4b } public DistanceManager getDistanceManager() { -@@ -950,19 +1035,19 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -950,19 +1008,19 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider } protected Iterable getChunks() { @@ -6585,7 +4589,7 @@ index 5b920beb39dad8d392b4e5e12a89880720e41942..319f51eb8adde7584c74780ac0539f4b Optional optional = Optional.ofNullable(playerchunk.getLatestChunk()); Optional optional1 = optional.flatMap((ichunkaccess) -> { return ichunkaccess instanceof LevelChunk ? Optional.of((LevelChunk) ichunkaccess) : Optional.empty(); -@@ -1083,6 +1168,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1083,6 +1141,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider player.setChunkTrackingView(ChunkTrackingView.EMPTY); this.updateChunkTracking(player); @@ -6593,7 +4597,7 @@ index 5b920beb39dad8d392b4e5e12a89880720e41942..319f51eb8adde7584c74780ac0539f4b } else { SectionPos sectionposition = player.getLastSectionPos(); -@@ -1091,6 +1177,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1091,6 +1150,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider this.distanceManager.removePlayer(sectionposition, player); } @@ -6601,7 +4605,7 @@ index 5b920beb39dad8d392b4e5e12a89880720e41942..319f51eb8adde7584c74780ac0539f4b this.applyChunkTrackingView(player, ChunkTrackingView.EMPTY); } -@@ -1142,6 +1229,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1142,6 +1202,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider this.updateChunkTracking(player); } @@ -6609,7 +4613,7 @@ index 5b920beb39dad8d392b4e5e12a89880720e41942..319f51eb8adde7584c74780ac0539f4b } private void updateChunkTracking(ServerPlayer player) { -@@ -1385,10 +1473,10 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1385,10 +1446,10 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider }); } @@ -6655,7 +4659,7 @@ index b6cc33943fe7e4667944f3e6f868b3033ea9ca18..27065ffc5473c518acee3a3096b83fac while (objectiterator.hasNext()) { diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -index d39268911ed7c4d60ee6a82178be23245aae58c4..ab57071cc6ce8b79d883f2426855c1abf577e90d 100644 +index d39268911ed7c4d60ee6a82178be23245aae58c4..e9f53f57c363a32106880ea9aad0ccf5a7342509 100644 --- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java +++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java @@ -48,6 +48,7 @@ import net.minecraft.world.level.storage.LevelStorageSource; @@ -6666,81 +4670,30 @@ index d39268911ed7c4d60ee6a82178be23245aae58c4..ab57071cc6ce8b79d883f2426855c1ab private static final List CHUNK_STATUSES = ChunkStatus.getStatusList(); private final DistanceManager distanceManager; final ServerLevel level; -@@ -66,6 +67,14 @@ public class ServerChunkCache extends ChunkSource { +@@ -66,6 +67,12 @@ public class ServerChunkCache extends ChunkSource { @Nullable @VisibleForDebug private NaturalSpawner.SpawnState lastSpawnState; + // Paper start + public final io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet tickingChunks = new io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet<>(4096, 0.75f, 4096, 0.15, true); + public final io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet entityTickingChunks = new io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet<>(4096, 0.75f, 4096, 0.15, true); -+ final com.destroystokyo.paper.util.concurrent.WeakSeqLock loadedChunkMapSeqLock = new com.destroystokyo.paper.util.concurrent.WeakSeqLock(); -+ final it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap loadedChunkMap = new it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap<>(8192, 0.5f); ++ private final ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable fullChunks = new ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable<>(); + long chunkFutureAwaitCounter; -+ private final LevelChunk[] lastLoadedChunks = new LevelChunk[4 * 4]; + // Paper end public ServerChunkCache(ServerLevel world, LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, StructureTemplateManager structureTemplateManager, Executor workerExecutor, ChunkGenerator chunkGenerator, int viewDistance, int simulationDistance, boolean dsync, ChunkProgressListener worldGenerationProgressListener, ChunkStatusUpdateListener chunkStatusChangeListener, Supplier persistentStateManagerFactory) { this.level = world; -@@ -91,6 +100,124 @@ public class ServerChunkCache extends ChunkSource { +@@ -91,6 +98,54 @@ public class ServerChunkCache extends ChunkSource { return chunk.getFullChunkNow() != null; } // CraftBukkit end + // Paper start -+ private static int getChunkCacheKey(int x, int z) { -+ return x & 3 | ((z & 3) << 2); -+ } -+ + public void addLoadedChunk(LevelChunk chunk) { -+ this.loadedChunkMapSeqLock.acquireWrite(); -+ try { -+ this.loadedChunkMap.put(chunk.coordinateKey, chunk); -+ } finally { -+ this.loadedChunkMapSeqLock.releaseWrite(); -+ } -+ -+ // rewrite cache if we have to -+ // we do this since we also cache null chunks -+ int cacheKey = getChunkCacheKey(chunk.locX, chunk.locZ); -+ -+ this.lastLoadedChunks[cacheKey] = chunk; ++ this.fullChunks.put(chunk.coordinateKey, chunk); + } + + public void removeLoadedChunk(LevelChunk chunk) { -+ this.loadedChunkMapSeqLock.acquireWrite(); -+ try { -+ this.loadedChunkMap.remove(chunk.coordinateKey); -+ } finally { -+ this.loadedChunkMapSeqLock.releaseWrite(); -+ } -+ -+ // rewrite cache if we have to -+ // we do this since we also cache null chunks -+ int cacheKey = getChunkCacheKey(chunk.locX, chunk.locZ); -+ -+ LevelChunk cachedChunk = this.lastLoadedChunks[cacheKey]; -+ if (cachedChunk != null && cachedChunk.coordinateKey == chunk.coordinateKey) { -+ this.lastLoadedChunks[cacheKey] = null; -+ } -+ } -+ -+ public final LevelChunk getChunkAtIfLoadedMainThread(int x, int z) { -+ int cacheKey = getChunkCacheKey(x, z); -+ -+ LevelChunk cachedChunk = this.lastLoadedChunks[cacheKey]; -+ if (cachedChunk != null && cachedChunk.locX == x & cachedChunk.locZ == z) { -+ return cachedChunk; -+ } -+ -+ long chunkKey = ChunkPos.asLong(x, z); -+ -+ cachedChunk = this.loadedChunkMap.get(chunkKey); -+ // Skipping a null check to avoid extra instructions to improve inline capability -+ this.lastLoadedChunks[cacheKey] = cachedChunk; -+ return cachedChunk; -+ } -+ -+ public final LevelChunk getChunkAtIfLoadedMainThreadNoCache(int x, int z) { -+ return this.loadedChunkMap.get(ChunkPos.asLong(x, z)); ++ this.fullChunks.remove(chunk.coordinateKey); + } + + @Nullable @@ -6779,34 +4732,13 @@ index d39268911ed7c4d60ee6a82178be23245aae58c4..ab57071cc6ce8b79d883f2426855c1ab + + @Nullable + public LevelChunk getChunkAtIfLoadedImmediately(int x, int z) { -+ long k = ChunkPos.asLong(x, z); -+ -+ if (Thread.currentThread() == this.mainThread) { -+ return this.getChunkAtIfLoadedMainThread(x, z); -+ } -+ -+ LevelChunk ret = null; -+ long readlock; -+ do { -+ readlock = this.loadedChunkMapSeqLock.acquireRead(); -+ try { -+ ret = this.loadedChunkMap.get(k); -+ } catch (Throwable thr) { -+ if (thr instanceof ThreadDeath) { -+ throw (ThreadDeath)thr; -+ } -+ // re-try, this means a CME occurred... -+ continue; -+ } -+ } while (!this.loadedChunkMapSeqLock.tryReleaseRead(readlock)); -+ -+ return ret; ++ return this.fullChunks.get(ChunkPos.asLong(x, z)); + } + // Paper end @Override public ThreadedLevelLightEngine getLightEngine() { -@@ -286,7 +413,7 @@ public class ServerChunkCache extends ChunkSource { +@@ -286,7 +341,7 @@ public class ServerChunkCache extends ChunkSource { return this.mainThreadProcessor.pollTask(); } @@ -6815,7 +4747,7 @@ index d39268911ed7c4d60ee6a82178be23245aae58c4..ab57071cc6ce8b79d883f2426855c1ab boolean flag = this.distanceManager.runAllUpdates(this.chunkMap); boolean flag1 = this.chunkMap.promoteChunkMap(); -@@ -299,6 +426,12 @@ public class ServerChunkCache extends ChunkSource { +@@ -299,6 +354,12 @@ public class ServerChunkCache extends ChunkSource { } } @@ -7466,120 +5398,21 @@ index a52077f0d93c94b0ea644bc14b9b28e84fd1b154..dcc0acd259920463a4464213b9a5e793 @Nullable @Override diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -index b537e7a079497db428db405edfccde74f32f4208..7898e1aaf82f096fa74bd3f5859f0f4303ea677f 100644 +index b537e7a079497db428db405edfccde74f32f4208..c664021dbfffcf0db3247041270ce9a1ee6940de 100644 --- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -@@ -116,6 +116,109 @@ public class LevelChunk extends ChunkAccess { +@@ -116,6 +116,10 @@ public class LevelChunk extends ChunkAccess { public boolean needsDecoration; // CraftBukkit end + // Paper start -+ public @Nullable net.minecraft.server.level.ChunkHolder playerChunk; -+ -+ static final int NEIGHBOUR_CACHE_RADIUS = 3; -+ public static int getNeighbourCacheRadius() { -+ return NEIGHBOUR_CACHE_RADIUS; -+ } -+ + boolean loadedTicketLevel; -+ private long neighbourChunksLoadedBitset; -+ private final LevelChunk[] loadedNeighbourChunks = new LevelChunk[(NEIGHBOUR_CACHE_RADIUS * 2 + 1) * (NEIGHBOUR_CACHE_RADIUS * 2 + 1)]; -+ -+ private static int getNeighbourIndex(final int relativeX, final int relativeZ) { -+ // index = (relativeX + NEIGHBOUR_CACHE_RADIUS) + (relativeZ + NEIGHBOUR_CACHE_RADIUS) * (NEIGHBOUR_CACHE_RADIUS * 2 + 1) -+ // optimised variant of the above by moving some of the ops to compile time -+ return relativeX + (relativeZ * (NEIGHBOUR_CACHE_RADIUS * 2 + 1)) + (NEIGHBOUR_CACHE_RADIUS + NEIGHBOUR_CACHE_RADIUS * ((NEIGHBOUR_CACHE_RADIUS * 2 + 1))); -+ } -+ -+ public final LevelChunk getRelativeNeighbourIfLoaded(final int relativeX, final int relativeZ) { -+ return this.loadedNeighbourChunks[getNeighbourIndex(relativeX, relativeZ)]; -+ } -+ -+ public final boolean isNeighbourLoaded(final int relativeX, final int relativeZ) { -+ return (this.neighbourChunksLoadedBitset & (1L << getNeighbourIndex(relativeX, relativeZ))) != 0; -+ } -+ -+ public final void setNeighbourLoaded(final int relativeX, final int relativeZ, final LevelChunk chunk) { -+ if (chunk == null) { -+ throw new IllegalArgumentException("Chunk must be non-null, neighbour: (" + relativeX + "," + relativeZ + "), chunk: " + this.chunkPos); -+ } -+ final long before = this.neighbourChunksLoadedBitset; -+ final int index = getNeighbourIndex(relativeX, relativeZ); -+ this.loadedNeighbourChunks[index] = chunk; -+ this.neighbourChunksLoadedBitset |= (1L << index); -+ this.onNeighbourChange(before, this.neighbourChunksLoadedBitset); -+ } -+ -+ public final void setNeighbourUnloaded(final int relativeX, final int relativeZ) { -+ final long before = this.neighbourChunksLoadedBitset; -+ final int index = getNeighbourIndex(relativeX, relativeZ); -+ this.loadedNeighbourChunks[index] = null; -+ this.neighbourChunksLoadedBitset &= ~(1L << index); -+ this.onNeighbourChange(before, this.neighbourChunksLoadedBitset); -+ } -+ -+ public final void resetNeighbours() { -+ final long before = this.neighbourChunksLoadedBitset; -+ this.neighbourChunksLoadedBitset = 0L; -+ java.util.Arrays.fill(this.loadedNeighbourChunks, null); -+ this.onNeighbourChange(before, 0L); -+ } -+ -+ protected void onNeighbourChange(final long bitsetBefore, final long bitsetAfter) { -+ -+ } -+ -+ public final boolean isAnyNeighborsLoaded() { -+ return neighbourChunksLoadedBitset != 0; -+ } -+ public final boolean areNeighboursLoaded(final int radius) { -+ return LevelChunk.areNeighboursLoaded(this.neighbourChunksLoadedBitset, radius); -+ } -+ -+ public static boolean areNeighboursLoaded(final long bitset, final int radius) { -+ // index = relativeX + (relativeZ * (NEIGHBOUR_CACHE_RADIUS * 2 + 1)) + (NEIGHBOUR_CACHE_RADIUS + NEIGHBOUR_CACHE_RADIUS * ((NEIGHBOUR_CACHE_RADIUS * 2 + 1))) -+ switch (radius) { -+ case 0: { -+ return (bitset & (1L << getNeighbourIndex(0, 0))) != 0; -+ } -+ case 1: { -+ long mask = 0L; -+ for (int dx = -1; dx <= 1; ++dx) { -+ for (int dz = -1; dz <= 1; ++dz) { -+ mask |= (1L << getNeighbourIndex(dx, dz)); -+ } -+ } -+ return (bitset & mask) == mask; -+ } -+ case 2: { -+ long mask = 0L; -+ for (int dx = -2; dx <= 2; ++dx) { -+ for (int dz = -2; dz <= 2; ++dz) { -+ mask |= (1L << getNeighbourIndex(dx, dz)); -+ } -+ } -+ return (bitset & mask) == mask; -+ } -+ case 3: { -+ long mask = 0L; -+ for (int dx = -3; dx <= 3; ++dx) { -+ for (int dz = -3; dz <= 3; ++dz) { -+ mask |= (1L << getNeighbourIndex(dx, dz)); -+ } -+ } -+ return (bitset & mask) == mask; -+ } -+ -+ default: -+ throw new IllegalArgumentException("Radius not recognized: " + radius); -+ } -+ } + // Paper end + public LevelChunk(ServerLevel world, ProtoChunk protoChunk, @Nullable LevelChunk.PostLoadProcessor entityLoader) { this(world, protoChunk.getPos(), protoChunk.getUpgradeData(), protoChunk.unpackBlockTicks(), protoChunk.unpackFluidTicks(), protoChunk.getInhabitedTime(), protoChunk.getSections(), entityLoader, protoChunk.getBlendingData()); Iterator iterator = protoChunk.getBlockEntities().values().iterator(); -@@ -181,8 +284,25 @@ public class LevelChunk extends ChunkAccess { +@@ -181,8 +185,25 @@ public class LevelChunk extends ChunkAccess { } } @@ -7605,7 +5438,7 @@ index b537e7a079497db428db405edfccde74f32f4208..7898e1aaf82f096fa74bd3f5859f0f43 int i = pos.getX(); int j = pos.getY(); int k = pos.getZ(); -@@ -224,6 +344,18 @@ public class LevelChunk extends ChunkAccess { +@@ -224,6 +245,18 @@ public class LevelChunk extends ChunkAccess { } } @@ -7624,51 +5457,25 @@ index b537e7a079497db428db405edfccde74f32f4208..7898e1aaf82f096fa74bd3f5859f0f43 @Override public FluidState getFluidState(BlockPos pos) { return this.getFluidState(pos.getX(), pos.getY(), pos.getZ()); -@@ -549,7 +681,25 @@ public class LevelChunk extends ChunkAccess { +@@ -549,7 +582,11 @@ public class LevelChunk extends ChunkAccess { // CraftBukkit start public void loadCallback() { -+ // Paper start - neighbour cache -+ int chunkX = this.chunkPos.x; -+ int chunkZ = this.chunkPos.z; -+ net.minecraft.server.level.ServerChunkCache chunkProvider = this.level.getChunkSource(); -+ for (int dx = -NEIGHBOUR_CACHE_RADIUS; dx <= NEIGHBOUR_CACHE_RADIUS; ++dx) { -+ for (int dz = -NEIGHBOUR_CACHE_RADIUS; dz <= NEIGHBOUR_CACHE_RADIUS; ++dz) { -+ LevelChunk neighbour = chunkProvider.getChunkAtIfLoadedMainThreadNoCache(chunkX + dx, chunkZ + dz); -+ if (neighbour != null) { -+ neighbour.setNeighbourLoaded(-dx, -dz, this); -+ // should be in cached already -+ this.setNeighbourLoaded(dx, dz, neighbour); -+ } -+ } -+ } -+ this.setNeighbourLoaded(0, 0, this); ++ // Paper start + this.loadedTicketLevel = true; -+ // Paper end - neighbour cache ++ // Paper end org.bukkit.Server server = this.level.getCraftServer(); + this.level.getChunkSource().addLoadedChunk(this); // Paper if (server != null) { /* * If it's a new world, the first few chunks are generated inside -@@ -590,6 +740,22 @@ public class LevelChunk extends ChunkAccess { +@@ -590,6 +627,10 @@ public class LevelChunk extends ChunkAccess { server.getPluginManager().callEvent(unloadEvent); // note: saving can be prevented, but not forced if no saving is actually required this.mustNotSave = !unloadEvent.isSaveChunk(); + this.level.getChunkSource().removeLoadedChunk(this); // Paper -+ // Paper start - neighbour cache -+ int chunkX = this.chunkPos.x; -+ int chunkZ = this.chunkPos.z; -+ net.minecraft.server.level.ServerChunkCache chunkProvider = this.level.getChunkSource(); -+ for (int dx = -NEIGHBOUR_CACHE_RADIUS; dx <= NEIGHBOUR_CACHE_RADIUS; ++dx) { -+ for (int dz = -NEIGHBOUR_CACHE_RADIUS; dz <= NEIGHBOUR_CACHE_RADIUS; ++dz) { -+ LevelChunk neighbour = chunkProvider.getChunkAtIfLoadedMainThreadNoCache(chunkX + dx, chunkZ + dz); -+ if (neighbour != null) { -+ neighbour.setNeighbourUnloaded(-dx, -dz); -+ } -+ } -+ } ++ // Paper start + this.loadedTicketLevel = false; -+ this.resetNeighbours(); + // Paper end } @@ -8150,7 +5957,7 @@ index e08d4a45e313ef1b9005ef00ee0185a188171207..2fc68d129e2fdfd51e310ea5bdfb8332 public static byte toLegacyData(BlockState data) { diff --git a/src/main/java/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java b/src/main/java/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java -index 5fd6eb754c4edebed6798c65b06507a4e89ca48f..0794d92c42b0db6b367505ae28f09f1fd39fa312 100644 +index 5fd6eb754c4edebed6798c65b06507a4e89ca48f..524b51a0ab808a0629c871ad813115abd4b49dbd 100644 --- a/src/main/java/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java +++ b/src/main/java/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java @@ -58,6 +58,7 @@ import net.minecraft.world.phys.shapes.VoxelShape; @@ -8170,19 +5977,19 @@ index 5fd6eb754c4edebed6798c65b06507a4e89ca48f..0794d92c42b0db6b367505ae28f09f1f + @Nullable + @Override + public BlockState getBlockStateIfLoaded(final BlockPos blockposition) { -+ return null; ++ return this.handle.getBlockStateIfLoaded(blockposition); + } + + @Nullable + @Override + public FluidState getFluidIfLoaded(final BlockPos blockposition) { -+ return null; ++ return this.handle.getFluidIfLoaded(blockposition); + } + + @Nullable + @Override + public ChunkAccess getChunkIfLoadedImmediately(final int x, final int z) { -+ return null; ++ return this.handle.getChunkIfLoadedImmediately(x, z); + } + // Paper end } diff --git a/patches/server/0010-Adventure.patch b/patches/server/0010-Adventure.patch index c6a160a49b..783ccc1503 100644 --- a/patches/server/0010-Adventure.patch +++ b/patches/server/0010-Adventure.patch @@ -2606,7 +2606,7 @@ index bb97fdb9aa6167083442a928276ebe4225a586ef..5d1758086ed4fce5b36a5b31df44ccea @Override diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index b1325e090f2c7aff31d27fc38ca7173efe31ed7c..0742aaf07f37e51d24295e7819ac6cec961c7626 100644 +index 9eb987f9d86396d6b7e9d4f3834bea3326640ac7..25d6be308be03815301cfbefdc4199c898f7b9c0 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -201,6 +201,7 @@ import org.bukkit.craftbukkit.SpigotTimings; // Spigot @@ -2644,7 +2644,7 @@ index b1325e090f2c7aff31d27fc38ca7173efe31ed7c..0742aaf07f37e51d24295e7819ac6cec this.profiler.push("commandFunctions"); SpigotTimings.commandFunctionsTimer.startTiming(); // Spigot this.getFunctions().tick(); -@@ -1805,10 +1806,20 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop list = Lists.newArrayList(); List list1 = this.level.players(); ObjectIterator objectiterator = this.entityMap.values().iterator(); @@ -1000,7 +1000,7 @@ index 319f51eb8adde7584c74780ac0539f4b8ef8fe7f..ddadb0f13b96a39ec89cdaeea7bc02ee ChunkMap.TrackedEntity playerchunkmap_entitytracker; -@@ -1387,14 +1390,17 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1360,14 +1363,17 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider playerchunkmap_entitytracker.serverEntity.sendChanges(); } } @@ -1019,10 +1019,10 @@ index 319f51eb8adde7584c74780ac0539f4b8ef8fe7f..ddadb0f13b96a39ec89cdaeea7bc02ee } diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -index ab57071cc6ce8b79d883f2426855c1abf577e90d..c627d32e7dbc83b9fc60b89c73bff5c9992ad548 100644 +index e9f53f57c363a32106880ea9aad0ccf5a7342509..0ff45d4ce6ae0b7feac53c7a9f361f6454f8b7c3 100644 --- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java +++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -@@ -269,13 +269,15 @@ public class ServerChunkCache extends ChunkSource { +@@ -197,13 +197,15 @@ public class ServerChunkCache extends ChunkSource { } gameprofilerfiller.incrementCounter("getChunkCacheMiss"); @@ -1040,7 +1040,7 @@ index ab57071cc6ce8b79d883f2426855c1abf577e90d..c627d32e7dbc83b9fc60b89c73bff5c9 ChunkResult chunkresult = (ChunkResult) completablefuture.join(); ChunkAccess ichunkaccess1 = (ChunkAccess) chunkresult.orElse(null); // CraftBukkit - decompile error -@@ -440,7 +442,9 @@ public class ServerChunkCache extends ChunkSource { +@@ -368,7 +370,9 @@ public class ServerChunkCache extends ChunkSource { public void save(boolean flush) { this.runDistanceManagerUpdates(); @@ -1050,7 +1050,7 @@ index ab57071cc6ce8b79d883f2426855c1abf577e90d..c627d32e7dbc83b9fc60b89c73bff5c9 } @Override -@@ -482,10 +486,10 @@ public class ServerChunkCache extends ChunkSource { +@@ -410,10 +414,10 @@ public class ServerChunkCache extends ChunkSource { this.level.timings.doChunkMap.stopTiming(); // Spigot this.level.getProfiler().popPush("chunks"); if (tickChunks) { @@ -1063,7 +1063,7 @@ index ab57071cc6ce8b79d883f2426855c1abf577e90d..c627d32e7dbc83b9fc60b89c73bff5c9 } this.level.timings.doChunkUnload.startTiming(); // Spigot -@@ -508,6 +512,7 @@ public class ServerChunkCache extends ChunkSource { +@@ -436,6 +440,7 @@ public class ServerChunkCache extends ChunkSource { gameprofilerfiller.push("filteringLoadedChunks"); List list = Lists.newArrayListWithCapacity(this.chunkMap.size()); Iterator iterator = this.chunkMap.getChunks().iterator(); @@ -1071,7 +1071,7 @@ index ab57071cc6ce8b79d883f2426855c1abf577e90d..c627d32e7dbc83b9fc60b89c73bff5c9 while (iterator.hasNext()) { ChunkHolder playerchunk = (ChunkHolder) iterator.next(); -@@ -520,8 +525,10 @@ public class ServerChunkCache extends ChunkSource { +@@ -448,8 +453,10 @@ public class ServerChunkCache extends ChunkSource { if (this.level.tickRateManager().runsNormally()) { gameprofilerfiller.popPush("naturalSpawnCount"); @@ -1082,7 +1082,7 @@ index ab57071cc6ce8b79d883f2426855c1abf577e90d..c627d32e7dbc83b9fc60b89c73bff5c9 this.lastSpawnState = spawnercreature_d; gameprofilerfiller.popPush("spawnAndTick"); -@@ -544,22 +551,25 @@ public class ServerChunkCache extends ChunkSource { +@@ -472,22 +479,25 @@ public class ServerChunkCache extends ChunkSource { } if (this.level.shouldTickBlocksAt(chunkcoordintpair.toLong())) { @@ -1545,10 +1545,10 @@ index e6c586eb85c6c477a3c130e1e1a37b41f17c30c8..6e35709f2a7c32050908e7e5af5529c9 private static final CraftPersistentDataTypeRegistry DATA_TYPE_REGISTRY = new CraftPersistentDataTypeRegistry(); public CraftPersistentDataContainer persistentDataContainer; diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -index 7898e1aaf82f096fa74bd3f5859f0f4303ea677f..05d959ccc424aaaa465ec256213f2ec4d44ef8b5 100644 +index c664021dbfffcf0db3247041270ce9a1ee6940de..332351b78fa22cd354b916c4a29bea5b4b223e40 100644 --- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -@@ -710,6 +710,7 @@ public class LevelChunk extends ChunkAccess { +@@ -597,6 +597,7 @@ public class LevelChunk extends ChunkAccess { server.getPluginManager().callEvent(new org.bukkit.event.world.ChunkLoadEvent(bukkitChunk, this.needsDecoration)); if (this.needsDecoration) { @@ -1556,7 +1556,7 @@ index 7898e1aaf82f096fa74bd3f5859f0f4303ea677f..05d959ccc424aaaa465ec256213f2ec4 this.needsDecoration = false; java.util.Random random = new java.util.Random(); random.setSeed(this.level.getSeed()); -@@ -729,6 +730,7 @@ public class LevelChunk extends ChunkAccess { +@@ -616,6 +617,7 @@ public class LevelChunk extends ChunkAccess { } } server.getPluginManager().callEvent(new org.bukkit.event.world.ChunkPopulateEvent(bukkitChunk)); diff --git a/patches/server/0025-Further-improve-server-tick-loop.patch b/patches/server/0025-Further-improve-server-tick-loop.patch index 0a1f62fc08..0fc8d093bf 100644 --- a/patches/server/0025-Further-improve-server-tick-loop.patch +++ b/patches/server/0025-Further-improve-server-tick-loop.patch @@ -12,7 +12,7 @@ Previous implementation did not calculate TPS correctly. Switch to a realistic rolling average and factor in std deviation as an extra reporting variable diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 4137cf4d716680ff1b1ab0b8a3e8f7cb4bae7dbe..8fbc764fd2802f735b9d5fac2dabca6a7cebe58e 100644 +index a720743149f9d1f90eda3b7b928b9d25d8c8f553..c09f3dc29c86fef9edfe7831776a77fc73e52210 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -296,7 +296,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { -@@ -2554,9 +2556,8 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop this.level.spigotConfig.viewDistance) ? (byte) this.level.spigotConfig.viewDistance : chunkRange; chunkRange = (chunkRange > 8) ? 8 : chunkRange; @@ -23,7 +23,7 @@ index ddadb0f13b96a39ec89cdaeea7bc02ee62ef2a06..d04b69838c6f5fd1808782cacb31c6e0 // Spigot end if (!this.distanceManager.hasPlayersNearby(chunkcoordintpair.toLong())) { return false; -@@ -1116,6 +1118,15 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1089,6 +1091,15 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider } entityplayer = (ServerPlayer) iterator.next(); @@ -40,10 +40,10 @@ index ddadb0f13b96a39ec89cdaeea7bc02ee62ef2a06..d04b69838c6f5fd1808782cacb31c6e0 return true; diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -index c627d32e7dbc83b9fc60b89c73bff5c9992ad548..f4451bb402abbc8e6506132b9b9702bb5d75e097 100644 +index 0ff45d4ce6ae0b7feac53c7a9f361f6454f8b7c3..035c33b1e1be4f41e5cbe09bf6c3abd617258cad 100644 --- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java +++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -@@ -535,6 +535,15 @@ public class ServerChunkCache extends ChunkSource { +@@ -463,6 +463,15 @@ public class ServerChunkCache extends ChunkSource { boolean flag = this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && !this.level.players().isEmpty(); // CraftBukkit Util.shuffle(list, this.level.random); diff --git a/patches/server/0175-Implement-extended-PaperServerListPingEvent.patch b/patches/server/0175-Implement-extended-PaperServerListPingEvent.patch index bdf24696e3..4ca3384be1 100644 --- a/patches/server/0175-Implement-extended-PaperServerListPingEvent.patch +++ b/patches/server/0175-Implement-extended-PaperServerListPingEvent.patch @@ -162,7 +162,7 @@ index 0000000000000000000000000000000000000000..874dcbefea469440a9028ddc399a37c9 + +} diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 7335f9bb936eeb585ee077b0b9c461d7946d6134..1c024fb7f682c81c465f59f4ab5fbeac964d73e1 100644 +index 6c46ce47d1460dcd9b387f02a09d85048a45b049..f71b1c2497594b592bcc51be9fa6a6d8fe8e819b 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -3,6 +3,9 @@ package net.minecraft.server; diff --git a/patches/server/0227-Add-Debug-Entities-option-to-debug-dupe-uuid-issues.patch b/patches/server/0227-Add-Debug-Entities-option-to-debug-dupe-uuid-issues.patch index d443d145a4..df5cc389e6 100644 --- a/patches/server/0227-Add-Debug-Entities-option-to-debug-dupe-uuid-issues.patch +++ b/patches/server/0227-Add-Debug-Entities-option-to-debug-dupe-uuid-issues.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Add Debug Entities option to debug dupe uuid issues diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index d04b69838c6f5fd1808782cacb31c6e00087bbac..96b7f0ac35a1e87c3f78a24180b207c32749fb71 100644 +index 767e3aed729b630d67a702d5e5bda02e9865e6e1..27eaeea3f0719167d89e39b0b60f91ad9cc9f64e 100644 --- a/src/main/java/net/minecraft/server/level/ChunkMap.java +++ b/src/main/java/net/minecraft/server/level/ChunkMap.java -@@ -1321,6 +1321,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1294,6 +1294,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider } else { ChunkMap.TrackedEntity playerchunkmap_entitytracker = new ChunkMap.TrackedEntity(entity, i, j, entitytypes.trackDeltas()); @@ -16,7 +16,7 @@ index d04b69838c6f5fd1808782cacb31c6e00087bbac..96b7f0ac35a1e87c3f78a24180b207c3 this.entityMap.put(entity.getId(), playerchunkmap_entitytracker); playerchunkmap_entitytracker.updatePlayers(this.level.players()); if (entity instanceof ServerPlayer) { -@@ -1361,7 +1362,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1334,7 +1335,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider if (playerchunkmap_entitytracker1 != null) { playerchunkmap_entitytracker1.broadcastRemoved(); } diff --git a/patches/server/0228-Add-Early-Warning-Feature-to-WatchDog.patch b/patches/server/0228-Add-Early-Warning-Feature-to-WatchDog.patch index 6be15047b5..545fb80f1b 100644 --- a/patches/server/0228-Add-Early-Warning-Feature-to-WatchDog.patch +++ b/patches/server/0228-Add-Early-Warning-Feature-to-WatchDog.patch @@ -9,7 +9,7 @@ thread dumps at an interval until the point of crash. This will help diagnose what was going on in that time before the crash. diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 1c024fb7f682c81c465f59f4ab5fbeac964d73e1..3fbe9a4981c682ec602d8ad1c390a10f26505f08 100644 +index f71b1c2497594b592bcc51be9fa6a6d8fe8e819b..6a97ab5e8a435e67df77b1d0480ccb397314ee05 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -1108,6 +1108,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop completablefuture = CompletableFuture.supplyAsync(() -> { Stream stream = dataPacks.stream(); // CraftBukkit - decompile error PackRepository resourcepackrepository = this.packRepository; -@@ -2154,6 +2160,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0; // Paper - BlockPhysicsEvent -@@ -1655,6 +1658,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { @@ -2254,7 +2254,7 @@ index 837fc12dfc57f36f06bd8e49681bb4b98a87397c..6915522f669631779c1fb8a8e2db330f this.packRepository.setSelected(dataPacks); WorldDataConfiguration worlddataconfiguration = new WorldDataConfiguration(MinecraftServer.getSelectedPacks(this.packRepository, true), this.worldData.enabledFeatures()); -@@ -2200,8 +2202,17 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop capturedTileEntities = new java.util.LinkedHashMap<>(); // Paper - Retain block place order when capturing blockstates public List captureDrops; diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -index 8ae6a4311aa52f84130a1eba61a1ad84e2272350..6c0e12c9c9c0fb8377cd1f48a43ca75c9fc3e58e 100644 +index 7f8983a2102787b13e5d28d6981055da6acd1012..602ad80c2b93d320bf2a25832d25a58cb8c72e4b 100644 --- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -@@ -444,7 +444,7 @@ public class LevelChunk extends ChunkAccess { +@@ -345,7 +345,7 @@ public class LevelChunk extends ChunkAccess { boolean flag3 = iblockdata1.hasBlockEntity(); diff --git a/patches/server/0991-Chunk-System-Starlight-from-Moonrise.patch b/patches/server/0991-Chunk-System-Starlight-from-Moonrise.patch index 3a0997f047..cde583319b 100644 --- a/patches/server/0991-Chunk-System-Starlight-from-Moonrise.patch +++ b/patches/server/0991-Chunk-System-Starlight-from-Moonrise.patch @@ -3105,10 +3105,10 @@ index 0000000000000000000000000000000000000000..e95cc73ddf20050aa4a241b0a309240e +} diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/ChunkSystem.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/ChunkSystem.java new file mode 100644 -index 0000000000000000000000000000000000000000..532a8c15009a4514d2682f52129785feb34a56f8 +index 0000000000000000000000000000000000000000..c2ff037e180393de6576f12c32c665ef640d6f50 --- /dev/null +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/ChunkSystem.java -@@ -0,0 +1,156 @@ +@@ -0,0 +1,162 @@ +package ca.spottedleaf.moonrise.patches.chunk_system; + +import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; @@ -3194,22 +3194,28 @@ index 0000000000000000000000000000000000000000..532a8c15009a4514d2682f52129785fe + io.papermc.paper.chunk.system.ChunkSystem.onChunkHolderDelete(level, holder); + } + -+ public static void onChunkBorder(final LevelChunk chunk, final ChunkHolder holder) { ++ public static void onChunkPreBorder(final LevelChunk chunk, final ChunkHolder holder) { + ((ChunkSystemServerChunkCache)((ServerLevel)chunk.getLevel()).getChunkSource()) + .moonrise$setFullChunk(chunk.getPos().x, chunk.getPos().z, chunk); ++ } ++ ++ public static void onChunkBorder(final LevelChunk chunk, final ChunkHolder holder) { + // TODO move hook + io.papermc.paper.chunk.system.ChunkSystem.onChunkBorder(chunk, holder); + chunk.loadCallback(); // Paper + } + + public static void onChunkNotBorder(final LevelChunk chunk, final ChunkHolder holder) { -+ ((ChunkSystemServerChunkCache)((ServerLevel)chunk.getLevel()).getChunkSource()) -+ .moonrise$setFullChunk(chunk.getPos().x, chunk.getPos().z, null); + // TODO move hook + io.papermc.paper.chunk.system.ChunkSystem.onChunkNotBorder(chunk, holder); + chunk.unloadCallback(); // Paper + } + ++ public static void onChunkPostNotBorder(final LevelChunk chunk, final ChunkHolder holder) { ++ ((ChunkSystemServerChunkCache)((ServerLevel)chunk.getLevel()).getChunkSource()) ++ .moonrise$setFullChunk(chunk.getPos().x, chunk.getPos().z, null); ++ } ++ + public static void onChunkTicking(final LevelChunk chunk, final ChunkHolder holder) { + // TODO move hook + io.papermc.paper.chunk.system.ChunkSystem.onChunkTicking(chunk, holder); @@ -11034,10 +11040,10 @@ index 0000000000000000000000000000000000000000..faf76a5c2f9fa2eea38d2c7f2ab1c438 +} diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/NewChunkHolder.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/NewChunkHolder.java new file mode 100644 -index 0000000000000000000000000000000000000000..05381b2c58c1b60f222eed672dddede047b663c5 +index 0000000000000000000000000000000000000000..d5fc5756ea960096ff23376a6b7ac68a2a462d22 --- /dev/null +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/NewChunkHolder.java -@@ -0,0 +1,2032 @@ +@@ -0,0 +1,2034 @@ +package ca.spottedleaf.moonrise.patches.chunk_system.scheduling; + +import ca.spottedleaf.concurrentutil.completable.Completable; @@ -12314,6 +12320,7 @@ index 0000000000000000000000000000000000000000..05381b2c58c1b60f222eed672dddede0 + // state upgrade + if (!current.isOrAfter(FullChunkStatus.FULL) && pending.isOrAfter(FullChunkStatus.FULL)) { + this.updateCurrentState(FullChunkStatus.FULL); ++ ChunkSystem.onChunkPreBorder(chunk, this.vanillaChunkHolder); + this.scheduler.chunkHolderManager.ensureInAutosave(this); + this.changeEntityChunkStatus(FullChunkStatus.FULL); + ChunkSystem.onChunkBorder(chunk, this.vanillaChunkHolder); @@ -12351,6 +12358,7 @@ index 0000000000000000000000000000000000000000..05381b2c58c1b60f222eed672dddede0 + this.onFullChunkLoadChange(false, changedFullStatus); + this.changeEntityChunkStatus(FullChunkStatus.INACCESSIBLE); + ChunkSystem.onChunkNotBorder(chunk, this.vanillaChunkHolder); ++ ChunkSystem.onChunkPostNotBorder(chunk, this.vanillaChunkHolder); + this.updateCurrentState(FullChunkStatus.INACCESSIBLE); + } + } @@ -21949,7 +21957,7 @@ index 0000000000000000000000000000000000000000..57692a503e147a00ac4e1586cd78e12b + private SaveUtil() {} +} diff --git a/src/main/java/io/papermc/paper/chunk/system/ChunkSystem.java b/src/main/java/io/papermc/paper/chunk/system/ChunkSystem.java -index 0d0cb3e63acd5156b6f9d6d78cc949b0af36a77b..dd1649abe57d7191c15a9b2862d5fd11ff0706b1 100644 +index a79abe9b26f68d573812e91554124783075ae17a..183d99ec9b94ca20a823c46a2d6bf0a215046d48 100644 --- a/src/main/java/io/papermc/paper/chunk/system/ChunkSystem.java +++ b/src/main/java/io/papermc/paper/chunk/system/ChunkSystem.java @@ -25,6 +25,10 @@ import java.util.List; @@ -21963,7 +21971,7 @@ index 0d0cb3e63acd5156b6f9d6d78cc949b0af36a77b..dd1649abe57d7191c15a9b2862d5fd11 public final class ChunkSystem { private static final Logger LOGGER = LogUtils.getLogger(); -@@ -35,31 +39,17 @@ public final class ChunkSystem { +@@ -35,35 +39,17 @@ public final class ChunkSystem { } public static void scheduleChunkTask(final ServerLevel level, final int chunkX, final int chunkZ, final Runnable run) { @@ -21985,12 +21993,16 @@ index 0d0cb3e63acd5156b6f9d6d78cc949b0af36a77b..dd1649abe57d7191c15a9b2862d5fd11 - } - scheduleChunkLoad(level, chunkX, chunkZ, ChunkStatus.EMPTY, addTicket, priority, (final ChunkAccess chunk) -> { - if (chunk == null) { -- onComplete.accept(null); +- if (onComplete != null) { +- onComplete.accept(null); +- } - } else { - if (chunk.getPersistedStatus().isOrAfter(toStatus)) { - scheduleChunkLoad(level, chunkX, chunkZ, toStatus, addTicket, priority, onComplete); - } else { -- onComplete.accept(null); +- if (onComplete != null) { +- onComplete.accept(null); +- } - } - } - }); @@ -21998,7 +22010,7 @@ index 0d0cb3e63acd5156b6f9d6d78cc949b0af36a77b..dd1649abe57d7191c15a9b2862d5fd11 } static final TicketType CHUNK_LOAD = TicketType.create("chunk_load", Long::compareTo); -@@ -67,164 +57,29 @@ public final class ChunkSystem { +@@ -71,160 +57,29 @@ public final class ChunkSystem { private static long chunkLoadCounter = 0L; public static void scheduleChunkLoad(final ServerLevel level, final int chunkX, final int chunkZ, final ChunkStatus toStatus, final boolean addTicket, final PrioritisedExecutor.Priority priority, final Consumer onComplete) { @@ -22023,8 +22035,6 @@ index 0d0cb3e63acd5156b6f9d6d78cc949b0af36a77b..dd1649abe57d7191c15a9b2862d5fd11 - if (onComplete != null) { - onComplete.accept(chunk); - } -- } catch (final ThreadDeath death) { -- throw death; - } catch (final Throwable thr) { - LOGGER.error("Exception handling chunk load callback", thr); - SneakyThrow.sneaky(thr); @@ -22092,8 +22102,6 @@ index 0d0cb3e63acd5156b6f9d6d78cc949b0af36a77b..dd1649abe57d7191c15a9b2862d5fd11 - if (onComplete != null) { - onComplete.accept(chunk); - } -- } catch (final ThreadDeath death) { -- throw death; - } catch (final Throwable thr) { - LOGGER.error("Exception handling chunk load callback", thr); - SneakyThrow.sneaky(thr); @@ -22169,7 +22177,7 @@ index 0d0cb3e63acd5156b6f9d6d78cc949b0af36a77b..dd1649abe57d7191c15a9b2862d5fd11 } public static boolean hasAnyChunkHolders(final ServerLevel level) { -@@ -274,27 +129,19 @@ public final class ChunkSystem { +@@ -268,27 +123,19 @@ public final class ChunkSystem { } public static ChunkHolder getUnloadingChunkHolder(final ServerLevel level, final int chunkX, final int chunkZ) { @@ -22571,7 +22579,7 @@ index c33f85b570f159ab465b5a10a8044a81f2797f43..244a19ecd0234fa1d7a6ecfea2075159 DedicatedServer dedicatedserver1 = new DedicatedServer(optionset, worldLoader.get(), thread, convertable_conversionsession, resourcepackrepository, worldstem, dedicatedserversettings, DataFixers.getDataFixer(), services, LoggerChunkProgressListener::createFromGameruleRadius); diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 6915522f669631779c1fb8a8e2db330f4b9fb921..3aecd55b9a069710c5d383b2f9113b147ad1ab57 100644 +index e14c0e1ccf526f81e28db5545d9e2351641e1bc8..3c230ae060998bfb79d5812fef21a80a9df8c8ff 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -198,7 +198,7 @@ import org.bukkit.event.server.ServerLoadEvent; @@ -22699,7 +22707,7 @@ index 6915522f669631779c1fb8a8e2db330f4b9fb921..3aecd55b9a069710c5d383b2f9113b14 if (entity.isRemoved()) { continue; } -@@ -2661,6 +2671,13 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop threadedmailbox1 = ProcessorMailbox.create(executor, "light"); @@ -23338,9 +23346,9 @@ index 2ce7da9707d7c1a48b5609ae51a516d599d7aee8..b849e0cf15f894aa87b1bb397d85b887 - this.worldGenContext = new WorldGenContext(world, chunkGenerator, structureTemplateManager, this.lightEngine, this.mainThreadMailbox); + this.worldGenContext = new WorldGenContext(world, chunkGenerator, structureTemplateManager, this.lightEngine, null); // Paper - rewrite chunk system // Paper start - this.dataRegionManager = new io.papermc.paper.chunk.SingleThreadChunkRegionManager(this.level, 2, (1.0 / 3.0), 1, 6, "Data", DataRegionData::new, DataRegionSectionData::new); - this.regionManagers.add(this.dataRegionManager); -@@ -319,23 +310,11 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + this.nearbyPlayers = new io.papermc.paper.util.player.NearbyPlayers(this.level); + // Paper end +@@ -292,23 +283,11 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider } boolean isChunkTracked(ServerPlayer player, int chunkX, int chunkZ) { @@ -23366,7 +23374,7 @@ index 2ce7da9707d7c1a48b5609ae51a516d599d7aee8..b849e0cf15f894aa87b1bb397d85b887 } protected ThreadedLevelLightEngine getLightEngine() { -@@ -344,20 +323,22 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -317,20 +296,22 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider @Nullable protected ChunkHolder getUpdatingChunkIfPresent(long pos) { @@ -23396,7 +23404,7 @@ index 2ce7da9707d7c1a48b5609ae51a516d599d7aee8..b849e0cf15f894aa87b1bb397d85b887 } public String getChunkDebugData(ChunkPos chunkPos) { -@@ -386,55 +367,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -359,55 +340,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider } private CompletableFuture>> getChunkRangeFuture(ChunkHolder centerChunk, int margin, IntFunction distanceToStatus) { @@ -23453,7 +23461,7 @@ index 2ce7da9707d7c1a48b5609ae51a516d599d7aee8..b849e0cf15f894aa87b1bb397d85b887 } public ReportedException debugFuturesAndCreateReportedException(IllegalStateException exception, String details) { -@@ -464,93 +397,23 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -437,93 +370,23 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider } public CompletableFuture> prepareEntityTickingChunk(ChunkHolder holder) { @@ -23553,7 +23561,7 @@ index 2ce7da9707d7c1a48b5609ae51a516d599d7aee8..b849e0cf15f894aa87b1bb397d85b887 } protected void tick(BooleanSupplier shouldKeepTicking) { -@@ -567,134 +430,25 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -540,134 +403,25 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider } public boolean hasWork() { @@ -23694,7 +23702,7 @@ index 2ce7da9707d7c1a48b5609ae51a516d599d7aee8..b849e0cf15f894aa87b1bb397d85b887 } private static boolean isChunkDataValid(CompoundTag nbt) { -@@ -754,137 +508,44 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -727,137 +481,44 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider @Override public GenerationChunkHolder acquireGeneration(long pos) { @@ -23841,7 +23849,7 @@ index 2ce7da9707d7c1a48b5609ae51a516d599d7aee8..b849e0cf15f894aa87b1bb397d85b887 } public int getTickingGenerated() { -@@ -892,135 +553,84 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -865,135 +526,84 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider } private boolean saveChunkIfNeeded(ChunkHolder chunkHolder) { @@ -24029,7 +24037,7 @@ index 2ce7da9707d7c1a48b5609ae51a516d599d7aee8..b849e0cf15f894aa87b1bb397d85b887 @Nullable public LevelChunk getChunkToSend(long pos) { ChunkHolder playerchunk = this.getVisibleChunkIfPresent(pos); -@@ -1086,7 +696,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1059,7 +669,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider } // CraftBukkit start @@ -24038,7 +24046,7 @@ index 2ce7da9707d7c1a48b5609ae51a516d599d7aee8..b849e0cf15f894aa87b1bb397d85b887 return this.upgradeChunkTag(this.level.getTypeKey(), this.overworldDataStorage, nbttagcompound, this.generator().getTypeNameForDataFixer(), chunkcoordintpair, this.level); // CraftBukkit end } -@@ -1180,7 +790,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1153,7 +763,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider } player.setChunkTrackingView(ChunkTrackingView.EMPTY); @@ -24047,7 +24055,7 @@ index 2ce7da9707d7c1a48b5609ae51a516d599d7aee8..b849e0cf15f894aa87b1bb397d85b887 this.addPlayerToDistanceMaps(player); // Paper - distance maps } else { SectionPos sectionposition = player.getLastSectionPos(); -@@ -1191,7 +801,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1164,7 +774,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider } this.removePlayerFromDistanceMaps(player); // Paper - distance maps @@ -24056,7 +24064,7 @@ index 2ce7da9707d7c1a48b5609ae51a516d599d7aee8..b849e0cf15f894aa87b1bb397d85b887 } } -@@ -1239,71 +849,31 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1212,71 +822,31 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider this.playerMap.unIgnorePlayer(player); } @@ -24139,7 +24147,7 @@ index 2ce7da9707d7c1a48b5609ae51a516d599d7aee8..b849e0cf15f894aa87b1bb397d85b887 } public void addEntity(Entity entity) { -@@ -1374,13 +944,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1347,13 +917,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider } protected void tick() { @@ -24154,7 +24162,7 @@ index 2ce7da9707d7c1a48b5609ae51a516d599d7aee8..b849e0cf15f894aa87b1bb397d85b887 List list = Lists.newArrayList(); List list1 = this.level.players(); -@@ -1487,27 +1051,25 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1460,27 +1024,25 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider } public void waitForLightBeforeSending(ChunkPos centerPos, int radius) { @@ -24944,7 +24952,7 @@ index 3dc1daa3c6a04d3ff1a2353773b465fc380994a2..3575782f13a7f3c52e64dc5046803305 } } diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -index a94833f58eb823332890d07c147161e9e0a938e9..cba80945fa0d8ca55b0d422925f8c94c51f9a50d 100644 +index be9604a0f267558c95125852d86761a2f175732a..014e7d3c3b9e8f6c2b456d63bcf885f55b01ded9 100644 --- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java +++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java @@ -46,7 +46,7 @@ import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemp @@ -24956,12 +24964,11 @@ index a94833f58eb823332890d07c147161e9e0a938e9..cba80945fa0d8ca55b0d422925f8c94c public static final org.slf4j.Logger LOGGER = com.mojang.logging.LogUtils.getLogger(); // Paper private static final List CHUNK_STATUSES = ChunkStatus.getStatusList(); -@@ -75,6 +75,62 @@ public class ServerChunkCache extends ChunkSource { +@@ -73,6 +73,61 @@ public class ServerChunkCache extends ChunkSource { + private final ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable fullChunks = new ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable<>(); long chunkFutureAwaitCounter; - private final LevelChunk[] lastLoadedChunks = new LevelChunk[4 * 4]; // Paper end + // Paper start - rewrite chunk system -+ private final ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable fullChunks = new ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable<>(); + + @Override + public final void moonrise$setFullChunk(final int chunkX, final int chunkZ, final LevelChunk chunk) { @@ -25019,7 +25026,22 @@ index a94833f58eb823332890d07c147161e9e0a938e9..cba80945fa0d8ca55b0d422925f8c94c public ServerChunkCache(ServerLevel world, LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, StructureTemplateManager structureTemplateManager, Executor workerExecutor, ChunkGenerator chunkGenerator, int viewDistance, int simulationDistance, boolean dsync, ChunkProgressListener worldGenerationProgressListener, ChunkStatusUpdateListener chunkStatusChangeListener, Supplier persistentStateManagerFactory) { this.level = world; -@@ -248,63 +304,25 @@ public class ServerChunkCache extends ChunkSource { +@@ -99,13 +154,7 @@ public class ServerChunkCache extends ChunkSource { + } + // CraftBukkit end + // Paper start +- public void addLoadedChunk(LevelChunk chunk) { +- this.fullChunks.put(chunk.coordinateKey, chunk); +- } +- +- public void removeLoadedChunk(LevelChunk chunk) { +- this.fullChunks.remove(chunk.coordinateKey); +- } ++ // Paper - rewrite chunk system + + @Nullable + public ChunkAccess getChunkAtImmediately(int x, int z) { +@@ -176,63 +225,25 @@ public class ServerChunkCache extends ChunkSource { @Nullable @Override public ChunkAccess getChunk(int x, int z, ChunkStatus leastStatus, boolean create) { @@ -25035,13 +25057,13 @@ index a94833f58eb823332890d07c147161e9e0a938e9..cba80945fa0d8ca55b0d422925f8c94c - } - // Paper end - Perf: Optimise getChunkAt calls for loaded chunks - ProfilerFiller gameprofilerfiller = this.level.getProfiler(); -- -- gameprofilerfiller.incrementCounter("getChunk"); -- long k = ChunkPos.asLong(x, z); + // Paper start - rewrite chunk system + if (leastStatus == ChunkStatus.FULL) { + final LevelChunk ret = this.fullChunks.get(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(x, z)); +- gameprofilerfiller.incrementCounter("getChunk"); +- long k = ChunkPos.asLong(x, z); +- - for (int l = 0; l < 4; ++l) { - if (k == this.lastChunkPos[l] && leastStatus == this.lastChunkStatus[l]) { - ChunkAccess ichunkaccess = this.lastChunk[l]; @@ -25093,7 +25115,7 @@ index a94833f58eb823332890d07c147161e9e0a938e9..cba80945fa0d8ca55b0d422925f8c94c } private void clearCache() { -@@ -335,56 +353,59 @@ public class ServerChunkCache extends ChunkSource { +@@ -263,56 +274,59 @@ public class ServerChunkCache extends ChunkSource { } private CompletableFuture> getChunkFutureMainThread(int chunkX, int chunkZ, ChunkStatus leastStatus, boolean create) { @@ -25110,7 +25132,14 @@ index a94833f58eb823332890d07c147161e9e0a938e9..cba80945fa0d8ca55b0d422925f8c94c - FullChunkStatus oldChunkState = ChunkLevel.fullStatus(playerchunk.oldTicketLevel); - FullChunkStatus currentChunkState = ChunkLevel.fullStatus(playerchunk.getTicketLevel()); - currentlyUnloading = (oldChunkState.isOrAfter(FullChunkStatus.FULL) && !currentChunkState.isOrAfter(FullChunkStatus.FULL)); -- } ++ final int minLevel = ChunkLevel.byStatus(leastStatus); ++ final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder chunkHolder = ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(chunkX, chunkZ); ++ ++ final boolean needsFullScheduling = leastStatus == ChunkStatus.FULL && (chunkHolder == null || !chunkHolder.getChunkStatus().isOrAfter(FullChunkStatus.FULL)); ++ ++ if ((chunkHolder == null || chunkHolder.getTicketLevel() > minLevel || needsFullScheduling) && !create) { ++ return ChunkHolder.UNLOADED_CHUNK_FUTURE; + } - if (create && !currentlyUnloading) { - // CraftBukkit end - this.distanceManager.addTicket(TicketType.UNKNOWN, chunkcoordintpair, l, chunkcoordintpair); @@ -25123,19 +25152,7 @@ index a94833f58eb823332890d07c147161e9e0a938e9..cba80945fa0d8ca55b0d422925f8c94c - gameprofilerfiller.pop(); - if (this.chunkAbsent(playerchunk, l)) { - throw (IllegalStateException) Util.pauseInIde(new IllegalStateException("No chunk holder after ticket has been added")); -- } -- } -+ final int minLevel = ChunkLevel.byStatus(leastStatus); -+ final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder chunkHolder = ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(chunkX, chunkZ); + -+ final boolean needsFullScheduling = leastStatus == ChunkStatus.FULL && (chunkHolder == null || !chunkHolder.getChunkStatus().isOrAfter(FullChunkStatus.FULL)); -+ -+ if ((chunkHolder == null || chunkHolder.getTicketLevel() > minLevel || needsFullScheduling) && !create) { -+ return ChunkHolder.UNLOADED_CHUNK_FUTURE; - } - -- return this.chunkAbsent(playerchunk, l) ? GenerationChunkHolder.UNLOADED_CHUNK_FUTURE : playerchunk.scheduleChunkGenerationTask(leastStatus, this.chunkMap); -- } + final ChunkAccess ifPresent = chunkHolder == null ? null : chunkHolder.getChunkIfPresent(leastStatus); + if (needsFullScheduling || ifPresent == null) { + // schedule @@ -25145,17 +25162,21 @@ index a94833f58eb823332890d07c147161e9e0a938e9..cba80945fa0d8ca55b0d422925f8c94c + ret.complete(ChunkHolder.UNLOADED_CHUNK); + } else { + ret.complete(ChunkResult.of(chunk)); -+ } + } +- } +- } + }; -- private boolean chunkAbsent(@Nullable ChunkHolder holder, int maxLevel) { -- return holder == null || holder.oldTicketLevel > maxLevel; // CraftBukkit using oldTicketLevel for isLoaded checks +- return this.chunkAbsent(playerchunk, l) ? GenerationChunkHolder.UNLOADED_CHUNK_FUTURE : playerchunk.scheduleChunkGenerationTask(leastStatus, this.chunkMap); +- } + ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getChunkTaskScheduler().scheduleChunkLoad( + chunkX, chunkZ, leastStatus, true, + ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority.HIGHER, + complete + ); -+ + +- private boolean chunkAbsent(@Nullable ChunkHolder holder, int maxLevel) { +- return holder == null || holder.oldTicketLevel > maxLevel; // CraftBukkit using oldTicketLevel for isLoaded checks + return ret; + } else { + // can return now @@ -25190,7 +25211,7 @@ index a94833f58eb823332890d07c147161e9e0a938e9..cba80945fa0d8ca55b0d422925f8c94c } @Override -@@ -397,16 +418,7 @@ public class ServerChunkCache extends ChunkSource { +@@ -325,16 +339,7 @@ public class ServerChunkCache extends ChunkSource { } public boolean runDistanceManagerUpdates() { // Paper - public @@ -25208,7 +25229,7 @@ index a94833f58eb823332890d07c147161e9e0a938e9..cba80945fa0d8ca55b0d422925f8c94c } // Paper start -@@ -416,13 +428,14 @@ public class ServerChunkCache extends ChunkSource { +@@ -344,13 +349,14 @@ public class ServerChunkCache extends ChunkSource { // Paper end public boolean isPositionTicking(long pos) { @@ -25227,7 +25248,7 @@ index a94833f58eb823332890d07c147161e9e0a938e9..cba80945fa0d8ca55b0d422925f8c94c try (co.aikar.timings.Timing timed = level.timings.chunkSaveData.startTiming()) { // Paper - Timings this.chunkMap.saveAllChunks(flush); } // Paper - Timings -@@ -435,12 +448,7 @@ public class ServerChunkCache extends ChunkSource { +@@ -363,12 +369,7 @@ public class ServerChunkCache extends ChunkSource { } public void close(boolean save) throws IOException { @@ -25241,7 +25262,7 @@ index a94833f58eb823332890d07c147161e9e0a938e9..cba80945fa0d8ca55b0d422925f8c94c } // CraftBukkit start - modelled on below -@@ -468,6 +476,7 @@ public class ServerChunkCache extends ChunkSource { +@@ -396,6 +397,7 @@ public class ServerChunkCache extends ChunkSource { this.level.getProfiler().popPush("chunks"); if (tickChunks) { this.level.timings.chunks.startTiming(); // Paper - timings @@ -25249,7 +25270,7 @@ index a94833f58eb823332890d07c147161e9e0a938e9..cba80945fa0d8ca55b0d422925f8c94c this.tickChunks(); this.level.timings.chunks.stopTiming(); // Paper - timings this.chunkMap.tick(); -@@ -567,11 +576,12 @@ public class ServerChunkCache extends ChunkSource { +@@ -495,11 +497,12 @@ public class ServerChunkCache extends ChunkSource { } private void getFullChunk(long pos, Consumer chunkConsumer) { @@ -25266,7 +25287,7 @@ index a94833f58eb823332890d07c147161e9e0a938e9..cba80945fa0d8ca55b0d422925f8c94c } -@@ -665,6 +675,12 @@ public class ServerChunkCache extends ChunkSource { +@@ -593,6 +596,12 @@ public class ServerChunkCache extends ChunkSource { this.chunkMap.setServerViewDistance(watchDistance); } @@ -25279,7 +25300,7 @@ index a94833f58eb823332890d07c147161e9e0a938e9..cba80945fa0d8ca55b0d422925f8c94c public void setSimulationDistance(int simulationDistance) { this.distanceManager.updateSimulationDistance(simulationDistance); } -@@ -743,16 +759,14 @@ public class ServerChunkCache extends ChunkSource { +@@ -671,16 +680,14 @@ public class ServerChunkCache extends ChunkSource { @Override // CraftBukkit start - process pending Chunk loadCallback() and unloadCallback() after each run task public boolean pollTask() { @@ -26763,7 +26784,7 @@ index bd20bea7f76a7307f1698fb2dfef37125032d166..70c2017400168d4fef3c14462798edcf if (shape.isEmpty()) { return true; diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index 6f822e9487bef5b9766d5ae86ebbd687e4eadc42..77c6613c39e3b266944e28cf2627483d9f32c511 100644 +index e27d3547d1e19c137e05e6b8d075127a8bafb237..557273061fa03ebaa4b9de01ad12ed4ac859c292 100644 --- a/src/main/java/net/minecraft/world/level/Level.java +++ b/src/main/java/net/minecraft/world/level/Level.java @@ -102,7 +102,7 @@ import org.bukkit.entity.SpawnCategory; @@ -26842,7 +26863,7 @@ index 6f822e9487bef5b9766d5ae86ebbd687e4eadc42..77c6613c39e3b266944e28cf2627483d } // Paper start - Cancel hit for vanished players -@@ -551,7 +604,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { +@@ -549,7 +602,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { this.setBlocksDirty(blockposition, iblockdata1, iblockdata2); } @@ -26851,7 +26872,7 @@ index 6f822e9487bef5b9766d5ae86ebbd687e4eadc42..77c6613c39e3b266944e28cf2627483d this.sendBlockUpdated(blockposition, iblockdata1, iblockdata, i); } -@@ -951,7 +1004,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { +@@ -949,7 +1002,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { } // Paper end - Perf: Optimize capturedTileEntities lookup // CraftBukkit end @@ -26860,7 +26881,7 @@ index 6f822e9487bef5b9766d5ae86ebbd687e4eadc42..77c6613c39e3b266944e28cf2627483d } public void setBlockEntity(BlockEntity blockEntity) { -@@ -1041,28 +1094,13 @@ public abstract class Level implements LevelAccessor, AutoCloseable { +@@ -1039,28 +1092,13 @@ public abstract class Level implements LevelAccessor, AutoCloseable { @Override public List getEntities(@Nullable Entity except, AABB box, Predicate predicate) { this.getProfiler().incrementCounter("getEntities"); @@ -26894,7 +26915,7 @@ index 6f822e9487bef5b9766d5ae86ebbd687e4eadc42..77c6613c39e3b266944e28cf2627483d } @Override -@@ -1077,36 +1115,77 @@ public abstract class Level implements LevelAccessor, AutoCloseable { +@@ -1075,36 +1113,77 @@ public abstract class Level implements LevelAccessor, AutoCloseable { this.getEntities(filter, box, predicate, result, Integer.MAX_VALUE); } @@ -27315,7 +27336,7 @@ index 365074be989aa4a178114fd5e9810f1a68640196..4af698930712389881601069a921f054 @Override public BlockEntity getBlockEntity(BlockPos pos) { diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -index 6c0e12c9c9c0fb8377cd1f48a43ca75c9fc3e58e..c4f87ff9e0c9806a1d7abc4842caa5a4caa357bd 100644 +index 602ad80c2b93d320bf2a25832d25a58cb8c72e4b..443e5e1b1c0e7c93f61c1905c78c29a17860989c 100644 --- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java @@ -53,7 +53,7 @@ import net.minecraft.world.ticks.LevelChunkTicks; @@ -27327,9 +27348,9 @@ index 6c0e12c9c9c0fb8377cd1f48a43ca75c9fc3e58e..c4f87ff9e0c9806a1d7abc4842caa5a4 static final Logger LOGGER = LogUtils.getLogger(); private static final TickingBlockEntity NULL_TICKER = new TickingBlockEntity() { -@@ -218,6 +218,14 @@ public class LevelChunk extends ChunkAccess { - } - } +@@ -119,6 +119,14 @@ public class LevelChunk extends ChunkAccess { + // Paper start + boolean loadedTicketLevel; // Paper end + // Paper start - rewrite chunk system + private boolean postProcessingDone; @@ -27342,7 +27363,7 @@ index 6c0e12c9c9c0fb8377cd1f48a43ca75c9fc3e58e..c4f87ff9e0c9806a1d7abc4842caa5a4 public LevelChunk(ServerLevel world, ProtoChunk protoChunk, @Nullable LevelChunk.PostLoadProcessor entityLoader) { this(world, protoChunk.getPos(), protoChunk.getUpgradeData(), protoChunk.unpackBlockTicks(), protoChunk.unpackFluidTicks(), protoChunk.getInhabitedTime(), protoChunk.getSections(), entityLoader, protoChunk.getBlendingData()); -@@ -247,13 +255,19 @@ public class LevelChunk extends ChunkAccess { +@@ -148,13 +156,19 @@ public class LevelChunk extends ChunkAccess { } } @@ -27363,7 +27384,7 @@ index 6c0e12c9c9c0fb8377cd1f48a43ca75c9fc3e58e..c4f87ff9e0c9806a1d7abc4842caa5a4 } @Override -@@ -436,7 +450,7 @@ public class LevelChunk extends ChunkAccess { +@@ -337,7 +351,7 @@ public class LevelChunk extends ChunkAccess { ProfilerFiller gameprofilerfiller = this.level.getProfiler(); gameprofilerfiller.push("updateSkyLightSources"); @@ -27372,15 +27393,21 @@ index 6c0e12c9c9c0fb8377cd1f48a43ca75c9fc3e58e..c4f87ff9e0c9806a1d7abc4842caa5a4 gameprofilerfiller.popPush("queueCheckLight"); this.level.getChunkSource().getLightEngine().checkBlock(blockposition); gameprofilerfiller.pop(); -@@ -696,6 +710,7 @@ public class LevelChunk extends ChunkAccess { +@@ -597,11 +611,12 @@ public class LevelChunk extends ChunkAccess { // CraftBukkit start public void loadCallback() { + if (this.loadedTicketLevel) { LOGGER.error("Double calling chunk load!", new Throwable()); } // Paper - // Paper start - neighbour cache - int chunkX = this.chunkPos.x; - int chunkZ = this.chunkPos.z; -@@ -723,6 +738,7 @@ public class LevelChunk extends ChunkAccess { + // Paper start + this.loadedTicketLevel = true; + // Paper end + org.bukkit.Server server = this.level.getCraftServer(); +- this.level.getChunkSource().addLoadedChunk(this); // Paper ++ // Paper - rewrite chunk system + if (server != null) { + /* + * If it's a new world, the first few chunks are generated inside +@@ -610,6 +625,7 @@ public class LevelChunk extends ChunkAccess { */ org.bukkit.Chunk bukkitChunk = new org.bukkit.craftbukkit.CraftChunk(this); server.getPluginManager().callEvent(new org.bukkit.event.world.ChunkLoadEvent(bukkitChunk, this.needsDecoration)); @@ -27388,7 +27415,7 @@ index 6c0e12c9c9c0fb8377cd1f48a43ca75c9fc3e58e..c4f87ff9e0c9806a1d7abc4842caa5a4 if (this.needsDecoration) { try (co.aikar.timings.Timing ignored = this.level.timings.chunkLoadPopulate.startTiming()) { // Paper -@@ -751,9 +767,11 @@ public class LevelChunk extends ChunkAccess { +@@ -638,13 +654,15 @@ public class LevelChunk extends ChunkAccess { } public void unloadCallback() { @@ -27401,7 +27428,12 @@ index 6c0e12c9c9c0fb8377cd1f48a43ca75c9fc3e58e..c4f87ff9e0c9806a1d7abc4842caa5a4 server.getPluginManager().callEvent(unloadEvent); // note: saving can be prevented, but not forced if no saving is actually required this.mustNotSave = !unloadEvent.isSaveChunk(); -@@ -777,8 +795,27 @@ public class LevelChunk extends ChunkAccess { +- this.level.getChunkSource().removeLoadedChunk(this); // Paper ++ // Paper - rewrite chunk system + // Paper start + this.loadedTicketLevel = false; + // Paper end +@@ -652,8 +670,27 @@ public class LevelChunk extends ChunkAccess { @Override public boolean isUnsaved() { @@ -27430,7 +27462,7 @@ index 6c0e12c9c9c0fb8377cd1f48a43ca75c9fc3e58e..c4f87ff9e0c9806a1d7abc4842caa5a4 // CraftBukkit end public boolean isEmpty() { -@@ -884,6 +921,7 @@ public class LevelChunk extends ChunkAccess { +@@ -759,6 +796,7 @@ public class LevelChunk extends ChunkAccess { this.pendingBlockEntities.clear(); this.upgradeData.upgrade(this); @@ -29064,29 +29096,12 @@ index 5717c0e1d6df07a4613356dc78d970d2101c68d7..cab7ca4218e5903b6a5e518af55457b9 @Override diff --git a/src/main/java/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java b/src/main/java/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java -index cd7f1309cf01a5f01a28aded03a36fe15adb1756..43cb7b91945a72ab4bd998a3a3eca3cdcf0432a9 100644 +index fceed3d08ee6f4c171685986bb19d2be592eedc6..bf18f9ad7dec2b09ebfcb5ec6566f2556e842f22 100644 --- a/src/main/java/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java +++ b/src/main/java/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java -@@ -815,19 +815,26 @@ public abstract class DelegatedGeneratorAccess implements WorldGenLevel { - @Nullable - @Override - public BlockState getBlockStateIfLoaded(final BlockPos blockposition) { -- return null; -+ return this.handle.getBlockStateIfLoaded(blockposition); // Paper - rewrite chunk system - } - - @Nullable - @Override - public FluidState getFluidIfLoaded(final BlockPos blockposition) { -- return null; -+ return this.handle.getFluidIfLoaded(blockposition); // Paper - rewrite chunk system - } - - @Nullable - @Override +@@ -829,5 +829,12 @@ public abstract class DelegatedGeneratorAccess implements WorldGenLevel { public ChunkAccess getChunkIfLoadedImmediately(final int x, final int z) { -- return null; -+ return this.handle.getChunkIfLoadedImmediately(x, z); // Paper - rewrite chunk system + return this.handle.getChunkIfLoadedImmediately(x, z); } + + // Paper start - rewrite chunk system diff --git a/patches/server/0994-Optimize-isInWorldBounds-and-getBlockState-for-inlin.patch b/patches/server/0994-Optimize-isInWorldBounds-and-getBlockState-for-inlin.patch index 2cdcbebb6f..fc25a541d6 100644 --- a/patches/server/0994-Optimize-isInWorldBounds-and-getBlockState-for-inlin.patch +++ b/patches/server/0994-Optimize-isInWorldBounds-and-getBlockState-for-inlin.patch @@ -29,7 +29,7 @@ index 02367ef1371dde94ff6c4cd40bd32e800d6ccaaf..7b0fc7135bc107103dcaed6dc0707b18 this.x = x; this.y = y; diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index 77c6613c39e3b266944e28cf2627483d9f32c511..b3a433786fabf6f2cfba2cdc8d21f6447191a310 100644 +index 557273061fa03ebaa4b9de01ad12ed4ac859c292..316a72f5019d4ad65237b41ccb4ef3be729246ad 100644 --- a/src/main/java/net/minecraft/world/level/Level.java +++ b/src/main/java/net/minecraft/world/level/Level.java @@ -394,7 +394,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl diff --git a/patches/server/0996-Strip-raytracing-for-EntityLiving-hasLineOfSight.patch b/patches/server/0996-Strip-raytracing-for-EntityLiving-hasLineOfSight.patch index 52c1df5a4d..4ef94ee4ce 100644 --- a/patches/server/0996-Strip-raytracing-for-EntityLiving-hasLineOfSight.patch +++ b/patches/server/0996-Strip-raytracing-for-EntityLiving-hasLineOfSight.patch @@ -62,7 +62,7 @@ index bb8e962e63c7a2d931f9bd7f7c002aa35cfa5fd3..0fa131a6c98adb498fc8d534e0e39647 default BlockHitResult clip(ClipContext raytrace1, BlockPos blockposition) { // Paper start - Add predicate for blocks when raytracing diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index b3a433786fabf6f2cfba2cdc8d21f6447191a310..2d318964ef1f54940059b8d62bfc8f8ae87424e5 100644 +index 316a72f5019d4ad65237b41ccb4ef3be729246ad..5ea8f51accdda2387b640d2cff1d6a8baa673bee 100644 --- a/src/main/java/net/minecraft/world/level/Level.java +++ b/src/main/java/net/minecraft/world/level/Level.java @@ -386,10 +386,87 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl diff --git a/patches/server/1000-Entity-Activation-Range-2.0.patch b/patches/server/1000-Entity-Activation-Range-2.0.patch index 84b1883a6e..dcb2dabb14 100644 --- a/patches/server/1000-Entity-Activation-Range-2.0.patch +++ b/patches/server/1000-Entity-Activation-Range-2.0.patch @@ -340,7 +340,7 @@ index 0b7f52021441d633c37543e8ae485e81c292b747..d7f8464bf3eed0e42a5fc7f14a5b243d + } diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index 2d318964ef1f54940059b8d62bfc8f8ae87424e5..23af11c22713ac3e005fd89b4f3b3873bf74e751 100644 +index 5ea8f51accdda2387b640d2cff1d6a8baa673bee..3fa8ae3a9afd81bf757ee1e183684442444376f4 100644 --- a/src/main/java/net/minecraft/world/level/Level.java +++ b/src/main/java/net/minecraft/world/level/Level.java @@ -156,6 +156,12 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl diff --git a/patches/server/1001-Optional-per-player-mob-spawns.patch b/patches/server/1001-Optional-per-player-mob-spawns.patch index f9d00cb631..8905e1ab1c 100644 --- a/patches/server/1001-Optional-per-player-mob-spawns.patch +++ b/patches/server/1001-Optional-per-player-mob-spawns.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Optional per player mob spawns diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index b849e0cf15f894aa87b1bb397d85b887b8fb816e..7bebf252887ecc7594b1ce21471fb6ba7aa2c051 100644 +index 1363dda031d1b541d76241812a957a12521cbc05..b24d5b0818abfc8b969ab715ca21959c235a9d70 100644 --- a/src/main/java/net/minecraft/server/level/ChunkMap.java +++ b/src/main/java/net/minecraft/server/level/ChunkMap.java -@@ -283,8 +283,26 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -256,8 +256,26 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider return this.nearbyPlayers; } @@ -37,10 +37,10 @@ index b849e0cf15f894aa87b1bb397d85b887b8fb816e..7bebf252887ecc7594b1ce21471fb6ba // Paper end diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -index cba80945fa0d8ca55b0d422925f8c94c51f9a50d..a5d465a81ba6ba7dea352005bf02ae2ae424ed14 100644 +index 014e7d3c3b9e8f6c2b456d63bcf885f55b01ded9..7bb3277883a326c436ef070eaf285343fe502a32 100644 --- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java +++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -@@ -517,7 +517,19 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon +@@ -438,7 +438,19 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon gameprofilerfiller.popPush("naturalSpawnCount"); this.level.timings.countNaturalMobs.startTiming(); // Paper - timings int k = this.distanceManager.getNaturalSpawnChunkCount(); diff --git a/patches/server/1002-Anti-Xray.patch b/patches/server/1002-Anti-Xray.patch index 56181f6189..249d9e10fd 100644 --- a/patches/server/1002-Anti-Xray.patch +++ b/patches/server/1002-Anti-Xray.patch @@ -1168,7 +1168,7 @@ index 9b1a6d8351fb473eec75a2fd08fb892b770e3586..0d0b07c9199be9ca0d5ac3feb1d44f14 } // Paper end - Send empty chunk diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index 23af11c22713ac3e005fd89b4f3b3873bf74e751..9f2fad2f4b1e4e7eda645d53eb76ed04fc0b3451 100644 +index 3fa8ae3a9afd81bf757ee1e183684442444376f4..392032d749b8a9077b61f4e1c5e413de048c7067 100644 --- a/src/main/java/net/minecraft/world/level/Level.java +++ b/src/main/java/net/minecraft/world/level/Level.java @@ -171,6 +171,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl @@ -1196,7 +1196,7 @@ index 23af11c22713ac3e005fd89b4f3b3873bf74e751..9f2fad2f4b1e4e7eda645d53eb76ed04 } // Paper start - Cancel hit for vanished players -@@ -619,6 +621,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl +@@ -617,6 +619,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl // CraftBukkit end BlockState iblockdata1 = chunk.setBlockState(pos, state, (flags & 64) != 0, (flags & 1024) == 0); // CraftBukkit custom NO_PLACE flag @@ -1231,7 +1231,7 @@ index 2822a9b010e6d45f9562950a94f1942784db9784..97f8ef86a0e398b7e4aa3445d5e413ad } diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -index c4f87ff9e0c9806a1d7abc4842caa5a4caa357bd..e5f301f867057ac986725a76c13f260ff40489c4 100644 +index 443e5e1b1c0e7c93f61c1905c78c29a17860989c..b2a06fc1192cd6050d6d7ea8620a4fa5a12182cc 100644 --- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java @@ -91,7 +91,7 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p diff --git a/patches/server/1004-Add-Alternate-Current-redstone-implementation.patch b/patches/server/1004-Add-Alternate-Current-redstone-implementation.patch index a5295f6b05..29b12d1dfb 100644 --- a/patches/server/1004-Add-Alternate-Current-redstone-implementation.patch +++ b/patches/server/1004-Add-Alternate-Current-redstone-implementation.patch @@ -2035,10 +2035,10 @@ index a463cdd3b9dbda64d52c86223e1f2e1164deac80..798016774df02c3f7ebf909c9cc125f8 EntityCallbacks() {} diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index 9f2fad2f4b1e4e7eda645d53eb76ed04fc0b3451..96ad3868a93964247790134fa5f4b18e5c07aea8 100644 +index 392032d749b8a9077b61f4e1c5e413de048c7067..55e841269585feb6a083b48ffeecea30cc65f6d6 100644 --- a/src/main/java/net/minecraft/world/level/Level.java +++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -1570,4 +1570,14 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl +@@ -1568,4 +1568,14 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl } } // Paper end - notify observers even if grow failed diff --git a/patches/server/1005-Improve-cancelling-PreCreatureSpawnEvent-with-per-pl.patch b/patches/server/1005-Improve-cancelling-PreCreatureSpawnEvent-with-per-pl.patch index ab570def0a..3e51783f35 100644 --- a/patches/server/1005-Improve-cancelling-PreCreatureSpawnEvent-with-per-pl.patch +++ b/patches/server/1005-Improve-cancelling-PreCreatureSpawnEvent-with-per-pl.patch @@ -6,10 +6,10 @@ Subject: [PATCH] Improve cancelling PreCreatureSpawnEvent with per player mob diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index 7bebf252887ecc7594b1ce21471fb6ba7aa2c051..df00ea382915480be1279a5347872cf7a1417341 100644 +index b24d5b0818abfc8b969ab715ca21959c235a9d70..c96740a82eac9101f74edeb44edf4b64d1d633e0 100644 --- a/src/main/java/net/minecraft/server/level/ChunkMap.java +++ b/src/main/java/net/minecraft/server/level/ChunkMap.java -@@ -300,8 +300,25 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -273,8 +273,25 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider ++((ServerPlayer)backingSet[i]).mobCounts[index]; } } @@ -37,10 +37,10 @@ index 7bebf252887ecc7594b1ce21471fb6ba7aa2c051..df00ea382915480be1279a5347872cf7 } // Paper end diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -index a5d465a81ba6ba7dea352005bf02ae2ae424ed14..681fdab250d924a29ca160acffbcbf7f8a3ca78a 100644 +index 7bb3277883a326c436ef070eaf285343fe502a32..82e7f7c3c2f51bc135585f43bc5167bcde2f8a98 100644 --- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java +++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -@@ -523,7 +523,17 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon +@@ -444,7 +444,17 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon if ((this.spawnFriendlies || this.spawnEnemies) && this.level.paperConfig().entities.spawning.perPlayerMobSpawns) { // don't count mobs when animals and monsters are disabled // re-set mob counts for (ServerPlayer player : this.level.players) { diff --git a/patches/server/1008-Optimize-Hoppers.patch b/patches/server/1008-Optimize-Hoppers.patch index 28b2e6b92d..aa1c0074e6 100644 --- a/patches/server/1008-Optimize-Hoppers.patch +++ b/patches/server/1008-Optimize-Hoppers.patch @@ -50,7 +50,7 @@ index 0000000000000000000000000000000000000000..5c42823726e70ce6c9d0121d07431548 + } +} diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 3aecd55b9a069710c5d383b2f9113b147ad1ab57..2a65c39feeb03a165ccc4afd974fb6935b2ff546 100644 +index 3c230ae060998bfb79d5812fef21a80a9df8c8ff..64d0e04b6f9c52d90d0bc185b16a8a4768af4ac1 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -1659,6 +1659,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop