321 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
			
		
		
	
	
			321 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
| package net.minecraft.server.level;
 | |
| 
 | |
| import com.mojang.datafixers.util.Pair;
 | |
| import java.util.ArrayList;
 | |
| import java.util.List;
 | |
| import java.util.Objects;
 | |
| import java.util.concurrent.CompletableFuture;
 | |
| import java.util.concurrent.atomic.AtomicInteger;
 | |
| import java.util.concurrent.atomic.AtomicReference;
 | |
| import java.util.concurrent.atomic.AtomicReferenceArray;
 | |
| import net.minecraft.CrashReport;
 | |
| import net.minecraft.ReportedException;
 | |
| import net.minecraft.server.MinecraftServer;
 | |
| import net.minecraft.util.StaticCache2D;
 | |
| import net.minecraft.util.VisibleForDebug;
 | |
| import net.minecraft.world.level.ChunkPos;
 | |
| import net.minecraft.world.level.chunk.ChunkAccess;
 | |
| import net.minecraft.world.level.chunk.ImposterProtoChunk;
 | |
| import net.minecraft.world.level.chunk.ProtoChunk;
 | |
| import net.minecraft.world.level.chunk.status.ChunkStatus;
 | |
| import net.minecraft.world.level.chunk.status.ChunkStep;
 | |
| import org.jetbrains.annotations.Nullable;
 | |
| 
 | |
