From d3a6b1376aa6197a51adbfee328f3a4deb1efbf9 Mon Sep 17 00:00:00 2001 From: Spottedleaf Date: Mon, 11 Sep 2023 07:15:18 -0700 Subject: [PATCH] Optimise multiple block updates occurring in the same chunk We can avoid multiple ticket additions for the same ChunkTasks instance. This will help in situations where significant number of block updates occur for the same chunk in the same tick, such as water draining. --- patches/server/Rewrite-chunk-system.patch | 76 +++++++++++++++++------ patches/server/Starlight.patch | 55 +++++++++------- 2 files changed, 88 insertions(+), 43 deletions(-) diff --git a/patches/server/Rewrite-chunk-system.patch b/patches/server/Rewrite-chunk-system.patch index dd7931972d..f44dbd1b53 100644 --- a/patches/server/Rewrite-chunk-system.patch +++ b/patches/server/Rewrite-chunk-system.patch @@ -845,6 +845,24 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 if (this.cachedBlockPropagators == null) { return; } +@@ -0,0 +0,0 @@ public final class StarLightInterface { + } + } + +- public LightQueue.ChunkTasks blockChange(final BlockPos pos) { ++ public io.papermc.paper.chunk.system.light.LightQueue.ChunkTasks blockChange(final BlockPos pos) { // Paper - rewrite chunk system + if (this.world == null || pos.getY() < WorldUtil.getMinBlockY(this.world) || pos.getY() > WorldUtil.getMaxBlockY(this.world)) { // empty world + return null; + } +@@ -0,0 +0,0 @@ public final class StarLightInterface { + return this.lightQueue.queueBlockChange(pos); + } + +- public LightQueue.ChunkTasks sectionChange(final SectionPos pos, final boolean newEmptyValue) { ++ public io.papermc.paper.chunk.system.light.LightQueue.ChunkTasks sectionChange(final SectionPos pos, final boolean newEmptyValue) { // Paper - rewrite chunk system + if (this.world == null) { // empty world + return null; + } @@ -0,0 +0,0 @@ public final class StarLightInterface { } @@ -905,7 +923,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + throw new UnsupportedOperationException("No longer implemented, task draining is now performed by the light thread"); // Paper - replace light queue } - protected static final class LightQueue { + public static final class LightQueue { diff --git a/src/main/java/co/aikar/timings/TimingsExport.java b/src/main/java/co/aikar/timings/TimingsExport.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/co/aikar/timings/TimingsExport.java @@ -6036,7 +6054,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + } + -+ public CompletableFuture queueBlockChange(final BlockPos pos) { ++ public ChunkTasks queueBlockChange(final BlockPos pos) { + final ChunkTasks tasks; + synchronized (this) { + tasks = this.chunkTasks.computeIfAbsent(CoordinateUtils.getChunkKey(pos), (final long keyInMap) -> { @@ -6047,10 +6065,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + + tasks.schedule(); + -+ return tasks.onComplete; ++ return tasks; + } + -+ public CompletableFuture queueSectionChange(final SectionPos pos, final boolean newEmptyValue) { ++ public ChunkTasks queueSectionChange(final SectionPos pos, final boolean newEmptyValue) { + final ChunkTasks tasks; + synchronized (this) { + tasks = this.chunkTasks.computeIfAbsent(CoordinateUtils.getChunkKey(pos), (final long keyInMap) -> { @@ -6065,10 +6083,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + + tasks.schedule(); + -+ return tasks.onComplete; ++ return tasks; + } + -+ public CompletableFuture queueChunkLightTask(final ChunkPos pos, final BooleanSupplier lightTask, final PrioritisedExecutor.Priority priority) { ++ public ChunkTasks queueChunkLightTask(final ChunkPos pos, final BooleanSupplier lightTask, final PrioritisedExecutor.Priority priority) { + final ChunkTasks tasks; + synchronized (this) { + tasks = this.chunkTasks.computeIfAbsent(CoordinateUtils.getChunkKey(pos), (final long keyInMap) -> { @@ -6082,10 +6100,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + + tasks.schedule(); + -+ return tasks.onComplete; ++ return tasks; + } + -+ public CompletableFuture queueChunkSkylightEdgeCheck(final SectionPos pos, final ShortCollection sections) { ++ public ChunkTasks queueChunkSkylightEdgeCheck(final SectionPos pos, final ShortCollection sections) { + final ChunkTasks tasks; + synchronized (this) { + tasks = this.chunkTasks.computeIfAbsent(CoordinateUtils.getChunkKey(pos), (final long keyInMap) -> { @@ -6101,10 +6119,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + + tasks.schedule(); + -+ return tasks.onComplete; ++ return tasks; + } + -+ public CompletableFuture queueChunkBlocklightEdgeCheck(final SectionPos pos, final ShortCollection sections) { ++ public ChunkTasks queueChunkBlocklightEdgeCheck(final SectionPos pos, final ShortCollection sections) { + final ChunkTasks tasks; + + synchronized (this) { @@ -6121,7 +6139,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + + tasks.schedule(); + -+ return tasks.onComplete; ++ return tasks; + } + + public void removeChunk(final ChunkPos pos) { @@ -6134,20 +6152,20 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + } + -+ protected static final class ChunkTasks implements Runnable { -+ -+ final Set changedPositions = new HashSet<>(); -+ Boolean[] changedSectionSet; -+ ShortOpenHashSet queuedEdgeChecksSky; -+ ShortOpenHashSet queuedEdgeChecksBlock; -+ List lightTasks; -+ -+ final CompletableFuture onComplete = new CompletableFuture<>(); ++ public static final class ChunkTasks implements Runnable { + ++ public final CompletableFuture onComplete = new CompletableFuture<>(); ++ public boolean isTicketAdded; + public final long chunkCoordinate; ++ + private final StarLightInterface lightEngine; + private final LightQueue queue; + private final PrioritisedExecutor.PrioritisedTask task; ++ private final Set changedPositions = new HashSet<>(); ++ private Boolean[] changedSectionSet; ++ private ShortOpenHashSet queuedEdgeChecksSky; ++ private ShortOpenHashSet queuedEdgeChecksBlock; ++ private List lightTasks; + + public ChunkTasks(final long chunkCoordinate, final StarLightInterface lightEngine, final LightQueue queue) { + this(chunkCoordinate, lightEngine, queue, PrioritisedExecutor.Priority.NORMAL); @@ -20276,6 +20294,24 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 this.theLightEngine.relightChunks(chunks, (ChunkPos chunkPos) -> { chunkLightCallback.accept(chunkPos); ((java.util.concurrent.Executor)((ServerLevel)this.theLightEngine.getWorld()).getChunkSource().mainThreadProcessor).execute(() -> { +@@ -0,0 +0,0 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl + private final Long2IntOpenHashMap chunksBeingWorkedOn = new Long2IntOpenHashMap(); + + private void queueTaskForSection(final int chunkX, final int chunkY, final int chunkZ, +- final Supplier runnable) { ++ final Supplier runnable) { // Paper - rewrite chunk system + final ServerLevel world = (ServerLevel)this.theLightEngine.getWorld(); + + final ChunkAccess center = this.theLightEngine.getAnyChunkNow(chunkX, chunkZ); +@@ -0,0 +0,0 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl + + final long key = CoordinateUtils.getChunkKey(chunkX, chunkZ); + +- final ca.spottedleaf.starlight.common.light.StarLightInterface.LightQueue.ChunkTasks updateFuture = runnable.get(); ++ final io.papermc.paper.chunk.system.light.LightQueue.ChunkTasks updateFuture = runnable.get(); // Paper - rewrite chunk system + + if (updateFuture == null) { + // not scheduled @@ -0,0 +0,0 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl } diff --git a/patches/server/Starlight.patch b/patches/server/Starlight.patch index 15bd634d36..7bc4fb686c 100644 --- a/patches/server/Starlight.patch +++ b/patches/server/Starlight.patch @@ -3233,7 +3233,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + return this.hasBlockLight; + } + -+ protected int getSkyLightValue(final BlockPos blockPos, final ChunkAccess chunk) { ++ public int getSkyLightValue(final BlockPos blockPos, final ChunkAccess chunk) { + if (!this.hasSkyLight) { + return 0; + } @@ -3303,7 +3303,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + return 15; + } + -+ protected int getBlockLightValue(final BlockPos blockPos, final ChunkAccess chunk) { ++ public int getBlockLightValue(final BlockPos blockPos, final ChunkAccess chunk) { + if (!this.hasBlockLight) { + return 0; + } @@ -3422,7 +3422,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + } + -+ public CompletableFuture blockChange(final BlockPos pos) { ++ public LightQueue.ChunkTasks blockChange(final BlockPos pos) { + if (this.world == null || pos.getY() < WorldUtil.getMinBlockY(this.world) || pos.getY() > WorldUtil.getMaxBlockY(this.world)) { // empty world + return null; + } @@ -3430,7 +3430,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + return this.lightQueue.queueBlockChange(pos); + } + -+ public CompletableFuture sectionChange(final SectionPos pos, final boolean newEmptyValue) { ++ public LightQueue.ChunkTasks sectionChange(final SectionPos pos, final boolean newEmptyValue) { + if (this.world == null) { // empty world + return null; + } @@ -3613,7 +3613,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + } + -+ protected static final class LightQueue { ++ public static final class LightQueue { + + protected final Long2ObjectLinkedOpenHashMap chunkTasks = new Long2ObjectLinkedOpenHashMap<>(); + protected final StarLightInterface manager; @@ -3626,13 +3626,13 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + return this.chunkTasks.isEmpty(); + } + -+ public synchronized CompletableFuture queueBlockChange(final BlockPos pos) { ++ public synchronized LightQueue.ChunkTasks queueBlockChange(final BlockPos pos) { + final ChunkTasks tasks = this.chunkTasks.computeIfAbsent(CoordinateUtils.getChunkKey(pos), ChunkTasks::new); + tasks.changedPositions.add(pos.immutable()); -+ return tasks.onComplete; ++ return tasks; + } + -+ public synchronized CompletableFuture queueSectionChange(final SectionPos pos, final boolean newEmptyValue) { ++ public synchronized LightQueue.ChunkTasks queueSectionChange(final SectionPos pos, final boolean newEmptyValue) { + final ChunkTasks tasks = this.chunkTasks.computeIfAbsent(CoordinateUtils.getChunkKey(pos), ChunkTasks::new); + + if (tasks.changedSectionSet == null) { @@ -3640,20 +3640,20 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + tasks.changedSectionSet[pos.getY() - this.manager.minSection] = Boolean.valueOf(newEmptyValue); + -+ return tasks.onComplete; ++ return tasks; + } + -+ public synchronized CompletableFuture queueChunkLighting(final ChunkPos pos, final Runnable lightTask) { ++ public synchronized LightQueue.ChunkTasks queueChunkLighting(final ChunkPos pos, final Runnable lightTask) { + final ChunkTasks tasks = this.chunkTasks.computeIfAbsent(CoordinateUtils.getChunkKey(pos), ChunkTasks::new); + if (tasks.lightTasks == null) { + tasks.lightTasks = new ArrayList<>(); + } + tasks.lightTasks.add(lightTask); + -+ return tasks.onComplete; ++ return tasks; + } + -+ public synchronized CompletableFuture queueChunkSkylightEdgeCheck(final SectionPos pos, final ShortCollection sections) { ++ public synchronized LightQueue.ChunkTasks queueChunkSkylightEdgeCheck(final SectionPos pos, final ShortCollection sections) { + final ChunkTasks tasks = this.chunkTasks.computeIfAbsent(CoordinateUtils.getChunkKey(pos), ChunkTasks::new); + + ShortOpenHashSet queuedEdges = tasks.queuedEdgeChecksSky; @@ -3662,10 +3662,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + queuedEdges.addAll(sections); + -+ return tasks.onComplete; ++ return tasks; + } + -+ public synchronized CompletableFuture queueChunkBlocklightEdgeCheck(final SectionPos pos, final ShortCollection sections) { ++ public synchronized LightQueue.ChunkTasks queueChunkBlocklightEdgeCheck(final SectionPos pos, final ShortCollection sections) { + final ChunkTasks tasks = this.chunkTasks.computeIfAbsent(CoordinateUtils.getChunkKey(pos), ChunkTasks::new); + + ShortOpenHashSet queuedEdges = tasks.queuedEdgeChecksBlock; @@ -3674,7 +3674,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + queuedEdges.addAll(sections); + -+ return tasks.onComplete; ++ return tasks; + } + + public void removeChunk(final ChunkPos pos) { @@ -3694,7 +3694,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + return this.chunkTasks.removeFirst(); + } + -+ protected static final class ChunkTasks { ++ public static final class ChunkTasks { + + public final Set changedPositions = new ObjectOpenHashSet<>(); + public Boolean[] changedSectionSet; @@ -3702,6 +3702,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + public ShortOpenHashSet queuedEdgeChecksBlock; + public List lightTasks; + ++ public boolean isTicketAdded = false; + public final CompletableFuture onComplete = new CompletableFuture<>(); + + public final long chunkCoordinate; @@ -4614,7 +4615,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + + private final Long2IntOpenHashMap chunksBeingWorkedOn = new Long2IntOpenHashMap(); + -+ private void queueTaskForSection(final int chunkX, final int chunkY, final int chunkZ, final Supplier> runnable) { ++ private void queueTaskForSection(final int chunkX, final int chunkY, final int chunkZ, ++ final Supplier runnable) { + final ServerLevel world = (ServerLevel)this.theLightEngine.getWorld(); + + final ChunkAccess center = this.theLightEngine.getAnyChunkNow(chunkX, chunkZ); @@ -4641,20 +4643,26 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + + final long key = CoordinateUtils.getChunkKey(chunkX, chunkZ); + -+ final CompletableFuture updateFuture = runnable.get(); ++ final ca.spottedleaf.starlight.common.light.StarLightInterface.LightQueue.ChunkTasks updateFuture = runnable.get(); + + if (updateFuture == null) { + // not scheduled + return; + } + ++ if (updateFuture.isTicketAdded) { ++ // ticket already added ++ return; ++ } ++ updateFuture.isTicketAdded = true; ++ + final int references = this.chunksBeingWorkedOn.addTo(key, 1); + if (references == 0) { + final ChunkPos pos = new ChunkPos(chunkX, chunkZ); + world.getChunkSource().addRegionTicket(ca.spottedleaf.starlight.common.light.StarLightInterface.CHUNK_WORK_TICKET, pos, 0, pos); + } + -+ updateFuture.thenAcceptAsync((final Void ignore) -> { ++ updateFuture.onComplete.thenAcceptAsync((final Void ignore) -> { + final int newReferences = this.chunksBeingWorkedOn.get(key); + if (newReferences == 1) { + this.chunksBeingWorkedOn.remove(key); @@ -4668,8 +4676,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + LOGGER.error("Failed to remove ticket level for post chunk task " + new ChunkPos(chunkX, chunkZ), thr); + } + }); -+ } -+ + } + + @Override + public boolean hasLightWork() { + // route to new light engine @@ -4689,11 +4697,12 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + if (sky == 15) return 15; + final int block = this.theLightEngine.getBlockReader().getLightValue(pos); + return Math.max(sky, block); - } ++ } + // Paper end - replace light engine imp - ++ @Override public void close() { + } @@ -0,0 +0,0 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl @Override