From 1e44e31be7879e93bdadd32b9bb491c3f1161f40 Mon Sep 17 00:00:00 2001 From: Spottedleaf Date: Mon, 24 Oct 2022 19:48:19 -0700 Subject: [PATCH] Add dirty flag to chunk tick lists For whatever reason, vanilla does not mark the chunk as dirty when changing its tick lists. We also have it return dirty if the time since the last save has changed, since it would affect the tick offsets in the ticklist. --- patches/server/Rewrite-chunk-system.patch | 114 +++++++++++++++++++++- 1 file changed, 109 insertions(+), 5 deletions(-) diff --git a/patches/server/Rewrite-chunk-system.patch b/patches/server/Rewrite-chunk-system.patch index b75a3145e7..312681cd12 100644 --- a/patches/server/Rewrite-chunk-system.patch +++ b/patches/server/Rewrite-chunk-system.patch @@ -5671,7 +5671,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + this.autoSaveQueue.remove(holder); + + holder.lastAutoSave = currentTick; -+ if (holder.save(false, false)) { ++ if (holder.save(false, false) != null) { + ++autoSaved; + } + @@ -5703,12 +5703,26 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + boolean needsFlush = false; + final int flushInterval = 50; + ++ int savedChunk = 0; ++ int savedEntity = 0; ++ int savedPoi = 0; ++ + for (int i = 0, len = holders.size(); i < len; ++i) { + final NewChunkHolder holder = holders.get(i); + try { -+ if (holder.save(shutdown, false)) { ++ final NewChunkHolder.SaveStat saveStat = holder.save(shutdown, false); ++ if (saveStat != null) { + ++saved; + needsFlush = flush; ++ if (saveStat.savedChunk()) { ++ ++savedChunk; ++ } ++ if (saveStat.savedEntityChunk()) { ++ ++savedEntity; ++ } ++ if (saveStat.savedPoiChunk()) { ++ ++savedPoi; ++ } + } + } catch (final ThreadDeath thr) { + throw thr; @@ -5731,7 +5745,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + RegionFileIOThread.flush(); + } + if (logProgress) { -+ LOGGER.info("Saved " + saved + " chunks in world '" + this.world.getWorld().getName() + "' in " + TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - start) + "s"); ++ LOGGER.info("Saved " + savedChunk + " block chunks, " + savedEntity + " entity chunks, " + savedPoi + " poi chunks in world '" + this.world.getWorld().getName() + "' in " + format.format(1.0E-9 * (System.nanoTime() - start)) + "s"); + } + } + @@ -10955,7 +10969,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + + public long lastAutoSave; + -+ public boolean save(final boolean shutdown, final boolean unloading) { ++ public static final record SaveStat(boolean savedChunk, boolean savedEntityChunk, boolean savedPoiChunk) {} ++ ++ public SaveStat save(final boolean shutdown, final boolean unloading) { + TickThread.ensureTickThread(this.world, this.chunkX, this.chunkZ, "Cannot save data off-main"); + + ChunkAccess chunk = this.getCurrentChunk(); @@ -11002,7 +11018,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + } + -+ return executedUnloadTask | canSaveChunk | canSaveEntities | canSavePOI; ++ return executedUnloadTask | canSaveChunk | canSaveEntities | canSavePOI ? new SaveStat(executedUnloadTask || canSaveChunk, canSaveEntities, canSavePOI): null; + } + + static final class AsyncChunkSerializeTask implements Runnable { @@ -16832,10 +16848,29 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 // note: saving can be prevented, but not forced if no saving is actually required this.mustNotSave = !unloadEvent.isSaveChunk(); @@ -0,0 +0,0 @@ public class LevelChunk extends ChunkAccess { + // Paper end + } ++ // Paper start - add dirty system to tick lists ++ @Override ++ public void setUnsaved(boolean needsSaving) { ++ if (!needsSaving) { ++ this.blockTicks.clearDirty(); ++ this.fluidTicks.clearDirty(); ++ } ++ super.setUnsaved(needsSaving); ++ } ++ // Paper end - add dirty system to tick lists ++ @Override public boolean isUnsaved() { - return super.isUnsaved() && !this.mustNotSave; ++ // Paper start - add dirty system to tick lists ++ long gameTime = this.level.getLevelData().getGameTime(); ++ if (this.blockTicks.isDirty(gameTime) || this.fluidTicks.isDirty(gameTime)) { ++ return true; ++ } ++ // Paper end - add dirty system to tick lists + return super.isUnsaved(); // Paper - rewrite chunk system - do NOT clobber the dirty flag } // CraftBukkit end @@ -17672,6 +17707,75 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 Iterator iterator = set.iterator(); while (iterator.hasNext()) { +diff --git a/src/main/java/net/minecraft/world/ticks/LevelChunkTicks.java b/src/main/java/net/minecraft/world/ticks/LevelChunkTicks.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/world/ticks/LevelChunkTicks.java ++++ b/src/main/java/net/minecraft/world/ticks/LevelChunkTicks.java +@@ -0,0 +0,0 @@ public class LevelChunkTicks implements SerializableTickContainer, TickCon + @Nullable + private BiConsumer, ScheduledTick> onTickAdded; + ++ // Paper start - add dirty flag ++ private boolean dirty; ++ private long lastSaved = Long.MIN_VALUE; ++ ++ public boolean isDirty(final long tick) { ++ return this.dirty || (!this.tickQueue.isEmpty() && tick != this.lastSaved); ++ } ++ ++ public void clearDirty() { ++ this.dirty = false; ++ } ++ // Paper end - add dirty flag ++ + public LevelChunkTicks() { + } + +@@ -0,0 +0,0 @@ public class LevelChunkTicks implements SerializableTickContainer, TickCon + public ScheduledTick poll() { + ScheduledTick scheduledTick = this.tickQueue.poll(); + if (scheduledTick != null) { ++ this.dirty = true; // Paper - add dirty flag + this.ticksPerPosition.remove(scheduledTick); + } + +@@ -0,0 +0,0 @@ public class LevelChunkTicks implements SerializableTickContainer, TickCon + @Override + public void schedule(ScheduledTick orderedTick) { + if (this.ticksPerPosition.add(orderedTick)) { ++ this.dirty = true; // Paper - add dirty flag + this.scheduleUnchecked(orderedTick); + } + +@@ -0,0 +0,0 @@ public class LevelChunkTicks implements SerializableTickContainer, TickCon + while(iterator.hasNext()) { + ScheduledTick scheduledTick = iterator.next(); + if (predicate.test(scheduledTick)) { +- iterator.remove(); ++ iterator.remove(); this.dirty = true; // Paper - add dirty flag + this.ticksPerPosition.remove(scheduledTick); + } + } +@@ -0,0 +0,0 @@ public class LevelChunkTicks implements SerializableTickContainer, TickCon + + @Override + public ListTag save(long l, Function function) { ++ this.lastSaved = l; // Paper - add dirty system to level ticks + ListTag listTag = new ListTag(); + if (this.pendingTicks != null) { + for(SavedTick savedTick : this.pendingTicks) { +@@ -0,0 +0,0 @@ public class LevelChunkTicks implements SerializableTickContainer, TickCon + + public void unpack(long time) { + if (this.pendingTicks != null) { ++ // Paper start - add dirty system to level chunk ticks ++ if (this.tickQueue.isEmpty()) { ++ this.lastSaved = time; ++ } ++ // Paper end - add dirty system to level chunk ticks + int i = -this.pendingTicks.size(); + + for(SavedTick savedTick : this.pendingTicks) { diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java