aboutsummaryrefslogtreecommitdiffhomepage
path: root/patches/server/0811-Actually-unload-POI-data.patch
diff options
context:
space:
mode:
Diffstat (limited to 'patches/server/0811-Actually-unload-POI-data.patch')
-rw-r--r--patches/server/0811-Actually-unload-POI-data.patch325
1 files changed, 325 insertions, 0 deletions
diff --git a/patches/server/0811-Actually-unload-POI-data.patch b/patches/server/0811-Actually-unload-POI-data.patch
new file mode 100644
index 0000000000..8a61aa29f6
--- /dev/null
+++ b/patches/server/0811-Actually-unload-POI-data.patch
@@ -0,0 +1,325 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Spottedleaf <[email protected]>
+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/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
+index 727ee42ec573fd1d008f526f49d0e63396ef0a9d..0e6bc8f7b31581fe0e7adb13119b30295aeac48d 100644
+--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
+@@ -848,6 +848,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+ }
+ // Paper end
+ }
++ this.getPoiManager().dequeueUnload(holder.pos.longKey); // Paper - unload POI data
+
+ this.updatingChunks.queueUpdate(pos, holder); // Paper - Don't copy
+ this.modified = true;
+@@ -999,7 +1000,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+
+ private void processUnloads(BooleanSupplier shouldKeepTicking) {
+ LongIterator longiterator = this.toDrop.iterator();
+- for (int i = 0; longiterator.hasNext() && (shouldKeepTicking.getAsBoolean() || i < 200 || this.toDrop.size() > 2000); longiterator.remove()) {
++ for (int i = 0; longiterator.hasNext() && (shouldKeepTicking.getAsBoolean() || i < 200 || this.toDrop.size() > 2000); longiterator.remove()) { // Paper - diff on change
+ long j = longiterator.nextLong();
+ ChunkHolder playerchunk = this.updatingChunks.queueRemove(j); // Paper - Don't copy
+
+@@ -1047,6 +1048,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+ this.regionManagers.get(index).removeChunk(holder.pos.x, holder.pos.z);
+ }
+ // Paper end
++ this.getPoiManager().queueUnload(holder.pos.longKey, MinecraftServer.currentTickLong + 1); // Paper - unload POI data
+ if (ichunkaccess instanceof LevelChunk) {
+ ((LevelChunk) ichunkaccess).setLoaded(false);
+ }
+@@ -1076,6 +1078,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+ for (int index = 0, len = this.regionManagers.size(); index < len; ++index) {
+ this.regionManagers.get(index).removeChunk(holder.pos.x, holder.pos.z);
+ }
++ this.getPoiManager().queueUnload(holder.pos.longKey, MinecraftServer.currentTickLong + 1); // Paper - unload POI data
+ } // Paper end
+ } finally { this.unloadingPlayerChunk = unloadingBefore; } // Paper - do not allow ticket level changes while unloading chunks
+
+@@ -1152,6 +1155,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+ }
+ this.poiManager.loadInData(pos, chunkHolder.poiData);
+ chunkHolder.tasks.forEach(Runnable::run);
++ this.getPoiManager().dequeueUnload(pos.longKey); // Paper
+ // Paper end
+
+ if (chunkHolder.protoChunk != null) {try (Timing ignored2 = this.level.timings.chunkLoadLevelTimer.startTimingIfSync()) { // Paper start - timings // Paper - chunk is created async
+diff --git a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java
+index 8a569e3300543cb171c3befae59969628adc424c..54013682ee07cfe34f47070b6460c21ff420130f 100644
+--- a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java
++++ b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java
+@@ -1,5 +1,6 @@
+ package net.minecraft.world.entity.ai.village.poi;
+
++import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; // Paper
+ import com.mojang.datafixers.DataFixer;
+ import com.mojang.datafixers.util.Pair;
+ import it.unimi.dsi.fastutil.longs.Long2ByteMap;
+@@ -35,16 +36,145 @@ import net.minecraft.world.level.chunk.storage.SectionStorage;
+ public class PoiManager extends SectionStorage<PoiSection> {
+ public static final int MAX_VILLAGE_DISTANCE = 6;
+ public static final int VILLAGE_SECTION_SIZE = 1;
+- private final PoiManager.DistanceTracker distanceTracker;
++ // Paper start - unload poi data
++ // the vanilla tracker needs to be replaced because it does not support level removes
++ private final io.papermc.paper.util.misc.Delayed26WayDistancePropagator3D villageDistanceTracker = new io.papermc.paper.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.isVillageCenter(section)) {
++ this.villageDistanceTracker.setSource(section, POI_DATA_SOURCE);
++ } else {
++ this.villageDistanceTracker.removeSource(section);
++ }
++ }
++ // Paper end - unload poi data
+ private final LongSet loadedChunks = new LongOpenHashSet();
+ public final net.minecraft.server.level.ServerLevel world; // Paper // Paper public
+
+ public PoiManager(Path path, DataFixer dataFixer, boolean dsync, LevelHeightAccessor world) {
+ super(path, PoiSection::codec, PoiSection::new, dataFixer, DataFixTypes.POI_CHUNK, dsync, world);
++ if (world == null) { throw new IllegalStateException("world must be non-null"); } // Paper - require non-null
+ this.world = (net.minecraft.server.level.ServerLevel)world; // Paper
+- this.distanceTracker = new PoiManager.DistanceTracker();
+ }
+
++ // Paper 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) {
++ io.papermc.paper.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) {
++ io.papermc.paper.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) {
++ io.papermc.paper.util.TickThread.softEnsureTickThread("async poi unload");
++ long currentTick = net.minecraft.server.MinecraftServer.currentTickLong;
++ net.minecraft.server.level.ServerChunkCache chunkProvider = this.world.getChunkSource();
++ net.minecraft.server.level.ChunkMap playerChunkMap = chunkProvider.chunkMap;
++ // copied target determination from PlayerChunkMap
++
++ java.util.Iterator<QueuedUnload> iterator = this.queuedUnloads.iterator();
++ for (int i = 0; iterator.hasNext() && (i < 200 || this.queuedUnloads.size() > 2000 || canSleepForTick.getAsBoolean()); i++) {
++ QueuedUnload unload = iterator.next();
++ if (unload.unloadTick > currentTick) {
++ break;
++ }
++
++ long coordinate = unload.coordinate;
++
++ iterator.remove();
++ this.queuedUnloadsByCoordinate.remove(coordinate);
++
++ if (playerChunkMap.getUnloadingChunkHolder(net.minecraft.server.MCUtil.getCoordinateX(coordinate), net.minecraft.server.MCUtil.getCoordinateZ(coordinate)) != null
++ || playerChunkMap.getUpdatingChunkIfPresent(coordinate) != null) {
++ continue;
++ }
++
++ this.unloadData(coordinate);
++ }
++ }
++
++ @Override
++ public void unloadData(long coordinate) {
++ io.papermc.paper.util.TickThread.softEnsureTickThread("async unloading poi data");
++ super.unloadData(coordinate);
++ }
++
++ @Override
++ protected void onUnload(long coordinate) {
++ io.papermc.paper.util.TickThread.softEnsureTickThread("async poi unload callback");
++ this.loadedChunks.remove(coordinate);
++ int chunkX = net.minecraft.server.MCUtil.getCoordinateX(coordinate);
++ int chunkZ = net.minecraft.server.MCUtil.getCoordinateZ(coordinate);
++ for (int section = this.levelHeightAccessor.getMinSection(); section < this.levelHeightAccessor.getMaxSection(); ++section) {
++ long sectionPos = SectionPos.asLong(chunkX, section, chunkZ);
++ this.updateDistanceTracking(sectionPos);
++ }
++ }
++ // Paper end - actually unload POI data
++
+ public void add(BlockPos pos, PoiType type) {
+ this.getOrCreate(SectionPos.asLong(pos)).add(pos, type);
+ }
+@@ -181,8 +311,8 @@ public class PoiManager extends SectionStorage<PoiSection> {
+ }
+
+ public int sectionsToVillage(SectionPos pos) {
+- this.distanceTracker.runAllUpdates();
+- return this.distanceTracker.getLevel(pos.asLong());
++ this.villageDistanceTracker.propagateUpdates(); // Paper - replace distance tracking util
++ return convertBetweenLevels(this.villageDistanceTracker.getLevel(io.papermc.paper.util.CoordinateUtils.getChunkSectionKey(pos))); // Paper - replace distance tracking util
+ }
+
+ boolean isVillageCenter(long pos) {
+@@ -195,7 +325,7 @@ public class PoiManager extends SectionStorage<PoiSection> {
+ @Override
+ public void tick(BooleanSupplier shouldKeepTicking) {
+ // Paper start - async chunk io
+- while (!this.dirty.isEmpty() && shouldKeepTicking.getAsBoolean()) {
++ while (!this.dirty.isEmpty() && shouldKeepTicking.getAsBoolean() && !this.world.noSave()) { // Paper - unload POI data - don't write to disk if saving is disabled
+ ChunkPos chunkcoordintpair = SectionPos.of(this.dirty.firstLong()).chunk();
+
+ net.minecraft.nbt.CompoundTag data;
+@@ -205,19 +335,24 @@ public class PoiManager extends SectionStorage<PoiSection> {
+ com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE.scheduleSave(this.world,
+ chunkcoordintpair.x, chunkcoordintpair.z, data, null, com.destroystokyo.paper.io.PrioritizedTaskQueue.NORMAL_PRIORITY);
+ }
++ // Paper start - unload POI data
++ if (!this.world.noSave()) { // don't write to disk if saving is disabled
++ this.pollUnloads(shouldKeepTicking);
++ }
++ // Paper end - unload POI data
+ // Paper end
+- this.distanceTracker.runAllUpdates();
++ this.villageDistanceTracker.propagateUpdates(); // Paper - replace distance tracking until
+ }
+
+ @Override
+ protected void setDirty(long pos) {
+ super.setDirty(pos);
+- this.distanceTracker.update(pos, this.distanceTracker.getLevelFromSource(pos), false);
++ this.updateDistanceTracking(pos); // Paper - move to new distance tracking util
+ }
+
+ @Override
+ protected void onSectionLoad(long pos) {
+- this.distanceTracker.update(pos, this.distanceTracker.getLevelFromSource(pos), false);
++ this.updateDistanceTracking(pos); // Paper - move to new distance tracking util
+ }
+
+ public void checkConsistencyWithBlocks(ChunkPos chunkPos, LevelChunkSection chunkSection) {
+@@ -275,7 +410,7 @@ public class PoiManager extends SectionStorage<PoiSection> {
+
+ @Override
+ protected int getLevelFromSource(long id) {
+- return PoiManager.this.isVillageCenter(id) ? 0 : 7;
++ return PoiManager.this.isVillageCenter(id) ? 0 : 7; // Paper - 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/SectionStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java
+index 3cfc57b49fb3d85c4b9039907fc22bad3a0efe3e..2df0b55a18e57163e49770e83ef067a5587d2126 100644
+--- a/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java
++++ b/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java
+@@ -52,6 +52,40 @@ public class SectionStorage<R> extends RegionFileStorage implements AutoCloseabl
+ // Paper - remove mojang I/O thread
+ }
+
++ // Paper start - actually unload POI data
++ public void unloadData(long coordinate) {
++ ChunkPos chunkPos = new ChunkPos(coordinate);
++ this.flush(chunkPos);
++
++ Long2ObjectMap<Optional<R>> data = this.storage;
++ int before = data.size();
++
++ for (int section = this.levelHeightAccessor.getMinSection(); section < this.levelHeightAccessor.getMaxSection(); ++section) {
++ data.remove(SectionPos.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.storage;
++ int x = net.minecraft.server.MCUtil.getCoordinateX(coordinate);
++ int z = net.minecraft.server.MCUtil.getCoordinateZ(coordinate);
++ for (int section = this.levelHeightAccessor.getMinSection(); section < this.levelHeightAccessor.getMaxSection(); ++section) {
++ Optional<R> optional = data.get(SectionPos.asLong(x, section, z));
++ if (optional != null && optional.orElse(null) != null) {
++ return false;
++ }
++ }
++
++ return true;
++ }
++ // Paper end - actually unload POI data
++
+ protected void tick(BooleanSupplier shouldKeepTicking) {
+ while(this.hasWork() && shouldKeepTicking.getAsBoolean()) {
+ ChunkPos chunkPos = SectionPos.of(this.dirty.firstLong()).chunk();
+@@ -166,6 +200,7 @@ public class SectionStorage<R> extends RegionFileStorage implements AutoCloseabl
+ });
+ }
+ }
++ if (this instanceof net.minecraft.world.entity.ai.village.poi.PoiManager) { ((net.minecraft.world.entity.ai.village.poi.PoiManager)this).queueUnload(pos.longKey, net.minecraft.server.MinecraftServer.currentTickLong + 1); } // Paper - unload POI data
+
+ }
+