Skip to content

Commit

Permalink
feat: Implement chunk lighting
Browse files Browse the repository at this point in the history
Chunks are lit during world generation, and lighting is updating using the `NoisiumServerChunkEvent.LIGHT_UPDATE` event.
  • Loading branch information
Steveplays28 committed Jun 23, 2024
1 parent c25300c commit 5c857c0
Show file tree
Hide file tree
Showing 9 changed files with 123 additions and 64 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package io.github.steveplays28.noisium.extension.world.chunk;

import java.util.BitSet;

public interface NoisiumWorldChunkExtension {
BitSet noisium$getBlockLightBits();

BitSet noisium$getSkyLightBits();
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.github.steveplays28.noisium.mixin.server.world;

import io.github.steveplays28.noisium.extension.world.server.NoisiumServerWorldExtension;
import io.github.steveplays28.noisium.server.world.chunk.event.NoisiumServerChunkEvent;
import io.github.steveplays28.noisium.server.world.event.NoisiumServerTickEvent;
import io.github.steveplays28.noisium.util.world.chunk.networking.packet.PacketUtil;
import net.minecraft.entity.Entity;
Expand All @@ -11,6 +12,8 @@
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.ChunkPos;
import net.minecraft.util.math.ChunkSectionPos;
import net.minecraft.world.LightType;
import net.minecraft.world.World;
import net.minecraft.world.chunk.WorldChunk;
import org.spongepowered.asm.mixin.Mixin;
Expand Down Expand Up @@ -42,7 +45,8 @@ public abstract class ServerChunkManagerMixin {
@Inject(method = "getWorldChunk", at = @At(value = "HEAD"), cancellable = true)
private void noisium$getWorldChunkFromNoisiumServerWorldChunkManager(int chunkX, int chunkZ, CallbackInfoReturnable<WorldChunk> cir) {
((NoisiumServerWorldExtension) this.getWorld()).noisium$getServerWorldChunkManager().getChunkAsync(
new ChunkPos(chunkX, chunkZ)).whenComplete((worldChunk, throwable) -> cir.setReturnValue(worldChunk));
new ChunkPos(chunkX, chunkZ)
).whenComplete((worldChunk, throwable) -> cir.setReturnValue(worldChunk));
}

// TODO: Don't send this packet to players out of range, to save on bandwidth
Expand Down Expand Up @@ -102,4 +106,10 @@ public abstract class ServerChunkManagerMixin {
PacketUtil.sendPacketToPlayers(serverWorld.getPlayers(), new BlockUpdateS2CPacket(blockPos, serverWorld.getBlockState(blockPos)));
ci.cancel();
}

@Inject(method = "onLightUpdate", at = @At(value = "HEAD"), cancellable = true)
private void noisium$updateLightingViaNoisiumServerWorldChunkManager(LightType lightType, ChunkSectionPos chunkSectionPos, CallbackInfo ci) {
NoisiumServerChunkEvent.LIGHT_UPDATE.invoker().onLightUpdate(lightType, chunkSectionPos);
ci.cancel();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package io.github.steveplays28.noisium.mixin.world.chunk;

import io.github.steveplays28.noisium.extension.world.chunk.NoisiumWorldChunkExtension;
import net.minecraft.world.chunk.WorldChunk;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;

import java.util.BitSet;

@Mixin(WorldChunk.class)
public class WorldChunkMixin implements NoisiumWorldChunkExtension {
@Unique
private final BitSet noisium$blockLightBits = new BitSet();
@Unique
private final BitSet noisium$skyLightBits = new BitSet();

@Override
public BitSet noisium$getBlockLightBits() {
return noisium$blockLightBits;
}

@Override
public BitSet noisium$getSkyLightBits() {
return noisium$skyLightBits;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public NoisiumServerPlayerBlockUpdater() {
BlockEvent.BREAK.register((world, blockPos, blockState, player, xp) -> onBlockBreak(world, blockPos));
}

private EventResult onBlockBreak(@NotNull World world, BlockPos blockPos) {
private EventResult onBlockBreak(@NotNull World world, @NotNull BlockPos blockPos) {
if (world.isClient()) {
return EventResult.pass();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.mojang.datafixers.DataFixer;
import io.github.steveplays28.noisium.Noisium;
import io.github.steveplays28.noisium.extension.world.chunk.NoisiumWorldChunkExtension;
import io.github.steveplays28.noisium.mixin.accessor.NoiseChunkGeneratorAccessor;
import io.github.steveplays28.noisium.server.world.chunk.ServerChunkData;
import io.github.steveplays28.noisium.server.world.chunk.event.NoisiumServerChunkEvent;
import io.github.steveplays28.noisium.util.world.chunk.ChunkUtil;
import net.minecraft.entity.EntityType;
Expand All @@ -15,9 +15,11 @@
import net.minecraft.util.math.ChunkPos;
import net.minecraft.util.math.ChunkSectionPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.world.*;
import net.minecraft.world.ChunkRegion;
import net.minecraft.world.ChunkSerializer;
import net.minecraft.world.Heightmap;
import net.minecraft.world.LightType;
import net.minecraft.world.chunk.*;
import net.minecraft.world.chunk.light.LightSourceView;
import net.minecraft.world.gen.GenerationStep;
import net.minecraft.world.gen.chunk.Blender;
import net.minecraft.world.gen.chunk.ChunkGenerator;
Expand All @@ -39,14 +41,14 @@
// TODO: Fix canTickBlockEntities() check
// The check needs to be changed to point to the server world's isChunkLoaded() method
// TODO: Implement chunk ticking
public class NoisiumServerWorldChunkManager implements ChunkProvider {
public class NoisiumServerWorldChunkManager {
private final ServerWorld serverWorld;
private final ChunkGenerator chunkGenerator;
private final PointOfInterestStorage pointOfInterestStorage;
private final VersionedChunkStorage versionedChunkStorage;
private final NoiseConfig noiseConfig;
private final Executor threadPoolExecutor;
private final Map<ChunkPos, ServerChunkData> loadedWorldChunks;
private final Map<ChunkPos, WorldChunk> loadedWorldChunks;

public NoisiumServerWorldChunkManager(@NotNull ServerWorld serverWorld, @NotNull ChunkGenerator chunkGenerator, @NotNull Path worldDirectoryPath, DataFixer dataFixer) {
this.serverWorld = serverWorld;
Expand All @@ -70,52 +72,21 @@ public NoisiumServerWorldChunkManager(@NotNull ServerWorld serverWorld, @NotNull
serverWorld.getRegistryManager().getWrapperOrThrow(RegistryKeys.NOISE_PARAMETERS), serverWorld.getSeed()
);
}
}

@Override
public BlockView getWorld() {
return serverWorld;
}

@Nullable
@Override
public LightSourceView getChunk(int chunkX, int chunkZ) {
return getChunk(new ChunkPos(chunkX, chunkZ));
}

@Override
public void onLightUpdate(LightType lightType, ChunkSectionPos chunkSectionPosition) {
var lightingProvider = serverWorld.getLightingProvider();
var chunkPos = chunkSectionPosition.toChunkPos();
var chunkSectionYPosition = chunkSectionPosition.getSectionY();
int bottomY = lightingProvider.getBottomY();
int topY = lightingProvider.getTopY();

if (chunkSectionYPosition >= bottomY && chunkSectionYPosition <= topY) {
int yDifference = chunkSectionYPosition - bottomY;
var serverChunkData = loadedWorldChunks.get(chunkPos);
var skyLightBits = serverChunkData.skyLightBits();
var blockLightBits = serverChunkData.skyLightBits();

if (lightType == LightType.SKY) {
skyLightBits.set(yDifference);
} else {
blockLightBits.set(yDifference);
}
ChunkUtil.sendLightUpdateToPlayers(serverWorld.getPlayers(), lightingProvider, chunkPos, skyLightBits, blockLightBits);
}
NoisiumServerChunkEvent.LIGHT_UPDATE.register(this::onLightUpdateAsync);
}

/**
* Loads the chunk at the specified position, returning the loaded chunk when done. Returns the chunk from the <code>loadedChunks</code> cache if available.
* Loads the chunk at the specified position, returning the loaded chunk when done.
* Returns the chunk from the {@link NoisiumServerWorldChunkManager#loadedWorldChunks} cache if available.
* This method is ran asynchronously.
*
* @param chunkPos The position at which to load the chunk.
* @return The loaded chunk.
*/
public @NotNull CompletableFuture<WorldChunk> getChunkAsync(ChunkPos chunkPos) {
if (loadedWorldChunks.containsKey(chunkPos)) {
return CompletableFuture.completedFuture(loadedWorldChunks.get(chunkPos).worldChunk());
return CompletableFuture.completedFuture(loadedWorldChunks.get(chunkPos));
}

return CompletableFuture.supplyAsync(() -> {
Expand All @@ -136,28 +107,29 @@ public void onLightUpdate(LightType lightType, ChunkSectionPos chunkSectionPosit

fetchedWorldChunk.addChunkTickSchedulers(serverWorld);
fetchedWorldChunk.loadEntities();
loadedWorldChunks.put(chunkPos, new ServerChunkData(fetchedWorldChunk, (short) 0, new BitSet(), new BitSet()));
loadedWorldChunks.put(chunkPos, fetchedWorldChunk);
NoisiumServerChunkEvent.WORLD_CHUNK_GENERATED.invoker().onWorldChunkGenerated(fetchedWorldChunk);
});
}

/**
* Loads the chunk at the specified position, returning the loaded chunk when done. Returns the chunk from the <code>loadedChunks</code> cache if available.
* WARNING: This method blocks the server thread. Prefer using {@link NoisiumServerWorldChunkManager#getChunk(int, int)} instead.
* Loads the chunk at the specified position, returning the loaded {@link WorldChunk} when done.
* Returns the chunk from the {@link NoisiumServerWorldChunkManager#loadedWorldChunks} cache if available.
* WARNING: This method blocks the server thread. Prefer using {@link NoisiumServerWorldChunkManager#getChunkAsync} instead.
*
* @param chunkPos The position at which to load the chunk.
* @return The loaded chunk.
* @param chunkPos The position at which to load the {@link WorldChunk}.
* @return The loaded {@link WorldChunk}.
*/
public @Nullable WorldChunk getChunk(ChunkPos chunkPos) {
public @NotNull WorldChunk getChunk(ChunkPos chunkPos) {
if (loadedWorldChunks.containsKey(chunkPos)) {
return loadedWorldChunks.get(chunkPos).worldChunk();
return loadedWorldChunks.get(chunkPos);
}

var fetchedNbtData = getNbtDataAtChunkPosition(chunkPos);
if (fetchedNbtData == null) {
// TODO: Schedule ProtoChunk worldgen and update loadedWorldChunks incrementally during worldgen steps
var fetchedWorldChunk = new WorldChunk(serverWorld, generateChunk(chunkPos), null);
loadedWorldChunks.put(chunkPos, new ServerChunkData(fetchedWorldChunk, (short) 0, new BitSet(), new BitSet()));
loadedWorldChunks.put(chunkPos, fetchedWorldChunk);
NoisiumServerChunkEvent.WORLD_CHUNK_GENERATED.invoker().onWorldChunkGenerated(fetchedWorldChunk);
return fetchedWorldChunk;
}
Expand All @@ -169,7 +141,7 @@ public void onLightUpdate(LightType lightType, ChunkSectionPos chunkSectionPosit
fetchedWorldChunk.addChunkTickSchedulers(serverWorld);
fetchedWorldChunk.loadEntities();

loadedWorldChunks.put(chunkPos, new ServerChunkData(fetchedWorldChunk, (short) 0, new BitSet(), new BitSet()));
loadedWorldChunks.put(chunkPos, fetchedWorldChunk);
NoisiumServerChunkEvent.WORLD_CHUNK_GENERATED.invoker().onWorldChunkGenerated(fetchedWorldChunk);
return fetchedWorldChunk;
}
Expand Down Expand Up @@ -216,6 +188,38 @@ public void onLightUpdate(LightType lightType, ChunkSectionPos chunkSectionPosit
return chunks;
}

/**
* Updates the chunk's lighting at the specified {@link ChunkSectionPos}.
* This method is ran asynchronously.
*
* @param lightType The {@link LightType} that should be updated for this {@link WorldChunk}.
* @param chunkSectionPosition The {@link ChunkSectionPos} of the {@link WorldChunk}.
*/
private void onLightUpdateAsync(@NotNull LightType lightType, @NotNull ChunkSectionPos chunkSectionPosition) {
CompletableFuture.runAsync(() -> {
var lightingProvider = serverWorld.getLightingProvider();
int bottomY = lightingProvider.getBottomY();
int topY = lightingProvider.getTopY();
var chunkSectionYPosition = chunkSectionPosition.getSectionY();
if (chunkSectionYPosition < bottomY || chunkSectionYPosition > topY) {
return;
}

var chunkPosition = chunkSectionPosition.toChunkPos();
var worldChunk = (NoisiumWorldChunkExtension) getChunk(chunkPosition);
var skyLightBits = worldChunk.noisium$getBlockLightBits();
var blockLightBits = worldChunk.noisium$getSkyLightBits();
int chunkSectionYPositionDifference = chunkSectionYPosition - bottomY;

if (lightType == LightType.SKY) {
skyLightBits.set(chunkSectionYPositionDifference);
} else {
blockLightBits.set(chunkSectionYPositionDifference);
}
ChunkUtil.sendLightUpdateToPlayers(serverWorld.getPlayers(), lightingProvider, chunkPosition, skyLightBits, blockLightBits);
}, threadPoolExecutor);
}

private @Nullable NbtCompound getNbtDataAtChunkPosition(ChunkPos chunkPos) {
try {
var fetchedNbtCompoundOptionalFuture = versionedChunkStorage.getNbt(chunkPos).get();
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,22 @@

import dev.architectury.event.Event;
import dev.architectury.event.EventFactory;
import net.minecraft.util.math.ChunkSectionPos;
import net.minecraft.world.LightType;
import net.minecraft.world.chunk.WorldChunk;
import org.jetbrains.annotations.NotNull;

public interface NoisiumServerChunkEvent {
/**
* @see WorldChunkGenerated
*/
Event<WorldChunkGenerated> WORLD_CHUNK_GENERATED = EventFactory.createLoop();

/**
* @see WorldChunkGenerated
*/
Event<LightUpdate> LIGHT_UPDATE = EventFactory.createLoop();

@FunctionalInterface
interface WorldChunkGenerated {
/**
Expand All @@ -19,4 +27,15 @@ interface WorldChunkGenerated {
*/
void onWorldChunkGenerated(WorldChunk worldChunk);
}

@FunctionalInterface
interface LightUpdate {
/**
* Invoked before a {@link WorldChunk} has had a light update processed by {@link io.github.steveplays28.noisium.server.world.NoisiumServerWorldChunkManager}.
*
* @param lightType The {@link LightType} of the {@link WorldChunk}.
* @param chunkSectionPosition The {@link ChunkSectionPos} of the {@link WorldChunk}.
*/
void onLightUpdate(@NotNull LightType lightType, @NotNull ChunkSectionPos chunkSectionPosition);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public static void sendWorldChunksToPlayerAsync(@NotNull ServerWorld serverWorld
* @param blockLightBits The blocklight {@link BitSet}.
*/
@SuppressWarnings("ForLoopReplaceableByForEach")
public static void sendLightUpdateToPlayers(@NotNull List<ServerPlayerEntity> players, LightingProvider lightingProvider, ChunkPos chunkPos, BitSet skyLightBits, BitSet blockLightBits) {
public static void sendLightUpdateToPlayers(@NotNull List<ServerPlayerEntity> players, @NotNull LightingProvider lightingProvider, @NotNull ChunkPos chunkPos, @NotNull BitSet skyLightBits, @NotNull BitSet blockLightBits) {
for (int i = 0; i < players.size(); i++) {
players.get(i).networkHandler.sendPacket(new LightUpdateS2CPacket(chunkPos, lightingProvider, skyLightBits, blockLightBits));
}
Expand All @@ -85,7 +85,7 @@ public static void sendLightUpdateToPlayers(@NotNull List<ServerPlayerEntity> pl
* @param blockState The {@link BlockState} at the specified {@link BlockPos} of the block update that should be sent to the {@link List} of players.
*/
@SuppressWarnings("ForLoopReplaceableByForEach")
public static void sendBlockUpdateToPlayers(@NotNull List<ServerPlayerEntity> players, @NotNull BlockPos blockPos, BlockState blockState) {
public static void sendBlockUpdateToPlayers(@NotNull List<ServerPlayerEntity> players, @NotNull BlockPos blockPos, @NotNull BlockState blockState) {
for (int i = 0; i < players.size(); i++) {
players.get(i).networkHandler.sendPacket(new BlockUpdateS2CPacket(blockPos, blockState));
}
Expand Down
3 changes: 2 additions & 1 deletion common/src/main/resources/noisium-common.mixins.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
"server.world.ServerWorldMixin",
"server.world.ThreadedAnvilChunkStorageMixin",
"world.WorldAccessMixin",
"world.WorldMixin"
"world.WorldMixin",
"world.chunk.WorldChunkMixin"
],
"client": [
"client.gui.hud.DebugHudMixin"
Expand Down

0 comments on commit 5c857c0

Please sign in to comment.