Code/net/minecraft/server/level/GenerationChunkHolder.java

321 lines
13 KiB
Java
Raw Permalink Normal View History

2025-07-01 06:20:03 +00:00
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 javax.annotation.Nullable;
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;
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 p_342238_) {
this.pos = p_342238_;
if (p_342238_.getChessboardDistance(ChunkPos.ZERO) > ChunkPos.MAX_COORDINATE_VALUE) {
throw new IllegalStateException("Trying to create chunk out of reasonable bounds: " + p_342238_);
}
}
public CompletableFuture<ChunkResult<ChunkAccess>> scheduleChunkGenerationTask(ChunkStatus p_342080_, ChunkMap p_343602_) {
if (this.isStatusDisallowed(p_342080_)) {
return UNLOADED_CHUNK_FUTURE;
} else {
CompletableFuture<ChunkResult<ChunkAccess>> completablefuture = this.getOrCreateFuture(p_342080_);
if (completablefuture.isDone()) {
return completablefuture;
} else {
ChunkGenerationTask chunkgenerationtask = this.task.get();
if (chunkgenerationtask == null || p_342080_.isAfter(chunkgenerationtask.targetStatus)) {
this.rescheduleChunkTask(p_343602_, p_342080_);
}
return completablefuture;
}
}
}
CompletableFuture<ChunkResult<ChunkAccess>> applyStep(ChunkStep p_344844_, GeneratingChunkMap p_342173_, StaticCache2D<GenerationChunkHolder> p_343026_) {
if (this.isStatusDisallowed(p_344844_.targetStatus())) {
return UNLOADED_CHUNK_FUTURE;
} else {
return this.acquireStatusBump(p_344844_.targetStatus()) ? p_342173_.applyStep(this, p_344844_, p_343026_).handle((p_343850_, p_344393_) -> {
if (p_344393_ != null) {
CrashReport crashreport = CrashReport.forThrowable(p_344393_, "Exception chunk generation/loading");
MinecraftServer.setFatalException(new ReportedException(crashreport));
} else {
this.completeFuture(p_344844_.targetStatus(), p_343850_);
}
return ChunkResult.of(p_343850_);
}) : this.getOrCreateFuture(p_344844_.targetStatus());
}
}
protected void updateHighestAllowedStatus(ChunkMap p_345117_) {
ChunkStatus chunkstatus = this.highestAllowedStatus;
ChunkStatus chunkstatus1 = ChunkLevel.generationStatus(this.getTicketLevel());
this.highestAllowedStatus = chunkstatus1;
boolean flag = chunkstatus != null && (chunkstatus1 == null || chunkstatus1.isBefore(chunkstatus));
if (flag) {
this.failAndClearPendingFuturesBetween(chunkstatus1, chunkstatus);
if (this.task.get() != null) {
this.rescheduleChunkTask(p_345117_, this.findHighestStatusWithPendingFuture(chunkstatus1));
}
}
}
public void replaceProtoChunk(ImposterProtoChunk p_343560_) {
CompletableFuture<ChunkResult<ChunkAccess>> completablefuture = CompletableFuture.completedFuture(ChunkResult.of(p_343560_));
for (int i = 0; i < this.futures.length() - 1; i++) {
CompletableFuture<ChunkResult<ChunkAccess>> completablefuture1 = this.futures.get(i);
Objects.requireNonNull(completablefuture1);
ChunkAccess chunkaccess = completablefuture1.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, completablefuture1, completablefuture)) {
throw new IllegalStateException("Future changed by other thread while trying to replace it");
}
}
}
void removeTask(ChunkGenerationTask p_342447_) {
this.task.compareAndSet(p_342447_, null);
}
private void rescheduleChunkTask(ChunkMap p_344344_, @Nullable ChunkStatus p_343189_) {
ChunkGenerationTask chunkgenerationtask;
if (p_343189_ != null) {
chunkgenerationtask = p_344344_.scheduleGenerationTask(p_343189_, this.getPos());
} else {
chunkgenerationtask = null;
}
ChunkGenerationTask chunkgenerationtask1 = this.task.getAndSet(chunkgenerationtask);
if (chunkgenerationtask1 != null) {
chunkgenerationtask1.markForCancellation();
}
}
private CompletableFuture<ChunkResult<ChunkAccess>> getOrCreateFuture(ChunkStatus p_342279_) {
if (this.isStatusDisallowed(p_342279_)) {
return UNLOADED_CHUNK_FUTURE;
} else {
int i = p_342279_.getIndex();
CompletableFuture<ChunkResult<ChunkAccess>> completablefuture = this.futures.get(i);
while (completablefuture == null) {
CompletableFuture<ChunkResult<ChunkAccess>> completablefuture1 = new CompletableFuture<>();
completablefuture = this.futures.compareAndExchange(i, null, completablefuture1);
if (completablefuture == null) {
if (this.isStatusDisallowed(p_342279_)) {
this.failAndClearPendingFuture(i, completablefuture1);
return UNLOADED_CHUNK_FUTURE;
}
return completablefuture1;
}
}
return completablefuture;
}
}
private void failAndClearPendingFuturesBetween(@Nullable ChunkStatus p_343092_, ChunkStatus p_345118_) {
int i = p_343092_ == null ? 0 : p_343092_.getIndex() + 1;
int j = p_345118_.getIndex();
for (int k = i; k <= j; k++) {
CompletableFuture<ChunkResult<ChunkAccess>> completablefuture = this.futures.get(k);
if (completablefuture != null) {
this.failAndClearPendingFuture(k, completablefuture);
}
}
}
private void failAndClearPendingFuture(int p_342343_, CompletableFuture<ChunkResult<ChunkAccess>> p_345346_) {
if (p_345346_.complete(UNLOADED_CHUNK) && !this.futures.compareAndSet(p_342343_, p_345346_, null)) {
throw new IllegalStateException("Nothing else should replace the future here");
}
}
private void completeFuture(ChunkStatus p_342456_, ChunkAccess p_342625_) {
ChunkResult<ChunkAccess> chunkresult = ChunkResult.of(p_342625_);
int i = p_342456_.getIndex();
while (true) {
CompletableFuture<ChunkResult<ChunkAccess>> completablefuture = this.futures.get(i);
if (completablefuture == null) {
if (this.futures.compareAndSet(i, null, CompletableFuture.completedFuture(chunkresult))) {
return;
}
} else {
if (completablefuture.complete(chunkresult)) {
return;
}
if (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 p_342852_) {
if (p_342852_ == null) {
return null;
} else {
ChunkStatus chunkstatus = p_342852_;
for (ChunkStatus chunkstatus1 = this.startedWork.get();
chunkstatus1 == null || chunkstatus.isAfter(chunkstatus1);
chunkstatus = chunkstatus.getParent()
) {
if (this.futures.get(chunkstatus.getIndex()) != null) {
return chunkstatus;
}
if (chunkstatus == ChunkStatus.EMPTY) {
break;
}
}
return null;
}
}
private boolean acquireStatusBump(ChunkStatus p_343283_) {
ChunkStatus chunkstatus = p_343283_ == ChunkStatus.EMPTY ? null : p_343283_.getParent();
ChunkStatus chunkstatus1 = this.startedWork.compareAndExchange(chunkstatus, p_343283_);
if (chunkstatus1 == chunkstatus) {
return true;
} else if (chunkstatus1 != null && !p_343283_.isAfter(chunkstatus1)) {
return false;
} else {
throw new IllegalStateException("Unexpected last startedWork status: " + chunkstatus1 + " while trying to start: " + p_343283_);
}
}
private boolean isStatusDisallowed(ChunkStatus p_342117_) {
ChunkStatus chunkstatus = this.highestAllowedStatus;
return chunkstatus == null || p_342117_.isAfter(chunkstatus);
}
protected abstract void addSaveDependency(CompletableFuture<?> p_363915_);
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 p_342422_) {
CompletableFuture<ChunkResult<ChunkAccess>> completablefuture = this.futures.get(p_342422_.getIndex());
return completablefuture == null ? null : completablefuture.getNow(NOT_DONE_YET).orElse(null);
}
@Nullable
public ChunkAccess getChunkIfPresent(ChunkStatus p_342964_) {
return this.isStatusDisallowed(p_342964_) ? null : this.getChunkIfPresentUnchecked(p_342964_);
}
@Nullable
public ChunkAccess getLatestChunk() {
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 = this.futures.get(ChunkStatus.EMPTY.getIndex());
ChunkAccess chunkaccess = completablefuture == null ? null : 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(CHUNK_STATUSES.get(i), this.futures.get(i)));
}
return list;
}
@Nullable
@VisibleForDebug
public ChunkStatus getLatestStatus() {
for (int i = CHUNK_STATUSES.size() - 1; i >= 0; i--) {
ChunkStatus chunkstatus = CHUNK_STATUSES.get(i);
ChunkAccess chunkaccess = this.getChunkIfPresentUnchecked(chunkstatus);
if (chunkaccess != null) {
return chunkstatus;
}
}
return null;
}
}