ChunkMap.java

net.minecraft.server.level.ChunkMap

信息

  • 全限定名:net.minecraft.server.level.ChunkMap
  • 类型:public class
  • 包:net.minecraft.server.level
  • 源码路径:src/main/java/net/minecraft/server/level/ChunkMap.java
  • 起始行号:L111
  • 继承:SimpleRegionStorage
  • 实现:ChunkHolder.PlayerProvider, GeneratingChunkMap
  • 职责:

    TODO

字段/常量

  • UNLOADED_CHUNK_LIST_RESULT

    • 类型: ChunkResult<List<ChunkAccess>>
    • 修饰符: private static final
    • 源码定位: L112
    • 说明:

      TODO

  • UNLOADED_CHUNK_LIST_FUTURE

    • 类型: CompletableFuture<ChunkResult<List<ChunkAccess>>>
    • 修饰符: private static final
    • 源码定位: L113
    • 说明:

      TODO

  • CHUNK_TYPE_REPLACEABLE

    • 类型: byte
    • 修饰符: private static final
    • 源码定位: L116
    • 说明:

      TODO

  • CHUNK_TYPE_UNKNOWN

    • 类型: byte
    • 修饰符: private static final
    • 源码定位: L117
    • 说明:

      TODO

  • CHUNK_TYPE_FULL

    • 类型: byte
    • 修饰符: private static final
    • 源码定位: L118
    • 说明:

      TODO

  • LOGGER

    • 类型: Logger
    • 修饰符: private static final
    • 源码定位: L119
    • 说明:

      TODO

  • CHUNK_SAVED_PER_TICK

    • 类型: int
    • 修饰符: private static final
    • 源码定位: L120
    • 说明:

      TODO

  • CHUNK_SAVED_EAGERLY_PER_TICK

    • 类型: int
    • 修饰符: private static final
    • 源码定位: L121
    • 说明:

      TODO

  • EAGER_CHUNK_SAVE_COOLDOWN_IN_MILLIS

    • 类型: int
    • 修饰符: private static final
    • 源码定位: L122
    • 说明:

      TODO

  • MAX_ACTIVE_CHUNK_WRITES

    • 类型: int
    • 修饰符: private static final
    • 源码定位: L123
    • 说明:

      TODO

  • MIN_VIEW_DISTANCE

    • 类型: int
    • 修饰符: public static final
    • 源码定位: L124
    • 说明:

      TODO

  • MAX_VIEW_DISTANCE

    • 类型: int
    • 修饰符: public static final
    • 源码定位: L125
    • 说明:

      TODO

  • FORCED_TICKET_LEVEL

    • 类型: int
    • 修饰符: public static final
    • 源码定位: L126
    • 说明:

      TODO

  • updatingChunkMap

    • 类型: Long2ObjectLinkedOpenHashMap<ChunkHolder>
    • 修饰符: private final
    • 源码定位: L127
    • 说明:

      TODO

  • visibleChunkMap

    • 类型: Long2ObjectLinkedOpenHashMap<ChunkHolder>
    • 修饰符: private volatile
    • 源码定位: L128
    • 说明:

      TODO

  • pendingUnloads

    • 类型: Long2ObjectLinkedOpenHashMap<ChunkHolder>
    • 修饰符: private final
    • 源码定位: L129
    • 说明:

      TODO

  • pendingGenerationTasks

    • 类型: List<ChunkGenerationTask>
    • 修饰符: private final
    • 源码定位: L130
    • 说明:

      TODO

  • level

    • 类型: ServerLevel
    • 修饰符: private final
    • 源码定位: L131
    • 说明:

      TODO

  • lightEngine

    • 类型: ThreadedLevelLightEngine
    • 修饰符: private final
    • 源码定位: L132
    • 说明:

      TODO

  • mainThreadExecutor

    • 类型: BlockableEventLoop<Runnable>
    • 修饰符: private final
    • 源码定位: L133
    • 说明:

      TODO

  • randomState

    • 类型: RandomState
    • 修饰符: private final
    • 源码定位: L134
    • 说明:

      TODO

  • chunkGeneratorState

    • 类型: ChunkGeneratorStructureState
    • 修饰符: private final
    • 源码定位: L135
    • 说明:

      TODO

  • ticketStorage

    • 类型: TicketStorage
    • 修饰符: private final
    • 源码定位: L136
    • 说明:

      TODO

  • poiManager

    • 类型: PoiManager
    • 修饰符: private final
    • 源码定位: L137
    • 说明:

      TODO

  • toDrop

    • 类型: LongSet
    • 修饰符: private final
    • 源码定位: L138
    • 说明:

      TODO

  • modified

    • 类型: boolean
    • 修饰符: private
    • 源码定位: L139
    • 说明:

      TODO

  • worldgenTaskDispatcher

    • 类型: ChunkTaskDispatcher
    • 修饰符: private final
    • 源码定位: L140
    • 说明:

      TODO

  • lightTaskDispatcher

    • 类型: ChunkTaskDispatcher
    • 修饰符: private final
    • 源码定位: L141
    • 说明:

      TODO

  • chunkStatusListener

    • 类型: ChunkStatusUpdateListener
    • 修饰符: private final
    • 源码定位: L142
    • 说明:

      TODO

  • distanceManager

    • 类型: ChunkMap.DistanceManager
    • 修饰符: private final
    • 源码定位: L143
    • 说明:

      TODO

  • storageName

    • 类型: String
    • 修饰符: private final
    • 源码定位: L144
    • 说明:

      TODO

  • playerMap

    • 类型: PlayerMap
    • 修饰符: private final
    • 源码定位: L145
    • 说明:

      TODO

  • entityMap

    • 类型: Int2ObjectMap<ChunkMap.TrackedEntity>
    • 修饰符: private final
    • 源码定位: L146
    • 说明:

      TODO

  • chunkTypeCache

    • 类型: Long2ByteMap
    • 修饰符: private final
    • 源码定位: L147
    • 说明:

      TODO

  • nextChunkSaveTime

    • 类型: Long2LongMap
    • 修饰符: private final
    • 源码定位: L148
    • 说明:

      TODO

  • chunksToEagerlySave

    • 类型: LongSet
    • 修饰符: private final
    • 源码定位: L149
    • 说明:

      TODO

  • unloadQueue

    • 类型: Queue<Runnable>
    • 修饰符: private final
    • 源码定位: L150
    • 说明:

      TODO

  • activeChunkWrites

    • 类型: AtomicInteger
    • 修饰符: private final
    • 源码定位: L151
    • 说明:

      TODO

  • serverViewDistance

    • 类型: int
    • 修饰符: private
    • 源码定位: L152
    • 说明:

      TODO

  • worldGenContext

    • 类型: WorldGenContext
    • 修饰符: private final
    • 源码定位: L153
    • 说明:

      TODO

内部类/嵌套类型

  • net.minecraft.server.level.ChunkMap.DistanceManager

    • 类型: class
    • 修饰符: private
    • 源码定位: L1294
    • 说明:

      TODO

  • net.minecraft.server.level.ChunkMap.TrackedEntity

    • 类型: class
    • 修饰符: private
    • 源码定位: L1316
    • 说明:

      TODO

构造器

public ChunkMap(ServerLevel level, LevelStorageSource.LevelStorageAccess levelStorage, DataFixer dataFixer, StructureTemplateManager structureManager, Executor executor, BlockableEventLoop<Runnable> mainThreadExecutor, LightChunkGetter chunkGetter, ChunkGenerator generator, ChunkStatusUpdateListener chunkStatusListener, Supplier<SavedDataStorage> overworldDataStorage, TicketStorage ticketStorage, int serverViewDistance, boolean syncWrites) @ L155

  • 构造器名:ChunkMap
  • 源码定位:L155
  • 修饰符:public

