package net.minecraft.server.level; import com.mojang.logging.LogUtils; import it.unimi.dsi.fastutil.longs.Long2ByteMap; import it.unimi.dsi.fastutil.longs.Long2ByteMaps; import it.unimi.dsi.fastutil.longs.Long2ByteOpenHashMap; import it.unimi.dsi.fastutil.longs.Long2IntMap; import it.unimi.dsi.fastutil.longs.Long2IntMaps; import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; import it.unimi.dsi.fastutil.longs.LongConsumer; import it.unimi.dsi.fastutil.longs.LongIterator; import it.unimi.dsi.fastutil.longs.LongOpenHashSet; import it.unimi.dsi.fastutil.longs.LongSet; import it.unimi.dsi.fastutil.longs.Long2ByteMap.Entry; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import it.unimi.dsi.fastutil.objects.ObjectSet; import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; import javax.annotation.Nullable; import net.minecraft.core.SectionPos; import net.minecraft.util.TriState; import net.minecraft.util.thread.TaskScheduler; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.NaturalSpawner; import net.minecraft.world.level.TicketStorage; import net.minecraft.world.level.chunk.LevelChunk; import org.slf4j.Logger; public abstract class DistanceManager { private static final Logger LOGGER = LogUtils.getLogger(); static final int PLAYER_TICKET_LEVEL = ChunkLevel.byStatus(FullChunkStatus.ENTITY_TICKING); final Long2ObjectMap> playersPerChunk = new Long2ObjectOpenHashMap<>(); private final LoadingChunkTracker loadingChunkTracker; private final SimulationChunkTracker simulationChunkTracker; final TicketStorage ticketStorage; private final DistanceManager.FixedPlayerDistanceChunkTracker naturalSpawnChunkCounter = new DistanceManager.FixedPlayerDistanceChunkTracker(8); private final DistanceManager.PlayerTicketTracker playerTicketManager = new DistanceManager.PlayerTicketTracker(32); protected final Set chunksToUpdateFutures = new ReferenceOpenHashSet<>(); final ThrottlingChunkTaskDispatcher ticketDispatcher; final LongSet ticketsToRelease = new LongOpenHashSet(); final Executor mainThreadExecutor; private int simulationDistance = 10; protected DistanceManager(TicketStorage p_395018_, Executor p_140774_, Executor p_140775_) { this.ticketStorage = p_395018_; this.loadingChunkTracker = new LoadingChunkTracker(this, p_395018_); this.simulationChunkTracker = new SimulationChunkTracker(p_395018_); TaskScheduler taskscheduler = TaskScheduler.wrapExecutor("player ticket throttler", p_140775_); this.ticketDispatcher = new ThrottlingChunkTaskDispatcher(taskscheduler, p_140774_, 4); this.mainThreadExecutor = p_140775_; } protected abstract boolean isChunkToRemove(long p_140779_); @Nullable protected abstract ChunkHolder getChunk(long p_140817_); @Nullable protected abstract ChunkHolder updateChunkScheduling(long p_140780_, int p_140781_, @Nullable ChunkHolder p_140782_, int p_140783_); public boolean runAllUpdates(ChunkMap p_140806_) { this.naturalSpawnChunkCounter.runAllUpdates(); this.simulationChunkTracker.runAllUpdates(); this.playerTicketManager.runAllUpdates(); int i = Integer.MAX_VALUE - this.loadingChunkTracker.runDistanceUpdates(Integer.MAX_VALUE); boolean flag = i != 0; if (flag) { } if (!this.chunksToUpdateFutures.isEmpty()) { for (ChunkHolder chunkholder1 : this.chunksToUpdateFutures) { chunkholder1.updateHighestAllowedStatus(p_140806_); } for (ChunkHolder chunkholder2 : this.chunksToUpdateFutures) { chunkholder2.updateFutures(p_140806_, this.mainThreadExecutor); } this.chunksToUpdateFutures.clear(); return true; } else { if (!this.ticketsToRelease.isEmpty()) { LongIterator longiterator = this.ticketsToRelease.iterator(); while (longiterator.hasNext()) { long j = longiterator.nextLong(); if (this.ticketStorage.getTickets(j).stream().anyMatch(p_390137_ -> p_390137_.getType() == TicketType.PLAYER_LOADING)) { ChunkHolder chunkholder = p_140806_.getUpdatingChunkIfPresent(j); if (chunkholder == null) { throw new IllegalStateException(); } CompletableFuture> completablefuture = chunkholder.getEntityTickingChunkFuture(); completablefuture.thenAccept(p_336030_ -> this.mainThreadExecutor.execute(() -> this.ticketDispatcher.release(j, () -> {}, false))); } } this.ticketsToRelease.clear(); } return flag; } } public void addPlayer(SectionPos p_140803_, ServerPlayer p_140804_) { ChunkPos chunkpos = p_140803_.chunk(); long i = chunkpos.toLong(); this.playersPerChunk.computeIfAbsent(i, p_183921_ -> new ObjectOpenHashSet<>()).add(p_140804_); this.naturalSpawnChunkCounter.update(i, 0, true); this.playerTicketManager.update(i, 0, true); this.ticketStorage.addTicket(new Ticket(TicketType.PLAYER_SIMULATION, this.getPlayerTicketLevel()), chunkpos); } public void removePlayer(SectionPos p_140829_, ServerPlayer p_140830_) { ChunkPos chunkpos = p_140829_.chunk(); long i = chunkpos.toLong(); ObjectSet objectset = this.playersPerChunk.get(i); objectset.remove(p_140830_); if (objectset.isEmpty()) { this.playersPerChunk.remove(i); this.naturalSpawnChunkCounter.update(i, Integer.MAX_VALUE, false); this.playerTicketManager.update(i, Integer.MAX_VALUE, false); this.ticketStorage.removeTicket(new Ticket(TicketType.PLAYER_SIMULATION, this.getPlayerTicketLevel()), chunkpos); } } private int getPlayerTicketLevel() { return Math.max(0, ChunkLevel.byStatus(FullChunkStatus.ENTITY_TICKING) - this.simulationDistance); } public boolean inEntityTickingRange(long p_183914_) { return ChunkLevel.isEntityTicking(this.simulationChunkTracker.getLevel(p_183914_)); } public boolean inBlockTickingRange(long p_183917_) { return ChunkLevel.isBlockTicking(this.simulationChunkTracker.getLevel(p_183917_)); } public int getChunkLevel(long p_392779_, boolean p_394642_) { return p_394642_ ? this.simulationChunkTracker.getLevel(p_392779_) : this.loadingChunkTracker.getLevel(p_392779_); } protected void updatePlayerTickets(int p_140778_) { this.playerTicketManager.updateViewDistance(p_140778_); } public void updateSimulationDistance(int p_183912_) { if (p_183912_ != this.simulationDistance) { this.simulationDistance = p_183912_; this.ticketStorage.replaceTicketLevelOfType(this.getPlayerTicketLevel(), TicketType.PLAYER_SIMULATION); } } public int getNaturalSpawnChunkCount() { this.naturalSpawnChunkCounter.runAllUpdates(); return this.naturalSpawnChunkCounter.chunks.size(); } public TriState hasPlayersNearby(long p_140848_) { this.naturalSpawnChunkCounter.runAllUpdates(); int i = this.naturalSpawnChunkCounter.getLevel(p_140848_); if (i <= NaturalSpawner.INSCRIBED_SQUARE_SPAWN_DISTANCE_CHUNK) { return TriState.TRUE; } else { return i > 8 ? TriState.FALSE : TriState.DEFAULT; } } public void forEachEntityTickingChunk(LongConsumer p_397433_) { for (Entry entry : Long2ByteMaps.fastIterable(this.simulationChunkTracker.chunks)) { byte b0 = entry.getByteValue(); long i = entry.getLongKey(); if (ChunkLevel.isEntityTicking(b0)) { p_397433_.accept(i); } } } public LongIterator getSpawnCandidateChunks() { this.naturalSpawnChunkCounter.runAllUpdates(); return this.naturalSpawnChunkCounter.chunks.keySet().iterator(); } public String getDebugStatus() { return this.ticketDispatcher.getDebugStatus(); } public boolean hasTickets() { return this.ticketStorage.hasTickets(); } class FixedPlayerDistanceChunkTracker extends ChunkTracker { protected final Long2ByteMap chunks = new Long2ByteOpenHashMap(); protected final int maxDistance; protected FixedPlayerDistanceChunkTracker(final int p_140891_) { super(p_140891_ + 2, 16, 256); this.maxDistance = p_140891_; this.chunks.defaultReturnValue((byte)(p_140891_ + 2)); } @Override protected int getLevel(long p_140901_) { return this.chunks.get(p_140901_); } @Override protected void setLevel(long p_140893_, int p_140894_) { byte b0; if (p_140894_ > this.maxDistance) { b0 = this.chunks.remove(p_140893_); } else { b0 = this.chunks.put(p_140893_, (byte)p_140894_); } this.onLevelChange(p_140893_, b0, p_140894_); } protected void onLevelChange(long p_140895_, int p_140896_, int p_140897_) { } @Override protected int getLevelFromSource(long p_140899_) { return this.havePlayer(p_140899_) ? 0 : Integer.MAX_VALUE; } private boolean havePlayer(long p_140903_) { ObjectSet objectset = DistanceManager.this.playersPerChunk.get(p_140903_); return objectset != null && !objectset.isEmpty(); } public void runAllUpdates() { this.runUpdates(Integer.MAX_VALUE); } } class PlayerTicketTracker extends DistanceManager.FixedPlayerDistanceChunkTracker { private int viewDistance; private final Long2IntMap queueLevels = Long2IntMaps.synchronize(new Long2IntOpenHashMap()); private final LongSet toUpdate = new LongOpenHashSet(); protected PlayerTicketTracker(final int p_140910_) { super(p_140910_); this.viewDistance = 0; this.queueLevels.defaultReturnValue(p_140910_ + 2); } @Override protected void onLevelChange(long p_140915_, int p_140916_, int p_140917_) { this.toUpdate.add(p_140915_); } public void updateViewDistance(int p_140913_) { for (Entry entry : this.chunks.long2ByteEntrySet()) { byte b0 = entry.getByteValue(); long i = entry.getLongKey(); this.onLevelChange(i, b0, this.haveTicketFor(b0), b0 <= p_140913_); } this.viewDistance = p_140913_; } private void onLevelChange(long p_140919_, int p_140920_, boolean p_140921_, boolean p_140922_) { if (p_140921_ != p_140922_) { Ticket ticket = new Ticket(TicketType.PLAYER_LOADING, DistanceManager.PLAYER_TICKET_LEVEL); if (p_140922_) { DistanceManager.this.ticketDispatcher.submit(() -> DistanceManager.this.mainThreadExecutor.execute(() -> { if (this.haveTicketFor(this.getLevel(p_140919_))) { DistanceManager.this.ticketStorage.addTicket(p_140919_, ticket); DistanceManager.this.ticketsToRelease.add(p_140919_); } else { DistanceManager.this.ticketDispatcher.release(p_140919_, () -> {}, false); } }), p_140919_, () -> p_140920_); } else { DistanceManager.this.ticketDispatcher .release( p_140919_, () -> DistanceManager.this.mainThreadExecutor.execute(() -> DistanceManager.this.ticketStorage.removeTicket(p_140919_, ticket)), true ); } } } @Override public void runAllUpdates() { super.runAllUpdates(); if (!this.toUpdate.isEmpty()) { LongIterator longiterator = this.toUpdate.iterator(); while (longiterator.hasNext()) { long i = longiterator.nextLong(); int j = this.queueLevels.get(i); int k = this.getLevel(i); if (j != k) { DistanceManager.this.ticketDispatcher.onLevelChange(new ChunkPos(i), () -> this.queueLevels.get(i), k, p_140928_ -> { if (p_140928_ >= this.queueLevels.defaultReturnValue()) { this.queueLevels.remove(i); } else { this.queueLevels.put(i, p_140928_); } }); this.onLevelChange(i, k, this.haveTicketFor(j), this.haveTicketFor(k)); } } this.toUpdate.clear(); } } private boolean haveTicketFor(int p_140933_) { return p_140933_ <= this.viewDistance; } } }