aboutsummaryrefslogtreecommitdiffhomepage
path: root/patch-remap/mache-vineflower/net/minecraft/server/level/ChunkHolder.java.patch
diff options
context:
space:
mode:
Diffstat (limited to 'patch-remap/mache-vineflower/net/minecraft/server/level/ChunkHolder.java.patch')
-rw-r--r--patch-remap/mache-vineflower/net/minecraft/server/level/ChunkHolder.java.patch753
1 files changed, 753 insertions, 0 deletions
diff --git a/patch-remap/mache-vineflower/net/minecraft/server/level/ChunkHolder.java.patch b/patch-remap/mache-vineflower/net/minecraft/server/level/ChunkHolder.java.patch
new file mode 100644
index 0000000000..96da1be143
--- /dev/null
+++ b/patch-remap/mache-vineflower/net/minecraft/server/level/ChunkHolder.java.patch
@@ -0,0 +1,753 @@
+--- a/net/minecraft/server/level/ChunkHolder.java
++++ b/net/minecraft/server/level/ChunkHolder.java
+@@ -9,7 +9,6 @@
+ import java.util.List;
+ import java.util.Optional;
+ import java.util.concurrent.CompletableFuture;
+-import java.util.concurrent.CompletionStage;
+ import java.util.concurrent.Executor;
+ import java.util.concurrent.atomic.AtomicReferenceArray;
+ import java.util.function.IntConsumer;
+@@ -24,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;
+@@ -37,50 +36,52 @@
+ 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(
+- 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(
+- 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 = new AtomicReferenceArray<>(
+- CHUNK_STATUSES.size()
+- );
++ private final AtomicReferenceArray<CompletableFuture<Either<ChunkAccess, ChunkHolder.Failure>>> futures;
+ private final LevelHeightAccessor levelHeightAccessor;
+- private volatile CompletableFuture<Either<LevelChunk, ChunkHolder.ChunkLoadingFailure>> fullChunkFuture = UNLOADED_LEVEL_CHUNK_FUTURE;
+- private volatile CompletableFuture<Either<LevelChunk, ChunkHolder.ChunkLoadingFailure>> tickingChunkFuture = UNLOADED_LEVEL_CHUNK_FUTURE;
+- private volatile CompletableFuture<Either<LevelChunk, ChunkHolder.ChunkLoadingFailure>> entityTickingChunkFuture = UNLOADED_LEVEL_CHUNK_FUTURE;
+- private CompletableFuture<ChunkAccess> chunkToSave = CompletableFuture.completedFuture(null);
++ 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 = null;
+- private int oldTicketLevel;
++ private final DebugBuffer<ChunkHolder.ChunkSaveDebug> chunkToSaveHistory;
++ public int oldTicketLevel;
+ private int ticketLevel;
+ private int queueLevel;
+ final ChunkPos pos;
+ private boolean hasChangedSections;
+ private final ShortSet[] changedBlocksPerSection;
+- private final BitSet blockChangedLightSectionFilter = new BitSet();
+- private final BitSet skyChangedLightSectionFilter = new BitSet();
++ private final BitSet blockChangedLightSectionFilter;
++ 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 = CompletableFuture.completedFuture(null);
+- private CompletableFuture<?> sendSync = CompletableFuture.completedFuture(null);
++ private CompletableFuture<Void> pendingFullStateConfirmation;
++ private CompletableFuture<?> sendSync;
+
+- public ChunkHolder(
+- ChunkPos pos,
+- int ticketLevel,
+- LevelHeightAccessor levelHeightAccessor,
+- LevelLightEngine lightEngine,
+- ChunkHolder.LevelChangeListener onLevelChange,
+- ChunkHolder.PlayerProvider 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(null); // CraftBukkit - decompile error
++ this.chunkToSaveHistory = null;
++ this.blockChangedLightSectionFilter = new BitSet();
++ this.skyChangedLightSectionFilter = new BitSet();
++ 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;
+@@ -93,32 +94,48 @@
+ this.changedBlocksPerSection = new ShortSet[levelHeightAccessor.getSectionsCount()];
+ }
+
+- public CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> getFutureIfPresentUnchecked(ChunkStatus chunkStatus) {
+- CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> completableFuture = this.futures.get(chunkStatus.getIndex());
+- return completableFuture == null ? UNLOADED_CHUNK_FUTURE : completableFuture;
++ // 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 CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> getFutureIfPresent(ChunkStatus chunkStatus) {
+- return ChunkLevel.generationStatus(this.ticketLevel).isOrAfter(chunkStatus) ? this.getFutureIfPresentUnchecked(chunkStatus) : UNLOADED_CHUNK_FUTURE;
++ 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<LevelChunk, ChunkHolder.ChunkLoadingFailure>> getTickingChunkFuture() {
++ 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.Failure>> getFutureIfPresent(ChunkStatus chunkStatus) {
++ return ChunkLevel.generationStatus(this.ticketLevel).isOrAfter(chunkStatus) ? this.getFutureIfPresentUnchecked(chunkStatus) : ChunkHolder.UNLOADED_CHUNK_FUTURE;
++ }
++
++ 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>> tickingChunkFuture = this.getTickingChunkFuture();
+- Either<LevelChunk, ChunkHolder.ChunkLoadingFailure> either = tickingChunkFuture.getNow(null);
+- return either == null ? null : either.left().orElse(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(null); // CraftBukkit - decompile error
+ }
+
+ public CompletableFuture<?> getChunkSendSyncFuture() {
+@@ -132,18 +149,20 @@
+
+ @Nullable
+ public LevelChunk getFullChunk() {
+- CompletableFuture<Either<LevelChunk, ChunkHolder.ChunkLoadingFailure>> fullChunkFuture = this.getFullChunkFuture();
+- Either<LevelChunk, ChunkHolder.ChunkLoadingFailure> either = fullChunkFuture.getNow(null);
+- return either == null ? null : either.left().orElse(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(null); // CraftBukkit - decompile error
+ }
+
+ @Nullable
+ public ChunkStatus getLastAvailableStatus() {
+- for (int i = CHUNK_STATUSES.size() - 1; i >= 0; i--) {
+- ChunkStatus chunkStatus = CHUNK_STATUSES.get(i);
+- CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> futureIfPresentUnchecked = this.getFutureIfPresentUnchecked(chunkStatus);
+- if (futureIfPresentUnchecked.getNow(UNLOADED_CHUNK).left().isPresent()) {
+- return chunkStatus;
++ for (int i = ChunkHolder.CHUNK_STATUSES.size() - 1; i >= 0; --i) {
++ ChunkStatus chunkstatus = (ChunkStatus) ChunkHolder.CHUNK_STATUSES.get(i);
++ CompletableFuture<Either<ChunkAccess, ChunkHolder.Failure>> completablefuture = this.getFutureIfPresentUnchecked(chunkstatus);
++
++ if (((Either) completablefuture.getNow(ChunkHolder.UNLOADED_CHUNK)).left().isPresent()) {
++ return chunkstatus;
+ }
+ }
+
+@@ -152,13 +171,15 @@
+
+ @Nullable
+ public ChunkAccess getLastAvailable() {
+- for (int i = CHUNK_STATUSES.size() - 1; i >= 0; i--) {
+- ChunkStatus chunkStatus = CHUNK_STATUSES.get(i);
+- CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> futureIfPresentUnchecked = this.getFutureIfPresentUnchecked(chunkStatus);
+- if (!futureIfPresentUnchecked.isCompletedExceptionally()) {
+- Optional<ChunkAccess> optional = futureIfPresentUnchecked.getNow(UNLOADED_CHUNK).left();
++ for (int i = ChunkHolder.CHUNK_STATUSES.size() - 1; i >= 0; --i) {
++ ChunkStatus chunkstatus = (ChunkStatus) ChunkHolder.CHUNK_STATUSES.get(i);
++ CompletableFuture<Either<ChunkAccess, ChunkHolder.Failure>> completablefuture = this.getFutureIfPresentUnchecked(chunkstatus);
++
++ if (!completablefuture.isCompletedExceptionally()) {
++ Optional<ChunkAccess> optional = ((Either) completablefuture.getNow(ChunkHolder.UNLOADED_CHUNK)).left();
++
+ if (optional.isPresent()) {
+- return optional.get();
++ return (ChunkAccess) optional.get();
+ }
+ }
+ }
+@@ -171,35 +192,44 @@
+ }
+
+ public void blockChanged(BlockPos pos) {
+- LevelChunk tickingChunk = this.getTickingChunk();
+- if (tickingChunk != null) {
+- int sectionIndex = this.levelHeightAccessor.getSectionIndex(pos.getY());
+- if (this.changedBlocksPerSection[sectionIndex] == null) {
++ LevelChunk chunk = this.getTickingChunk();
++
++ 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[sectionIndex] = new ShortOpenHashSet();
++ this.changedBlocksPerSection[i] = new ShortOpenHashSet();
+ }
+
+- this.changedBlocksPerSection[sectionIndex].add(SectionPos.sectionRelativePos(pos));
++ this.changedBlocksPerSection[i].add(SectionPos.sectionRelativePos(pos));
+ }
+ }
+
+- public void sectionLightChanged(LightLayer type, int sectionY) {
+- Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure> either = this.getFutureIfPresent(ChunkStatus.INITIALIZE_LIGHT).getNow(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 = either.left().orElse(null);
+- if (chunkAccess != null) {
+- chunkAccess.setUnsaved(true);
+- LevelChunk tickingChunk = this.getTickingChunk();
+- if (tickingChunk != null) {
+- int minLightSection = this.lightEngine.getMinLightSection();
+- int maxLightSection = this.lightEngine.getMaxLightSection();
+- if (sectionY >= minLightSection && sectionY <= maxLightSection) {
+- int i = sectionY - minLightSection;
+- if (type == LightLayer.SKY) {
+- this.skyChangedLightSectionFilter.set(i);
++ ChunkAccess ichunkaccess = (ChunkAccess) either.left().orElse(null); // CraftBukkit - decompile error
++
++ if (ichunkaccess != null) {
++ ichunkaccess.setUnsaved(true);
++ LevelChunk chunk = this.getTickingChunk();
++
++ if (chunk != null) {
++ int j = this.lightEngine.getMinLightSection();
++ int k = this.lightEngine.getMaxLightSection();
++
++ if (sectionY >= j && sectionY <= k) {
++ int l = sectionY - j;
++
++ if (type == EnumSkyBlock.SKY) {
++ this.skyChangedLightSectionFilter.set(l);
+ } else {
+- this.blockChangedLightSectionFilter.set(i);
++ this.blockChangedLightSectionFilter.set(l);
+ }
++
+ }
+ }
+ }
+@@ -208,14 +238,15 @@
+
+ public void broadcastChanges(LevelChunk chunk) {
+ if (this.hasChangedSections || !this.skyChangedLightSectionFilter.isEmpty() || !this.blockChangedLightSectionFilter.isEmpty()) {
+- Level level = chunk.getLevel();
++ Level world = chunk.getLevel();
++ List list;
++
+ if (!this.skyChangedLightSectionFilter.isEmpty() || !this.blockChangedLightSectionFilter.isEmpty()) {
+- List<ServerPlayer> players = this.playerProvider.getPlayers(this.pos, true);
+- if (!players.isEmpty()) {
+- ClientboundLightUpdatePacket clientboundLightUpdatePacket = new ClientboundLightUpdatePacket(
+- chunk.getPos(), this.lightEngine, this.skyChangedLightSectionFilter, this.blockChangedLightSectionFilter
+- );
+- this.broadcast(players, clientboundLightUpdatePacket);
++ list = this.playerProvider.getPlayers(this.pos, true);
++ if (!list.isEmpty()) {
++ ClientboundLightUpdatePacket packetplayoutlightupdate = new ClientboundLightUpdatePacket(chunk.getPos(), this.lightEngine, this.skyChangedLightSectionFilter, this.blockChangedLightSectionFilter);
++
++ this.broadcast(list, packetplayoutlightupdate);
+ }
+
+ this.skyChangedLightSectionFilter.clear();
+@@ -223,29 +254,34 @@
+ }
+
+ if (this.hasChangedSections) {
+- List<ServerPlayer> players = this.playerProvider.getPlayers(this.pos, false);
++ list = this.playerProvider.getPlayers(this.pos, false);
+
+- for (int i = 0; i < this.changedBlocksPerSection.length; i++) {
+- ShortSet set = this.changedBlocksPerSection[i];
+- if (set != null) {
++ for (int i = 0; i < this.changedBlocksPerSection.length; ++i) {
++ ShortSet shortset = this.changedBlocksPerSection[i];
++
++ if (shortset != null) {
+ this.changedBlocksPerSection[i] = null;
+- if (!players.isEmpty()) {
+- int sectionYFromSectionIndex = this.levelHeightAccessor.getSectionYFromSectionIndex(i);
+- SectionPos sectionPos = SectionPos.of(chunk.getPos(), sectionYFromSectionIndex);
+- if (set.size() == 1) {
+- BlockPos blockPos = sectionPos.relativeToBlockPos(set.iterator().nextShort());
+- BlockState blockState = level.getBlockState(blockPos);
+- this.broadcast(players, new ClientboundBlockUpdatePacket(blockPos, blockState));
+- this.broadcastBlockEntityIfNeeded(players, level, blockPos, blockState);
++ if (!list.isEmpty()) {
++ int j = this.levelHeightAccessor.getSectionYFromSectionIndex(i);
++ SectionPos sectionposition = SectionPos.of(chunk.getPos(), j);
++
++ if (shortset.size() == 1) {
++ BlockPos blockposition = sectionposition.relativeToBlockPos(shortset.iterator().nextShort());
++ IBlockData iblockdata = world.getBlockState(blockposition);
++
++ this.broadcast(list, new ClientboundBlockUpdatePacket(blockposition, iblockdata));
++ this.broadcastBlockEntityIfNeeded(list, world, blockposition, iblockdata);
+ } else {
+- LevelChunkSection section = chunk.getSection(i);
+- ClientboundSectionBlocksUpdatePacket clientboundSectionBlocksUpdatePacket = new ClientboundSectionBlocksUpdatePacket(
+- sectionPos, set, section
+- );
+- this.broadcast(players, clientboundSectionBlocksUpdatePacket);
+- clientboundSectionBlocksUpdatePacket.runUpdates(
+- (blockPos1, blockState1) -> this.broadcastBlockEntityIfNeeded(players, level, blockPos1, blockState1)
+- );
++ LevelChunkSection chunksection = chunk.getSection(i);
++ ClientboundSectionBlocksUpdatePacket packetplayoutmultiblockchange = new ClientboundSectionBlocksUpdatePacket(sectionposition, shortset, chunksection);
++
++ this.broadcast(list, packetplayoutmultiblockchange);
++ // CraftBukkit start
++ List finalList = list;
++ packetplayoutmultiblockchange.runUpdates((blockposition1, iblockdata1) -> {
++ this.broadcastBlockEntityIfNeeded(finalList, world, blockposition1, iblockdata1);
++ // CraftBukkit end
++ });
+ }
+ }
+ }
+@@ -256,48 +292,58 @@
+ }
+ }
+
+- private void broadcastBlockEntityIfNeeded(List<ServerPlayer> players, Level level, BlockPos pos, BlockState state) {
++ 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> players, Level level, BlockPos pox) {
+- BlockEntity blockEntity = level.getBlockEntity(pox);
+- if (blockEntity != null) {
+- Packet<?> updatePacket = blockEntity.getUpdatePacket();
+- if (updatePacket != null) {
+- this.broadcast(players, updatePacket);
++ BlockEntity tileentity = level.getBlockEntity(pox);
++
++ if (tileentity != null) {
++ Packet<?> packet = tileentity.getUpdatePacket();
++
++ if (packet != null) {
++ this.broadcast(players, packet);
+ }
+ }
++
+ }
+
+ private void broadcast(List<ServerPlayer> players, Packet<?> packet) {
+- players.forEach(serverPlayer -> serverPlayer.connection.send(packet));
++ players.forEach((entityplayer) -> {
++ entityplayer.connection.send(packet);
++ });
+ }
+
+- public CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> getOrScheduleFuture(ChunkStatus status, ChunkMap map) {
+- int index = status.getIndex();
+- CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> completableFuture = this.futures.get(index);
+- if (completableFuture != null) {
+- Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure> either = completableFuture.getNow(NOT_DONE_YET);
++ 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.Failure> either = (Either) completablefuture.getNow(ChunkHolder.NOT_DONE_YET);
++
+ if (either == null) {
+- String string = "value in future for status: " + status + " was incorrectly set to null at chunk: " + this.pos;
+- throw map.debugFuturesAndCreateReportedException(new IllegalStateException("null value previously set for chunk status"), string);
++ String s = "value in future for status: " + status + " was incorrectly set to null at chunk: " + this.pos;
++
++ throw map.debugFuturesAndCreateReportedException(new IllegalStateException("null value previously set for chunk status"), s);
+ }
+
+- if (either == NOT_DONE_YET || either.right().isEmpty()) {
+- return completableFuture;
++ if (either == ChunkHolder.NOT_DONE_YET || either.right().isEmpty()) {
++ return completablefuture;
+ }
+ }
+
+ if (ChunkLevel.generationStatus(this.ticketLevel).isOrAfter(status)) {
+- CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> completableFuture1 = map.schedule(this, status);
+- this.updateChunkToSave(completableFuture1, "schedule " + status);
+- this.futures.set(index, completableFuture1);
+- return completableFuture1;
++ CompletableFuture<Either<ChunkAccess, ChunkHolder.Failure>> completablefuture1 = map.schedule(this, status);
++
++ this.updateChunkToSave(completablefuture1, "schedule " + status);
++ this.futures.set(i, completablefuture1);
++ return completablefuture1;
+ } else {
+- return completableFuture == null ? UNLOADED_CHUNK_FUTURE : completableFuture;
++ return completablefuture == null ? ChunkHolder.UNLOADED_CHUNK_FUTURE : completablefuture;
+ }
+ }
+
+@@ -306,26 +352,34 @@
+ this.chunkToSaveHistory.push(new ChunkHolder.ChunkSaveDebug(Thread.currentThread(), future, source));
+ }
+
+- this.chunkToSave = this.chunkToSave.thenCombine((CompletionStage<? extends Object>)future, (chunkAccess, object) -> (ChunkAccess)chunkAccess);
++ this.chunkToSave = this.chunkToSave.thenCombine(future, (ichunkaccess, object) -> {
++ return ichunkaccess;
++ });
+ }
+
+- private void updateChunkToSave(CompletableFuture<? extends Either<? extends ChunkAccess, ChunkHolder.ChunkLoadingFailure>> feature, String source) {
++ 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(), feature, source));
+ }
+
+- this.chunkToSave = this.chunkToSave
+- .thenCombine(
+- feature, (chunkAccess, either) -> either.map(chunkAccess1 -> (ChunkAccess)chunkAccess1, chunkLoadingFailure -> (ChunkAccess)chunkAccess)
+- );
++ this.chunkToSave = this.chunkToSave.thenCombine(feature, (ichunkaccess, either) -> {
++ return (ChunkAccess) either.map((ichunkaccess1) -> {
++ return ichunkaccess1;
++ }, (playerchunk_failure) -> {
++ return ichunkaccess;
++ });
++ });
+ }
+
+- public void addSendDependency(CompletableFuture<?> completableFuture) {
++ public void addSendDependency(CompletableFuture<?> completablefuture) {
+ if (this.sendSync.isDone()) {
+- this.sendSync = completableFuture;
++ this.sendSync = completablefuture;
+ } else {
+- this.sendSync = this.sendSync.thenCombine((CompletionStage<? extends Object>)completableFuture, (object, object1) -> null);
++ this.sendSync = this.sendSync.thenCombine(completablefuture, (object, object1) -> {
++ return null;
++ });
+ }
++
+ }
+
+ public FullChunkStatus getFullStatus() {
+@@ -352,14 +406,19 @@
+ this.ticketLevel = level;
+ }
+
+- private void scheduleFullChunkPromotion(
+- ChunkMap chunkMap, CompletableFuture<Either<LevelChunk, ChunkHolder.ChunkLoadingFailure>> future, 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> completableFuture = new CompletableFuture<>();
+- completableFuture.thenRunAsync(() -> chunkMap.onFullChunkStatusChange(this.pos, fullChunkStatus), executor);
+- this.pendingFullStateConfirmation = completableFuture;
+- future.thenAccept(either -> either.ifLeft(levelChunk -> completableFuture.complete(null)));
++ CompletableFuture<Void> completablefuture1 = new CompletableFuture();
++
++ completablefuture1.thenRunAsync(() -> {
++ chunkMap.onFullChunkStatusChange(this.pos, fullChunkStatus);
++ }, executor);
++ this.pendingFullStateConfirmation = completablefuture1;
++ future.thenAccept((either) -> {
++ either.ifLeft((chunk) -> {
++ completablefuture1.complete(null); // CraftBukkit - decompile error
++ });
++ });
+ }
+
+ private void demoteFullChunk(ChunkMap chunkMap, FullChunkStatus fullChunkStatus) {
+@@ -368,60 +427,88 @@
+ }
+
+ protected void updateFutures(ChunkMap chunkMap, Executor executor) {
+- ChunkStatus chunkStatus = ChunkLevel.generationStatus(this.oldTicketLevel);
+- ChunkStatus chunkStatus1 = ChunkLevel.generationStatus(this.ticketLevel);
+- boolean isLoaded = ChunkLevel.isLoaded(this.oldTicketLevel);
+- boolean isLoaded1 = ChunkLevel.isLoaded(this.ticketLevel);
+- FullChunkStatus fullChunkStatus = ChunkLevel.fullStatus(this.oldTicketLevel);
+- FullChunkStatus fullChunkStatus1 = ChunkLevel.fullStatus(this.ticketLevel);
+- if (isLoaded) {
+- Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure> either = Either.right(new ChunkHolder.ChunkLoadingFailure() {
+- @Override
++ 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.Failure> either = Either.right(new ChunkHolder.Failure() {
+ public String toString() {
+ return "Unloaded ticket level " + ChunkHolder.this.pos;
+ }
+ });
+
+- for (int i = isLoaded1 ? chunkStatus1.getIndex() + 1 : 0; i <= chunkStatus.getIndex(); i++) {
+- CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> completableFuture = this.futures.get(i);
+- if (completableFuture == null) {
++ for (int i = flag1 ? chunkstatus1.getIndex() + 1 : 0; i <= chunkstatus.getIndex(); ++i) {
++ CompletableFuture<Either<ChunkAccess, ChunkHolder.Failure>> completablefuture = (CompletableFuture) this.futures.get(i);
++
++ if (completablefuture == null) {
+ this.futures.set(i, CompletableFuture.completedFuture(either));
+ }
+ }
+ }
+
+- boolean isOrAfter = fullChunkStatus.isOrAfter(FullChunkStatus.FULL);
+- boolean isOrAfter1 = fullChunkStatus1.isOrAfter(FullChunkStatus.FULL);
+- this.wasAccessibleSinceLastSave |= isOrAfter1;
+- if (!isOrAfter && isOrAfter1) {
++ boolean flag2 = fullchunkstatus.isOrAfter(FullChunkStatus.FULL);
++ boolean flag3 = fullchunkstatus1.isOrAfter(FullChunkStatus.FULL);
++
++ this.wasAccessibleSinceLastSave |= flag3;
++ if (!flag2 && flag3) {
+ this.fullChunkFuture = chunkMap.prepareAccessibleChunk(this);
+ this.scheduleFullChunkPromotion(chunkMap, this.fullChunkFuture, executor, FullChunkStatus.FULL);
+ this.updateChunkToSave(this.fullChunkFuture, "full");
+ }
+
+- if (isOrAfter && !isOrAfter1) {
+- this.fullChunkFuture.complete(UNLOADED_LEVEL_CHUNK);
+- this.fullChunkFuture = UNLOADED_LEVEL_CHUNK_FUTURE;
++ if (flag2 && !flag3) {
++ this.fullChunkFuture.complete(ChunkHolder.UNLOADED_LEVEL_CHUNK);
++ this.fullChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE;
+ }
+
+- boolean isOrAfter2 = fullChunkStatus.isOrAfter(FullChunkStatus.BLOCK_TICKING);
+- boolean isOrAfter3 = fullChunkStatus1.isOrAfter(FullChunkStatus.BLOCK_TICKING);
+- if (!isOrAfter2 && isOrAfter3) {
++ boolean flag4 = fullchunkstatus.isOrAfter(FullChunkStatus.BLOCK_TICKING);
++ 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.updateChunkToSave(this.tickingChunkFuture, "ticking");
+ }
+
+- if (isOrAfter2 && !isOrAfter3) {
+- this.tickingChunkFuture.complete(UNLOADED_LEVEL_CHUNK);
+- this.tickingChunkFuture = UNLOADED_LEVEL_CHUNK_FUTURE;
++ if (flag4 && !flag5) {
++ this.tickingChunkFuture.complete(ChunkHolder.UNLOADED_LEVEL_CHUNK);
++ this.tickingChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE;
+ }
+
+- boolean isOrAfter4 = fullChunkStatus.isOrAfter(FullChunkStatus.ENTITY_TICKING);
+- boolean isOrAfter5 = fullChunkStatus1.isOrAfter(FullChunkStatus.ENTITY_TICKING);
+- if (!isOrAfter4 && isOrAfter5) {
+- if (this.entityTickingChunkFuture != UNLOADED_LEVEL_CHUNK_FUTURE) {
+- throw (IllegalStateException)Util.pauseInIde(new IllegalStateException());
++ boolean flag6 = fullchunkstatus.isOrAfter(FullChunkStatus.ENTITY_TICKING);
++ boolean flag7 = fullchunkstatus1.isOrAfter(FullChunkStatus.ENTITY_TICKING);
++
++ if (!flag6 && flag7) {
++ if (this.entityTickingChunkFuture != ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE) {
++ throw (IllegalStateException) Util.pauseInIde(new IllegalStateException());
+ }
+
+ this.entityTickingChunkFuture = chunkMap.prepareEntityTickingChunk(this);
+@@ -429,17 +516,37 @@
+ this.updateChunkToSave(this.entityTickingChunkFuture, "entity ticking");
+ }
+
+- if (isOrAfter4 && !isOrAfter5) {
+- this.entityTickingChunkFuture.complete(UNLOADED_LEVEL_CHUNK);
+- this.entityTickingChunkFuture = UNLOADED_LEVEL_CHUNK_FUTURE;
++ if (flag6 && !flag7) {
++ this.entityTickingChunkFuture.complete(ChunkHolder.UNLOADED_LEVEL_CHUNK);
++ this.entityTickingChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE;
+ }
+
+- if (!fullChunkStatus1.isOrAfter(fullChunkStatus)) {
+- this.demoteFullChunk(chunkMap, fullChunkStatus1);
++ if (!fullchunkstatus1.isOrAfter(fullchunkstatus)) {
++ 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() {
+@@ -451,10 +558,12 @@
+ }
+
+ public void replaceProtoChunk(ImposterProtoChunk imposter) {
+- for (int i = 0; i < this.futures.length(); i++) {
+- CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> completableFuture = this.futures.get(i);
+- if (completableFuture != null) {
+- Optional<ChunkAccess> optional = completableFuture.getNow(UNLOADED_CHUNK).left();
++ for (int i = 0; i < this.futures.length(); ++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(imposter)));
+ }
+@@ -464,26 +573,29 @@
+ 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 < CHUNK_STATUSES.size(); i++) {
+- list.add(Pair.of(CHUNK_STATUSES.get(i), this.futures.get(i)));
++ 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)));
+ }
+
+ return list;
+ }
+
+- public interface ChunkLoadingFailure {
+- ChunkHolder.ChunkLoadingFailure UNLOADED = new ChunkHolder.ChunkLoadingFailure() {
+- @Override
+- public String toString() {
+- return "UNLOADED";
+- }
+- };
++ @FunctionalInterface
++ public interface LevelChangeListener {
++
++ void onLevelChange(ChunkPos chunkPos, IntSupplier intsupplier, int i, IntConsumer intconsumer);
+ }
+
+- static final class ChunkSaveDebug {
++ public interface PlayerProvider {
++
++ List<ServerPlayer> getPlayers(ChunkPos pos, boolean boundaryOnly);
++ }
++
++ private static final class ChunkSaveDebug {
++
+ private final Thread thread;
+ private final CompletableFuture<?> future;
+ private final String source;
+@@ -495,12 +607,12 @@
+ }
+ }
+
+- @FunctionalInterface
+- public interface LevelChangeListener {
+- void onLevelChange(ChunkPos chunkPos, IntSupplier intSupplier, int i, IntConsumer intConsumer);
+- }
++ public interface Failure {
+
+- public interface PlayerProvider {
+- List<ServerPlayer> getPlayers(ChunkPos pos, boolean boundaryOnly);
++ ChunkHolder.Failure UNLOADED = new ChunkHolder.Failure() {
++ public String toString() {
++ return "UNLOADED";
++ }
++ };
+ }
+ }