参数:

  • level: ServerLevel
  • levelStorage: LevelStorageSource.LevelStorageAccess
  • dataFixer: DataFixer
  • structureManager: StructureTemplateManager
  • executor: Executor
  • mainThreadExecutor: BlockableEventLoop
  • chunkGetter: LightChunkGetter
  • generator: ChunkGenerator
  • chunkStatusListener: ChunkStatusUpdateListener
  • overworldDataStorage: Supplier
  • ticketStorage: TicketStorage
  • serverViewDistance: int
  • syncWrites: boolean

说明:

TODO

方法

下面的方法块按源码顺序生成。

private void setChunkUnsaved(ChunkPos chunkPos) @ L211

  • 方法名:setChunkUnsaved
  • 源码定位:L211
  • 返回类型:void
  • 修饰符:private

参数:

  • chunkPos: ChunkPos

说明:

TODO

protected ChunkGenerator generator() @ L215

  • 方法名:generator
  • 源码定位:L215
  • 返回类型:ChunkGenerator
  • 修饰符:protected

参数:

说明:

TODO

protected ChunkGeneratorStructureState generatorState() @ L219

  • 方法名:generatorState
  • 源码定位:L219
  • 返回类型:ChunkGeneratorStructureState
  • 修饰符:protected

参数:

说明:

TODO

protected RandomState randomState() @ L223

  • 方法名:randomState
  • 源码定位:L223
  • 返回类型:RandomState
  • 修饰符:protected

参数:

说明:

TODO

public boolean isChunkTracked(ServerPlayer player, int chunkX, int chunkZ) @ L227

  • 方法名:isChunkTracked
  • 源码定位:L227
  • 返回类型:boolean
  • 修饰符:public

参数:

  • player: ServerPlayer
  • chunkX: int
  • chunkZ: int

说明:

TODO

private boolean isChunkOnTrackedBorder(ServerPlayer player, int chunkX, int chunkZ) @ L231

  • 方法名:isChunkOnTrackedBorder
  • 源码定位:L231
  • 返回类型:boolean
  • 修饰符:private

参数:

  • player: ServerPlayer
  • chunkX: int
  • chunkZ: int

说明:

TODO

protected ThreadedLevelLightEngine getLightEngine() @ L247

  • 方法名:getLightEngine
  • 源码定位:L247
  • 返回类型:ThreadedLevelLightEngine
  • 修饰符:protected

参数:

说明:

TODO

public ChunkHolder getUpdatingChunkIfPresent(long key) @ L251

  • 方法名:getUpdatingChunkIfPresent
  • 源码定位:L251
  • 返回类型:ChunkHolder
  • 修饰符:public

参数:

  • key: long

说明:

TODO

protected ChunkHolder getVisibleChunkIfPresent(long key) @ L255

  • 方法名:getVisibleChunkIfPresent
  • 源码定位:L255
  • 返回类型:ChunkHolder
  • 修饰符:protected

参数:

  • key: long

说明:

TODO

public ChunkStatus getLatestStatus(long key) @ L259

  • 方法名:getLatestStatus
  • 源码定位:L259
  • 返回类型:ChunkStatus
  • 修饰符:public

参数:

  • key: long

说明:

TODO

protected IntSupplier getChunkQueueLevel(long pos) @ L264

  • 方法名:getChunkQueueLevel
  • 源码定位:L264
  • 返回类型:IntSupplier
  • 修饰符:protected

参数:

  • pos: long

说明:

TODO

public String getChunkDebugData(ChunkPos pos) @ L273

  • 方法名:getChunkDebugData
  • 源码定位:L273
  • 返回类型:String
  • 修饰符:public

参数:

  • pos: ChunkPos

说明:

TODO

CompletableFuture<ChunkResult<List<ChunkAccess>>> getChunkRangeFuture(ChunkHolder centerChunk, int range, IntFunction<ChunkStatus> distanceToStatus) @ L295

  • 方法名:getChunkRangeFuture
  • 源码定位:L295
  • 返回类型:CompletableFuture<ChunkResult<List>>
  • 修饰符:package-private

参数:

  • centerChunk: ChunkHolder
  • range: int
  • distanceToStatus: IntFunction

说明:

TODO

public ReportedException debugFuturesAndCreateReportedException(IllegalStateException exception, String details) @ L339

  • 方法名:debugFuturesAndCreateReportedException
  • 源码定位:L339
  • 返回类型:ReportedException
  • 修饰符:public

参数:

  • exception: IllegalStateException
  • details: String

说明:

TODO

public CompletableFuture<ChunkResult<LevelChunk>> prepareEntityTickingChunk(ChunkHolder chunk) @ L359

  • 方法名:prepareEntityTickingChunk
  • 源码定位:L359
  • 返回类型:CompletableFuture<ChunkResult>
  • 修饰符:public

参数:

  • chunk: ChunkHolder

说明:

TODO

private ChunkHolder updateChunkScheduling(long node, int level, ChunkHolder chunk, int oldLevel) @ L364

  • 方法名:updateChunkScheduling
  • 源码定位:L364
  • 返回类型:ChunkHolder
  • 修饰符:private

参数:

  • node: long
  • level: int
  • chunk: ChunkHolder
  • oldLevel: int

说明:

TODO

private void onLevelChange(ChunkPos pos, IntSupplier oldLevel, int newLevel, IntConsumer setQueueLevel) @ L396

  • 方法名:onLevelChange
  • 源码定位:L396
  • 返回类型:void
  • 修饰符:private

参数:

  • pos: ChunkPos
  • oldLevel: IntSupplier
  • newLevel: int
  • setQueueLevel: IntConsumer

说明:

TODO

public void close() @ L401

  • 方法名:close
  • 源码定位:L401
  • 返回类型:void
  • 修饰符:public

参数:

说明:

TODO

protected void saveAllChunks(boolean flushStorage) @ L412

  • 方法名:saveAllChunks
  • 源码定位:L412
  • 返回类型:void
  • 修饰符:protected

参数:

  • flushStorage: boolean

说明:

TODO

protected void tick(BooleanSupplier haveTime) @ L447

  • 方法名:tick
  • 源码定位:L447
  • 返回类型:void
  • 修饰符:protected

参数:

  • haveTime: BooleanSupplier

说明:

TODO

public boolean hasWork() @ L459

  • 方法名:hasWork
  • 源码定位:L459
  • 返回类型:boolean
  • 修饰符:public

参数:

说明:

TODO

private void processUnloads(BooleanSupplier haveTime) @ L471

  • 方法名:processUnloads
  • 源码定位:L471
  • 返回类型:void
  • 修饰符:private

参数:

  • haveTime: BooleanSupplier

说明:

TODO

private void saveChunksEagerly(BooleanSupplier haveTime) @ L494

  • 方法名:saveChunksEagerly
  • 源码定位:L494
  • 返回类型:void
  • 修饰符:private

参数:

  • haveTime: BooleanSupplier

说明:

TODO

private void scheduleUnload(long pos, ChunkHolder chunkHolder) @ L512

  • 方法名:scheduleUnload
  • 源码定位:L512
  • 返回类型:void
  • 修饰符:private

参数:

  • pos: long
  • chunkHolder: ChunkHolder

说明:

TODO

protected boolean promoteChunkMap() @ L542

  • 方法名:promoteChunkMap
  • 源码定位:L542
  • 返回类型:boolean
  • 修饰符:protected

参数:

说明:

TODO

private CompletableFuture<ChunkAccess> scheduleChunkLoad(ChunkPos pos) @ L552

  • 方法名:scheduleChunkLoad
  • 源码定位:L552
  • 返回类型:CompletableFuture
  • 修饰符:private

参数:

  • pos: ChunkPos