| public abstract class GenerationChunkHolder {
 | |
| 	private static final List<ChunkStatus> CHUNK_STATUSES = ChunkStatus.getStatusList();
 | |
| 	private static final ChunkResult<ChunkAccess> NOT_DONE_YET = ChunkResult.error("Not done yet");
 | |
| 	public static final ChunkResult<ChunkAccess> UNLOADED_CHUNK = ChunkResult.error("Unloaded chunk");
 | |
| 	public static final CompletableFuture<ChunkResult<ChunkAccess>> UNLOADED_CHUNK_FUTURE = CompletableFuture.completedFuture(UNLOADED_CHUNK);
 | |
| 	protected final ChunkPos pos;
 | |
| 	@Nullable
 | |
| 	private volatile ChunkStatus highestAllowedStatus;
 | |
| 	private final AtomicReference<ChunkStatus> startedWork = new AtomicReference();
 | |
| 	private final AtomicReferenceArray<CompletableFuture<ChunkResult<ChunkAccess>>> futures = new AtomicReferenceArray(CHUNK_STATUSES.size());
 | |
| 	private final AtomicReference<ChunkGenerationTask> task = new AtomicReference();
 | |
| 	private final AtomicInteger generationRefCount = new AtomicInteger();
 | |
| 	private volatile CompletableFuture<Void> generationSaveSyncFuture = CompletableFuture.completedFuture(null);
 | |
| 
 | |
| 	public GenerationChunkHolder(ChunkPos pos) {
 | |
| 		this.pos = pos;
 | |
| 		if (pos.getChessboardDistance(ChunkPos.ZERO) > ChunkPos.MAX_COORDINATE_VALUE) {
 | |
| 			throw new IllegalStateException("Trying to create chunk out of reasonable bounds: " + pos);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	public CompletableFuture<ChunkResult<ChunkAccess>> scheduleChunkGenerationTask(ChunkStatus targetStatus, ChunkMap chunkMap) {
 | |
| 		if (this.isStatusDisallowed(targetStatus)) {
 | |
| 			return UNLOADED_CHUNK_FUTURE;
 | |
| 		} else {
 | |
| 			CompletableFuture<ChunkResult<ChunkAccess>> completableFuture = this.getOrCreateFuture(targetStatus);
 | |
| 			if (completableFuture.isDone()) {
 | |
| 				return completableFuture;
 | |
| 			} else {
 | |
| 				ChunkGenerationTask chunkGenerationTask = (ChunkGenerationTask)this.task.get();
 | |
| 				if (chunkGenerationTask == null || targetStatus.isAfter(chunkGenerationTask.targetStatus)) {
 | |
| 					this.rescheduleChunkTask(chunkMap, targetStatus);
 | |
| 				}
 | |
| 
 | |
| 				return completableFuture;
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	CompletableFuture<ChunkResult<ChunkAccess>> applyStep(ChunkStep step, GeneratingChunkMap chunkMap, StaticCache2D<GenerationChunkHolder> cache) {
 | |
| 		if (this.isStatusDisallowed(step.targetStatus())) {
 | |
| 			return UNLOADED_CHUNK_FUTURE;
 | |
| 		} else {
 | |
| 			return this.acquireStatusBump(step.targetStatus()) ? chunkMap.applyStep(this, step, cache).handle((chunkAccess, throwable) -> {
 | |
| 				if (throwable != null) {
 | |
| 					CrashReport crashReport = CrashReport.forThrowable(throwable, "Exception chunk generation/loading");
 | |
| 					MinecraftServer.setFatalException(new ReportedException(crashReport));
 | |
| 				} else {
 | |
| 					this.completeFuture(step.targetStatus(), chunkAccess);
 | |
| 				}
 | |
| 
 | |
| 				return ChunkResult.of(chunkAccess);
 | |
| 			}) : this.getOrCreateFuture(step.targetStatus());
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	protected void updateHighestAllowedStatus(ChunkMap chunkMap) {
 | |
| 		ChunkStatus chunkStatus = this.highestAllowedStatus;
 | |
| 		ChunkStatus chunkStatus2 = ChunkLevel.generationStatus(this.getTicketLevel());
 | |
| 		this.highestAllowedStatus = chunkStatus2;
 | |
| 		boolean bl = chunkStatus != null && (chunkStatus2 == null || chunkStatus2.isBefore(chunkStatus));
 | |
| 		if (bl) {
 | |
| 			this.failAndClearPendingFuturesBetween(chunkStatus2, chunkStatus);
 | |
| 			if (this.task.get() != null) {
 | |
| 				this.rescheduleChunkTask(chunkMap, this.findHighestStatusWithPendingFuture(chunkStatus2));
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	public void replaceProtoChunk(ImposterProtoChunk chunk) {
 | |
| 		CompletableFuture<ChunkResult<ChunkAccess>> completableFuture = CompletableFuture.completedFuture(ChunkResult.of(chunk));
 | |
| 
 | |
| 		for (int i = 0; i < this.futures.length() - 1; i++) {
 | |
| 			CompletableFuture<ChunkResult<ChunkAccess>> completableFuture2 = (CompletableFuture<ChunkResult<ChunkAccess>>)this.futures.get(i);
 | |
| 			Objects.requireNonNull(completableFuture2);
 | |
| 			ChunkAccess chunkAccess = (ChunkAccess)((ChunkResult)completableFuture2.getNow(NOT_DONE_YET)).orElse(null);
 | |
| 			if (!(chunkAccess instanceof ProtoChunk)) {
 | |
| 				throw new IllegalStateException("Trying to replace a ProtoChunk, but found " + chunkAccess);
 | |
| 			}
 | |
| 
 | |
| 			if (!this.futures.compareAndSet(i, completableFuture2, completableFuture)) {
 | |
| 				throw new IllegalStateException("Future changed by other thread while trying to replace it");
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	void removeTask(ChunkGenerationTask task) {
 | |
| 		this.task.compareAndSet(task, null);
 | |
| 	}
 | |
| 
 | |
| 	private void rescheduleChunkTask(ChunkMap chunkMap, @Nullable ChunkStatus targetStatus) {
 | |
| 		ChunkGenerationTask chunkGenerationTask;
 | |
| 		if (targetStatus != null) {
 | |
| 			chunkGenerationTask = chunkMap.scheduleGenerationTask(targetStatus, this.getPos());
 | |
| 		} else {
 | |
| 			chunkGenerationTask = null;
 | |
| 		}
 | |
| 
 | |
| 		ChunkGenerationTask chunkGenerationTask2 = (ChunkGenerationTask)this.task.getAndSet(chunkGenerationTask);
 | |
| 		if (chunkGenerationTask2 != null) {
 | |
| 			chunkGenerationTask2.markForCancellation();
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	private CompletableFuture<ChunkResult<ChunkAccess>> getOrCreateFuture(ChunkStatus targetStatus) {
 | |
| 		if (this.isStatusDisallowed(targetStatus)) {
 | |
| 			return UNLOADED_CHUNK_FUTURE;
 | |
| 		} else {
 | |
| 			int i = targetStatus.getIndex();
 | |
| 			CompletableFuture<ChunkResult<ChunkAccess>> completableFuture = (CompletableFuture<ChunkResult<ChunkAccess>>)this.futures.get(i);
 | |
| 
 | |
| 			while (completableFuture == null) {
 | |
| 				CompletableFuture<ChunkResult<ChunkAccess>> completableFuture2 = new CompletableFuture();
 | |
| 				completableFuture = (CompletableFuture<ChunkResult<ChunkAccess>>)this.futures.compareAndExchange(i, null, completableFuture2);
 | |
| 				if (completableFuture == null) {
 | |
| 					if (this.isStatusDisallowed(targetStatus)) {
 | |
| 						this.failAndClearPendingFuture(i, completableFuture2);
 | |
| 						return UNLOADED_CHUNK_FUTURE;
 | |
| 					}
 | |
| 
 | |
| 					return completableFuture2;
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			return completableFuture;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	private void failAndClearPendingFuturesBetween(@Nullable ChunkStatus highestAllowableStatus, ChunkStatus currentStatus) {
 | |
| 		int i = highestAllowableStatus == null ? 0 : highestAllowableStatus.getIndex() + 1;
 | |
| 		int j = currentStatus.getIndex();
 | |
| 
 | |
| 		for (int k = i; k <= j; k++) {
 | |
| 			CompletableFuture<ChunkResult<ChunkAccess>> completableFuture = (CompletableFuture<ChunkResult<ChunkAccess>>)this.futures.get(k);
 | |
| 			if (completableFuture != null) {
 | |
| 				this.failAndClearPendingFuture(k, completableFuture);
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	private void failAndClearPendingFuture(int status, CompletableFuture<ChunkResult<ChunkAccess>> future) {
 | |
| 		if (future.complete(UNLOADED_CHUNK) && !this.futures.compareAndSet(status, future, null)) {
 | |
| 			throw new IllegalStateException("Nothing else should replace the future here");
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	private void completeFuture(ChunkStatus targetStatus, ChunkAccess chunkAccess) {
 | |
| 		ChunkResult<ChunkAccess> chunkResult = ChunkResult.of(chunkAccess);
 | |
| 		int i = targetStatus.getIndex();
 | |
| 
 | |
| 		while (true) {
 | |
| 			CompletableFuture<ChunkResult<ChunkAccess>> completableFuture = (CompletableFuture<ChunkResult<ChunkAccess>>)this.futures.get(i);
 | |
| 			if (completableFuture == null) {
 | |
| 				if (this.futures.compareAndSet(i, null, CompletableFuture.completedFuture(chunkResult))) {
 | |
| 					return;
 | |
| 				}
 | |
| 			} else {
 | |
| 				if (completableFuture.complete(chunkResult)) {
 | |
| 					return;
 | |
| 				}
 | |
| 
 | |
| 				if (((ChunkResult)completableFuture.getNow(NOT_DONE_YET)).isSuccess()) {
 | |
| 					throw new IllegalStateException("Trying to complete a future but found it to be completed successfully already");
 | |
| 				}
 | |
| 
 | |
| 				Thread.yield();
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	@Nullable
 | |
| 	private ChunkStatus findHighestStatusWithPendingFuture(@Nullable ChunkStatus generationStatus) {
 | |
| 		if (generationStatus == null) {
 | |
| 			return null;
 | |
| 		} else {
 | |
| 			ChunkStatus chunkStatus = generationStatus;
 | |
| 
 | |
| 			for (ChunkStatus chunkStatus2 = (ChunkStatus)this.startedWork.get();
 | |
| 				chunkStatus2 == null || chunkStatus.isAfter(chunkStatus2);
 | |
| 				chunkStatus = chunkStatus.getParent()
 | |
| 			) {
 | |
| 				if (this.futures.get(chunkStatus.getIndex()) != null) {
 | |
| 					return chunkStatus;
 | |
| 				}
 | |
| 
 | |
| 				if (chunkStatus == ChunkStatus.EMPTY) {
 | |
| 					break;
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			return null;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	private boolean acquireStatusBump(ChunkStatus status) {
 | |
| 		ChunkStatus chunkStatus = status == ChunkStatus.EMPTY ? null : status.getParent();
 | |
| 		ChunkStatus chunkStatus2 = (ChunkStatus)this.startedWork.compareAndExchange(chunkStatus, status);
 | |
| 		if (chunkStatus2 == chunkStatus) {
 | |
| 			return true;
 | |
| 		} else if (chunkStatus2 != null && !status.isAfter(chunkStatus2)) {
 | |
| 			return false;
 | |
| 		} else {
 | |
| 			throw new IllegalStateException("Unexpected last startedWork status: " + chunkStatus2 + " while trying to start: " + status);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	private boolean isStatusDisallowed(ChunkStatus status) {
 | |
| 		ChunkStatus chunkStatus = this.highestAllowedStatus;
 | |
| 		return chunkStatus == null || status.isAfter(chunkStatus);
 | |
| 	}
 | |
| 
 | |
| 	protected abstract void addSaveDependency(CompletableFuture<?> saveDependency);
 | |
| 
 | |
| 	public void increaseGenerationRefCount() {
 | |
| 		if (this.generationRefCount.getAndIncrement() == 0) {
 | |
| 			this.generationSaveSyncFuture = new CompletableFuture();
 | |
| 			this.addSaveDependency(this.generationSaveSyncFuture);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	public void decreaseGenerationRefCount() {
 | |
| 		CompletableFuture<Void> completableFuture = this.generationSaveSyncFuture;
 | |
| 		int i = this.generationRefCount.decrementAndGet();
 | |
| 		if (i == 0) {
 | |
| 			completableFuture.complete(null);
 | |
| 		}
 | |
| 
 | |
| 		if (i < 0) {
 | |
| 			throw new IllegalStateException("More releases than claims. Count: " + i);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	@Nullable
 | |
| 	public ChunkAccess getChunkIfPresentUnchecked(ChunkStatus status) {
 | |
| 		CompletableFuture<ChunkResult<ChunkAccess>> completableFuture = (CompletableFuture<ChunkResult<ChunkAccess>>)this.futures.get(status.getIndex());
 | |
| 		return completableFuture == null ? null : (ChunkAccess)((ChunkResult)completableFuture.getNow(NOT_DONE_YET)).orElse(null);
 | |
| 	}
 | |
| 
 | |
| 	@Nullable
 | |
| 	public ChunkAccess getChunkIfPresent(ChunkStatus status) {
 | |
| 		return this.isStatusDisallowed(status) ? null : this.getChunkIfPresentUnchecked(status);
 | |
| 	}
 | |
| 
 | |
| 	@Nullable
 | |
| 	public ChunkAccess getLatestChunk() {
 | |
| 		ChunkStatus chunkStatus = (ChunkStatus)this.startedWork.get();
 | |
| 		if (chunkStatus == null) {
 | |
| 			return null;
 | |
| 		} else {
 | |
| 			ChunkAccess chunkAccess = this.getChunkIfPresentUnchecked(chunkStatus);
 | |
| 			return chunkAccess != null ? chunkAccess : this.getChunkIfPresentUnchecked(chunkStatus.getParent());
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	@Nullable
 | |
| 	public ChunkStatus getPersistedStatus() {
 | |
| 		CompletableFuture<ChunkResult<ChunkAccess>> completableFuture = (CompletableFuture<ChunkResult<ChunkAccess>>)this.futures.get(ChunkStatus.EMPTY.getIndex());
 | |
| 		ChunkAccess chunkAccess = completableFuture == null ? null : (ChunkAccess)((ChunkResult)completableFuture.getNow(NOT_DONE_YET)).orElse(null);
 | |
| 		return chunkAccess == null ? null : chunkAccess.getPersistedStatus();
 | |
| 	}
 | |
| 
 | |
| 	public ChunkPos getPos() {
 | |
| 		return this.pos;
 | |
| 	}
 | |
| 
 | |
| 	public FullChunkStatus getFullStatus() {
 | |
| 		return ChunkLevel.fullStatus(this.getTicketLevel());
 | |
| 	}
 | |
| 
 | |
| 	public abstract int getTicketLevel();
 | |
| 
 | |
| 	public abstract int getQueueLevel();
 | |
| 
 | |
| 	@VisibleForDebug
 | |
| 	public List<Pair<ChunkStatus, CompletableFuture<ChunkResult<ChunkAccess>>>> getAllFutures() {
 | |
| 		List<Pair<ChunkStatus, CompletableFuture<ChunkResult<ChunkAccess>>>> list = new ArrayList();
 | |
| 
 | |
| 		for (int i = 0; i < CHUNK_STATUSES.size(); i++) {
 | |
| 			list.add(Pair.of((ChunkStatus)CHUNK_STATUSES.get(i), (CompletableFuture)this.futures.get(i)));
 | |
| 		}
 | |
| 
 | |
| 		return list;
 | |
| 	}
 | |
| 
 | |
| 	@Nullable
 | |
| 	@VisibleForDebug
 | |
| 	public ChunkStatus getLatestStatus() {
 | |
| 		for (int i = CHUNK_STATUSES.size() - 1; i >= 0; i--) {
 | |
| 			ChunkStatus chunkStatus = (ChunkStatus)CHUNK_STATUSES.get(i);
 | |
| 			ChunkAccess chunkAccess = this.getChunkIfPresentUnchecked(chunkStatus);
 | |
| 			if (chunkAccess != null) {
 | |
| 				return chunkStatus;
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		return null;
 | |
| 	}
 | |
| }
 |