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