--- a/net/minecraft/server/level/DistanceManager.java +++ b/net/minecraft/server/level/DistanceManager.java @@ -107,6 +_,12 @@ } if (!this.chunksToUpdateFutures.isEmpty()) { + // CraftBukkit start - SPIGOT-7780: Call chunk unload events before updateHighestAllowedStatus + for (final ChunkHolder chunkHolder : this.chunksToUpdateFutures) { + chunkHolder.callEventIfUnloading(chunkMap); + } + // CraftBukkit end - SPIGOT-7780: Call chunk unload events before updateHighestAllowedStatus + for (ChunkHolder chunkHolder : this.chunksToUpdateFutures) { chunkHolder.updateHighestAllowedStatus(chunkMap); } @@ -143,7 +_,7 @@ } } - void addTicket(long chunkPos, Ticket ticket) { + boolean addTicket(long chunkPos, Ticket ticket) { // CraftBukkit - void -> boolean SortedArraySet> tickets = this.getTickets(chunkPos); int ticketLevelAt = getTicketLevelAt(tickets); Ticket ticket1 = tickets.addOrGet(ticket); @@ -151,11 +_,14 @@ if (ticket.getTicketLevel() < ticketLevelAt) { this.ticketTracker.update(chunkPos, ticket.getTicketLevel(), true); } + return ticket == ticket1; // CraftBukkit } - void removeTicket(long chunkPos, Ticket ticket) { + boolean removeTicket(long chunkPos, Ticket ticket) { // CraftBukkit - void -> boolean SortedArraySet> tickets = this.getTickets(chunkPos); + boolean removed = false; // CraftBukkit if (tickets.remove(ticket)) { + removed = true; // CraftBukkit } if (tickets.isEmpty()) { @@ -163,6 +_,7 @@ } this.ticketTracker.update(chunkPos, getTicketLevelAt(tickets), false); + return removed; // CraftBukkit } public void addTicket(TicketType type, ChunkPos pos, int level, T value) { @@ -175,17 +_,29 @@ } public void addRegionTicket(TicketType type, ChunkPos pos, int distance, T value) { + // CraftBukkit start + this.addRegionTicketAtDistance(type, pos, distance, value); + } + public boolean addRegionTicketAtDistance(TicketType type, ChunkPos pos, int distance, T value) { + // CraftBukkit end Ticket ticket = new Ticket<>(type, ChunkLevel.byStatus(FullChunkStatus.FULL) - distance, value); long packedChunkPos = pos.toLong(); - this.addTicket(packedChunkPos, ticket); + final boolean addded = this.addTicket(packedChunkPos, ticket); // CraftBukkit this.tickingTicketsTracker.addTicket(packedChunkPos, ticket); + return addded; // CraftBukkit } public void removeRegionTicket(TicketType type, ChunkPos pos, int distance, T value) { + // CraftBukkit start + removeRegionTicketAtDistance(type, pos, distance, value); + } + public boolean removeRegionTicketAtDistance(TicketType type, ChunkPos pos, int distance, T value) { + // CraftBukkit end Ticket ticket = new Ticket<>(type, ChunkLevel.byStatus(FullChunkStatus.FULL) - distance, value); long packedChunkPos = pos.toLong(); - this.removeTicket(packedChunkPos, ticket); + final boolean removed = this.removeTicket(packedChunkPos, ticket); // CraftBukkit this.tickingTicketsTracker.removeTicket(packedChunkPos, ticket); + return removed; // CraftBukkit } private SortedArraySet> getTickets(long chunkPos) { @@ -217,8 +_,12 @@ ChunkPos chunkPos = sectionPos.chunk(); long packedChunkPos = chunkPos.toLong(); ObjectSet set = this.playersPerChunk.get(packedChunkPos); - set.remove(player); - if (set.isEmpty()) { + // Paper start - some state corruption happens here, don't crash, clean up gracefully + if (set != null) { + set.remove(player); + } + if (set == null || set.isEmpty()) { + // Paper end - some state corruption happens here, don't crash, clean up gracefully this.playersPerChunk.remove(packedChunkPos); this.naturalSpawnChunkCounter.update(packedChunkPos, Integer.MAX_VALUE, false); this.playerTicketManager.update(packedChunkPos, Integer.MAX_VALUE, false); @@ -299,7 +_,7 @@ } public void removeTicketsOnClosing() { - ImmutableSet> set = ImmutableSet.of(TicketType.UNKNOWN); + ImmutableSet> set = ImmutableSet.of(TicketType.UNKNOWN, TicketType.POST_TELEPORT, TicketType.FUTURE_AWAIT); // Paper - add additional tickets to preserve ObjectIterator>>> objectIterator = this.tickets.long2ObjectEntrySet().fastIterator(); while (objectIterator.hasNext()) { @@ -329,6 +_,26 @@ public boolean hasTickets() { return !this.tickets.isEmpty(); } + + // CraftBukkit start + public void removeAllTicketsFor(TicketType ticketType, int ticketLevel, T ticketIdentifier) { + Ticket target = new Ticket<>(ticketType, ticketLevel, ticketIdentifier); + + for (java.util.Iterator>>> iterator = this.tickets.long2ObjectEntrySet().fastIterator(); iterator.hasNext();) { + Entry>> entry = iterator.next(); + SortedArraySet> tickets = entry.getValue(); + if (tickets.remove(target)) { + // copied from removeTicket + this.ticketTracker.update(entry.getLongKey(), DistanceManager.getTicketLevelAt(tickets), false); + + // can't use entry after it's removed + if (tickets.isEmpty()) { + iterator.remove(); + } + } + } + } + // CraftBukkit end class ChunkTicketTracker extends ChunkTracker { private static final int MAX_LEVEL = ChunkLevel.MAX_LEVEL + 1;