615 lines
24 KiB
Java
615 lines
24 KiB
Java
/*
|
|
* Decompiled with CFR 0.152.
|
|
*
|
|
* Could not load the following classes:
|
|
* com.google.common.annotations.VisibleForTesting
|
|
* com.mojang.datafixers.DataFixer
|
|
* com.mojang.logging.LogUtils
|
|
* it.unimi.dsi.fastutil.longs.LongSet
|
|
* it.unimi.dsi.fastutil.objects.ObjectArrayList
|
|
* it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet
|
|
* org.jspecify.annotations.Nullable
|
|
* org.slf4j.Logger
|
|
*/
|
|
package net.minecraft.server.level;
|
|
|
|
import com.google.common.annotations.VisibleForTesting;
|
|
import com.mojang.datafixers.DataFixer;
|
|
import com.mojang.logging.LogUtils;
|
|
import it.unimi.dsi.fastutil.longs.LongSet;
|
|
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
|
import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet;
|
|
import java.io.IOException;
|
|
import java.nio.file.Path;
|
|
import java.util.Arrays;
|
|
import java.util.List;
|
|
import java.util.Objects;
|
|
import java.util.Set;
|
|
import java.util.concurrent.CompletableFuture;
|
|
import java.util.concurrent.CompletionStage;
|
|
import java.util.concurrent.Executor;
|
|
import java.util.function.BooleanSupplier;
|
|
import java.util.function.Consumer;
|
|
import java.util.function.Supplier;
|
|
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.server.MinecraftServer;
|
|
import net.minecraft.server.level.ChunkHolder;
|
|
import net.minecraft.server.level.ChunkLevel;
|
|
import net.minecraft.server.level.ChunkMap;
|
|
import net.minecraft.server.level.ChunkResult;
|
|
import net.minecraft.server.level.DistanceManager;
|
|
import net.minecraft.server.level.GenerationChunkHolder;
|
|
import net.minecraft.server.level.ServerLevel;
|
|
import net.minecraft.server.level.ServerPlayer;
|
|
import net.minecraft.server.level.ThreadedLevelLightEngine;
|
|
import net.minecraft.server.level.Ticket;
|
|
import net.minecraft.server.level.TicketType;
|
|
import net.minecraft.util.FileUtil;
|
|
import net.minecraft.util.Util;
|
|
import net.minecraft.util.VisibleForDebug;
|
|
import net.minecraft.util.profiling.Profiler;
|
|
import net.minecraft.util.profiling.ProfilerFiller;
|
|
import net.minecraft.util.thread.BlockableEventLoop;
|
|
import net.minecraft.world.entity.Entity;
|
|
import net.minecraft.world.entity.MobCategory;
|
|
import net.minecraft.world.entity.ai.village.poi.PoiManager;
|
|
import net.minecraft.world.level.ChunkPos;
|
|
import net.minecraft.world.level.Level;
|
|
import net.minecraft.world.level.LightLayer;
|
|
import net.minecraft.world.level.LocalMobCapCalculator;
|
|
import net.minecraft.world.level.NaturalSpawner;
|
|
import net.minecraft.world.level.TicketStorage;
|
|
import net.minecraft.world.level.chunk.ChunkAccess;
|
|
import net.minecraft.world.level.chunk.ChunkGenerator;
|
|
import net.minecraft.world.level.chunk.ChunkGeneratorStructureState;
|
|
import net.minecraft.world.level.chunk.ChunkSource;
|
|
import net.minecraft.world.level.chunk.LevelChunk;
|
|
import net.minecraft.world.level.chunk.LightChunk;
|
|
import net.minecraft.world.level.chunk.status.ChunkStatus;
|
|
import net.minecraft.world.level.chunk.storage.ChunkScanAccess;
|
|
import net.minecraft.world.level.entity.ChunkStatusUpdateListener;
|
|
import net.minecraft.world.level.gamerules.GameRules;
|
|
import net.minecraft.world.level.levelgen.RandomState;
|
|
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
|
|
import net.minecraft.world.level.storage.DimensionDataStorage;
|
|
import net.minecraft.world.level.storage.LevelStorageSource;
|
|
import org.jspecify.annotations.Nullable;
|
|
import org.slf4j.Logger;
|
|
|
|
public class ServerChunkCache
|
|
extends ChunkSource {
|
|
private static final Logger LOGGER = LogUtils.getLogger();
|
|
private final DistanceManager distanceManager;
|
|
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 final TicketStorage ticketStorage;
|
|
private long lastInhabitedUpdate;
|
|
private boolean spawnEnemies = true;
|
|
private static final int CACHE_SIZE = 4;
|
|
private final long[] lastChunkPos = new long[4];
|
|
private final @Nullable ChunkStatus[] lastChunkStatus = new ChunkStatus[4];
|
|
private final @Nullable ChunkAccess[] lastChunk = new ChunkAccess[4];
|
|
private final List<LevelChunk> spawningChunks = new ObjectArrayList();
|
|
private final Set<ChunkHolder> chunkHoldersToBroadcast = new ReferenceOpenHashSet();
|
|
@VisibleForDebug
|
|
private @Nullable NaturalSpawner.SpawnState lastSpawnState;
|
|
|
|
public ServerChunkCache(ServerLevel level, LevelStorageSource.LevelStorageAccess levelStorage, DataFixer fixerUpper, StructureTemplateManager structureTemplateManager, Executor executor, ChunkGenerator generator, int viewDistance, int simulationDistance, boolean syncWrites, ChunkStatusUpdateListener chunkStatusListener, Supplier<DimensionDataStorage> overworldDataStorage) {
|
|
this.level = level;
|
|
this.mainThreadProcessor = new MainThreadExecutor(level);
|
|
this.mainThread = Thread.currentThread();
|
|
Path dataFolder = levelStorage.getDimensionPath(level.dimension()).resolve("data");
|
|
try {
|
|
FileUtil.createDirectoriesSafe(dataFolder);
|
|
}
|
|
catch (IOException e) {
|
|
LOGGER.error("Failed to create dimension data storage directory", (Throwable)e);
|
|
}
|
|
this.dataStorage = new DimensionDataStorage(dataFolder, fixerUpper, level.registryAccess());
|
|
this.ticketStorage = this.dataStorage.computeIfAbsent(TicketStorage.TYPE);
|
|
this.chunkMap = new ChunkMap(level, levelStorage, fixerUpper, structureTemplateManager, executor, this.mainThreadProcessor, this, generator, chunkStatusListener, overworldDataStorage, this.ticketStorage, viewDistance, syncWrites);
|
|
this.lightEngine = this.chunkMap.getLightEngine();
|
|
this.distanceManager = this.chunkMap.getDistanceManager();
|
|
this.distanceManager.updateSimulationDistance(simulationDistance);
|
|
this.clearCache();
|
|
}
|
|
|
|
@Override
|
|
public ThreadedLevelLightEngine getLightEngine() {
|
|
return this.lightEngine;
|
|
}
|
|
|
|
private @Nullable ChunkHolder getVisibleChunkIfPresent(long key) {
|
|
return this.chunkMap.getVisibleChunkIfPresent(key);
|
|
}
|
|
|
|
private void storeInCache(long pos, @Nullable ChunkAccess chunk, ChunkStatus status) {
|
|
for (int i = 3; i > 0; --i) {
|
|
this.lastChunkPos[i] = this.lastChunkPos[i - 1];
|
|
this.lastChunkStatus[i] = this.lastChunkStatus[i - 1];
|
|
this.lastChunk[i] = this.lastChunk[i - 1];
|
|
}
|
|
this.lastChunkPos[0] = pos;
|
|
this.lastChunkStatus[0] = status;
|
|
this.lastChunk[0] = chunk;
|
|
}
|
|
|
|
@Override
|
|
public @Nullable ChunkAccess getChunk(int x, int z, ChunkStatus targetStatus, boolean loadOrGenerate) {
|
|
if (Thread.currentThread() != this.mainThread) {
|
|
return CompletableFuture.supplyAsync(() -> this.getChunk(x, z, targetStatus, loadOrGenerate), this.mainThreadProcessor).join();
|
|
}
|
|
ProfilerFiller profiler = Profiler.get();
|
|
profiler.incrementCounter("getChunk");
|
|
long pos = ChunkPos.asLong(x, z);
|
|
for (int i = 0; i < 4; ++i) {
|
|
ChunkAccess chunkAccess;
|
|
if (pos != this.lastChunkPos[i] || targetStatus != this.lastChunkStatus[i] || (chunkAccess = this.lastChunk[i]) == null && loadOrGenerate) continue;
|
|
return chunkAccess;
|
|
}
|
|
profiler.incrementCounter("getChunkCacheMiss");
|
|
CompletableFuture<ChunkResult<ChunkAccess>> serverFuture = this.getChunkFutureMainThread(x, z, targetStatus, loadOrGenerate);
|
|
this.mainThreadProcessor.managedBlock(serverFuture::isDone);
|
|
ChunkResult<ChunkAccess> chunkResult = serverFuture.join();
|
|
ChunkAccess chunk = chunkResult.orElse(null);
|
|
if (chunk == null && loadOrGenerate) {
|
|
throw Util.pauseInIde(new IllegalStateException("Chunk not there when requested: " + chunkResult.getError()));
|
|
}
|
|
this.storeInCache(pos, chunk, targetStatus);
|
|
return chunk;
|
|
}
|
|
|
|
@Override
|
|
public @Nullable LevelChunk getChunkNow(int x, int z) {
|
|
if (Thread.currentThread() != this.mainThread) {
|
|
return null;
|
|
}
|
|
Profiler.get().incrementCounter("getChunkNow");
|
|
long pos = ChunkPos.asLong(x, z);
|
|
for (int i = 0; i < 4; ++i) {
|
|
if (pos != this.lastChunkPos[i] || this.lastChunkStatus[i] != ChunkStatus.FULL) continue;
|
|
ChunkAccess chunkAccess = this.lastChunk[i];
|
|
return chunkAccess instanceof LevelChunk ? (LevelChunk)chunkAccess : null;
|
|
}
|
|
ChunkHolder chunkHolder = this.getVisibleChunkIfPresent(pos);
|
|
if (chunkHolder == null) {
|
|
return null;
|
|
}
|
|
ChunkAccess chunk = chunkHolder.getChunkIfPresent(ChunkStatus.FULL);
|
|
if (chunk != null) {
|
|
this.storeInCache(pos, chunk, ChunkStatus.FULL);
|
|
if (chunk instanceof LevelChunk) {
|
|
return (LevelChunk)chunk;
|
|
}
|
|
}
|
|
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<ChunkResult<ChunkAccess>> getChunkFuture(int x, int z, ChunkStatus targetStatus, boolean loadOrGenerate) {
|
|
CompletionStage<ChunkResult<ChunkAccess>> serverFuture;
|
|
boolean isMainThread;
|
|
boolean bl = isMainThread = Thread.currentThread() == this.mainThread;
|
|
if (isMainThread) {
|
|
serverFuture = this.getChunkFutureMainThread(x, z, targetStatus, loadOrGenerate);
|
|
this.mainThreadProcessor.managedBlock(() -> serverFuture.isDone());
|
|
} else {
|
|
serverFuture = CompletableFuture.supplyAsync(() -> this.getChunkFutureMainThread(x, z, targetStatus, loadOrGenerate), this.mainThreadProcessor).thenCompose(chunk -> chunk);
|
|
}
|
|
return serverFuture;
|
|
}
|
|
|
|
private CompletableFuture<ChunkResult<ChunkAccess>> getChunkFutureMainThread(int x, int z, ChunkStatus targetStatus, boolean loadOrGenerate) {
|
|
ChunkPos pos = new ChunkPos(x, z);
|
|
long key = pos.toLong();
|
|
int targetTicketLevel = ChunkLevel.byStatus(targetStatus);
|
|
ChunkHolder chunkHolder = this.getVisibleChunkIfPresent(key);
|
|
if (loadOrGenerate) {
|
|
this.addTicket(new Ticket(TicketType.UNKNOWN, targetTicketLevel), pos);
|
|
if (this.chunkAbsent(chunkHolder, targetTicketLevel)) {
|
|
ProfilerFiller profiler = Profiler.get();
|
|
profiler.push("chunkLoad");
|
|
this.runDistanceManagerUpdates();
|
|
chunkHolder = this.getVisibleChunkIfPresent(key);
|
|
profiler.pop();
|
|
if (this.chunkAbsent(chunkHolder, targetTicketLevel)) {
|
|
throw Util.pauseInIde(new IllegalStateException("No chunk holder after ticket has been added"));
|
|
}
|
|
}
|
|
}
|
|
if (this.chunkAbsent(chunkHolder, targetTicketLevel)) {
|
|
return GenerationChunkHolder.UNLOADED_CHUNK_FUTURE;
|
|
}
|
|
return chunkHolder.scheduleChunkGenerationTask(targetStatus, this.chunkMap);
|
|
}
|
|
|
|
private boolean chunkAbsent(@Nullable ChunkHolder chunkHolder, int targetTicketLevel) {
|
|
return chunkHolder == null || chunkHolder.getTicketLevel() > targetTicketLevel;
|
|
}
|
|
|
|
@Override
|
|
public boolean hasChunk(int x, int z) {
|
|
int targetTicketLevel;
|
|
ChunkHolder chunkHolder = this.getVisibleChunkIfPresent(new ChunkPos(x, z).toLong());
|
|
return !this.chunkAbsent(chunkHolder, targetTicketLevel = ChunkLevel.byStatus(ChunkStatus.FULL));
|
|
}
|
|
|
|
@Override
|
|
public @Nullable LightChunk getChunkForLighting(int x, int z) {
|
|
long key = ChunkPos.asLong(x, z);
|
|
ChunkHolder chunkHolder = this.getVisibleChunkIfPresent(key);
|
|
if (chunkHolder == null) {
|
|
return null;
|
|
}
|
|
return chunkHolder.getChunkIfPresentUnchecked(ChunkStatus.INITIALIZE_LIGHT.getParent());
|
|
}
|
|
|
|
@Override
|
|
public Level getLevel() {
|
|
return this.level;
|
|
}
|
|
|
|
public boolean pollTask() {
|
|
return this.mainThreadProcessor.pollTask();
|
|
}
|
|
|
|
boolean runDistanceManagerUpdates() {
|
|
boolean updated = this.distanceManager.runAllUpdates(this.chunkMap);
|
|
boolean promoted = this.chunkMap.promoteChunkMap();
|
|
this.chunkMap.runGenerationTasks();
|
|
if (updated || promoted) {
|
|
this.clearCache();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public boolean isPositionTicking(long chunkKey) {
|
|
if (!this.level.shouldTickBlocksAt(chunkKey)) {
|
|
return false;
|
|
}
|
|
ChunkHolder holder = this.getVisibleChunkIfPresent(chunkKey);
|
|
if (holder == null) {
|
|
return false;
|
|
}
|
|
return holder.getTickingChunkFuture().getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK).isSuccess();
|
|
}
|
|
|
|
public void save(boolean flushStorage) {
|
|
this.runDistanceManagerUpdates();
|
|
this.chunkMap.saveAllChunks(flushStorage);
|
|
}
|
|
|
|
@Override
|
|
public void close() throws IOException {
|
|
this.save(true);
|
|
this.dataStorage.close();
|
|
this.lightEngine.close();
|
|
this.chunkMap.close();
|
|
}
|
|
|
|
@Override
|
|
public void tick(BooleanSupplier haveTime, boolean tickChunks) {
|
|
ProfilerFiller profiler = Profiler.get();
|
|
profiler.push("purge");
|
|
if (this.level.tickRateManager().runsNormally() || !tickChunks) {
|
|
this.ticketStorage.purgeStaleTickets(this.chunkMap);
|
|
}
|
|
this.runDistanceManagerUpdates();
|
|
profiler.popPush("chunks");
|
|
if (tickChunks) {
|
|
this.tickChunks();
|
|
this.chunkMap.tick();
|
|
}
|
|
profiler.popPush("unload");
|
|
this.chunkMap.tick(haveTime);
|
|
profiler.pop();
|
|
this.clearCache();
|
|
}
|
|
|
|
private void tickChunks() {
|
|
long time = this.level.getGameTime();
|
|
long timeDiff = time - this.lastInhabitedUpdate;
|
|
this.lastInhabitedUpdate = time;
|
|
if (this.level.isDebug()) {
|
|
return;
|
|
}
|
|
ProfilerFiller profiler = Profiler.get();
|
|
profiler.push("pollingChunks");
|
|
if (this.level.tickRateManager().runsNormally()) {
|
|
profiler.push("tickingChunks");
|
|
this.tickChunks(profiler, timeDiff);
|
|
profiler.pop();
|
|
}
|
|
this.broadcastChangedChunks(profiler);
|
|
profiler.pop();
|
|
}
|
|
|
|
private void broadcastChangedChunks(ProfilerFiller profiler) {
|
|
profiler.push("broadcast");
|
|
for (ChunkHolder chunkHolder : this.chunkHoldersToBroadcast) {
|
|
LevelChunk chunk = chunkHolder.getTickingChunk();
|
|
if (chunk == null) continue;
|
|
chunkHolder.broadcastChanges(chunk);
|
|
}
|
|
this.chunkHoldersToBroadcast.clear();
|
|
profiler.pop();
|
|
}
|
|
|
|
/*
|
|
* WARNING - Removed try catching itself - possible behaviour change.
|
|
*/
|
|
private void tickChunks(ProfilerFiller profiler, long timeDiff) {
|
|
List<MobCategory> spawningCategories;
|
|
NaturalSpawner.SpawnState spawnCookie;
|
|
profiler.push("naturalSpawnCount");
|
|
int chunkCount = this.distanceManager.getNaturalSpawnChunkCount();
|
|
this.lastSpawnState = spawnCookie = NaturalSpawner.createState(chunkCount, this.level.getAllEntities(), this::getFullChunk, new LocalMobCapCalculator(this.chunkMap));
|
|
boolean doMobSpawning = this.level.getGameRules().get(GameRules.SPAWN_MOBS);
|
|
int tickSpeed = this.level.getGameRules().get(GameRules.RANDOM_TICK_SPEED);
|
|
if (doMobSpawning) {
|
|
boolean spawnPersistent = this.level.getGameTime() % 400L == 0L;
|
|
spawningCategories = NaturalSpawner.getFilteredSpawningCategories(spawnCookie, true, this.spawnEnemies, spawnPersistent);
|
|
} else {
|
|
spawningCategories = List.of();
|
|
}
|
|
List<LevelChunk> spawningChunks = this.spawningChunks;
|
|
try {
|
|
profiler.popPush("filteringSpawningChunks");
|
|
this.chunkMap.collectSpawningChunks(spawningChunks);
|
|
profiler.popPush("shuffleSpawningChunks");
|
|
Util.shuffle(spawningChunks, this.level.random);
|
|
profiler.popPush("tickSpawningChunks");
|
|
for (LevelChunk chunk2 : spawningChunks) {
|
|
this.tickSpawningChunk(chunk2, timeDiff, spawningCategories, spawnCookie);
|
|
}
|
|
}
|
|
finally {
|
|
spawningChunks.clear();
|
|
}
|
|
profiler.popPush("tickTickingChunks");
|
|
this.chunkMap.forEachBlockTickingChunk(chunk -> this.level.tickChunk((LevelChunk)chunk, tickSpeed));
|
|
if (doMobSpawning) {
|
|
profiler.popPush("customSpawners");
|
|
this.level.tickCustomSpawners(this.spawnEnemies);
|
|
}
|
|
profiler.pop();
|
|
}
|
|
|
|
private void tickSpawningChunk(LevelChunk chunk, long timeDiff, List<MobCategory> spawningCategories, NaturalSpawner.SpawnState spawnCookie) {
|
|
ChunkPos chunkPos = chunk.getPos();
|
|
chunk.incrementInhabitedTime(timeDiff);
|
|
if (this.distanceManager.inEntityTickingRange(chunkPos.toLong())) {
|
|
this.level.tickThunder(chunk);
|
|
}
|
|
if (spawningCategories.isEmpty()) {
|
|
return;
|
|
}
|
|
if (this.level.canSpawnEntitiesInChunk(chunkPos)) {
|
|
NaturalSpawner.spawnForChunk(this.level, chunk, spawnCookie, spawningCategories);
|
|
}
|
|
}
|
|
|
|
private void getFullChunk(long chunkKey, Consumer<LevelChunk> output) {
|
|
ChunkHolder chunkHolder = this.getVisibleChunkIfPresent(chunkKey);
|
|
if (chunkHolder != null) {
|
|
chunkHolder.getFullChunkFuture().getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK).ifSuccess(output);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public String gatherStats() {
|
|
return Integer.toString(this.getLoadedChunksCount());
|
|
}
|
|
|
|
@VisibleForTesting
|
|
public int getPendingTasksCount() {
|
|
return this.mainThreadProcessor.getPendingTasksCount();
|
|
}
|
|
|
|
public ChunkGenerator getGenerator() {
|
|
return this.chunkMap.generator();
|
|
}
|
|
|
|
public ChunkGeneratorStructureState getGeneratorState() {
|
|
return this.chunkMap.generatorState();
|
|
}
|
|
|
|
public RandomState randomState() {
|
|
return this.chunkMap.randomState();
|
|
}
|
|
|
|
@Override
|
|
public int getLoadedChunksCount() {
|
|
return this.chunkMap.size();
|
|
}
|
|
|
|
public void blockChanged(BlockPos pos) {
|
|
int zc;
|
|
int xc = SectionPos.blockToSectionCoord(pos.getX());
|
|
ChunkHolder chunk = this.getVisibleChunkIfPresent(ChunkPos.asLong(xc, zc = SectionPos.blockToSectionCoord(pos.getZ())));
|
|
if (chunk != null && chunk.blockChanged(pos)) {
|
|
this.chunkHoldersToBroadcast.add(chunk);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onLightUpdate(LightLayer layer, SectionPos pos) {
|
|
this.mainThreadProcessor.execute(() -> {
|
|
ChunkHolder chunk = this.getVisibleChunkIfPresent(pos.chunk().toLong());
|
|
if (chunk != null && chunk.sectionLightChanged(layer, pos.y())) {
|
|
this.chunkHoldersToBroadcast.add(chunk);
|
|
}
|
|
});
|
|
}
|
|
|
|
public boolean hasActiveTickets() {
|
|
return this.ticketStorage.shouldKeepDimensionActive();
|
|
}
|
|
|
|
public void addTicket(Ticket ticket, ChunkPos pos) {
|
|
this.ticketStorage.addTicket(ticket, pos);
|
|
}
|
|
|
|
public CompletableFuture<?> addTicketAndLoadWithRadius(TicketType type, ChunkPos pos, int radius) {
|
|
if (!type.doesLoad()) {
|
|
throw new IllegalStateException("Ticket type " + String.valueOf(type) + " does not trigger chunk loading");
|
|
}
|
|
if (type.canExpireIfUnloaded()) {
|
|
throw new IllegalStateException("Ticket type " + String.valueOf(type) + " can expire before it loads, cannot fetch asynchronously");
|
|
}
|
|
this.addTicketWithRadius(type, pos, radius);
|
|
this.runDistanceManagerUpdates();
|
|
ChunkHolder chunkHolder = this.getVisibleChunkIfPresent(pos.toLong());
|
|
Objects.requireNonNull(chunkHolder, "No chunk was scheduled for loading");
|
|
return this.chunkMap.getChunkRangeFuture(chunkHolder, radius, distance -> ChunkStatus.FULL);
|
|
}
|
|
|
|
public void addTicketWithRadius(TicketType type, ChunkPos pos, int radius) {
|
|
this.ticketStorage.addTicketWithRadius(type, pos, radius);
|
|
}
|
|
|
|
public void removeTicketWithRadius(TicketType type, ChunkPos pos, int radius) {
|
|
this.ticketStorage.removeTicketWithRadius(type, pos, radius);
|
|
}
|
|
|
|
@Override
|
|
public boolean updateChunkForced(ChunkPos pos, boolean forced) {
|
|
return this.ticketStorage.updateChunkForced(pos, forced);
|
|
}
|
|
|
|
@Override
|
|
public LongSet getForceLoadedChunks() {
|
|
return this.ticketStorage.getForceLoadedChunks();
|
|
}
|
|
|
|
public void move(ServerPlayer player) {
|
|
if (!player.isRemoved()) {
|
|
this.chunkMap.move(player);
|
|
if (player.isReceivingWaypoints()) {
|
|
this.level.getWaypointManager().updatePlayer(player);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void removeEntity(Entity entity) {
|
|
this.chunkMap.removeEntity(entity);
|
|
}
|
|
|
|
public void addEntity(Entity entity) {
|
|
this.chunkMap.addEntity(entity);
|
|
}
|
|
|
|
public void sendToTrackingPlayersAndSelf(Entity entity, Packet<? super ClientGamePacketListener> packet) {
|
|
this.chunkMap.sendToTrackingPlayersAndSelf(entity, packet);
|
|
}
|
|
|
|
public void sendToTrackingPlayers(Entity entity, Packet<? super ClientGamePacketListener> packet) {
|
|
this.chunkMap.sendToTrackingPlayers(entity, packet);
|
|
}
|
|
|
|
public void setViewDistance(int newDistance) {
|
|
this.chunkMap.setServerViewDistance(newDistance);
|
|
}
|
|
|
|
public void setSimulationDistance(int simulationDistance) {
|
|
this.distanceManager.updateSimulationDistance(simulationDistance);
|
|
}
|
|
|
|
@Override
|
|
public void setSpawnSettings(boolean spawnEnemies) {
|
|
this.spawnEnemies = spawnEnemies;
|
|
}
|
|
|
|
public String getChunkDebugData(ChunkPos pos) {
|
|
return this.chunkMap.getChunkDebugData(pos);
|
|
}
|
|
|
|
public DimensionDataStorage getDataStorage() {
|
|
return this.dataStorage;
|
|
}
|
|
|
|
public PoiManager getPoiManager() {
|
|
return this.chunkMap.getPoiManager();
|
|
}
|
|
|
|
public ChunkScanAccess chunkScanner() {
|
|
return this.chunkMap.chunkScanner();
|
|
}
|
|
|
|
@VisibleForDebug
|
|
public @Nullable NaturalSpawner.SpawnState getLastSpawnState() {
|
|
return this.lastSpawnState;
|
|
}
|
|
|
|
public void deactivateTicketsOnClosing() {
|
|
this.ticketStorage.deactivateTicketsOnClosing();
|
|
}
|
|
|
|
public void onChunkReadyToSend(ChunkHolder chunk) {
|
|
if (chunk.hasChangesToBroadcast()) {
|
|
this.chunkHoldersToBroadcast.add(chunk);
|
|
}
|
|
}
|
|
|
|
private final class MainThreadExecutor
|
|
extends BlockableEventLoop<Runnable> {
|
|
private MainThreadExecutor(Level level) {
|
|
super("Chunk source main thread executor for " + String.valueOf(level.dimension().identifier()));
|
|
}
|
|
|
|
@Override
|
|
public void managedBlock(BooleanSupplier condition) {
|
|
super.managedBlock(() -> MinecraftServer.throwIfFatalException() && condition.getAsBoolean());
|
|
}
|
|
|
|
@Override
|
|
public Runnable wrapRunnable(Runnable runnable) {
|
|
return runnable;
|
|
}
|
|
|
|
@Override
|
|
protected boolean shouldRun(Runnable task) {
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
protected boolean scheduleExecutables() {
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
protected Thread getRunningThread() {
|
|
return ServerChunkCache.this.mainThread;
|
|
}
|
|
|
|
@Override
|
|
protected void doRunTask(Runnable task) {
|
|
Profiler.get().incrementCounter("runTask");
|
|
super.doRunTask(task);
|
|
}
|
|
|
|
@Override
|
|
protected boolean pollTask() {
|
|
if (ServerChunkCache.this.runDistanceManagerUpdates()) {
|
|
return true;
|
|
}
|
|
ServerChunkCache.this.lightEngine.tryScheduleUpdate();
|
|
return super.pollTask();
|
|
}
|
|
}
|
|
}
|
|
|