diff --git a/patches/server/Optimize-Light-Engine.patch b/patches/server/Optimize-Light-Engine.patch new file mode 100644 index 000000000..c3c2bb476 --- /dev/null +++ b/patches/server/Optimize-Light-Engine.patch @@ -0,0 +1,1319 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Thu, 4 Jun 2020 22:43:29 -0400 +Subject: [PATCH] Optimize Light Engine + +Massive update to light to improve performance and chunk loading/generation. + +1) Massive bit packing/unpacking optimizations and inlining. + A lot of performance has to do with constant packing and unpacking of bits. + We now inline a most bit operations, and re-use base x/y/z bits in many places. + This helps with cpu level processing to just do all the math at once instead + of having to jump in and out of function calls. + + This much logic also is likely over the JVM Inline limit for JIT too. +2) Applied a few of JellySquid's Phosphor mod optimizations such as + - ensuring we don't notify neighbor chunks when neighbor chunk doesn't need to be notified + - reduce hasLight checks in initializing light, and prob some more, they are tagged JellySquid where phosphor influence was used. +3) Optimize hot path accesses to getting updating chunk to have less branching +4) Optimize getBlock accesses to have less branching, and less unpacking +5) Have a separate urgent bucket for chunk light tasks. These tasks will always cut in line over non blocking light tasks. +6) Retain chunk priority while light tasks are enqueued. So if a task comes in at high priority but the queue is full + of tasks already at a lower priority, before the task was simply added to the end. Now it can cut in line to the front. + this applies for both urgent and non urgent tasks. +7) Buffer non urgent tasks even if queueUpdate is called multiple times to improve efficiency. +8) Fix NPE risk that crashes server in getting nibble data + +diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkHolder.java ++++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java +@@ -0,0 +0,0 @@ public class ChunkHolder { + ioPriority = com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGH_PRIORITY; + } + chunkMap.level.asyncChunkTaskManager.raisePriority(pos.x, pos.z, ioPriority); ++ chunkMap.level.getChunkSource().getLightEngine().queue.changePriority(pos.toLong(), this.queueLevel, priority); // Paper + } + if (this.queueLevel != priority) { + this.onLevelChange.onLevelChange(this.pos, () -> this.queueLevel, priority, p -> this.queueLevel = p); // use preferred priority +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -0,0 +0,0 @@ import net.minecraft.world.level.levelgen.structure.templatesystem.StructureMana + import net.minecraft.world.level.storage.DimensionDataStorage; + import net.minecraft.world.level.storage.LevelStorageSource; + import net.minecraft.world.phys.Vec3; ++import net.minecraft.world.level.storage.PrimaryLevelData; + import it.unimi.dsi.fastutil.objects.ObjectRBTreeSet; // Paper + import org.apache.commons.lang3.mutable.MutableBoolean; + import org.apache.logging.log4j.LogManager; +@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + } + // Paper end + ++ private final java.util.concurrent.ExecutorService lightThread; // Paper + public ChunkMap(ServerLevel world, LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, StructureManager structureManager, Executor executor, BlockableEventLoop mainThreadExecutor, LightChunkGetter chunkProvider, ChunkGenerator chunkGenerator, ChunkProgressListener worldGenerationProgressListener, ChunkStatusUpdateListener chunkStatusChangeListener, Supplier persistentStateManagerFactory, int viewDistance, boolean dsync) { + super(new File(session.getDimensionPath(world.dimension()), "region"), dataFixer, dsync); + //this.visibleChunks = this.updatingChunks.clone(); // Paper - no more cloning +@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + + this.progressListener = worldGenerationProgressListener; + this.chunkStatusListener = chunkStatusChangeListener; +- ProcessorMailbox lightthreaded; ProcessorMailbox threadedmailbox1 = lightthreaded = ProcessorMailbox.create(executor, "light"); // Paper ++ // Paper start - use light thread ++ ProcessorMailbox lightthreaded; ProcessorMailbox threadedmailbox1 = lightthreaded = ProcessorMailbox.create(lightThread = java.util.concurrent.Executors.newSingleThreadExecutor(r -> { ++ Thread thread = new Thread(r); ++ thread.setName(((PrimaryLevelData)level.getLevelData()).getLevelName() + " - Light"); ++ thread.setDaemon(true); ++ thread.setPriority(Thread.NORM_PRIORITY+1); ++ return thread; ++ }), "light"); ++ // Paper end + + this.queueSorter = new ChunkTaskPriorityQueueSorter(ImmutableList.of(threadedmailbox, mailbox, threadedmailbox1), executor, Integer.MAX_VALUE); + this.worldgenMailbox = this.queueSorter.getProcessor(threadedmailbox, false); +@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + @Override + public void close() throws IOException { + try { ++ this.lightThread.shutdown(); // Paper + this.queueSorter.close(); + this.level.asyncChunkTaskManager.close(true); // Paper - Required since we're closing regionfiles in the next line + this.poiManager.close(); +diff --git a/src/main/java/net/minecraft/server/level/SectionTracker.java b/src/main/java/net/minecraft/server/level/SectionTracker.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/server/level/SectionTracker.java ++++ b/src/main/java/net/minecraft/server/level/SectionTracker.java +@@ -0,0 +0,0 @@ public abstract class SectionTracker extends DynamicGraphMinFixedPoint { + + @Override + protected void checkNeighborsAfterUpdate(long id, int level, boolean decrease) { ++ // Paper start ++ int x = (int) (id >> 42); ++ int y = (int) (id << 44 >> 44); ++ int z = (int) (id << 22 >> 42); ++ // Paper end + for(int i = -1; i <= 1; ++i) { + for(int j = -1; j <= 1; ++j) { + for(int k = -1; k <= 1; ++k) { +- long l = SectionPos.offset(id, i, j, k); +- if (l != id) { ++ if (i == 0 && j == 0 && k == 0) continue; // Paper ++ long l = (((long) (x + i) & 4194303L) << 42) | (((long) (y + j) & 1048575L)) | (((long) (z + k) & 4194303L) << 20); ++ // if (l != id) { // Paper - checked above + this.checkNeighbor(id, l, level, decrease); +- } ++ //} // Paper + } + } + } +@@ -0,0 +0,0 @@ public abstract class SectionTracker extends DynamicGraphMinFixedPoint { + protected int getComputedLevel(long id, long excludedId, int maxLevel) { + int i = maxLevel; + ++ // Paper start ++ int x = (int) (id >> 42); ++ int y = (int) (id << 44 >> 44); ++ int z = (int) (id << 22 >> 42); ++ // Paper end + for(int j = -1; j <= 1; ++j) { + for(int k = -1; k <= 1; ++k) { + for(int l = -1; l <= 1; ++l) { +- long m = SectionPos.offset(id, j, k, l); ++ long m = (((long) (x + j) & 4194303L) << 42) | (((long) (y + k) & 1048575L)) | (((long) (z + l) & 4194303L) << 20); // Paper + if (m == id) { + m = Long.MAX_VALUE; + } +diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java ++++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +@@ -0,0 +0,0 @@ public class ServerChunkCache extends ChunkSource { + if (ServerChunkCache.this.runDistanceManagerUpdates()) { + return true; + } else { +- ServerChunkCache.this.lightEngine.tryScheduleUpdate(); ++ ServerChunkCache.this.lightEngine.tryScheduleUpdate(); // Paper - not needed + return super.pollTask() || execChunkTask; // Paper + } + } finally { +diff --git a/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java b/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java ++++ b/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java +@@ -0,0 +0,0 @@ + package net.minecraft.server.level; + + import com.mojang.datafixers.util.Pair; ++import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; // Paper + import it.unimi.dsi.fastutil.objects.ObjectArrayList; + import it.unimi.dsi.fastutil.objects.ObjectList; + import it.unimi.dsi.fastutil.objects.ObjectListIterator; +@@ -0,0 +0,0 @@ import net.minecraft.util.thread.ProcessorMailbox; + import net.minecraft.world.level.ChunkPos; + import net.minecraft.world.level.LightLayer; + import net.minecraft.world.level.chunk.ChunkAccess; ++import net.minecraft.world.level.chunk.ChunkStatus; + import net.minecraft.world.level.chunk.DataLayer; + import net.minecraft.world.level.chunk.LevelChunkSection; + import net.minecraft.world.level.chunk.LightChunkGetter; +@@ -0,0 +0,0 @@ import org.apache.logging.log4j.Logger; + public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCloseable { + private static final Logger LOGGER = LogManager.getLogger(); + private final ProcessorMailbox taskMailbox; +- private final ObjectList> lightTasks = new ObjectArrayList<>(); +- private final ChunkMap chunkMap; ++ // Paper start ++ private static final int MAX_PRIORITIES = ChunkMap.MAX_CHUNK_DISTANCE + 2; ++ ++ private boolean isChunkLightStatus(long pair) { ++ ChunkHolder playerChunk = playerChunkMap.getVisibleChunkIfPresent(pair); ++ if (playerChunk == null) { ++ return false; ++ } ++ ChunkStatus status = ChunkHolder.getStatus(playerChunk.getTicketLevel()); ++ return status != null && status.isOrAfter(ChunkStatus.LIGHT); ++ } ++ ++ static class ChunkLightQueue { ++ public boolean shouldFastUpdate; ++ java.util.ArrayDeque pre = new java.util.ArrayDeque(); ++ java.util.ArrayDeque post = new java.util.ArrayDeque(); ++ ++ ChunkLightQueue(long chunk) {} ++ } ++ ++ static class PendingLightTask { ++ long chunkId; ++ IntSupplier priority; ++ Runnable pre; ++ Runnable post; ++ boolean fastUpdate; ++ ++ public PendingLightTask(long chunkId, IntSupplier priority, Runnable pre, Runnable post, boolean fastUpdate) { ++ this.chunkId = chunkId; ++ this.priority = priority; ++ this.pre = pre; ++ this.post = post; ++ this.fastUpdate = fastUpdate; ++ } ++ } ++ ++ ++ // Retain the chunks priority level for queued light tasks ++ class LightQueue { ++ private int size = 0; ++ private final Long2ObjectLinkedOpenHashMap[] buckets = new Long2ObjectLinkedOpenHashMap[MAX_PRIORITIES]; ++ private final java.util.concurrent.ConcurrentLinkedQueue pendingTasks = new java.util.concurrent.ConcurrentLinkedQueue<>(); ++ private final java.util.concurrent.ConcurrentLinkedQueue priorityChanges = new java.util.concurrent.ConcurrentLinkedQueue<>(); ++ ++ private LightQueue() { ++ for (int i = 0; i < buckets.length; i++) { ++ buckets[i] = new Long2ObjectLinkedOpenHashMap<>(); ++ } ++ } ++ ++ public void changePriority(long pair, int currentPriority, int priority) { ++ this.priorityChanges.add(() -> { ++ ChunkLightQueue remove = this.buckets[currentPriority].remove(pair); ++ if (remove != null) { ++ ChunkLightQueue existing = this.buckets[Math.max(1, priority)].put(pair, remove); ++ if (existing != null) { ++ remove.pre.addAll(existing.pre); ++ remove.post.addAll(existing.post); ++ } ++ } ++ }); ++ } ++ ++ public final void addChunk(long chunkId, IntSupplier priority, Runnable pre, Runnable post) { ++ pendingTasks.add(new PendingLightTask(chunkId, priority, pre, post, true)); ++ tryScheduleUpdate(); ++ } ++ ++ public final void add(long chunkId, IntSupplier priority, ThreadedLevelLightEngine.TaskType type, Runnable run) { ++ pendingTasks.add(new PendingLightTask(chunkId, priority, type == TaskType.PRE_UPDATE ? run : null, type == TaskType.POST_UPDATE ? run : null, false)); ++ } ++ public final void add(PendingLightTask update) { ++ int priority = update.priority.getAsInt(); ++ ChunkLightQueue lightQueue = this.buckets[priority].computeIfAbsent(update.chunkId, ChunkLightQueue::new); ++ ++ if (update.pre != null) { ++ this.size++; ++ lightQueue.pre.add(update.pre); ++ } ++ if (update.post != null) { ++ this.size++; ++ lightQueue.post.add(update.post); ++ } ++ if (update.fastUpdate) { ++ lightQueue.shouldFastUpdate = true; ++ } ++ } ++ ++ public final boolean isEmpty() { ++ return this.size == 0 && this.pendingTasks.isEmpty(); ++ } ++ ++ public final int size() { ++ return this.size; ++ } ++ ++ public boolean poll(java.util.List pre, java.util.List post) { ++ PendingLightTask pending; ++ while ((pending = pendingTasks.poll()) != null) { ++ add(pending); ++ } ++ Runnable run; ++ while ((run = priorityChanges.poll()) != null) { ++ run.run(); ++ } ++ boolean hasWork = false; ++ Long2ObjectLinkedOpenHashMap[] buckets = this.buckets; ++ int priority = 0; ++ while (priority < MAX_PRIORITIES && !isEmpty()) { ++ Long2ObjectLinkedOpenHashMap bucket = buckets[priority]; ++ if (bucket.isEmpty()) { ++ priority++; ++ if (hasWork) { ++ return true; ++ } else { ++ 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 hasWork; ++ } ++ } ++ ++ final LightQueue queue = new LightQueue(); ++ // Paper end ++ private final ChunkMap chunkMap; private final ChunkMap playerChunkMap; // Paper + private final ProcessorHandle> sorterMailbox; + private volatile int taskPerBatch = 5; + private final AtomicBoolean scheduled = new AtomicBoolean(); + + public ThreadedLevelLightEngine(LightChunkGetter chunkProvider, ChunkMap chunkStorage, boolean hasBlockLight, ProcessorMailbox processor, ProcessorHandle> executor) { + super(chunkProvider, true, hasBlockLight); +- this.chunkMap = chunkStorage; ++ this.chunkMap = chunkStorage; this.playerChunkMap = chunkMap; // Paper + this.sorterMailbox = executor; + this.taskMailbox = processor; + } +@@ -0,0 +0,0 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl + } + + private void addTask(int x, int z, IntSupplier completedLevelSupplier, ThreadedLevelLightEngine.TaskType stage, Runnable task) { +- this.sorterMailbox.tell(ChunkTaskPriorityQueueSorter.message(() -> { +- this.lightTasks.add(Pair.of(stage, task)); +- if (this.lightTasks.size() >= this.taskPerBatch) { +- this.runUpdate(); +- } +- +- }, ChunkPos.asLong(x, z), completedLevelSupplier)); ++ // Paper start - replace method ++ this.queue.add(ChunkPos.asLong(x, z), completedLevelSupplier, stage, task); ++ // Paper end + } + + @Override +@@ -0,0 +0,0 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl + + public CompletableFuture lightChunk(ChunkAccess chunk, boolean excludeBlocks) { + ChunkPos chunkPos = chunk.getPos(); +- chunk.setLightCorrect(false); +- this.addTask(chunkPos.x, chunkPos.z, ThreadedLevelLightEngine.TaskType.PRE_UPDATE, Util.name(() -> { ++ // Paper start ++ //ichunkaccess.b(false); // Don't need to disable this ++ long pair = chunkPos.toLong(); ++ CompletableFuture future = new CompletableFuture<>(); ++ IntSupplier prioritySupplier = playerChunkMap.getChunkQueueLevel(pair); ++ boolean[] skippedPre = {false}; ++ this.queue.addChunk(pair, prioritySupplier, Util.name(() -> { ++ if (!isChunkLightStatus(pair)) { ++ future.complete(chunk); ++ skippedPre[0] = true; ++ return; ++ } ++ // Paper end + LevelChunkSection[] levelChunkSections = chunk.getSections(); + + for(int i = 0; i < chunk.getSectionsCount(); ++i) { +@@ -0,0 +0,0 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl + }); + } + +- this.chunkMap.releaseLightTicket(chunkPos); ++ // this.chunkMap.releaseLightTicket(chunkPos); // Paper - move into post task below + }, () -> { + return "lightChunk " + chunkPos + " " + excludeBlocks; +- })); +- return CompletableFuture.supplyAsync(() -> { ++ // Paper start - merge the 2 together ++ }), () -> { ++ this.chunkMap.releaseLightTicket(chunkPos); // Paper - release light tickets as post task to ensure they stay loaded until fully done ++ if (skippedPre[0]) return; // Paper - future's already complete + chunk.setLightCorrect(true); + super.retainData(chunkPos, false); +- return chunk; +- }, (runnable) -> { +- this.addTask(chunkPos.x, chunkPos.z, ThreadedLevelLightEngine.TaskType.POST_UPDATE, runnable); ++ // Paper start ++ future.complete(chunk); + }); ++ return future; ++ // Paper end + } + + public void tryScheduleUpdate() { +- if ((!this.lightTasks.isEmpty() || super.hasLightWork()) && this.scheduled.compareAndSet(false, true)) { ++ if ((!this.queue.isEmpty() || super.hasLightWork()) && this.scheduled.compareAndSet(false, true)) { // Paper + this.taskMailbox.tell(() -> { + this.runUpdate(); + this.scheduled.set(false); ++ tryScheduleUpdate(); // Paper - if we still have work to do, do it! + }); + } + + } + ++ // Paper start - replace impl ++ private final java.util.List pre = new java.util.ArrayList<>(); ++ private final java.util.List post = new java.util.ArrayList<>(); + private void runUpdate() { +- int i = Math.min(this.lightTasks.size(), this.taskPerBatch); +- ObjectListIterator> objectListIterator = this.lightTasks.iterator(); +- +- int j; +- for(j = 0; objectListIterator.hasNext() && j < i; ++j) { +- Pair pair = objectListIterator.next(); +- if (pair.getFirst() == ThreadedLevelLightEngine.TaskType.PRE_UPDATE) { +- pair.getSecond().run(); +- } ++ if (queue.poll(pre, post)) { ++ pre.forEach(Runnable::run); ++ pre.clear(); ++ super.runUpdates(Integer.MAX_VALUE, true, true); ++ post.forEach(Runnable::run); ++ post.clear(); ++ } else { ++ // might have level updates to go still ++ super.runUpdates(Integer.MAX_VALUE, true, true); + } +- +- objectListIterator.back(j); +- super.runUpdates(Integer.MAX_VALUE, true, true); +- +- for(int var5 = 0; objectListIterator.hasNext() && var5 < i; ++var5) { +- Pair pair2 = objectListIterator.next(); +- if (pair2.getFirst() == ThreadedLevelLightEngine.TaskType.POST_UPDATE) { +- pair2.getSecond().run(); +- } +- +- objectListIterator.remove(); +- } +- ++ // Paper end + } + + public void setTaskPerBatch(int taskBatchSize) { +diff --git a/src/main/java/net/minecraft/world/level/chunk/DataLayer.java b/src/main/java/net/minecraft/world/level/chunk/DataLayer.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/DataLayer.java ++++ b/src/main/java/net/minecraft/world/level/chunk/DataLayer.java +@@ -0,0 +0,0 @@ public class DataLayer { + @Nullable + protected byte[] data; + // Paper start ++ public static final DataLayer EMPTY_NIBBLE_ARRAY = new DataLayer() { ++ @Override ++ public byte[] getData() { ++ throw new IllegalStateException(); ++ } ++ }; ++ public 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/world/level/lighting/BlockLightEngine.java b/src/main/java/net/minecraft/world/level/lighting/BlockLightEngine.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/world/level/lighting/BlockLightEngine.java ++++ b/src/main/java/net/minecraft/world/level/lighting/BlockLightEngine.java +@@ -0,0 +0,0 @@ public final class BlockLightEngine extends LayerLightEngine> 38); ++ int j = (int) ((blockPos << 52) >> 52); ++ int k = (int) ((blockPos << 26) >> 38); ++ // Paper end + BlockGetter blockGetter = this.chunkSource.getChunkForLighting(SectionPos.blockToSectionCoord(i), SectionPos.blockToSectionCoord(k)); + return blockGetter != null ? blockGetter.getLightEmission(this.pos.set(i, j, k)) : 0; + } +@@ -0,0 +0,0 @@ public final class BlockLightEngine extends LayerLightEngine= 15) { + return level; + } else { +- int i = Integer.signum(BlockPos.getX(targetId) - BlockPos.getX(sourceId)); +- int j = Integer.signum(BlockPos.getY(targetId) - BlockPos.getY(sourceId)); +- int k = Integer.signum(BlockPos.getZ(targetId) - BlockPos.getZ(sourceId)); ++ // Paper start ++ int ix = (int) (targetId >> 38); ++ int iy = (int) ((targetId << 52) >> 52); ++ int iz = (int) ((targetId << 26) >> 38); ++ int jx = (int) (sourceId >> 38); ++ int jy = (int) ((sourceId << 52) >> 52); ++ int jz = (int) ((sourceId << 26) >> 38); ++ int i = Integer.signum(ix - jx); ++ int j = Integer.signum(iy - jy); ++ int k = Integer.signum(iz - jz); ++ // Paper end + Direction direction = Direction.fromNormal(i, j, k); + if (direction == null) { + return 15; + } else { + //MutableInt mutableint = new MutableInt(); // Paper - share mutableint, single threaded +- BlockState blockState = this.getStateAndOpacity(targetId, mutableInt); +- if (mutableInt.getValue() >= 15) { ++ // Paper start ++ BlockState blockState = this.getBlockOptimized(ix, iy, iz, mutableInt); ++ int blockdLight = mutableInt.getValue(); ++ if (blockdLight >= 15) { ++ // Paper end + return 15; + } else { +- BlockState blockState2 = this.getStateAndOpacity(sourceId, (MutableInt)null); ++ BlockState blockState2 = this.getBlockOptimized(jx, jy, jz); // Paper + VoxelShape voxelShape = this.getShape(blockState2, sourceId, direction); + VoxelShape voxelShape2 = this.getShape(blockState, targetId, direction.getOpposite()); +- return Shapes.faceShapeOccludes(voxelShape, voxelShape2) ? 15 : level + Math.max(1, mutableInt.getValue()); ++ return Shapes.faceShapeOccludes(voxelShape, voxelShape2) ? 15 : level + Math.max(1, blockdLight); // Paper + } + } + } +@@ -0,0 +0,0 @@ public final class BlockLightEngine extends LayerLightEngine> 38); ++ int y = (int) ((id << 52) >> 52); ++ int z = (int) ((id << 26) >> 38); ++ long l = SectionPos.blockPosAsSectionLong(x, y, z); ++ // Paper end + + for(Direction direction : DIRECTIONS) { +- long m = BlockPos.offset(id, direction); ++ long m = BlockPos.getAdjacent(x, y, z, direction); // Paper + long n = SectionPos.blockToSection(m); + if (l == n || this.storage.storingLightForSection(n)) { + this.checkNeighbor(id, m, level, decrease); +@@ -0,0 +0,0 @@ public final class BlockLightEngine extends LayerLightEngine> 38); ++ int baseY = (int) ((id << 52) >> 52); ++ int baseZ = (int) ((id << 26) >> 38); ++ long l = SectionPos.blockPosAsSectionLong(baseX, baseY, baseZ); ++ DataLayer dataLayer = this.storage.updating.getUpdatingOptimized(l); ++ // Paper end + + for(Direction direction : DIRECTIONS) { +- long m = BlockPos.offset(id, direction); ++ // Paper start ++ int newX = baseX + direction.getStepX(); ++ int newY = baseY + direction.getStepY(); ++ int newZ = baseZ + direction.getStepZ(); ++ long m = BlockPos.asLong(newX, newY, newZ); + if (m != excludedId) { +- long n = SectionPos.blockToSection(m); ++ long n = SectionPos.blockPosAsSectionLong(newX, newY, newZ); ++ // Paper end + DataLayer dataLayer2; + if (l == n) { + dataLayer2 = dataLayer; + } else { +- dataLayer2 = this.storage.getDataLayer(n, true); ++ dataLayer2 = this.storage.updating.getUpdatingOptimized(n); // Paper + } + + if (dataLayer2 != null) { +- int k = this.computeLevelFromNeighbor(m, id, this.getLevel(dataLayer2, m)); ++ int k = this.computeLevelFromNeighbor(m, id, this.getLevel(dataLayer2, newX, newY, newZ)); // Paper + if (i > k) { + i = k; + } +diff --git a/src/main/java/net/minecraft/world/level/lighting/BlockLightSectionStorage.java b/src/main/java/net/minecraft/world/level/lighting/BlockLightSectionStorage.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/world/level/lighting/BlockLightSectionStorage.java ++++ b/src/main/java/net/minecraft/world/level/lighting/BlockLightSectionStorage.java +@@ -0,0 +0,0 @@ public class BlockLightSectionStorage extends LayerLightSectionStorage> 38); ++ int baseY = (int) ((blockPos << 52) >> 52); ++ int baseZ = (int) ((blockPos << 26) >> 38); ++ long l = net.minecraft.core.SectionPos.blockPosAsSectionLong(baseX, baseY, baseZ); ++ DataLayer dataLayer = this.e_visible.lookup.apply(l); ++ return dataLayer == null ? 0 : dataLayer.get(baseX & 15, baseY & 15, baseZ & 15); ++ // Paper end + } + + protected static final class BlockDataLayerStorageMap extends DataLayerStorageMap { +diff --git a/src/main/java/net/minecraft/world/level/lighting/DataLayerStorageMap.java b/src/main/java/net/minecraft/world/level/lighting/DataLayerStorageMap.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/world/level/lighting/DataLayerStorageMap.java ++++ b/src/main/java/net/minecraft/world/level/lighting/DataLayerStorageMap.java +@@ -0,0 +0,0 @@ import net.minecraft.world.level.chunk.DataLayer; + + public abstract class DataLayerStorageMap> { + private static final int CACHE_SIZE = 2; +- private final long[] lastSectionKeys = new long[2]; +- private final DataLayer[] lastSections = new DataLayer[2]; ++ // private final long[] b = new long[2]; // Paper - unused ++ private final DataLayer[] lastSections = new DataLayer[]{DataLayer.EMPTY_NIBBLE_ARRAY, DataLayer.EMPTY_NIBBLE_ARRAY}; private final DataLayer[] cache = lastSections; // Paper - OBFHELPER + private boolean cacheEnabled; + protected final com.destroystokyo.paper.util.map.QueuedChangesMapLong2Object data; // Paper - avoid copying light data + protected final boolean isVisible; // Paper - avoid copying light data +- java.util.function.Function 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 { ++ DataLayer apply(long id); ++ } ++ // Paper end + // Paper start - avoid copying light data + protected DataLayerStorageMap(com.destroystokyo.paper.util.map.QueuedChangesMapLong2Object data, boolean isVisible) { + if (isVisible) { +@@ -0,0 +0,0 @@ public abstract class DataLayerStorageMap> { + } + 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.clearCache(); + this.cacheEnabled = true; + } +@@ -0,0 +0,0 @@ public abstract class DataLayerStorageMap> { + public void copyDataLayer(long pos) { + if (this.isVisible) { throw new IllegalStateException("writing to visible data"); } // Paper - avoid copying light data + DataLayer updating = this.data.getUpdating(pos); // Paper - pool nibbles +- this.data.queueUpdate(pos, new DataLayer().markPoolSafe(updating.getCloneIfSet())); // Paper - avoid copying light data - pool safe clone ++ DataLayer nibblearray = new DataLayer().markPoolSafe(updating.getCloneIfSet()); // Paper ++ nibblearray.lightCacheKey = pos; // Paper ++ this.data.queueUpdate(pos, nibblearray); // Paper - avoid copying light data - pool safe clone + if (updating.cleaner != null) net.minecraft.server.MCUtil.scheduleTask(2, updating.cleaner, "Light Engine Release"); // Paper - delay clean incase anything holding ref was still using it + this.clearCache(); + } +@@ -0,0 +0,0 @@ public abstract class DataLayerStorageMap> { + return lookup.apply(chunkPos) != null; // Paper - avoid copying light data + } + +- @Nullable +- public final DataLayer getLayer(long chunkPos) { // Paper - final +- if (this.cacheEnabled) { +- for(int i = 0; i < 2; ++i) { +- if (chunkPos == this.lastSectionKeys[i]) { +- return this.lastSections[i]; +- } +- } +- } ++ // Paper start - less branching as we know we are using cache and updating ++ public final DataLayer getUpdatingOptimized(final long chunkPos) { // Paper - final ++ final DataLayer[] cache = this.cache; ++ if (cache[0].lightCacheKey == chunkPos) return cache[0]; ++ if (cache[1].lightCacheKey == chunkPos) return cache[1]; + +- DataLayer dataLayer = lookup.apply(chunkPos); // Paper - avoid copying light data ++ final DataLayer dataLayer = this.lookup.apply(chunkPos); // Paper - avoid copying light data + if (dataLayer == null) { + return null; + } else { +- if (this.cacheEnabled) { +- for(int j = 1; j > 0; --j) { +- this.lastSectionKeys[j] = this.lastSectionKeys[j - 1]; +- this.lastSections[j] = this.lastSections[j - 1]; +- } +- +- this.lastSectionKeys[0] = chunkPos; +- this.lastSections[0] = dataLayer; +- } +- ++ cache[1] = cache[0]; ++ cache[0] = dataLayer; + return dataLayer; + } + } ++ // Paper end ++ ++ @Nullable ++ public final DataLayer getLayer(final long chunkPos) { // Paper - final ++ // Paper start - optimize visible case or missed updating cases ++ if (this.cacheEnabled) { ++ // short circuit to optimized ++ return getUpdatingOptimized(chunkPos); ++ } ++ ++ return this.lookup.apply(chunkPos); ++ // Paper end ++ } + + @Nullable + public DataLayer removeLayer(long chunkPos) { +@@ -0,0 +0,0 @@ public abstract class DataLayerStorageMap> { + + public void setLayer(long pos, DataLayer data) { + if (this.isVisible) { throw new IllegalStateException("writing to visible data"); } // Paper - avoid copying light data ++ data.lightCacheKey = pos; // Paper + this.data.queueUpdate(pos, data); // Paper - avoid copying light data + } + + public void clearCache() { + for(int i = 0; i < 2; ++i) { +- this.lastSectionKeys[i] = Long.MAX_VALUE; +- this.lastSections[i] = null; ++ // this.b[i] = Long.MAX_VALUE; // Paper - Unused ++ this.lastSections[i] = DataLayer.EMPTY_NIBBLE_ARRAY; // Paper + } + + } +diff --git a/src/main/java/net/minecraft/world/level/lighting/LayerLightEngine.java b/src/main/java/net/minecraft/world/level/lighting/LayerLightEngine.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/world/level/lighting/LayerLightEngine.java ++++ b/src/main/java/net/minecraft/world/level/lighting/LayerLightEngine.java +@@ -0,0 +0,0 @@ import net.minecraft.world.level.ChunkPos; + import net.minecraft.world.level.LightLayer; + import net.minecraft.world.level.block.Blocks; + import net.minecraft.world.level.block.state.BlockState; ++import net.minecraft.world.level.chunk.ChunkAccess; + import net.minecraft.world.level.chunk.DataLayer; + import net.minecraft.world.level.chunk.LightChunkGetter; + import net.minecraft.world.phys.shapes.Shapes; +@@ -0,0 +0,0 @@ public abstract class LayerLightEngine, S exten + protected final BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos(); + private static final int CACHE_SIZE = 2; + private final long[] lastChunkPos = new long[2]; +- private final BlockGetter[] lastChunk = new BlockGetter[2]; ++ private final ChunkAccess[] lastChunk = new ChunkAccess[2]; // Paper + ++ // Paper start - see fully commented out method below (look for Bedrock) ++ // optimized method with less branching for when scenarios arent needed. ++ // avoid using mutable version if can ++ protected final BlockState getBlockOptimized(int x, int y, int z, MutableInt mutableint) { ++ ChunkAccess iblockaccess = this.getChunk(x >> 4, z >> 4); ++ ++ if (iblockaccess == null) { ++ mutableint.setValue(16); ++ return Blocks.BEDROCK.defaultBlockState(); ++ } else { ++ this.pos.set(x, y, z); ++ BlockState iblockdata = iblockaccess.getType(x, y, z); ++ mutableint.setValue(iblockdata.getLightBlock(this.chunkSource.getLevel(), this.pos)); ++ return iblockdata.canOcclude() && iblockdata.useShapeForLightOcclusion() ? iblockdata : Blocks.AIR.defaultBlockState(); ++ } ++ } ++ protected final BlockState getBlockOptimized(int x, int y, int z) { ++ ChunkAccess iblockaccess = this.getChunk(x >> 4, z >> 4); ++ ++ if (iblockaccess == null) { ++ return Blocks.BEDROCK.defaultBlockState(); ++ } else { ++ BlockState iblockdata = iblockaccess.getType(x, y, z); ++ return iblockdata.canOcclude() && iblockdata.useShapeForLightOcclusion() ? iblockdata : Blocks.AIR.defaultBlockState(); ++ } ++ } ++ // Paper end + public LayerLightEngine(LightChunkGetter chunkProvider, LightLayer type, S lightStorage) { + super(16, 256, 8192); + this.chunkSource = chunkProvider; +@@ -0,0 +0,0 @@ public abstract class LayerLightEngine, S exten + } + + @Nullable +- private BlockGetter getChunk(int chunkX, int chunkZ) { ++ private ChunkAccess getChunk(int chunkX, int chunkZ) { // Paper + long l = ChunkPos.asLong(chunkX, chunkZ); + + for(int i = 0; i < 2; ++i) { +@@ -0,0 +0,0 @@ public abstract class LayerLightEngine, S exten + } + } + +- BlockGetter blockGetter = this.chunkSource.getChunkForLighting(chunkX, chunkZ); ++ ChunkAccess blockGetter = (ChunkAccess) this.chunkSource.getChunkForLighting(chunkX, chunkZ); // Paper + + for(int j = 1; j > 0; --j) { + this.lastChunkPos[j] = this.lastChunkPos[j - 1]; +@@ -0,0 +0,0 @@ public abstract class LayerLightEngine, S exten + Arrays.fill(this.lastChunk, (Object)null); + } + +- protected BlockState getStateAndOpacity(long pos, @Nullable MutableInt mutableInt) { +- if (pos == Long.MAX_VALUE) { +- if (mutableInt != null) { +- mutableInt.setValue(0); +- } +- +- return Blocks.AIR.defaultBlockState(); +- } else { +- int i = SectionPos.blockToSectionCoord(BlockPos.getX(pos)); +- int j = SectionPos.blockToSectionCoord(BlockPos.getZ(pos)); +- BlockGetter blockGetter = this.getChunk(i, j); +- if (blockGetter == null) { +- if (mutableInt != null) { +- mutableInt.setValue(16); +- } +- +- return Blocks.BEDROCK.defaultBlockState(); +- } else { +- this.pos.set(pos); +- BlockState blockState = blockGetter.getBlockState(this.pos); +- boolean bl = blockState.canOcclude() && blockState.useShapeForLightOcclusion(); +- if (mutableInt != null) { +- mutableInt.setValue(blockState.getLightBlock(this.chunkSource.getLevel(), this.pos)); +- } +- +- return bl ? blockState : Blocks.AIR.defaultBlockState(); +- } +- } +- } ++ // Paper start - comment out, see getBlockOptimized ++ // protected BlockState getStateAndOpacity(long pos, @Nullable MutableInt mutableInt) { ++ // if (pos == Long.MAX_VALUE) { ++ // if (mutableInt != null) { ++ // mutableInt.setValue(0); ++ // } ++ // ++ // return Blocks.AIR.defaultBlockState(); ++ // } else { ++ // int i = SectionPos.blockToSectionCoord(BlockPos.getX(pos)); ++ // int j = SectionPos.blockToSectionCoord(BlockPos.getZ(pos)); ++ // BlockGetter blockGetter = this.getChunk(i, j); ++ // if (blockGetter == null) { ++ // if (mutableInt != null) { ++ // mutableInt.setValue(16); ++ // } ++ // ++ // return Blocks.BEDROCK.defaultBlockState(); ++ // } else { ++ // this.pos.set(pos); ++ // BlockState blockState = blockGetter.getBlockState(this.pos); ++ // boolean bl = blockState.canOcclude() && blockState.useShapeForLightOcclusion(); ++ // if (mutableInt != null) { ++ // mutableInt.setValue(blockState.getLightBlock(this.chunkSource.getLevel(), this.pos)); ++ // } ++ // ++ // return bl ? blockState : Blocks.AIR.defaultBlockState(); ++ // } ++ // } ++ // } ++ // Paper end + + protected VoxelShape getShape(BlockState world, long pos, Direction facing) { + return world.canOcclude() ? world.getFaceOcclusionShape(this.chunkSource.getLevel(), this.pos.set(pos), facing) : Shapes.empty(); +@@ -0,0 +0,0 @@ public abstract class LayerLightEngine, S exten + return id == Long.MAX_VALUE ? 0 : 15 - this.storage.getStoredLevel(id); + } + ++ protected int getLevel(DataLayer section, int x, int y, int z) { return 15 - section.get(x & 15, y & 15, z & 15); } // Paper - x/y/z version of below + protected int getLevel(DataLayer section, long blockPos) { +- return 15 - section.get(SectionPos.sectionRelative(BlockPos.getX(blockPos)), SectionPos.sectionRelative(BlockPos.getY(blockPos)), SectionPos.sectionRelative(BlockPos.getZ(blockPos))); ++ return 15 - section.get((int) (blockPos >> 38) & 15, (int) ((blockPos << 52) >> 52) & 15, (int) ((blockPos << 26) >> 38) & 15); // Paper + } + + @Override +diff --git a/src/main/java/net/minecraft/world/level/lighting/LayerLightSectionStorage.java b/src/main/java/net/minecraft/world/level/lighting/LayerLightSectionStorage.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/world/level/lighting/LayerLightSectionStorage.java ++++ b/src/main/java/net/minecraft/world/level/lighting/LayerLightSectionStorage.java +@@ -0,0 +0,0 @@ public abstract class LayerLightSectionStorage> + protected final LongSet toMarkNoData = new LongOpenHashSet(); + protected final LongSet toMarkData = new LongOpenHashSet(); + protected volatile M e_visible; protected final Object visibleUpdateLock = new Object(); // Paper - diff on change, should be "visible" - force compile fail on usage change +- protected final M updatingSectionData; ++ protected final M updatingSectionData; protected final M updating; // Paper - diff on change, should be "updating" + protected final LongSet changedSections = new LongOpenHashSet(); +- protected final LongSet sectionsAffectedByLightUpdates = new LongOpenHashSet(); ++ protected final LongSet sectionsAffectedByLightUpdates = new LongOpenHashSet(); LongSet dirty = sectionsAffectedByLightUpdates; // Paper - OBFHELPER + protected final Long2ObjectMap queuedSections = Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap<>()); + private final LongSet untrustedSections = new LongOpenHashSet(); + private final LongSet columnsToRetainQueuedDataFor = new LongOpenHashSet(); +@@ -0,0 +0,0 @@ public abstract class LayerLightSectionStorage> + protected volatile boolean hasToRemove; + + protected LayerLightSectionStorage(LightLayer lightType, LightChunkGetter chunkProvider, M lightData) { +- super(3, 16, 256); ++ super(3, 256, 256); // Paper - bump expected size of level sets to improve collisions and reduce rehashing (seen a lot of it) + this.layer = lightType; + this.chunkSource = chunkProvider; +- this.updatingSectionData = lightData; ++ this.updatingSectionData = lightData; updating = lightData; // Paper + this.e_visible = lightData.copy(); // Paper - avoid copying light dat + this.e_visible.disableCache(); // Paper - avoid copying light dat + } + +- protected boolean storingLightForSection(long sectionPos) { +- return this.getDataLayer(sectionPos, true) != null; ++ protected final boolean storingLightForSection(long sectionPos) { // Paper - final to help inlining ++ return this.updating.getUpdatingOptimized(sectionPos) != null; // Paper - inline to avoid branching + } + + @Nullable + protected DataLayer getDataLayer(long sectionPos, boolean cached) { + // Paper start - avoid copying light data + if (cached) { +- return this.getDataLayer(this.updatingSectionData, sectionPos); ++ return this.updating.getUpdatingOptimized(sectionPos); + } else { + synchronized (this.visibleUpdateLock) { +- return this.getDataLayer(this.e_visible, sectionPos); ++ return this.e_visible.lookup.apply(sectionPos); + } + } + // Paper end - avoid copying light data + } + + @Nullable +- protected DataLayer getDataLayer(M storage, long sectionPos) { ++ protected final DataLayer getDataLayer(M storage, long sectionPos) { // Paper + return storage.getLayer(sectionPos); + } + +@@ -0,0 +0,0 @@ public abstract class LayerLightSectionStorage> + protected abstract int getLightValue(long blockPos); + + protected int getStoredLevel(long blockPos) { +- long l = SectionPos.blockToSection(blockPos); +- DataLayer dataLayer = this.getDataLayer(l, true); +- return dataLayer.get(SectionPos.sectionRelative(BlockPos.getX(blockPos)), SectionPos.sectionRelative(BlockPos.getY(blockPos)), SectionPos.sectionRelative(BlockPos.getZ(blockPos))); ++ // Paper start - reuse and inline math, use Optimized Updating path ++ final int x = (int) (blockPos >> 38); ++ final int y = (int) ((blockPos << 52) >> 52); ++ final int z = (int) ((blockPos << 26) >> 38); ++ long j = SectionPos.blockPosAsSectionLong(x, y, z); ++ DataLayer 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(j); ++ } ++ if (nibblearray == null) { ++ System.err.println("Null nibble, preventing crash " + BlockPos.of(blockPos)); ++ return 0; ++ } ++ ++ return nibblearray.get(x & 15, y & 15, z & 15); // Paper - inline operations ++ // Paper end + } + + protected void setStoredLevel(long blockPos, int value) { +- long l = SectionPos.blockToSection(blockPos); ++ // Paper start - cache part of the math done in loop below ++ int x = (int) (blockPos >> 38); ++ int y = (int) ((blockPos << 52) >> 52); ++ int z = (int) ((blockPos << 26) >> 38); ++ long l = SectionPos.blockPosAsSectionLong(x, y, z); ++ // Paper end + if (this.changedSections.add(l)) { + this.updatingSectionData.copyDataLayer(l); + } + + DataLayer dataLayer = this.getDataLayer(l, true); +- dataLayer.set(SectionPos.sectionRelative(BlockPos.getX(blockPos)), SectionPos.sectionRelative(BlockPos.getY(blockPos)), SectionPos.sectionRelative(BlockPos.getZ(blockPos)), value); +- +- for(int i = -1; i <= 1; ++i) { +- for(int j = -1; j <= 1; ++j) { +- for(int k = -1; k <= 1; ++k) { +- this.sectionsAffectedByLightUpdates.add(SectionPos.blockToSection(BlockPos.offset(blockPos, j, k, i))); ++ dataLayer.set(x & 15, y & 15, z & 15, value); // Paper - use already calculated x/y/z ++ ++ // Paper start - credit to JellySquid for a major optimization here: ++ /* ++ * An extremely important optimization is made here in regards to adding items to the pending notification set. The ++ * original implementation attempts to add the coordinate of every chunk which contains a neighboring block position ++ * even though a huge number of loop iterations will simply map to block positions within the same updating chunk. ++ * ++ * Our implementation here avoids this by pre-calculating the min/max chunk coordinates so we can iterate over only ++ * the relevant chunk positions once. This reduces what would always be 27 iterations to just 1-8 iterations. ++ * ++ * @reason Use faster implementation ++ * @author JellySquid ++ */ ++ for (int z2 = (z - 1) >> 4; z2 <= (z + 1) >> 4; ++z2) { ++ for (int x2 = (x - 1) >> 4; x2 <= (x + 1) >> 4; ++x2) { ++ for (int y2 = (y - 1) >> 4; y2 <= (y + 1) >> 4; ++y2) { ++ this.dirty.add(SectionPos.asLong(x2, y2, z2)); ++ // Paper end + } + } + } +@@ -0,0 +0,0 @@ public abstract class LayerLightSectionStorage> + } + + if (i >= 2 && level != 2) { +- if (this.toRemove.contains(id)) { +- this.toRemove.remove(id); +- } else { ++ if (!this.toRemove.remove(id)) { // Paper - remove useless contains - credit to JellySquid ++ //this.toRemove.remove(id); // Paper ++ //} else { // Paper + this.updatingSectionData.setLayer(id, this.createDataLayer(id)); + this.changedSections.add(id); + this.onNodeAdded(id); + +- for(int j = -1; j <= 1; ++j) { +- for(int k = -1; k <= 1; ++k) { +- for(int l = -1; l <= 1; ++l) { +- this.sectionsAffectedByLightUpdates.add(SectionPos.blockToSection(BlockPos.offset(id, k, l, j))); ++ // Paper start - reuse x/y/z and only notify valid chunks - Credit to JellySquid (See above method for notes) ++ int x = (int) (id >> 38); ++ int y = (int) ((id << 52) >> 52); ++ int z = (int) ((id << 26) >> 38); ++ ++ for (int z2 = (z - 1) >> 4; z2 <= (z + 1) >> 4; ++z2) { ++ for (int x2 = (x - 1) >> 4; x2 <= (x + 1) >> 4; ++x2) { ++ for (int y2 = (y - 1) >> 4; y2 <= (y + 1) >> 4; ++y2) { ++ this.dirty.add(SectionPos.asLong(x2, y2, z2)); ++ // Paper end + } + } + } +@@ -0,0 +0,0 @@ public abstract class LayerLightSectionStorage> + return SectionPos.blockToSection(mx) == sectionPos; + }); + } else { +- int i = SectionPos.sectionToBlockCoord(SectionPos.x(sectionPos)); +- int j = SectionPos.sectionToBlockCoord(SectionPos.y(sectionPos)); +- int k = SectionPos.sectionToBlockCoord(SectionPos.z(sectionPos)); ++ int i = (int) (sectionPos >> 42) << 4; // Paper - inline ++ int j = (int) (sectionPos << 44 >> 44) << 4; // Paper - inline ++ int k = (int) (sectionPos << 22 >> 42) << 4; // Paper - inline + + for(int l = 0; l < 16; ++l) { + for(int m = 0; m < 16; ++m) { +@@ -0,0 +0,0 @@ public abstract class LayerLightSectionStorage> + this.toRemove.clear(); + this.hasToRemove = false; + ++ DataLayer test = null; // Paper + for(Entry entry : this.queuedSections.long2ObjectEntrySet()) { + long n = entry.getLongKey(); +- if (this.storingLightForSection(n)) { ++ if ((test = this.updating.getUpdatingOptimized(n)) != null) { // Paper - dont look up data layer twice + DataLayer dataLayer3 = entry.getValue(); +- if (this.updatingSectionData.getLayer(n) != dataLayer3) { ++ if (test != dataLayer3) { // Paper + this.clearQueuedSectionBlocks(lightProvider, n); + this.updatingSectionData.setLayer(n, dataLayer3); + this.changedSections.add(n); +@@ -0,0 +0,0 @@ public abstract class LayerLightSectionStorage> + + private void checkEdgesForSection(LayerLightEngine lightProvider, long sectionPos) { + if (this.storingLightForSection(sectionPos)) { +- int i = SectionPos.sectionToBlockCoord(SectionPos.x(sectionPos)); +- int j = SectionPos.sectionToBlockCoord(SectionPos.y(sectionPos)); +- int k = SectionPos.sectionToBlockCoord(SectionPos.z(sectionPos)); ++ // Paper start ++ int secX = (int) (sectionPos >> 42); ++ int secY = (int) (sectionPos << 44 >> 44); ++ int secZ = (int) (sectionPos << 22 >> 42); ++ int i = secX << 4; // baseX ++ int j = secY << 4; // baseY ++ int k = secZ << 4; // baseZ ++ // Paper end + + for(Direction direction : DIRECTIONS) { +- long l = SectionPos.offset(sectionPos, direction); ++ long l = SectionPos.getAdjacentFromSectionPos(secX, secY, secZ, direction); + if (!this.queuedSections.containsKey(l) && this.storingLightForSection(l)) { + for(int m = 0; m < 16; ++m) { + for(int n = 0; n < 16; ++n) { +diff --git a/src/main/java/net/minecraft/world/level/lighting/SkyLightEngine.java b/src/main/java/net/minecraft/world/level/lighting/SkyLightEngine.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/world/level/lighting/SkyLightEngine.java ++++ b/src/main/java/net/minecraft/world/level/lighting/SkyLightEngine.java +@@ -0,0 +0,0 @@ import net.minecraft.core.BlockPos; + import net.minecraft.core.Direction; + import net.minecraft.core.SectionPos; + import net.minecraft.world.level.LightLayer; ++import net.minecraft.world.level.block.Blocks; + import net.minecraft.world.level.block.state.BlockState; + import net.minecraft.world.level.chunk.DataLayer; + import net.minecraft.world.level.chunk.LightChunkGetter; +@@ -0,0 +0,0 @@ public final class SkyLightEngine extends LayerLightEngine= 15) { ++ // Paper start - use x/y/z and optimized block lookup ++ int jx = (int) (targetId >> 38); ++ int jy = (int) ((targetId << 52) >> 52); ++ int jz = (int) ((targetId << 26) >> 38); ++ BlockState blockState = this.getBlockOptimized(jx, jy, jz, mutableInt); ++ int blockedLight = mutableInt.getValue(); ++ if (blockedLight >= 15) { ++ // Paper end + return 15; + } else { +- int i = BlockPos.getX(sourceId); +- int j = BlockPos.getY(sourceId); +- int k = BlockPos.getZ(sourceId); +- int l = BlockPos.getX(targetId); +- int m = BlockPos.getY(targetId); +- int n = BlockPos.getZ(targetId); +- int o = Integer.signum(l - i); +- int p = Integer.signum(m - j); +- int q = Integer.signum(n - k); ++ // Paper start - inline math ++ int i = (int) (sourceId >> 38); ++ int j = (int) ((sourceId << 52) >> 52); ++ int k = (int) ((sourceId << 26) >> 38); ++ boolean bl = i == jx && k == jz; ++ int o = Integer.signum(jx - i); ++ int p = Integer.signum(jy - j); ++ int q = Integer.signum(jz - k); ++ // Paper end + Direction direction = Direction.fromNormal(o, p, q); + if (direction == null) { + throw new IllegalStateException(String.format("Light was spread in illegal direction %d, %d, %d", o, p, q)); + } else { +- BlockState blockState2 = this.getStateAndOpacity(sourceId, (MutableInt)null); ++ BlockState blockState2 = sourceId == Long.MAX_VALUE ? net.minecraft.world.level.block.Blocks.AIR.defaultBlockState() : this.getBlockOptimized(i, j, k); // Paper + VoxelShape voxelShape = this.getShape(blockState2, sourceId, direction); + VoxelShape voxelShape2 = this.getShape(blockState, targetId, direction.getOpposite()); + if (Shapes.faceShapeOccludes(voxelShape, voxelShape2)) { + return 15; + } else { +- boolean bl = i == l && k == n; +- boolean bl2 = bl && j > m; +- return bl2 && level == 0 && mutableInt.getValue() == 0 ? 0 : level + Math.max(1, mutableInt.getValue()); ++ boolean bl2 = bl && j > jy; // Paper rename vars to jy from m ++ return bl2 && level == 0 && blockedLight == 0 ? 0 : level + Math.max(1, blockedLight); // Paper + } + } + } +@@ -0,0 +0,0 @@ public final class SkyLightEngine extends LayerLightEngine> 38); ++ int baseY = (int) ((id << 52) >> 52); ++ int baseZ = (int) ((id << 26) >> 38); ++ long l = SectionPos.blockPosAsSectionLong(baseX, baseY, baseZ); ++ int j = baseY & 15; ++ int k = baseY >> 4; ++ // Paper end + int m; + if (j != 0) { + m = 0; +@@ -0,0 +0,0 @@ public final class SkyLightEngine extends LayerLightEngine> 38); ++ int baseY = (int) ((id << 52) >> 52); ++ int baseZ = (int) ((id << 26) >> 38); ++ long l = SectionPos.blockPosAsSectionLong(baseX, baseY, baseZ); ++ DataLayer dataLayer = this.storage.updating.getUpdatingOptimized(l); ++ // Paper end + + for(Direction direction : DIRECTIONS) { +- long m = BlockPos.offset(id, direction); ++ // Paper start ++ int newX = baseX + direction.getStepX(); ++ int newY = baseY + direction.getStepY(); ++ int newZ = baseZ + direction.getStepZ(); ++ long m = BlockPos.asLong(newX, newY, newZ); + if (m != excludedId) { +- long n = SectionPos.blockToSection(m); ++ long n = SectionPos.blockPosAsSectionLong(newX, newY, newZ); ++ // Paper end + DataLayer dataLayer2; + if (l == n) { + dataLayer2 = dataLayer; + } else { +- dataLayer2 = this.storage.getDataLayer(n, true); ++ dataLayer2 = this.storage.updating.getUpdatingOptimized(n); // Paper + } + + int j; + if (dataLayer2 != null) { +- j = this.getLevel(dataLayer2, m); ++ j = this.getLevel(dataLayer2, newX, newY, newZ); // Paper + } else { + if (direction == Direction.DOWN) { + continue; +diff --git a/src/main/java/net/minecraft/world/level/lighting/SkyLightSectionStorage.java b/src/main/java/net/minecraft/world/level/lighting/SkyLightSectionStorage.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/world/level/lighting/SkyLightSectionStorage.java ++++ b/src/main/java/net/minecraft/world/level/lighting/SkyLightSectionStorage.java +@@ -0,0 +0,0 @@ public class SkyLightSectionStorage extends LayerLightSectionStorage> 38); ++ int baseY = (int) ((l << 52) >> 52); ++ int baseZ = (int) ((l << 26) >> 38); ++ long m = SectionPos.blockPosAsSectionLong(baseX, baseY, baseZ); ++ // Paper end + int i = SectionPos.y(m); + synchronized (this.visibleUpdateLock) { // Paper - avoid copying light data + SkyLightSectionStorage.SkyDataLayerStorageMap skyDataLayerStorageMap = (SkyLightSectionStorage.SkyDataLayerStorageMap) this.e_visible; // Paper - avoid copying light data - must be after lock acquire +@@ -0,0 +0,0 @@ public class SkyLightSectionStorage extends LayerLightSectionStorage> 52) & 15, (int) baseZ & 15); + } else { + return bl && !this.lightOnInSection(m) ? 0 : 15; + } +@@ -0,0 +0,0 @@ public class SkyLightSectionStorage extends LayerLightSectionStorage> 42) << 4; // Paper ++ int baseY = (int) (l << 44 >> 44) << 4; // Paper ++ int baseZ = (int) (l << 22 >> 42) << 4; // Paper + int i = this.getLevel(l); + if (i != 2 && !this.sectionsToRemoveSourcesFrom.contains(l) && this.sectionsWithSources.add(l)) { + if (i == 1) { +@@ -0,0 +0,0 @@ public class SkyLightSectionStorage extends LayerLightSectionStorage> 42) << 4; // Paper ++ int baseY = (int) (af << 44 >> 44) << 4; // Paper ++ int baseZ = (int) (af << 22 >> 42) << 4; // Paper + if (this.sectionsWithSources.remove(af) && this.storingLightForSection(af)) { + for(int ag = 0; ag < 16; ++ag) { + for(int ah = 0; ah < 16; ++ah) { +- long ai = BlockPos.asLong(SectionPos.sectionToBlockCoord(SectionPos.x(af), ag), SectionPos.sectionToBlockCoord(SectionPos.y(af), 15), SectionPos.sectionToBlockCoord(SectionPos.z(af), ah)); ++ long ai = BlockPos.asLong(baseX + ag, baseY + 16 - 1, baseZ + ah); // Paper + lightProvider.checkEdge(Long.MAX_VALUE, ai, 15, false); + } + }