说明:

TODO

private ChunkAccess handleChunkLoadFailure(Throwable throwable, ChunkPos pos) @ L578

  • 方法名:handleChunkLoadFailure
  • 源码定位:L578
  • 返回类型:ChunkAccess
  • 修饰符:private

参数:

  • throwable: Throwable
  • pos: ChunkPos

说明:

TODO

private ChunkAccess createEmptyChunk(ChunkPos pos) @ L598

  • 方法名:createEmptyChunk
  • 源码定位:L598
  • 返回类型:ChunkAccess
  • 修饰符:private

参数:

  • pos: ChunkPos

说明:

TODO

private void markPositionReplaceable(ChunkPos pos) @ L603

  • 方法名:markPositionReplaceable
  • 源码定位:L603
  • 返回类型:void
  • 修饰符:private

参数:

  • pos: ChunkPos

说明:

TODO

private byte markPosition(ChunkPos pos, ChunkType type) @ L607

  • 方法名:markPosition
  • 源码定位:L607
  • 返回类型:byte
  • 修饰符:private

参数:

  • pos: ChunkPos
  • type: ChunkType

说明:

TODO

public GenerationChunkHolder acquireGeneration(long chunkNode) @ L611

  • 方法名:acquireGeneration
  • 源码定位:L611
  • 返回类型:GenerationChunkHolder
  • 修饰符:public

参数:

  • chunkNode: long

说明:

TODO

public void releaseGeneration(GenerationChunkHolder chunkHolder) @ L618

  • 方法名:releaseGeneration
  • 源码定位:L618
  • 返回类型:void
  • 修饰符:public

参数:

  • chunkHolder: GenerationChunkHolder

说明:

TODO

public CompletableFuture<ChunkAccess> applyStep(GenerationChunkHolder chunkHolder, ChunkStep step, StaticCache2D<GenerationChunkHolder> cache) @ L623

  • 方法名:applyStep
  • 源码定位:L623
  • 返回类型:CompletableFuture
  • 修饰符:public

参数:

  • chunkHolder: GenerationChunkHolder
  • step: ChunkStep
  • cache: StaticCache2D

说明:

TODO

public ChunkGenerationTask scheduleGenerationTask(ChunkStatus targetStatus, ChunkPos pos) @ L650

  • 方法名:scheduleGenerationTask
  • 源码定位:L650
  • 返回类型:ChunkGenerationTask
  • 修饰符:public

参数:

  • targetStatus: ChunkStatus
  • pos: ChunkPos

说明:

TODO

private void runGenerationTask(ChunkGenerationTask task) @ L657

  • 方法名:runGenerationTask
  • 源码定位:L657
  • 返回类型:void
  • 修饰符:private

参数:

  • task: ChunkGenerationTask

说明:

TODO

public void runGenerationTasks() @ L667

  • 方法名:runGenerationTasks
  • 源码定位:L667
  • 返回类型:void
  • 修饰符:public

参数:

说明:

TODO

public CompletableFuture<ChunkResult<LevelChunk>> prepareTickingChunk(ChunkHolder chunk) @ L673

  • 方法名:prepareTickingChunk
  • 源码定位:L673
  • 返回类型:CompletableFuture<ChunkResult>
  • 修饰符:public

参数:

  • chunk: ChunkHolder

说明:

TODO

private void onChunkReadyToSend(ChunkHolder chunkHolder, LevelChunk chunk) @ L690

  • 方法名:onChunkReadyToSend
  • 源码定位:L690
  • 返回类型:void
  • 修饰符:private

参数:

  • chunkHolder: ChunkHolder
  • chunk: LevelChunk

说明:

TODO

public CompletableFuture<ChunkResult<LevelChunk>> prepareAccessibleChunk(ChunkHolder chunk) @ L703

  • 方法名:prepareAccessibleChunk
  • 源码定位:L703
  • 返回类型:CompletableFuture<ChunkResult>
  • 修饰符:public

参数:

  • chunk: ChunkHolder

说明:

TODO

Stream<ChunkHolder> allChunksWithAtLeastStatus(ChunkStatus status) @ L708

  • 方法名:allChunksWithAtLeastStatus
  • 源码定位:L708
  • 返回类型:Stream
  • 修饰符:package-private

参数:

  • status: ChunkStatus

说明:

TODO

private boolean saveChunkIfNeeded(ChunkHolder chunk, long now) @ L713

  • 方法名:saveChunkIfNeeded
  • 源码定位:L713
  • 返回类型:boolean
  • 修饰符:private

参数:

  • chunk: ChunkHolder
  • now: long

说明:

TODO

private boolean save(ChunkAccess chunk) @ L740

  • 方法名:save
  • 源码定位:L740
  • 返回类型:boolean
  • 修饰符:private

参数:

  • chunk: ChunkAccess

说明:

TODO

private boolean isExistingChunkFull(ChunkPos pos) @ L780

  • 方法名:isExistingChunkFull
  • 源码定位:L780
  • 返回类型:boolean
  • 修饰符:private

参数:

  • pos: ChunkPos

说明:

TODO

protected void setServerViewDistance(int newViewDistance) @ L803

  • 方法名:setServerViewDistance
  • 源码定位:L803
  • 返回类型:void
  • 修饰符:protected

参数:

  • newViewDistance: int

说明:

TODO

private int getPlayerViewDistance(ServerPlayer player) @ L815

  • 方法名:getPlayerViewDistance
  • 源码定位:L815
  • 返回类型:int
  • 修饰符:private

参数:

  • player: ServerPlayer

说明:

TODO

private void markChunkPendingToSend(ServerPlayer player, ChunkPos pos) @ L819

  • 方法名:markChunkPendingToSend
  • 源码定位:L819
  • 返回类型:void
  • 修饰符:private

参数:

  • player: ServerPlayer
  • pos: ChunkPos

说明:

TODO

private static void markChunkPendingToSend(ServerPlayer player, LevelChunk chunk) @ L826

  • 方法名:markChunkPendingToSend
  • 源码定位:L826
  • 返回类型:void
  • 修饰符:private static

参数:

  • player: ServerPlayer
  • chunk: LevelChunk

说明:

TODO

private static void dropChunk(ServerPlayer player, ChunkPos pos) @ L830

  • 方法名:dropChunk
  • 源码定位:L830
  • 返回类型:void
  • 修饰符:private static

参数:

  • player: ServerPlayer
  • pos: ChunkPos

说明:

TODO

public LevelChunk getChunkToSend(long key) @ L834

  • 方法名:getChunkToSend
  • 源码定位:L834
  • 返回类型:LevelChunk
  • 修饰符:public

参数:

  • key: long

说明:

TODO

public int size() @ L839

  • 方法名:size
  • 源码定位:L839
  • 返回类型:int
  • 修饰符:public

参数:

说明:

TODO

public net.minecraft.server.level.DistanceManager getDistanceManager() @ L843

  • 方法名:getDistanceManager
  • 源码定位:L843
  • 返回类型:net.minecraft.server.level.DistanceManager
  • 修饰符:public

参数:

说明:

TODO

void dumpChunks(Writer output) @ L847

  • 方法名:dumpChunks
  • 源码定位:L847
  • 返回类型:void
  • 修饰符:package-private

参数:

  • output: Writer

说明:

TODO

private static String printFuture(CompletableFuture<ChunkResult<LevelChunk>> future) @ L896

  • 方法名:printFuture
  • 源码定位:L896
  • 返回类型:String
  • 修饰符:private static

参数:

  • future: CompletableFuture<ChunkResult>

说明:

TODO

private CompletableFuture<Optional<CompoundTag>> readChunk(ChunkPos pos) @ L911

  • 方法名:readChunk
  • 源码定位:L911
  • 返回类型:CompletableFuture<Optional>
  • 修饰符:private

