Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1031,6 +1031,15 @@
profiler.push("tallying");
long tickTime = Util.getNanos() - nano;
int tickIndex = this.tickCount % 100;
@@ -1005,5 +_,8 @@
this.logTickMethodTime(nano);
profiler.pop();
}
+ // Paper start - sweep map holders of departed players
+ io.papermc.paper.util.MapHolderCleanup.tick(this);
+ // Paper end

protected void processPacketsAndTick(final boolean sprinting) {
@@ -1012,16 +_,16 @@
ProfilerFiller profiler = Profiler.get();
profiler.push("tick");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@
}

@Override
@@ -292,65 +_,146 @@
@@ -292,65 +_,149 @@
}

protected void save(final ServerPlayer player) {
Expand Down Expand Up @@ -398,6 +398,9 @@
+ // This removes the scoreboard (and player reference) for the specific player in the manager
+ this.cserver.getScoreboardManager().removePlayer(player.getBukkitEntity());
+ // CraftBukkit end
+ // Paper start - queue map holder cleanup for departed player
+ io.papermc.paper.util.MapHolderCleanup.onPlayerDisconnect(uuid);
+ // Paper end
+ return playerQuitEvent.quitMessage(); // Paper - Adventure
+ }
+
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
--- a/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java
+++ b/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java
@@ -12,4 +_,6 @@
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
+import java.util.Set;
+import java.util.UUID;
import java.util.function.Predicate;
@@ -48,7 +_,7 @@
private static final String FRAME_PREFIX = "frame-";
public static final Codec<MapItemSavedData> CODEC = RecordCodecBuilder.create(
Expand Down Expand Up @@ -108,6 +115,29 @@

for (MapItemSavedData.HoldingPlayer holdingPlayer : this.carriedBy) {
holdingPlayer.markColorsDirty(x, y);
@@ -388,8 +_,22 @@
this.carriedByPlayers.put(player, holdingPlayer);
this.carriedBy.add(holdingPlayer);
}

return holdingPlayer;
}
+
+ // Paper start - remove holder state for departed players
+ public void removeHolders(final Set<UUID> playerIds) {
+ Iterator<HoldingPlayer> iterator = this.carriedBy.iterator();
+ while (iterator.hasNext()) {
+ HoldingPlayer holder = iterator.next();
+ if (playerIds.contains(holder.player.getUUID())) {
+ this.carriedByPlayers.remove(holder.player);
+ iterator.remove();
+ this.removeDecoration(holder.player.getPlainTextName());
+ }
+ }
+ }
+ // Paper end

public boolean toggleBanner(final LevelAccessor level, final BlockPos pos) {
@@ -416,7 +_,7 @@
return true;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,24 @@
--- a/net/minecraft/world/level/storage/SavedDataStorage.java
+++ b/net/minecraft/world/level/storage/SavedDataStorage.java
@@ -104,8 +_,18 @@
}

public <T extends SavedData> void set(final SavedDataType<T> type, final T data) {
this.cache.put(type, Optional.of(data));
data.setDirty();
}
+
+ // Paper start - expose loaded data for map holder cleanup
+ public List<SavedData> getLoadedData() {
+ List<SavedData> loaded = new ArrayList<>();
+ for (Optional<SavedData> data : this.cache.values()) {
+ data.ifPresent(loaded::add);
+ }
+ return loaded;
+ }
+ // Paper end

public CompoundTag readTagFromDisk(final Path dataFile, final DataFixTypes type, final int newVersion) throws IOException {
@@ -155,7 +_,7 @@

int threads = Util.maxAllowedExecutorThreads();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package io.papermc.paper.util;

import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.saveddata.SavedData;
import net.minecraft.world.level.saveddata.maps.MapItemSavedData;

/**
* Removes per-player map holder state for departed players.
*
* <p>Vanilla only drops {@code MapItemSavedData} holder entries while a map
* ticks for an online player, so disconnected players are retained - along
* with their whole {@code ServerPlayer} - until the map data unloads.
* Disconnects are collected here and swept in one batched pass, so quit
* storms cost a single catalogue scan instead of one per player.</p>
*/
public final class MapHolderCleanup {
private static final int SWEEP_INTERVAL_TICKS = 100; // 5 seconds
private static final Set<UUID> pendingDepartures = ConcurrentHashMap.newKeySet();
private static int lastSweepTick;

private MapHolderCleanup() {
}

public static void onPlayerDisconnect(final UUID playerId) {
pendingDepartures.add(playerId);
}

public static void tick(final MinecraftServer server) {
if (pendingDepartures.isEmpty()) {
return;
}
int tick = server.getTickCount();
if (tick - lastSweepTick < SWEEP_INTERVAL_TICKS) {
return;
}
lastSweepTick = tick;

Set<UUID> departed = Set.copyOf(pendingDepartures);
pendingDepartures.removeAll(departed);

Set<UUID> offline = new HashSet<>();
for (UUID uuid : departed) {
if (server.getPlayerList().getPlayer(uuid) == null) {
offline.add(uuid);
}
}
if (offline.isEmpty()) {
return;
}

for (ServerLevel level : server.getAllLevels()) {
for (SavedData data : level.getDataStorage().getLoadedData()) {
if (data instanceof MapItemSavedData mapData) {
mapData.removeHolders(offline);
}
}
}
}
}