diff options
Diffstat (limited to 'patch-remap/mache-spigotflower/net/minecraft/server/level/ChunkHolder.java.patch')
-rw-r--r-- | patch-remap/mache-spigotflower/net/minecraft/server/level/ChunkHolder.java.patch | 630 |
1 files changed, 630 insertions, 0 deletions
diff --git a/patch-remap/mache-spigotflower/net/minecraft/server/level/ChunkHolder.java.patch b/patch-remap/mache-spigotflower/net/minecraft/server/level/ChunkHolder.java.patch new file mode 100644 index 0000000000..f589430b82 --- /dev/null +++ b/patch-remap/mache-spigotflower/net/minecraft/server/level/ChunkHolder.java.patch @@ -0,0 +1,630 @@ +--- a/net/minecraft/server/level/ChunkHolder.java ++++ b/net/minecraft/server/level/ChunkHolder.java +@@ -23,11 +23,11 @@ + import net.minecraft.network.protocol.game.ClientboundSectionBlocksUpdatePacket; + import net.minecraft.util.DebugBuffer; + import net.minecraft.world.level.ChunkPos; ++import net.minecraft.world.level.EnumSkyBlock; + import net.minecraft.world.level.Level; + import net.minecraft.world.level.LevelHeightAccessor; +-import net.minecraft.world.level.LightLayer; + import net.minecraft.world.level.block.entity.BlockEntity; +-import net.minecraft.world.level.block.state.BlockState; ++import net.minecraft.world.level.block.state.IBlockData; + import net.minecraft.world.level.chunk.ChunkAccess; + import net.minecraft.world.level.chunk.ChunkStatus; + import net.minecraft.world.level.chunk.ImposterProtoChunk; +@@ -36,23 +36,27 @@ + import net.minecraft.world.level.chunk.ProtoChunk; + import net.minecraft.world.level.lighting.LevelLightEngine; + ++// CraftBukkit start ++import net.minecraft.server.MinecraftServer; ++// CraftBukkit end ++ + public class ChunkHolder { + +- public static final Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure> UNLOADED_CHUNK = Either.right(ChunkHolder.ChunkLoadingFailure.UNLOADED); +- public static final CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> UNLOADED_CHUNK_FUTURE = CompletableFuture.completedFuture(ChunkHolder.UNLOADED_CHUNK); +- public static final Either<LevelChunk, ChunkHolder.ChunkLoadingFailure> UNLOADED_LEVEL_CHUNK = Either.right(ChunkHolder.ChunkLoadingFailure.UNLOADED); +- private static final Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure> NOT_DONE_YET = Either.right(ChunkHolder.ChunkLoadingFailure.UNLOADED); +- private static final CompletableFuture<Either<LevelChunk, ChunkHolder.ChunkLoadingFailure>> UNLOADED_LEVEL_CHUNK_FUTURE = CompletableFuture.completedFuture(ChunkHolder.UNLOADED_LEVEL_CHUNK); ++ public static final Either<ChunkAccess, ChunkHolder.Failure> UNLOADED_CHUNK = Either.right(ChunkHolder.Failure.UNLOADED); ++ public static final CompletableFuture<Either<ChunkAccess, ChunkHolder.Failure>> UNLOADED_CHUNK_FUTURE = CompletableFuture.completedFuture(ChunkHolder.UNLOADED_CHUNK); ++ public static final Either<LevelChunk, ChunkHolder.Failure> UNLOADED_LEVEL_CHUNK = Either.right(ChunkHolder.Failure.UNLOADED); ++ private static final Either<ChunkAccess, ChunkHolder.Failure> NOT_DONE_YET = Either.right(ChunkHolder.Failure.UNLOADED); ++ private static final CompletableFuture<Either<LevelChunk, ChunkHolder.Failure>> UNLOADED_LEVEL_CHUNK_FUTURE = CompletableFuture.completedFuture(ChunkHolder.UNLOADED_LEVEL_CHUNK); + private static final List<ChunkStatus> CHUNK_STATUSES = ChunkStatus.getStatusList(); +- private final AtomicReferenceArray<CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>>> futures; ++ private final AtomicReferenceArray<CompletableFuture<Either<ChunkAccess, ChunkHolder.Failure>>> futures; + private final LevelHeightAccessor levelHeightAccessor; +- private volatile CompletableFuture<Either<LevelChunk, ChunkHolder.ChunkLoadingFailure>> fullChunkFuture; +- private volatile CompletableFuture<Either<LevelChunk, ChunkHolder.ChunkLoadingFailure>> tickingChunkFuture; +- private volatile CompletableFuture<Either<LevelChunk, ChunkHolder.ChunkLoadingFailure>> entityTickingChunkFuture; ++ private volatile CompletableFuture<Either<LevelChunk, ChunkHolder.Failure>> fullChunkFuture; ++ private volatile CompletableFuture<Either<LevelChunk, ChunkHolder.Failure>> tickingChunkFuture; ++ private volatile CompletableFuture<Either<LevelChunk, ChunkHolder.Failure>> entityTickingChunkFuture; + private CompletableFuture<ChunkAccess> chunkToSave; + @Nullable + private final DebugBuffer<ChunkHolder.ChunkSaveDebug> chunkToSaveHistory; +- private int oldTicketLevel; ++ public int oldTicketLevel; + private int ticketLevel; + private int queueLevel; + final ChunkPos pos; +@@ -62,62 +66,76 @@ + private final BitSet skyChangedLightSectionFilter; + private final LevelLightEngine lightEngine; + private final ChunkHolder.LevelChangeListener onLevelChange; +- private final ChunkHolder.PlayerProvider playerProvider; ++ public final ChunkHolder.PlayerProvider playerProvider; + private boolean wasAccessibleSinceLastSave; + private CompletableFuture<Void> pendingFullStateConfirmation; + private CompletableFuture<?> sendSync; + +- public ChunkHolder(ChunkPos chunkpos, int i, LevelHeightAccessor levelheightaccessor, LevelLightEngine levellightengine, ChunkHolder.LevelChangeListener chunkholder_levelchangelistener, ChunkHolder.PlayerProvider chunkholder_playerprovider) { ++ public ChunkHolder(ChunkPos pos, int ticketLevel, LevelHeightAccessor levelHeightAccessor, LevelLightEngine lightEngine, ChunkHolder.LevelChangeListener onLevelChange, ChunkHolder.PlayerProvider playerProvider) { + this.futures = new AtomicReferenceArray(ChunkHolder.CHUNK_STATUSES.size()); + this.fullChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE; + this.tickingChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE; + this.entityTickingChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE; +- this.chunkToSave = CompletableFuture.completedFuture((Object) null); ++ this.chunkToSave = CompletableFuture.completedFuture(null); // CraftBukkit - decompile error + this.chunkToSaveHistory = null; + this.blockChangedLightSectionFilter = new BitSet(); + this.skyChangedLightSectionFilter = new BitSet(); +- this.pendingFullStateConfirmation = CompletableFuture.completedFuture((Object) null); +- this.sendSync = CompletableFuture.completedFuture((Object) null); +- this.pos = chunkpos; +- this.levelHeightAccessor = levelheightaccessor; +- this.lightEngine = levellightengine; +- this.onLevelChange = chunkholder_levelchangelistener; +- this.playerProvider = chunkholder_playerprovider; ++ this.pendingFullStateConfirmation = CompletableFuture.completedFuture(null); // CraftBukkit - decompile error ++ this.sendSync = CompletableFuture.completedFuture(null); // CraftBukkit - decompile error ++ this.pos = pos; ++ this.levelHeightAccessor = levelHeightAccessor; ++ this.lightEngine = lightEngine; ++ this.onLevelChange = onLevelChange; ++ this.playerProvider = playerProvider; + this.oldTicketLevel = ChunkLevel.MAX_LEVEL + 1; + this.ticketLevel = this.oldTicketLevel; + this.queueLevel = this.oldTicketLevel; +- this.setTicketLevel(i); +- this.changedBlocksPerSection = new ShortSet[levelheightaccessor.getSectionsCount()]; ++ this.setTicketLevel(ticketLevel); ++ this.changedBlocksPerSection = new ShortSet[levelHeightAccessor.getSectionsCount()]; + } + +- public CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> getFutureIfPresentUnchecked(ChunkStatus chunkstatus) { +- CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> completablefuture = (CompletableFuture) this.futures.get(chunkstatus.getIndex()); ++ // CraftBukkit start ++ public LevelChunk getFullChunkNow() { ++ // Note: We use the oldTicketLevel for isLoaded checks. ++ if (!ChunkLevel.fullStatus(this.oldTicketLevel).isOrAfter(FullChunkStatus.FULL)) return null; ++ return this.getFullChunkNowUnchecked(); ++ } + ++ public LevelChunk getFullChunkNowUnchecked() { ++ CompletableFuture<Either<ChunkAccess, ChunkHolder.Failure>> statusFuture = this.getFutureIfPresentUnchecked(ChunkStatus.FULL); ++ Either<ChunkAccess, ChunkHolder.Failure> either = (Either<ChunkAccess, ChunkHolder.Failure>) statusFuture.getNow(null); ++ return (either == null) ? null : (LevelChunk) either.left().orElse(null); ++ } ++ // CraftBukkit end ++ ++ public CompletableFuture<Either<ChunkAccess, ChunkHolder.Failure>> getFutureIfPresentUnchecked(ChunkStatus chunkStatus) { ++ CompletableFuture<Either<ChunkAccess, ChunkHolder.Failure>> completablefuture = (CompletableFuture) this.futures.get(chunkStatus.getIndex()); ++ + return completablefuture == null ? ChunkHolder.UNLOADED_CHUNK_FUTURE : completablefuture; + } + +- public CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> getFutureIfPresent(ChunkStatus chunkstatus) { +- return ChunkLevel.generationStatus(this.ticketLevel).isOrAfter(chunkstatus) ? this.getFutureIfPresentUnchecked(chunkstatus) : ChunkHolder.UNLOADED_CHUNK_FUTURE; ++ public CompletableFuture<Either<ChunkAccess, ChunkHolder.Failure>> getFutureIfPresent(ChunkStatus chunkStatus) { ++ return ChunkLevel.generationStatus(this.ticketLevel).isOrAfter(chunkStatus) ? this.getFutureIfPresentUnchecked(chunkStatus) : ChunkHolder.UNLOADED_CHUNK_FUTURE; + } + +- public CompletableFuture<Either<LevelChunk, ChunkHolder.ChunkLoadingFailure>> getTickingChunkFuture() { ++ public CompletableFuture<Either<LevelChunk, ChunkHolder.Failure>> getTickingChunkFuture() { + return this.tickingChunkFuture; + } + +- public CompletableFuture<Either<LevelChunk, ChunkHolder.ChunkLoadingFailure>> getEntityTickingChunkFuture() { ++ public CompletableFuture<Either<LevelChunk, ChunkHolder.Failure>> getEntityTickingChunkFuture() { + return this.entityTickingChunkFuture; + } + +- public CompletableFuture<Either<LevelChunk, ChunkHolder.ChunkLoadingFailure>> getFullChunkFuture() { ++ public CompletableFuture<Either<LevelChunk, ChunkHolder.Failure>> getFullChunkFuture() { + return this.fullChunkFuture; + } + + @Nullable + public LevelChunk getTickingChunk() { +- CompletableFuture<Either<LevelChunk, ChunkHolder.ChunkLoadingFailure>> completablefuture = this.getTickingChunkFuture(); +- Either<LevelChunk, ChunkHolder.ChunkLoadingFailure> either = (Either) completablefuture.getNow((Object) null); ++ CompletableFuture<Either<LevelChunk, ChunkHolder.Failure>> completablefuture = this.getTickingChunkFuture(); ++ Either<LevelChunk, ChunkHolder.Failure> either = (Either) completablefuture.getNow(null); // CraftBukkit - decompile error + +- return either == null ? null : (LevelChunk) either.left().orElse((Object) null); ++ return either == null ? null : (LevelChunk) either.left().orElse(null); // CraftBukkit - decompile error + } + + public CompletableFuture<?> getChunkSendSyncFuture() { +@@ -131,17 +149,17 @@ + + @Nullable + public LevelChunk getFullChunk() { +- CompletableFuture<Either<LevelChunk, ChunkHolder.ChunkLoadingFailure>> completablefuture = this.getFullChunkFuture(); +- Either<LevelChunk, ChunkHolder.ChunkLoadingFailure> either = (Either) completablefuture.getNow((Object) null); ++ CompletableFuture<Either<LevelChunk, ChunkHolder.Failure>> completablefuture = this.getFullChunkFuture(); ++ Either<LevelChunk, ChunkHolder.Failure> either = (Either) completablefuture.getNow(null); // CraftBukkit - decompile error + +- return either == null ? null : (LevelChunk) either.left().orElse((Object) null); ++ return either == null ? null : (LevelChunk) either.left().orElse(null); // CraftBukkit - decompile error + } + + @Nullable + public ChunkStatus getLastAvailableStatus() { + for (int i = ChunkHolder.CHUNK_STATUSES.size() - 1; i >= 0; --i) { + ChunkStatus chunkstatus = (ChunkStatus) ChunkHolder.CHUNK_STATUSES.get(i); +- CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> completablefuture = this.getFutureIfPresentUnchecked(chunkstatus); ++ CompletableFuture<Either<ChunkAccess, ChunkHolder.Failure>> completablefuture = this.getFutureIfPresentUnchecked(chunkstatus); + + if (((Either) completablefuture.getNow(ChunkHolder.UNLOADED_CHUNK)).left().isPresent()) { + return chunkstatus; +@@ -155,7 +173,7 @@ + public ChunkAccess getLastAvailable() { + for (int i = ChunkHolder.CHUNK_STATUSES.size() - 1; i >= 0; --i) { + ChunkStatus chunkstatus = (ChunkStatus) ChunkHolder.CHUNK_STATUSES.get(i); +- CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> completablefuture = this.getFutureIfPresentUnchecked(chunkstatus); ++ CompletableFuture<Either<ChunkAccess, ChunkHolder.Failure>> completablefuture = this.getFutureIfPresentUnchecked(chunkstatus); + + if (!completablefuture.isCompletedExceptionally()) { + Optional<ChunkAccess> optional = ((Either) completablefuture.getNow(ChunkHolder.UNLOADED_CHUNK)).left(); +@@ -173,39 +191,40 @@ + return this.chunkToSave; + } + +- public void blockChanged(BlockPos blockpos) { +- LevelChunk levelchunk = this.getTickingChunk(); ++ public void blockChanged(BlockPos pos) { ++ LevelChunk chunk = this.getTickingChunk(); + +- if (levelchunk != null) { +- int i = this.levelHeightAccessor.getSectionIndex(blockpos.getY()); ++ if (chunk != null) { ++ int i = this.levelHeightAccessor.getSectionIndex(pos.getY()); + ++ if (i < 0 || i >= this.changedBlocksPerSection.length) return; // CraftBukkit - SPIGOT-6086, SPIGOT-6296 + if (this.changedBlocksPerSection[i] == null) { + this.hasChangedSections = true; + this.changedBlocksPerSection[i] = new ShortOpenHashSet(); + } + +- this.changedBlocksPerSection[i].add(SectionPos.sectionRelativePos(blockpos)); ++ this.changedBlocksPerSection[i].add(SectionPos.sectionRelativePos(pos)); + } + } + +- public void sectionLightChanged(LightLayer lightlayer, int i) { +- Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure> either = (Either) this.getFutureIfPresent(ChunkStatus.INITIALIZE_LIGHT).getNow((Object) null); ++ public void sectionLightChanged(EnumSkyBlock type, int sectionY) { ++ Either<ChunkAccess, ChunkHolder.Failure> either = (Either) this.getFutureIfPresent(ChunkStatus.INITIALIZE_LIGHT).getNow(null); // CraftBukkit - decompile error + + if (either != null) { +- ChunkAccess chunkaccess = (ChunkAccess) either.left().orElse((Object) null); ++ ChunkAccess ichunkaccess = (ChunkAccess) either.left().orElse(null); // CraftBukkit - decompile error + +- if (chunkaccess != null) { +- chunkaccess.setUnsaved(true); +- LevelChunk levelchunk = this.getTickingChunk(); ++ if (ichunkaccess != null) { ++ ichunkaccess.setUnsaved(true); ++ LevelChunk chunk = this.getTickingChunk(); + +- if (levelchunk != null) { ++ if (chunk != null) { + int j = this.lightEngine.getMinLightSection(); + int k = this.lightEngine.getMaxLightSection(); + +- if (i >= j && i <= k) { +- int l = i - j; ++ if (sectionY >= j && sectionY <= k) { ++ int l = sectionY - j; + +- if (lightlayer == LightLayer.SKY) { ++ if (type == EnumSkyBlock.SKY) { + this.skyChangedLightSectionFilter.set(l); + } else { + this.blockChangedLightSectionFilter.set(l); +@@ -217,17 +236,17 @@ + } + } + +- public void broadcastChanges(LevelChunk levelchunk) { ++ public void broadcastChanges(LevelChunk chunk) { + if (this.hasChangedSections || !this.skyChangedLightSectionFilter.isEmpty() || !this.blockChangedLightSectionFilter.isEmpty()) { +- Level level = levelchunk.getLevel(); ++ Level world = chunk.getLevel(); + List list; + + if (!this.skyChangedLightSectionFilter.isEmpty() || !this.blockChangedLightSectionFilter.isEmpty()) { + list = this.playerProvider.getPlayers(this.pos, true); + if (!list.isEmpty()) { +- ClientboundLightUpdatePacket clientboundlightupdatepacket = new ClientboundLightUpdatePacket(levelchunk.getPos(), this.lightEngine, this.skyChangedLightSectionFilter, this.blockChangedLightSectionFilter); ++ ClientboundLightUpdatePacket packetplayoutlightupdate = new ClientboundLightUpdatePacket(chunk.getPos(), this.lightEngine, this.skyChangedLightSectionFilter, this.blockChangedLightSectionFilter); + +- this.broadcast(list, clientboundlightupdatepacket); ++ this.broadcast(list, packetplayoutlightupdate); + } + + this.skyChangedLightSectionFilter.clear(); +@@ -244,21 +263,24 @@ + this.changedBlocksPerSection[i] = null; + if (!list.isEmpty()) { + int j = this.levelHeightAccessor.getSectionYFromSectionIndex(i); +- SectionPos sectionpos = SectionPos.of(levelchunk.getPos(), j); ++ SectionPos sectionposition = SectionPos.of(chunk.getPos(), j); + + if (shortset.size() == 1) { +- BlockPos blockpos = sectionpos.relativeToBlockPos(shortset.iterator().nextShort()); +- BlockState blockstate = level.getBlockState(blockpos); ++ BlockPos blockposition = sectionposition.relativeToBlockPos(shortset.iterator().nextShort()); ++ IBlockData iblockdata = world.getBlockState(blockposition); + +- this.broadcast(list, new ClientboundBlockUpdatePacket(blockpos, blockstate)); +- this.broadcastBlockEntityIfNeeded(list, level, blockpos, blockstate); ++ this.broadcast(list, new ClientboundBlockUpdatePacket(blockposition, iblockdata)); ++ this.broadcastBlockEntityIfNeeded(list, world, blockposition, iblockdata); + } else { +- LevelChunkSection levelchunksection = levelchunk.getSection(i); +- ClientboundSectionBlocksUpdatePacket clientboundsectionblocksupdatepacket = new ClientboundSectionBlocksUpdatePacket(sectionpos, shortset, levelchunksection); ++ LevelChunkSection chunksection = chunk.getSection(i); ++ ClientboundSectionBlocksUpdatePacket packetplayoutmultiblockchange = new ClientboundSectionBlocksUpdatePacket(sectionposition, shortset, chunksection); + +- this.broadcast(list, clientboundsectionblocksupdatepacket); +- clientboundsectionblocksupdatepacket.runUpdates((blockpos1, blockstate1) -> { +- this.broadcastBlockEntityIfNeeded(list, level, blockpos1, blockstate1); ++ this.broadcast(list, packetplayoutmultiblockchange); ++ // CraftBukkit start ++ List finalList = list; ++ packetplayoutmultiblockchange.runUpdates((blockposition1, iblockdata1) -> { ++ this.broadcastBlockEntityIfNeeded(finalList, world, blockposition1, iblockdata1); ++ // CraftBukkit end + }); + } + } +@@ -270,43 +292,43 @@ + } + } + +- private void broadcastBlockEntityIfNeeded(List<ServerPlayer> list, Level level, BlockPos blockpos, BlockState blockstate) { +- if (blockstate.hasBlockEntity()) { +- this.broadcastBlockEntity(list, level, blockpos); ++ private void broadcastBlockEntityIfNeeded(List<ServerPlayer> players, Level level, BlockPos pos, IBlockData state) { ++ if (state.hasBlockEntity()) { ++ this.broadcastBlockEntity(players, level, pos); + } + + } + +- private void broadcastBlockEntity(List<ServerPlayer> list, Level level, BlockPos blockpos) { +- BlockEntity blockentity = level.getBlockEntity(blockpos); ++ private void broadcastBlockEntity(List<ServerPlayer> players, Level level, BlockPos pox) { ++ BlockEntity tileentity = level.getBlockEntity(pox); + +- if (blockentity != null) { +- Packet<?> packet = blockentity.getUpdatePacket(); ++ if (tileentity != null) { ++ Packet<?> packet = tileentity.getUpdatePacket(); + + if (packet != null) { +- this.broadcast(list, packet); ++ this.broadcast(players, packet); + } + } + + } + +- private void broadcast(List<ServerPlayer> list, Packet<?> packet) { +- list.forEach((serverplayer) -> { +- serverplayer.connection.send(packet); ++ private void broadcast(List<ServerPlayer> players, Packet<?> packet) { ++ players.forEach((entityplayer) -> { ++ entityplayer.connection.send(packet); + }); + } + +- public CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> getOrScheduleFuture(ChunkStatus chunkstatus, ChunkMap chunkmap) { +- int i = chunkstatus.getIndex(); +- CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> completablefuture = (CompletableFuture) this.futures.get(i); ++ public CompletableFuture<Either<ChunkAccess, ChunkHolder.Failure>> getOrScheduleFuture(ChunkStatus status, ChunkMap map) { ++ int i = status.getIndex(); ++ CompletableFuture<Either<ChunkAccess, ChunkHolder.Failure>> completablefuture = (CompletableFuture) this.futures.get(i); + + if (completablefuture != null) { +- Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure> either = (Either) completablefuture.getNow(ChunkHolder.NOT_DONE_YET); ++ Either<ChunkAccess, ChunkHolder.Failure> either = (Either) completablefuture.getNow(ChunkHolder.NOT_DONE_YET); + + if (either == null) { +- String s = "value in future for status: " + chunkstatus + " was incorrectly set to null at chunk: " + this.pos; ++ String s = "value in future for status: " + status + " was incorrectly set to null at chunk: " + this.pos; + +- throw chunkmap.debugFuturesAndCreateReportedException(new IllegalStateException("null value previously set for chunk status"), s); ++ throw map.debugFuturesAndCreateReportedException(new IllegalStateException("null value previously set for chunk status"), s); + } + + if (either == ChunkHolder.NOT_DONE_YET || either.right().isEmpty()) { +@@ -314,10 +336,10 @@ + } + } + +- if (ChunkLevel.generationStatus(this.ticketLevel).isOrAfter(chunkstatus)) { +- CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> completablefuture1 = chunkmap.schedule(this, chunkstatus); ++ if (ChunkLevel.generationStatus(this.ticketLevel).isOrAfter(status)) { ++ CompletableFuture<Either<ChunkAccess, ChunkHolder.Failure>> completablefuture1 = map.schedule(this, status); + +- this.updateChunkToSave(completablefuture1, "schedule " + chunkstatus); ++ this.updateChunkToSave(completablefuture1, "schedule " + status); + this.futures.set(i, completablefuture1); + return completablefuture1; + } else { +@@ -325,26 +347,26 @@ + } + } + +- protected void addSaveDependency(String s, CompletableFuture<?> completablefuture) { ++ protected void addSaveDependency(String source, CompletableFuture<?> future) { + if (this.chunkToSaveHistory != null) { +- this.chunkToSaveHistory.push(new ChunkHolder.ChunkSaveDebug(Thread.currentThread(), completablefuture, s)); ++ this.chunkToSaveHistory.push(new ChunkHolder.ChunkSaveDebug(Thread.currentThread(), future, source)); + } + +- this.chunkToSave = this.chunkToSave.thenCombine(completablefuture, (chunkaccess, object) -> { +- return chunkaccess; ++ this.chunkToSave = this.chunkToSave.thenCombine(future, (ichunkaccess, object) -> { ++ return ichunkaccess; + }); + } + +- private void updateChunkToSave(CompletableFuture<? extends Either<? extends ChunkAccess, ChunkHolder.ChunkLoadingFailure>> completablefuture, String s) { ++ private void updateChunkToSave(CompletableFuture<? extends Either<? extends ChunkAccess, ChunkHolder.Failure>> feature, String source) { + if (this.chunkToSaveHistory != null) { +- this.chunkToSaveHistory.push(new ChunkHolder.ChunkSaveDebug(Thread.currentThread(), completablefuture, s)); ++ this.chunkToSaveHistory.push(new ChunkHolder.ChunkSaveDebug(Thread.currentThread(), feature, source)); + } + +- this.chunkToSave = this.chunkToSave.thenCombine(completablefuture, (chunkaccess, either) -> { +- return (ChunkAccess) either.map((chunkaccess1) -> { +- return chunkaccess1; +- }, (chunkholder_chunkloadingfailure) -> { +- return chunkaccess; ++ this.chunkToSave = this.chunkToSave.thenCombine(feature, (ichunkaccess, either) -> { ++ return (ChunkAccess) either.map((ichunkaccess1) -> { ++ return ichunkaccess1; ++ }, (playerchunk_failure) -> { ++ return ichunkaccess; + }); + }); + } +@@ -376,52 +398,75 @@ + return this.queueLevel; + } + +- private void setQueueLevel(int i) { +- this.queueLevel = i; ++ private void setQueueLevel(int queueLevel) { ++ this.queueLevel = queueLevel; + } + +- public void setTicketLevel(int i) { +- this.ticketLevel = i; ++ public void setTicketLevel(int level) { ++ this.ticketLevel = level; + } + +- private void scheduleFullChunkPromotion(ChunkMap chunkmap, CompletableFuture<Either<LevelChunk, ChunkHolder.ChunkLoadingFailure>> completablefuture, Executor executor, FullChunkStatus fullchunkstatus) { ++ private void scheduleFullChunkPromotion(ChunkMap chunkMap, CompletableFuture<Either<LevelChunk, ChunkHolder.Failure>> future, Executor executor, FullChunkStatus fullChunkStatus) { + this.pendingFullStateConfirmation.cancel(false); + CompletableFuture<Void> completablefuture1 = new CompletableFuture(); + + completablefuture1.thenRunAsync(() -> { +- chunkmap.onFullChunkStatusChange(this.pos, fullchunkstatus); ++ chunkMap.onFullChunkStatusChange(this.pos, fullChunkStatus); + }, executor); + this.pendingFullStateConfirmation = completablefuture1; +- completablefuture.thenAccept((either) -> { +- either.ifLeft((levelchunk) -> { +- completablefuture1.complete((Object) null); ++ future.thenAccept((either) -> { ++ either.ifLeft((chunk) -> { ++ completablefuture1.complete(null); // CraftBukkit - decompile error + }); + }); + } + +- private void demoteFullChunk(ChunkMap chunkmap, FullChunkStatus fullchunkstatus) { ++ private void demoteFullChunk(ChunkMap chunkMap, FullChunkStatus fullChunkStatus) { + this.pendingFullStateConfirmation.cancel(false); +- chunkmap.onFullChunkStatusChange(this.pos, fullchunkstatus); ++ chunkMap.onFullChunkStatusChange(this.pos, fullChunkStatus); + } + +- protected void updateFutures(ChunkMap chunkmap, Executor executor) { ++ protected void updateFutures(ChunkMap chunkMap, Executor executor) { + ChunkStatus chunkstatus = ChunkLevel.generationStatus(this.oldTicketLevel); + ChunkStatus chunkstatus1 = ChunkLevel.generationStatus(this.ticketLevel); + boolean flag = ChunkLevel.isLoaded(this.oldTicketLevel); + boolean flag1 = ChunkLevel.isLoaded(this.ticketLevel); + FullChunkStatus fullchunkstatus = ChunkLevel.fullStatus(this.oldTicketLevel); + FullChunkStatus fullchunkstatus1 = ChunkLevel.fullStatus(this.ticketLevel); ++ // CraftBukkit start ++ // ChunkUnloadEvent: Called before the chunk is unloaded: isChunkLoaded is still true and chunk can still be modified by plugins. ++ if (fullchunkstatus.isOrAfter(FullChunkStatus.FULL) && !fullchunkstatus1.isOrAfter(FullChunkStatus.FULL)) { ++ this.getFutureIfPresentUnchecked(ChunkStatus.FULL).thenAccept((either) -> { ++ LevelChunk chunk = (LevelChunk)either.left().orElse(null); ++ if (chunk != null) { ++ chunkMap.callbackExecutor.execute(() -> { ++ // Minecraft will apply the chunks tick lists to the world once the chunk got loaded, and then store the tick ++ // lists again inside the chunk once the chunk becomes inaccessible and set the chunk's needsSaving flag. ++ // These actions may however happen deferred, so we manually set the needsSaving flag already here. ++ chunk.setUnsaved(true); ++ chunk.unloadCallback(); ++ }); ++ } ++ }).exceptionally((throwable) -> { ++ // ensure exceptions are printed, by default this is not the case ++ MinecraftServer.LOGGER.error("Failed to schedule unload callback for chunk " + ChunkHolder.this.pos, throwable); ++ return null; ++ }); + ++ // Run callback right away if the future was already done ++ chunkMap.callbackExecutor.run(); ++ } ++ // CraftBukkit end ++ + if (flag) { +- Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure> either = Either.right(new ChunkHolder.ChunkLoadingFailure() { +- @Override ++ Either<ChunkAccess, ChunkHolder.Failure> either = Either.right(new ChunkHolder.Failure() { + public String toString() { + return "Unloaded ticket level " + ChunkHolder.this.pos; + } + }); + + for (int i = flag1 ? chunkstatus1.getIndex() + 1 : 0; i <= chunkstatus.getIndex(); ++i) { +- CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> completablefuture = (CompletableFuture) this.futures.get(i); ++ CompletableFuture<Either<ChunkAccess, ChunkHolder.Failure>> completablefuture = (CompletableFuture) this.futures.get(i); + + if (completablefuture == null) { + this.futures.set(i, CompletableFuture.completedFuture(either)); +@@ -434,8 +479,8 @@ + + this.wasAccessibleSinceLastSave |= flag3; + if (!flag2 && flag3) { +- this.fullChunkFuture = chunkmap.prepareAccessibleChunk(this); +- this.scheduleFullChunkPromotion(chunkmap, this.fullChunkFuture, executor, FullChunkStatus.FULL); ++ this.fullChunkFuture = chunkMap.prepareAccessibleChunk(this); ++ this.scheduleFullChunkPromotion(chunkMap, this.fullChunkFuture, executor, FullChunkStatus.FULL); + this.updateChunkToSave(this.fullChunkFuture, "full"); + } + +@@ -448,8 +493,8 @@ + boolean flag5 = fullchunkstatus1.isOrAfter(FullChunkStatus.BLOCK_TICKING); + + if (!flag4 && flag5) { +- this.tickingChunkFuture = chunkmap.prepareTickingChunk(this); +- this.scheduleFullChunkPromotion(chunkmap, this.tickingChunkFuture, executor, FullChunkStatus.BLOCK_TICKING); ++ this.tickingChunkFuture = chunkMap.prepareTickingChunk(this); ++ this.scheduleFullChunkPromotion(chunkMap, this.tickingChunkFuture, executor, FullChunkStatus.BLOCK_TICKING); + this.updateChunkToSave(this.tickingChunkFuture, "ticking"); + } + +@@ -466,8 +511,8 @@ + throw (IllegalStateException) Util.pauseInIde(new IllegalStateException()); + } + +- this.entityTickingChunkFuture = chunkmap.prepareEntityTickingChunk(this); +- this.scheduleFullChunkPromotion(chunkmap, this.entityTickingChunkFuture, executor, FullChunkStatus.ENTITY_TICKING); ++ this.entityTickingChunkFuture = chunkMap.prepareEntityTickingChunk(this); ++ this.scheduleFullChunkPromotion(chunkMap, this.entityTickingChunkFuture, executor, FullChunkStatus.ENTITY_TICKING); + this.updateChunkToSave(this.entityTickingChunkFuture, "entity ticking"); + } + +@@ -477,11 +522,31 @@ + } + + if (!fullchunkstatus1.isOrAfter(fullchunkstatus)) { +- this.demoteFullChunk(chunkmap, fullchunkstatus1); ++ this.demoteFullChunk(chunkMap, fullchunkstatus1); + } + + this.onLevelChange.onLevelChange(this.pos, this::getQueueLevel, this.ticketLevel, this::setQueueLevel); + this.oldTicketLevel = this.ticketLevel; ++ // CraftBukkit start ++ // ChunkLoadEvent: Called after the chunk is loaded: isChunkLoaded returns true and chunk is ready to be modified by plugins. ++ if (!fullchunkstatus.isOrAfter(FullChunkStatus.FULL) && fullchunkstatus1.isOrAfter(FullChunkStatus.FULL)) { ++ this.getFutureIfPresentUnchecked(ChunkStatus.FULL).thenAccept((either) -> { ++ LevelChunk chunk = (LevelChunk)either.left().orElse(null); ++ if (chunk != null) { ++ chunkMap.callbackExecutor.execute(() -> { ++ chunk.loadCallback(); ++ }); ++ } ++ }).exceptionally((throwable) -> { ++ // ensure exceptions are printed, by default this is not the case ++ MinecraftServer.LOGGER.error("Failed to schedule load callback for chunk " + ChunkHolder.this.pos, throwable); ++ return null; ++ }); ++ ++ // Run callback right away if the future was already done ++ chunkMap.callbackExecutor.run(); ++ } ++ // CraftBukkit end + } + + public boolean wasAccessibleSinceLastSave() { +@@ -492,24 +557,24 @@ + this.wasAccessibleSinceLastSave = ChunkLevel.fullStatus(this.ticketLevel).isOrAfter(FullChunkStatus.FULL); + } + +- public void replaceProtoChunk(ImposterProtoChunk imposterprotochunk) { ++ public void replaceProtoChunk(ImposterProtoChunk imposter) { + for (int i = 0; i < this.futures.length(); ++i) { +- CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> completablefuture = (CompletableFuture) this.futures.get(i); ++ CompletableFuture<Either<ChunkAccess, ChunkHolder.Failure>> completablefuture = (CompletableFuture) this.futures.get(i); + + if (completablefuture != null) { + Optional<ChunkAccess> optional = ((Either) completablefuture.getNow(ChunkHolder.UNLOADED_CHUNK)).left(); + + if (!optional.isEmpty() && optional.get() instanceof ProtoChunk) { +- this.futures.set(i, CompletableFuture.completedFuture(Either.left(imposterprotochunk))); ++ this.futures.set(i, CompletableFuture.completedFuture(Either.left(imposter))); + } + } + } + +- this.updateChunkToSave(CompletableFuture.completedFuture(Either.left(imposterprotochunk.getWrapped())), "replaceProto"); ++ this.updateChunkToSave(CompletableFuture.completedFuture(Either.left(imposter.getWrapped())), "replaceProto"); + } + +- public List<Pair<ChunkStatus, CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>>>> getAllFutures() { +- List<Pair<ChunkStatus, CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>>>> list = new ArrayList(); ++ public List<Pair<ChunkStatus, CompletableFuture<Either<ChunkAccess, ChunkHolder.Failure>>>> getAllFutures() { ++ List<Pair<ChunkStatus, CompletableFuture<Either<ChunkAccess, ChunkHolder.Failure>>>> list = new ArrayList(); + + for (int i = 0; i < ChunkHolder.CHUNK_STATUSES.size(); ++i) { + list.add(Pair.of((ChunkStatus) ChunkHolder.CHUNK_STATUSES.get(i), (CompletableFuture) this.futures.get(i))); +@@ -521,7 +586,7 @@ + @FunctionalInterface + public interface LevelChangeListener { + +- void onLevelChange(ChunkPos chunkPos, IntSupplier intSupplier, int i, IntConsumer intConsumer); ++ void onLevelChange(ChunkPos chunkPos, IntSupplier intsupplier, int i, IntConsumer intconsumer); + } + + public interface PlayerProvider { +@@ -535,17 +600,16 @@ + private final CompletableFuture<?> future; + private final String source; + +- ChunkSaveDebug(Thread thread, CompletableFuture<?> completablefuture, String s) { ++ ChunkSaveDebug(Thread thread, CompletableFuture<?> future, String source) { + this.thread = thread; +- this.future = completablefuture; +- this.source = s; ++ this.future = future; ++ this.source = source; + } + } + +- public interface ChunkLoadingFailure { ++ public interface Failure { + +- ChunkHolder.ChunkLoadingFailure UNLOADED = new ChunkHolder.ChunkLoadingFailure() { +- @Override ++ ChunkHolder.Failure UNLOADED = new ChunkHolder.Failure() { + public String toString() { + return "UNLOADED"; + } |