minecraft-source/src/net/minecraft/server/level/DistanceManager.java

396 lines
17 KiB
Java

package net.minecraft.server.level;
import it.unimi.dsi.fastutil.longs.Long2IntMaps;
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
import it.unimi.dsi.fastutil.longs.Long2IntMap;
import it.unimi.dsi.fastutil.longs.Long2ByteOpenHashMap;
import it.unimi.dsi.fastutil.longs.Long2ByteMap;
import net.minecraft.world.level.chunk.ChunkStatus;
import org.apache.logging.log4j.LogManager;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import net.minecraft.core.SectionPos;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.chunk.LevelChunk;
import com.mojang.datafixers.util.Either;
import java.util.concurrent.CompletableFuture;
import it.unimi.dsi.fastutil.longs.LongIterator;
import javax.annotation.Nullable;
import it.unimi.dsi.fastutil.objects.ObjectIterator;
import java.util.List;
import com.google.common.collect.ImmutableList;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import com.google.common.collect.Sets;
import java.util.concurrent.Executor;
import it.unimi.dsi.fastutil.longs.LongSet;
import net.minecraft.util.thread.ProcessorHandle;
import java.util.Set;
import net.minecraft.util.SortedArraySet;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectSet;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import org.apache.logging.log4j.Logger;
public abstract class DistanceManager {
private static final Logger LOGGER;
private static final int PLAYER_TICKET_LEVEL;
private final Long2ObjectMap<ObjectSet<ServerPlayer>> playersPerChunk;
private final Long2ObjectOpenHashMap<SortedArraySet<Ticket<?>>> tickets;
private final ChunkTicketTracker ticketTracker;
private final FixedPlayerDistanceChunkTracker naturalSpawnChunkCounter;
private final PlayerTicketTracker playerTicketManager;
private final Set<ChunkHolder> chunksToUpdateFutures;
private final ChunkTaskPriorityQueueSorter ticketThrottler;
private final ProcessorHandle<ChunkTaskPriorityQueueSorter.Message<Runnable>> ticketThrottlerInput;
private final ProcessorHandle<ChunkTaskPriorityQueueSorter.Release> ticketThrottlerReleaser;
private final LongSet ticketsToRelease;
private final Executor mainThreadExecutor;
private long ticketTickCounter;
protected DistanceManager(final Executor executor1, final Executor executor2) {
this.playersPerChunk = (Long2ObjectMap<ObjectSet<ServerPlayer>>)new Long2ObjectOpenHashMap();
this.tickets = (Long2ObjectOpenHashMap<SortedArraySet<Ticket<?>>>)new Long2ObjectOpenHashMap();
this.ticketTracker = new ChunkTicketTracker();
this.naturalSpawnChunkCounter = new FixedPlayerDistanceChunkTracker(8);
this.playerTicketManager = new PlayerTicketTracker(33);
this.chunksToUpdateFutures = Sets.newHashSet();
this.ticketsToRelease = (LongSet)new LongOpenHashSet();
final ProcessorHandle<Runnable> air4 = ProcessorHandle.<Runnable>of("player ticket throttler", executor2::execute);
final ChunkTaskPriorityQueueSorter wr5 = new ChunkTaskPriorityQueueSorter(ImmutableList.of(air4), executor1, 4);
this.ticketThrottler = wr5;
this.ticketThrottlerInput = wr5.<Runnable>getProcessor(air4, true);
this.ticketThrottlerReleaser = wr5.getReleaseProcessor(air4);
this.mainThreadExecutor = executor2;
}
protected void purgeStaleTickets() {
++this.ticketTickCounter;
final ObjectIterator<Long2ObjectMap.Entry<SortedArraySet<Ticket<?>>>> objectIterator2 = (ObjectIterator<Long2ObjectMap.Entry<SortedArraySet<Ticket<?>>>>)this.tickets.long2ObjectEntrySet().fastIterator();
while (objectIterator2.hasNext()) {
final Long2ObjectMap.Entry<SortedArraySet<Ticket<?>>> entry3 = (Long2ObjectMap.Entry<SortedArraySet<Ticket<?>>>)objectIterator2.next();
if (((SortedArraySet)entry3.getValue()).removeIf(xh -> xh.timedOut(this.ticketTickCounter))) {
this.ticketTracker.update(entry3.getLongKey(), getTicketLevelAt((SortedArraySet<Ticket<?>>)entry3.getValue()), false);
}
if (((SortedArraySet)entry3.getValue()).isEmpty()) {
objectIterator2.remove();
}
}
}
private static int getTicketLevelAt(final SortedArraySet<Ticket<?>> abw) {
return abw.isEmpty() ? (ChunkMap.MAX_CHUNK_DISTANCE + 1) : abw.first().getTicketLevel();
}
protected abstract boolean isChunkToRemove(final long long1);
@Nullable
protected abstract ChunkHolder getChunk(final long long1);
@Nullable
protected abstract ChunkHolder updateChunkScheduling(final long long1, final int integer2, @Nullable final ChunkHolder wo, final int integer4);
public boolean runAllUpdates(final ChunkMap wp) {
this.naturalSpawnChunkCounter.runAllUpdates();
this.playerTicketManager.runAllUpdates();
final int integer3 = Integer.MAX_VALUE - this.ticketTracker.runDistnaceUpdates(Integer.MAX_VALUE);
final boolean boolean4 = integer3 != 0;
if (boolean4) {}
if (!this.chunksToUpdateFutures.isEmpty()) {
this.chunksToUpdateFutures.forEach(wo -> wo.updateFutures(wp));
this.chunksToUpdateFutures.clear();
return true;
}
if (!this.ticketsToRelease.isEmpty()) {
final LongIterator longIterator5 = this.ticketsToRelease.iterator();
while (longIterator5.hasNext()) {
final long long6 = longIterator5.nextLong();
if (this.getTickets(long6).stream().anyMatch(xh -> xh.getType() == TicketType.PLAYER)) {
final ChunkHolder wo2 = wp.getUpdatingChunkIfPresent(long6);
if (wo2 == null) {
throw new IllegalStateException();
}
final CompletableFuture<Either<LevelChunk, ChunkHolder.ChunkLoadingFailure>> completableFuture9 = wo2.getEntityTickingChunkFuture();
completableFuture9.thenAccept(either -> this.mainThreadExecutor.execute(() -> this.ticketThrottlerReleaser.tell(ChunkTaskPriorityQueueSorter.release(() -> {}, long6, false))));
}
}
this.ticketsToRelease.clear();
}
return boolean4;
}
private void addTicket(final long long1, final Ticket<?> xh) {
final SortedArraySet<Ticket<?>> abw5 = this.getTickets(long1);
final int integer6 = getTicketLevelAt(abw5);
final Ticket<?> xh2 = abw5.addOrGet(xh);
xh2.setCreatedTick(this.ticketTickCounter);
if (xh.getTicketLevel() < integer6) {
this.ticketTracker.update(long1, xh.getTicketLevel(), true);
}
}
private void removeTicket(final long long1, final Ticket<?> xh) {
final SortedArraySet<Ticket<?>> abw5 = this.getTickets(long1);
if (abw5.remove(xh)) {}
if (abw5.isEmpty()) {
this.tickets.remove(long1);
}
this.ticketTracker.update(long1, getTicketLevelAt(abw5), false);
}
public <T> void addTicket(final TicketType<T> xi, final ChunkPos bje, final int integer, final T object) {
this.addTicket(bje.toLong(), new Ticket<>(xi, integer, object));
}
public <T> void removeTicket(final TicketType<T> xi, final ChunkPos bje, final int integer, final T object) {
final Ticket<T> xh6 = new Ticket<T>(xi, integer, object);
this.removeTicket(bje.toLong(), xh6);
}
public <T> void addRegionTicket(final TicketType<T> xi, final ChunkPos bje, final int integer, final T object) {
this.addTicket(bje.toLong(), new Ticket<>(xi, 33 - integer, object));
}
public <T> void removeRegionTicket(final TicketType<T> xi, final ChunkPos bje, final int integer, final T object) {
final Ticket<T> xh6 = new Ticket<T>(xi, 33 - integer, object);
this.removeTicket(bje.toLong(), xh6);
}
private SortedArraySet<Ticket<?>> getTickets(final long long1) {
return (SortedArraySet<Ticket<?>>)this.tickets.computeIfAbsent(long1, long1 -> SortedArraySet.<Comparable>create(4));
}
protected void updateChunkForced(final ChunkPos bje, final boolean boolean2) {
final Ticket<ChunkPos> xh4 = new Ticket<ChunkPos>(TicketType.FORCED, 31, bje);
if (boolean2) {
this.addTicket(bje.toLong(), xh4);
}
else {
this.removeTicket(bje.toLong(), xh4);
}
}
public void addPlayer(final SectionPos gd, final ServerPlayer xe) {
final long long4 = gd.chunk().toLong();
((ObjectSet)this.playersPerChunk.computeIfAbsent(long4, long1 -> new ObjectOpenHashSet())).add(xe);
this.naturalSpawnChunkCounter.update(long4, 0, true);
this.playerTicketManager.update(long4, 0, true);
}
public void removePlayer(final SectionPos gd, final ServerPlayer xe) {
final long long4 = gd.chunk().toLong();
final ObjectSet<ServerPlayer> objectSet6 = (ObjectSet<ServerPlayer>)this.playersPerChunk.get(long4);
objectSet6.remove(xe);
if (objectSet6.isEmpty()) {
this.playersPerChunk.remove(long4);
this.naturalSpawnChunkCounter.update(long4, Integer.MAX_VALUE, false);
this.playerTicketManager.update(long4, Integer.MAX_VALUE, false);
}
}
protected String getTicketDebugString(final long long1) {
final SortedArraySet<Ticket<?>> abw4 = (SortedArraySet<Ticket<?>>)this.tickets.get(long1);
String string5;
if (abw4 == null || abw4.isEmpty()) {
string5 = "no_ticket";
}
else {
string5 = abw4.first().toString();
}
return string5;
}
protected void updatePlayerTickets(final int integer) {
this.playerTicketManager.updateViewDistance(integer);
}
public int getNaturalSpawnChunkCount() {
this.naturalSpawnChunkCounter.runAllUpdates();
return this.naturalSpawnChunkCounter.chunks.size();
}
public boolean hasPlayersNearby(final long long1) {
this.naturalSpawnChunkCounter.runAllUpdates();
return this.naturalSpawnChunkCounter.chunks.containsKey(long1);
}
public String getDebugStatus() {
return this.ticketThrottler.getDebugStatus();
}
static {
LOGGER = LogManager.getLogger();
PLAYER_TICKET_LEVEL = 33 + ChunkStatus.getDistance(ChunkStatus.FULL) - 2;
}
class FixedPlayerDistanceChunkTracker extends ChunkTracker {
protected final Long2ByteMap chunks;
protected final int maxDistance;
protected FixedPlayerDistanceChunkTracker(final int integer) {
super(integer + 2, 16, 256);
this.chunks = (Long2ByteMap)new Long2ByteOpenHashMap();
this.maxDistance = integer;
this.chunks.defaultReturnValue((byte)(integer + 2));
}
@Override
protected int getLevel(final long long1) {
return this.chunks.get(long1);
}
@Override
protected void setLevel(final long long1, final int integer) {
byte byte5;
if (integer > this.maxDistance) {
byte5 = this.chunks.remove(long1);
}
else {
byte5 = this.chunks.put(long1, (byte)integer);
}
this.onLevelChange(long1, byte5, integer);
}
protected void onLevelChange(final long long1, final int integer2, final int integer3) {
}
@Override
protected int getLevelFromSource(final long long1) {
return this.havePlayer(long1) ? 0 : Integer.MAX_VALUE;
}
private boolean havePlayer(final long long1) {
final ObjectSet<ServerPlayer> objectSet4 = (ObjectSet<ServerPlayer>)DistanceManager.this.playersPerChunk.get(long1);
return objectSet4 != null && !objectSet4.isEmpty();
}
public void runAllUpdates() {
this.runUpdates(Integer.MAX_VALUE);
}
}
class PlayerTicketTracker extends FixedPlayerDistanceChunkTracker {
private int viewDistance;
private final Long2IntMap queueLevels;
private final LongSet toUpdate;
protected PlayerTicketTracker(final int integer) {
super(integer);
this.queueLevels = Long2IntMaps.synchronize((Long2IntMap)new Long2IntOpenHashMap());
this.toUpdate = (LongSet)new LongOpenHashSet();
this.viewDistance = 0;
this.queueLevels.defaultReturnValue(integer + 2);
}
@Override
protected void onLevelChange(final long long1, final int integer2, final int integer3) {
this.toUpdate.add(long1);
}
public void updateViewDistance(final int integer) {
for (final Long2ByteMap.Entry entry4 : this.chunks.long2ByteEntrySet()) {
final byte byte5 = entry4.getByteValue();
final long long6 = entry4.getLongKey();
this.onLevelChange(long6, byte5, this.haveTicketFor(byte5), byte5 <= integer - 2);
}
this.viewDistance = integer;
}
private void onLevelChange(final long long1, final int integer, final boolean boolean3, final boolean boolean4) {
if (boolean3 != boolean4) {
final Ticket<?> xh7 = new Ticket<>(TicketType.PLAYER, DistanceManager.PLAYER_TICKET_LEVEL, new ChunkPos(long1));
if (boolean4) {
final Ticket xh8;
DistanceManager.this.ticketThrottlerInput.tell(ChunkTaskPriorityQueueSorter.message(() -> DistanceManager.this.mainThreadExecutor.execute(() -> {
if (this.haveTicketFor(this.getLevel(long1))) {
DistanceManager.this.addTicket(long1, xh8);
DistanceManager.this.ticketsToRelease.add(long1);
}
else {
DistanceManager.this.ticketThrottlerReleaser.tell(ChunkTaskPriorityQueueSorter.release(() -> {}, long1, false));
}
}), long1, () -> integer));
}
else {
DistanceManager.this.ticketThrottlerReleaser.tell(ChunkTaskPriorityQueueSorter.release(() -> DistanceManager.this.mainThreadExecutor.execute(() -> DistanceManager.this.removeTicket(long1, xh7)), long1, true));
}
}
}
@Override
public void runAllUpdates() {
super.runAllUpdates();
if (!this.toUpdate.isEmpty()) {
final LongIterator longIterator2 = this.toUpdate.iterator();
while (longIterator2.hasNext()) {
final long long3 = longIterator2.nextLong();
final int integer2 = this.queueLevels.get(long3);
final int integer3 = this.getLevel(long3);
if (integer2 != integer3) {
final long n;
DistanceManager.this.ticketThrottler.onLevelChange(new ChunkPos(long3), () -> this.queueLevels.get(long3), integer3, integer -> {
if (integer >= this.queueLevels.defaultReturnValue()) {
this.queueLevels.remove(n);
}
else {
this.queueLevels.put(n, integer);
}
return;
});
this.onLevelChange(long3, integer3, this.haveTicketFor(integer2), this.haveTicketFor(integer3));
}
}
this.toUpdate.clear();
}
}
private boolean haveTicketFor(final int integer) {
return integer <= this.viewDistance - 2;
}
}
class ChunkTicketTracker extends ChunkTracker {
public ChunkTicketTracker() {
super(ChunkMap.MAX_CHUNK_DISTANCE + 2, 16, 256);
}
@Override
protected int getLevelFromSource(final long long1) {
final SortedArraySet<Ticket<?>> abw4 = (SortedArraySet<Ticket<?>>)DistanceManager.this.tickets.get(long1);
if (abw4 == null) {
return Integer.MAX_VALUE;
}
if (abw4.isEmpty()) {
return Integer.MAX_VALUE;
}
return abw4.first().getTicketLevel();
}
@Override
protected int getLevel(final long long1) {
if (!DistanceManager.this.isChunkToRemove(long1)) {
final ChunkHolder wo4 = DistanceManager.this.getChunk(long1);
if (wo4 != null) {
return wo4.getTicketLevel();
}
}
return ChunkMap.MAX_CHUNK_DISTANCE + 1;
}
@Override
protected void setLevel(final long long1, final int integer) {
ChunkHolder wo5 = DistanceManager.this.getChunk(long1);
final int integer2 = (wo5 == null) ? (ChunkMap.MAX_CHUNK_DISTANCE + 1) : wo5.getTicketLevel();
if (integer2 == integer) {
return;
}
wo5 = DistanceManager.this.updateChunkScheduling(long1, integer, wo5, integer2);
if (wo5 != null) {
DistanceManager.this.chunksToUpdateFutures.add(wo5);
}
}
public int runDistnaceUpdates(final int integer) {
return this.runUpdates(integer);
}
}
}