/* * Decompiled with CFR 0.152. * * Could not load the following classes: * com.google.common.collect.ImmutableList * com.google.common.collect.ImmutableList$Builder * com.google.common.collect.Lists * com.google.common.collect.Queues * com.google.common.collect.Sets * com.mojang.datafixers.DataFixer * com.mojang.logging.LogUtils * com.mojang.serialization.MapCodec * it.unimi.dsi.fastutil.ints.Int2ObjectMap * it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap * it.unimi.dsi.fastutil.longs.Long2ByteMap * it.unimi.dsi.fastutil.longs.Long2ByteOpenHashMap * it.unimi.dsi.fastutil.longs.Long2LongMap * it.unimi.dsi.fastutil.longs.Long2LongOpenHashMap * it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap * it.unimi.dsi.fastutil.longs.Long2ObjectMap$Entry * it.unimi.dsi.fastutil.longs.LongIterator * it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet * it.unimi.dsi.fastutil.longs.LongOpenHashSet * it.unimi.dsi.fastutil.longs.LongSet * org.apache.commons.lang3.mutable.MutableBoolean * org.jspecify.annotations.Nullable * org.slf4j.Logger */ package net.minecraft.server.level; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.common.collect.Queues; import com.google.common.collect.Sets; import com.mojang.datafixers.DataFixer; import com.mojang.logging.LogUtils; import com.mojang.serialization.MapCodec; 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.Long2ObjectMap; 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 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.Objects; import java.util.Optional; import java.util.Queue; import java.util.Set; import java.util.concurrent.CancellationException; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import java.util.concurrent.CompletionStage; 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.Predicate; import java.util.function.Supplier; import java.util.stream.Stream; import net.minecraft.CrashReport; import net.minecraft.CrashReportCategory; import net.minecraft.ReportedException; import net.minecraft.core.BlockPos; 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.ClientGamePacketListener; import net.minecraft.network.protocol.game.ClientboundChunksBiomesPacket; import net.minecraft.network.protocol.game.ClientboundSetChunkCacheCenterPacket; import net.minecraft.resources.ResourceKey; import net.minecraft.server.level.ChunkGenerationTask; import net.minecraft.server.level.ChunkHolder; import net.minecraft.server.level.ChunkLevel; import net.minecraft.server.level.ChunkResult; import net.minecraft.server.level.ChunkTaskDispatcher; import net.minecraft.server.level.ChunkTaskPriorityQueue; import net.minecraft.server.level.ChunkTrackingView; import net.minecraft.server.level.FullChunkStatus; import net.minecraft.server.level.GeneratingChunkMap; import net.minecraft.server.level.GenerationChunkHolder; import net.minecraft.server.level.PlayerMap; import net.minecraft.server.level.ServerEntity; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.level.ThreadedLevelLightEngine; 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.Util; import net.minecraft.util.datafix.DataFixTypes; 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.Level; 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.RegionStorageInfo; import net.minecraft.world.level.chunk.storage.SerializableChunkData; import net.minecraft.world.level.chunk.storage.SimpleRegionStorage; import net.minecraft.world.level.entity.ChunkStatusUpdateListener; import net.minecraft.world.level.gamerules.GameRules; 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.LegacyStructureDataHandler; 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.jspecify.annotations.Nullable; import org.slf4j.Logger; public class ChunkMap extends SimpleRegionStorage implements ChunkHolder.PlayerProvider, GeneratingChunkMap { private static final ChunkResult> UNLOADED_CHUNK_LIST_RESULT = ChunkResult.error("Unloaded chunks found in range"); private static final CompletableFuture>> 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); private final Long2ObjectLinkedOpenHashMap updatingChunkMap = new Long2ObjectLinkedOpenHashMap(); private volatile Long2ObjectLinkedOpenHashMap visibleChunkMap = this.updatingChunkMap.clone(); private final Long2ObjectLinkedOpenHashMap pendingUnloads = new Long2ObjectLinkedOpenHashMap(); private final List pendingGenerationTasks = new ArrayList(); private final ServerLevel level; private final ThreadedLevelLightEngine lightEngine; private final BlockableEventLoop mainThreadExecutor; private final RandomState randomState; private final ChunkGeneratorStructureState chunkGeneratorState; private final TicketStorage ticketStorage; private final PoiManager poiManager; private final LongSet toDrop = new LongOpenHashSet(); private boolean modified; private final ChunkTaskDispatcher worldgenTaskDispatcher; private final ChunkTaskDispatcher lightTaskDispatcher; private final ChunkStatusUpdateListener chunkStatusListener; private final DistanceManager distanceManager; private final String storageName; private final PlayerMap playerMap = new PlayerMap(); private final Int2ObjectMap entityMap = new Int2ObjectOpenHashMap(); private final Long2ByteMap chunkTypeCache = new Long2ByteOpenHashMap(); private final Long2LongMap nextChunkSaveTime = new Long2LongOpenHashMap(); private final LongSet chunksToEagerlySave = new LongLinkedOpenHashSet(); private final Queue unloadQueue = Queues.newConcurrentLinkedQueue(); private final AtomicInteger activeChunkWrites = new AtomicInteger(); private int serverViewDistance; private final WorldGenContext worldGenContext; public ChunkMap(ServerLevel level, LevelStorageSource.LevelStorageAccess levelStorage, DataFixer dataFixer, StructureTemplateManager structureManager, Executor executor, BlockableEventLoop mainThreadExecutor, LightChunkGetter chunkGetter, ChunkGenerator generator, ChunkStatusUpdateListener chunkStatusListener, Supplier overworldDataStorage, TicketStorage ticketStorage, int serverViewDistance, boolean syncWrites) { super(new RegionStorageInfo(levelStorage.getLevelId(), level.dimension(), "chunk"), levelStorage.getDimensionPath(level.dimension()).resolve("region"), dataFixer, syncWrites, DataFixTypes.CHUNK, LegacyStructureDataHandler.getLegacyTagFixer(level.dimension(), overworldDataStorage, dataFixer)); Path storageFolder = levelStorage.getDimensionPath(level.dimension()); this.storageName = storageFolder.getFileName().toString(); this.level = level; RegistryAccess registryAccess = level.registryAccess(); long levelSeed = level.getSeed(); if (generator instanceof NoiseBasedChunkGenerator) { NoiseBasedChunkGenerator noiseGenerator = (NoiseBasedChunkGenerator)generator; this.randomState = RandomState.create(noiseGenerator.generatorSettings().value(), registryAccess.lookupOrThrow(Registries.NOISE), levelSeed); } else { this.randomState = RandomState.create(NoiseGeneratorSettings.dummy(), registryAccess.lookupOrThrow(Registries.NOISE), levelSeed); } this.chunkGeneratorState = generator.createState(registryAccess.lookupOrThrow(Registries.STRUCTURE_SET), this.randomState, levelSeed); this.mainThreadExecutor = mainThreadExecutor; ConsecutiveExecutor worldgen = new ConsecutiveExecutor(executor, "worldgen"); this.chunkStatusListener = chunkStatusListener; ConsecutiveExecutor light = new ConsecutiveExecutor(executor, "light"); this.worldgenTaskDispatcher = new ChunkTaskDispatcher(worldgen, executor); this.lightTaskDispatcher = new ChunkTaskDispatcher(light, executor); this.lightEngine = new ThreadedLevelLightEngine(chunkGetter, this, this.level.dimensionType().hasSkyLight(), light, this.lightTaskDispatcher); this.distanceManager = new DistanceManager(ticketStorage, executor, mainThreadExecutor); this.ticketStorage = ticketStorage; this.poiManager = new PoiManager(new RegionStorageInfo(levelStorage.getLevelId(), level.dimension(), "poi"), storageFolder.resolve("poi"), dataFixer, syncWrites, 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; } public boolean isChunkTracked(ServerPlayer player, int chunkX, int chunkZ) { return player.getChunkTrackingView().contains(chunkX, chunkZ) && !player.connection.chunkSender.isPending(ChunkPos.asLong(chunkX, chunkZ)); } private boolean isChunkOnTrackedBorder(ServerPlayer player, int chunkX, int chunkZ) { if (!this.isChunkTracked(player, chunkX, chunkZ)) { return false; } for (int dx = -1; dx <= 1; ++dx) { for (int dz = -1; dz <= 1; ++dz) { if (dx == 0 && dz == 0 || this.isChunkTracked(player, chunkX + dx, chunkZ + dz)) continue; return true; } } return false; } protected ThreadedLevelLightEngine getLightEngine() { return this.lightEngine; } public @Nullable ChunkHolder getUpdatingChunkIfPresent(long key) { return (ChunkHolder)this.updatingChunkMap.get(key); } protected @Nullable ChunkHolder getVisibleChunkIfPresent(long key) { return (ChunkHolder)this.visibleChunkMap.get(key); } public @Nullable ChunkStatus getLatestStatus(long key) { ChunkHolder chunkHolder = this.getVisibleChunkIfPresent(key); return chunkHolder != null ? chunkHolder.getLatestStatus() : null; } protected IntSupplier getChunkQueueLevel(long pos) { return () -> { ChunkHolder chunk = this.getVisibleChunkIfPresent(pos); if (chunk == null) { return ChunkTaskPriorityQueue.PRIORITY_LEVEL_COUNT - 1; } return Math.min(chunk.getQueueLevel(), ChunkTaskPriorityQueue.PRIORITY_LEVEL_COUNT - 1); }; } public String getChunkDebugData(ChunkPos pos) { ChunkHolder chunkHolder = this.getVisibleChunkIfPresent(pos.toLong()); if (chunkHolder == null) { return "null"; } String result = chunkHolder.getTicketLevel() + "\n"; ChunkStatus status = chunkHolder.getLatestStatus(); ChunkAccess chunk = chunkHolder.getLatestChunk(); if (status != null) { result = result + "St: \u00a7" + status.getIndex() + String.valueOf(status) + "\u00a7r\n"; } if (chunk != null) { result = result + "Ch: \u00a7" + chunk.getPersistedStatus().getIndex() + String.valueOf(chunk.getPersistedStatus()) + "\u00a7r\n"; } FullChunkStatus fullStatus = chunkHolder.getFullStatus(); result = result + String.valueOf('\u00a7') + fullStatus.ordinal() + String.valueOf((Object)fullStatus); return result + "\u00a7r"; } CompletableFuture>> getChunkRangeFuture(ChunkHolder centerChunk, int range, IntFunction distanceToStatus) { if (range == 0) { ChunkStatus status = distanceToStatus.apply(0); return centerChunk.scheduleChunkGenerationTask(status, this).thenApply(r -> r.map(List::of)); } int chunkCount = Mth.square(range * 2 + 1); ArrayList>> deps = new ArrayList>>(chunkCount); ChunkPos centerPos = centerChunk.getPos(); for (int z = -range; z <= range; ++z) { for (int x = -range; x <= range; ++x) { int distance = Math.max(Math.abs(x), Math.abs(z)); long chunkNode = ChunkPos.asLong(centerPos.x + x, centerPos.z + z); ChunkHolder chunk = this.getUpdatingChunkIfPresent(chunkNode); if (chunk == null) { return UNLOADED_CHUNK_LIST_FUTURE; } ChunkStatus depStatus = distanceToStatus.apply(distance); deps.add(chunk.scheduleChunkGenerationTask(depStatus, this)); } } return Util.sequence(deps).thenApply(chunkResults -> { ArrayList chunks = new ArrayList(chunkResults.size()); for (ChunkResult chunkResult : chunkResults) { if (chunkResult == null) { throw this.debugFuturesAndCreateReportedException(new IllegalStateException("At least one of the chunk futures were null"), "n/a"); } ChunkAccess chunk = chunkResult.orElse(null); if (chunk == null) { return UNLOADED_CHUNK_LIST_RESULT; } chunks.add(chunk); } return ChunkResult.of(chunks); }); } public ReportedException debugFuturesAndCreateReportedException(IllegalStateException exception, String details) { StringBuilder sb = new StringBuilder(); Consumer addToDebug = holder -> holder.getAllFutures().forEach(pair -> { ChunkStatus status = (ChunkStatus)pair.getFirst(); CompletableFuture future = (CompletableFuture)pair.getSecond(); if (future != null && future.isDone() && future.join() == null) { sb.append(holder.getPos()).append(" - status: ").append(status).append(" future: ").append(future).append(System.lineSeparator()); } }); sb.append("Updating:").append(System.lineSeparator()); this.updatingChunkMap.values().forEach(addToDebug); sb.append("Visible:").append(System.lineSeparator()); this.visibleChunkMap.values().forEach(addToDebug); CrashReport report = CrashReport.forThrowable(exception, "Chunk loading"); CrashReportCategory category = report.addCategory("Chunk loading"); category.setDetail("Details", details); category.setDetail("Futures", sb); return new ReportedException(report); } public CompletableFuture> prepareEntityTickingChunk(ChunkHolder chunk) { return this.getChunkRangeFuture(chunk, 2, distance -> ChunkStatus.FULL).thenApply(chunkResult -> chunkResult.map(list -> (LevelChunk)list.get(list.size() / 2))); } private @Nullable ChunkHolder updateChunkScheduling(long node, int level, @Nullable ChunkHolder chunk, int oldLevel) { if (!ChunkLevel.isLoaded(oldLevel) && !ChunkLevel.isLoaded(level)) { return chunk; } if (chunk != null) { chunk.setTicketLevel(level); } if (chunk != null) { if (!ChunkLevel.isLoaded(level)) { this.toDrop.add(node); } else { this.toDrop.remove(node); } } if (ChunkLevel.isLoaded(level) && chunk == null) { chunk = (ChunkHolder)this.pendingUnloads.remove(node); if (chunk != null) { chunk.setTicketLevel(level); } else { chunk = new ChunkHolder(new ChunkPos(node), level, this.level, this.lightEngine, this::onLevelChange, this); } this.updatingChunkMap.put(node, (Object)chunk); this.modified = true; } return chunk; } private void onLevelChange(ChunkPos pos, IntSupplier oldLevel, int newLevel, IntConsumer setQueueLevel) { this.worldgenTaskDispatcher.onLevelChange(pos, oldLevel, newLevel, setQueueLevel); this.lightTaskDispatcher.onLevelChange(pos, oldLevel, newLevel, setQueueLevel); } @Override public void close() throws IOException { try { this.worldgenTaskDispatcher.close(); this.lightTaskDispatcher.close(); this.poiManager.close(); } finally { super.close(); } } protected void saveAllChunks(boolean flushStorage) { if (flushStorage) { List chunksToSave = this.visibleChunkMap.values().stream().filter(ChunkHolder::wasAccessibleSinceLastSave).peek(ChunkHolder::refreshAccessibility).toList(); MutableBoolean didWork = new MutableBoolean(); do { didWork.setFalse(); chunksToSave.stream().map(chunk -> { this.mainThreadExecutor.managedBlock(chunk::isReadyForSaving); return chunk.getLatestChunk(); }).filter(chunkAccess -> chunkAccess instanceof ImposterProtoChunk || chunkAccess instanceof LevelChunk).filter(this::save).forEach(c -> didWork.setTrue()); } while (didWork.isTrue()); this.poiManager.flushAll(); this.processUnloads(() -> true); this.synchronize(true).join(); } else { this.nextChunkSaveTime.clear(); long now = Util.getMillis(); for (ChunkHolder chunk2 : this.visibleChunkMap.values()) { this.saveChunkIfNeeded(chunk2, now); } } } protected void tick(BooleanSupplier haveTime) { ProfilerFiller profiler = Profiler.get(); profiler.push("poi"); this.poiManager.tick(haveTime); profiler.popPush("chunk_unload"); if (!this.level.noSave()) { this.processUnloads(haveTime); } profiler.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 haveTime) { Runnable unloadTask; LongIterator iterator = this.toDrop.iterator(); while (iterator.hasNext()) { long pos = iterator.nextLong(); ChunkHolder chunkHolder = (ChunkHolder)this.updatingChunkMap.get(pos); if (chunkHolder != null) { this.updatingChunkMap.remove(pos); this.pendingUnloads.put(pos, (Object)chunkHolder); this.modified = true; this.scheduleUnload(pos, chunkHolder); } iterator.remove(); } for (int minimalNumberOfChunksToProcess = Math.max(0, this.unloadQueue.size() - 2000); (minimalNumberOfChunksToProcess > 0 || haveTime.getAsBoolean()) && (unloadTask = this.unloadQueue.poll()) != null; --minimalNumberOfChunksToProcess) { unloadTask.run(); } this.saveChunksEagerly(haveTime); } private void saveChunksEagerly(BooleanSupplier haveTime) { long now = Util.getMillis(); int eagerlySavedCount = 0; LongIterator iterator = this.chunksToEagerlySave.iterator(); while (eagerlySavedCount < 20 && this.activeChunkWrites.get() < 128 && haveTime.getAsBoolean() && iterator.hasNext()) { ChunkAccess latestChunk; long chunkPos = iterator.nextLong(); ChunkHolder chunkHolder = (ChunkHolder)this.visibleChunkMap.get(chunkPos); ChunkAccess chunkAccess = latestChunk = chunkHolder != null ? chunkHolder.getLatestChunk() : null; if (latestChunk == null || !latestChunk.isUnsaved()) { iterator.remove(); continue; } if (!this.saveChunkIfNeeded(chunkHolder, now)) continue; ++eagerlySavedCount; iterator.remove(); } } private void scheduleUnload(long pos, ChunkHolder chunkHolder) { CompletableFuture saveSyncFuture = chunkHolder.getSaveSyncFuture(); ((CompletableFuture)saveSyncFuture.thenRunAsync(() -> { CompletableFuture currentFuture = chunkHolder.getSaveSyncFuture(); if (currentFuture != saveSyncFuture) { this.scheduleUnload(pos, chunkHolder); return; } ChunkAccess chunk = chunkHolder.getLatestChunk(); if (this.pendingUnloads.remove(pos, (Object)chunkHolder) && chunk != null) { LevelChunk levelChunk; if (chunk instanceof LevelChunk) { levelChunk = (LevelChunk)chunk; levelChunk.setLoaded(false); } this.save(chunk); if (chunk instanceof LevelChunk) { levelChunk = (LevelChunk)chunk; this.level.unload(levelChunk); } this.lightEngine.updateChunkStatus(chunk.getPos()); this.lightEngine.tryScheduleUpdate(); this.nextChunkSaveTime.remove(chunk.getPos().toLong()); } }, this.unloadQueue::add)).whenComplete((ignored, throwable) -> { if (throwable != null) { LOGGER.error("Failed to save chunk {}", (Object)chunkHolder.getPos(), throwable); } }); } protected boolean promoteChunkMap() { if (!this.modified) { return false; } this.visibleChunkMap = this.updatingChunkMap.clone(); this.modified = false; return true; } private CompletableFuture scheduleChunkLoad(ChunkPos pos) { CompletionStage chunkDataFuture = this.readChunk(pos).thenApplyAsync(chunkData -> chunkData.map(tag -> { SerializableChunkData parsedData = SerializableChunkData.parse(this.level, this.level.palettedContainerFactory(), tag); if (parsedData == null) { LOGGER.error("Chunk file at {} is missing level data, skipping", (Object)pos); } return parsedData; }), Util.backgroundExecutor().forName("parseChunk")); CompletableFuture poiFuture = this.poiManager.prefetch(pos); return ((CompletableFuture)((CompletableFuture)((CompletableFuture)chunkDataFuture).thenCombine(poiFuture, (chunkData, ignored) -> chunkData)).thenApplyAsync(chunkData -> { Profiler.get().incrementCounter("chunkLoad"); if (chunkData.isPresent()) { ProtoChunk chunk = ((SerializableChunkData)chunkData.get()).read(this.level, this.poiManager, this.storageInfo(), pos); this.markPosition(pos, ((ChunkAccess)chunk).getPersistedStatus().getChunkType()); return chunk; } return this.createEmptyChunk(pos); }, (Executor)this.mainThreadExecutor)).exceptionallyAsync(throwable -> this.handleChunkLoadFailure((Throwable)throwable, pos), (Executor)this.mainThreadExecutor); } private ChunkAccess handleChunkLoadFailure(Throwable throwable, ChunkPos pos) { boolean ioException; Throwable throwable2; Throwable unwrapped; if (throwable instanceof CompletionException) { CompletionException e = (CompletionException)throwable; v0 = e.getCause(); } else { v0 = unwrapped = throwable; } if (unwrapped instanceof ReportedException) { ReportedException e = (ReportedException)unwrapped; throwable2 = e.getCause(); } else { throwable2 = unwrapped; } Throwable cause = throwable2; boolean alwaysThrow = cause instanceof Error; boolean bl = ioException = cause instanceof IOException || cause instanceof NbtException; if (alwaysThrow || !ioException) { CrashReport report = CrashReport.forThrowable(throwable, "Exception loading chunk"); CrashReportCategory chunkBeingLoaded = report.addCategory("Chunk being loaded"); chunkBeingLoaded.setDetail("pos", pos); this.markPositionReplaceable(pos); throw new ReportedException(report); } this.level.getServer().reportChunkLoadFailure(cause, this.storageInfo(), pos); return this.createEmptyChunk(pos); } private ChunkAccess createEmptyChunk(ChunkPos pos) { this.markPositionReplaceable(pos); return new ProtoChunk(pos, UpgradeData.EMPTY, this.level, this.level.palettedContainerFactory(), null); } private void markPositionReplaceable(ChunkPos pos) { this.chunkTypeCache.put(pos.toLong(), (byte)-1); } private byte markPosition(ChunkPos pos, ChunkType type) { return this.chunkTypeCache.put(pos.toLong(), type == ChunkType.PROTOCHUNK ? (byte)-1 : 1); } @Override public GenerationChunkHolder acquireGeneration(long chunkNode) { ChunkHolder chunkHolder = (ChunkHolder)this.updatingChunkMap.get(chunkNode); chunkHolder.increaseGenerationRefCount(); return chunkHolder; } @Override public void releaseGeneration(GenerationChunkHolder chunkHolder) { chunkHolder.decreaseGenerationRefCount(); } @Override public CompletableFuture applyStep(GenerationChunkHolder chunkHolder, ChunkStep step, StaticCache2D cache) { ChunkPos pos = chunkHolder.getPos(); if (step.targetStatus() == ChunkStatus.EMPTY) { return this.scheduleChunkLoad(pos); } try { GenerationChunkHolder holder = cache.get(pos.x, pos.z); ChunkAccess centerChunk = holder.getChunkIfPresentUnchecked(step.targetStatus().getParent()); if (centerChunk == null) { throw new IllegalStateException("Parent chunk missing"); } return step.apply(this.worldGenContext, cache, centerChunk); } catch (Exception e) { e.getStackTrace(); CrashReport report = CrashReport.forThrowable(e, "Exception generating new chunk"); CrashReportCategory category = report.addCategory("Chunk to be generated"); category.setDetail("Status being generated", () -> step.targetStatus().getName()); category.setDetail("Location", String.format(Locale.ROOT, "%d,%d", pos.x, pos.z)); category.setDetail("Position hash", ChunkPos.asLong(pos.x, pos.z)); category.setDetail("Generator", this.generator()); this.mainThreadExecutor.execute(() -> { throw new ReportedException(report); }); throw new ReportedException(report); } } @Override public ChunkGenerationTask scheduleGenerationTask(ChunkStatus targetStatus, ChunkPos pos) { ChunkGenerationTask task = ChunkGenerationTask.create(this, targetStatus, pos); this.pendingGenerationTasks.add(task); return task; } private void runGenerationTask(ChunkGenerationTask task) { GenerationChunkHolder chunk = task.getCenter(); this.worldgenTaskDispatcher.submit(() -> { CompletableFuture future = task.runUntilWait(); if (future == null) { return; } future.thenRun(() -> this.runGenerationTask(task)); }, chunk.getPos().toLong(), chunk::getQueueLevel); } @Override public void runGenerationTasks() { this.pendingGenerationTasks.forEach(this::runGenerationTask); this.pendingGenerationTasks.clear(); } public CompletableFuture> prepareTickingChunk(ChunkHolder chunk) { CompletableFuture>> future = this.getChunkRangeFuture(chunk, 1, distance -> ChunkStatus.FULL); return future.thenApplyAsync(listResult -> listResult.map(list -> { LevelChunk levelChunk = (LevelChunk)list.get(list.size() / 2); levelChunk.postProcessGeneration(this.level); this.level.startTickingChunk(levelChunk); CompletableFuture sendSyncFuture = chunk.getSendSyncFuture(); if (sendSyncFuture.isDone()) { this.onChunkReadyToSend(chunk, levelChunk); } else { sendSyncFuture.thenAcceptAsync(ignored -> this.onChunkReadyToSend(chunk, levelChunk), (Executor)this.mainThreadExecutor); } return levelChunk; }), (Executor)this.mainThreadExecutor); } private void onChunkReadyToSend(ChunkHolder chunkHolder, LevelChunk chunk) { ChunkPos chunkPos = chunk.getPos(); for (ServerPlayer player : this.playerMap.getAllPlayers()) { if (!player.getChunkTrackingView().contains(chunkPos)) continue; ChunkMap.markChunkPendingToSend(player, chunk); } this.level.getChunkSource().onChunkReadyToSend(chunkHolder); this.level.debugSynchronizers().registerChunk(chunk); } public CompletableFuture> prepareAccessibleChunk(ChunkHolder chunk) { return this.getChunkRangeFuture(chunk, 1, ChunkLevel::getStatusAroundFullChunk).thenApply(chunkResult -> chunkResult.map(list -> (LevelChunk)list.get(list.size() / 2))); } Stream allChunksWithAtLeastStatus(ChunkStatus status) { int level = ChunkLevel.byStatus(status); return this.visibleChunkMap.values().stream().filter(chunk -> chunk.getTicketLevel() <= level); } private boolean saveChunkIfNeeded(ChunkHolder chunk, long now) { if (!chunk.wasAccessibleSinceLastSave() || !chunk.isReadyForSaving()) { return false; } ChunkAccess chunkAccess = chunk.getLatestChunk(); if (chunkAccess instanceof ImposterProtoChunk || chunkAccess instanceof LevelChunk) { if (!chunkAccess.isUnsaved()) { return false; } long chunkPos = chunkAccess.getPos().toLong(); long nextSaveTime = this.nextChunkSaveTime.getOrDefault(chunkPos, -1L); if (now < nextSaveTime) { return false; } boolean saved = this.save(chunkAccess); chunk.refreshAccessibility(); if (saved) { this.nextChunkSaveTime.put(chunkPos, now + 10000L); } return saved; } return false; } private boolean save(ChunkAccess chunk) { this.poiManager.flush(chunk.getPos()); if (!chunk.tryMarkSaved()) { return false; } ChunkPos pos = chunk.getPos(); try { ChunkStatus status = chunk.getPersistedStatus(); if (status.getChunkType() != ChunkType.LEVELCHUNK) { if (this.isExistingChunkFull(pos)) { return false; } if (status == ChunkStatus.EMPTY && chunk.getAllStarts().values().stream().noneMatch(StructureStart::isValid)) { return false; } } Profiler.get().incrementCounter("chunkSave"); this.activeChunkWrites.incrementAndGet(); SerializableChunkData data = SerializableChunkData.copyOf(this.level, chunk); CompletableFuture encodedData = CompletableFuture.supplyAsync(data::write, Util.backgroundExecutor()); this.write(pos, encodedData::join).handle((ignored, throwable) -> { if (throwable != null) { this.level.getServer().reportChunkSaveFailure((Throwable)throwable, this.storageInfo(), pos); } this.activeChunkWrites.decrementAndGet(); return null; }); this.markPosition(pos, status.getChunkType()); return true; } catch (Exception e) { this.level.getServer().reportChunkSaveFailure(e, this.storageInfo(), pos); return false; } } private boolean isExistingChunkFull(ChunkPos pos) { CompoundTag currentTag; byte cachedChunkType = this.chunkTypeCache.get(pos.toLong()); if (cachedChunkType != 0) { return cachedChunkType == 1; } try { currentTag = this.readChunk(pos).join().orElse(null); if (currentTag == null) { this.markPositionReplaceable(pos); return false; } } catch (Exception e) { LOGGER.error("Failed to read chunk {}", (Object)pos, (Object)e); this.markPositionReplaceable(pos); return false; } ChunkType chunkType = SerializableChunkData.getChunkStatusFromTag(currentTag).getChunkType(); return this.markPosition(pos, chunkType) == 1; } protected void setServerViewDistance(int newViewDistance) { int actualNewDistance = Mth.clamp(newViewDistance, 2, 32); if (actualNewDistance != this.serverViewDistance) { this.serverViewDistance = actualNewDistance; this.distanceManager.updatePlayerTickets(this.serverViewDistance); for (ServerPlayer player : this.playerMap.getAllPlayers()) { this.updateChunkTracking(player); } } } private int getPlayerViewDistance(ServerPlayer player) { return Mth.clamp(player.requestedViewDistance(), 2, this.serverViewDistance); } private void markChunkPendingToSend(ServerPlayer player, ChunkPos pos) { LevelChunk chunk = this.getChunkToSend(pos.toLong()); if (chunk != null) { ChunkMap.markChunkPendingToSend(player, chunk); } } private static void markChunkPendingToSend(ServerPlayer player, LevelChunk chunk) { player.connection.chunkSender.markChunkPendingToSend(chunk); } private static void dropChunk(ServerPlayer player, ChunkPos pos) { player.connection.chunkSender.dropChunk(player, pos); } public @Nullable LevelChunk getChunkToSend(long key) { ChunkHolder chunkHolder = this.getVisibleChunkIfPresent(key); if (chunkHolder == null) { return null; } return chunkHolder.getChunkToSend(); } public int size() { return this.visibleChunkMap.size(); } public net.minecraft.server.level.DistanceManager getDistanceManager() { return this.distanceManager; } void dumpChunks(Writer output) 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(output); for (Long2ObjectMap.Entry entry : this.visibleChunkMap.long2ObjectEntrySet()) { long posKey = entry.getLongKey(); ChunkPos pos = new ChunkPos(posKey); ChunkHolder holder = (ChunkHolder)entry.getValue(); Optional chunk = Optional.ofNullable(holder.getLatestChunk()); Optional fullChunk = chunk.flatMap(chunkAccess -> chunkAccess instanceof LevelChunk ? Optional.of((LevelChunk)chunkAccess) : Optional.empty()); csvOutput.writeRow(pos.x, pos.z, holder.getTicketLevel(), chunk.isPresent(), chunk.map(ChunkAccess::getPersistedStatus).orElse(null), fullChunk.map(LevelChunk::getFullStatus).orElse(null), ChunkMap.printFuture(holder.getFullChunkFuture()), ChunkMap.printFuture(holder.getTickingChunkFuture()), ChunkMap.printFuture(holder.getEntityTickingChunkFuture()), this.ticketStorage.getTicketDebugString(posKey, false), this.anyPlayerCloseEnoughForSpawning(pos), fullChunk.map(c -> c.getBlockEntities().size()).orElse(0), this.ticketStorage.getTicketDebugString(posKey, true), this.distanceManager.getChunkLevel(posKey, true), fullChunk.map(levelChunk -> levelChunk.getBlockTicks().count()).orElse(0), fullChunk.map(levelChunk -> levelChunk.getFluidTicks().count()).orElse(0)); } } private static String printFuture(CompletableFuture> future) { try { ChunkResult result = future.getNow(null); if (result != null) { return result.isSuccess() ? "done" : "unloaded"; } return "not completed"; } catch (CompletionException e) { return "failed " + e.getCause().getMessage(); } catch (CancellationException e) { return "cancelled"; } } private CompletableFuture> readChunk(ChunkPos pos) { return this.read(pos).thenApplyAsync(chunkTag -> chunkTag.map(this::upgradeChunkTag), Util.backgroundExecutor().forName("upgradeChunk")); } private CompoundTag upgradeChunkTag(CompoundTag tag) { return this.upgradeChunkTag(tag, -1, ChunkMap.getChunkDataFixContextTag(this.level.dimension(), this.generator().getTypeNameForDataFixer())); } public static CompoundTag getChunkDataFixContextTag(ResourceKey dimension, Optional>> generator) { CompoundTag contextTag = new CompoundTag(); contextTag.putString("dimension", dimension.identifier().toString()); generator.ifPresent(k -> contextTag.putString("generator", k.identifier().toString())); return contextTag; } void collectSpawningChunks(List output) { LongIterator spawnCandidateChunks = this.distanceManager.getSpawnCandidateChunks(); while (spawnCandidateChunks.hasNext()) { LevelChunk chunk; ChunkHolder holder = (ChunkHolder)this.visibleChunkMap.get(spawnCandidateChunks.nextLong()); if (holder == null || (chunk = holder.getTickingChunk()) == null || !this.anyPlayerCloseEnoughForSpawningInternal(holder.getPos())) continue; output.add(chunk); } } void forEachBlockTickingChunk(Consumer tickingChunkConsumer) { this.distanceManager.forEachEntityTickingChunk(chunkPos -> { ChunkHolder holder = (ChunkHolder)this.visibleChunkMap.get(chunkPos); if (holder == null) { return; } LevelChunk chunk = holder.getTickingChunk(); if (chunk == null) { return; } tickingChunkConsumer.accept(chunk); }); } boolean anyPlayerCloseEnoughForSpawning(ChunkPos pos) { TriState triState = this.distanceManager.hasPlayersNearby(pos.toLong()); if (triState == TriState.DEFAULT) { return this.anyPlayerCloseEnoughForSpawningInternal(pos); } return triState.toBoolean(true); } boolean anyPlayerCloseEnoughTo(BlockPos pos, int maxDistance) { Vec3 target = new Vec3(pos); for (ServerPlayer player : this.playerMap.getAllPlayers()) { if (!this.playerIsCloseEnoughTo(player, target, maxDistance)) continue; return true; } return false; } private boolean anyPlayerCloseEnoughForSpawningInternal(ChunkPos pos) { for (ServerPlayer player : this.playerMap.getAllPlayers()) { if (!this.playerIsCloseEnoughForSpawning(player, pos)) continue; return true; } return false; } public List getPlayersCloseForSpawning(ChunkPos pos) { long key = pos.toLong(); if (!this.distanceManager.hasPlayersNearby(key).toBoolean(true)) { return List.of(); } ImmutableList.Builder builder = ImmutableList.builder(); for (ServerPlayer player : this.playerMap.getAllPlayers()) { if (!this.playerIsCloseEnoughForSpawning(player, pos)) continue; builder.add((Object)player); } return builder.build(); } private boolean playerIsCloseEnoughForSpawning(ServerPlayer player, ChunkPos pos) { if (player.isSpectator()) { return false; } double distanceToChunk = ChunkMap.euclideanDistanceSquared(pos, player.position()); return distanceToChunk < 16384.0; } private boolean playerIsCloseEnoughTo(ServerPlayer player, Vec3 pos, int maxDistance) { if (player.isSpectator()) { return false; } double distanceToPos = player.position().distanceTo(pos); return distanceToPos < (double)maxDistance; } private static double euclideanDistanceSquared(ChunkPos chunkPos, Vec3 pos) { double xPos = SectionPos.sectionToBlockCoord(chunkPos.x, 8); double zPos = SectionPos.sectionToBlockCoord(chunkPos.z, 8); double xd = xPos - pos.x; double zd = zPos - pos.z; return xd * xd + zd * zd; } private boolean skipPlayer(ServerPlayer player) { return player.isSpectator() && this.level.getGameRules().get(GameRules.SPECTATORS_GENERATE_CHUNKS) == false; } void updatePlayerStatus(ServerPlayer player, boolean added) { boolean ignored = this.skipPlayer(player); boolean wasIgnored = this.playerMap.ignoredOrUnknown(player); if (added) { this.playerMap.addPlayer(player, ignored); this.updatePlayerPos(player); if (!ignored) { this.distanceManager.addPlayer(SectionPos.of(player), player); } player.setChunkTrackingView(ChunkTrackingView.EMPTY); this.updateChunkTracking(player); } else { SectionPos lastPos = player.getLastSectionPos(); this.playerMap.removePlayer(player); if (!wasIgnored) { this.distanceManager.removePlayer(lastPos, player); } this.applyChunkTrackingView(player, ChunkTrackingView.EMPTY); } } private void updatePlayerPos(ServerPlayer player) { SectionPos pos = SectionPos.of(player); player.setLastSectionPos(pos); } public void move(ServerPlayer player) { boolean positionChanged; for (TrackedEntity trackedEntity : this.entityMap.values()) { if (trackedEntity.entity == player) { trackedEntity.updatePlayers(this.level.players()); continue; } trackedEntity.updatePlayer(player); } SectionPos oldSection = player.getLastSectionPos(); SectionPos newSection = SectionPos.of(player); boolean wasIgnored = this.playerMap.ignored(player); boolean ignored = this.skipPlayer(player); boolean bl = positionChanged = oldSection.asLong() != newSection.asLong(); if (positionChanged || wasIgnored != ignored) { this.updatePlayerPos(player); if (!wasIgnored) { this.distanceManager.removePlayer(oldSection, player); } if (!ignored) { this.distanceManager.addPlayer(newSection, player); } if (!wasIgnored && ignored) { this.playerMap.ignorePlayer(player); } if (wasIgnored && !ignored) { this.playerMap.unIgnorePlayer(player); } this.updateChunkTracking(player); } } private void updateChunkTracking(ServerPlayer player) { ChunkTrackingView.Positioned view; ChunkPos chunkPos = player.chunkPosition(); int playerViewDistance = this.getPlayerViewDistance(player); ChunkTrackingView chunkTrackingView = player.getChunkTrackingView(); if (chunkTrackingView instanceof ChunkTrackingView.Positioned && (view = (ChunkTrackingView.Positioned)chunkTrackingView).center().equals(chunkPos) && view.viewDistance() == playerViewDistance) { return; } this.applyChunkTrackingView(player, ChunkTrackingView.of(chunkPos, playerViewDistance)); } private void applyChunkTrackingView(ServerPlayer player, ChunkTrackingView next) { if (player.level() != this.level) { return; } ChunkTrackingView previous = player.getChunkTrackingView(); if (next instanceof ChunkTrackingView.Positioned) { ChunkTrackingView.Positioned from; ChunkTrackingView.Positioned to = (ChunkTrackingView.Positioned)next; if (!(previous instanceof ChunkTrackingView.Positioned) || !(from = (ChunkTrackingView.Positioned)previous).center().equals(to.center())) { player.connection.send(new ClientboundSetChunkCacheCenterPacket(to.center().x, to.center().z)); } } ChunkTrackingView.difference(previous, next, pos -> this.markChunkPendingToSend(player, (ChunkPos)pos), pos -> ChunkMap.dropChunk(player, pos)); player.setChunkTrackingView(next); } @Override public List getPlayers(ChunkPos pos, boolean borderOnly) { Set allPlayers = this.playerMap.getAllPlayers(); ImmutableList.Builder result = ImmutableList.builder(); for (ServerPlayer player : allPlayers) { if ((!borderOnly || !this.isChunkOnTrackedBorder(player, pos.x, pos.z)) && (borderOnly || !this.isChunkTracked(player, pos.x, pos.z))) continue; result.add((Object)player); } return result.build(); } protected void addEntity(Entity entity) { if (entity instanceof EnderDragonPart) { return; } EntityType type = entity.getType(); int range = type.clientTrackingRange() * 16; if (range == 0) { return; } int updateInterval = type.updateInterval(); if (this.entityMap.containsKey(entity.getId())) { throw Util.pauseInIde(new IllegalStateException("Entity is already tracked!")); } TrackedEntity trackedEntity = new TrackedEntity(entity, range, updateInterval, type.trackDeltas()); this.entityMap.put(entity.getId(), (Object)trackedEntity); trackedEntity.updatePlayers(this.level.players()); if (entity instanceof ServerPlayer) { ServerPlayer player = (ServerPlayer)entity; this.updatePlayerStatus(player, true); for (TrackedEntity e : this.entityMap.values()) { if (e.entity == player) continue; e.updatePlayer(player); } } } protected void removeEntity(Entity entity) { TrackedEntity trackedEntity; if (entity instanceof ServerPlayer) { ServerPlayer player = (ServerPlayer)entity; this.updatePlayerStatus(player, false); for (TrackedEntity trackedEntity2 : this.entityMap.values()) { trackedEntity2.removePlayer(player); } } if ((trackedEntity = (TrackedEntity)this.entityMap.remove(entity.getId())) != null) { trackedEntity.broadcastRemoved(); } } protected void tick() { for (ServerPlayer player : this.playerMap.getAllPlayers()) { this.updateChunkTracking(player); } ArrayList movedPlayers = Lists.newArrayList(); List players = this.level.players(); for (TrackedEntity trackedEntity : this.entityMap.values()) { boolean sectionPosChanged; SectionPos oldPos = trackedEntity.lastSectionPos; SectionPos newPos = SectionPos.of(trackedEntity.entity); boolean bl = sectionPosChanged = !Objects.equals(oldPos, newPos); if (sectionPosChanged) { trackedEntity.updatePlayers(players); Entity entity = trackedEntity.entity; if (entity instanceof ServerPlayer) { movedPlayers.add((ServerPlayer)entity); } trackedEntity.lastSectionPos = newPos; } if (!sectionPosChanged && !trackedEntity.entity.needsSync && !this.distanceManager.inEntityTickingRange(newPos.chunk().toLong())) continue; trackedEntity.serverEntity.sendChanges(); } if (!movedPlayers.isEmpty()) { for (TrackedEntity trackedEntity : this.entityMap.values()) { trackedEntity.updatePlayers(movedPlayers); } } } public void sendToTrackingPlayers(Entity entity, Packet packet) { TrackedEntity trackedEntity = (TrackedEntity)this.entityMap.get(entity.getId()); if (trackedEntity != null) { trackedEntity.sendToTrackingPlayers(packet); } } public void sendToTrackingPlayersFiltered(Entity entity, Packet packet, Predicate targetPredicate) { TrackedEntity trackedEntity = (TrackedEntity)this.entityMap.get(entity.getId()); if (trackedEntity != null) { trackedEntity.sendToTrackingPlayersFiltered(packet, targetPredicate); } } protected void sendToTrackingPlayersAndSelf(Entity entity, Packet packet) { TrackedEntity trackedEntity = (TrackedEntity)this.entityMap.get(entity.getId()); if (trackedEntity != null) { trackedEntity.sendToTrackingPlayersAndSelf(packet); } } public boolean isTrackedByAnyPlayer(Entity entity) { TrackedEntity trackedEntity = (TrackedEntity)this.entityMap.get(entity.getId()); if (trackedEntity != null) { return !trackedEntity.seenBy.isEmpty(); } return false; } public void forEachEntityTrackedBy(ServerPlayer player, Consumer consumer) { for (TrackedEntity entity : this.entityMap.values()) { if (!entity.seenBy.contains(player.connection)) continue; consumer.accept(entity.entity); } } public void resendBiomesForChunks(List chunks) { HashMap chunksForPlayers = new HashMap(); for (ChunkAccess chunkAccess : chunks) { LevelChunk levelChunk; ChunkPos pos = chunkAccess.getPos(); LevelChunk chunk = chunkAccess instanceof LevelChunk ? (levelChunk = (LevelChunk)chunkAccess) : this.level.getChunk(pos.x, pos.z); for (ServerPlayer player2 : this.getPlayers(pos, false)) { chunksForPlayers.computeIfAbsent(player2, p -> new ArrayList()).add(chunk); } } chunksForPlayers.forEach((player, chunkList) -> player.connection.send(ClientboundChunksBiomesPacket.forChunks(chunkList))); } protected PoiManager getPoiManager() { return this.poiManager; } public String getStorageName() { return this.storageName; } void onFullChunkStatusChange(ChunkPos pos, FullChunkStatus status) { this.chunkStatusListener.onChunkStatusChange(pos, status); } public void waitForLightBeforeSending(ChunkPos centerChunk, int chunkRadius) { int affectedLightChunkRadius = chunkRadius + 1; ChunkPos.rangeClosed(centerChunk, affectedLightChunkRadius).forEach(chunkPos -> { ChunkHolder chunkHolder = this.getVisibleChunkIfPresent(chunkPos.toLong()); if (chunkHolder != null) { chunkHolder.addSendDependency(this.lightEngine.waitForPendingTasks(chunkPos.x, chunkPos.z)); } }); } public void forEachReadyToSendChunk(Consumer consumer) { for (ChunkHolder chunkHolder : this.visibleChunkMap.values()) { LevelChunk chunk = chunkHolder.getChunkToSend(); if (chunk == null) continue; consumer.accept(chunk); } } private class DistanceManager extends net.minecraft.server.level.DistanceManager { protected DistanceManager(TicketStorage ticketStorage, Executor executor, Executor mainThreadExecutor) { super(ticketStorage, executor, mainThreadExecutor); } @Override protected boolean isChunkToRemove(long node) { return ChunkMap.this.toDrop.contains(node); } @Override protected @Nullable ChunkHolder getChunk(long node) { return ChunkMap.this.getUpdatingChunkIfPresent(node); } @Override protected @Nullable ChunkHolder updateChunkScheduling(long node, int level, @Nullable ChunkHolder chunk, int oldLevel) { return ChunkMap.this.updateChunkScheduling(node, level, chunk, oldLevel); } } private class TrackedEntity implements ServerEntity.Synchronizer { private final ServerEntity serverEntity; private final Entity entity; private final int range; private SectionPos lastSectionPos; private final Set seenBy = Sets.newIdentityHashSet(); public TrackedEntity(Entity entity, int range, int updateInterval, boolean trackDelta) { this.serverEntity = new ServerEntity(ChunkMap.this.level, entity, updateInterval, trackDelta, this); this.entity = entity; this.range = range; this.lastSectionPos = SectionPos.of(entity); } public boolean equals(Object obj) { if (obj instanceof TrackedEntity) { return ((TrackedEntity)obj).entity.getId() == this.entity.getId(); } return false; } public int hashCode() { return this.entity.getId(); } @Override public void sendToTrackingPlayers(Packet packet) { for (ServerPlayerConnection connection : this.seenBy) { connection.send(packet); } } @Override public void sendToTrackingPlayersAndSelf(Packet packet) { this.sendToTrackingPlayers(packet); Entity entity = this.entity; if (entity instanceof ServerPlayer) { ServerPlayer player = (ServerPlayer)entity; player.connection.send(packet); } } @Override public void sendToTrackingPlayersFiltered(Packet packet, Predicate targetPredicate) { for (ServerPlayerConnection connection : this.seenBy) { if (!targetPredicate.test(connection.getPlayer())) continue; connection.send(packet); } } public void broadcastRemoved() { for (ServerPlayerConnection connection : this.seenBy) { this.serverEntity.removePairing(connection.getPlayer()); } } public void removePlayer(ServerPlayer player) { if (this.seenBy.remove(player.connection)) { this.serverEntity.removePairing(player); if (this.seenBy.isEmpty()) { ChunkMap.this.level.debugSynchronizers().dropEntity(this.entity); } } } public void updatePlayer(ServerPlayer player) { boolean visibleToPlayer; if (player == this.entity) { return; } Vec3 deltaToPlayer = player.position().subtract(this.entity.position()); int playerViewDistance = ChunkMap.this.getPlayerViewDistance(player); double distanceSquared = deltaToPlayer.x * deltaToPlayer.x + deltaToPlayer.z * deltaToPlayer.z; double visibleRange = Math.min(this.getEffectiveRange(), playerViewDistance * 16); double rangeSquared = visibleRange * visibleRange; boolean bl = visibleToPlayer = distanceSquared <= rangeSquared && this.entity.broadcastToPlayer(player) && ChunkMap.this.isChunkTracked(player, this.entity.chunkPosition().x, this.entity.chunkPosition().z); if (visibleToPlayer) { if (this.seenBy.add(player.connection)) { this.serverEntity.addPairing(player); if (this.seenBy.size() == 1) { ChunkMap.this.level.debugSynchronizers().registerEntity(this.entity); } ChunkMap.this.level.debugSynchronizers().startTrackingEntity(player, this.entity); } } else { this.removePlayer(player); } } private int scaledRange(int range) { return ChunkMap.this.level.getServer().getScaledTrackingDistance(range); } private int getEffectiveRange() { int effectiveRange = this.range; for (Entity passenger : this.entity.getIndirectPassengers()) { int passengerRange = passenger.getType().clientTrackingRange() * 16; if (passengerRange <= effectiveRange) continue; effectiveRange = passengerRange; } return this.scaledRange(effectiveRange); } public void updatePlayers(List players) { for (ServerPlayer player : players) { this.updatePlayer(player); } } } }