参数:

  • pos: ChunkPos

说明:

TODO

private CompoundTag upgradeChunkTag(CompoundTag tag) @ L915

  • 方法名:upgradeChunkTag
  • 源码定位:L915
  • 返回类型:CompoundTag
  • 修饰符:private

参数:

  • tag: CompoundTag

说明:

TODO

public static CompoundTag getChunkDataFixContextTag(ResourceKey<Level> dimension, Optional<Identifier> generatorIdentifier) @ L924

  • 方法名:getChunkDataFixContextTag
  • 源码定位:L924
  • 返回类型:CompoundTag
  • 修饰符:public static

参数:

  • dimension: ResourceKey
  • generatorIdentifier: Optional

说明:

TODO

void collectSpawningChunks(List<LevelChunk> output) @ L931

  • 方法名:collectSpawningChunks
  • 源码定位:L931
  • 返回类型:void
  • 修饰符:package-private

参数:

  • output: List

说明:

TODO

void forEachBlockTickingChunk(Consumer<LevelChunk> tickingChunkConsumer) @ L945

  • 方法名:forEachBlockTickingChunk
  • 源码定位:L945
  • 返回类型:void
  • 修饰符:package-private

参数:

  • tickingChunkConsumer: Consumer

说明:

TODO

boolean anyPlayerCloseEnoughForSpawning(ChunkPos pos) @ L957

  • 方法名:anyPlayerCloseEnoughForSpawning
  • 源码定位:L957
  • 返回类型:boolean
  • 修饰符:package-private

参数:

  • pos: ChunkPos

说明:

TODO

boolean anyPlayerCloseEnoughTo(BlockPos pos, int maxDistance) @ L962

  • 方法名:anyPlayerCloseEnoughTo
  • 源码定位:L962
  • 返回类型:boolean
  • 修饰符:package-private

参数:

  • pos: BlockPos
  • maxDistance: int

说明:

TODO

private boolean anyPlayerCloseEnoughForSpawningInternal(ChunkPos pos) @ L974

  • 方法名:anyPlayerCloseEnoughForSpawningInternal
  • 源码定位:L974
  • 返回类型:boolean
  • 修饰符:private

参数:

  • pos: ChunkPos

说明:

TODO

public List<ServerPlayer> getPlayersCloseForSpawning(ChunkPos pos) @ L984

  • 方法名:getPlayersCloseForSpawning
  • 源码定位:L984
  • 返回类型:List
  • 修饰符:public

参数:

  • pos: ChunkPos

说明:

TODO

private boolean playerIsCloseEnoughForSpawning(ServerPlayer player, ChunkPos pos) @ L1001

  • 方法名:playerIsCloseEnoughForSpawning
  • 源码定位:L1001
  • 返回类型:boolean
  • 修饰符:private

参数:

  • player: ServerPlayer
  • pos: ChunkPos

说明:

TODO

private boolean playerIsCloseEnoughTo(ServerPlayer player, Vec3 pos, int maxDistance) @ L1010

  • 方法名:playerIsCloseEnoughTo
  • 源码定位:L1010
  • 返回类型:boolean
  • 修饰符:private

参数:

  • player: ServerPlayer
  • pos: Vec3
  • maxDistance: int

说明:

TODO

private static double euclideanDistanceSquared(ChunkPos chunkPos, Vec3 pos) @ L1019

  • 方法名:euclideanDistanceSquared
  • 源码定位:L1019
  • 返回类型:double
  • 修饰符:private static

参数:

  • chunkPos: ChunkPos
  • pos: Vec3

说明:

TODO

private boolean skipPlayer(ServerPlayer player) @ L1027

  • 方法名:skipPlayer
  • 源码定位:L1027
  • 返回类型:boolean
  • 修饰符:private

参数:

  • player: ServerPlayer

说明:

TODO

void updatePlayerStatus(ServerPlayer player, boolean added) @ L1031

  • 方法名:updatePlayerStatus
  • 源码定位:L1031
  • 返回类型:void
  • 修饰符:package-private

参数:

  • player: ServerPlayer
  • added: boolean

说明:

TODO

private void updatePlayerPos(ServerPlayer player) @ L1054

  • 方法名:updatePlayerPos
  • 源码定位:L1054
  • 返回类型:void
  • 修饰符:private

参数:

  • player: ServerPlayer

说明:

TODO

public void move(ServerPlayer player) @ L1059

  • 方法名:move
  • 源码定位:L1059
  • 返回类型:void
  • 修饰符:public

参数:

  • player: ServerPlayer

说明:

TODO

private void updateChunkTracking(ServerPlayer player) @ L1095

  • 方法名:updateChunkTracking
  • 源码定位:L1095
  • 返回类型:void
  • 修饰符:private

参数:

  • player: ServerPlayer

说明:

TODO

private void applyChunkTrackingView(ServerPlayer player, ChunkTrackingView next) @ L1107

  • 方法名:applyChunkTrackingView
  • 源码定位:L1107
  • 返回类型:void
  • 修饰符:private

参数:

  • player: ServerPlayer
  • next: ChunkTrackingView

说明:

TODO

public List<ServerPlayer> getPlayers(ChunkPos pos, boolean borderOnly) @ L1120

  • 方法名:getPlayers
  • 源码定位:L1120
  • 返回类型:List
  • 修饰符:public

参数:

  • pos: ChunkPos
  • borderOnly: boolean

说明:

TODO

protected void addEntity(Entity entity) @ L1134

  • 方法名:addEntity
  • 源码定位:L1134
  • 返回类型:void
  • 修饰符:protected

参数:

  • entity: Entity

说明:

TODO

protected void removeEntity(Entity entity) @ L1160

  • 方法名:removeEntity
  • 源码定位:L1160
  • 返回类型:void
  • 修饰符:protected

参数:

  • entity: Entity

说明:

TODO

protected void tick() @ L1175

  • 方法名:tick
  • 源码定位:L1175
  • 返回类型:void
  • 修饰符:protected

参数:

说明:

TODO

public void sendToTrackingPlayers(Entity entity, Packet<?super ClientGamePacketListener> packet) @ L1209

  • 方法名:sendToTrackingPlayers
  • 源码定位:L1209
  • 返回类型:void
  • 修饰符:public

参数:

  • entity: Entity
  • packet: Packet<?super ClientGamePacketListener>

说明:

TODO

public void sendToTrackingPlayersFiltered(Entity entity, Packet<?super ClientGamePacketListener> packet, Predicate<ServerPlayer> targetPredicate) @ L1216

  • 方法名:sendToTrackingPlayersFiltered
  • 源码定位:L1216
  • 返回类型:void
  • 修饰符:public

参数:

  • entity: Entity
  • packet: Packet<?super ClientGamePacketListener>
  • targetPredicate: Predicate

说明:

TODO

protected void sendToTrackingPlayersAndSelf(Entity entity, Packet<?super ClientGamePacketListener> packet) @ L1223

  • 方法名:sendToTrackingPlayersAndSelf
  • 源码定位:L1223
  • 返回类型:void
  • 修饰符:protected

参数:

  • entity: Entity
  • packet: Packet<?super ClientGamePacketListener>

说明:

TODO

public boolean isTrackedByAnyPlayer(Entity entity) @ L1230

  • 方法名:isTrackedByAnyPlayer
  • 源码定位:L1230
  • 返回类型:boolean
  • 修饰符:public

参数:

  • entity: Entity

说明:

TODO

public void forEachEntityTrackedBy(ServerPlayer player, Consumer<Entity> consumer) @ L1235

  • 方法名:forEachEntityTrackedBy
  • 源码定位:L1235
  • 返回类型:void
  • 修饰符:public

参数:

  • player: ServerPlayer
  • consumer: Consumer

