Skip to content

Commit

Permalink
feat: introduce proto chunk to world generating
Browse files Browse the repository at this point in the history
  • Loading branch information
smartcmd committed Jun 19, 2024
1 parent d96d776 commit 9f42b97
Show file tree
Hide file tree
Showing 7 changed files with 88 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ default Set<EntityPlayer> getPlayerChunkLoaders() {
return getChunkLoaders().stream().filter(EntityPlayer.class::isInstance).map(EntityPlayer.class::cast).collect(Collectors.toSet());
}

@ApiStatus.Internal
void setChunkSetCallback(Runnable callback);

@ApiStatus.Internal
Runnable getChunkSetCallback();

void addChunkLoader(ChunkLoader chunkLoader);

void removeChunkLoader(ChunkLoader chunkLoader);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import org.allaymc.api.utils.HashUtils;

import java.util.concurrent.CompletableFuture;

/**
* Allay Project 2023/7/1
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import lombok.Getter;
import org.allaymc.api.block.type.BlockState;
import org.allaymc.api.block.type.BlockTypes;
import org.allaymc.api.world.chunk.ChunkAccessible;
import org.allaymc.api.world.chunk.UnsafeChunk;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
public interface ChunkService extends ChunkAccessible {
void tick();

CompletableFuture<Chunk> getChunkLoadingFuture(int x, int z);

CompletableFuture<Chunk> getOrLoadChunk(int x, int z);

CompletableFuture<Set<Chunk>> getOrLoadRangedChunk(int x, int z, int range);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ public class AllayChunk implements Chunk {
protected final StampedLock lightLock;
protected final Set<ChunkLoader> chunkLoaders;
protected final Queue<ChunkPacketEntry> chunkPacketQueue;
protected Runnable chunkSetCallback = () -> {};

public AllayChunk(AllayUnsafeChunk unsafeChunk) {
this.unsafeChunk = unsafeChunk;
Expand Down Expand Up @@ -584,6 +585,16 @@ public Set<ChunkLoader> getChunkLoaders() {
return Collections.unmodifiableSet(chunkLoaders);
}

@Override
public void setChunkSetCallback(Runnable callback) {
this.chunkSetCallback = callback;
}

@Override
public Runnable getChunkSetCallback() {
return chunkSetCallback;
}

@Override
public void addChunkLoader(ChunkLoader chunkLoader) {
chunkLoaders.add(chunkLoader);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,11 @@
import org.allaymc.api.world.generator.function.Populator;
import org.allaymc.server.world.chunk.AllayUnsafeChunk;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;

/**
Expand All @@ -43,9 +46,10 @@ public final class AllayWorldGenerator implements WorldGenerator {
private final List<Lighter> lighters;
private final List<EntitySpawner> entitySpawners;
private final Consumer<Dimension> onDimensionSet;
// 存储基本区块,基本区块的ChunkStatus为NOISED
// 基本区块只会在当一个区块生成时需要访问相邻未生成区块时产生
private final Map<Long, Chunk> basicChunks = new Long2ObjectNonBlockingMap<>();
// 原型区块是未完成生成的区块
private final Map<Long, CompletableFuture<Chunk>> protoChunks = new Long2ObjectNonBlockingMap<>();
// 存储已完成生成且正在被载入世界的基本区块
private final Set<Long> beingSetProtoChunk = Collections.newSetFromMap(new Long2ObjectNonBlockingMap<>());

@Getter
private Dimension dimension; // Will be set later
Expand Down Expand Up @@ -96,29 +100,48 @@ public void setDimension(Dimension dimension) {
*/
@Override
public Chunk generateChunk(int x, int z) {
Chunk chunk = basicChunks.remove(HashUtils.hashXZ(x, z));
if (chunk != null) {
statusNoisedToFinished(chunk);
} else {
chunk = AllayUnsafeChunk.builder().emptyChunk(x, z, dimension.getDimensionInfo()).toSafeChunk();
statusEmptyToFinished(chunk);
}
var chunk = getOrGenerateProtoChunk(x, z);
statusNoisedToFinished(chunk);
markProtoChunkBeingSet(x, z);
protoChunks.remove(HashUtils.hashXZ(x, z));
chunk.setChunkSetCallback(() -> afterProtoChunkBeingSet(x, z));
return chunk;
}

