diff --git a/Allay-API/src/main/java/org/allaymc/api/world/chunk/Chunk.java b/Allay-API/src/main/java/org/allaymc/api/world/chunk/Chunk.java index e3e15e42a..2feaa3173 100644 --- a/Allay-API/src/main/java/org/allaymc/api/world/chunk/Chunk.java +++ b/Allay-API/src/main/java/org/allaymc/api/world/chunk/Chunk.java @@ -24,6 +24,8 @@ public interface Chunk extends UnsafeChunk { @ApiStatus.Internal void tick(); + boolean isLoaded(); + @UnmodifiableView Set getChunkLoaders(); @@ -32,11 +34,18 @@ default Set getPlayerChunkLoaders() { return getChunkLoaders().stream().filter(EntityPlayer.class::isInstance).map(EntityPlayer.class::cast).collect(Collectors.toSet()); } + /** + * Set the callback to be called when the chunk is loaded into the world + * @param callback the callback + */ @ApiStatus.Internal void setChunkSetCallback(Runnable callback); + /** + * Called when the chunk is loaded into the world + */ @ApiStatus.Internal - Runnable getChunkSetCallback(); + void onChunkSet(); void addChunkLoader(ChunkLoader chunkLoader); diff --git a/Allay-API/src/main/java/org/allaymc/api/world/generator/WorldGenerator.java b/Allay-API/src/main/java/org/allaymc/api/world/generator/WorldGenerator.java index bf193a50d..bfdc9a050 100644 --- a/Allay-API/src/main/java/org/allaymc/api/world/generator/WorldGenerator.java +++ b/Allay-API/src/main/java/org/allaymc/api/world/generator/WorldGenerator.java @@ -4,10 +4,11 @@ import org.allaymc.api.world.Dimension; import org.allaymc.api.world.chunk.Chunk; import org.allaymc.api.world.generator.function.EntitySpawner; -import org.allaymc.api.world.generator.function.Noiser; import org.allaymc.api.world.generator.function.Lighter; +import org.allaymc.api.world.generator.function.Noiser; import org.allaymc.api.world.generator.function.Populator; +import java.util.concurrent.CompletableFuture; import java.util.function.Consumer; /** @@ -23,7 +24,14 @@ static WorldGeneratorBuilder builder() { return BUILDER_FACTORY.get().create(); } - Chunk generateChunk(int x, int z); + /** + * 生成一个区块
+ * 此方法可能被任意线程调用,故实现应确保线程安全 + * @param x 区块x坐标 + * @param z 区块z坐标 + * @return 生成的区块 + */ + CompletableFuture generateChunk(int x, int z); String getName(); diff --git a/Allay-API/src/main/java/org/allaymc/api/world/generator/context/OtherChunkAccessibleContext.java b/Allay-API/src/main/java/org/allaymc/api/world/generator/context/OtherChunkAccessibleContext.java index d0cf9ea55..0d5bf2780 100644 --- a/Allay-API/src/main/java/org/allaymc/api/world/generator/context/OtherChunkAccessibleContext.java +++ b/Allay-API/src/main/java/org/allaymc/api/world/generator/context/OtherChunkAccessibleContext.java @@ -3,6 +3,7 @@ import lombok.Getter; import org.allaymc.api.block.type.BlockState; import org.allaymc.api.block.type.BlockTypes; +import org.allaymc.api.world.Dimension; import org.allaymc.api.world.chunk.ChunkAccessible; import org.allaymc.api.world.chunk.UnsafeChunk; @@ -13,6 +14,8 @@ */ public abstract class OtherChunkAccessibleContext extends Context { + private static BlockState AIR = BlockTypes.AIR_TYPE.getDefaultState(); + @Getter protected ChunkAccessible chunkAccessor; @@ -35,7 +38,12 @@ public void setBlockState(int x, int y, int z, BlockState blockState, int layer) private void setBlockStateInOtherChunk(int x, int y, int z, BlockState blockState, int layer) { var chunk = chunkAccessor.getChunk(x >> 4, z >> 4); + if (chunk == null) return; chunk.setBlockState(x & 15, y, z & 15, blockState, layer); + if (chunk.isLoaded()) { + // 区块已经载入世界了,所以需要发送方块更新包 + chunk.addChunkPacket(Dimension.createBlockUpdatePacket(blockState, x, y, z, layer)); + } } public BlockState getBlockState(int x, int y, int z) { @@ -51,6 +59,7 @@ public BlockState getBlockState(int x, int y, int z, int layer) { private BlockState getBlockStateInOtherChunk(int x, int y, int z, int layer) { var chunk = chunkAccessor.getChunk(x >> 4, z >> 4); + if (chunk == null) return AIR; return chunk.getBlockState(x & 15, y, z & 15); } diff --git a/Allay-Server/src/main/java/org/allaymc/server/block/component/common/BlockBaseComponentImpl.java b/Allay-Server/src/main/java/org/allaymc/server/block/component/common/BlockBaseComponentImpl.java index 62eb37fc6..b15b87d83 100644 --- a/Allay-Server/src/main/java/org/allaymc/server/block/component/common/BlockBaseComponentImpl.java +++ b/Allay-Server/src/main/java/org/allaymc/server/block/component/common/BlockBaseComponentImpl.java @@ -1,11 +1,14 @@ package org.allaymc.server.block.component.common; - import lombok.Getter; +import lombok.Getter; import org.allaymc.api.block.BlockBehavior; import org.allaymc.api.block.component.common.BlockBaseComponent; - import org.allaymc.api.block.component.common.PlayerInteractInfo; - import org.allaymc.api.block.component.event.*; - import org.allaymc.api.block.data.BlockFace; +import org.allaymc.api.block.component.common.PlayerInteractInfo; +import org.allaymc.api.block.component.event.BlockOnInteractEvent; +import org.allaymc.api.block.component.event.BlockOnNeighborUpdateEvent; +import org.allaymc.api.block.component.event.BlockOnPlaceEvent; +import org.allaymc.api.block.component.event.BlockOnReplaceEvent; +import org.allaymc.api.block.data.BlockFace; import org.allaymc.api.block.data.BlockStateWithPos; import org.allaymc.api.block.type.BlockState; import org.allaymc.api.block.type.BlockType; @@ -17,9 +20,9 @@ import org.allaymc.api.item.enchantment.type.EnchantmentSilkTouchType; import org.allaymc.api.utils.Identifier; import org.allaymc.api.world.Dimension; - import org.cloudburstmc.protocol.bedrock.data.GameType; - import org.joml.Vector3f; - import org.joml.Vector3ic; +import org.cloudburstmc.protocol.bedrock.data.GameType; +import org.joml.Vector3f; +import org.joml.Vector3ic; /** * Allay Project 2023/4/8 @@ -91,7 +94,7 @@ public void onBreak(BlockStateWithPos blockState, ItemStack usedItem, EntityPlay @Override public boolean isDroppable(BlockStateWithPos blockState, ItemStack usedItem, EntityPlayer player) { - if (player.getGameType() == GameType.CREATIVE) return false; + if (player != null && player.getGameType() == GameType.CREATIVE) return false; return blockState.blockState().getBlockAttributes().canHarvestWithHand() || usedItem.isCorrectToolFor(blockState.blockState()); } diff --git a/Allay-Server/src/main/java/org/allaymc/server/world/chunk/AllayChunk.java b/Allay-Server/src/main/java/org/allaymc/server/world/chunk/AllayChunk.java index 90cdcaedb..d39dc0e9c 100644 --- a/Allay-Server/src/main/java/org/allaymc/server/world/chunk/AllayChunk.java +++ b/Allay-Server/src/main/java/org/allaymc/server/world/chunk/AllayChunk.java @@ -7,6 +7,7 @@ import io.netty.buffer.Unpooled; import io.netty.util.internal.PlatformDependent; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.allaymc.api.block.type.BlockState; import org.allaymc.api.blockentity.BlockEntity; @@ -14,12 +15,7 @@ import org.allaymc.api.world.Dimension; import org.allaymc.api.world.DimensionInfo; import org.allaymc.api.world.biome.BiomeType; -import org.allaymc.api.world.chunk.Chunk; -import org.allaymc.api.world.chunk.ChunkLoader; -import org.allaymc.api.world.chunk.ChunkSection; -import org.allaymc.api.world.chunk.ChunkState; -import org.allaymc.api.world.chunk.UnsafeChunk; -import org.allaymc.api.world.chunk.UnsafeChunkOperate; +import org.allaymc.api.world.chunk.*; import org.cloudburstmc.nbt.NbtUtils; import org.cloudburstmc.protocol.bedrock.packet.BedrockPacket; import org.cloudburstmc.protocol.bedrock.packet.LevelChunkPacket; @@ -28,12 +24,7 @@ import javax.annotation.concurrent.ThreadSafe; import java.io.IOException; -import java.util.Collection; -import java.util.Collections; -import java.util.Map; -import java.util.Objects; -import java.util.Queue; -import java.util.Set; +import java.util.*; import java.util.concurrent.locks.StampedLock; import java.util.function.Predicate; @@ -51,6 +42,9 @@ public class AllayChunk implements Chunk { protected final StampedLock lightLock; protected final Set chunkLoaders; protected final Queue chunkPacketQueue; + // 区块是否已载入世界 + @Getter + protected boolean loaded = false; protected Runnable chunkSetCallback = () -> {}; public AllayChunk(AllayUnsafeChunk unsafeChunk) { @@ -591,8 +585,9 @@ public void setChunkSetCallback(Runnable callback) { } @Override - public Runnable getChunkSetCallback() { - return chunkSetCallback; + public void onChunkSet() { + chunkSetCallback.run(); + loaded = true; } @Override diff --git a/Allay-Server/src/main/java/org/allaymc/server/world/generator/AllayWorldGenerator.java b/Allay-Server/src/main/java/org/allaymc/server/world/generator/AllayWorldGenerator.java index 781e6a009..6df0ec1ad 100644 --- a/Allay-Server/src/main/java/org/allaymc/server/world/generator/AllayWorldGenerator.java +++ b/Allay-Server/src/main/java/org/allaymc/server/world/generator/AllayWorldGenerator.java @@ -1,8 +1,12 @@ package org.allaymc.server.world.generator; +import com.google.common.base.Preconditions; +import io.netty.util.internal.PlatformDependent; import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.allaymc.api.datastruct.collections.nb.Long2ObjectNonBlockingMap; +import org.allaymc.api.server.Server; +import org.allaymc.api.utils.GameLoop; import org.allaymc.api.utils.HashUtils; import org.allaymc.api.world.Dimension; import org.allaymc.api.world.chunk.Chunk; @@ -11,20 +15,19 @@ import org.allaymc.api.world.generator.WorldGenerator; import org.allaymc.api.world.generator.WorldGeneratorType; import org.allaymc.api.world.generator.context.EntitySpawnContext; -import org.allaymc.api.world.generator.context.NoiseContext; import org.allaymc.api.world.generator.context.LightContext; +import org.allaymc.api.world.generator.context.NoiseContext; import org.allaymc.api.world.generator.context.PopulateContext; import org.allaymc.api.world.generator.function.EntitySpawner; -import org.allaymc.api.world.generator.function.Noiser; import org.allaymc.api.world.generator.function.Lighter; +import org.allaymc.api.world.generator.function.Noiser; import org.allaymc.api.world.generator.function.Populator; +import org.allaymc.server.AllayServer; 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.*; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; import java.util.function.Consumer; /** @@ -42,19 +45,20 @@ public final class AllayWorldGenerator implements WorldGenerator { @Getter private final String preset; private final List noisers; - private final List populators ; + private final List populators; private final List lighters; private final List entitySpawners; private final Consumer onDimensionSet; - // 原型区块是未完成生成的区块 - private final Map> protoChunks = new Long2ObjectNonBlockingMap<>(); - // 存储已完成生成且正在被载入世界的基本区块 - private final Set beingSetProtoChunk = Collections.newSetFromMap(new Long2ObjectNonBlockingMap<>()); + private final Map> chunkNoiseFutures = new Long2ObjectNonBlockingMap<>(); + private final Map> chunkFutures = new Long2ObjectNonBlockingMap<>(); + private final Queue populationQueue = PlatformDependent.newMpscQueue(); + private final Set populationLocks = Collections.newSetFromMap(new Long2ObjectNonBlockingMap<>()); + private final ExecutorService computeThreadPool = AllayServer.getInstance().getComputeThreadPool(); @Getter private Dimension dimension; // Will be set later - public static AllayWorldGeneratorBuilder builder() { + public static WorldGeneratorBuilder builder() { return new AllayWorldGeneratorBuilder(); } @@ -77,6 +81,10 @@ private AllayWorldGenerator( this.entitySpawners = entitySpawners; this.onDimensionSet = onDimensionSet; init(); + GameLoop populationQueueLoop = GameLoop.builder() + .onTick(this::processPopulationQueue) + .build(); + Thread.ofVirtual().start(populationQueueLoop::startLoop); } private void init() { @@ -95,59 +103,121 @@ public void setDimension(Dimension dimension) { onDimensionSet.accept(dimension); } - /** - * 立即在此线程生成完整区块,完整区块的ChunkStatus为FINISHED,即可被载入世界 - */ - @Override - public Chunk generateChunk(int x, int z) { - var chunk = getOrGenerateProtoChunk(x, z); - statusNoisedToFinished(chunk); - markProtoChunkBeingSet(x, z); - chunk.setChunkSetCallback(() -> { - afterProtoChunkBeingSet(x, z); - protoChunks.remove(HashUtils.hashXZ(x, z)); - }); - return chunk; + private void processPopulationQueue(GameLoop loop) { + if (!Server.getInstance().isRunning()) { + loop.stop(); + return; + } + PopulationQueueEntry entry; + while ((entry = populationQueue.poll()) != null) { + var noiseFuture = entry.noiseFuture; + var chunk = noiseFuture.getNow(null); + if (chunk == null) { + // 噪声未生成,重新加入队列 + populationQueue.add(entry); + continue; + } + var canPopulate = tryEnterPopulationStage(chunk.getX(), chunk.getZ()); + if (!canPopulate) { + // 本区块或相邻区块有锁,重新加入队列 + populationQueue.add(entry); + continue; + } + var chunkFuture = entry.chunkFuture; + noiseFuture + .thenAcceptAsync(unused -> { + statusNoisedToPopulated(chunk); + releasePopulationLock(chunk.getX(), chunk.getZ()); + statusPopulatedToFinished(chunk); + var chunkHash = HashUtils.hashXZ(chunk.getX(), chunk.getZ()); + // 删除记录的future + chunk.setChunkSetCallback(() -> { + chunkNoiseFutures.remove(chunkHash); + chunkFutures.remove(chunkHash); + }); + chunkFuture.complete(chunk); + }, computeThreadPool); + } } - private Chunk getOrGenerateProtoChunk(int x, int z) { - CompletableFuture future = new CompletableFuture<>(); - // 这里的putIfAbsent()保证了只有一个线程可以写入future,其他线程只能获取这一个线程写入的future - var presentFuture = protoChunks.putIfAbsent(HashUtils.hashXZ(x, z), future); - if (presentFuture != null) { - // 原型区块已经在生成或已生成完毕 - // 等待生成完毕后返回区块,或直接返回若已生成完毕 - return presentFuture.join(); + private boolean tryEnterPopulationStage(int x, int z) { + // 相邻区块噪声都已生成或已载入世界 + // 且本区块以及相邻区块都没锁 + for (int i = -1; i <= 1; i++) { + for (int j = -1; j <= 1; j++) { + var chunkHash = HashUtils.hashXZ(x + i, z + j); + if (populationLocks.contains(chunkHash)) { + return false; + } + var noiseFuture = chunkNoiseFutures.get(chunkHash); + if (noiseFuture == null) { + // 区块噪声未生成 + if (dimension.getChunkService().getChunk(chunkHash) == null) { + getOrCreateNoiseFuture(x + i, z + j); + return false; + } + } else if (!noiseFuture.isDone()) { + return false; + } + } } - var chunk = generateProtoChunk(x, z); - future.complete(chunk); - return chunk; + // 加锁 + for (int i = -1; i <= 1; i++) { + for (int j = -1; j <= 1; j++) { + populationLocks.add(HashUtils.hashXZ(x + i, z + j)); + } + } + return true; } - private Chunk generateProtoChunk(int x, int z) { - var chunk = AllayUnsafeChunk.builder().emptyChunk(x, z, dimension.getDimensionInfo()).toSafeChunk(); - statusEmptyToNoised(chunk); - return chunk; + private void releasePopulationLock(int x, int z) { + for (int i = -1; i <= 1; i++) { + for (int j = -1; j <= 1; j++) { + populationLocks.remove(HashUtils.hashXZ(x + i, z + j)); + } + } } - private void markProtoChunkBeingSet(int x, int z) { - beingSetProtoChunk.add(HashUtils.hashXZ(x, z)); + @Override + public CompletableFuture generateChunk(int x, int z) { + CompletableFuture future = new CompletableFuture<>(); + var presentFuture = chunkFutures.putIfAbsent(HashUtils.hashXZ(x, z), future); + if (presentFuture != null) { + return presentFuture; + } else { + createChunkFuture(x, z).thenAccept(future::complete); + return future; + } } - private void afterProtoChunkBeingSet(int x, int z) { - beingSetProtoChunk.remove(HashUtils.hashXZ(x, z)); + public CompletableFuture createChunkFuture(int x, int z) { + var noiseFuture = getOrCreateNoiseFuture(x, z); + CompletableFuture chunkFuture = new CompletableFuture<>(); + // 添加区块到population队列中 + populationQueue.add(new PopulationQueueEntry(chunkFuture, noiseFuture)); + return chunkFuture; } - private boolean protoChunkBeingSet(int x, int z) { - return beingSetProtoChunk.contains(HashUtils.hashXZ(x, z)); + public CompletableFuture getOrCreateNoiseFuture(int x, int z) { + // 并行计算区块噪声 + CompletableFuture noiseFuture = new CompletableFuture<>(); + var presentFuture = chunkNoiseFutures.putIfAbsent(HashUtils.hashXZ(x, z), noiseFuture); + if (presentFuture == null) { + generateNoisedChunk(x, z).thenAccept(noiseFuture::complete); + } else noiseFuture = presentFuture; + return noiseFuture; } - private void statusEmptyToFinished(Chunk chunk) { - statusEmptyToNoised(chunk); - statusNoisedToFinished(chunk); + /** + * 计算噪声
+ * 噪声生成由于不需要操作相邻区块,故是全并行的 + */ + public CompletableFuture generateNoisedChunk(int x, int z) { + var chunk = AllayUnsafeChunk.builder().emptyChunk(x, z, dimension.getDimensionInfo()).toSafeChunk(); + return CompletableFuture.supplyAsync(() -> statusEmptyToNoised(chunk), computeThreadPool); } - private void statusEmptyToNoised(Chunk chunk) { + private Chunk statusEmptyToNoised(Chunk chunk) { // 基本地形 var generateContext = new NoiseContext(chunk.toUnsafeChunk()); for (var noiser : noisers) { @@ -156,17 +226,21 @@ private void statusEmptyToNoised(Chunk chunk) { } } chunk.setState(ChunkState.NOISED); + return chunk; } - private void statusNoisedToFinished(Chunk chunk) { - // 装饰地形 - var populateContext = new PopulateContext(chunk.toUnsafeChunk(), new WorldGeneratorChunkAccessor(chunk)); - for (var populator : populators) { - if (!populator.apply(populateContext)) { - log.error("Failed to populate chunk {} with populator {}", chunk, populator.getName()); - } - } - chunk.setState(ChunkState.POPULATED); + private void statusNoisedToPopulated(Chunk chunk) { + // 装饰地形 + var populateContext = new PopulateContext(chunk.toUnsafeChunk(), new PopulationStageChunkAccessor(chunk)); + for (var populator : populators) { + if (!populator.apply(populateContext)) { + log.error("Failed to populate chunk {} with populator {}", chunk, populator.getName()); + } + } + chunk.setState(ChunkState.POPULATED); + } + + private void statusPopulatedToFinished(Chunk chunk) { // 烘培光照 var lightContext = new LightContext(chunk.toUnsafeChunk()); for (var lighter : lighters) { @@ -186,10 +260,10 @@ private void statusNoisedToFinished(Chunk chunk) { chunk.setState(ChunkState.FINISHED); } - public final class WorldGeneratorChunkAccessor implements ChunkAccessible { + public final class PopulationStageChunkAccessor implements ChunkAccessible { private final Chunk currentChunk; - public WorldGeneratorChunkAccessor(Chunk currentChunk) { + public PopulationStageChunkAccessor(Chunk currentChunk) { this.currentChunk = currentChunk; } @@ -198,29 +272,26 @@ public Chunk getChunk(int x, int z) { if (x == currentChunk.getX() && z == currentChunk.getZ()) { return currentChunk; } - var chunkService = dimension.getChunkService(); - - // 先获取future,避免在此过程中区块完成加载导致future被删除 - CompletableFuture future = chunkService.getChunkLoadingFuture(x, z); - if (future != null && protoChunkBeingSet(x, z)) { - // 即使在此过程中future被删除(区块完成加载),此代码依然可以工作因为 - // 区块完成加载后,future.join()等效于chunkService.getChunk(x, z) - return future.join(); + if (!isInRange(x, z)) { +// log.warn("Attempted to access chunk out of range during chunk population stage! CurrentChunk: ({}. {}), RequestedChunk: ({}. {})", currentChunk.getX(), currentChunk.getZ(), x, z); + return null; } - - var chunk = chunkService.getChunk(x, z); - // 若区块已生成则直接返回 - if (chunk != null) return chunk; - - // 获取或生成原型区块供使用 - // 此时有小概率出现目标区块已进入"BeingSet"状态的情况 - // 但是这不会产生问题,因为在这种情况下获取的原型区块等效于稍后的chunkService.getChunk(x, z) - chunk = getOrGenerateProtoChunk(x, z); + var loadedChunk = dimension.getChunkService().getChunk(x, z); + if (loadedChunk != null) return loadedChunk; + var noiseFuture = chunkNoiseFutures.get(HashUtils.hashXZ(x, z)); + Preconditions.checkNotNull(noiseFuture); + var chunk = noiseFuture.getNow(null); + Preconditions.checkNotNull(chunk); return chunk; } + + private boolean isInRange(int x, int z) { + // 只能访问相邻的区块 + return Math.abs(x - currentChunk.getX()) <= 1 && Math.abs(z - currentChunk.getZ()) <= 1; + } } - public static final class AllayWorldGeneratorBuilder implements WorldGenerator.WorldGeneratorBuilder { + protected static final class AllayWorldGeneratorBuilder implements WorldGenerator.WorldGeneratorBuilder { private String name; private WorldGeneratorType type = WorldGeneratorType.INFINITE; @@ -278,4 +349,6 @@ public WorldGenerator build() { return new AllayWorldGenerator(name, type, preset, noisers, populators, lighters, entitySpawners, onDimensionSet); } } + + protected record PopulationQueueEntry(CompletableFuture chunkFuture, CompletableFuture noiseFuture) {} } diff --git a/Allay-Server/src/main/java/org/allaymc/server/world/generator/AllayWorldGeneratorFactory.java b/Allay-Server/src/main/java/org/allaymc/server/world/generator/AllayWorldGeneratorFactory.java index a51108a21..056935165 100644 --- a/Allay-Server/src/main/java/org/allaymc/server/world/generator/AllayWorldGeneratorFactory.java +++ b/Allay-Server/src/main/java/org/allaymc/server/world/generator/AllayWorldGeneratorFactory.java @@ -21,8 +21,8 @@ public AllayWorldGeneratorFactory() { } protected void init() { - register("VOID", preset -> AllayWorldGenerator.builder().name("VOID").preset(preset).build()); - register("FLAT", preset -> AllayWorldGenerator.builder().name("FLAT").noisers(new FlatNoiser()).preset(preset).build()); + register("VOID", preset -> WorldGenerator.builder().name("VOID").preset(preset).build()); + register("FLAT", preset -> WorldGenerator.builder().name("FLAT").noisers(new FlatNoiser()).preset(preset).build()); // TODO: Pass preset to je generator loader register("JEGEN_OVERWORLD", preset -> JeGeneratorLoader.getJeGenerator(DimensionInfo.OVERWORLD)); register("JEGEN_NETHER", preset -> JeGeneratorLoader.getJeGenerator(DimensionInfo.NETHER)); diff --git a/Allay-Server/src/main/java/org/allaymc/server/world/service/AllayChunkService.java b/Allay-Server/src/main/java/org/allaymc/server/world/service/AllayChunkService.java index 0f832d9d4..7474b9678 100644 --- a/Allay-Server/src/main/java/org/allaymc/server/world/service/AllayChunkService.java +++ b/Allay-Server/src/main/java/org/allaymc/server/world/service/AllayChunkService.java @@ -1,13 +1,8 @@ package org.allaymc.server.world.service; import com.google.common.collect.Sets; -import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; -import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue; -import it.unimi.dsi.fastutil.longs.LongComparator; -import it.unimi.dsi.fastutil.longs.LongOpenHashSet; +import it.unimi.dsi.fastutil.longs.*; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; -import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.allaymc.api.annotation.SlowOperation; import org.allaymc.api.datastruct.collections.nb.Long2ObjectNonBlockingMap; @@ -33,7 +28,7 @@ import static org.allaymc.api.server.ServerSettings.WorldConfig.ChunkSendingStrategy.ASYNC; import static org.allaymc.api.server.ServerSettings.WorldConfig.ChunkSendingStrategy.SYNC; -import static org.allaymc.api.world.chunk.ChunkState.*; +import static org.allaymc.api.world.chunk.ChunkState.FINISHED; /** * Allay Project 2023/7/1 @@ -131,11 +126,7 @@ public CompletableFuture getChunkLoadingFuture(int x, int z) { @SlowOperation @Override public Chunk getOrLoadChunkSynchronously(int x, int z) { - var chunk = getChunk(x, z); - if (chunk != null) { - return chunk; - } - return loadChunkSynchronously(x, z, null); + return getOrLoadChunk(x, z).join(); } @Override @@ -185,13 +176,13 @@ public CompletableFuture loadChunk(int x, int z) { log.error("Error while reading chunk ({},{}) !", x, z, t); return AllayUnsafeChunk.builder().emptyChunk(x, z, dimension.getDimensionInfo()).toSafeChunk(); }) - .thenApplyAsync(chunk -> { + .thenCompose(chunk -> { if (chunk.getState() != FINISHED) { // 只要未完全加载好都重新加载 - chunk = getWorldGenerator().generateChunk(x, z); + return getWorldGenerator().generateChunk(x, z); } - return chunk; - }, Server.getInstance().getComputeThreadPool()) + return CompletableFuture.completedFuture(chunk); + }) .exceptionally(t -> { log.error("Error while generating chunk ({},{}) !", x, z, t); return AllayUnsafeChunk.builder().emptyChunk(x, z, dimension.getDimensionInfo()).toSafeChunk(); @@ -199,7 +190,7 @@ public CompletableFuture loadChunk(int x, int z) { .thenApply(preparedChunk -> { preparedChunk.beforeSetChunk(dimension); setChunk(x, z, preparedChunk); - preparedChunk.getChunkSetCallback().run(); + preparedChunk.onChunkSet(); preparedChunk.afterSetChunk(dimension); future.complete(preparedChunk); loadingChunks.remove(hashXZ); @@ -208,38 +199,6 @@ public CompletableFuture loadChunk(int x, int z) { return future; } - @SneakyThrows - private Chunk loadChunkSynchronously(int x, int z, CompletableFuture futureAlreadyExists) { - var hash = HashUtils.hashXZ(x, z); - var synchronizedFuture = futureAlreadyExists; - if (futureAlreadyExists == null) { - synchronizedFuture = new CompletableFuture<>(); - loadingChunks.put(hash, synchronizedFuture); - } - Chunk chunk; - try { - chunk = worldStorage.readChunkSynchronously(x, z, dimension.getDimensionInfo()); - } catch (Throwable t) { - log.error("Error while reading chunk ({},{}) !", x, z, t); - chunk = AllayUnsafeChunk.builder().emptyChunk(x, z, dimension.getDimensionInfo()).toSafeChunk(); - } - if (chunk.getState() != FINISHED) { - try { - chunk = getWorldGenerator().generateChunk(x, z); - } catch (Throwable t) { - log.error("Error while generating chunk ({},{}) !", x, z, t); - chunk = AllayUnsafeChunk.builder().emptyChunk(x, z, dimension.getDimensionInfo()).toSafeChunk(); - } - } - chunk.beforeSetChunk(dimension); - setChunk(x, z, chunk); - chunk.getChunkSetCallback().run(); - chunk.afterSetChunk(dimension); - synchronizedFuture.complete(chunk); - loadingChunks.remove(hash); - return chunk; - } - @Override public boolean isChunkLoaded(int x, int z) { return loadedChunks.containsKey(HashUtils.hashXZ(x, z));