说明:

TODO

public void resendBiomesForChunks(List<ChunkAccess> chunks) @ L1243

  • 方法名:resendBiomesForChunks
  • 源码定位:L1243
  • 返回类型:void
  • 修饰符:public

参数:

  • chunks: List

说明:

TODO

protected PoiManager getPoiManager() @ L1263

  • 方法名:getPoiManager
  • 源码定位:L1263
  • 返回类型:PoiManager
  • 修饰符:protected

参数:

说明:

TODO

public String getStorageName() @ L1267

  • 方法名:getStorageName
  • 源码定位:L1267
  • 返回类型:String
  • 修饰符:public

参数:

说明:

TODO

void onFullChunkStatusChange(ChunkPos pos, FullChunkStatus status) @ L1271

  • 方法名:onFullChunkStatusChange
  • 源码定位:L1271
  • 返回类型:void
  • 修饰符:package-private

参数:

  • pos: ChunkPos
  • status: FullChunkStatus

说明:

TODO

public void waitForLightBeforeSending(ChunkPos centerChunk, int chunkRadius) @ L1275

  • 方法名:waitForLightBeforeSending
  • 源码定位:L1275
  • 返回类型:void
  • 修饰符:public

参数:

  • centerChunk: ChunkPos
  • chunkRadius: int

说明:

TODO

public void forEachReadyToSendChunk(Consumer<LevelChunk> consumer) @ L1285

  • 方法名:forEachReadyToSendChunk
  • 源码定位:L1285
  • 返回类型:void
  • 修饰符:public

参数:

  • consumer: Consumer

说明:

TODO

代码

