From f6f42ece6b7ad713b36de8b5fa4ebbe20fc5a9a7 Mon Sep 17 00:00:00 2001 From: Aikar <aikar@aikar.co> Date: Fri, 19 Jun 2020 19:10:38 -0400 Subject: [PATCH] Improve light optimizations and fix bugs Rewrites the Threaded task logic to no longer use 2 queues and instead keep a single prioritized queue and do all of a chunks light tasks in a single batch Fix a math issue in one place (Thankfully didn't seem to really be a common place since didn't notice anything) --- .../Optimize-Bit-Operations-by-inlining.patch | 10 +- .../Optimize-Light-Engine.patch | 486 ++++++++++++------ 2 files changed, 337 insertions(+), 159 deletions(-) diff --git a/Spigot-Server-Patches/Optimize-Bit-Operations-by-inlining.patch b/Spigot-Server-Patches/Optimize-Bit-Operations-by-inlining.patch index 36fa57e576..5dfc5efb24 100644 --- a/Spigot-Server-Patches/Optimize-Bit-Operations-by-inlining.patch +++ b/Spigot-Server-Patches/Optimize-Bit-Operations-by-inlining.patch @@ -78,9 +78,17 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 } public static long a(long i, EnumDirection enumdirection) { -@@ -0,0 +0,0 @@ public class SectionPosition extends BaseBlockPosition { + return a(i, enumdirection.getAdjacentX(), enumdirection.getAdjacentY(), enumdirection.getAdjacentZ()); } ++ // Paper start ++ public static long getAdjacentFromBlockPos(int x, int y, int z, EnumDirection enumdirection) { ++ return (((long) ((x >> 4) + enumdirection.getAdjacentX()) & 4194303L) << 42) | (((long) ((y >> 4) + enumdirection.getAdjacentY()) & 1048575L)) | (((long) ((z >> 4) + enumdirection.getAdjacentZ()) & 4194303L) << 20); ++ } ++ public static long getAdjacentFromSectionPos(int x, int y, int z, EnumDirection enumdirection) { ++ return (((long) (x + enumdirection.getAdjacentX()) & 4194303L) << 42) | (((long) ((y) + enumdirection.getAdjacentY()) & 1048575L)) | (((long) (z + enumdirection.getAdjacentZ()) & 4194303L) << 20); ++ } ++ // Paper end public static long a(long i, int j, int k, int l) { - return b(b(i) + j, c(i) + k, d(i) + l); + return (((long) ((int) (i >> 42) + j) & 4194303L) << 42) | (((long) ((int) (i << 44 >> 44) + k) & 1048575L)) | (((long) ((int) (i << 22 >> 42) + l) & 4194303L) << 20); // Simplify to reduce instruction count diff --git a/Spigot-Server-Patches/Optimize-Light-Engine.patch b/Spigot-Server-Patches/Optimize-Light-Engine.patch index da2d2e06a9..46acc7c520 100644 --- a/Spigot-Server-Patches/Optimize-Light-Engine.patch +++ b/Spigot-Server-Patches/Optimize-Light-Engine.patch @@ -46,16 +46,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 return true; } else { - ChunkProviderServer.this.lightEngine.queueUpdate(); -+ //ChunkProviderServer.this.lightEngine.queueUpdate(); // Paper - move down ++ //ChunkProviderServer.this.lightEngine.queueUpdate(); // Paper - not needed return super.executeNext() || execChunkTask; // Paper } } finally { - playerChunkMap.chunkLoadConversionCallbackExecutor.run(); // Paper - Add chunk load conversion callback executor to prevent deadlock due to recursion in the chunk task queue sorter - playerChunkMap.callbackExecutor.run(); -+ ChunkProviderServer.this.lightEngine.queueUpdate(); // Paper - always run, this is rate limited now - } - // CraftBukkit end - } diff --git a/src/main/java/net/minecraft/server/IBlockData.java b/src/main/java/net/minecraft/server/IBlockData.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/server/IBlockData.java @@ -166,7 +160,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + int x = (int) (i >> 38); + int y = (int) ((i << 52) >> 52); + int z = (int) ((i << 26) >> 38); -+ long k = SectionPosition.asLong(x >> 4, y >> 4, z >> 4); ++ long k = SectionPosition.blockPosAsSectionLong(x, y, z); + // Paper end EnumDirection[] aenumdirection = LightEngineBlock.e; int l = aenumdirection.length; @@ -175,8 +169,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 EnumDirection enumdirection = aenumdirection[i1]; - long j1 = BlockPosition.a(i, enumdirection); - long k1 = SectionPosition.e(j1); -+ long j1 = BlockPosition.asLong(x + enumdirection.getAdjacentX(), y + enumdirection.getAdjacentY(), z + enumdirection.getAdjacentZ()); // Paper -+ long k1 = SectionPosition.asLong((x + enumdirection.getAdjacentX()) >> 4, (y + enumdirection.getAdjacentY()) >> 4, (z + enumdirection.getAdjacentZ()) >> 4); // Paper ++ long j1 = BlockPosition.getAdjacent(x, y, z, enumdirection); // Paper ++ long k1 = SectionPosition.getAdjacentFromBlockPos(x, y, z, enumdirection); // Paper if (k == k1 || ((LightEngineStorageBlock) this.c).g(k1)) { this.b(i, j1, j, flag); @@ -190,7 +184,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + int baseX = (int) (i >> 38); + int baseY = (int) ((i << 52) >> 52); + int baseZ = (int) ((i << 26) >> 38); -+ long j1 = (((long) (baseX >> 4) & 4194303L) << 42) | (((long) (baseY >> 4) & 1048575L)) | (((long) (baseZ >> 4) & 4194303L) << 20); ++ long j1 = SectionPosition.blockPosAsSectionLong(baseX, baseY, baseZ); + NibbleArray nibblearray = this.c.updating.getUpdatingOptimized(j1); + // Paper end EnumDirection[] aenumdirection = LightEngineBlock.e; @@ -199,15 +193,14 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 for (int l1 = 0; l1 < k1; ++l1) { EnumDirection enumdirection = aenumdirection[l1]; - long i2 = BlockPosition.a(i, enumdirection); -- -- if (i2 != j) { -- long j2 = SectionPosition.e(i2); + // Paper start + int newX = baseX + enumdirection.getAdjacentX(); + int newY = baseY + enumdirection.getAdjacentY(); + int newZ = baseZ + enumdirection.getAdjacentZ(); -+ if (newX != baseX || newY != baseY || newZ != baseZ) { -+ long i2 = BlockPosition.asLong(newX, newY, newZ); ++ long i2 = BlockPosition.asLong(newX, newY, newZ); + + if (i2 != j) { +- long j2 = SectionPosition.e(i2); + long j2 = SectionPosition.blockPosAsSectionLong(newX, newY, newZ); + // Paper end NibbleArray nibblearray1; @@ -477,36 +470,26 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 protected void a(long i, int j, boolean flag) { - long k = SectionPosition.e(i); - int l = BlockPosition.c(i); +- int i1 = SectionPosition.b(l); +- int j1 = SectionPosition.a(l); + // Paper start + int baseX = (int) (i >> 38); + int baseY = (int) ((i << 52) >> 52); + int baseZ = (int) ((i << 26) >> 38); + long k = SectionPosition.blockPosAsSectionLong(baseX, baseY, baseZ); -+ int l = baseY; ++ int i1 = baseY & 15; ++ int j1 = baseY >> 4; + // Paper end - int i1 = SectionPosition.b(l); - int j1 = SectionPosition.a(l); int k1; + + if (i1 != 0) { @@ -0,0 +0,0 @@ public final class LightEngineSky extends LightEngineLayer<LightEngineStorageSky - } else { - int l1; - -- for (l1 = 0; !((LightEngineStorageSky) this.c).g(SectionPosition.a(k, 0, -l1 - 1, 0)) && ((LightEngineStorageSky) this.c).a(j1 - l1 - 1); ++l1) { -+ // Paper start - cache and optimize base values -+ int secX = baseX >> 4; -+ int secY = baseY >> 4; -+ int secZ = baseZ >> 4; -+ for (l1 = 0; !((LightEngineStorageSky) this.c).g(SectionPosition.asLong(secX, secY + -l1 - 1, secZ)) && ((LightEngineStorageSky) this.c).a(j1 - l1 - 1); ++l1) { -+ // Paper end - ; - } - k1 = l1; } - long i2 = BlockPosition.a(i, 0, -1 - k1 * 16, 0); - long j2 = SectionPosition.e(i2); -+ int newBaseY = baseY + -1 - k1 * 16; // Paper ++ int newBaseY = baseY + (-1 - k1 * 16); // Paper + long i2 = BlockPosition.asLong(baseX, newBaseY, baseZ); // Paper + long j2 = SectionPosition.blockPosAsSectionLong(baseX, newBaseY, baseZ); // Paper @@ -542,7 +525,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + int baseX = (int) (i >> 38); + int baseY = (int) ((i << 52) >> 52); + int baseZ = (int) ((i << 26) >> 38); -+ long j1 = (((long) (baseX >> 4) & 4194303L) << 42) | (((long) (baseY >> 4) & 1048575L)) | (((long) (baseZ >> 4) & 4194303L) << 20); ++ long j1 = SectionPosition.blockPosAsSectionLong(baseX, baseY, baseZ); + NibbleArray nibblearray = this.c.updating.getUpdatingOptimized(j1); + // Paper end EnumDirection[] aenumdirection = LightEngineSky.e; @@ -650,10 +633,11 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + final int x = (int) (i >> 38); + final int y = (int) ((i << 52) >> 52); + final int z = (int) ((i << 26) >> 38); -+ NibbleArray nibblearray = this.updating.getUpdatingOptimized((((long) (x >> 4) & 4194303L) << 42) | (((long) (y >> 4) & 1048575L)) | (((long) (z >> 4) & 4194303L) << 20)); ++ long j = SectionPosition.blockPosAsSectionLong(x, y, z); ++ NibbleArray nibblearray = this.updating.getUpdatingOptimized(j); + // BUG: Sometimes returns null and crashes, try to recover, but to prevent crash just return no light. + if (nibblearray == null) { -+ nibblearray = this.e_visible.lookup.apply((((long) (x >> 4) & 4194303L) << 42) | (((long) (y >> 4) & 1048575L)) | (((long) (z >> 4) & 4194303L) << 20)); ++ nibblearray = this.e_visible.lookup.apply(j); + } + if (nibblearray == null) { + System.err.println("Null nibble, preventing crash " + BlockPosition.fromLong(i)); @@ -671,7 +655,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + int x = (int) (i >> 38); + int y = (int) ((i << 52) >> 52); + int z = (int) ((i << 26) >> 38); -+ long k = SectionPosition.asLong(x >> 4, y >> 4, z >> 4); ++ long k = SectionPosition.blockPosAsSectionLong(x, y, z); + // Paper end if (this.g.add(k)) { @@ -812,12 +796,12 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + // Paper start + i = longiterator.nextLong(); + if (true) { // don't check hasLight, this iterator is filtered already -+ int baseX = (int) (i >> 42); -+ int baseY = (int) (i << 44 >> 44); -+ int baseZ = (int) (i << 22 >> 42); -+ int k = baseX << 4; -+ int l = baseY << 4; -+ int i1 = baseZ << 4; ++ int secX = (int) (i >> 42); ++ int secY = (int) (i << 44 >> 44); ++ int secZ = (int) (i << 22 >> 42); ++ int k = secX << 4; // baseX ++ int l = secY << 4; // baseY ++ int i1 = secZ << 4; // baseZ + // Paper end EnumDirection[] aenumdirection = LightEngineStorage.k; int j1 = aenumdirection.length; @@ -827,7 +811,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 - long l1 = SectionPosition.a(i, enumdirection); - - if (!this.i.containsKey(l1) && this.g(l1)) { -+ long l1 = SectionPosition.asLong(baseX + enumdirection.getAdjacentX(), baseY + enumdirection.getAdjacentY(), baseZ + enumdirection.getAdjacentZ()); // Paper - avoid unpacking ++ long l1 = SectionPosition.getAdjacentFromSectionPos(secX, secY, secZ, enumdirection); // Paper - avoid extra unpacking + if (!propagating.contains(l1) && this.g(l1)) { // Paper - use propagating for (int i2 = 0; i2 < 16; ++i2) { for (int j2 = 0; j2 < 16; ++j2) { @@ -855,32 +839,129 @@ diff --git a/src/main/java/net/minecraft/server/LightEngineStorageArray.java b/s index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/server/LightEngineStorageArray.java +++ b/src/main/java/net/minecraft/server/LightEngineStorageArray.java +@@ -0,0 +0,0 @@ import javax.annotation.Nullable; + + public abstract class LightEngineStorageArray<M extends LightEngineStorageArray<M>> { + +- private final long[] b = new long[2]; +- private final NibbleArray[] c = new NibbleArray[2]; ++ // private final long[] b = new long[2]; // Paper - unused ++ private final NibbleArray[] c = new NibbleArray[]{NibbleArray.EMPTY_NIBBLE_ARRAY, NibbleArray.EMPTY_NIBBLE_ARRAY}; private final NibbleArray[] cache = c; // Paper - OBFHELPER + private boolean d; + protected final com.destroystokyo.paper.util.map.QueuedChangesMapLong2Object<NibbleArray> data; // Paper - avoid copying light data + protected final boolean isVisible; // Paper - avoid copying light data +- java.util.function.Function<Long, NibbleArray> lookup; // Paper - faster branchless lookup + ++ // Paper start - faster lookups with less branching, use interface to avoid boxing instead of Function ++ public final NibbleArrayAccess lookup; ++ public interface NibbleArrayAccess { ++ NibbleArray apply(long id); ++ } ++ // Paper end + // Paper start - avoid copying light data + protected LightEngineStorageArray(com.destroystokyo.paper.util.map.QueuedChangesMapLong2Object<NibbleArray> data, boolean isVisible) { + if (isVisible) { +@@ -0,0 +0,0 @@ public abstract class LightEngineStorageArray<M extends LightEngineStorageArray< + } + this.data = data; + this.isVisible = isVisible; ++ // Paper end - avoid copying light data ++ // Paper start - faster lookups with less branching + if (isVisible) { + lookup = data::getVisibleAsync; + } else { +- lookup = data::getUpdating; ++ lookup = data.getUpdatingMap()::get; // jump straight the sub map + } +- // Paper end - avoid copying light data ++ // Paper end + this.c(); + this.d = true; + } +@@ -0,0 +0,0 @@ public abstract class LightEngineStorageArray<M extends LightEngineStorageArray< + public void a(long i) { + if (this.isVisible) { throw new IllegalStateException("writing to visible data"); } // Paper - avoid copying light data + NibbleArray updating = this.data.getUpdating(i); // Paper - pool nibbles +- this.data.queueUpdate(i, new NibbleArray().markPoolSafe(updating.getCloneIfSet())); // Paper - avoid copying light data - pool safe clone ++ NibbleArray nibblearray = new NibbleArray().markPoolSafe(updating.getCloneIfSet()); // Paper ++ nibblearray.lightCacheKey = i; // Paper ++ this.data.queueUpdate(i, nibblearray); // Paper - avoid copying light data - pool safe clone + if (updating.cleaner != null) MCUtil.scheduleTask(2, updating.cleaner, "Light Engine Release"); // Paper - delay clean incase anything holding ref was still using it + this.c(); + } @@ -0,0 +0,0 @@ public abstract class LightEngineStorageArray<M extends LightEngineStorageArray< return lookup.apply(i) != null; // Paper - avoid copying light data } +- @Nullable +- public final NibbleArray c(long i) { // Paper - final +- if (this.d) { +- for (int j = 0; j < 2; ++j) { +- if (i == this.b[j]) { +- return this.c[j]; +- } +- } +- } +- +- NibbleArray nibblearray = lookup.apply(i); // Paper - avoid copying light data + // Paper start - less branching as we know we are using cache and updating -+ public final NibbleArray getUpdatingOptimized(long i) { // Paper - final -+ if (this.b[0] == i) return this.c[0]; -+ if (this.b[1] == i) return this.c[1]; -+ -+ NibbleArray nibblearray = this.data.getUpdating(i); // Paper - avoid copying light data -+ if (nibblearray == null) { -+ return null; -+ } else { -+ this.b[1] = this.b[0]; -+ this.c[1] = this.c[0]; -+ -+ this.b[0] = i; -+ this.c[0] = nibblearray; -+ return nibblearray; -+ } -+ } ++ public final NibbleArray getUpdatingOptimized(final long i) { // Paper - final ++ final NibbleArray[] cache = this.cache; ++ if (cache[0].lightCacheKey == i) return cache[0]; ++ if (cache[1].lightCacheKey == i) return cache[1]; + ++ final NibbleArray nibblearray = this.lookup.apply(i); // Paper - avoid copying light data + if (nibblearray == null) { + return null; + } else { +- if (this.d) { +- for (int k = 1; k > 0; --k) { +- this.b[k] = this.b[k - 1]; +- this.c[k] = this.c[k - 1]; +- } +- +- this.b[0] = i; +- this.c[0] = nibblearray; +- } +- ++ cache[1] = cache[0]; ++ cache[0] = nibblearray; + return nibblearray; + } + } + // Paper end + ++ @Nullable ++ public final NibbleArray c(final long i) { // Paper - final ++ // Paper start - optimize visible case or missed updating cases ++ if (this.d) { ++ // short circuit to optimized ++ return getUpdatingOptimized(i); ++ } ++ ++ return this.lookup.apply(i); ++ // Paper end ++ } + @Nullable - public final NibbleArray c(long i) { // Paper - final - if (this.d) { + public NibbleArray d(long i) { +@@ -0,0 +0,0 @@ public abstract class LightEngineStorageArray<M extends LightEngineStorageArray< + + public void a(long i, NibbleArray nibblearray) { + if (this.isVisible) { throw new IllegalStateException("writing to visible data"); } // Paper - avoid copying light data ++ nibblearray.lightCacheKey = i; // Paper + this.data.queueUpdate(i, nibblearray); // Paper - avoid copying light data + } + + public void c() { + for (int i = 0; i < 2; ++i) { +- this.b[i] = Long.MAX_VALUE; +- this.c[i] = null; ++ // this.b[i] = Long.MAX_VALUE; // Paper - Unused ++ this.c[i] = NibbleArray.EMPTY_NIBBLE_ARRAY; // Paper + } + } + diff --git a/src/main/java/net/minecraft/server/LightEngineStorageBlock.java b/src/main/java/net/minecraft/server/LightEngineStorageBlock.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/server/LightEngineStorageBlock.java @@ -890,16 +971,17 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 @Override protected int d(long i) { - long j = SectionPosition.e(i); +- NibbleArray nibblearray = this.a(j, false); +- +- return nibblearray == null ? 0 : nibblearray.a(SectionPosition.b(BlockPosition.b(i)), SectionPosition.b(BlockPosition.c(i)), SectionPosition.b(BlockPosition.d(i))); + // Paper start + int baseX = (int) (i >> 38); + int baseY = (int) ((i << 52) >> 52); + int baseZ = (int) ((i << 26) >> 38); + long j = (((long) (baseX >> 4) & 4194303L) << 42) | (((long) (baseY >> 4) & 1048575L)) | (((long) (baseZ >> 4) & 4194303L) << 20); ++ NibbleArray nibblearray = this.e_visible.lookup.apply(j); ++ return nibblearray == null ? 0 : nibblearray.a(baseX & 15, baseY & 15, baseZ & 15); + // Paper end - NibbleArray nibblearray = this.a(j, false); - -- return nibblearray == null ? 0 : nibblearray.a(SectionPosition.b(BlockPosition.b(i)), SectionPosition.b(BlockPosition.c(i)), SectionPosition.b(BlockPosition.d(i))); -+ return nibblearray == null ? 0 : nibblearray.a(baseX & 15, baseY & 15, baseZ & 15); // Paper } public static final class a extends LightEngineStorageArray<LightEngineStorageBlock.a> { @@ -916,7 +998,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + int baseX = (int) (i >> 38); + int baseY = (int) ((i << 52) >> 52); + int baseZ = (int) ((i << 26) >> 38); -+ long j = (((long) (baseX >> 4) & 4194303L) << 42) | (((long) (baseY >> 4) & 1048575L)) | (((long) (baseZ >> 4) & 4194303L) << 20); ++ long j = SectionPosition.blockPosAsSectionLong(baseX, baseY, baseZ); + // Paper end int k = SectionPosition.c(j); synchronized (this.visibleUpdateLock) { // Paper - avoid copying light data @@ -971,7 +1053,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 EnumDirection enumdirection = aenumdirection[l1]; - k1 = SectionPosition.a(i, enumdirection); -+ k1 = SectionPosition.asLong(baseX + enumdirection.getAdjacentX(), baseY + enumdirection.getAdjacentY(), baseZ + enumdirection.getAdjacentZ()); // Paper ++ k1 = SectionPosition.getAdjacentFromBlockPos(baseX, baseY, baseZ, enumdirection); // Paper if ((this.n.contains(k1) || !this.l.contains(k1) && !this.m.contains(k1)) && this.g(k1)) { for (int i2 = 0; i2 < 16; ++i2) { for (int j2 = 0; j2 < 16; ++j2) { @@ -1016,50 +1098,68 @@ diff --git a/src/main/java/net/minecraft/server/LightEngineThreaded.java b/src/m index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/server/LightEngineThreaded.java +++ b/src/main/java/net/minecraft/server/LightEngineThreaded.java -@@ -0,0 +0,0 @@ public class LightEngineThreaded extends LightEngine implements AutoCloseable { +@@ -0,0 +0,0 @@ import org.apache.logging.log4j.Logger; + public class LightEngineThreaded extends LightEngine implements AutoCloseable { private static final Logger LOGGER = LogManager.getLogger(); - private final ThreadedMailbox<Runnable> b; +- private final ThreadedMailbox<Runnable> b; - private final ObjectList<Pair<LightEngineThreaded.Update, Runnable>> c = new ObjectArrayList(); -- private final PlayerChunkMap d; -+ // Paper start - add urgent queue, switch to ArrayDeque -+ private long nextNonUrgent = 0; -+ private boolean shouldPollNonUrgent() { -+ return urgent.isEmpty() && !c.isEmpty() && (this.c.size() >= this.f || System.nanoTime() > nextNonUrgent); -+ } -+ -+ private boolean shouldPollUrgent() { -+ return (super.a() || !urgent.isEmpty()); -+ } -+ -+ private IntSupplier getChunkPrioritySupplier(ChunkCoordIntPair coords) { -+ return getChunkMap().getPrioritySupplier(coords.pair()); -+ } ++ private final ThreadedMailbox<Runnable> b; ThreadedMailbox<Runnable> mailbox; // Paper ++ // Paper start + private static final int MAX_PRIORITIES = PlayerChunkMap.GOLDEN_TICKET + 2; -+ private static class LightQueueBucket extends java.util.ArrayDeque<Pair<LightEngineThreaded.Update, Runnable>> { -+ public LightQueueBucket() { -+ super(64); -+ } ++ ++ public void changePriority(long pair, int currentPriority, int priority) { ++ this.mailbox.queue(() -> { ++ ChunkLightQueue remove = this.queue.buckets[currentPriority].remove(pair); ++ if (remove != null) { ++ ChunkLightQueue existing = this.queue.buckets[priority].put(pair, remove); ++ if (existing != null) { ++ remove.pre.addAll(existing.pre); ++ remove.post.addAll(existing.post); ++ } ++ } ++ }); + } ++ ++ static class ChunkLightQueue { ++ public boolean shouldFastUpdate; ++ java.util.ArrayDeque<Runnable> pre = new java.util.ArrayDeque<Runnable>(); ++ java.util.ArrayDeque<Runnable> post = new java.util.ArrayDeque<Runnable>(); ++ ++ ChunkLightQueue(long chunk) {} ++ } ++ ++ + // Retain the chunks priority level for queued light tasks + private static class LightQueue { -+ + private int size = 0; + private int lowestPriority = MAX_PRIORITIES; -+ private final LightQueueBucket[] buckets = new LightQueueBucket[MAX_PRIORITIES]; ++ private final it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap<ChunkLightQueue>[] buckets = new it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap[MAX_PRIORITIES]; + + private LightQueue() { + for (int i = 0; i < buckets.length; i++) { -+ buckets[i] = new LightQueueBucket(); ++ buckets[i] = new it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap<>(); + } + } + -+ public final void add(int priority, LightEngineThreaded.Update type, Runnable run) { ++ public final void add(long chunkId, int priority, LightEngineThreaded.Update type, Runnable run) { ++ add(chunkId, priority, type, run, false); ++ } ++ public final void add(long chunkId, int priority, LightEngineThreaded.Update type, Runnable run, boolean shouldFastUpdate) { ++ ChunkLightQueue lightQueue = this.buckets[priority].computeIfAbsent(chunkId, ChunkLightQueue::new); + this.size++; -+ if (lowestPriority > priority) { -+ lowestPriority = priority; ++ if (type == Update.PRE_UPDATE) { ++ lightQueue.pre.add(run); ++ } else { ++ lightQueue.post.add(run); ++ } ++ if (shouldFastUpdate) { ++ lightQueue.shouldFastUpdate = true; ++ } ++ ++ if (this.lowestPriority > priority) { ++ this.lowestPriority = priority; + } -+ this.buckets[priority].add(new Pair<>(type, run)); + } + + public final boolean isEmpty() { @@ -1070,50 +1170,71 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + return this.size; + } + -+ public Pair<Update, Runnable> poll() { -+ for (; lowestPriority < MAX_PRIORITIES; lowestPriority++) { -+ Pair<Update, Runnable> entry = buckets[lowestPriority].pollFirst(); -+ if (entry != null) { -+ this.size--; -+ return entry; ++ public boolean poll(java.util.List<Runnable> pre, java.util.List<Runnable> post) { ++ boolean hasWork = false; ++ it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap<ChunkLightQueue>[] buckets = this.buckets; ++ while (lowestPriority < MAX_PRIORITIES && !isEmpty()) { ++ it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap<ChunkLightQueue> bucket = buckets[lowestPriority]; ++ if (bucket.isEmpty()) { ++ lowestPriority++; ++ continue; ++ } ++ ChunkLightQueue queue = bucket.removeFirst(); ++ this.size -= queue.pre.size() + queue.post.size(); ++ pre.addAll(queue.pre); ++ post.addAll(queue.post); ++ queue.pre.clear(); ++ queue.post.clear(); ++ hasWork = true; ++ if (queue.shouldFastUpdate) { ++ return true; + } + } -+ return null; ++ return hasWork; + } + } + -+ private final LightQueue urgent = new LightQueue(); -+ private final LightQueue c = new LightQueue(); ++ private final LightQueue queue = new LightQueue(); + // Paper end -+ private final PlayerChunkMap d; private PlayerChunkMap getChunkMap() { return d; } // Paper - OBFHELPER + private final PlayerChunkMap d; private final Mailbox<ChunkTaskQueueSorter.a<Runnable>> e; private volatile int f = 5; - private final AtomicBoolean g = new AtomicBoolean(); @@ -0,0 +0,0 @@ public class LightEngineThreaded extends LightEngine implements AutoCloseable { + super(ilightaccess, true, flag); + this.d = playerchunkmap; + this.e = mailbox; +- this.b = threadedmailbox; ++ this.mailbox = this.b = threadedmailbox; // Paper } + public void close() {} +@@ -0,0 +0,0 @@ public class LightEngineThreaded extends LightEngine implements AutoCloseable { + private void a(int i, int j, IntSupplier intsupplier, LightEngineThreaded.Update lightenginethreaded_update, Runnable runnable) { -+ // Paper start -+ scheduleLightTask(i, j, intsupplier, lightenginethreaded_update, runnable, false); -+ } -+ private void scheduleLightTask(int i, int j, IntSupplier intsupplier, LightEngineThreaded.Update lightenginethreaded_update, Runnable runnable, boolean urgent) { this.e.a(ChunkTaskQueueSorter.a(() -> { // Paper - decompile error - this.c.add(Pair.of(lightenginethreaded_update, runnable)); - if (this.c.size() >= this.f) { -- this.b(); -- } -+ (urgent ? this.urgent : this.c).add(intsupplier.getAsInt(), lightenginethreaded_update, runnable); -+ if (shouldPollUrgent() || shouldPollNonUrgent()) queueUpdate(); -+ // Paper end ++ // Paper start ++ int priority = intsupplier.getAsInt(); ++ this.queue.add(ChunkCoordIntPair.pair(i, j), priority, lightenginethreaded_update, runnable); // Paper ++ if (priority <= 25) { // don't auto kick off unless priority ++ // Paper end + this.b(); + } - }, ChunkCoordIntPair.pair(i, j), intsupplier)); - } @@ -0,0 +0,0 @@ public class LightEngineThreaded extends LightEngine implements AutoCloseable { ChunkCoordIntPair chunkcoordintpair = ichunkaccess.getPos(); ichunkaccess.b(false); - this.a(chunkcoordintpair.x, chunkcoordintpair.z, LightEngineThreaded.Update.PRE_UPDATE, SystemUtils.a(() -> { -+ this.scheduleLightTask(chunkcoordintpair.x, chunkcoordintpair.z, getChunkPrioritySupplier(chunkcoordintpair), LightEngineThreaded.Update.PRE_UPDATE, SystemUtils.a(() -> { // Paper ++ // Paper start ++ long pair = chunkcoordintpair.pair(); ++ CompletableFuture<IChunkAccess> future = new CompletableFuture<>(); ++ IntSupplier prioritySupplier1 = d.getPrioritySupplier(pair); ++ IntSupplier prioritySupplier = flag ? () -> Math.max(1, prioritySupplier1.getAsInt() - 10) : prioritySupplier1; ++ this.e.a(ChunkTaskQueueSorter.a(() -> { ++ this.queue.add(pair, prioritySupplier.getAsInt(), LightEngineThreaded.Update.PRE_UPDATE, SystemUtils.a(() -> { ++ // Paper end ChunkSection[] achunksection = ichunkaccess.getSections(); for (int i = 0; i < 16; ++i) { @@ -1121,31 +1242,30 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 this.d.c(chunkcoordintpair); }, () -> { return "lightChunk " + chunkcoordintpair + " " + flag; -- })); -+ }), true); // Paper - urgent flag - return CompletableFuture.supplyAsync(() -> { ++ // Paper start - merge the 2 together + })); +- return CompletableFuture.supplyAsync(() -> { ++ ++ this.queue.add(pair, prioritySupplier.getAsInt(), LightEngineThreaded.Update.POST_UPDATE, () -> { ichunkaccess.b(true); super.b(chunkcoordintpair, false); - return ichunkaccess; - }, (runnable) -> { +- return ichunkaccess; +- }, (runnable) -> { - this.a(chunkcoordintpair.x, chunkcoordintpair.z, LightEngineThreaded.Update.POST_UPDATE, runnable); -+ this.scheduleLightTask(chunkcoordintpair.x, chunkcoordintpair.z, getChunkPrioritySupplier(chunkcoordintpair), LightEngineThreaded.Update.POST_UPDATE, runnable, true); // Paper -+ queueUpdate(); // Paper ++ // Paper start ++ future.complete(ichunkaccess); }); ++ queueUpdate(); // run queue now ++ }, pair, prioritySupplier)); ++ return future; ++ // Paper end } public void queueUpdate() { - if ((!this.c.isEmpty() || super.a()) && this.g.compareAndSet(false, true)) { -+ if ((shouldPollUrgent() || shouldPollNonUrgent()) && this.g.compareAndSet(false, true)) { // Paper - level check is now in shouldPollUrgent ++ if ((!this.queue.isEmpty() || super.a()) && this.g.compareAndSet(false, true)) { // Paper this.b.a((() -> { // Paper - decompile error -- this.b(); -+ // Paper start -+ if (shouldPollUrgent()) { -+ do { -+ this.runQueue(true); -+ } while (shouldPollUrgent()); -+ } else if (shouldPollNonUrgent()) this.runQueue(false); // don't loop non urgent as urgent might come in -+ // Paper end + this.b(); this.g.set(false); + queueUpdate(); // Paper - if we still have work to do, do it! })); @@ -1153,6 +1273,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 } ++ // Paper start - replace impl private void b() { - int i = Math.min(this.c.size(), this.f); - ObjectListIterator<Pair<LightEngineThreaded.Update, Runnable>> objectlistiterator = this.c.iterator(); @@ -1164,28 +1285,12 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 - pair = (Pair) objectlistiterator.next(); - if (pair.getFirst() == LightEngineThreaded.Update.PRE_UPDATE) { - ((Runnable) pair.getSecond()).run(); -+ // Paper start - replace impl, use more effecient deque, avoid single removes (iterator.remove() which does a lot of copying) -+ runQueue(!this.urgent.isEmpty()); -+ } -+ private void runQueue(boolean urgent) { -+ LightQueue col = urgent ? this.urgent : c; -+ java.util.List<Pair<LightEngineThreaded.Update, Runnable>> pre = new java.util.ArrayList<>(); -+ java.util.List<Pair<LightEngineThreaded.Update, Runnable>> post = new java.util.ArrayList<>(); -+ int i = Math.min(col.size(), 8); // process small batches so chunks can progress without waiting for everything -+ Pair<LightEngineThreaded.Update, Runnable> pair; -+ while (i-- > 0 && (pair = col.poll()) != null) { -+ if (pair.getFirst() == Update.PRE_UPDATE) { -+ pre.add(pair); -+ } else { -+ post.add(pair); - } - } - +- } +- } +- - objectlistiterator.back(j); -+ pre.forEach(entry -> entry.getSecond().run()); - super.a(Integer.MAX_VALUE, true, true); -+ post.forEach(entry -> entry.getSecond().run()); - +- super.a(Integer.MAX_VALUE, true, true); +- - for (j = 0; objectlistiterator.hasNext() && j < i; ++j) { - pair = (Pair) objectlistiterator.next(); - if (pair.getFirst() == LightEngineThreaded.Update.POST_UPDATE) { @@ -1193,12 +1298,51 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 - } - - objectlistiterator.remove(); -- } -+ if (!urgent && this.c.isEmpty()) nextNonUrgent = System.nanoTime() + (50 * 1000000); ++ java.util.List<Runnable> pre = new java.util.ArrayList<>(); ++ java.util.List<Runnable> post = new java.util.ArrayList<>(); ++ int i = Math.min(queue.size(), 4); ++ while (i-- > 0 && queue.poll(pre, post)) { ++ pre.forEach(Runnable::run); ++ pre.clear(); ++ super.a(Integer.MAX_VALUE, true, true); ++ post.forEach(Runnable::run); ++ post.clear(); + } +- + // Paper end - } + public void a(int i) { +diff --git a/src/main/java/net/minecraft/server/NibbleArray.java b/src/main/java/net/minecraft/server/NibbleArray.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/server/NibbleArray.java ++++ b/src/main/java/net/minecraft/server/NibbleArray.java +@@ -0,0 +0,0 @@ import javax.annotation.Nullable; + public class NibbleArray { + + // Paper start ++ static final NibbleArray EMPTY_NIBBLE_ARRAY = new NibbleArray() { ++ @Override ++ public byte[] asBytes() { ++ throw new IllegalStateException(); ++ } ++ }; ++ long lightCacheKey = Long.MIN_VALUE; + public static byte[] EMPTY_NIBBLE = new byte[2048]; + private static final int nibbleBucketSizeMultiplier = Integer.getInteger("Paper.nibbleBucketSize", 3072); + private static final int maxPoolSize = Integer.getInteger("Paper.maxNibblePoolSize", (int) Math.min(6, Math.max(1, Runtime.getRuntime().maxMemory() / 1024 / 1024 / 1024)) * (nibbleBucketSizeMultiplier * 8)); +diff --git a/src/main/java/net/minecraft/server/PlayerChunk.java b/src/main/java/net/minecraft/server/PlayerChunk.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/server/PlayerChunk.java ++++ b/src/main/java/net/minecraft/server/PlayerChunk.java +@@ -0,0 +0,0 @@ public class PlayerChunk { + ioPriority = com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGH_PRIORITY; + } + chunkMap.world.asyncChunkTaskManager.raisePriority(location.x, location.z, ioPriority); ++ chunkMap.world.getChunkProvider().getLightEngine().changePriority(location.pair(), getCurrentPriority(), priority); + } + if (getCurrentPriority() != priority) { + this.w.a(this.location, this::getCurrentPriority, priority, this::setPriority); // use preferred priority diff --git a/src/main/java/net/minecraft/server/PlayerChunkMap.java b/src/main/java/net/minecraft/server/PlayerChunkMap.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/server/PlayerChunkMap.java @@ -1211,3 +1355,29 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 protected IntSupplier c(long i) { return () -> { PlayerChunk playerchunk = this.getVisibleChunk(i); +diff --git a/src/main/java/net/minecraft/server/ThreadedMailbox.java b/src/main/java/net/minecraft/server/ThreadedMailbox.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/server/ThreadedMailbox.java ++++ b/src/main/java/net/minecraft/server/ThreadedMailbox.java +@@ -0,0 +0,0 @@ public class ThreadedMailbox<T> implements Mailbox<T>, AutoCloseable, Runnable { + + } + +- @Override ++ ++ public void queue(T t0) { a(t0); } @Override // Paper - OBFHELPER + public void a(T t0) { + this.a.a(t0); + this.f(); +diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/server/WorldServer.java ++++ b/src/main/java/net/minecraft/server/WorldServer.java +@@ -0,0 +0,0 @@ public class WorldServer extends World { + } + gameprofilerfiller.exit(); + timings.chunkTicksBlocks.stopTiming(); // Paper ++ getChunkProvider().getLightEngine().queueUpdate(); // Paper + // Paper end + } + }