private Chunk getOrGenerateBasicChunk(int x, int z) {
return basicChunks.computeIfAbsent(HashUtils.hashXZ(x, z), unused -> generateBasicChunk(x, z));
private Chunk getOrGenerateProtoChunk(int x, int z) {
if (protoChunkBeingSet(x, z)) {
throw new IllegalStateException("Trying to access a proto chunk which is being set: x: " + x + ", z: " + z);
}
CompletableFuture<Chunk> future = new CompletableFuture<>();
var presentFuture = protoChunks.putIfAbsent(HashUtils.hashXZ(x, z), future);
if (presentFuture != null) {
// 原型区块已经在生成或已生成完毕
// 等待生成完毕后返回区块,或直接返回若已生成完毕
return presentFuture.join();
}
var chunk = generateProtoChunk(x, z);
future.complete(chunk);
return chunk;
}

/**
* 生成基本区块,基本区块的ChunkStatus为NOISED
*/
private Chunk generateBasicChunk(int x, int z) {
private Chunk generateProtoChunk(int x, int z) {
var chunk = AllayUnsafeChunk.builder().emptyChunk(x, z, dimension.getDimensionInfo()).toSafeChunk();
statusEmptyToNoised(chunk);
return chunk;
}

private void markProtoChunkBeingSet(int x, int z) {
beingSetProtoChunk.add(HashUtils.hashXZ(x, z));
}

private void afterProtoChunkBeingSet(int x, int z) {
beingSetProtoChunk.remove(HashUtils.hashXZ(x, z));
}

private boolean protoChunkBeingSet(int x, int z) {
return beingSetProtoChunk.contains(HashUtils.hashXZ(x, z));
}

private void statusEmptyToFinished(Chunk chunk) {
statusEmptyToNoised(chunk);
statusNoisedToFinished(chunk);
Expand Down Expand Up @@ -176,11 +199,22 @@ public Chunk getChunk(int x, int z) {
if (x == currentChunk.getX() && z == currentChunk.getZ()) {
return currentChunk;
}
var chunkService = dimension.getChunkService();

// 先获取future,避免在此过程中区块完成加载导致future被删除
CompletableFuture<Chunk> future = chunkService.getChunkLoadingFuture(x, z);
if (protoChunkBeingSet(x, z)) {
// 即使在此过程中future被删除(区块完成加载),此代码依然可以工作因为
// 区块完成加载后,future.join()等效于chunkService.getChunk(x, z)
return future.join();
}

var chunk = chunkService.getChunk(x, z);
// 若区块已生成则直接返回
var chunk = dimension.getChunkService().getChunk(x, z);
if (chunk != null) return chunk;
// 生成基本区块供使用
chunk = getOrGenerateBasicChunk(x, z);

// 获取或生成原型区块供使用
chunk = getOrGenerateProtoChunk(x, z);
return chunk;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
import org.allaymc.api.annotation.SlowOperation;
import org.allaymc.api.datastruct.collections.nb.Long2ObjectNonBlockingMap;
import org.allaymc.api.server.Server;
import org.allaymc.api.utils.AllayComputeThread;
import org.allaymc.api.utils.GameLoop;
import org.allaymc.api.utils.HashUtils;
import org.allaymc.api.utils.MathUtils;
Expand Down Expand Up @@ -124,6 +123,11 @@ public Chunk getChunk(long chunkHash) {
return loadedChunks.get(chunkHash);
}

@Override
public CompletableFuture<Chunk> getChunkLoadingFuture(int x, int z) {
return loadingChunks.get(HashUtils.hashXZ(x, z));
}

@SlowOperation
@Override
public Chunk getOrLoadChunkSynchronously(int x, int z) {
Expand Down Expand Up @@ -192,13 +196,14 @@ public CompletableFuture<Chunk> loadChunk(int x, int z) {
log.error("Error while generating chunk ({},{}) !", x, z, t);
return AllayUnsafeChunk.builder().emptyChunk(x, z, dimension.getDimensionInfo()).toSafeChunk();
})
.thenApply(prepareChunk -> {
prepareChunk.beforeSetChunk(dimension);
setChunk(x, z, prepareChunk);
prepareChunk.afterSetChunk(dimension);
future.complete(prepareChunk);
.thenApply(preparedChunk -> {
preparedChunk.beforeSetChunk(dimension);
setChunk(x, z, preparedChunk);
preparedChunk.getChunkSetCallback().run();
preparedChunk.afterSetChunk(dimension);
future.complete(preparedChunk);
loadingChunks.remove(hashXZ);
return prepareChunk;
return preparedChunk;
});
return future;
}
Expand Down Expand Up @@ -228,6 +233,7 @@ private Chunk loadChunkSynchronously(int x, int z, CompletableFuture<Chunk> futu
}
chunk.beforeSetChunk(dimension);
setChunk(x, z, chunk);
chunk.getChunkSetCallback().run();
chunk.afterSetChunk(dimension);
synchronizedFuture.complete(chunk);
loadingChunks.remove(hash);
Expand Down

0 comments on commit 9f42b97

Please sign in to comment.