From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <spottedleaf@spottedleaf.dev>
Date: Mon, 31 Aug 2020 11:08:17 -0700
Subject: [PATCH] Actually unload POI data

While it's not likely for a poi data leak to be meaningful,
sometimes it is.

This patch also prevents the saving/unloading of POI data when
world saving is disabled.

diff --git a/src/main/java/net/minecraft/server/level/LightEngineGraphSection.java b/src/main/java/net/minecraft/server/level/LightEngineGraphSection.java
index 4ee7070364a8989eece4fa4237b529926821f9c9..f22ab98d2e250081df8949be8a99737069c83d34 100644
--- a/src/main/java/net/minecraft/server/level/LightEngineGraphSection.java
+++ b/src/main/java/net/minecraft/server/level/LightEngineGraphSection.java
@@ -77,8 +77,10 @@ public abstract class LightEngineGraphSection extends LightEngineGraph {
         return i == Long.MAX_VALUE ? this.b(j) : k + 1;
     }
 
+    public final int getSource(long coordinate) { return this.b(coordinate); } // Tuinity - OBFHELPER
     protected abstract int b(long i);
 
+    public final void update(long coordinate, int level, boolean flag) { this.b(coordinate, level, flag); } // Tuinity - OBFHELPER
     public void b(long i, int j, boolean flag) {
         this.a(Long.MAX_VALUE, i, j, flag);
     }
diff --git a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java
index b48d419f82f0d9df1f8848d95066bad7e8e5629a..7136e2adc5844280471fd392a3d49da7295ef006 100644
--- a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java
+++ b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java
@@ -982,6 +982,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
                     }
                     // Tuinity end
                 }
+                this.getVillagePlace().dequeueUnload(playerchunk.location.pair()); // Tuinity - unload POI data
 
                 this.updatingChunks.put(i, playerchunk);
                 this.updatingChunksModified = true;
@@ -1107,7 +1108,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
 
     }
 
-    private static final double UNLOAD_QUEUE_RESIZE_FACTOR = 0.90; // Spigot // Paper - unload more
+    public static final double UNLOAD_QUEUE_RESIZE_FACTOR = 0.90; // Spigot // Paper - unload more // Tuinity - private -> public
 
     protected void unloadChunks(BooleanSupplier booleansupplier) {
         GameProfilerFiller gameprofilerfiller = this.world.getMethodProfiler();
@@ -1239,6 +1240,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
                         this.regionManagers.get(index).removeChunk(playerchunk.location.x, playerchunk.location.z);
                     }
                     // Tuinity end
+                    this.getVillagePlace().queueUnload(playerchunk.location.pair(), MinecraftServer.currentTickLong + 1); // Tuinity - unload POI data
                     if (ichunkaccess instanceof Chunk) {
                         ((Chunk) ichunkaccess).setLoaded(false);
                     }
@@ -1267,6 +1269,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
                         this.regionManagers.get(index).removeChunk(playerchunk.location.x, playerchunk.location.z);
                     }
                     // Tuinity end
+                    this.getVillagePlace().queueUnload(playerchunk.location.pair(), MinecraftServer.currentTickLong + 1); // Tuinity - unload POI data
                 } // Tuinity end
                 } finally { this.unloadingPlayerChunk = unloadingBefore; } // Tuinity - do not allow ticket level changes while unloading chunks
 
@@ -1360,6 +1363,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
 
                 this.getVillagePlace().loadInData(chunkcoordintpair, chunkHolder.poiData);
                 chunkHolder.tasks.forEach(Runnable::run);
