package net.minecraft.world.level; import com.mojang.datafixers.util.Pair; import com.mojang.logging.LogUtils; import com.mojang.serialization.Codec; import com.mojang.serialization.codecs.RecordCodecBuilder; import com.mojang.serialization.codecs.RecordCodecBuilder.Instance; 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.longs.Long2ObjectMap.Entry; 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 javax.annotation.Nullable; 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.saveddata.SavedData; import net.minecraft.world.level.saveddata.SavedDataType; 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(ChunkPos.CODEC.fieldOf("chunk_pos"), Ticket.CODEC).codec(); public static final Codec CODEC = RecordCodecBuilder.create( p_395442_ -> p_395442_.group(TICKET_ENTRY.listOf().optionalFieldOf("tickets", List.of()).forGetter(TicketStorage::packTickets)) .apply(p_395442_, 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(); @Nullable private TicketStorage.ChunkUpdated loadingChunkUpdatedListener; @Nullable private TicketStorage.ChunkUpdated simulationChunkUpdatedListener; private TicketStorage(Long2ObjectOpenHashMap> p_392328_, Long2ObjectOpenHashMap> p_396095_) { this.tickets = p_392328_; this.deactivatedTickets = p_396095_; this.updateForcedChunks(); } public TicketStorage() { this(new Long2ObjectOpenHashMap<>(4), new Long2ObjectOpenHashMap<>()); } private static TicketStorage fromPacked(List> p_392693_) { Long2ObjectOpenHashMap> long2objectopenhashmap = new Long2ObjectOpenHashMap<>(); for (Pair pair : p_392693_) { ChunkPos chunkpos = pair.getFirst(); List list = long2objectopenhashmap.computeIfAbsent(chunkpos.toLong(), p_396965_ -> new ObjectArrayList<>(4)); list.add(pair.getSecond()); } return new TicketStorage(new Long2ObjectOpenHashMap<>(4), long2objectopenhashmap); } private List> packTickets() { List> list = new ArrayList<>(); this.forEachTicket((p_397558_, p_396676_) -> { if (p_396676_.getType().persist()) { list.add(new Pair<>(p_397558_, p_396676_)); } }); return list; } private void forEachTicket(BiConsumer p_394872_) { forEachTicket(p_394872_, this.tickets); forEachTicket(p_394872_, this.deactivatedTickets); } private static void forEachTicket(BiConsumer p_392035_, Long2ObjectOpenHashMap> p_392917_) { for (Entry> entry : Long2ObjectMaps.fastIterable(p_392917_)) { ChunkPos chunkpos = new ChunkPos(entry.getLongKey()); for (Ticket ticket : entry.getValue()) { p_392035_.accept(chunkpos, ticket); } } } public void activateAllDeactivatedTickets() { for (Entry> entry : Long2ObjectMaps.fastIterable(this.deactivatedTickets)) { for (Ticket ticket : entry.getValue()) { this.addTicket(entry.getLongKey(), ticket); } } this.deactivatedTickets.clear(); } public void setLoadingChunkUpdatedListener(@Nullable TicketStorage.ChunkUpdated p_395306_) { this.loadingChunkUpdatedListener = p_395306_; } public void setSimulationChunkUpdatedListener(@Nullable TicketStorage.ChunkUpdated p_394573_) { this.simulationChunkUpdatedListener = p_394573_; } public boolean hasTickets() { return !this.tickets.isEmpty(); } public List getTickets(long p_394970_) { return this.tickets.getOrDefault(p_394970_, List.of()); } private List getOrCreateTickets(long p_391734_) { return this.tickets.computeIfAbsent(p_391734_, p_395686_ -> new ObjectArrayList<>(4)); } public void addTicketWithRadius(TicketType p_397648_, ChunkPos p_392624_, int p_397085_) { Ticket ticket = new Ticket(p_397648_, ChunkLevel.byStatus(FullChunkStatus.FULL) - p_397085_); this.addTicket(p_392624_.toLong(), ticket); } public void addTicket(Ticket p_391314_, ChunkPos p_393095_) { this.addTicket(p_393095_.toLong(), p_391314_); } public boolean addTicket(long p_391964_, Ticket p_394243_) { List list = this.getOrCreateTickets(p_391964_); for (Ticket ticket : list) { if (isTicketSameTypeAndLevel(p_394243_, ticket)) { ticket.resetTicksLeft(); this.setDirty(); return false; } } int i = getTicketLevelAt(list, true); int j = getTicketLevelAt(list, false); list.add(p_394243_); if (p_394243_.getType().doesSimulate() && p_394243_.getTicketLevel() < i && this.simulationChunkUpdatedListener != null) { this.simulationChunkUpdatedListener.update(p_391964_, p_394243_.getTicketLevel(), true); } if (p_394243_.getType().doesLoad() && p_394243_.getTicketLevel() < j && this.loadingChunkUpdatedListener != null) { this.loadingChunkUpdatedListener.update(p_391964_, p_394243_.getTicketLevel(), true); } if (p_394243_.getType().equals(TicketType.FORCED)) { this.chunksWithForcedTickets.add(p_391964_); } this.setDirty(); return true; } private static boolean isTicketSameTypeAndLevel(Ticket p_394527_, Ticket p_393032_) { return p_393032_.getType() == p_394527_.getType() && p_393032_.getTicketLevel() == p_394527_.getTicketLevel(); } public int getTicketLevelAt(long p_397585_, boolean p_392636_) { return getTicketLevelAt(this.getTickets(p_397585_), p_392636_); } private static int getTicketLevelAt(List p_394180_, boolean p_396295_) { Ticket ticket = getLowestTicket(p_394180_, p_396295_); return ticket == null ? ChunkLevel.MAX_LEVEL + 1 : ticket.getTicketLevel(); } @Nullable private static Ticket getLowestTicket(@Nullable List p_394356_, boolean p_394342_) { if (p_394356_ == null) { return null; } else { Ticket ticket = null; for (Ticket ticket1 : p_394356_) { if (ticket == null || ticket1.getTicketLevel() < ticket.getTicketLevel()) { if (p_394342_ && ticket1.getType().doesSimulate()) { ticket = ticket1; } else if (!p_394342_ && ticket1.getType().doesLoad()) { ticket = ticket1; } } } return ticket; } } public void removeTicketWithRadius(TicketType p_393730_, ChunkPos p_393037_, int p_393610_) { Ticket ticket = new Ticket(p_393730_, ChunkLevel.byStatus(FullChunkStatus.FULL) - p_393610_); this.removeTicket(p_393037_.toLong(), ticket); } public void removeTicket(Ticket p_392695_, ChunkPos p_391186_) { this.removeTicket(p_391186_.toLong(), p_392695_); } public boolean removeTicket(long p_397743_, Ticket p_395417_) { List list = this.tickets.get(p_397743_); if (list == null) { return false; } else { boolean flag = false; Iterator iterator = list.iterator(); while (iterator.hasNext()) { Ticket ticket = iterator.next(); if (isTicketSameTypeAndLevel(p_395417_, ticket)) { iterator.remove(); flag = true; break; } } if (!flag) { return false; } else { if (list.isEmpty()) { this.tickets.remove(p_397743_); } if (p_395417_.getType().doesSimulate() && this.simulationChunkUpdatedListener != null) { this.simulationChunkUpdatedListener.update(p_397743_, getTicketLevelAt(list, true), false); } if (p_395417_.getType().doesLoad() && this.loadingChunkUpdatedListener != null) { this.loadingChunkUpdatedListener.update(p_397743_, getTicketLevelAt(list, false), false); } if (p_395417_.getType().equals(TicketType.FORCED)) { this.updateForcedChunks(); } this.setDirty(); return true; } } } private void updateForcedChunks() { this.chunksWithForcedTickets = this.getAllChunksWithTicketThat(p_394883_ -> p_394883_.getType().equals(TicketType.FORCED)); } public String getTicketDebugString(long p_393984_, boolean p_394252_) { List list = this.getTickets(p_393984_); Ticket ticket = getLowestTicket(list, p_394252_); return ticket == null ? "no_ticket" : ticket.toString(); } public void purgeStaleTickets() { this.removeTicketIf(p_395227_ -> { p_395227_.decreaseTicksLeft(); return p_395227_.isTimedOut(); }, null); this.setDirty(); } public void deactivateTicketsOnClosing() { this.removeTicketIf(p_392990_ -> p_392990_.getType() != TicketType.UNKNOWN, this.deactivatedTickets); } public void removeTicketIf(Predicate p_394101_, @Nullable Long2ObjectOpenHashMap> p_396309_) { ObjectIterator>> objectiterator = this.tickets.long2ObjectEntrySet().fastIterator(); boolean flag = false; while (objectiterator.hasNext()) { Entry> entry = objectiterator.next(); Iterator iterator = entry.getValue().iterator(); boolean flag1 = false; boolean flag2 = false; while (iterator.hasNext()) { Ticket ticket = iterator.next(); if (p_394101_.test(ticket)) { if (p_396309_ != null) { List list = p_396309_.computeIfAbsent(entry.getLongKey(), p_394290_ -> new ObjectArrayList<>(entry.getValue().size())); list.add(ticket); } iterator.remove(); if (ticket.getType().doesLoad()) { flag2 = true; } if (ticket.getType().doesSimulate()) { flag1 = true; } if (ticket.getType().equals(TicketType.FORCED)) { flag = true; } } } if (flag2 || flag1) { if (flag2 && this.loadingChunkUpdatedListener != null) { this.loadingChunkUpdatedListener.update(entry.getLongKey(), getTicketLevelAt(entry.getValue(), false), false); } if (flag1 && this.simulationChunkUpdatedListener != null) { this.simulationChunkUpdatedListener.update(entry.getLongKey(), getTicketLevelAt(entry.getValue(), true), false); } this.setDirty(); if (entry.getValue().isEmpty()) { objectiterator.remove(); } } } if (flag) { this.updateForcedChunks(); } } public void replaceTicketLevelOfType(int p_391433_, TicketType p_396214_) { List> list = new ArrayList<>(); for (Entry> entry : this.tickets.long2ObjectEntrySet()) { for (Ticket ticket : entry.getValue()) { if (ticket.getType() == p_396214_) { list.add(Pair.of(ticket, entry.getLongKey())); } } } for (Pair pair : list) { Long olong = pair.getSecond(); Ticket ticket1 = pair.getFirst(); this.removeTicket(olong, ticket1); TicketType tickettype = ticket1.getType(); this.addTicket(olong, new Ticket(tickettype, p_391433_)); } } public boolean updateChunkForced(ChunkPos p_392116_, boolean p_394247_) { Ticket ticket = new Ticket(TicketType.FORCED, ChunkMap.FORCED_TICKET_LEVEL); return p_394247_ ? this.addTicket(p_392116_.toLong(), ticket) : this.removeTicket(p_392116_.toLong(), ticket); } public LongSet getForceLoadedChunks() { return this.chunksWithForcedTickets; } private LongSet getAllChunksWithTicketThat(Predicate p_397128_) { LongOpenHashSet longopenhashset = new LongOpenHashSet(); for (Entry> entry : Long2ObjectMaps.fastIterable(this.tickets)) { for (Ticket ticket : entry.getValue()) { if (p_397128_.test(ticket)) { longopenhashset.add(entry.getLongKey()); break; } } } return longopenhashset; } @FunctionalInterface public interface ChunkUpdated { void update(long p_392076_, int p_391279_, boolean p_392790_); } }