/* * Decompiled with CFR 0.152. * * Could not load the following classes: * com.mojang.logging.LogUtils * it.unimi.dsi.fastutil.longs.Long2ByteMap * it.unimi.dsi.fastutil.longs.Long2ByteMap$Entry * it.unimi.dsi.fastutil.longs.Long2ByteMaps * it.unimi.dsi.fastutil.longs.Long2ByteOpenHashMap * it.unimi.dsi.fastutil.longs.Long2IntMap * it.unimi.dsi.fastutil.longs.Long2IntMaps * it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap * it.unimi.dsi.fastutil.longs.Long2ObjectMap * it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap * it.unimi.dsi.fastutil.longs.LongConsumer * it.unimi.dsi.fastutil.longs.LongIterator * it.unimi.dsi.fastutil.longs.LongOpenHashSet * it.unimi.dsi.fastutil.longs.LongSet * it.unimi.dsi.fastutil.objects.ObjectOpenHashSet * it.unimi.dsi.fastutil.objects.ObjectSet * it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet * org.jspecify.annotations.Nullable * org.slf4j.Logger */ 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.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 net.minecraft.SharedConstants; import net.minecraft.core.SectionPos; import net.minecraft.server.level.ChunkHolder; import net.minecraft.server.level.ChunkLevel; import net.minecraft.server.level.ChunkMap; import net.minecraft.server.level.ChunkResult; import net.minecraft.server.level.ChunkTracker; import net.minecraft.server.level.FullChunkStatus; import net.minecraft.server.level.LoadingChunkTracker; import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.level.SimulationChunkTracker; import net.minecraft.server.level.ThrottlingChunkTaskDispatcher; import net.minecraft.server.level.Ticket; import net.minecraft.server.level.TicketType; 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.jspecify.annotations.Nullable; import org.slf4j.Logger; public abstract class DistanceManager { private static final Logger LOGGER = LogUtils.getLogger(); private static final int PLAYER_TICKET_LEVEL = ChunkLevel.byStatus(FullChunkStatus.ENTITY_TICKING); private final Long2ObjectMap> playersPerChunk = new Long2ObjectOpenHashMap(); private final LoadingChunkTracker loadingChunkTracker; private final SimulationChunkTracker simulationChunkTracker; private final TicketStorage ticketStorage; private final FixedPlayerDistanceChunkTracker naturalSpawnChunkCounter = new FixedPlayerDistanceChunkTracker(8); private final PlayerTicketTracker playerTicketManager = new PlayerTicketTracker(32); protected final Set chunksToUpdateFutures = new ReferenceOpenHashSet(); private final ThrottlingChunkTaskDispatcher ticketDispatcher; private final LongSet ticketsToRelease = new LongOpenHashSet(); private final Executor mainThreadExecutor; private int simulationDistance = 10; protected DistanceManager(TicketStorage ticketStorage, Executor executor, Executor mainThreadExecutor) { this.ticketStorage = ticketStorage; this.loadingChunkTracker = new LoadingChunkTracker(this, ticketStorage); this.simulationChunkTracker = new SimulationChunkTracker(ticketStorage); TaskScheduler mainThreadTaskScheduler = TaskScheduler.wrapExecutor("player ticket throttler", mainThreadExecutor); this.ticketDispatcher = new ThrottlingChunkTaskDispatcher(mainThreadTaskScheduler, executor, 4); this.mainThreadExecutor = mainThreadExecutor; } protected abstract boolean isChunkToRemove(long var1); protected abstract @Nullable ChunkHolder getChunk(long var1); protected abstract @Nullable ChunkHolder updateChunkScheduling(long var1, int var3, @Nullable ChunkHolder var4, int var5); public boolean runAllUpdates(ChunkMap scheduler) { boolean updated; this.naturalSpawnChunkCounter.runAllUpdates(); this.simulationChunkTracker.runAllUpdates(); this.playerTicketManager.runAllUpdates(); int updates = Integer.MAX_VALUE - this.loadingChunkTracker.runDistanceUpdates(Integer.MAX_VALUE); boolean bl = updated = updates != 0; if (updated && SharedConstants.DEBUG_VERBOSE_SERVER_EVENTS) { LOGGER.debug("DMU {}", (Object)updates); } if (!this.chunksToUpdateFutures.isEmpty()) { for (ChunkHolder chunksToUpdateFuture : this.chunksToUpdateFutures) { chunksToUpdateFuture.updateHighestAllowedStatus(scheduler); } for (ChunkHolder chunkHolder : this.chunksToUpdateFutures) { chunkHolder.updateFutures(scheduler, this.mainThreadExecutor); } this.chunksToUpdateFutures.clear(); return true; } if (!this.ticketsToRelease.isEmpty()) { LongIterator iterator = this.ticketsToRelease.iterator(); while (iterator.hasNext()) { long pos = iterator.nextLong(); if (!this.ticketStorage.getTickets(pos).stream().anyMatch(t -> t.getType() == TicketType.PLAYER_LOADING)) continue; ChunkHolder chunk = scheduler.getUpdatingChunkIfPresent(pos); if (chunk == null) { throw new IllegalStateException(); } CompletableFuture> future = chunk.getEntityTickingChunkFuture(); future.thenAccept(c -> this.mainThreadExecutor.execute(() -> this.ticketDispatcher.release(pos, () -> {}, false))); } this.ticketsToRelease.clear(); } return updated; } public void addPlayer(SectionPos pos, ServerPlayer player) { ChunkPos chunk = pos.chunk(); long chunkPos = chunk.toLong(); ((ObjectSet)this.playersPerChunk.computeIfAbsent(chunkPos, k -> new ObjectOpenHashSet())).add((Object)player); this.naturalSpawnChunkCounter.update(chunkPos, 0, true); this.playerTicketManager.update(chunkPos, 0, true); this.ticketStorage.addTicket(new Ticket(TicketType.PLAYER_SIMULATION, this.getPlayerTicketLevel()), chunk); } public void removePlayer(SectionPos pos, ServerPlayer player) { ChunkPos chunk = pos.chunk(); long chunkPos = chunk.toLong(); ObjectSet chunkPlayers = (ObjectSet)this.playersPerChunk.get(chunkPos); chunkPlayers.remove((Object)player); if (chunkPlayers.isEmpty()) { this.playersPerChunk.remove(chunkPos); this.naturalSpawnChunkCounter.update(chunkPos, Integer.MAX_VALUE, false); this.playerTicketManager.update(chunkPos, Integer.MAX_VALUE, false); this.ticketStorage.removeTicket(new Ticket(TicketType.PLAYER_SIMULATION, this.getPlayerTicketLevel()), chunk); } } private int getPlayerTicketLevel() { return Math.max(0, ChunkLevel.byStatus(FullChunkStatus.ENTITY_TICKING) - this.simulationDistance); } public boolean inEntityTickingRange(long key) { return ChunkLevel.isEntityTicking(this.simulationChunkTracker.getLevel(key)); } public boolean inBlockTickingRange(long key) { return ChunkLevel.isBlockTicking(this.simulationChunkTracker.getLevel(key)); } public int getChunkLevel(long key, boolean simulation) { if (simulation) { return this.simulationChunkTracker.getLevel(key); } return this.loadingChunkTracker.getLevel(key); } protected void updatePlayerTickets(int viewDistance) { this.playerTicketManager.updateViewDistance(viewDistance); } public void updateSimulationDistance(int newDistance) { if (newDistance != this.simulationDistance) { this.simulationDistance = newDistance; this.ticketStorage.replaceTicketLevelOfType(this.getPlayerTicketLevel(), TicketType.PLAYER_SIMULATION); } } public int getNaturalSpawnChunkCount() { this.naturalSpawnChunkCounter.runAllUpdates(); return this.naturalSpawnChunkCounter.chunks.size(); } public TriState hasPlayersNearby(long pos) { this.naturalSpawnChunkCounter.runAllUpdates(); int distance = this.naturalSpawnChunkCounter.getLevel(pos); if (distance <= NaturalSpawner.INSCRIBED_SQUARE_SPAWN_DISTANCE_CHUNK) { return TriState.TRUE; } if (distance > 8) { return TriState.FALSE; } return TriState.DEFAULT; } public void forEachEntityTickingChunk(LongConsumer consumer) { for (Long2ByteMap.Entry entry : Long2ByteMaps.fastIterable((Long2ByteMap)this.simulationChunkTracker.chunks)) { byte level = entry.getByteValue(); long key = entry.getLongKey(); if (!ChunkLevel.isEntityTicking(level)) continue; consumer.accept(key); } } 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(); } private class FixedPlayerDistanceChunkTracker extends ChunkTracker { protected final Long2ByteMap chunks; protected final int maxDistance; protected FixedPlayerDistanceChunkTracker(int maxDistance) { super(maxDistance + 2, 16, 256); this.chunks = new Long2ByteOpenHashMap(); this.maxDistance = maxDistance; this.chunks.defaultReturnValue((byte)(maxDistance + 2)); } @Override protected int getLevel(long node) { return this.chunks.get(node); } @Override protected void setLevel(long node, int level) { byte oldLevel = level > this.maxDistance ? this.chunks.remove(node) : this.chunks.put(node, (byte)level); this.onLevelChange(node, oldLevel, level); } protected void onLevelChange(long node, int oldLevel, int level) { } @Override protected int getLevelFromSource(long to) { return this.havePlayer(to) ? 0 : Integer.MAX_VALUE; } private boolean havePlayer(long chunkPos) { ObjectSet players = (ObjectSet)DistanceManager.this.playersPerChunk.get(chunkPos); return players != null && !players.isEmpty(); } public void runAllUpdates() { this.runUpdates(Integer.MAX_VALUE); } } private class PlayerTicketTracker extends FixedPlayerDistanceChunkTracker { private int viewDistance; private final Long2IntMap queueLevels; private final LongSet toUpdate; protected PlayerTicketTracker(int maxDistance) { super(maxDistance); this.queueLevels = Long2IntMaps.synchronize((Long2IntMap)new Long2IntOpenHashMap()); this.toUpdate = new LongOpenHashSet(); this.viewDistance = 0; this.queueLevels.defaultReturnValue(maxDistance + 2); } @Override protected void onLevelChange(long node, int oldLevel, int level) { this.toUpdate.add(node); } public void updateViewDistance(int viewDistance) { for (Long2ByteMap.Entry entry : this.chunks.long2ByteEntrySet()) { byte level = entry.getByteValue(); long key = entry.getLongKey(); this.onLevelChange(key, level, this.haveTicketFor(level), level <= viewDistance); } this.viewDistance = viewDistance; } private void onLevelChange(long key, int level, boolean saw, boolean sees) { if (saw != sees) { Ticket ticket = new Ticket(TicketType.PLAYER_LOADING, PLAYER_TICKET_LEVEL); if (sees) { DistanceManager.this.ticketDispatcher.submit(() -> DistanceManager.this.mainThreadExecutor.execute(() -> { if (this.haveTicketFor(this.getLevel(key))) { DistanceManager.this.ticketStorage.addTicket(key, ticket); DistanceManager.this.ticketsToRelease.add(key); } else { DistanceManager.this.ticketDispatcher.release(key, () -> {}, false); } }), key, () -> level); } else { DistanceManager.this.ticketDispatcher.release(key, () -> DistanceManager.this.mainThreadExecutor.execute(() -> DistanceManager.this.ticketStorage.removeTicket(key, ticket)), true); } } } @Override public void runAllUpdates() { super.runAllUpdates(); if (!this.toUpdate.isEmpty()) { LongIterator iterator = this.toUpdate.iterator(); while (iterator.hasNext()) { int level; long node = iterator.nextLong(); int oldLevel = this.queueLevels.get(node); if (oldLevel == (level = this.getLevel(node))) continue; DistanceManager.this.ticketDispatcher.onLevelChange(new ChunkPos(node), () -> this.queueLevels.get(node), level, l -> { if (l >= this.queueLevels.defaultReturnValue()) { this.queueLevels.remove(node); } else { this.queueLevels.put(node, l); } }); this.onLevelChange(node, level, this.haveTicketFor(oldLevel), this.haveTicketFor(level)); } this.toUpdate.clear(); } } private boolean haveTicketFor(int level) { return level <= this.viewDistance; } } }