minecraft-src/net/minecraft/server/level/ChunkMap.java
2025-07-04 03:45:38 +03:00

1391 lines
50 KiB
Java

package net.minecraft.server.level;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Queues;
import com.google.common.collect.Sets;
import com.google.common.collect.ImmutableList.Builder;
import com.mojang.datafixers.DataFixer;
import com.mojang.logging.LogUtils;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.longs.Long2ByteMap;
import it.unimi.dsi.fastutil.longs.Long2ByteOpenHashMap;
import it.unimi.dsi.fastutil.longs.Long2LongMap;
import it.unimi.dsi.fastutil.longs.Long2LongOpenHashMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
import it.unimi.dsi.fastutil.longs.LongIterator;
import it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import it.unimi.dsi.fastutil.longs.LongSet;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap.Entry;
import java.io.IOException;
import java.io.Writer;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Queue;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BooleanSupplier;
import java.util.function.Consumer;
import java.util.function.IntConsumer;
import java.util.function.IntFunction;
import java.util.function.IntSupplier;
import java.util.function.Supplier;
import net.minecraft.CrashReport;
import net.minecraft.CrashReportCategory;
import net.minecraft.CrashReportDetail;
import net.minecraft.ReportedException;
import net.minecraft.Util;
import net.minecraft.core.RegistryAccess;
import net.minecraft.core.SectionPos;
import net.minecraft.core.registries.Registries;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtException;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.ClientboundChunksBiomesPacket;
import net.minecraft.network.protocol.game.ClientboundSetChunkCacheCenterPacket;
import net.minecraft.server.level.ChunkHolder.PlayerProvider;
import net.minecraft.server.level.ChunkTrackingView.Positioned;
import net.minecraft.server.level.progress.ChunkProgressListener;
import net.minecraft.server.network.ServerPlayerConnection;
import net.minecraft.util.CsvOutput;
import net.minecraft.util.Mth;
import net.minecraft.util.StaticCache2D;
import net.minecraft.util.TriState;
import net.minecraft.util.profiling.Profiler;
import net.minecraft.util.profiling.ProfilerFiller;
import net.minecraft.util.thread.BlockableEventLoop;
import net.minecraft.util.thread.ConsecutiveExecutor;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.ai.village.poi.PoiManager;
import net.minecraft.world.entity.boss.EnderDragonPart;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.GameRules;
import net.minecraft.world.level.TicketStorage;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkGenerator;
import net.minecraft.world.level.chunk.ChunkGeneratorStructureState;
import net.minecraft.world.level.chunk.ImposterProtoChunk;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.chunk.LightChunkGetter;
import net.minecraft.world.level.chunk.ProtoChunk;
import net.minecraft.world.level.chunk.UpgradeData;
import net.minecraft.world.level.chunk.status.ChunkStatus;
import net.minecraft.world.level.chunk.status.ChunkStep;
import net.minecraft.world.level.chunk.status.ChunkType;
import net.minecraft.world.level.chunk.status.WorldGenContext;
import net.minecraft.world.level.chunk.storage.ChunkStorage;
import net.minecraft.world.level.chunk.storage.RegionStorageInfo;
import net.minecraft.world.level.chunk.storage.SerializableChunkData;
import net.minecraft.world.level.entity.ChunkStatusUpdateListener;
import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator;
import net.minecraft.world.level.levelgen.NoiseGeneratorSettings;
import net.minecraft.world.level.levelgen.RandomState;
import net.minecraft.world.level.levelgen.structure.StructureStart;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
import net.minecraft.world.level.storage.DimensionDataStorage;
import net.minecraft.world.level.storage.LevelStorageSource;
import net.minecraft.world.phys.Vec3;
import org.apache.commons.lang3.mutable.MutableBoolean;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
public class ChunkMap extends ChunkStorage implements PlayerProvider, GeneratingChunkMap {
private static final ChunkResult<List<ChunkAccess>> UNLOADED_CHUNK_LIST_RESULT = ChunkResult.error("Unloaded chunks found in range");
private static final CompletableFuture<ChunkResult<List<ChunkAccess>>> UNLOADED_CHUNK_LIST_FUTURE = CompletableFuture.completedFuture(
UNLOADED_CHUNK_LIST_RESULT
);
private static final byte CHUNK_TYPE_REPLACEABLE = -1;
private static final byte CHUNK_TYPE_UNKNOWN = 0;
private static final byte CHUNK_TYPE_FULL = 1;
private static final Logger LOGGER = LogUtils.getLogger();
private static final int CHUNK_SAVED_PER_TICK = 200;
private static final int CHUNK_SAVED_EAGERLY_PER_TICK = 20;
private static final int EAGER_CHUNK_SAVE_COOLDOWN_IN_MILLIS = 10000;
private static final int MAX_ACTIVE_CHUNK_WRITES = 128;
public static final int MIN_VIEW_DISTANCE = 2;
public static final int MAX_VIEW_DISTANCE = 32;
public static final int FORCED_TICKET_LEVEL = ChunkLevel.byStatus(FullChunkStatus.ENTITY_TICKING);
/**
* Chunks in memory. This should only ever be manipulated by the main thread.
*/
private final Long2ObjectLinkedOpenHashMap<ChunkHolder> updatingChunkMap = new Long2ObjectLinkedOpenHashMap<>();
/**
* Same as {@link #loadedChunks}, but immutable for access from other threads. <em>This should never be mutated.</em>
*/
private volatile Long2ObjectLinkedOpenHashMap<ChunkHolder> visibleChunkMap = this.updatingChunkMap.clone();
private final Long2ObjectLinkedOpenHashMap<ChunkHolder> pendingUnloads = new Long2ObjectLinkedOpenHashMap<>();
private final List<ChunkGenerationTask> pendingGenerationTasks = new ArrayList();
final ServerLevel level;
private final ThreadedLevelLightEngine lightEngine;
private final BlockableEventLoop<Runnable> mainThreadExecutor;
private final RandomState randomState;
private final ChunkGeneratorStructureState chunkGeneratorState;
private final Supplier<DimensionDataStorage> overworldDataStorage;
private final TicketStorage ticketStorage;
private final PoiManager poiManager;
/**
* Chunks that have been requested to be unloaded, but haven't been unloaded yet.
*/
final LongSet toDrop = new LongOpenHashSet();
/**
* True if changes have been made to {@link #loadedChunks} and thus a new copy of the collection has to be made into {@link #immutableLoadedChunks}.
*/
private boolean modified;
private final ChunkTaskDispatcher worldgenTaskDispatcher;
private final ChunkTaskDispatcher lightTaskDispatcher;
private final ChunkProgressListener progressListener;
private final ChunkStatusUpdateListener chunkStatusListener;
private final ChunkMap.DistanceManager distanceManager;
private final AtomicInteger tickingGenerated = new AtomicInteger();
private final String storageName;
private final PlayerMap playerMap = new PlayerMap();
private final Int2ObjectMap<ChunkMap.TrackedEntity> entityMap = new Int2ObjectOpenHashMap<>();
private final Long2ByteMap chunkTypeCache = new Long2ByteOpenHashMap();
private final Long2LongMap nextChunkSaveTime = new Long2LongOpenHashMap();
private final LongSet chunksToEagerlySave = new LongLinkedOpenHashSet();
private final Queue<Runnable> unloadQueue = Queues.<Runnable>newConcurrentLinkedQueue();
private final AtomicInteger activeChunkWrites = new AtomicInteger();
private int serverViewDistance;
private final WorldGenContext worldGenContext;
public ChunkMap(
ServerLevel level,
LevelStorageSource.LevelStorageAccess levelStorageAccess,
DataFixer fixerUpper,
StructureTemplateManager structureManager,
Executor dispatcher,
BlockableEventLoop<Runnable> mainThreadExecutor,
LightChunkGetter lightChunk,
ChunkGenerator generator,
ChunkProgressListener progressListener,
ChunkStatusUpdateListener chunkStatusListener,
Supplier<DimensionDataStorage> overworldDataStorage,
TicketStorage ticketStorage,
int serverViewDistance,
boolean sync
) {
super(
new RegionStorageInfo(levelStorageAccess.getLevelId(), level.dimension(), "chunk"),
levelStorageAccess.getDimensionPath(level.dimension()).resolve("region"),
fixerUpper,
sync
);
Path path = levelStorageAccess.getDimensionPath(level.dimension());
this.storageName = path.getFileName().toString();
this.level = level;
RegistryAccess registryAccess = level.registryAccess();
long l = level.getSeed();
if (generator instanceof NoiseBasedChunkGenerator noiseBasedChunkGenerator) {
this.randomState = RandomState.create(noiseBasedChunkGenerator.generatorSettings().value(), registryAccess.lookupOrThrow(Registries.NOISE), l);
} else {
this.randomState = RandomState.create(NoiseGeneratorSettings.dummy(), registryAccess.lookupOrThrow(Registries.NOISE), l);
}
this.chunkGeneratorState = generator.createState(registryAccess.lookupOrThrow(Registries.STRUCTURE_SET), this.randomState, l);
this.mainThreadExecutor = mainThreadExecutor;
ConsecutiveExecutor consecutiveExecutor = new ConsecutiveExecutor(dispatcher, "worldgen");
this.progressListener = progressListener;
this.chunkStatusListener = chunkStatusListener;
ConsecutiveExecutor consecutiveExecutor2 = new ConsecutiveExecutor(dispatcher, "light");
this.worldgenTaskDispatcher = new ChunkTaskDispatcher(consecutiveExecutor, dispatcher);
this.lightTaskDispatcher = new ChunkTaskDispatcher(consecutiveExecutor2, dispatcher);
this.lightEngine = new ThreadedLevelLightEngine(lightChunk, this, this.level.dimensionType().hasSkyLight(), consecutiveExecutor2, this.lightTaskDispatcher);
this.distanceManager = new ChunkMap.DistanceManager(ticketStorage, dispatcher, mainThreadExecutor);
this.overworldDataStorage = overworldDataStorage;
this.ticketStorage = ticketStorage;
this.poiManager = new PoiManager(
new RegionStorageInfo(levelStorageAccess.getLevelId(), level.dimension(), "poi"),
path.resolve("poi"),
fixerUpper,
sync,
registryAccess,
level.getServer(),
level
);
this.setServerViewDistance(serverViewDistance);
this.worldGenContext = new WorldGenContext(level, generator, structureManager, this.lightEngine, mainThreadExecutor, this::setChunkUnsaved);
}
private void setChunkUnsaved(ChunkPos chunkPos) {
this.chunksToEagerlySave.add(chunkPos.toLong());
}
protected ChunkGenerator generator() {
return this.worldGenContext.generator();
}
protected ChunkGeneratorStructureState generatorState() {
return this.chunkGeneratorState;
}
protected RandomState randomState() {
return this.randomState;
}
/**
* Checks if a chunk is within a player's view distance.
*/
boolean isChunkTracked(ServerPlayer player, int x, int z) {
return player.getChunkTrackingView().contains(x, z) && !player.connection.chunkSender.isPending(ChunkPos.asLong(x, z));
}
/**
* Checks if a chunk is on the edge of the player's view distance.
*/
private boolean isChunkOnTrackedBorder(ServerPlayer player, int x, int z) {
if (!this.isChunkTracked(player, x, z)) {
return false;
} else {
for (int i = -1; i <= 1; i++) {
for (int j = -1; j <= 1; j++) {
if ((i != 0 || j != 0) && !this.isChunkTracked(player, x + i, z + j)) {
return true;
}
}
}
return false;
}
}
protected ThreadedLevelLightEngine getLightEngine() {
return this.lightEngine;
}
@Nullable
protected ChunkHolder getUpdatingChunkIfPresent(long chunkPos) {
return this.updatingChunkMap.get(chunkPos);
}
@Nullable
protected ChunkHolder getVisibleChunkIfPresent(long chunkPos) {
return this.visibleChunkMap.get(chunkPos);
}
protected IntSupplier getChunkQueueLevel(long chunkPos) {
return () -> {
ChunkHolder chunkHolder = this.getVisibleChunkIfPresent(chunkPos);
return chunkHolder == null
? ChunkTaskPriorityQueue.PRIORITY_LEVEL_COUNT - 1
: Math.min(chunkHolder.getQueueLevel(), ChunkTaskPriorityQueue.PRIORITY_LEVEL_COUNT - 1);
};
}
public String getChunkDebugData(ChunkPos pos) {
ChunkHolder chunkHolder = this.getVisibleChunkIfPresent(pos.toLong());
if (chunkHolder == null) {
return "null";
} else {
String string = chunkHolder.getTicketLevel() + "\n";
ChunkStatus chunkStatus = chunkHolder.getLatestStatus();
ChunkAccess chunkAccess = chunkHolder.getLatestChunk();
if (chunkStatus != null) {
string = string + "St: §" + chunkStatus.getIndex() + chunkStatus + "§r\n";
}
if (chunkAccess != null) {
string = string + "Ch: §" + chunkAccess.getPersistedStatus().getIndex() + chunkAccess.getPersistedStatus() + "§r\n";
}
FullChunkStatus fullChunkStatus = chunkHolder.getFullStatus();
string = string + '§' + fullChunkStatus.ordinal() + fullChunkStatus;
return string + "§r";
}
}
private CompletableFuture<ChunkResult<List<ChunkAccess>>> getChunkRangeFuture(ChunkHolder chunkHolder, int range, IntFunction<ChunkStatus> statusGetter) {
if (range == 0) {
ChunkStatus chunkStatus = (ChunkStatus)statusGetter.apply(0);
return chunkHolder.scheduleChunkGenerationTask(chunkStatus, this).thenApply(chunkResult -> chunkResult.map(List::of));
} else {
int i = Mth.square(range * 2 + 1);
List<CompletableFuture<ChunkResult<ChunkAccess>>> list = new ArrayList(i);
ChunkPos chunkPos = chunkHolder.getPos();
for (int j = -range; j <= range; j++) {
for (int k = -range; k <= range; k++) {
int l = Math.max(Math.abs(k), Math.abs(j));
long m = ChunkPos.asLong(chunkPos.x + k, chunkPos.z + j);
ChunkHolder chunkHolder2 = this.getUpdatingChunkIfPresent(m);
if (chunkHolder2 == null) {
return UNLOADED_CHUNK_LIST_FUTURE;
}
ChunkStatus chunkStatus2 = (ChunkStatus)statusGetter.apply(l);
list.add(chunkHolder2.scheduleChunkGenerationTask(chunkStatus2, this));
}
}
return Util.sequence(list).thenApply(listx -> {
List<ChunkAccess> list2 = new ArrayList(listx.size());
for (ChunkResult<ChunkAccess> chunkResult : listx) {
if (chunkResult == null) {
throw this.debugFuturesAndCreateReportedException(new IllegalStateException("At least one of the chunk futures were null"), "n/a");
}
ChunkAccess chunkAccess = chunkResult.orElse(null);
if (chunkAccess == null) {
return UNLOADED_CHUNK_LIST_RESULT;
}
list2.add(chunkAccess);
}
return ChunkResult.of(list2);
});
}
}
public ReportedException debugFuturesAndCreateReportedException(IllegalStateException exception, String details) {
StringBuilder stringBuilder = new StringBuilder();
Consumer<ChunkHolder> consumer = chunkHolder -> chunkHolder.getAllFutures()
.forEach(
pair -> {
ChunkStatus chunkStatus = (ChunkStatus)pair.getFirst();
CompletableFuture<ChunkResult<ChunkAccess>> completableFuture = (CompletableFuture<ChunkResult<ChunkAccess>>)pair.getSecond();
if (completableFuture != null && completableFuture.isDone() && completableFuture.join() == null) {
stringBuilder.append(chunkHolder.getPos())
.append(" - status: ")
.append(chunkStatus)
.append(" future: ")
.append(completableFuture)
.append(System.lineSeparator());
}
}
);
stringBuilder.append("Updating:").append(System.lineSeparator());
this.updatingChunkMap.values().forEach(consumer);
stringBuilder.append("Visible:").append(System.lineSeparator());
this.visibleChunkMap.values().forEach(consumer);
CrashReport crashReport = CrashReport.forThrowable(exception, "Chunk loading");
CrashReportCategory crashReportCategory = crashReport.addCategory("Chunk loading");
crashReportCategory.setDetail("Details", details);
crashReportCategory.setDetail("Futures", stringBuilder);
return new ReportedException(crashReport);
}
public CompletableFuture<ChunkResult<LevelChunk>> prepareEntityTickingChunk(ChunkHolder chunk) {
return this.getChunkRangeFuture(chunk, 2, i -> ChunkStatus.FULL).thenApply(chunkResult -> chunkResult.map(list -> (LevelChunk)list.get(list.size() / 2)));
}
/**
* Sets level and loads/unloads chunk.
*
* @param holder The {@link net.minecraft.server.level.ChunkHolder} of the chunk if it is loaded, and null otherwise.
*/
@Nullable
ChunkHolder updateChunkScheduling(long chunkPos, int newLevel, @Nullable ChunkHolder holder, int oldLevel) {
if (!ChunkLevel.isLoaded(oldLevel) && !ChunkLevel.isLoaded(newLevel)) {
return holder;
} else {
if (holder != null) {
holder.setTicketLevel(newLevel);
}
if (holder != null) {
if (!ChunkLevel.isLoaded(newLevel)) {
this.toDrop.add(chunkPos);
} else {
this.toDrop.remove(chunkPos);
}
}
if (ChunkLevel.isLoaded(newLevel) && holder == null) {
holder = this.pendingUnloads.remove(chunkPos);
if (holder != null) {
holder.setTicketLevel(newLevel);
} else {
holder = new ChunkHolder(new ChunkPos(chunkPos), newLevel, this.level, this.lightEngine, this::onLevelChange, this);
}
this.updatingChunkMap.put(chunkPos, holder);
this.modified = true;
}
return holder;
}
}
private void onLevelChange(ChunkPos chunkPos, IntSupplier queueLevelGetter, int ticketLevel, IntConsumer queueLevelSetter) {
this.worldgenTaskDispatcher.onLevelChange(chunkPos, queueLevelGetter, ticketLevel, queueLevelSetter);
this.lightTaskDispatcher.onLevelChange(chunkPos, queueLevelGetter, ticketLevel, queueLevelSetter);
}
@Override
public void close() throws IOException {
try {
this.worldgenTaskDispatcher.close();
this.lightTaskDispatcher.close();
this.poiManager.close();
} finally {
super.close();
}
}
protected void saveAllChunks(boolean flush) {
if (flush) {
List<ChunkHolder> list = this.visibleChunkMap
.values()
.stream()
.filter(ChunkHolder::wasAccessibleSinceLastSave)
.peek(ChunkHolder::refreshAccessibility)
.toList();
MutableBoolean mutableBoolean = new MutableBoolean();
do {
mutableBoolean.setFalse();
list.stream()
.map(chunkHolderx -> {
this.mainThreadExecutor.managedBlock(chunkHolderx::isReadyForSaving);
return chunkHolderx.getLatestChunk();
})
.filter(chunkAccess -> chunkAccess instanceof ImposterProtoChunk || chunkAccess instanceof LevelChunk)
.filter(this::save)
.forEach(chunkAccess -> mutableBoolean.setTrue());
} while (mutableBoolean.isTrue());
this.poiManager.flushAll();
this.processUnloads(() -> true);
this.flushWorker();
} else {
this.nextChunkSaveTime.clear();
long l = Util.getMillis();
for (ChunkHolder chunkHolder : this.visibleChunkMap.values()) {
this.saveChunkIfNeeded(chunkHolder, l);
}
}
}
protected void tick(BooleanSupplier hasMoreTime) {
ProfilerFiller profilerFiller = Profiler.get();
profilerFiller.push("poi");
this.poiManager.tick(hasMoreTime);
profilerFiller.popPush("chunk_unload");
if (!this.level.noSave()) {
this.processUnloads(hasMoreTime);
}
profilerFiller.pop();
}
public boolean hasWork() {
return this.lightEngine.hasLightWork()
|| !this.pendingUnloads.isEmpty()
|| !this.updatingChunkMap.isEmpty()
|| this.poiManager.hasWork()
|| !this.toDrop.isEmpty()
|| !this.unloadQueue.isEmpty()
|| this.worldgenTaskDispatcher.hasWork()
|| this.lightTaskDispatcher.hasWork()
|| this.distanceManager.hasTickets();
}
private void processUnloads(BooleanSupplier hasMoreTime) {
for (LongIterator longIterator = this.toDrop.iterator(); longIterator.hasNext(); longIterator.remove()) {
long l = longIterator.nextLong();
ChunkHolder chunkHolder = this.updatingChunkMap.get(l);
if (chunkHolder != null) {
this.updatingChunkMap.remove(l);
this.pendingUnloads.put(l, chunkHolder);
this.modified = true;
this.scheduleUnload(l, chunkHolder);
}
}
int i = Math.max(0, this.unloadQueue.size() - 2000);
Runnable runnable;
while ((i > 0 || hasMoreTime.getAsBoolean()) && (runnable = (Runnable)this.unloadQueue.poll()) != null) {
i--;
runnable.run();
}
this.saveChunksEagerly(hasMoreTime);
}
private void saveChunksEagerly(BooleanSupplier hasMoreTime) {
long l = Util.getMillis();
int i = 0;
LongIterator longIterator = this.chunksToEagerlySave.iterator();
while (i < 20 && this.activeChunkWrites.get() < 128 && hasMoreTime.getAsBoolean() && longIterator.hasNext()) {
long m = longIterator.nextLong();
ChunkHolder chunkHolder = this.visibleChunkMap.get(m);
ChunkAccess chunkAccess = chunkHolder != null ? chunkHolder.getLatestChunk() : null;
if (chunkAccess == null || !chunkAccess.isUnsaved()) {
longIterator.remove();
} else if (this.saveChunkIfNeeded(chunkHolder, l)) {
i++;
longIterator.remove();
}
}
}
private void scheduleUnload(long chunkPos, ChunkHolder chunkHolder) {
CompletableFuture<?> completableFuture = chunkHolder.getSaveSyncFuture();
completableFuture.thenRunAsync(() -> {
CompletableFuture<?> completableFuture2 = chunkHolder.getSaveSyncFuture();
if (completableFuture2 != completableFuture) {
this.scheduleUnload(chunkPos, chunkHolder);
} else {
ChunkAccess chunkAccess = chunkHolder.getLatestChunk();
if (this.pendingUnloads.remove(chunkPos, chunkHolder) && chunkAccess != null) {
if (chunkAccess instanceof LevelChunk levelChunk) {
levelChunk.setLoaded(false);
}
this.save(chunkAccess);
if (chunkAccess instanceof LevelChunk levelChunk) {
this.level.unload(levelChunk);
}
this.lightEngine.updateChunkStatus(chunkAccess.getPos());
this.lightEngine.tryScheduleUpdate();
this.progressListener.onStatusChange(chunkAccess.getPos(), null);
this.nextChunkSaveTime.remove(chunkAccess.getPos().toLong());
}
}
}, this.unloadQueue::add).whenComplete((void_, throwable) -> {
if (throwable != null) {
LOGGER.error("Failed to save chunk {}", chunkHolder.getPos(), throwable);
}
});
}
protected boolean promoteChunkMap() {
if (!this.modified) {
return false;
} else {
this.visibleChunkMap = this.updatingChunkMap.clone();
this.modified = false;
return true;
}
}
private CompletableFuture<ChunkAccess> scheduleChunkLoad(ChunkPos chunkPos) {
CompletableFuture<Optional<SerializableChunkData>> completableFuture = this.readChunk(chunkPos).thenApplyAsync(optional -> optional.map(compoundTag -> {
SerializableChunkData serializableChunkData = SerializableChunkData.parse(this.level, this.level.registryAccess(), compoundTag);
if (serializableChunkData == null) {
LOGGER.error("Chunk file at {} is missing level data, skipping", chunkPos);
}
return serializableChunkData;
}), Util.backgroundExecutor().forName("parseChunk"));
CompletableFuture<?> completableFuture2 = this.poiManager.prefetch(chunkPos);
return completableFuture.thenCombine(completableFuture2, (optional, object) -> optional).thenApplyAsync(optional -> {
Profiler.get().incrementCounter("chunkLoad");
if (optional.isPresent()) {
ChunkAccess chunkAccess = ((SerializableChunkData)optional.get()).read(this.level, this.poiManager, this.storageInfo(), chunkPos);
this.markPosition(chunkPos, chunkAccess.getPersistedStatus().getChunkType());
return chunkAccess;
} else {
return this.createEmptyChunk(chunkPos);
}
}, this.mainThreadExecutor).exceptionallyAsync(throwable -> this.handleChunkLoadFailure(throwable, chunkPos), this.mainThreadExecutor);
}
private ChunkAccess handleChunkLoadFailure(Throwable exception, ChunkPos chunkPos) {
Throwable throwable = exception instanceof CompletionException completionException ? completionException.getCause() : exception;
Throwable throwable2 = throwable instanceof ReportedException reportedException ? reportedException.getCause() : throwable;
boolean bl = throwable2 instanceof Error;
boolean bl2 = throwable2 instanceof IOException || throwable2 instanceof NbtException;
if (!bl) {
if (!bl2) {
}
this.level.getServer().reportChunkLoadFailure(throwable2, this.storageInfo(), chunkPos);
return this.createEmptyChunk(chunkPos);
} else {
CrashReport crashReport = CrashReport.forThrowable(exception, "Exception loading chunk");
CrashReportCategory crashReportCategory = crashReport.addCategory("Chunk being loaded");
crashReportCategory.setDetail("pos", chunkPos);
this.markPositionReplaceable(chunkPos);
throw new ReportedException(crashReport);
}
}
private ChunkAccess createEmptyChunk(ChunkPos chunkPos) {
this.markPositionReplaceable(chunkPos);
return new ProtoChunk(chunkPos, UpgradeData.EMPTY, this.level, this.level.registryAccess().lookupOrThrow(Registries.BIOME), null);
}
private void markPositionReplaceable(ChunkPos chunkPos) {
this.chunkTypeCache.put(chunkPos.toLong(), (byte)-1);
}
private byte markPosition(ChunkPos chunkPos, ChunkType chunkType) {
return this.chunkTypeCache.put(chunkPos.toLong(), (byte)(chunkType == ChunkType.PROTOCHUNK ? -1 : 1));
}
@Override
public GenerationChunkHolder acquireGeneration(long chunkPos) {
ChunkHolder chunkHolder = this.updatingChunkMap.get(chunkPos);
chunkHolder.increaseGenerationRefCount();
return chunkHolder;
}
@Override
public void releaseGeneration(GenerationChunkHolder chunk) {
chunk.decreaseGenerationRefCount();
}
@Override
public CompletableFuture<ChunkAccess> applyStep(GenerationChunkHolder chunk, ChunkStep step, StaticCache2D<GenerationChunkHolder> cache) {
ChunkPos chunkPos = chunk.getPos();
if (step.targetStatus() == ChunkStatus.EMPTY) {
return this.scheduleChunkLoad(chunkPos);
} else {
try {
GenerationChunkHolder generationChunkHolder = cache.get(chunkPos.x, chunkPos.z);
ChunkAccess chunkAccess = generationChunkHolder.getChunkIfPresentUnchecked(step.targetStatus().getParent());
if (chunkAccess == null) {
throw new IllegalStateException("Parent chunk missing");
} else {
CompletableFuture<ChunkAccess> completableFuture = step.apply(this.worldGenContext, cache, chunkAccess);
this.progressListener.onStatusChange(chunkPos, step.targetStatus());
return completableFuture;
}
} catch (Exception var8) {
var8.getStackTrace();
CrashReport crashReport = CrashReport.forThrowable(var8, "Exception generating new chunk");
CrashReportCategory crashReportCategory = crashReport.addCategory("Chunk to be generated");
crashReportCategory.setDetail("Status being generated", (CrashReportDetail<String>)(() -> step.targetStatus().getName()));
crashReportCategory.setDetail("Location", String.format(Locale.ROOT, "%d,%d", chunkPos.x, chunkPos.z));
crashReportCategory.setDetail("Position hash", ChunkPos.asLong(chunkPos.x, chunkPos.z));
crashReportCategory.setDetail("Generator", this.generator());
this.mainThreadExecutor.execute(() -> {
throw new ReportedException(crashReport);
});
throw new ReportedException(crashReport);
}
}
}
@Override
public ChunkGenerationTask scheduleGenerationTask(ChunkStatus targetStatus, ChunkPos pos) {
ChunkGenerationTask chunkGenerationTask = ChunkGenerationTask.create(this, targetStatus, pos);
this.pendingGenerationTasks.add(chunkGenerationTask);
return chunkGenerationTask;
}
private void runGenerationTask(ChunkGenerationTask task) {
GenerationChunkHolder generationChunkHolder = task.getCenter();
this.worldgenTaskDispatcher.submit(() -> {
CompletableFuture<?> completableFuture = task.runUntilWait();
if (completableFuture != null) {
completableFuture.thenRun(() -> this.runGenerationTask(task));
}
}, generationChunkHolder.getPos().toLong(), generationChunkHolder::getQueueLevel);
}
@Override
public void runGenerationTasks() {
this.pendingGenerationTasks.forEach(this::runGenerationTask);
this.pendingGenerationTasks.clear();
}
public CompletableFuture<ChunkResult<LevelChunk>> prepareTickingChunk(ChunkHolder holder) {
CompletableFuture<ChunkResult<List<ChunkAccess>>> completableFuture = this.getChunkRangeFuture(holder, 1, i -> ChunkStatus.FULL);
CompletableFuture<ChunkResult<LevelChunk>> completableFuture2 = completableFuture.thenApplyAsync(chunkResult -> chunkResult.map(list -> {
LevelChunk levelChunk = (LevelChunk)list.get(list.size() / 2);
levelChunk.postProcessGeneration(this.level);
this.level.startTickingChunk(levelChunk);
CompletableFuture<?> completableFuturex = holder.getSendSyncFuture();
if (completableFuturex.isDone()) {
this.onChunkReadyToSend(holder, levelChunk);
} else {
completableFuturex.thenAcceptAsync(object -> this.onChunkReadyToSend(holder, levelChunk), this.mainThreadExecutor);
}
return levelChunk;
}), this.mainThreadExecutor);
completableFuture2.handle((chunkResult, throwable) -> {
this.tickingGenerated.getAndIncrement();
return null;
});
return completableFuture2;
}
private void onChunkReadyToSend(ChunkHolder chunkHolder, LevelChunk chunk) {
ChunkPos chunkPos = chunk.getPos();
for (ServerPlayer serverPlayer : this.playerMap.getAllPlayers()) {
if (serverPlayer.getChunkTrackingView().contains(chunkPos)) {
markChunkPendingToSend(serverPlayer, chunk);
}
}
this.level.getChunkSource().onChunkReadyToSend(chunkHolder);
}
public CompletableFuture<ChunkResult<LevelChunk>> prepareAccessibleChunk(ChunkHolder chunk) {
return this.getChunkRangeFuture(chunk, 1, ChunkLevel::getStatusAroundFullChunk)
.thenApply(chunkResult -> chunkResult.map(list -> (LevelChunk)list.get(list.size() / 2)));
}
public int getTickingGenerated() {
return this.tickingGenerated.get();
}
private boolean saveChunkIfNeeded(ChunkHolder chunk, long gametime) {
if (chunk.wasAccessibleSinceLastSave() && chunk.isReadyForSaving()) {
ChunkAccess chunkAccess = chunk.getLatestChunk();
if (!(chunkAccess instanceof ImposterProtoChunk) && !(chunkAccess instanceof LevelChunk)) {
return false;
} else if (!chunkAccess.isUnsaved()) {
return false;
} else {
long l = chunkAccess.getPos().toLong();
long m = this.nextChunkSaveTime.getOrDefault(l, -1L);
if (gametime < m) {
return false;
} else {
boolean bl = this.save(chunkAccess);
chunk.refreshAccessibility();
if (bl) {
this.nextChunkSaveTime.put(l, gametime + 10000L);
}
return bl;
}
}
} else {
return false;
}
}
private boolean save(ChunkAccess chunk) {
this.poiManager.flush(chunk.getPos());
if (!chunk.tryMarkSaved()) {
return false;
} else {
ChunkPos chunkPos = chunk.getPos();
try {
ChunkStatus chunkStatus = chunk.getPersistedStatus();
if (chunkStatus.getChunkType() != ChunkType.LEVELCHUNK) {
if (this.isExistingChunkFull(chunkPos)) {
return false;
}
if (chunkStatus == ChunkStatus.EMPTY && chunk.getAllStarts().values().stream().noneMatch(StructureStart::isValid)) {
return false;
}
}
Profiler.get().incrementCounter("chunkSave");
this.activeChunkWrites.incrementAndGet();
SerializableChunkData serializableChunkData = SerializableChunkData.copyOf(this.level, chunk);
CompletableFuture<CompoundTag> completableFuture = CompletableFuture.supplyAsync(serializableChunkData::write, Util.backgroundExecutor());
this.write(chunkPos, completableFuture::join).handle((void_, throwable) -> {
if (throwable != null) {
this.level.getServer().reportChunkSaveFailure(throwable, this.storageInfo(), chunkPos);
}
this.activeChunkWrites.decrementAndGet();
return null;
});
this.markPosition(chunkPos, chunkStatus.getChunkType());
return true;
} catch (Exception var6) {
this.level.getServer().reportChunkSaveFailure(var6, this.storageInfo(), chunkPos);
return false;
}
}
}
private boolean isExistingChunkFull(ChunkPos chunkPos) {
byte b = this.chunkTypeCache.get(chunkPos.toLong());
if (b != 0) {
return b == 1;
} else {
CompoundTag compoundTag;
try {
compoundTag = (CompoundTag)((Optional)this.readChunk(chunkPos).join()).orElse(null);
if (compoundTag == null) {
this.markPositionReplaceable(chunkPos);
return false;
}
} catch (Exception var5) {
LOGGER.error("Failed to read chunk {}", chunkPos, var5);
this.markPositionReplaceable(chunkPos);
return false;
}
ChunkType chunkType = SerializableChunkData.getChunkStatusFromTag(compoundTag).getChunkType();
return this.markPosition(chunkPos, chunkType) == 1;
}
}
protected void setServerViewDistance(int viewDistance) {
int i = Mth.clamp(viewDistance, 2, 32);
if (i != this.serverViewDistance) {
this.serverViewDistance = i;
this.distanceManager.updatePlayerTickets(this.serverViewDistance);
for (ServerPlayer serverPlayer : this.playerMap.getAllPlayers()) {
this.updateChunkTracking(serverPlayer);
}
}
}
int getPlayerViewDistance(ServerPlayer player) {
return Mth.clamp(player.requestedViewDistance(), 2, this.serverViewDistance);
}
private void markChunkPendingToSend(ServerPlayer player, ChunkPos chunkPos) {
LevelChunk levelChunk = this.getChunkToSend(chunkPos.toLong());
if (levelChunk != null) {
markChunkPendingToSend(player, levelChunk);
}
}
private static void markChunkPendingToSend(ServerPlayer player, LevelChunk chunk) {
player.connection.chunkSender.markChunkPendingToSend(chunk);
}
private static void dropChunk(ServerPlayer player, ChunkPos chunkPos) {
player.connection.chunkSender.dropChunk(player, chunkPos);
}
@Nullable
public LevelChunk getChunkToSend(long chunkPos) {
ChunkHolder chunkHolder = this.getVisibleChunkIfPresent(chunkPos);
return chunkHolder == null ? null : chunkHolder.getChunkToSend();
}
public int size() {
return this.visibleChunkMap.size();
}
public net.minecraft.server.level.DistanceManager getDistanceManager() {
return this.distanceManager;
}
/**
* Gets an unmodifiable iterable of all loaded chunks in the chunk manager
*/
protected Iterable<ChunkHolder> getChunks() {
return Iterables.unmodifiableIterable(this.visibleChunkMap.values());
}
void dumpChunks(Writer writer) throws IOException {
CsvOutput csvOutput = CsvOutput.builder()
.addColumn("x")
.addColumn("z")
.addColumn("level")
.addColumn("in_memory")
.addColumn("status")
.addColumn("full_status")
.addColumn("accessible_ready")
.addColumn("ticking_ready")
.addColumn("entity_ticking_ready")
.addColumn("ticket")
.addColumn("spawning")
.addColumn("block_entity_count")
.addColumn("ticking_ticket")
.addColumn("ticking_level")
.addColumn("block_ticks")
.addColumn("fluid_ticks")
.build(writer);
for (Entry<ChunkHolder> entry : this.visibleChunkMap.long2ObjectEntrySet()) {
long l = entry.getLongKey();
ChunkPos chunkPos = new ChunkPos(l);
ChunkHolder chunkHolder = (ChunkHolder)entry.getValue();
Optional<ChunkAccess> optional = Optional.ofNullable(chunkHolder.getLatestChunk());
Optional<LevelChunk> optional2 = optional.flatMap(chunkAccess -> chunkAccess instanceof LevelChunk ? Optional.of((LevelChunk)chunkAccess) : Optional.empty());
csvOutput.writeRow(
chunkPos.x,
chunkPos.z,
chunkHolder.getTicketLevel(),
optional.isPresent(),
optional.map(ChunkAccess::getPersistedStatus).orElse(null),
optional2.map(LevelChunk::getFullStatus).orElse(null),
printFuture(chunkHolder.getFullChunkFuture()),
printFuture(chunkHolder.getTickingChunkFuture()),
printFuture(chunkHolder.getEntityTickingChunkFuture()),
this.ticketStorage.getTicketDebugString(l, false),
this.anyPlayerCloseEnoughForSpawning(chunkPos),
optional2.map(levelChunk -> levelChunk.getBlockEntities().size()).orElse(0),
this.ticketStorage.getTicketDebugString(l, true),
this.distanceManager.getChunkLevel(l, true),
optional2.map(levelChunk -> levelChunk.getBlockTicks().count()).orElse(0),
optional2.map(levelChunk -> levelChunk.getFluidTicks().count()).orElse(0)
);
}
}
private static String printFuture(CompletableFuture<ChunkResult<LevelChunk>> future) {
try {
ChunkResult<LevelChunk> chunkResult = (ChunkResult<LevelChunk>)future.getNow(null);
if (chunkResult != null) {
return chunkResult.isSuccess() ? "done" : "unloaded";
} else {
return "not completed";
}
} catch (CompletionException var2) {
return "failed " + var2.getCause().getMessage();
} catch (CancellationException var3) {
return "cancelled";
}
}
private CompletableFuture<Optional<CompoundTag>> readChunk(ChunkPos pos) {
return this.read(pos).thenApplyAsync(optional -> optional.map(this::upgradeChunkTag), Util.backgroundExecutor().forName("upgradeChunk"));
}
private CompoundTag upgradeChunkTag(CompoundTag tag) {
return this.upgradeChunkTag(this.level.dimension(), this.overworldDataStorage, tag, this.generator().getTypeNameForDataFixer());
}
void collectSpawningChunks(List<LevelChunk> output) {
LongIterator longIterator = this.distanceManager.getSpawnCandidateChunks();
while (longIterator.hasNext()) {
ChunkHolder chunkHolder = this.visibleChunkMap.get(longIterator.nextLong());
if (chunkHolder != null) {
LevelChunk levelChunk = chunkHolder.getTickingChunk();
if (levelChunk != null && this.anyPlayerCloseEnoughForSpawningInternal(chunkHolder.getPos())) {
output.add(levelChunk);
}
}
}
}
void forEachBlockTickingChunk(Consumer<LevelChunk> action) {
this.distanceManager.forEachEntityTickingChunk(l -> {
ChunkHolder chunkHolder = this.visibleChunkMap.get(l);
if (chunkHolder != null) {
LevelChunk levelChunk = chunkHolder.getTickingChunk();
if (levelChunk != null) {
action.accept(levelChunk);
}
}
});
}
boolean anyPlayerCloseEnoughForSpawning(ChunkPos chunkPos) {
TriState triState = this.distanceManager.hasPlayersNearby(chunkPos.toLong());
return triState == TriState.DEFAULT ? this.anyPlayerCloseEnoughForSpawningInternal(chunkPos) : triState.toBoolean(true);
}
private boolean anyPlayerCloseEnoughForSpawningInternal(ChunkPos chunkPos) {
for (ServerPlayer serverPlayer : this.playerMap.getAllPlayers()) {
if (this.playerIsCloseEnoughForSpawning(serverPlayer, chunkPos)) {
return true;
}
}
return false;
}
public List<ServerPlayer> getPlayersCloseForSpawning(ChunkPos chunkPos) {
long l = chunkPos.toLong();
if (!this.distanceManager.hasPlayersNearby(l).toBoolean(true)) {
return List.of();
} else {
Builder<ServerPlayer> builder = ImmutableList.builder();
for (ServerPlayer serverPlayer : this.playerMap.getAllPlayers()) {
if (this.playerIsCloseEnoughForSpawning(serverPlayer, chunkPos)) {
builder.add(serverPlayer);
}
}
return builder.build();
}
}
private boolean playerIsCloseEnoughForSpawning(ServerPlayer player, ChunkPos chunkPos) {
if (player.isSpectator()) {
return false;
} else {
double d = euclideanDistanceSquared(chunkPos, player.position());
return d < 16384.0;
}
}
private static double euclideanDistanceSquared(ChunkPos chunkPos, Vec3 pos) {
double d = SectionPos.sectionToBlockCoord(chunkPos.x, 8);
double e = SectionPos.sectionToBlockCoord(chunkPos.z, 8);
double f = d - pos.x;
double g = e - pos.z;
return f * f + g * g;
}
private boolean skipPlayer(ServerPlayer player) {
return player.isSpectator() && !this.level.getGameRules().getBoolean(GameRules.RULE_SPECTATORSGENERATECHUNKS);
}
void updatePlayerStatus(ServerPlayer player, boolean track) {
boolean bl = this.skipPlayer(player);
boolean bl2 = this.playerMap.ignoredOrUnknown(player);
if (track) {
this.playerMap.addPlayer(player, bl);
this.updatePlayerPos(player);
if (!bl) {
this.distanceManager.addPlayer(SectionPos.of(player), player);
}
player.setChunkTrackingView(ChunkTrackingView.EMPTY);
this.updateChunkTracking(player);
} else {
SectionPos sectionPos = player.getLastSectionPos();
this.playerMap.removePlayer(player);
if (!bl2) {
this.distanceManager.removePlayer(sectionPos, player);
}
this.applyChunkTrackingView(player, ChunkTrackingView.EMPTY);
}
}
private void updatePlayerPos(ServerPlayer player) {
SectionPos sectionPos = SectionPos.of(player);
player.setLastSectionPos(sectionPos);
}
public void move(ServerPlayer player) {
for (ChunkMap.TrackedEntity trackedEntity : this.entityMap.values()) {
if (trackedEntity.entity == player) {
trackedEntity.updatePlayers(this.level.players());
} else {
trackedEntity.updatePlayer(player);
}
}
SectionPos sectionPos = player.getLastSectionPos();
SectionPos sectionPos2 = SectionPos.of(player);
boolean bl = this.playerMap.ignored(player);
boolean bl2 = this.skipPlayer(player);
boolean bl3 = sectionPos.asLong() != sectionPos2.asLong();
if (bl3 || bl != bl2) {
this.updatePlayerPos(player);
if (!bl) {
this.distanceManager.removePlayer(sectionPos, player);
}
if (!bl2) {
this.distanceManager.addPlayer(sectionPos2, player);
}
if (!bl && bl2) {
this.playerMap.ignorePlayer(player);
}
if (bl && !bl2) {
this.playerMap.unIgnorePlayer(player);
}
this.updateChunkTracking(player);
}
}
private void updateChunkTracking(ServerPlayer player) {
ChunkPos chunkPos = player.chunkPosition();
int i = this.getPlayerViewDistance(player);
if (!(player.getChunkTrackingView() instanceof Positioned positioned && positioned.center().equals(chunkPos) && positioned.viewDistance() == i)) {
this.applyChunkTrackingView(player, ChunkTrackingView.of(chunkPos, i));
}
}
private void applyChunkTrackingView(ServerPlayer player, ChunkTrackingView chunkTrackingView) {
if (player.level() == this.level) {
ChunkTrackingView chunkTrackingView2 = player.getChunkTrackingView();
if (chunkTrackingView instanceof Positioned positioned
&& !(chunkTrackingView2 instanceof Positioned positioned2 && positioned2.center().equals(positioned.center()))) {
player.connection.send(new ClientboundSetChunkCacheCenterPacket(positioned.center().x, positioned.center().z));
}
ChunkTrackingView.difference(
chunkTrackingView2, chunkTrackingView, chunkPos -> this.markChunkPendingToSend(player, chunkPos), chunkPos -> dropChunk(player, chunkPos)
);
player.setChunkTrackingView(chunkTrackingView);
}
}
@Override
public List<ServerPlayer> getPlayers(ChunkPos pos, boolean boundaryOnly) {
Set<ServerPlayer> set = this.playerMap.getAllPlayers();
Builder<ServerPlayer> builder = ImmutableList.builder();
for (ServerPlayer serverPlayer : set) {
if (boundaryOnly && this.isChunkOnTrackedBorder(serverPlayer, pos.x, pos.z) || !boundaryOnly && this.isChunkTracked(serverPlayer, pos.x, pos.z)) {
builder.add(serverPlayer);
}
}
return builder.build();
}
protected void addEntity(Entity entity) {
if (!(entity instanceof EnderDragonPart)) {
EntityType<?> entityType = entity.getType();
int i = entityType.clientTrackingRange() * 16;
if (i != 0) {
int j = entityType.updateInterval();
if (this.entityMap.containsKey(entity.getId())) {
throw (IllegalStateException)Util.pauseInIde(new IllegalStateException("Entity is already tracked!"));
} else {
ChunkMap.TrackedEntity trackedEntity = new ChunkMap.TrackedEntity(entity, i, j, entityType.trackDeltas());
this.entityMap.put(entity.getId(), trackedEntity);
trackedEntity.updatePlayers(this.level.players());
if (entity instanceof ServerPlayer serverPlayer) {
this.updatePlayerStatus(serverPlayer, true);
for (ChunkMap.TrackedEntity trackedEntity2 : this.entityMap.values()) {
if (trackedEntity2.entity != serverPlayer) {
trackedEntity2.updatePlayer(serverPlayer);
}
}
}
}
}
}
}
protected void removeEntity(Entity entity) {
if (entity instanceof ServerPlayer serverPlayer) {
this.updatePlayerStatus(serverPlayer, false);
for (ChunkMap.TrackedEntity trackedEntity : this.entityMap.values()) {
trackedEntity.removePlayer(serverPlayer);
}
}
ChunkMap.TrackedEntity trackedEntity2 = this.entityMap.remove(entity.getId());
if (trackedEntity2 != null) {
trackedEntity2.broadcastRemoved();
}
}
protected void tick() {
for (ServerPlayer serverPlayer : this.playerMap.getAllPlayers()) {
this.updateChunkTracking(serverPlayer);
}
List<ServerPlayer> list = Lists.<ServerPlayer>newArrayList();
List<ServerPlayer> list2 = this.level.players();
for (ChunkMap.TrackedEntity trackedEntity : this.entityMap.values()) {
SectionPos sectionPos = trackedEntity.lastSectionPos;
SectionPos sectionPos2 = SectionPos.of(trackedEntity.entity);
boolean bl = !Objects.equals(sectionPos, sectionPos2);
if (bl) {
trackedEntity.updatePlayers(list2);
Entity entity = trackedEntity.entity;
if (entity instanceof ServerPlayer) {
list.add((ServerPlayer)entity);
}
trackedEntity.lastSectionPos = sectionPos2;
}
if (bl || this.distanceManager.inEntityTickingRange(sectionPos2.chunk().toLong())) {
trackedEntity.serverEntity.sendChanges();
}
}
if (!list.isEmpty()) {
for (ChunkMap.TrackedEntity trackedEntity : this.entityMap.values()) {
trackedEntity.updatePlayers(list);
}
}
}
public void broadcast(Entity entity, Packet<?> packet) {
ChunkMap.TrackedEntity trackedEntity = this.entityMap.get(entity.getId());
if (trackedEntity != null) {
trackedEntity.broadcast(packet);
}
}
protected void broadcastAndSend(Entity entity, Packet<?> packet) {
ChunkMap.TrackedEntity trackedEntity = this.entityMap.get(entity.getId());
if (trackedEntity != null) {
trackedEntity.broadcastAndSend(packet);
}
}
public void resendBiomesForChunks(List<ChunkAccess> chunks) {
Map<ServerPlayer, List<LevelChunk>> map = new HashMap();
for (ChunkAccess chunkAccess : chunks) {
ChunkPos chunkPos = chunkAccess.getPos();
LevelChunk levelChunk2;
if (chunkAccess instanceof LevelChunk levelChunk) {
levelChunk2 = levelChunk;
} else {
levelChunk2 = this.level.getChunk(chunkPos.x, chunkPos.z);
}
for (ServerPlayer serverPlayer : this.getPlayers(chunkPos, false)) {
((List)map.computeIfAbsent(serverPlayer, serverPlayerx -> new ArrayList())).add(levelChunk2);
}
}
map.forEach((serverPlayerx, list) -> serverPlayerx.connection.send(ClientboundChunksBiomesPacket.forChunks(list)));
}
protected PoiManager getPoiManager() {
return this.poiManager;
}
public String getStorageName() {
return this.storageName;
}
void onFullChunkStatusChange(ChunkPos chunkPos, FullChunkStatus fullChunkStatus) {
this.chunkStatusListener.onChunkStatusChange(chunkPos, fullChunkStatus);
}
public void waitForLightBeforeSending(ChunkPos chunkPos, int range) {
int i = range + 1;
ChunkPos.rangeClosed(chunkPos, i).forEach(chunkPosx -> {
ChunkHolder chunkHolder = this.getVisibleChunkIfPresent(chunkPosx.toLong());
if (chunkHolder != null) {
chunkHolder.addSendDependency(this.lightEngine.waitForPendingTasks(chunkPosx.x, chunkPosx.z));
}
});
}
class DistanceManager extends net.minecraft.server.level.DistanceManager {
protected DistanceManager(final TicketStorage ticketStorage, final Executor dispatcher, final Executor mainThreadExecutor) {
super(ticketStorage, dispatcher, mainThreadExecutor);
}
@Override
protected boolean isChunkToRemove(long chunkPos) {
return ChunkMap.this.toDrop.contains(chunkPos);
}
@Nullable
@Override
protected ChunkHolder getChunk(long chunkPos) {
return ChunkMap.this.getUpdatingChunkIfPresent(chunkPos);
}
@Nullable
@Override
protected ChunkHolder updateChunkScheduling(long chunkPos, int newLevel, @Nullable ChunkHolder holder, int oldLevel) {
return ChunkMap.this.updateChunkScheduling(chunkPos, newLevel, holder, oldLevel);
}
}
class TrackedEntity {
final ServerEntity serverEntity;
final Entity entity;
private final int range;
SectionPos lastSectionPos;
private final Set<ServerPlayerConnection> seenBy = Sets.newIdentityHashSet();
public TrackedEntity(final Entity entity, final int range, final int updateInterval, final boolean trackDelta) {
this.serverEntity = new ServerEntity(ChunkMap.this.level, entity, updateInterval, trackDelta, this::broadcast, this::broadcastIgnorePlayers);
this.entity = entity;
this.range = range;
this.lastSectionPos = SectionPos.of(entity);
}
public boolean equals(Object object) {
return object instanceof ChunkMap.TrackedEntity ? ((ChunkMap.TrackedEntity)object).entity.getId() == this.entity.getId() : false;
}
public int hashCode() {
return this.entity.getId();
}
public void broadcast(Packet<?> packet) {
for (ServerPlayerConnection serverPlayerConnection : this.seenBy) {
serverPlayerConnection.send(packet);
}
}
public void broadcastIgnorePlayers(Packet<?> packet, List<UUID> ignoredPlayers) {
for (ServerPlayerConnection serverPlayerConnection : this.seenBy) {
if (!ignoredPlayers.contains(serverPlayerConnection.getPlayer().getUUID())) {
serverPlayerConnection.send(packet);
}
}
}
public void broadcastAndSend(Packet<?> packet) {
this.broadcast(packet);
if (this.entity instanceof ServerPlayer) {
((ServerPlayer)this.entity).connection.send(packet);
}
}
public void broadcastRemoved() {
for (ServerPlayerConnection serverPlayerConnection : this.seenBy) {
this.serverEntity.removePairing(serverPlayerConnection.getPlayer());
}
}
public void removePlayer(ServerPlayer player) {
if (this.seenBy.remove(player.connection)) {
this.serverEntity.removePairing(player);
}
}
public void updatePlayer(ServerPlayer player) {
if (player != this.entity) {
Vec3 vec3 = player.position().subtract(this.entity.position());
int i = ChunkMap.this.getPlayerViewDistance(player);
double d = Math.min(this.getEffectiveRange(), i * 16);
double e = vec3.x * vec3.x + vec3.z * vec3.z;
double f = d * d;
boolean bl = e <= f
&& this.entity.broadcastToPlayer(player)
&& ChunkMap.this.isChunkTracked(player, this.entity.chunkPosition().x, this.entity.chunkPosition().z);
if (bl) {
if (this.seenBy.add(player.connection)) {
this.serverEntity.addPairing(player);
}
} else if (this.seenBy.remove(player.connection)) {
this.serverEntity.removePairing(player);
}
}
}
private int scaledRange(int trackingDistance) {
return ChunkMap.this.level.getServer().getScaledTrackingDistance(trackingDistance);
}
private int getEffectiveRange() {
int i = this.range;
for (Entity entity : this.entity.getIndirectPassengers()) {
int j = entity.getType().clientTrackingRange() * 16;
if (j > i) {
i = j;
}
}
return this.scaledRange(i);
}
public void updatePlayers(List<ServerPlayer> playersList) {
for (ServerPlayer serverPlayer : playersList) {
this.updatePlayer(serverPlayer);
}
}
}
}