minecraft-source/src/net/minecraft/server/level/ServerChunkCache.java

539 lines
22 KiB
Java

package net.minecraft.server.level;
import net.minecraft.core.Registry;
import java.util.concurrent.CompletionStage;
import net.minecraft.world.level.lighting.LevelLightEngine;
import net.minecraft.world.entity.ai.village.poi.PoiManager;
import net.minecraft.network.protocol.Packet;
import net.minecraft.core.SectionPos;
import net.minecraft.world.level.LightLayer;
import com.google.common.annotations.VisibleForTesting;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import net.minecraft.world.level.storage.LevelData;
import net.minecraft.world.level.NaturalSpawner;
import net.minecraft.world.entity.MobCategory;
import net.minecraft.world.level.GameRules;
import net.minecraft.world.level.LevelType;
import java.util.function.BooleanSupplier;
import java.io.IOException;
import java.util.function.Function;
import net.minecraft.core.BlockPos;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.Entity;
import java.util.Optional;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.util.profiling.ProfilerFiller;
import java.util.Arrays;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.Util;
import com.mojang.datafixers.util.Either;
import net.minecraft.world.level.ChunkPos;
import java.util.concurrent.CompletableFuture;
import javax.annotation.Nullable;
import net.minecraft.world.level.chunk.LightChunkGetter;
import net.minecraft.util.thread.BlockableEventLoop;
import net.minecraft.world.level.Level;
import java.util.function.Supplier;
import net.minecraft.server.level.progress.ChunkProgressListener;
import java.util.concurrent.Executor;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureManager;
import com.mojang.datafixers.DataFixer;
import java.io.File;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.storage.DimensionDataStorage;
import net.minecraft.world.level.chunk.ChunkGenerator;
import net.minecraft.world.level.chunk.ChunkStatus;
import java.util.List;
import net.minecraft.world.level.chunk.ChunkSource;
public class ServerChunkCache extends ChunkSource {
private static final int MAGIC_NUMBER;
private static final List<ChunkStatus> CHUNK_STATUSES;
private final DistanceManager distanceManager;
private final ChunkGenerator<?> generator;
private final ServerLevel level;
private final Thread mainThread;
private final ThreadedLevelLightEngine lightEngine;
private final MainThreadExecutor mainThreadProcessor;
public final ChunkMap chunkMap;
private final DimensionDataStorage dataStorage;
private long lastInhabitedUpdate;
private boolean spawnEnemies;
private boolean spawnFriendlies;
private final long[] lastChunkPos;
private final ChunkStatus[] lastChunkStatus;
private final ChunkAccess[] lastChunk;
public ServerChunkCache(final ServerLevel xd, final File file, final DataFixer dataFixer, final StructureManager cml, final Executor executor, final ChunkGenerator<?> bzx, final int integer, final ChunkProgressListener xm, final Supplier<DimensionDataStorage> supplier) {
this.spawnEnemies = true;
this.spawnFriendlies = true;
this.lastChunkPos = new long[4];
this.lastChunkStatus = new ChunkStatus[4];
this.lastChunk = new ChunkAccess[4];
this.level = xd;
this.mainThreadProcessor = new MainThreadExecutor((Level)xd);
this.generator = bzx;
this.mainThread = Thread.currentThread();
final File file2 = xd.getDimension().getType().getStorageFolder(file);
final File file3 = new File(file2, "data");
file3.mkdirs();
this.dataStorage = new DimensionDataStorage(file3, dataFixer);
this.chunkMap = new ChunkMap(xd, file, dataFixer, cml, executor, this.mainThreadProcessor, this, this.getGenerator(), xm, supplier, integer);
this.lightEngine = this.chunkMap.getLightEngine();
this.distanceManager = this.chunkMap.getDistanceManager();
this.clearCache();
}
@Override
public ThreadedLevelLightEngine getLightEngine() {
return this.lightEngine;
}
@Nullable
private ChunkHolder getVisibleChunkIfPresent(final long long1) {
return this.chunkMap.getVisibleChunkIfPresent(long1);
}
public int getTickingGenerated() {
return this.chunkMap.getTickingGenerated();
}
private void storeInCache(final long long1, final ChunkAccess bzv, final ChunkStatus cab) {
for (int integer6 = 3; integer6 > 0; --integer6) {
this.lastChunkPos[integer6] = this.lastChunkPos[integer6 - 1];
this.lastChunkStatus[integer6] = this.lastChunkStatus[integer6 - 1];
this.lastChunk[integer6] = this.lastChunk[integer6 - 1];
}
this.lastChunkPos[0] = long1;
this.lastChunkStatus[0] = cab;
this.lastChunk[0] = bzv;
}
@Nullable
@Override
public ChunkAccess getChunk(final int integer1, final int integer2, final ChunkStatus cab, final boolean boolean4) {
if (Thread.currentThread() != this.mainThread) {
return CompletableFuture.<ChunkAccess>supplyAsync(() -> this.getChunk(integer1, integer2, cab, boolean4), this.mainThreadProcessor).join();
}
final long long6 = ChunkPos.asLong(integer1, integer2);
for (int integer3 = 0; integer3 < 4; ++integer3) {
if (long6 == this.lastChunkPos[integer3] && cab == this.lastChunkStatus[integer3]) {
final ChunkAccess bzv2 = this.lastChunk[integer3];
if (bzv2 != null || !boolean4) {
return bzv2;
}
}
}
final CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> completableFuture8 = this.getChunkFutureMainThread(integer1, integer2, cab, boolean4);
this.mainThreadProcessor.managedBlock(completableFuture8::isDone);
final IllegalStateException throwable;
final ChunkAccess bzv2 = (ChunkAccess)completableFuture8.join().map(bzv -> bzv, a -> {
if (boolean4) {
new IllegalStateException("Chunk not there when requested: " + a);
throw Util.<IllegalStateException>pauseInIde(throwable);
}
else {
return null;
}
});
this.storeInCache(long6, bzv2, cab);
return bzv2;
}
@Nullable
@Override
public LevelChunk getChunkNow(final int integer1, final int integer2) {
if (Thread.currentThread() != this.mainThread) {
return null;
}
final long long4 = ChunkPos.asLong(integer1, integer2);
for (int integer3 = 0; integer3 < 4; ++integer3) {
if (long4 == this.lastChunkPos[integer3] && this.lastChunkStatus[integer3] == ChunkStatus.FULL) {
final ChunkAccess bzv7 = this.lastChunk[integer3];
return (bzv7 instanceof LevelChunk) ? ((LevelChunk)bzv7) : null;
}
}
final ChunkHolder wo6 = this.getVisibleChunkIfPresent(long4);
if (wo6 == null) {
return null;
}
final Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure> either7 = wo6.getFutureIfPresent(ChunkStatus.FULL).getNow(null);
if (either7 == null) {
return null;
}
final ChunkAccess bzv8 = either7.left().orElse(null);
if (bzv8 != null) {
this.storeInCache(long4, bzv8, ChunkStatus.FULL);
if (bzv8 instanceof LevelChunk) {
return (LevelChunk)bzv8;
}
}
return null;
}
private void clearCache() {
Arrays.fill(this.lastChunkPos, ChunkPos.INVALID_CHUNK_POS);
Arrays.fill(this.lastChunkStatus, null);
Arrays.fill(this.lastChunk, null);
}
public CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> getChunkFuture(final int integer1, final int integer2, final ChunkStatus cab, final boolean boolean4) {
final boolean boolean5 = Thread.currentThread() == this.mainThread;
CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> completableFuture2;
if (boolean5) {
completableFuture2 = this.getChunkFutureMainThread(integer1, integer2, cab, boolean4);
this.mainThreadProcessor.managedBlock(completableFuture2::isDone);
}
else {
completableFuture2 = CompletableFuture.<CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>>>supplyAsync(() -> this.getChunkFutureMainThread(integer1, integer2, cab, boolean4), this.mainThreadProcessor).<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>>thenCompose(completableFuture -> completableFuture);
}
return completableFuture2;
}
private CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> getChunkFutureMainThread(final int integer1, final int integer2, final ChunkStatus cab, final boolean boolean4) {
final ChunkPos bje6 = new ChunkPos(integer1, integer2);
final long long7 = bje6.toLong();
final int integer3 = 33 + ChunkStatus.getDistance(cab);
ChunkHolder wo10 = this.getVisibleChunkIfPresent(long7);
if (boolean4) {
this.distanceManager.<ChunkPos>addTicket(TicketType.UNKNOWN, bje6, integer3, bje6);
if (this.chunkAbsent(wo10, integer3)) {
final ProfilerFiller aim11 = this.level.getProfiler();
aim11.push("chunkLoad");
this.runDistanceManagerUpdates();
wo10 = this.getVisibleChunkIfPresent(long7);
aim11.pop();
if (this.chunkAbsent(wo10, integer3)) {
throw Util.<IllegalStateException>pauseInIde(new IllegalStateException("No chunk holder after ticket has been added"));
}
}
}
if (this.chunkAbsent(wo10, integer3)) {
return ChunkHolder.UNLOADED_CHUNK_FUTURE;
}
return wo10.getOrScheduleFuture(cab, this.chunkMap);
}
private boolean chunkAbsent(@Nullable final ChunkHolder wo, final int integer) {
return wo == null || wo.getTicketLevel() > integer;
}
@Override
public boolean hasChunk(final int integer1, final int integer2) {
final ChunkHolder wo4 = this.getVisibleChunkIfPresent(new ChunkPos(integer1, integer2).toLong());
final int integer3 = 33 + ChunkStatus.getDistance(ChunkStatus.FULL);
return !this.chunkAbsent(wo4, integer3);
}
@Override
public BlockGetter getChunkForLighting(final int integer1, final int integer2) {
final long long4 = ChunkPos.asLong(integer1, integer2);
final ChunkHolder wo6 = this.getVisibleChunkIfPresent(long4);
if (wo6 == null) {
return null;
}
int integer3 = ServerChunkCache.CHUNK_STATUSES.size() - 1;
while (true) {
final ChunkStatus cab8 = ServerChunkCache.CHUNK_STATUSES.get(integer3);
final Optional<ChunkAccess> optional9 = (Optional<ChunkAccess>)wo6.getFutureIfPresentUnchecked(cab8).getNow(ChunkHolder.UNLOADED_CHUNK).left();
if (optional9.isPresent()) {
return optional9.get();
}
if (cab8 == ChunkStatus.LIGHT.getParent()) {
return null;
}
--integer3;
}
}
@Override
public Level getLevel() {
return this.level;
}
public boolean pollTask() {
return this.mainThreadProcessor.pollTask();
}
private boolean runDistanceManagerUpdates() {
final boolean boolean2 = this.distanceManager.runAllUpdates(this.chunkMap);
final boolean boolean3 = this.chunkMap.promoteChunkMap();
if (boolean2 || boolean3) {
this.clearCache();
return true;
}
return false;
}
@Override
public boolean isEntityTickingChunk(final Entity akn) {
final long long3 = ChunkPos.asLong(Mth.floor(akn.getX()) >> 4, Mth.floor(akn.getZ()) >> 4);
return this.checkChunkFuture(long3, ChunkHolder::getEntityTickingChunkFuture);
}
@Override
public boolean isEntityTickingChunk(final ChunkPos bje) {
return this.checkChunkFuture(bje.toLong(), ChunkHolder::getEntityTickingChunkFuture);
}
@Override
public boolean isTickingChunk(final BlockPos fk) {
final long long3 = ChunkPos.asLong(fk.getX() >> 4, fk.getZ() >> 4);
return this.checkChunkFuture(long3, ChunkHolder::getTickingChunkFuture);
}
public boolean isInAccessibleChunk(final Entity akn) {
final long long3 = ChunkPos.asLong(Mth.floor(akn.getX()) >> 4, Mth.floor(akn.getZ()) >> 4);
return this.checkChunkFuture(long3, ChunkHolder::getFullChunkFuture);
}
private boolean checkChunkFuture(final long long1, final Function<ChunkHolder, CompletableFuture<Either<LevelChunk, ChunkHolder.ChunkLoadingFailure>>> function) {
final ChunkHolder wo5 = this.getVisibleChunkIfPresent(long1);
if (wo5 == null) {
return false;
}
final Either<LevelChunk, ChunkHolder.ChunkLoadingFailure> either6 = function.apply(wo5).getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK);
return either6.left().isPresent();
}
public void save(final boolean boolean1) {
this.runDistanceManagerUpdates();
this.chunkMap.saveAllChunks(boolean1);
}
@Override
public void close() throws IOException {
this.save(true);
this.lightEngine.close();
this.chunkMap.close();
}
@Override
public void tick(final BooleanSupplier booleanSupplier) {
this.level.getProfiler().push("purge");
this.distanceManager.purgeStaleTickets();
this.runDistanceManagerUpdates();
this.level.getProfiler().popPush("chunks");
this.tickChunks();
this.level.getProfiler().popPush("unload");
this.chunkMap.tick(booleanSupplier);
this.level.getProfiler().pop();
this.clearCache();
}
private void tickChunks() {
final long long2 = this.level.getGameTime();
final long long3 = long2 - this.lastInhabitedUpdate;
this.lastInhabitedUpdate = long2;
final LevelData crj6 = this.level.getLevelData();
final boolean boolean7 = crj6.getGeneratorType() == LevelType.DEBUG_ALL_BLOCK_STATES;
final boolean boolean8 = this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING);
if (!boolean7) {
this.level.getProfiler().push("pollingChunks");
final int integer9 = this.level.getGameRules().getInt(GameRules.RULE_RANDOMTICKING);
final BlockPos fk10 = this.level.getSharedSpawnPos();
final boolean boolean9 = crj6.getGameTime() % 400L == 0L;
this.level.getProfiler().push("naturalSpawnCount");
final int integer10 = this.distanceManager.getNaturalSpawnChunkCount();
final MobCategory[] arr13 = MobCategory.values();
final Object2IntMap<MobCategory> object2IntMap14 = this.level.getMobCategoryCounts();
this.level.getProfiler().pop();
final Optional<LevelChunk> optional12;
LevelChunk cai13;
ChunkPos bje14;
final long n;
final boolean b;
final MobCategory[] array;
int length;
int i = 0;
MobCategory aky18;
final boolean b2;
final int n2;
int integer11;
final Object2IntMap object2IntMap15;
final BlockPos fk11;
final int integer12;
this.chunkMap.getChunks().forEach(wo -> {
optional12 = (Optional<LevelChunk>)wo.getEntityTickingChunkFuture().getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK).left();
if (!optional12.isPresent()) {
return;
}
else {
cai13 = optional12.get();
this.level.getProfiler().push("broadcast");
wo.broadcastChanges(cai13);
this.level.getProfiler().pop();
bje14 = wo.getPos();
if (this.chunkMap.noPlayersCloseForSpawning(bje14)) {
return;
}
else {
cai13.setInhabitedTime(cai13.getInhabitedTime() + n);
if (b && (this.spawnEnemies || this.spawnFriendlies) && this.level.getWorldBorder().isWithinBounds(cai13.getPos())) {
this.level.getProfiler().push("spawner");
for (length = array.length; i < length; ++i) {
aky18 = array[i];
if (aky18 != MobCategory.MISC) {
if (!aky18.isFriendly() || this.spawnFriendlies) {
if (aky18.isFriendly() || this.spawnEnemies) {
if (!aky18.isPersistent() || b2) {
integer11 = aky18.getMaxInstancesPerChunk() * n2 / ServerChunkCache.MAGIC_NUMBER;
if (object2IntMap15.getInt(aky18) <= integer11) {
NaturalSpawner.spawnCategoryForChunk(aky18, this.level, cai13, fk11);
}
}
}
}
}
}
this.level.getProfiler().pop();
}
this.level.tickChunk(cai13, integer12);
return;
}
}
});
this.level.getProfiler().push("customSpawners");
if (boolean8) {
this.generator.tickCustomSpawners(this.level, this.spawnEnemies, this.spawnFriendlies);
}
this.level.getProfiler().pop();
this.level.getProfiler().pop();
}
this.chunkMap.tick();
}
@Override
public String gatherStats() {
return "ServerChunkCache: " + this.getLoadedChunksCount();
}
@VisibleForTesting
public int getPendingTasksCount() {
return this.mainThreadProcessor.getPendingTasksCount();
}
public ChunkGenerator<?> getGenerator() {
return this.generator;
}
public int getLoadedChunksCount() {
return this.chunkMap.size();
}
public void blockChanged(final BlockPos fk) {
final int integer3 = fk.getX() >> 4;
final int integer4 = fk.getZ() >> 4;
final ChunkHolder wo5 = this.getVisibleChunkIfPresent(ChunkPos.asLong(integer3, integer4));
if (wo5 != null) {
wo5.blockChanged(fk.getX() & 0xF, fk.getY(), fk.getZ() & 0xF);
}
}
@Override
public void onLightUpdate(final LightLayer bkc, final SectionPos gd) {
final ChunkHolder wo4;
this.mainThreadProcessor.execute(() -> {
wo4 = this.getVisibleChunkIfPresent(gd.chunk().toLong());
if (wo4 != null) {
wo4.sectionLightChanged(bkc, gd.y());
}
});
}
public <T> void addRegionTicket(final TicketType<T> xi, final ChunkPos bje, final int integer, final T object) {
this.distanceManager.<T>addRegionTicket(xi, bje, integer, object);
}
public <T> void removeRegionTicket(final TicketType<T> xi, final ChunkPos bje, final int integer, final T object) {
this.distanceManager.<T>removeRegionTicket(xi, bje, integer, object);
}
@Override
public void updateChunkForced(final ChunkPos bje, final boolean boolean2) {
this.distanceManager.updateChunkForced(bje, boolean2);
}
public void move(final ServerPlayer xe) {
this.chunkMap.move(xe);
}
public void removeEntity(final Entity akn) {
this.chunkMap.removeEntity(akn);
}
public void addEntity(final Entity akn) {
this.chunkMap.addEntity(akn);
}
public void broadcastAndSend(final Entity akn, final Packet<?> lt) {
this.chunkMap.broadcastAndSend(akn, lt);
}
public void broadcast(final Entity akn, final Packet<?> lt) {
this.chunkMap.broadcast(akn, lt);
}
public void setViewDistance(final int integer) {
this.chunkMap.setViewDistance(integer);
}
@Override
public void setSpawnSettings(final boolean boolean1, final boolean boolean2) {
this.spawnEnemies = boolean1;
this.spawnFriendlies = boolean2;
}
public String getChunkDebugData(final ChunkPos bje) {
return this.chunkMap.getChunkDebugData(bje);
}
public DimensionDataStorage getDataStorage() {
return this.dataStorage;
}
public PoiManager getPoiManager() {
return this.chunkMap.getPoiManager();
}
static {
MAGIC_NUMBER = (int)Math.pow(17.0, 2.0);
CHUNK_STATUSES = ChunkStatus.getStatusList();
}
final class MainThreadExecutor extends BlockableEventLoop<Runnable> {
private MainThreadExecutor(final Level bjt) {
super("Chunk source main thread executor for " + Registry.DIMENSION_TYPE.getKey(bjt.getDimension().getType()));
}
@Override
protected Runnable wrapRunnable(final Runnable runnable) {
return runnable;
}
@Override
protected boolean shouldRun(final Runnable runnable) {
return true;
}
@Override
protected boolean scheduleExecutables() {
return true;
}
@Override
protected Thread getRunningThread() {
return ServerChunkCache.this.mainThread;
}
@Override
protected boolean pollTask() {
if (ServerChunkCache.this.runDistanceManagerUpdates()) {
return true;
}
ServerChunkCache.this.lightEngine.tryScheduleUpdate();
return super.pollTask();
}
}
}