396 lines
17 KiB
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);
|
|
}
|
|
}
|
|
}
|