public class ChunkMap extends SimpleRegionStorage implements ChunkHolder.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);
    private final Long2ObjectLinkedOpenHashMap<ChunkHolder> updatingChunkMap = new Long2ObjectLinkedOpenHashMap<>();
    private volatile Long2ObjectLinkedOpenHashMap<ChunkHolder> visibleChunkMap = this.updatingChunkMap.clone();
    private final Long2ObjectLinkedOpenHashMap<ChunkHolder> pendingUnloads = new Long2ObjectLinkedOpenHashMap<>();
    private final List<ChunkGenerationTask> pendingGenerationTasks = new ArrayList<>();
    private final ServerLevel level;
    private final ThreadedLevelLightEngine lightEngine;
    private final BlockableEventLoop<Runnable> 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 ChunkMap.DistanceManager distanceManager;
    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.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<Runnable> mainThreadExecutor,
        LightChunkGetter chunkGetter,
        ChunkGenerator generator,
        ChunkStatusUpdateListener chunkStatusListener,
        Supplier<SavedDataStorage> 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
        );
        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 noiseGenerator) {
            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 ChunkMap.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.pack());
    }
 
    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.pack(chunkX, chunkZ));
    }
 
    private boolean isChunkOnTrackedBorder(ServerPlayer player, int chunkX, int chunkZ) {
        if (!this.isChunkTracked(player, chunkX, chunkZ)) {
            return false;
        } else {
            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)) {
                        return true;
                    }
                }
            }
 
            return false;
        }
    }
 
    protected ThreadedLevelLightEngine getLightEngine() {
        return this.lightEngine;
    }
 
    public @Nullable ChunkHolder getUpdatingChunkIfPresent(long key) {
        return this.updatingChunkMap.get(key);
    }
 
    protected @Nullable ChunkHolder getVisibleChunkIfPresent(long key) {
        return 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);
            return chunk == null
                ? ChunkTaskPriorityQueue.PRIORITY_LEVEL_COUNT - 1
                : Math.min(chunk.getQueueLevel(), ChunkTaskPriorityQueue.PRIORITY_LEVEL_COUNT - 1);
        };
    }
 
    public String getChunkDebugData(ChunkPos pos) {
        ChunkHolder chunkHolder = this.getVisibleChunkIfPresent(pos.pack());
        if (chunkHolder == null) {
            return "null";
        } else {
            String result = chunkHolder.getTicketLevel() + "\n";
            ChunkStatus status = chunkHolder.getLatestStatus();
            ChunkAccess chunk = chunkHolder.getLatestChunk();
            if (status != null) {
                result = result + "St: \u00a7" + status.getIndex() + status + "\u00a7r\n";
            }
 
            if (chunk != null) {
                result = result + "Ch: \u00a7" + chunk.getPersistedStatus().getIndex() + chunk.getPersistedStatus() + "\u00a7r\n";
            }
 
            FullChunkStatus fullStatus = chunkHolder.getFullStatus();
            result = result + '\u00a7' + fullStatus.ordinal() + fullStatus;
            return result + "\u00a7r";
        }
    }
 
    CompletableFuture<ChunkResult<List<ChunkAccess>>> getChunkRangeFuture(ChunkHolder centerChunk, int range, IntFunction<ChunkStatus> distanceToStatus) {
        if (range == 0) {
            ChunkStatus status = distanceToStatus.apply(0);
            return centerChunk.scheduleChunkGenerationTask(status, this).thenApply(r -> r.map(List::of));
        } else {
            int chunkCount = Mth.square(range * 2 + 1);
            List<CompletableFuture<ChunkResult<ChunkAccess>>> 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.pack(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 -> {
                List<ChunkAccess> chunks = new ArrayList<>(chunkResults.size());
 
                for (ChunkResult<ChunkAccess> chunkResult : chunkResults) {
                    if (chunkResult == null) {
                        throw this.debugFuturesAndCreateReportedException(new IllegalStateException("At least one of the chunk futures were null"), "n/a");
                    }
 
                    ChunkAccess chunkx = chunkResult.orElse(null);
                    if (chunkx == null) {
                        return UNLOADED_CHUNK_LIST_RESULT;
                    }
 
                    chunks.add(chunkx);
                }
 
                return ChunkResult.of(chunks);
            });
        }
    }
 
    public ReportedException debugFuturesAndCreateReportedException(IllegalStateException exception, String details) {
        StringBuilder sb = new StringBuilder();
        Consumer<ChunkHolder> addToDebug = holder -> holder.getAllFutures().forEach(pair -> {
            ChunkStatus status = pair.getFirst();
            CompletableFuture<ChunkResult<ChunkAccess>> future = 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<ChunkResult<LevelChunk>> 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;
        } else {
            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 = this.pendingUnloads.remove(node);
                if (chunk != null) {
                    chunk.setTicketLevel(level);
                } else {
                    chunk = new ChunkHolder(ChunkPos.unpack(node), level, this.level, this.lightEngine, this::onLevelChange, this);
                }
 
                this.updatingChunkMap.put(node, 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<ChunkHolder> chunksToSave = this.visibleChunkMap
                .values()
                .stream()
                .filter(ChunkHolder::wasAccessibleSinceLastSave)
                .peek(ChunkHolder::refreshAccessibility)
                .toList();
            MutableBoolean didWork = new MutableBoolean();
 
            do {
                didWork.setFalse();
                chunksToSave.stream()
                    .map(chunkx -> {
                        this.mainThreadExecutor.managedBlock(chunkx::isReadyForSaving);
                        return chunkx.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 chunk : this.visibleChunkMap.values()) {
                this.saveChunkIfNeeded(chunk, 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) {
        for (LongIterator iterator = this.toDrop.iterator(); iterator.hasNext(); iterator.remove()) {
            long pos = iterator.nextLong();
            ChunkHolder chunkHolder = this.updatingChunkMap.get(pos);
            if (chunkHolder != null) {
                this.updatingChunkMap.remove(pos);
                this.pendingUnloads.put(pos, chunkHolder);
                this.modified = true;
                this.scheduleUnload(pos, chunkHolder);
            }
        }
 
        int minimalNumberOfChunksToProcess = Math.max(0, this.unloadQueue.size() - 2000);
 
        Runnable unloadTask;
        while ((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()) {
            long chunkPos = iterator.nextLong();
            ChunkHolder chunkHolder = this.visibleChunkMap.get(chunkPos);
            ChunkAccess latestChunk = chunkHolder != null ? chunkHolder.getLatestChunk() : null;
            if (latestChunk == null || !latestChunk.isUnsaved()) {
                iterator.remove();
            } else if (this.saveChunkIfNeeded(chunkHolder, now)) {
                eagerlySavedCount++;
                iterator.remove();
            }
        }
    }
 
    private void scheduleUnload(long pos, ChunkHolder chunkHolder) {
        CompletableFuture<?> saveSyncFuture = chunkHolder.getSaveSyncFuture();
        saveSyncFuture.thenRunAsync(() -> {
            CompletableFuture<?> currentFuture = chunkHolder.getSaveSyncFuture();
            if (currentFuture != saveSyncFuture) {
                this.scheduleUnload(pos, chunkHolder);
            } else {
                ChunkAccess chunk = chunkHolder.getLatestChunk();
                if (this.pendingUnloads.remove(pos, chunkHolder) && chunk != null) {
                    if (chunk instanceof LevelChunk levelChunk) {
                        levelChunk.setLoaded(false);
                    }
 
                    this.save(chunk);
                    if (chunk instanceof LevelChunk levelChunk) {
                        this.level.unload(levelChunk);
                    }
 
                    this.lightEngine.updateChunkStatus(chunk.getPos());
                    this.lightEngine.tryScheduleUpdate();
                    this.nextChunkSaveTime.remove(chunk.getPos().pack());
                }
            }
        }, this.unloadQueue::add).whenComplete((ignored, 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 pos) {
        CompletableFuture<Optional<SerializableChunkData>> 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", pos);
            }
 
            return parsedData;
        }), Util.backgroundExecutor().forName("parseChunk"));
        CompletableFuture<?> poiFuture = this.poiManager.prefetch(pos);
        return chunkDataFuture.<Object, Optional<SerializableChunkData>>thenCombine(
                (CompletionStage<? extends Object>)poiFuture, (chunkData, ignored) -> chunkData
            )
            .thenApplyAsync(chunkData -> {
                Profiler.get().incrementCounter("chunkLoad");
                if (chunkData.isPresent()) {
                    ChunkAccess chunk = chunkData.get().read(this.level, this.poiManager, this.storageInfo(), pos);
                    this.markPosition(pos, chunk.getPersistedStatus().getChunkType());
                    return chunk;
                } else {
                    return this.createEmptyChunk(pos);
                }
            }, this.mainThreadExecutor)
            .exceptionallyAsync(throwable -> this.handleChunkLoadFailure(throwable, pos), this.mainThreadExecutor);
    }
 
    private ChunkAccess handleChunkLoadFailure(Throwable throwable, ChunkPos pos) {
        Throwable unwrapped = throwable instanceof CompletionException e ? e.getCause() : throwable;
        Throwable cause = unwrapped instanceof ReportedException ex ? ex.getCause() : unwrapped;
        boolean alwaysThrow = cause instanceof Error;
        boolean ioException = cause instanceof IOException || cause instanceof NbtException;
        if (!alwaysThrow) {
            if (!ioException) {
            }
 
            this.level.getServer().reportChunkLoadFailure(cause, this.storageInfo(), pos);
            return this.createEmptyChunk(pos);
        } else {
            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);
        }
    }
 
    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.pack(), (byte)-1);
    }
 
    private byte markPosition(ChunkPos pos, ChunkType type) {
        return this.chunkTypeCache.put(pos.pack(), (byte)(type == ChunkType.PROTOCHUNK ? -1 : 1));
    }
 
    @Override
    public GenerationChunkHolder acquireGeneration(long chunkNode) {
        ChunkHolder chunkHolder = this.updatingChunkMap.get(chunkNode);
        chunkHolder.increaseGenerationRefCount();
        return chunkHolder;
    }
 
    @Override
    public void releaseGeneration(GenerationChunkHolder chunkHolder) {
        chunkHolder.decreaseGenerationRefCount();
    }
 
    @Override
    public CompletableFuture<ChunkAccess> applyStep(GenerationChunkHolder chunkHolder, ChunkStep step, StaticCache2D<GenerationChunkHolder> cache) {
        ChunkPos pos = chunkHolder.getPos();
        if (step.targetStatus() == ChunkStatus.EMPTY) {
            return this.scheduleChunkLoad(pos);
        } else {
            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");
                } else {
                    return step.apply(this.worldGenContext, cache, centerChunk);
                }
            } catch (Exception var8) {
                var8.getStackTrace();
                CrashReport report = CrashReport.forThrowable(var8, "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.pack(pos.x(), pos.z()));
                category.setDetail("Generator", this.generator());
                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) {
                future.thenRun(() -> this.runGenerationTask(task));
            }
        }, chunk.getPos().pack(), chunk::getQueueLevel);
    }
 
    @Override
    public void runGenerationTasks() {
        this.pendingGenerationTasks.forEach(this::runGenerationTask);
        this.pendingGenerationTasks.clear();
    }
 
    public CompletableFuture<ChunkResult<LevelChunk>> prepareTickingChunk(ChunkHolder chunk) {
        CompletableFuture<ChunkResult<List<ChunkAccess>>> 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), this.mainThreadExecutor);
            }
 
            return levelChunk;
        }), this.mainThreadExecutor);
    }
 
    private void onChunkReadyToSend(ChunkHolder chunkHolder, LevelChunk chunk) {
        ChunkPos chunkPos = chunk.getPos();
 
        for (ServerPlayer player : this.playerMap.getAllPlayers()) {
            if (player.getChunkTrackingView().contains(chunkPos)) {
                markChunkPendingToSend(player, chunk);
            }
        }
 
        this.level.getChunkSource().onChunkReadyToSend(chunkHolder);
        this.level.debugSynchronizers().registerChunk(chunk);
    }
 
    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)));
    }
 
    Stream<ChunkHolder> 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()) {
            ChunkAccess chunkAccess = chunk.getLatestChunk();
            if (!(chunkAccess instanceof ImposterProtoChunk) && !(chunkAccess instanceof LevelChunk)) {
                return false;
            } else if (!chunkAccess.isUnsaved()) {
                return false;
            } else {
                long chunkPos = chunkAccess.getPos().pack();
                long nextSaveTime = this.nextChunkSaveTime.getOrDefault(chunkPos, -1L);
                if (now < nextSaveTime) {
                    return false;
                } else {
                    boolean saved = this.save(chunkAccess);
                    chunk.refreshAccessibility();
                    if (saved) {
                        this.nextChunkSaveTime.put(chunkPos, now + 10000L);
                    }
 
                    return saved;
                }
            }
        } else {
            return false;
        }
    }
 
    private boolean save(ChunkAccess chunk) {
        this.poiManager.flush(chunk.getPos());
        if (!chunk.tryMarkSaved()) {
            return false;
        } else {
            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<CompoundTag> encodedData = CompletableFuture.supplyAsync(data::write, Util.backgroundExecutor());
                this.write(pos, encodedData::join).handle((ignored, throwable) -> {
                    if (throwable != null) {
                        this.level.getServer().reportChunkSaveFailure(throwable, this.storageInfo(), pos);
                    }
 
                    this.activeChunkWrites.decrementAndGet();
                    return null;
                });
                this.markPosition(pos, status.getChunkType());
                return true;
            } catch (Exception var6) {
                this.level.getServer().reportChunkSaveFailure(var6, this.storageInfo(), pos);
                return false;
            }
        }
    }
 
    private boolean isExistingChunkFull(ChunkPos pos) {
        byte cachedChunkType = this.chunkTypeCache.get(pos.pack());
        if (cachedChunkType != 0) {
            return cachedChunkType == 1;
        } else {
            CompoundTag currentTag;
            try {
                currentTag = this.readChunk(pos).join().orElse(null);
                if (currentTag == null) {
                    this.markPositionReplaceable(pos);
                    return false;
                }
            } catch (Exception var5) {
                LOGGER.error("Failed to read chunk {}", pos, var5);
                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.pack());
        if (chunk != null) {
            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);
        return chunkHolder == null ? null : 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 (Entry<ChunkHolder> entry : this.visibleChunkMap.long2ObjectEntrySet()) {
            long posKey = entry.getLongKey();
            ChunkPos pos = ChunkPos.unpack(posKey);
            ChunkHolder holder = entry.getValue();
            Optional<ChunkAccess> chunk = Optional.ofNullable(holder.getLatestChunk());
            Optional<LevelChunk> 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),
                printFuture(holder.getFullChunkFuture()),
                printFuture(holder.getTickingChunkFuture()),
                printFuture(holder.getEntityTickingChunkFuture()),
                this.ticketStorage.getTicketDebugString(posKey, false),
                this.anyPlayerCloseEnoughForSpawning(pos),
                fullChunk.<Integer>map(c -> c.getBlockEntities().size()).orElse(0),
                this.ticketStorage.getTicketDebugString(posKey, true),
                this.distanceManager.getChunkLevel(posKey, true),
                fullChunk.<Integer>map(levelChunk -> levelChunk.getBlockTicks().count()).orElse(0),
                fullChunk.<Integer>map(levelChunk -> levelChunk.getFluidTicks().count()).orElse(0)
            );
        }
    }
 
    private static String printFuture(CompletableFuture<ChunkResult<LevelChunk>> future) {
        try {
            ChunkResult<LevelChunk> result = future.getNow(null);
            if (result != null) {
                return result.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(chunkTag -> chunkTag.map(this::upgradeChunkTag), Util.backgroundExecutor().forName("upgradeChunk"));
    }
 
    private CompoundTag upgradeChunkTag(CompoundTag tag) {
        return this.upgradeChunkTag(
            tag,
            -1,
            getChunkDataFixContextTag(this.level.dimension(), this.generator().getTypeNameForDataFixer()),
            SharedConstants.getCurrentVersion().dataVersion().version()
        );
    }
 
    public static CompoundTag getChunkDataFixContextTag(ResourceKey<Level> dimension, Optional<Identifier> generatorIdentifier) {
        CompoundTag contextTag = new CompoundTag();
        contextTag.putString("dimension", dimension.identifier().toString());
        generatorIdentifier.ifPresent(identifier -> contextTag.putString("generator", identifier.toString()));
        return contextTag;
    }
 
    void collectSpawningChunks(List<LevelChunk> output) {
        LongIterator spawnCandidateChunks = this.distanceManager.getSpawnCandidateChunks();
 
        while (spawnCandidateChunks.hasNext()) {
            ChunkHolder holder = this.visibleChunkMap.get(spawnCandidateChunks.nextLong());
            if (holder != null) {
                LevelChunk chunk = holder.getTickingChunk();
                if (chunk != null && this.anyPlayerCloseEnoughForSpawningInternal(holder.getPos())) {
                    output.add(chunk);
                }
            }
        }
    }
 
    void forEachBlockTickingChunk(Consumer<LevelChunk> tickingChunkConsumer) {
        this.distanceManager.forEachEntityTickingChunk(chunkPos -> {
            ChunkHolder holder = this.visibleChunkMap.get(chunkPos);
            if (holder != null) {
                LevelChunk chunk = holder.getTickingChunk();
                if (chunk != null) {
                    tickingChunkConsumer.accept(chunk);
                }
            }
        });
    }
 
    boolean anyPlayerCloseEnoughForSpawning(ChunkPos pos) {
        TriState triState = this.distanceManager.hasPlayersNearby(pos.pack());
        return triState == TriState.DEFAULT ? this.anyPlayerCloseEnoughForSpawningInternal(pos) : 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)) {
                return true;
            }
        }
 
        return false;
    }
 
    private boolean anyPlayerCloseEnoughForSpawningInternal(ChunkPos pos) {
        for (ServerPlayer player : this.playerMap.getAllPlayers()) {
            if (this.playerIsCloseEnoughForSpawning(player, pos)) {
                return true;
            }
        }
 
        return false;
    }
 
    public List<ServerPlayer> getPlayersCloseForSpawning(ChunkPos pos) {
        long key = pos.pack();
        if (!this.distanceManager.hasPlayersNearby(key).toBoolean(true)) {
            return List.of();
        } else {
            Builder<ServerPlayer> builder = ImmutableList.builder();
 
            for (ServerPlayer player : this.playerMap.getAllPlayers()) {
                if (this.playerIsCloseEnoughForSpawning(player, pos)) {
                    builder.add(player);
                }
            }
 
            return builder.build();
        }
    }
 
    private boolean playerIsCloseEnoughForSpawning(ServerPlayer player, ChunkPos pos) {
        if (player.isSpectator()) {
            return false;
        } else {
            double distanceToChunk = euclideanDistanceSquared(pos, player.position());
            return distanceToChunk < 16384.0;
        }
    }
 
    private boolean playerIsCloseEnoughTo(ServerPlayer player, Vec3 pos, int maxDistance) {
        if (player.isSpectator()) {
            return false;
        } else {
            double distanceToPos = player.position().distanceTo(pos);
            return distanceToPos < 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);
    }
 
    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) {
        for (ChunkMap.TrackedEntity trackedEntity : this.entityMap.values()) {
            if (trackedEntity.entity == player) {
                trackedEntity.updatePlayers(this.level.players());
            } else {
                trackedEntity.updatePlayer(player);
            }
        }
 
        SectionPos oldSection = player.getLastSectionPos();
        SectionPos newSection = SectionPos.of(player);
        boolean wasIgnored = this.playerMap.ignored(player);
        boolean ignored = this.skipPlayer(player);
        boolean 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) {
        ChunkPos chunkPos = player.chunkPosition();
        int playerViewDistance = this.getPlayerViewDistance(player);
        if (!(
            player.getChunkTrackingView() instanceof ChunkTrackingView.Positioned view
                && view.center().equals(chunkPos)
                && view.viewDistance() == playerViewDistance
        )) {
            this.applyChunkTrackingView(player, ChunkTrackingView.of(chunkPos, playerViewDistance));
        }
    }
 
    private void applyChunkTrackingView(ServerPlayer player, ChunkTrackingView next) {
        if (player.level() == this.level) {
            ChunkTrackingView previous = player.getChunkTrackingView();
            if (next instanceof ChunkTrackingView.Positioned to
                && !(previous instanceof ChunkTrackingView.Positioned from && from.center().equals(to.center()))) {
                player.connection.send(new ClientboundSetChunkCacheCenterPacket(to.center().x(), to.center().z()));
            }
 
            ChunkTrackingView.difference(previous, next, pos -> this.markChunkPendingToSend(player, pos), pos -> dropChunk(player, pos));
            player.setChunkTrackingView(next);
        }
    }
 
    @Override
    public List<ServerPlayer> getPlayers(ChunkPos pos, boolean borderOnly) {
        Set<ServerPlayer> allPlayers = this.playerMap.getAllPlayers();
        Builder<ServerPlayer> result = ImmutableList.builder();
 
        for (ServerPlayer player : allPlayers) {
            if (borderOnly && this.isChunkOnTrackedBorder(player, pos.x(), pos.z()) || !borderOnly && this.isChunkTracked(player, pos.x(), pos.z())) {
                result.add(player);
            }
        }
 
        return result.build();
    }
 
    protected void addEntity(Entity entity) {
        if (!(entity instanceof EnderDragonPart)) {
            EntityType<?> type = entity.getType();
            int range = type.clientTrackingRange() * 16;
            if (range != 0) {
                int updateInterval = type.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, range, updateInterval, type.trackDeltas());
                    this.entityMap.put(entity.getId(), trackedEntity);
                    trackedEntity.updatePlayers(this.level.players());
                    if (entity instanceof ServerPlayer player) {
                        this.updatePlayerStatus(player, true);
 
                        for (ChunkMap.TrackedEntity e : this.entityMap.values()) {
                            if (e.entity != player) {
                                e.updatePlayer(player);
                            }
                        }
                    }
                }
            }
        }
    }
 
    protected void removeEntity(Entity entity) {
        if (entity instanceof ServerPlayer player) {
            this.updatePlayerStatus(player, false);
 
            for (ChunkMap.TrackedEntity trackedEntity : this.entityMap.values()) {
                trackedEntity.removePlayer(player);
            }
        }
 
        ChunkMap.TrackedEntity trackedEntity = this.entityMap.remove(entity.getId());
        if (trackedEntity != null) {
            trackedEntity.broadcastRemoved();
        }
    }
 
    protected void tick() {
        for (ServerPlayer player : this.playerMap.getAllPlayers()) {
            this.updateChunkTracking(player);
        }
 
        List<ServerPlayer> movedPlayers = Lists.newArrayList();
        List<ServerPlayer> players = this.level.players();
 
        for (ChunkMap.TrackedEntity trackedEntity : this.entityMap.values()) {
            SectionPos oldPos = trackedEntity.lastSectionPos;
            SectionPos newPos = SectionPos.of(trackedEntity.entity);
            boolean 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().pack())) {
                trackedEntity.serverEntity.sendChanges();
            }
        }
 
        if (!movedPlayers.isEmpty()) {
            for (ChunkMap.TrackedEntity trackedEntity : this.entityMap.values()) {
                trackedEntity.updatePlayers(movedPlayers);
            }
        }
    }
 
    public void sendToTrackingPlayers(Entity entity, Packet<? super ClientGamePacketListener> packet) {
        ChunkMap.TrackedEntity trackedEntity = this.entityMap.get(entity.getId());
        if (trackedEntity != null) {
            trackedEntity.sendToTrackingPlayers(packet);
        }
    }
 
    public void sendToTrackingPlayersFiltered(Entity entity, Packet<? super ClientGamePacketListener> packet, Predicate<ServerPlayer> targetPredicate) {
        ChunkMap.TrackedEntity trackedEntity = this.entityMap.get(entity.getId());
        if (trackedEntity != null) {
            trackedEntity.sendToTrackingPlayersFiltered(packet, targetPredicate);
        }
    }
 
    protected void sendToTrackingPlayersAndSelf(Entity entity, Packet<? super ClientGamePacketListener> packet) {
        ChunkMap.TrackedEntity trackedEntity = this.entityMap.get(entity.getId());
        if (trackedEntity != null) {
            trackedEntity.sendToTrackingPlayersAndSelf(packet);
        }
    }
 
    public boolean isTrackedByAnyPlayer(Entity entity) {
        ChunkMap.TrackedEntity trackedEntity = this.entityMap.get(entity.getId());
        return trackedEntity != null ? !trackedEntity.seenBy.isEmpty() : false;
    }
 
    public void forEachEntityTrackedBy(ServerPlayer player, Consumer<Entity> consumer) {
        for (ChunkMap.TrackedEntity entity : this.entityMap.values()) {
            if (entity.seenBy.contains(player.connection)) {
                consumer.accept(entity.entity);
            }
        }
    }
 
    public void resendBiomesForChunks(List<ChunkAccess> chunks) {
        Map<ServerPlayer, List<LevelChunk>> chunksForPlayers = new HashMap<>();
 
        for (ChunkAccess chunkAccess : chunks) {
            ChunkPos pos = chunkAccess.getPos();
            LevelChunk chunk;
            if (chunkAccess instanceof LevelChunk levelChunk) {
                chunk = levelChunk;
            } else {
                chunk = this.level.getChunk(pos.x(), pos.z());
            }
 
            for (ServerPlayer player : this.getPlayers(pos, false)) {
                chunksForPlayers.computeIfAbsent(player, p -> new ArrayList<>()).add(chunk);
            }
        }
 
        chunksForPlayers.forEach((playerx, chunkList) -> playerx.connection.send(ClientboundChunksBiomesPacket.forChunks((List<LevelChunk>)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.pack());
            if (chunkHolder != null) {
                chunkHolder.addSendDependency(this.lightEngine.waitForPendingTasks(chunkPos.x(), chunkPos.z()));
            }
        });
    }
 
    public void forEachReadyToSendChunk(Consumer<LevelChunk> consumer) {
        for (ChunkHolder chunkHolder : this.visibleChunkMap.values()) {
            LevelChunk chunk = chunkHolder.getChunkToSend();
            if (chunk != null) {
                consumer.accept(chunk);
            }
        }
    }
 
    private class DistanceManager extends net.minecraft.server.level.DistanceManager {
        protected DistanceManager(TicketStorage ticketStorage, Executor executor, Executor mainThreadExecutor) {
            Objects.requireNonNull(ChunkMap.this);
            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<ServerPlayerConnection> seenBy;
 
        public TrackedEntity(Entity entity, int range, int updateInterval, boolean trackDelta) {
            Objects.requireNonNull(ChunkMap.this);
            super();
            this.seenBy = Sets.newIdentityHashSet();
            this.serverEntity = new ServerEntity(ChunkMap.this.level, entity, updateInterval, trackDelta, this);
            this.entity = entity;
            this.range = range;
            this.lastSectionPos = SectionPos.of(entity);
        }
 
        @Override
        public boolean equals(Object obj) {
            return obj instanceof ChunkMap.TrackedEntity ? ((ChunkMap.TrackedEntity)obj).entity.getId() == this.entity.getId() : false;
        }
 
        @Override
        public int hashCode() {
            return this.entity.getId();
        }
 
        @Override
        public void sendToTrackingPlayers(Packet<? super ClientGamePacketListener> packet) {
            for (ServerPlayerConnection connection : this.seenBy) {
                connection.send(packet);
            }
        }
 
        @Override
        public void sendToTrackingPlayersAndSelf(Packet<? super ClientGamePacketListener> packet) {
            this.sendToTrackingPlayers(packet);
            if (this.entity instanceof ServerPlayer player) {
                player.connection.send(packet);
            }
        }
 
        @Override
        public void sendToTrackingPlayersFiltered(Packet<? super ClientGamePacketListener> packet, Predicate<ServerPlayer> targetPredicate) {
            for (ServerPlayerConnection connection : this.seenBy) {
                if (targetPredicate.test(connection.getPlayer())) {
                    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) {
            if (player != this.entity) {
                Vec3 deltaToPlayer = player.position().subtract(this.entity.position());
                int playerViewDistance = ChunkMap.this.getPlayerViewDistance(player);
                double visibleRange = Math.min(this.getEffectiveRange(), playerViewDistance * 16);
                double distanceSquared = deltaToPlayer.x * deltaToPlayer.x + deltaToPlayer.z * deltaToPlayer.z;
                double rangeSquared = visibleRange * visibleRange;
                boolean 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) {
                    effectiveRange = passengerRange;
                }
            }
 
            return this.scaledRange(effectiveRange);
        }
 
        public void updatePlayers(List<ServerPlayer> players) {
            for (ServerPlayer player : players) {
                this.updatePlayer(player);
            }
        }
    }
}

引用的其他类