+                this.getVillagePlace().dequeueUnload(chunkcoordintpair.pair()); // Tuinity
                 // Paper end
 
                 if (chunkHolder.protoChunk != null) {try (Timing ignored2 = this.world.timings.chunkLoadLevelTimer.startTimingIfSync()) { // Paper start - timings // Paper - chunk is created async
diff --git a/src/main/java/net/minecraft/world/entity/ai/village/poi/VillagePlace.java b/src/main/java/net/minecraft/world/entity/ai/village/poi/VillagePlace.java
index 84843ebbef7a6e56633b35712b1643801dca62dc..a5edfaa41156fc03181f38a8813eb5bcc8c08e48 100644
--- a/src/main/java/net/minecraft/world/entity/ai/village/poi/VillagePlace.java
+++ b/src/main/java/net/minecraft/world/entity/ai/village/poi/VillagePlace.java
@@ -4,6 +4,7 @@ import com.mojang.datafixers.DataFixer;
 import com.mojang.datafixers.util.Pair;
 import it.unimi.dsi.fastutil.longs.Long2ByteMap;
 import it.unimi.dsi.fastutil.longs.Long2ByteOpenHashMap;
+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; // Tuinity
 import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
 import it.unimi.dsi.fastutil.longs.LongSet;
 import java.io.File;
@@ -35,8 +36,24 @@ import net.minecraft.world.level.chunk.storage.RegionFileSection;
 
 public class VillagePlace extends RegionFileSection<VillagePlaceSection> {
 
-    private final VillagePlace.a a = new VillagePlace.a();
-    private final LongSet b = new LongOpenHashSet();
+    // Tuinity start - unload poi data
+    // the vanilla tracker needs to be replaced because it does not support level removes
+    private final com.tuinity.tuinity.util.misc.Delayed26WayDistancePropagator3D villageDistanceTracker = new com.tuinity.tuinity.util.misc.Delayed26WayDistancePropagator3D();
+    static final int POI_DATA_SOURCE = 7;
+    public static int convertBetweenLevels(final int level) {
+        return POI_DATA_SOURCE - level;
+    }
+
+    protected void updateDistanceTracking(long section) {
+        if (this.isSectionDistanceTrackerSource(section)) {
+            this.villageDistanceTracker.setSource(section, POI_DATA_SOURCE);
+        } else {
+            this.villageDistanceTracker.removeSource(section);
+        }
+    }
+    // Tuinity end - unload poi data
+
+    private final LongSet b = new LongOpenHashSet(); private final LongSet getLoadedChunks() { return this.b; } // Tuinity - OBFHELPER
 
     private final WorldServer world; // Paper
 
@@ -47,9 +64,124 @@ public class VillagePlace extends RegionFileSection<VillagePlaceSection> {
     public VillagePlace(File file, DataFixer datafixer, boolean flag, WorldServer world) {
         super(file, VillagePlaceSection::a, VillagePlaceSection::new, datafixer, DataFixTypes.POI_CHUNK, flag);
         this.world = world;
+        if (world == null) { throw new IllegalStateException("world must be non-null"); }// Tuinity - require non-null
         // Paper end - add world parameter
     }
 
+    // Tuinity start - actually unload POI data
+    private final java.util.TreeSet<QueuedUnload> queuedUnloads = new java.util.TreeSet<>();
+    private final Long2ObjectOpenHashMap<QueuedUnload> queuedUnloadsByCoordinate = new Long2ObjectOpenHashMap<>();
+
+    static final class QueuedUnload implements Comparable<QueuedUnload> {
+
+        private final long unloadTick;
+        private final long coordinate;
+
+        public QueuedUnload(long unloadTick, long coordinate) {
+            this.unloadTick = unloadTick;
+            this.coordinate = coordinate;
+        }
+
+        @Override
+        public int compareTo(QueuedUnload other) {
+            if (other.unloadTick == this.unloadTick) {
+                return Long.compare(this.coordinate, other.coordinate);
+            } else {
+                return Long.compare(this.unloadTick, other.unloadTick);
+            }
+        }
+
+        @Override
+        public int hashCode() {
+            int hash = 1;
+            hash = hash * 31 + Long.hashCode(this.unloadTick);
+            hash = hash * 31 + Long.hashCode(this.coordinate);
+            return hash;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (obj == null || obj.getClass() != QueuedUnload.class) {
+                return false;
+            }
+            QueuedUnload other = (QueuedUnload)obj;
+            return other.unloadTick == this.unloadTick && other.coordinate == this.coordinate;
+        }
+    }
+
+    long determineDelay(long coordinate) {
+        if (this.isEmpty(coordinate)) {
+            return 5 * 60 * 20;
+        } else {
+            return 60 * 20;
+        }
+    }
+
+    public void queueUnload(long coordinate, long minTarget) {
+        com.tuinity.tuinity.util.TickThread.softEnsureTickThread("async poi unload queue");
+        QueuedUnload unload = new QueuedUnload(minTarget + this.determineDelay(coordinate), coordinate);
+        QueuedUnload existing = this.queuedUnloadsByCoordinate.put(coordinate, unload);
+        if (existing != null) {
+            this.queuedUnloads.remove(existing);
+        }
+        this.queuedUnloads.add(unload);
+    }
+
+    public void dequeueUnload(long coordinate) {
+        com.tuinity.tuinity.util.TickThread.softEnsureTickThread("async poi unload dequeue");
+        QueuedUnload unload = this.queuedUnloadsByCoordinate.remove(coordinate);
+        if (unload != null) {
+            this.queuedUnloads.remove(unload);
+        }
+    }
+
+    public void pollUnloads(BooleanSupplier canSleepForTick) {
+        com.tuinity.tuinity.util.TickThread.softEnsureTickThread("async poi unload");
+        long currentTick = net.minecraft.server.MinecraftServer.currentTickLong;
+        net.minecraft.server.level.ChunkProviderServer chunkProvider = this.world.getChunkProvider();
+        net.minecraft.server.level.PlayerChunkMap playerChunkMap = chunkProvider.playerChunkMap;
+        // copied target determination from PlayerChunkMap
+        int target = Math.min(this.queuedUnloads.size() - 100,  (int) (this.queuedUnloads.size() * net.minecraft.server.level.PlayerChunkMap.UNLOAD_QUEUE_RESIZE_FACTOR)); // Paper - Make more aggressive
+        for (java.util.Iterator<QueuedUnload> iterator = this.queuedUnloads.iterator();
+             iterator.hasNext() && (this.queuedUnloads.size() > target || canSleepForTick.getAsBoolean());) {
+            QueuedUnload unload = iterator.next();
+            if (unload.unloadTick > currentTick) {
+                break;
+            }
+
+            long coordinate = unload.coordinate;
+
+            iterator.remove();
+            this.queuedUnloadsByCoordinate.remove(coordinate);
+
+            if (playerChunkMap.getUnloadingPlayerChunk(net.minecraft.server.MCUtil.getCoordinateX(coordinate), net.minecraft.server.MCUtil.getCoordinateZ(coordinate)) != null
+                || playerChunkMap.getUpdatingChunk(coordinate) != null) {
+                continue;
+            }
+
+            this.unloadData(coordinate);
+        }
+    }
+
+    @Override
+    public void unloadData(long coordinate) {
+        com.tuinity.tuinity.util.TickThread.softEnsureTickThread("async unloading poi data");
+        super.unloadData(coordinate);
+    }
+
+    @Override
+    protected void onUnload(long coordinate) {
+        com.tuinity.tuinity.util.TickThread.softEnsureTickThread("async poi unload callback");
+        this.getLoadedChunks().remove(coordinate);
+        int chunkX = net.minecraft.server.MCUtil.getCoordinateX(coordinate);
+        int chunkZ = net.minecraft.server.MCUtil.getCoordinateZ(coordinate);
+        for (int section = 0; section < 16; ++section) {
+            long sectionPos = SectionPosition.asLong(chunkX, section, chunkZ);
+            this.updateDistanceTracking(sectionPos);
+        }
+    }
+    // Tuinity end - actually unload POI data
+
     public void a(BlockPosition blockposition, VillagePlaceType villageplacetype) {
         ((VillagePlaceSection) this.e(SectionPosition.a(blockposition).s())).a(blockposition, villageplacetype);
     }
@@ -153,10 +285,11 @@ public class VillagePlace extends RegionFileSection<VillagePlaceSection> {
     }
 
     public int a(SectionPosition sectionposition) {
-        this.a.a();
-        return this.a.c(sectionposition.s());
+        this.villageDistanceTracker.propagateUpdates(); // Tuinity - replace distance tracking util
+        return convertBetweenLevels(this.villageDistanceTracker.getLevel(net.minecraft.server.MCUtil.getSectionKey(sectionposition))); // Tuinity - replace distance tracking util
     }
 
+    private boolean isSectionDistanceTrackerSource(long section) { return this.f(section); } // Tuinity - OBFHELPER
     private boolean f(long i) {
         Optional<VillagePlaceSection> optional = this.c(i);
 
@@ -172,7 +305,7 @@ public class VillagePlace extends RegionFileSection<VillagePlaceSection> {
             super.a(booleansupplier);
         } else {
             //super.a(booleansupplier); // re-implement below
-            while (!((RegionFileSection)this).d.isEmpty() && booleansupplier.getAsBoolean()) {
+            while (!((RegionFileSection)this).d.isEmpty() && booleansupplier.getAsBoolean() && !this.world.isSavingDisabled()) { // Tuinity - unload POI data - don't write to disk if saving is disabled
                 ChunkCoordIntPair chunkcoordintpair = SectionPosition.a(((RegionFileSection)this).d.firstLong()).r();
 
                 NBTTagCompound data;
@@ -183,19 +316,24 @@ public class VillagePlace extends RegionFileSection<VillagePlaceSection> {
                     chunkcoordintpair.x, chunkcoordintpair.z, data, null, com.destroystokyo.paper.io.PrioritizedTaskQueue.NORMAL_PRIORITY); // Tuinity - use normal priority
             }
         }
+        // Tuinity start - unload POI data
+        if (!this.world.isSavingDisabled()) { // don't write to disk if saving is disabled
+            this.pollUnloads(booleansupplier);
+        }
+        // Tuinity end - unload POI data
         // Paper end
-        this.a.a();
+        this.villageDistanceTracker.propagateUpdates(); // Tuinity - replace distance tracking until
     }
 
     @Override
     protected void a(long i) {
         super.a(i);
-        this.a.b(i, this.a.b(i), false);
+        this.updateDistanceTracking(i); // Tuinity - move to new distance tracking util
     }
 
     @Override
     protected void b(long i) {
-        this.a.b(i, this.a.b(i), false);
+        this.updateDistanceTracking(i); // Tuinity - move to new distance tracking util
     }
 
     public void a(ChunkCoordIntPair chunkcoordintpair, ChunkSection chunksection) {
@@ -260,7 +398,7 @@ public class VillagePlace extends RegionFileSection<VillagePlaceSection> {
 
         @Override
         protected int b(long i) {
-            return VillagePlace.this.f(i) ? 0 : 7;
+            return VillagePlace.this.f(i) ? 0 : 7; // Tuinity - unload poi data - diff on change, this specifies the source level to use for distance tracking
         }
 
         @Override
diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileSection.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileSection.java
index 0791dcc6c2efda54ce3afb36e8d3b0b022807f67..3628b62b8021bbb9b03b3236830b6dccef2d8419 100644
--- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileSection.java
+++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileSection.java
@@ -34,8 +34,8 @@ public class RegionFileSection<R> extends RegionFileCache implements AutoCloseab
 
     private static final Logger LOGGER = LogManager.getLogger();
     // Paper - nuke IOWorker
-    private final Long2ObjectMap<Optional<R>> c = new Long2ObjectOpenHashMap();
-    public final LongLinkedOpenHashSet d = new LongLinkedOpenHashSet(); // Paper - private -> public
+    private final Long2ObjectMap<Optional<R>> c = new Long2ObjectOpenHashMap(); protected final Long2ObjectMap<Optional<R>> getDataBySection() { return this.c; } // Tuinity - OBFHELPER
+    public final LongLinkedOpenHashSet d = new LongLinkedOpenHashSet();  protected final LongLinkedOpenHashSet getDirtySections() { return this.d; } // Paper - private -> public // Tuinity - OBFHELPER
     private final Function<Runnable, Codec<R>> e;
     private final Function<Runnable, R> f;
     private final DataFixer g;
@@ -59,6 +59,40 @@ public class RegionFileSection<R> extends RegionFileCache implements AutoCloseab
 
     }
 
+    // Tuinity start - actually unload POI data
+    public void unloadData(long coordinate) {
+        ChunkCoordIntPair chunkPos = new ChunkCoordIntPair(coordinate);
+        this.writeDirtyData(chunkPos);
+
+        Long2ObjectMap<Optional<R>> data = this.getDataBySection();
+        int before = data.size();
+
+        for (int section = 0; section < 16; ++section) {
+            data.remove(SectionPosition.asLong(chunkPos.x, section, chunkPos.z));
+        }
+
+        if (before != data.size()) {
+            this.onUnload(coordinate);
+        }
+    }
+
+    protected void onUnload(long coordinate) {}
+
+    public boolean isEmpty(long coordinate) {
+        Long2ObjectMap<Optional<R>> data = this.getDataBySection();
+        int x = net.minecraft.server.MCUtil.getCoordinateX(coordinate);
+        int z = net.minecraft.server.MCUtil.getCoordinateZ(coordinate);
+        for (int section = 0; section < 16; ++section) {
+            Optional<R> optional = data.get(SectionPosition.asLong(x, section, z));
+            if (optional != null && optional.orElse(null) != null) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+    // Tuinity end - actually unload POI data
+
     @Nullable protected Optional<R> getIfLoaded(long value) { return this.c(value); } // Tuinity - OBFHELPER
     @Nullable protected Optional<R> c(long i) { // Tuinity - OBFHELPER
         return (Optional) this.c.get(i);
@@ -159,6 +193,7 @@ public class RegionFileSection<R> extends RegionFileCache implements AutoCloseab
                 });
             }
         }
+        if (this instanceof net.minecraft.world.entity.ai.village.poi.VillagePlace) { ((net.minecraft.world.entity.ai.village.poi.VillagePlace)this).queueUnload(chunkcoordintpair.pair(), net.minecraft.server.MinecraftServer.currentTickLong + 1); } // Tuinity - unload POI data
 
     }
 
@@ -230,6 +265,7 @@ public class RegionFileSection<R> extends RegionFileCache implements AutoCloseab
         return dynamic.get("DataVersion").asInt(1945);
     }
 
+    public final void writeDirtyData(ChunkCoordIntPair chunkcoordintpair) { this.a(chunkcoordintpair); } // Tuinity - OBFHELPER
     public void a(ChunkCoordIntPair chunkcoordintpair) {
         if (!this.d.isEmpty()) {
             for (int i = 0; i < 16; ++i) {
