2025-11-24 22:52:51 +03:00

323 lines
14 KiB
Java

/*
* Decompiled with CFR 0.152.
*
* Could not load the following classes:
* it.unimi.dsi.fastutil.shorts.ShortOpenHashSet
* it.unimi.dsi.fastutil.shorts.ShortSet
* org.jspecify.annotations.Nullable
*/
package net.minecraft.server.level;
import it.unimi.dsi.fastutil.shorts.ShortOpenHashSet;
import it.unimi.dsi.fastutil.shorts.ShortSet;
import java.util.BitSet;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.function.IntConsumer;
import java.util.function.IntSupplier;
import net.minecraft.core.BlockPos;
import net.minecraft.core.SectionPos;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.ClientGamePacketListener;
import net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket;
import net.minecraft.network.protocol.game.ClientboundLightUpdatePacket;
import net.minecraft.network.protocol.game.ClientboundSectionBlocksUpdatePacket;
import net.minecraft.server.level.ChunkLevel;
import net.minecraft.server.level.ChunkMap;
import net.minecraft.server.level.ChunkResult;
import net.minecraft.server.level.FullChunkStatus;
import net.minecraft.server.level.GenerationChunkHolder;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.util.Util;
import net.minecraft.world.level.ChunkPos;
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.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.chunk.LevelChunkSection;
import net.minecraft.world.level.chunk.status.ChunkStatus;
import net.minecraft.world.level.lighting.LevelLightEngine;
import org.jspecify.annotations.Nullable;
public class ChunkHolder
extends GenerationChunkHolder {
public static final ChunkResult<LevelChunk> UNLOADED_LEVEL_CHUNK = ChunkResult.error("Unloaded level chunk");
private static final CompletableFuture<ChunkResult<LevelChunk>> UNLOADED_LEVEL_CHUNK_FUTURE = CompletableFuture.completedFuture(UNLOADED_LEVEL_CHUNK);
private final LevelHeightAccessor levelHeightAccessor;
private volatile CompletableFuture<ChunkResult<LevelChunk>> fullChunkFuture = UNLOADED_LEVEL_CHUNK_FUTURE;
private volatile CompletableFuture<ChunkResult<LevelChunk>> tickingChunkFuture = UNLOADED_LEVEL_CHUNK_FUTURE;
private volatile CompletableFuture<ChunkResult<LevelChunk>> entityTickingChunkFuture = UNLOADED_LEVEL_CHUNK_FUTURE;
private int oldTicketLevel;
private int ticketLevel;
private int queueLevel;
private boolean hasChangedSections;
private final @Nullable ShortSet[] changedBlocksPerSection;
private final BitSet blockChangedLightSectionFilter = new BitSet();
private final BitSet skyChangedLightSectionFilter = new BitSet();
private final LevelLightEngine lightEngine;
private final LevelChangeListener onLevelChange;
private final PlayerProvider playerProvider;
private boolean wasAccessibleSinceLastSave;
private CompletableFuture<?> pendingFullStateConfirmation = CompletableFuture.completedFuture(null);
private CompletableFuture<?> sendSync = CompletableFuture.completedFuture(null);
private CompletableFuture<?> saveSync = CompletableFuture.completedFuture(null);
public ChunkHolder(ChunkPos pos, int ticketLevel, LevelHeightAccessor levelHeightAccessor, LevelLightEngine lightEngine, LevelChangeListener onLevelChange, PlayerProvider playerProvider) {
super(pos);
this.levelHeightAccessor = levelHeightAccessor;
this.lightEngine = lightEngine;
this.onLevelChange = onLevelChange;
this.playerProvider = playerProvider;
this.ticketLevel = this.oldTicketLevel = ChunkLevel.MAX_LEVEL + 1;
this.queueLevel = this.oldTicketLevel;
this.setTicketLevel(ticketLevel);
this.changedBlocksPerSection = new ShortSet[levelHeightAccessor.getSectionsCount()];
}
public CompletableFuture<ChunkResult<LevelChunk>> getTickingChunkFuture() {
return this.tickingChunkFuture;
}
public CompletableFuture<ChunkResult<LevelChunk>> getEntityTickingChunkFuture() {
return this.entityTickingChunkFuture;
}
public CompletableFuture<ChunkResult<LevelChunk>> getFullChunkFuture() {
return this.fullChunkFuture;
}
public @Nullable LevelChunk getTickingChunk() {
return this.getTickingChunkFuture().getNow(UNLOADED_LEVEL_CHUNK).orElse(null);
}
public @Nullable LevelChunk getChunkToSend() {
if (!this.sendSync.isDone()) {
return null;
}
return this.getTickingChunk();
}
public CompletableFuture<?> getSendSyncFuture() {
return this.sendSync;
}
public void addSendDependency(CompletableFuture<?> sync) {
this.sendSync = this.sendSync.isDone() ? sync : this.sendSync.thenCombine(sync, (a, b) -> null);
}
public CompletableFuture<?> getSaveSyncFuture() {
return this.saveSync;
}
public boolean isReadyForSaving() {
return this.saveSync.isDone();
}
@Override
protected void addSaveDependency(CompletableFuture<?> sync) {
this.saveSync = this.saveSync.isDone() ? sync : this.saveSync.thenCombine(sync, (a, b) -> null);
}
public boolean blockChanged(BlockPos pos) {
LevelChunk chunk = this.getTickingChunk();
if (chunk == null) {
return false;
}
boolean hadChangedSections = this.hasChangedSections;
int sectionIndex = this.levelHeightAccessor.getSectionIndex(pos.getY());
ShortSet changedBlocksInSection = this.changedBlocksPerSection[sectionIndex];
if (changedBlocksInSection == null) {
this.hasChangedSections = true;
this.changedBlocksPerSection[sectionIndex] = changedBlocksInSection = new ShortOpenHashSet();
}
changedBlocksInSection.add(SectionPos.sectionRelativePos(pos));
return !hadChangedSections;
}
public boolean sectionLightChanged(LightLayer layer, int chunkY) {
int index;
ChunkAccess chunk = this.getChunkIfPresent(ChunkStatus.INITIALIZE_LIGHT);
if (chunk == null) {
return false;
}
chunk.markUnsaved();
LevelChunk tickingChunk = this.getTickingChunk();
if (tickingChunk == null) {
return false;
}
int minLightSection = this.lightEngine.getMinLightSection();
int maxLightSection = this.lightEngine.getMaxLightSection();
if (chunkY < minLightSection || chunkY > maxLightSection) {
return false;
}
BitSet filter = layer == LightLayer.SKY ? this.skyChangedLightSectionFilter : this.blockChangedLightSectionFilter;
if (!filter.get(index = chunkY - minLightSection)) {
filter.set(index);
return true;
}
return false;
}
public boolean hasChangesToBroadcast() {
return this.hasChangedSections || !this.skyChangedLightSectionFilter.isEmpty() || !this.blockChangedLightSectionFilter.isEmpty();
}
public void broadcastChanges(LevelChunk chunk) {
if (!this.hasChangesToBroadcast()) {
return;
}
Level level = chunk.getLevel();
if (!this.skyChangedLightSectionFilter.isEmpty() || !this.blockChangedLightSectionFilter.isEmpty()) {
List<ServerPlayer> borderPlayers = this.playerProvider.getPlayers(this.pos, true);
if (!borderPlayers.isEmpty()) {
ClientboundLightUpdatePacket lightPacket = new ClientboundLightUpdatePacket(chunk.getPos(), this.lightEngine, this.skyChangedLightSectionFilter, this.blockChangedLightSectionFilter);
this.broadcast(borderPlayers, lightPacket);
}
this.skyChangedLightSectionFilter.clear();
this.blockChangedLightSectionFilter.clear();
}
if (!this.hasChangedSections) {
return;
}
List<ServerPlayer> players = this.playerProvider.getPlayers(this.pos, false);
for (int sectionIndex = 0; sectionIndex < this.changedBlocksPerSection.length; ++sectionIndex) {
ShortSet changedBlocks = this.changedBlocksPerSection[sectionIndex];
if (changedBlocks == null) continue;
this.changedBlocksPerSection[sectionIndex] = null;
if (players.isEmpty()) continue;
int sectionY = this.levelHeightAccessor.getSectionYFromSectionIndex(sectionIndex);
SectionPos sectionPos = SectionPos.of(chunk.getPos(), sectionY);
if (changedBlocks.size() == 1) {
BlockPos pos2 = sectionPos.relativeToBlockPos(changedBlocks.iterator().nextShort());
BlockState state2 = level.getBlockState(pos2);
this.broadcast(players, new ClientboundBlockUpdatePacket(pos2, state2));
this.broadcastBlockEntityIfNeeded(players, level, pos2, state2);
continue;
}
LevelChunkSection section = chunk.getSection(sectionIndex);
ClientboundSectionBlocksUpdatePacket packet = new ClientboundSectionBlocksUpdatePacket(sectionPos, changedBlocks, section);
this.broadcast(players, packet);
packet.runUpdates((pos, state) -> this.broadcastBlockEntityIfNeeded(players, level, (BlockPos)pos, (BlockState)state));
}
this.hasChangedSections = false;
}
private void broadcastBlockEntityIfNeeded(List<ServerPlayer> players, Level level, BlockPos pos, BlockState state) {
if (state.hasBlockEntity()) {
this.broadcastBlockEntity(players, level, pos);
}
}
private void broadcastBlockEntity(List<ServerPlayer> players, Level level, BlockPos blockPos) {
Packet<ClientGamePacketListener> packet;
BlockEntity blockEntity = level.getBlockEntity(blockPos);
if (blockEntity != null && (packet = blockEntity.getUpdatePacket()) != null) {
this.broadcast(players, packet);
}
}
private void broadcast(List<ServerPlayer> players, Packet<?> packet) {
players.forEach(player -> player.connection.send(packet));
}
@Override
public int getTicketLevel() {
return this.ticketLevel;
}
@Override
public int getQueueLevel() {
return this.queueLevel;
}
private void setQueueLevel(int queueLevel) {
this.queueLevel = queueLevel;
}
public void setTicketLevel(int ticketLevel) {
this.ticketLevel = ticketLevel;
}
private void scheduleFullChunkPromotion(ChunkMap scheduler, CompletableFuture<ChunkResult<LevelChunk>> task, Executor mainThreadExecutor, FullChunkStatus status) {
this.pendingFullStateConfirmation.cancel(false);
CompletableFuture confirmation = new CompletableFuture();
confirmation.thenRunAsync(() -> scheduler.onFullChunkStatusChange(this.pos, status), mainThreadExecutor);
this.pendingFullStateConfirmation = confirmation;
task.thenAccept(r -> r.ifSuccess(l -> confirmation.complete(null)));
}
private void demoteFullChunk(ChunkMap scheduler, FullChunkStatus status) {
this.pendingFullStateConfirmation.cancel(false);
scheduler.onFullChunkStatusChange(this.pos, status);
}
protected void updateFutures(ChunkMap scheduler, Executor mainThreadExecutor) {
FullChunkStatus oldFullStatus = ChunkLevel.fullStatus(this.oldTicketLevel);
FullChunkStatus newFullStatus = ChunkLevel.fullStatus(this.ticketLevel);
boolean wasAccessible = oldFullStatus.isOrAfter(FullChunkStatus.FULL);
boolean isAccessible = newFullStatus.isOrAfter(FullChunkStatus.FULL);
this.wasAccessibleSinceLastSave |= isAccessible;
if (!wasAccessible && isAccessible) {
this.fullChunkFuture = scheduler.prepareAccessibleChunk(this);
this.scheduleFullChunkPromotion(scheduler, this.fullChunkFuture, mainThreadExecutor, FullChunkStatus.FULL);
this.addSaveDependency(this.fullChunkFuture);
}
if (wasAccessible && !isAccessible) {
this.fullChunkFuture.complete(UNLOADED_LEVEL_CHUNK);
this.fullChunkFuture = UNLOADED_LEVEL_CHUNK_FUTURE;
}
boolean wasTicking = oldFullStatus.isOrAfter(FullChunkStatus.BLOCK_TICKING);
boolean isTicking = newFullStatus.isOrAfter(FullChunkStatus.BLOCK_TICKING);
if (!wasTicking && isTicking) {
this.tickingChunkFuture = scheduler.prepareTickingChunk(this);
this.scheduleFullChunkPromotion(scheduler, this.tickingChunkFuture, mainThreadExecutor, FullChunkStatus.BLOCK_TICKING);
this.addSaveDependency(this.tickingChunkFuture);
}
if (wasTicking && !isTicking) {
this.tickingChunkFuture.complete(UNLOADED_LEVEL_CHUNK);
this.tickingChunkFuture = UNLOADED_LEVEL_CHUNK_FUTURE;
}
boolean wasEntityTicking = oldFullStatus.isOrAfter(FullChunkStatus.ENTITY_TICKING);
boolean isEntityTicking = newFullStatus.isOrAfter(FullChunkStatus.ENTITY_TICKING);
if (!wasEntityTicking && isEntityTicking) {
if (this.entityTickingChunkFuture != UNLOADED_LEVEL_CHUNK_FUTURE) {
throw Util.pauseInIde(new IllegalStateException());
}
this.entityTickingChunkFuture = scheduler.prepareEntityTickingChunk(this);
this.scheduleFullChunkPromotion(scheduler, this.entityTickingChunkFuture, mainThreadExecutor, FullChunkStatus.ENTITY_TICKING);
this.addSaveDependency(this.entityTickingChunkFuture);
}
if (wasEntityTicking && !isEntityTicking) {
this.entityTickingChunkFuture.complete(UNLOADED_LEVEL_CHUNK);
this.entityTickingChunkFuture = UNLOADED_LEVEL_CHUNK_FUTURE;
}
if (!newFullStatus.isOrAfter(oldFullStatus)) {
this.demoteFullChunk(scheduler, newFullStatus);
}
this.onLevelChange.onLevelChange(this.pos, this::getQueueLevel, this.ticketLevel, this::setQueueLevel);
this.oldTicketLevel = this.ticketLevel;
}
public boolean wasAccessibleSinceLastSave() {
return this.wasAccessibleSinceLastSave;
}
public void refreshAccessibility() {
this.wasAccessibleSinceLastSave = ChunkLevel.fullStatus(this.ticketLevel).isOrAfter(FullChunkStatus.FULL);
}
@FunctionalInterface
public static interface LevelChangeListener {
public void onLevelChange(ChunkPos var1, IntSupplier var2, int var3, IntConsumer var4);
}
public static interface PlayerProvider {
public List<ServerPlayer> getPlayers(ChunkPos var1, boolean var2);
}
}