Skip to content

Commit

Permalink
refactor: Fix concurrency issues when sending packets
Browse files Browse the repository at this point in the history
Also changed light updates to be handled on the server world chunk manager's thread pool instead of the common thread pool, discarded chunks that generated an exception during world generation, and increased the server world chunk manager's thread pool to 8 threads.
  • Loading branch information
Steveplays28 committed Jun 24, 2024
1 parent 6b13515 commit 6a5777c
Show file tree
Hide file tree
Showing 3 changed files with 12 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public NoisiumServerPlayerChunkLoader() {
@SuppressWarnings("ForLoopReplaceableByForEach")
private void tick(@NotNull ServerWorld serverWorld, @NotNull BiFunction<ChunkPos, Integer, Map<ChunkPos, CompletableFuture<WorldChunk>>> worldChunksSupplier) {
var players = serverWorld.getPlayers();
if (players.size() == 0 || previousPlayerPositions.size() == 0) {
if (players.isEmpty() || previousPlayerPositions.isEmpty()) {
return;
}

Expand All @@ -61,7 +61,7 @@ private void tick(@NotNull ServerWorld serverWorld, @NotNull BiFunction<ChunkPos
if (!playerBlockPos.isWithinDistance(previousPlayerPositions.get(player.getId()), 16d)) {
var worldChunks = worldChunksSupplier.apply(new ChunkPos(playerBlockPos), 6);

ChunkUtil.sendWorldChunksToPlayerAsync(serverWorld, new ArrayList<>(worldChunks.values()));
ChunkUtil.sendWorldChunksToPlayerAsync(serverWorld, new ArrayList<>(worldChunks.values()), threadPoolExecutor);
CompletableFuture.runAsync(() -> player.networkHandler.sendPacket(
new ChunkRenderDistanceCenterS2CPacket(
ChunkSectionPos.getSectionCoord(playerBlockPos.getX()),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ public NoisiumServerWorldChunkManager(@NotNull ServerWorld serverWorld, @NotNull
worldDirectoryPath.resolve("poi"), dataFixer, true, serverWorld.getRegistryManager(), serverWorld);
this.versionedChunkStorage = new NoisiumServerVersionedChunkStorage(worldDirectoryPath.resolve("region"), dataFixer, true);
this.threadPoolExecutor = Executors.newFixedThreadPool(
1, new ThreadFactoryBuilder().setNameFormat("Noisium Server World Chunk Manager %d").build());
8, new ThreadFactoryBuilder().setNameFormat("Noisium Server World Chunk Manager %d").build());
this.loadingWorldChunks = new ConcurrentHashMap<>();
this.loadedWorldChunks = new HashMap<>();

Expand Down Expand Up @@ -120,6 +120,8 @@ public NoisiumServerWorldChunkManager(@NotNull ServerWorld serverWorld, @NotNull
}, threadPoolExecutor).whenComplete((fetchedWorldChunk, throwable) -> {
if (throwable != null) {
Noisium.LOGGER.error("Exception thrown while getting a chunk asynchronously:\n{}", ExceptionUtils.getStackTrace(throwable));
loadingWorldChunks.remove(chunkPos);
return;
}

fetchedWorldChunk.addChunkTickSchedulers(serverWorld);
Expand Down Expand Up @@ -214,6 +216,7 @@ public boolean isChunkLoaded(ChunkPos chunkPos) {
return this.loadedWorldChunks.containsKey(chunkPos);
}

// TODO: Move into the ServerLightingProvider
/**
* Updates the chunk's lighting at the specified {@link ChunkSectionPos}.
* This method is ran asynchronously.
Expand All @@ -224,9 +227,8 @@ public boolean isChunkLoaded(ChunkPos chunkPos) {
private void onLightUpdateAsync(@NotNull LightType lightType, @NotNull ChunkSectionPos chunkSectionPosition) {
var lightingProvider = serverWorld.getLightingProvider();
int bottomY = lightingProvider.getBottomY();
int topY = lightingProvider.getTopY();
var chunkSectionYPosition = chunkSectionPosition.getSectionY();
if (chunkSectionYPosition < bottomY || chunkSectionYPosition > topY) {
if (chunkSectionYPosition < bottomY || chunkSectionYPosition > lightingProvider.getTopY()) {
return;
}

Expand All @@ -245,7 +247,7 @@ private void onLightUpdateAsync(@NotNull LightType lightType, @NotNull ChunkSect
blockLightBits.set(chunkSectionYPositionDifference);
}
ChunkUtil.sendLightUpdateToPlayers(serverWorld.getPlayers(), lightingProvider, chunkPosition, skyLightBits, blockLightBits);
});
}, threadPoolExecutor);
}

// TODO: Check if this can be ran asynchronously
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.github.steveplays28.noisium.experimental.util.world.chunk;

import net.minecraft.block.BlockState;
import net.minecraft.network.NetworkThreadUtils;
import net.minecraft.network.packet.s2c.play.BlockUpdateS2CPacket;
import net.minecraft.network.packet.s2c.play.ChunkDataS2CPacket;
import net.minecraft.network.packet.s2c.play.LightUpdateS2CPacket;
Expand All @@ -15,6 +16,7 @@
import java.util.BitSet;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;

public class ChunkUtil {
/**
Expand Down Expand Up @@ -54,10 +56,10 @@ public static void sendWorldChunksToPlayer(@NotNull ServerWorld serverWorld, @No
* @param worldChunkFutures The {@link List} of {@link CompletableFuture<WorldChunk>}s
*/
@SuppressWarnings("ForLoopReplaceableByForEach")
public static void sendWorldChunksToPlayerAsync(@NotNull ServerWorld serverWorld, @NotNull List<CompletableFuture<WorldChunk>> worldChunkFutures) {
public static void sendWorldChunksToPlayerAsync(@NotNull ServerWorld serverWorld, @NotNull List<CompletableFuture<WorldChunk>> worldChunkFutures, @NotNull Executor executor) {
// TODO: Send a whole batch of chunks to the player at once to save on network traffic
for (int i = 0; i < worldChunkFutures.size(); i++) {
worldChunkFutures.get(i).whenCompleteAsync((worldChunk, throwable) -> sendWorldChunkToPlayer(serverWorld, worldChunk));
worldChunkFutures.get(i).whenCompleteAsync((worldChunk, throwable) -> sendWorldChunkToPlayer(serverWorld, worldChunk), executor);
}
}

Expand Down

0 comments on commit 6a5777c

Please sign in to comment.