414 lines
19 KiB
Java
414 lines
19 KiB
Java
package net.minecraft.server.level;
|
|
|
|
import java.util.function.IntConsumer;
|
|
import java.util.function.IntSupplier;
|
|
import java.util.stream.Stream;
|
|
import net.minecraft.world.level.chunk.ProtoChunk;
|
|
import net.minecraft.world.level.chunk.ImposterProtoChunk;
|
|
import net.minecraft.util.Mth;
|
|
import net.minecraft.Util;
|
|
import java.util.function.Consumer;
|
|
import java.util.concurrent.CompletionStage;
|
|
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket;
|
|
import net.minecraft.world.level.block.entity.BlockEntity;
|
|
import net.minecraft.world.level.Level;
|
|
import net.minecraft.network.protocol.game.ClientboundChunkBlocksUpdatePacket;
|
|
import net.minecraft.network.protocol.game.ClientboundLevelChunkPacket;
|
|
import net.minecraft.world.level.BlockGetter;
|
|
import net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket;
|
|
import net.minecraft.core.BlockPos;
|
|
import net.minecraft.network.protocol.Packet;
|
|
import net.minecraft.network.protocol.game.ClientboundLightUpdatePacket;
|
|
import net.minecraft.world.level.LightLayer;
|
|
import java.util.Optional;
|
|
import javax.annotation.Nullable;
|
|
import net.minecraft.world.level.lighting.LevelLightEngine;
|
|
import net.minecraft.world.level.ChunkPos;
|
|
import java.util.concurrent.atomic.AtomicReferenceArray;
|
|
import net.minecraft.world.level.chunk.ChunkStatus;
|
|
import java.util.List;
|
|
import net.minecraft.world.level.chunk.LevelChunk;
|
|
import java.util.concurrent.CompletableFuture;
|
|
import net.minecraft.world.level.chunk.ChunkAccess;
|
|
import com.mojang.datafixers.util.Either;
|
|
|
|
public class ChunkHolder {
|
|
public static final Either<ChunkAccess, ChunkLoadingFailure> UNLOADED_CHUNK;
|
|
public static final CompletableFuture<Either<ChunkAccess, ChunkLoadingFailure>> UNLOADED_CHUNK_FUTURE;
|
|
public static final Either<LevelChunk, ChunkLoadingFailure> UNLOADED_LEVEL_CHUNK;
|
|
private static final CompletableFuture<Either<LevelChunk, ChunkLoadingFailure>> UNLOADED_LEVEL_CHUNK_FUTURE;
|
|
private static final List<ChunkStatus> CHUNK_STATUSES;
|
|
private static final FullChunkStatus[] FULL_CHUNK_STATUSES;
|
|
private final AtomicReferenceArray<CompletableFuture<Either<ChunkAccess, ChunkLoadingFailure>>> futures;
|
|
private volatile CompletableFuture<Either<LevelChunk, ChunkLoadingFailure>> fullChunkFuture;
|
|
private volatile CompletableFuture<Either<LevelChunk, ChunkLoadingFailure>> tickingChunkFuture;
|
|
private volatile CompletableFuture<Either<LevelChunk, ChunkLoadingFailure>> entityTickingChunkFuture;
|
|
private CompletableFuture<ChunkAccess> chunkToSave;
|
|
private int oldTicketLevel;
|
|
private int ticketLevel;
|
|
private int queueLevel;
|
|
private final ChunkPos pos;
|
|
private final short[] changedBlocks;
|
|
private int changes;
|
|
private int changedSectionFilter;
|
|
private int sectionsToForceSendLightFor;
|
|
private int blockChangedLightSectionFilter;
|
|
private int skyChangedLightSectionFilter;
|
|
private final LevelLightEngine lightEngine;
|
|
private final LevelChangeListener onLevelChange;
|
|
private final PlayerProvider playerProvider;
|
|
private boolean wasAccessibleSinceLastSave;
|
|
|
|
public ChunkHolder(final ChunkPos bje, final int integer, final LevelLightEngine cnx, final LevelChangeListener c, final PlayerProvider d) {
|
|
this.futures = new AtomicReferenceArray<CompletableFuture<Either<ChunkAccess, ChunkLoadingFailure>>>(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.<ChunkAccess>completedFuture((ChunkAccess)null);
|
|
this.changedBlocks = new short[64];
|
|
this.pos = bje;
|
|
this.lightEngine = cnx;
|
|
this.onLevelChange = c;
|
|
this.playerProvider = d;
|
|
this.oldTicketLevel = ChunkMap.MAX_CHUNK_DISTANCE + 1;
|
|
this.ticketLevel = this.oldTicketLevel;
|
|
this.queueLevel = this.oldTicketLevel;
|
|
this.setTicketLevel(integer);
|
|
}
|
|
|
|
public CompletableFuture<Either<ChunkAccess, ChunkLoadingFailure>> getFutureIfPresentUnchecked(final ChunkStatus cab) {
|
|
final CompletableFuture<Either<ChunkAccess, ChunkLoadingFailure>> completableFuture3 = this.futures.get(cab.getIndex());
|
|
return (completableFuture3 == null) ? ChunkHolder.UNLOADED_CHUNK_FUTURE : completableFuture3;
|
|
}
|
|
|
|
public CompletableFuture<Either<ChunkAccess, ChunkLoadingFailure>> getFutureIfPresent(final ChunkStatus cab) {
|
|
if (getStatus(this.ticketLevel).isOrAfter(cab)) {
|
|
return this.getFutureIfPresentUnchecked(cab);
|
|
}
|
|
return ChunkHolder.UNLOADED_CHUNK_FUTURE;
|
|
}
|
|
|
|
public CompletableFuture<Either<LevelChunk, ChunkLoadingFailure>> getTickingChunkFuture() {
|
|
return this.tickingChunkFuture;
|
|
}
|
|
|
|
public CompletableFuture<Either<LevelChunk, ChunkLoadingFailure>> getEntityTickingChunkFuture() {
|
|
return this.entityTickingChunkFuture;
|
|
}
|
|
|
|
public CompletableFuture<Either<LevelChunk, ChunkLoadingFailure>> getFullChunkFuture() {
|
|
return this.fullChunkFuture;
|
|
}
|
|
|
|
@Nullable
|
|
public LevelChunk getTickingChunk() {
|
|
final CompletableFuture<Either<LevelChunk, ChunkLoadingFailure>> completableFuture2 = this.getTickingChunkFuture();
|
|
final Either<LevelChunk, ChunkLoadingFailure> either3 = completableFuture2.getNow(null);
|
|
if (either3 == null) {
|
|
return null;
|
|
}
|
|
return either3.left().orElse(null);
|
|
}
|
|
|
|
@Nullable
|
|
public ChunkStatus getLastAvailableStatus() {
|
|
for (int integer2 = ChunkHolder.CHUNK_STATUSES.size() - 1; integer2 >= 0; --integer2) {
|
|
final ChunkStatus cab3 = ChunkHolder.CHUNK_STATUSES.get(integer2);
|
|
final CompletableFuture<Either<ChunkAccess, ChunkLoadingFailure>> completableFuture4 = this.getFutureIfPresentUnchecked(cab3);
|
|
if (completableFuture4.getNow(ChunkHolder.UNLOADED_CHUNK).left().isPresent()) {
|
|
return cab3;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
@Nullable
|
|
public ChunkAccess getLastAvailable() {
|
|
for (int integer2 = ChunkHolder.CHUNK_STATUSES.size() - 1; integer2 >= 0; --integer2) {
|
|
final ChunkStatus cab3 = ChunkHolder.CHUNK_STATUSES.get(integer2);
|
|
final CompletableFuture<Either<ChunkAccess, ChunkLoadingFailure>> completableFuture4 = this.getFutureIfPresentUnchecked(cab3);
|
|
if (!completableFuture4.isCompletedExceptionally()) {
|
|
final Optional<ChunkAccess> optional5 = (Optional<ChunkAccess>)completableFuture4.getNow(ChunkHolder.UNLOADED_CHUNK).left();
|
|
if (optional5.isPresent()) {
|
|
return optional5.get();
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public CompletableFuture<ChunkAccess> getChunkToSave() {
|
|
return this.chunkToSave;
|
|
}
|
|
|
|
public void blockChanged(final int integer1, final int integer2, final int integer3) {
|
|
final LevelChunk cai5 = this.getTickingChunk();
|
|
if (cai5 == null) {
|
|
return;
|
|
}
|
|
this.changedSectionFilter |= 1 << (integer2 >> 4);
|
|
if (this.changes < 64) {
|
|
final short short6 = (short)(integer1 << 12 | integer3 << 8 | integer2);
|
|
for (int integer4 = 0; integer4 < this.changes; ++integer4) {
|
|
if (this.changedBlocks[integer4] == short6) {
|
|
return;
|
|
}
|
|
}
|
|
this.changedBlocks[this.changes++] = short6;
|
|
}
|
|
}
|
|
|
|
public void sectionLightChanged(final LightLayer bkc, final int integer) {
|
|
final LevelChunk cai4 = this.getTickingChunk();
|
|
if (cai4 == null) {
|
|
return;
|
|
}
|
|
cai4.setUnsaved(true);
|
|
if (bkc == LightLayer.SKY) {
|
|
this.skyChangedLightSectionFilter |= 1 << integer + 1;
|
|
}
|
|
else {
|
|
this.blockChangedLightSectionFilter |= 1 << integer + 1;
|
|
}
|
|
}
|
|
|
|
public void broadcastChanges(final LevelChunk cai) {
|
|
if (this.changes == 0 && this.skyChangedLightSectionFilter == 0 && this.blockChangedLightSectionFilter == 0) {
|
|
return;
|
|
}
|
|
final Level bjt3 = cai.getLevel();
|
|
if (this.changes == 64) {
|
|
this.sectionsToForceSendLightFor = -1;
|
|
}
|
|
if (this.skyChangedLightSectionFilter != 0 || this.blockChangedLightSectionFilter != 0) {
|
|
this.broadcast(new ClientboundLightUpdatePacket(cai.getPos(), this.lightEngine, this.skyChangedLightSectionFilter & ~this.sectionsToForceSendLightFor, this.blockChangedLightSectionFilter & ~this.sectionsToForceSendLightFor), true);
|
|
final int integer4 = this.skyChangedLightSectionFilter & this.sectionsToForceSendLightFor;
|
|
final int integer5 = this.blockChangedLightSectionFilter & this.sectionsToForceSendLightFor;
|
|
if (integer4 != 0 || integer5 != 0) {
|
|
this.broadcast(new ClientboundLightUpdatePacket(cai.getPos(), this.lightEngine, integer4, integer5), false);
|
|
}
|
|
this.skyChangedLightSectionFilter = 0;
|
|
this.blockChangedLightSectionFilter = 0;
|
|
this.sectionsToForceSendLightFor &= ~(this.skyChangedLightSectionFilter & this.blockChangedLightSectionFilter);
|
|
}
|
|
if (this.changes == 1) {
|
|
final int integer4 = (this.changedBlocks[0] >> 12 & 0xF) + this.pos.x * 16;
|
|
final int integer5 = this.changedBlocks[0] & 0xFF;
|
|
final int integer6 = (this.changedBlocks[0] >> 8 & 0xF) + this.pos.z * 16;
|
|
final BlockPos fk7 = new BlockPos(integer4, integer5, integer6);
|
|
this.broadcast(new ClientboundBlockUpdatePacket(bjt3, fk7), false);
|
|
if (bjt3.getBlockState(fk7).getBlock().isEntityBlock()) {
|
|
this.broadcastBlockEntity(bjt3, fk7);
|
|
}
|
|
}
|
|
else if (this.changes == 64) {
|
|
this.broadcast(new ClientboundLevelChunkPacket(cai, this.changedSectionFilter), false);
|
|
}
|
|
else if (this.changes != 0) {
|
|
this.broadcast(new ClientboundChunkBlocksUpdatePacket(this.changes, this.changedBlocks, cai), false);
|
|
for (int integer4 = 0; integer4 < this.changes; ++integer4) {
|
|
final int integer5 = (this.changedBlocks[integer4] >> 12 & 0xF) + this.pos.x * 16;
|
|
final int integer6 = this.changedBlocks[integer4] & 0xFF;
|
|
final int integer7 = (this.changedBlocks[integer4] >> 8 & 0xF) + this.pos.z * 16;
|
|
final BlockPos fk8 = new BlockPos(integer5, integer6, integer7);
|
|
if (bjt3.getBlockState(fk8).getBlock().isEntityBlock()) {
|
|
this.broadcastBlockEntity(bjt3, fk8);
|
|
}
|
|
}
|
|
}
|
|
this.changes = 0;
|
|
this.changedSectionFilter = 0;
|
|
}
|
|
|
|
private void broadcastBlockEntity(final Level bjt, final BlockPos fk) {
|
|
final BlockEntity bwi4 = bjt.getBlockEntity(fk);
|
|
if (bwi4 != null) {
|
|
final ClientboundBlockEntityDataPacket mh5 = bwi4.getUpdatePacket();
|
|
if (mh5 != null) {
|
|
this.broadcast(mh5, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void broadcast(final Packet<?> lt, final boolean boolean2) {
|
|
this.playerProvider.getPlayers(this.pos, boolean2).forEach(xe -> xe.connection.send(lt));
|
|
}
|
|
|
|
public CompletableFuture<Either<ChunkAccess, ChunkLoadingFailure>> getOrScheduleFuture(final ChunkStatus cab, final ChunkMap wp) {
|
|
final int integer4 = cab.getIndex();
|
|
final CompletableFuture<Either<ChunkAccess, ChunkLoadingFailure>> completableFuture5 = this.futures.get(integer4);
|
|
if (completableFuture5 != null) {
|
|
final Either<ChunkAccess, ChunkLoadingFailure> either6 = completableFuture5.getNow(null);
|
|
if (either6 == null || either6.left().isPresent()) {
|
|
return completableFuture5;
|
|
}
|
|
}
|
|
if (getStatus(this.ticketLevel).isOrAfter(cab)) {
|
|
final CompletableFuture<Either<ChunkAccess, ChunkLoadingFailure>> completableFuture6 = wp.schedule(this, cab);
|
|
this.updateChunkToSave(completableFuture6);
|
|
this.futures.set(integer4, completableFuture6);
|
|
return completableFuture6;
|
|
}
|
|
return (completableFuture5 == null) ? ChunkHolder.UNLOADED_CHUNK_FUTURE : completableFuture5;
|
|
}
|
|
|
|
private void updateChunkToSave(final CompletableFuture<? extends Either<? extends ChunkAccess, ChunkLoadingFailure>> completableFuture) {
|
|
this.chunkToSave = this.chunkToSave.<Object, ChunkAccess>thenCombine(completableFuture, (bzv, either) -> (ChunkAccess)either.map(bzv -> bzv, a -> bzv));
|
|
}
|
|
|
|
public FullChunkStatus getFullStatus() {
|
|
return getFullChunkStatus(this.ticketLevel);
|
|
}
|
|
|
|
public ChunkPos getPos() {
|
|
return this.pos;
|
|
}
|
|
|
|
public int getTicketLevel() {
|
|
return this.ticketLevel;
|
|
}
|
|
|
|
public int getQueueLevel() {
|
|
return this.queueLevel;
|
|
}
|
|
|
|
private void setQueueLevel(final int integer) {
|
|
this.queueLevel = integer;
|
|
}
|
|
|
|
public void setTicketLevel(final int integer) {
|
|
this.ticketLevel = integer;
|
|
}
|
|
|
|
protected void updateFutures(final ChunkMap wp) {
|
|
final ChunkStatus cab3 = getStatus(this.oldTicketLevel);
|
|
final ChunkStatus cab4 = getStatus(this.ticketLevel);
|
|
final boolean boolean5 = this.oldTicketLevel <= ChunkMap.MAX_CHUNK_DISTANCE;
|
|
final boolean boolean6 = this.ticketLevel <= ChunkMap.MAX_CHUNK_DISTANCE;
|
|
final FullChunkStatus b7 = getFullChunkStatus(this.oldTicketLevel);
|
|
final FullChunkStatus b8 = getFullChunkStatus(this.ticketLevel);
|
|
if (boolean5) {
|
|
final Either<ChunkAccess, ChunkLoadingFailure> either2 = (Either<ChunkAccess, ChunkLoadingFailure>)Either.right(new ChunkLoadingFailure() {
|
|
@Override
|
|
public String toString() {
|
|
return "Unloaded ticket level " + ChunkHolder.this.pos.toString();
|
|
}
|
|
});
|
|
for (int integer10 = boolean6 ? (cab4.getIndex() + 1) : 0; integer10 <= cab3.getIndex(); ++integer10) {
|
|
final CompletableFuture<Either<ChunkAccess, ChunkLoadingFailure>> completableFuture11 = this.futures.get(integer10);
|
|
if (completableFuture11 != null) {
|
|
completableFuture11.complete(either2);
|
|
}
|
|
else {
|
|
this.futures.set(integer10, CompletableFuture.<Either<ChunkAccess, ChunkLoadingFailure>>completedFuture(either2));
|
|
}
|
|
}
|
|
}
|
|
final boolean boolean7 = b7.isOrAfter(FullChunkStatus.BORDER);
|
|
final boolean boolean8 = b8.isOrAfter(FullChunkStatus.BORDER);
|
|
this.wasAccessibleSinceLastSave |= boolean8;
|
|
if (!boolean7 && boolean8) {
|
|
this.updateChunkToSave(this.fullChunkFuture = wp.unpackTicks(this));
|
|
}
|
|
if (boolean7 && !boolean8) {
|
|
final CompletableFuture<Either<LevelChunk, ChunkLoadingFailure>> completableFuture12 = this.fullChunkFuture;
|
|
this.fullChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE;
|
|
this.updateChunkToSave(completableFuture12.thenApply(either -> either.ifLeft((Consumer)wp::packTicks)));
|
|
}
|
|
final boolean boolean9 = b7.isOrAfter(FullChunkStatus.TICKING);
|
|
final boolean boolean10 = b8.isOrAfter(FullChunkStatus.TICKING);
|
|
if (!boolean9 && boolean10) {
|
|
this.updateChunkToSave(this.tickingChunkFuture = wp.postProcess(this));
|
|
}
|
|
if (boolean9 && !boolean10) {
|
|
this.tickingChunkFuture.complete(ChunkHolder.UNLOADED_LEVEL_CHUNK);
|
|
this.tickingChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE;
|
|
}
|
|
final boolean boolean11 = b7.isOrAfter(FullChunkStatus.ENTITY_TICKING);
|
|
final boolean boolean12 = b8.isOrAfter(FullChunkStatus.ENTITY_TICKING);
|
|
if (!boolean11 && boolean12) {
|
|
if (this.entityTickingChunkFuture != ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE) {
|
|
throw Util.<IllegalStateException>pauseInIde(new IllegalStateException());
|
|
}
|
|
this.updateChunkToSave(this.entityTickingChunkFuture = wp.getEntityTickingRangeFuture(this.pos));
|
|
}
|
|
if (boolean11 && !boolean12) {
|
|
this.entityTickingChunkFuture.complete(ChunkHolder.UNLOADED_LEVEL_CHUNK);
|
|
this.entityTickingChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE;
|
|
}
|
|
this.onLevelChange.onLevelChange(this.pos, this::getQueueLevel, this.ticketLevel, this::setQueueLevel);
|
|
this.oldTicketLevel = this.ticketLevel;
|
|
}
|
|
|
|
public static ChunkStatus getStatus(final int integer) {
|
|
if (integer < 33) {
|
|
return ChunkStatus.FULL;
|
|
}
|
|
return ChunkStatus.getStatus(integer - 33);
|
|
}
|
|
|
|
public static FullChunkStatus getFullChunkStatus(final int integer) {
|
|
return ChunkHolder.FULL_CHUNK_STATUSES[Mth.clamp(33 - integer + 1, 0, ChunkHolder.FULL_CHUNK_STATUSES.length - 1)];
|
|
}
|
|
|
|
public boolean wasAccessibleSinceLastSave() {
|
|
return this.wasAccessibleSinceLastSave;
|
|
}
|
|
|
|
public void refreshAccessibility() {
|
|
this.wasAccessibleSinceLastSave = getFullChunkStatus(this.ticketLevel).isOrAfter(FullChunkStatus.BORDER);
|
|
}
|
|
|
|
public void replaceProtoChunk(final ImposterProtoChunk cah) {
|
|
for (int integer3 = 0; integer3 < this.futures.length(); ++integer3) {
|
|
final CompletableFuture<Either<ChunkAccess, ChunkLoadingFailure>> completableFuture4 = this.futures.get(integer3);
|
|
if (completableFuture4 != null) {
|
|
final Optional<ChunkAccess> optional5 = (Optional<ChunkAccess>)completableFuture4.getNow(ChunkHolder.UNLOADED_CHUNK).left();
|
|
if (optional5.isPresent()) {
|
|
if (optional5.get() instanceof ProtoChunk) {
|
|
this.futures.set(integer3, CompletableFuture.<Either<ChunkAccess, ChunkLoadingFailure>>completedFuture(Either.left(cah)));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
this.updateChunkToSave(CompletableFuture.<Either>completedFuture(Either.left(cah.getWrapped())));
|
|
}
|
|
|
|
static {
|
|
UNLOADED_CHUNK = Either.right(ChunkLoadingFailure.UNLOADED);
|
|
UNLOADED_CHUNK_FUTURE = CompletableFuture.<Either<ChunkAccess, ChunkLoadingFailure>>completedFuture(ChunkHolder.UNLOADED_CHUNK);
|
|
UNLOADED_LEVEL_CHUNK = Either.right(ChunkLoadingFailure.UNLOADED);
|
|
UNLOADED_LEVEL_CHUNK_FUTURE = CompletableFuture.<Either<LevelChunk, ChunkLoadingFailure>>completedFuture(ChunkHolder.UNLOADED_LEVEL_CHUNK);
|
|
CHUNK_STATUSES = ChunkStatus.getStatusList();
|
|
FULL_CHUNK_STATUSES = FullChunkStatus.values();
|
|
}
|
|
|
|
public enum FullChunkStatus {
|
|
INACCESSIBLE,
|
|
BORDER,
|
|
TICKING,
|
|
ENTITY_TICKING;
|
|
|
|
public boolean isOrAfter(final FullChunkStatus b) {
|
|
return this.ordinal() >= b.ordinal();
|
|
}
|
|
}
|
|
|
|
public interface ChunkLoadingFailure {
|
|
public static final ChunkLoadingFailure UNLOADED = new ChunkLoadingFailure() {
|
|
@Override
|
|
public String toString() {
|
|
return "UNLOADED";
|
|
}
|
|
};
|
|
}
|
|
|
|
public interface PlayerProvider {
|
|
Stream<ServerPlayer> getPlayers(final ChunkPos bje, final boolean boolean2);
|
|
}
|
|
|
|
public interface LevelChangeListener {
|
|
void onLevelChange(final ChunkPos bje, final IntSupplier intSupplier, final int integer, final IntConsumer intConsumer);
|
|
}
|
|
}
|