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;
|
|
}
|
|
}
|