/* * Decompiled with CFR 0.152. * * Could not load the following classes: * com.mojang.datafixers.kinds.App * com.mojang.datafixers.kinds.Applicative * com.mojang.datafixers.util.Pair * com.mojang.logging.LogUtils * com.mojang.serialization.Codec * com.mojang.serialization.MapCodec * com.mojang.serialization.codecs.RecordCodecBuilder * it.unimi.dsi.fastutil.longs.Long2ObjectMap$Entry * it.unimi.dsi.fastutil.longs.Long2ObjectMaps * it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap * it.unimi.dsi.fastutil.longs.LongOpenHashSet * it.unimi.dsi.fastutil.longs.LongSet * it.unimi.dsi.fastutil.objects.ObjectArrayList * it.unimi.dsi.fastutil.objects.ObjectIterator * org.jspecify.annotations.Nullable * org.slf4j.Logger */ package net.minecraft.world.level; import com.mojang.datafixers.kinds.App; import com.mojang.datafixers.kinds.Applicative; import com.mojang.datafixers.util.Pair; import com.mojang.logging.LogUtils; import com.mojang.serialization.Codec; import com.mojang.serialization.MapCodec; import com.mojang.serialization.codecs.RecordCodecBuilder; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.longs.Long2ObjectMaps; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; import it.unimi.dsi.fastutil.longs.LongOpenHashSet; import it.unimi.dsi.fastutil.longs.LongSet; import it.unimi.dsi.fastutil.objects.ObjectArrayList; import it.unimi.dsi.fastutil.objects.ObjectIterator; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.function.BiConsumer; import java.util.function.Predicate; import net.minecraft.SharedConstants; import net.minecraft.server.level.ChunkHolder; import net.minecraft.server.level.ChunkLevel; import net.minecraft.server.level.ChunkMap; import net.minecraft.server.level.FullChunkStatus; import net.minecraft.server.level.Ticket; import net.minecraft.server.level.TicketType; import net.minecraft.util.datafix.DataFixTypes; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.saveddata.SavedData; import net.minecraft.world.level.saveddata.SavedDataType; import org.jspecify.annotations.Nullable; import org.slf4j.Logger; public class TicketStorage extends SavedData { private static final int INITIAL_TICKET_LIST_CAPACITY = 4; private static final Logger LOGGER = LogUtils.getLogger(); private static final Codec> TICKET_ENTRY = Codec.mapPair((MapCodec)ChunkPos.CODEC.fieldOf("chunk_pos"), Ticket.CODEC).codec(); public static final Codec CODEC = RecordCodecBuilder.create(i -> i.group((App)TICKET_ENTRY.listOf().optionalFieldOf("tickets", List.of()).forGetter(TicketStorage::packTickets)).apply((Applicative)i, TicketStorage::fromPacked)); public static final SavedDataType TYPE = new SavedDataType("chunks", TicketStorage::new, CODEC, DataFixTypes.SAVED_DATA_FORCED_CHUNKS); private final Long2ObjectOpenHashMap> tickets; private final Long2ObjectOpenHashMap> deactivatedTickets; private LongSet chunksWithForcedTickets = new LongOpenHashSet(); private @Nullable ChunkUpdated loadingChunkUpdatedListener; private @Nullable ChunkUpdated simulationChunkUpdatedListener; private TicketStorage(Long2ObjectOpenHashMap> tickets, Long2ObjectOpenHashMap> deactivatedTickets) { this.tickets = tickets; this.deactivatedTickets = deactivatedTickets; this.updateForcedChunks(); } public TicketStorage() { this((Long2ObjectOpenHashMap>)new Long2ObjectOpenHashMap(4), (Long2ObjectOpenHashMap>)new Long2ObjectOpenHashMap()); } private static TicketStorage fromPacked(List> tickets) { Long2ObjectOpenHashMap ticketsToLoad = new Long2ObjectOpenHashMap(); for (Pair ticket : tickets) { ChunkPos pos = (ChunkPos)ticket.getFirst(); List ticketsInChunk = (List)ticketsToLoad.computeIfAbsent(pos.toLong(), k -> new ObjectArrayList(4)); ticketsInChunk.add((Ticket)ticket.getSecond()); } return new TicketStorage((Long2ObjectOpenHashMap>)new Long2ObjectOpenHashMap(4), (Long2ObjectOpenHashMap>)ticketsToLoad); } private List> packTickets() { ArrayList> tickets = new ArrayList>(); this.forEachTicket((pos, ticket) -> { if (ticket.getType().persist()) { tickets.add(new Pair(pos, ticket)); } }); return tickets; } private void forEachTicket(BiConsumer output) { TicketStorage.forEachTicket(output, this.tickets); TicketStorage.forEachTicket(output, this.deactivatedTickets); } private static void forEachTicket(BiConsumer output, Long2ObjectOpenHashMap> tickets) { for (Long2ObjectMap.Entry entry : Long2ObjectMaps.fastIterable(tickets)) { ChunkPos chunkPos = new ChunkPos(entry.getLongKey()); for (Ticket ticket : (List)entry.getValue()) { output.accept(chunkPos, ticket); } } } public void activateAllDeactivatedTickets() { for (Long2ObjectMap.Entry entry : Long2ObjectMaps.fastIterable(this.deactivatedTickets)) { for (Ticket ticket : (List)entry.getValue()) { this.addTicket(entry.getLongKey(), ticket); } } this.deactivatedTickets.clear(); } public void setLoadingChunkUpdatedListener(@Nullable ChunkUpdated loadingChunkUpdatedListener) { this.loadingChunkUpdatedListener = loadingChunkUpdatedListener; } public void setSimulationChunkUpdatedListener(@Nullable ChunkUpdated simulationChunkUpdatedListener) { this.simulationChunkUpdatedListener = simulationChunkUpdatedListener; } public boolean hasTickets() { return !this.tickets.isEmpty(); } public boolean shouldKeepDimensionActive() { for (List group : this.tickets.values()) { for (Ticket ticket : group) { if (!ticket.getType().shouldKeepDimensionActive()) continue; return true; } } return false; } public List getTickets(long key) { return (List)this.tickets.getOrDefault(key, List.of()); } private List getOrCreateTickets(long key) { return (List)this.tickets.computeIfAbsent(key, k -> new ObjectArrayList(4)); } public void addTicketWithRadius(TicketType type, ChunkPos chunkPos, int radius) { Ticket ticket = new Ticket(type, ChunkLevel.byStatus(FullChunkStatus.FULL) - radius); this.addTicket(chunkPos.toLong(), ticket); } public void addTicket(Ticket ticket, ChunkPos chunkPos) { this.addTicket(chunkPos.toLong(), ticket); } public boolean addTicket(long key, Ticket ticket) { List tickets = this.getOrCreateTickets(key); for (Ticket t : tickets) { if (!TicketStorage.isTicketSameTypeAndLevel(ticket, t)) continue; t.resetTicksLeft(); this.setDirty(); return false; } int oldSimulationTicketLevel = TicketStorage.getTicketLevelAt(tickets, true); int oldLoadingTicketLevel = TicketStorage.getTicketLevelAt(tickets, false); tickets.add(ticket); if (SharedConstants.DEBUG_VERBOSE_SERVER_EVENTS) { LOGGER.debug("ATI {} {}", (Object)new ChunkPos(key), (Object)ticket); } if (ticket.getType().doesSimulate() && ticket.getTicketLevel() < oldSimulationTicketLevel && this.simulationChunkUpdatedListener != null) { this.simulationChunkUpdatedListener.update(key, ticket.getTicketLevel(), true); } if (ticket.getType().doesLoad() && ticket.getTicketLevel() < oldLoadingTicketLevel && this.loadingChunkUpdatedListener != null) { this.loadingChunkUpdatedListener.update(key, ticket.getTicketLevel(), true); } if (ticket.getType().equals(TicketType.FORCED)) { this.chunksWithForcedTickets.add(key); } this.setDirty(); return true; } private static boolean isTicketSameTypeAndLevel(Ticket ticket, Ticket t) { return t.getType() == ticket.getType() && t.getTicketLevel() == ticket.getTicketLevel(); } public int getTicketLevelAt(long key, boolean simulation) { return TicketStorage.getTicketLevelAt(this.getTickets(key), simulation); } private static int getTicketLevelAt(List tickets, boolean simulation) { Ticket lowestTicket = TicketStorage.getLowestTicket(tickets, simulation); return lowestTicket == null ? ChunkLevel.MAX_LEVEL + 1 : lowestTicket.getTicketLevel(); } private static @Nullable Ticket getLowestTicket(@Nullable List tickets, boolean simulation) { if (tickets == null) { return null; } Ticket t = null; for (Ticket ticket : tickets) { if (t != null && ticket.getTicketLevel() >= t.getTicketLevel()) continue; if (simulation && ticket.getType().doesSimulate()) { t = ticket; continue; } if (simulation || !ticket.getType().doesLoad()) continue; t = ticket; } return t; } public void removeTicketWithRadius(TicketType type, ChunkPos chunkPos, int radius) { Ticket ticket = new Ticket(type, ChunkLevel.byStatus(FullChunkStatus.FULL) - radius); this.removeTicket(chunkPos.toLong(), ticket); } public void removeTicket(Ticket ticket, ChunkPos chunkPos) { this.removeTicket(chunkPos.toLong(), ticket); } public boolean removeTicket(long key, Ticket ticket) { List tickets = (List)this.tickets.get(key); if (tickets == null) { return false; } boolean found = false; Iterator iterator = tickets.iterator(); while (iterator.hasNext()) { Ticket t = (Ticket)iterator.next(); if (!TicketStorage.isTicketSameTypeAndLevel(ticket, t)) continue; iterator.remove(); if (SharedConstants.DEBUG_VERBOSE_SERVER_EVENTS) { LOGGER.debug("RTI {} {}", (Object)new ChunkPos(key), (Object)t); } found = true; break; } if (!found) { return false; } if (tickets.isEmpty()) { this.tickets.remove(key); } if (ticket.getType().doesSimulate() && this.simulationChunkUpdatedListener != null) { this.simulationChunkUpdatedListener.update(key, TicketStorage.getTicketLevelAt(tickets, true), false); } if (ticket.getType().doesLoad() && this.loadingChunkUpdatedListener != null) { this.loadingChunkUpdatedListener.update(key, TicketStorage.getTicketLevelAt(tickets, false), false); } if (ticket.getType().equals(TicketType.FORCED)) { this.updateForcedChunks(); } this.setDirty(); return true; } private void updateForcedChunks() { this.chunksWithForcedTickets = this.getAllChunksWithTicketThat(t -> t.getType().equals(TicketType.FORCED)); } public String getTicketDebugString(long key, boolean simulation) { List tickets = this.getTickets(key); Ticket lowestTicket = TicketStorage.getLowestTicket(tickets, simulation); return lowestTicket == null ? "no_ticket" : lowestTicket.toString(); } public void purgeStaleTickets(ChunkMap chunkMap) { this.removeTicketIf((ticket, chunkPos) -> { if (this.canTicketExpire(chunkMap, ticket, chunkPos)) { ticket.decreaseTicksLeft(); return ticket.isTimedOut(); } return false; }, null); this.setDirty(); } private boolean canTicketExpire(ChunkMap chunkMap, Ticket ticket, long chunkPos) { if (!ticket.getType().hasTimeout()) { return false; } if (ticket.getType().canExpireIfUnloaded()) { return true; } ChunkHolder updatingChunk = chunkMap.getUpdatingChunkIfPresent(chunkPos); return updatingChunk == null || updatingChunk.isReadyForSaving(); } public void deactivateTicketsOnClosing() { this.removeTicketIf((ticket, chunkPos) -> ticket.getType() != TicketType.UNKNOWN, this.deactivatedTickets); } public void removeTicketIf(TicketPredicate predicate, @Nullable Long2ObjectOpenHashMap> removedTickets) { ObjectIterator ticketsPerChunkIterator = this.tickets.long2ObjectEntrySet().fastIterator(); boolean removedForced = false; while (ticketsPerChunkIterator.hasNext()) { Long2ObjectMap.Entry entry = (Long2ObjectMap.Entry)ticketsPerChunkIterator.next(); Iterator chunkTicketsIterator = ((List)entry.getValue()).iterator(); long chunkPos = entry.getLongKey(); boolean removedSimulation = false; boolean removedLoading = false; while (chunkTicketsIterator.hasNext()) { Ticket ticket = (Ticket)chunkTicketsIterator.next(); if (!predicate.test(ticket, chunkPos)) continue; if (removedTickets != null) { List tickets = (List)removedTickets.computeIfAbsent(chunkPos, k -> new ObjectArrayList(((List)entry.getValue()).size())); tickets.add(ticket); } chunkTicketsIterator.remove(); if (ticket.getType().doesLoad()) { removedLoading = true; } if (ticket.getType().doesSimulate()) { removedSimulation = true; } if (!ticket.getType().equals(TicketType.FORCED)) continue; removedForced = true; } if (!removedLoading && !removedSimulation) continue; if (removedLoading && this.loadingChunkUpdatedListener != null) { this.loadingChunkUpdatedListener.update(chunkPos, TicketStorage.getTicketLevelAt((List)entry.getValue(), false), false); } if (removedSimulation && this.simulationChunkUpdatedListener != null) { this.simulationChunkUpdatedListener.update(chunkPos, TicketStorage.getTicketLevelAt((List)entry.getValue(), true), false); } this.setDirty(); if (!((List)entry.getValue()).isEmpty()) continue; ticketsPerChunkIterator.remove(); } if (removedForced) { this.updateForcedChunks(); } } public void replaceTicketLevelOfType(int newLevel, TicketType ticketType) { ArrayList affectedTickets = new ArrayList(); for (Long2ObjectMap.Entry entry : this.tickets.long2ObjectEntrySet()) { for (Ticket ticket : (List)entry.getValue()) { if (ticket.getType() != ticketType) continue; affectedTickets.add(Pair.of((Object)ticket, (Object)entry.getLongKey())); } } for (Pair pair : affectedTickets) { Ticket ticket; Long key = (Long)pair.getSecond(); ticket = (Ticket)pair.getFirst(); this.removeTicket(key, ticket); TicketType type = ticket.getType(); this.addTicket(key, new Ticket(type, newLevel)); } } public boolean updateChunkForced(ChunkPos chunkPos, boolean forced) { Ticket ticket = new Ticket(TicketType.FORCED, ChunkMap.FORCED_TICKET_LEVEL); if (forced) { return this.addTicket(chunkPos.toLong(), ticket); } return this.removeTicket(chunkPos.toLong(), ticket); } public LongSet getForceLoadedChunks() { return this.chunksWithForcedTickets; } private LongSet getAllChunksWithTicketThat(Predicate ticketCheck) { LongOpenHashSet chunks = new LongOpenHashSet(); block0: for (Long2ObjectMap.Entry entry : Long2ObjectMaps.fastIterable(this.tickets)) { for (Ticket ticket : (List)entry.getValue()) { if (!ticketCheck.test(ticket)) continue; chunks.add(entry.getLongKey()); continue block0; } } return chunks; } @FunctionalInterface public static interface ChunkUpdated { public void update(long var1, int var3, boolean var4); } public static interface TicketPredicate { public boolean test(Ticket var1, long var2); } }