Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,13 @@ default void onPolymerBlockSend(BlockState blockState, BlockPos.MutableBlockPos
*/
default boolean forceLightUpdates(BlockState blockState) { return false; }

/**
* You can override this method to force light to be estimated for the block position. This is useful for blocks
* using display entities, which sample their brightness from the light level at their position
* @param blockState
*/
default boolean forceLightInsideBlock(BlockState blockState) { return false; }

/**
* Overrides breaking particle used by the block
* @param state
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,12 +115,22 @@ public static boolean forceLightUpdates(BlockState blockState) {
if (virtualBlock.forceLightUpdates(blockState)) {
return true;
}
if (virtualBlock.forceLightInsideBlock(blockState)) {
return true;
}

return ((BlockStateExtra) blockState).polymer$isPolymerLightSource();
}
return false;
}

public static boolean forceLightInsideBlock(BlockState blockState) {
if (PolymerSyncedObject.getSyncedObject(BuiltInRegistries.BLOCK, blockState.getBlock()) instanceof PolymerBlock virtualBlock) {
return virtualBlock.forceLightInsideBlock(blockState);
}
return false;
}

/**
* Gets BlockState used on client side
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package eu.pb4.polymer.core.impl;

import eu.pb4.polymer.core.impl.interfaces.PolymerChunkStorage;
import eu.pb4.polymer.core.impl.interfaces.PolymerChunkSectionStorage;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.SectionPos;
import net.minecraft.network.protocol.game.ClientboundLightUpdatePacketData;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.LightLayer;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.chunk.LevelChunkSection;
import net.minecraft.world.level.lighting.LayerLightEventListener;
import net.minecraft.world.level.lighting.LevelLightEngine;
import org.jspecify.annotations.Nullable;

import java.util.BitSet;
import java.util.List;

public final class PolymerLightUpdateHelper {
public static final ScopedValue<LevelChunk> CHUNK_CONTEXT = ScopedValue.newInstance();
private static final Direction[] LIGHT_SAMPLE_DIRECTIONS = new Direction[] {
Direction.UP, Direction.DOWN, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST
};

private PolymerLightUpdateHelper() {
}

public static void patchLightData(ClientboundLightUpdatePacketData data, ChunkPos chunkPos, LevelLightEngine lightEngine,
@Nullable BitSet skyChangedLightSectionFilter, @Nullable BitSet blockChangedLightSectionFilter) {
if (!CHUNK_CONTEXT.isBound()) return;
var chunk = CHUNK_CONTEXT.get();

if (chunk == null || !chunk.getPos().equals(chunkPos) || !((PolymerChunkStorage) chunk).polymer$hasAny()) {
return;
}

patchLightLayer(data.getSkyYMask(), data.getSkyUpdates(), chunk, lightEngine, LightLayer.SKY, skyChangedLightSectionFilter);
patchLightLayer(data.getBlockYMask(), data.getBlockUpdates(), chunk, lightEngine, LightLayer.BLOCK, blockChangedLightSectionFilter);
}

private static void patchLightLayer(BitSet mask, List<byte[]> updates, LevelChunk chunk, LevelLightEngine lightEngine,
LightLayer layer, @Nullable BitSet changedLightSectionFilter) {
var listener = lightEngine.getLayerListener(layer);
var sections = chunk.getSections();
var mutable = new BlockPos.MutableBlockPos();

for (int sectionIndex = 0; sectionIndex < sections.length; sectionIndex++) {
LevelChunkSection section = sections[sectionIndex];
if (section == null) {
continue;
}

var storage = (PolymerChunkSectionStorage) section;
if (!storage.polymer$hasAny()) {
continue;
}

int sectionY = chunk.getSectionYFromSectionIndex(sectionIndex);
int lightSectionIndex = sectionY - lightEngine.getMinLightSection();

if (changedLightSectionFilter != null && !changedLightSectionFilter.get(lightSectionIndex)) {
continue;
}

if (!mask.get(lightSectionIndex)) {
continue;
}
byte[] update = updates.get(mask.get(0, lightSectionIndex).cardinality());

for (var iterator = storage.polymer$lightInsideIterator(SectionPos.of(chunk.getPos(), sectionY)); iterator.hasNext();) {
var pos = iterator.next();
int value = getBestLightValue(listener, mutable, pos);

setNibble(update, pos.getX() & 15, pos.getY() & 15, pos.getZ() & 15, value);
}
}
}

private static int getBestLightValue(LayerLightEventListener listener, BlockPos.MutableBlockPos mutable, BlockPos pos) {
int value = listener.getLightValue(pos);

for (var direction : LIGHT_SAMPLE_DIRECTIONS) {
mutable.setWithOffset(pos, direction);
value = Math.max(value, listener.getLightValue(mutable));
}

return value;
}

private static void setNibble(byte[] data, int x, int y, int z, int val) {
// Matches DataLayer.set(int x, int y, int z, int val)
int index = y << 8 | z << 4 | x;
int position = index >> 1;
int nibble = index & 1;
int mask = ~(15 << 4 * nibble);
int valueToSet = (val & 0xF) << 4 * nibble;
data[position] = (byte)(data[position] & mask | valueToSet);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,20 @@

import it.unimi.dsi.fastutil.shorts.ShortSet;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.Nullable;

import java.util.Iterator;
import net.minecraft.core.BlockPos;
import net.minecraft.core.SectionPos;

@ApiStatus.Internal
public interface PolymerBlockPosStorage {
@Nullable
public interface PolymerChunkSectionStorage {
ShortSet polymer$getBackendSet();

@Nullable
Iterator<BlockPos.MutableBlockPos> polymer$iterator(SectionPos sectionPos);
Iterator<BlockPos.MutableBlockPos> polymer$blockIterator(SectionPos sectionPos);

@Nullable
Iterator<BlockPos.MutableBlockPos> polymer$iterator();
Iterator<BlockPos.MutableBlockPos> polymer$lightInsideIterator(SectionPos sectionPos);

void polymer$setSynced(int x, int y, int z, boolean lightSource);
void polymer$setSynced(int x, int y, int z, boolean lightSource, boolean lightInside);
void polymer$removeSynced(int x, int y, int z);

boolean polymer$isSynced(int x, int y, int z);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package eu.pb4.polymer.core.impl.interfaces;

import net.minecraft.core.BlockPos;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.Nullable;

import java.util.Iterator;

@ApiStatus.Internal
public interface PolymerChunkStorage {
@Nullable
Iterator<BlockPos.MutableBlockPos> polymer$iterator();

void polymer$setSynced(int x, int y, int z, boolean lightSource, boolean lightInside);
void polymer$removeSynced(int x, int y, int z);

boolean polymer$isSynced(int x, int y, int z);

boolean polymer$hasAny();
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import eu.pb4.polymer.core.api.utils.PolymerSyncedObject;
import eu.pb4.polymer.core.impl.PolymerImplUtils;
import eu.pb4.polymer.core.impl.interfaces.ChunkDataS2CPacketInterface;
import eu.pb4.polymer.core.impl.interfaces.PolymerBlockPosStorage;
import eu.pb4.polymer.core.impl.interfaces.PolymerChunkStorage;
import eu.pb4.polymer.core.impl.interfaces.PolymerGamePacketListenerExtension;
import eu.pb4.polymer.core.mixin.block.packet.ClientboundBlockUpdatePacketAccessor;
import eu.pb4.polymer.core.mixin.block.packet.ClientboundSectionBlocksUpdatePacketAccessor;
Expand All @@ -20,7 +20,6 @@
import net.minecraft.server.network.ServerGamePacketListenerImpl;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.LevelChunk;
import net.fabricmc.fabric.api.networking.v1.context.PacketContext;

public class BlockPacketUtil {
public static void sendFromPacket(Packet<?> packet, ServerGamePacketListenerImpl handler) {
Expand All @@ -31,7 +30,7 @@ public static void sendFromPacket(Packet<?> packet, ServerGamePacketListenerImpl
}
} else if (packet instanceof ClientboundLevelChunkWithLightPacket) {
LevelChunk wc = ((ChunkDataS2CPacketInterface) packet).polymer$getWorldChunk();
PolymerBlockPosStorage wci = (PolymerBlockPosStorage) wc;
PolymerChunkStorage wci = (PolymerChunkStorage) wc;
if (wc != null && wci.polymer$hasAny()) {
PolymerServerProtocol.sendSectionUpdate(handler, wc);
var iterator = wci.polymer$iterator();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
import eu.pb4.polymer.core.api.utils.PolymerSyncedObject;
import eu.pb4.polymer.core.impl.PolymerImpl;
import eu.pb4.polymer.core.impl.PolymerImplUtils;
import eu.pb4.polymer.core.impl.interfaces.PolymerBlockPosStorage;
import eu.pb4.polymer.core.impl.interfaces.PolymerChunkStorage;
import eu.pb4.polymer.core.impl.interfaces.PolymerChunkSectionStorage;
import eu.pb4.polymer.core.impl.interfaces.PolymerIdMapper;
import eu.pb4.polymer.core.impl.interfaces.RegistryExtension;
import eu.pb4.polymer.core.impl.networking.entry.*;
Expand Down Expand Up @@ -69,12 +70,12 @@ public static void sendSectionUpdate(ServerGamePacketListenerImpl player, LevelC
var version = PolymerServerNetworking.getSupportedVersion(player, S2CPackets.WORLD_CHUNK_SECTION_UPDATE);

if (version > -1) {
var wci = (PolymerBlockPosStorage) chunk;
var wci = (PolymerChunkStorage) chunk;
if (wci.polymer$hasAny()) {
var sections = chunk.getSections();
for (var i = 0; i < sections.length; i++) {
var section = sections[i];
var storage = (PolymerBlockPosStorage) section;
var storage = (PolymerChunkSectionStorage) section;

if (section != null && storage.polymer$hasAny()) {
var set = storage.polymer$getBackendSet();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public abstract class BlockStateMixin implements BlockStateExtra {
}

this.polymer$calculatedIsLight = true;
return false;
return polymer$isLight;
}

@ModifyExpressionValue(method = "<clinit>", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/block/state/BlockState;codec(Lcom/mojang/serialization/Codec;Ljava/util/function/Function;Ljava/util/function/Function;)Lcom/mojang/serialization/Codec;"))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package eu.pb4.polymer.core.mixin.block;

import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import eu.pb4.polymer.core.impl.PolymerLightUpdateHelper;
import net.minecraft.network.protocol.game.ClientboundLightUpdatePacket;
import net.minecraft.server.level.ChunkHolder;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.lighting.LevelLightEngine;
import org.jspecify.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;

import java.util.BitSet;

@Mixin(ChunkHolder.class)
public class ChunkHolderMixin {
@WrapOperation(
method = "broadcastChanges",
at = @At(
value = "NEW",
target = "(Lnet/minecraft/world/level/ChunkPos;Lnet/minecraft/world/level/lighting/LevelLightEngine;Ljava/util/BitSet;Ljava/util/BitSet;)Lnet/minecraft/network/protocol/game/ClientboundLightUpdatePacket;"
)
)
private ClientboundLightUpdatePacket addPolymerLightContext(
ChunkPos pos, LevelLightEngine lightEngine, @Nullable BitSet skyChangedLightSectionFilter,
@Nullable BitSet blockChangedLightSectionFilter, Operation<ClientboundLightUpdatePacket> operation,
LevelChunk chunk
) {
return ScopedValue.where(PolymerLightUpdateHelper.CHUNK_CONTEXT, chunk)
.call(() -> operation.call(pos, lightEngine, skyChangedLightSectionFilter, blockChangedLightSectionFilter));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
import eu.pb4.polymer.common.impl.CompatStatus;
import eu.pb4.polymer.core.api.block.PolymerBlockUtils;
import eu.pb4.polymer.core.impl.PolymerImpl;
import eu.pb4.polymer.core.impl.PolymerLightUpdateHelper;
import eu.pb4.polymer.core.impl.compat.ImmersivePortalsUtils;
import eu.pb4.polymer.core.impl.interfaces.PolymerBlockPosStorage;
import eu.pb4.polymer.core.impl.interfaces.PolymerChunkSectionStorage;
import it.unimi.dsi.fastutil.objects.Object2LongArrayMap;
import it.unimi.dsi.fastutil.objects.Object2LongMap;
import org.jspecify.annotations.Nullable;
Expand Down Expand Up @@ -75,12 +76,12 @@ public abstract class ServerChunkCacheMixin {
int sectionIndex = chunk.getSectionIndexFromSectionY(sectionPos.y());
// As there is an additional light section above and below the world, there might not even be a block section here
if (sectionIndex >= 0 && sectionIndex < sections.length) {
if (sections[sectionIndex] instanceof PolymerBlockPosStorage section) {
if (sections[sectionIndex] instanceof PolymerChunkSectionStorage section) {
section.polymer$setRequireLights(false);
}
}

polymer$broadcastBlockLightForSection(sectionPos);
polymer$broadcastBlockLightForSection(chunk, sectionPos);

return true;
});
Expand All @@ -96,14 +97,14 @@ private List<ServerPlayer> getPlayersWatchingChunk(ChunkPos chunkPos) {
}

@Unique
private void polymer$broadcastBlockLightForSection(SectionPos pos) {
private void polymer$broadcastBlockLightForSection(LevelChunk chunk, SectionPos pos) {
List<ServerPlayer> players = getPlayersWatchingChunk(pos.chunk());
if (players.isEmpty()) {
return;
}
BitSet dirtyBlockLightSections = new BitSet();
dirtyBlockLightSections.set(pos.y() - this.lightEngine.getMinLightSection());
Packet<?> packet = new ClientboundLightUpdatePacket(pos.chunk(), this.lightEngine, new BitSet(), dirtyBlockLightSections);
Packet<?> packet = ScopedValue.where(PolymerLightUpdateHelper.CHUNK_CONTEXT, chunk).call(() -> new ClientboundLightUpdatePacket(pos.chunk(), this.lightEngine, new BitSet(), dirtyBlockLightSections));
for (ServerPlayer player : players) {
player.connection.send(packet);
}
Expand Down Expand Up @@ -132,7 +133,7 @@ private List<ServerPlayer> getPlayersWatchingChunk(ChunkPos chunkPos) {

for (var i = Math.max(0, chunk.getSectionIndexFromSectionY(pos.y() - 1)); i <= max; i++) {
var section = sections[i];
if (section != null && !section.hasOnlyAir() && ((PolymerBlockPosStorage) section).polymer$requireLights()) {
if (section != null && !section.hasOnlyAir() && ((PolymerChunkSectionStorage) section).polymer$requireLights()) {
return true;
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
package eu.pb4.polymer.core.mixin.block.packet;

import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import eu.pb4.polymer.core.impl.PolymerLightUpdateHelper;
import eu.pb4.polymer.core.impl.interfaces.ChunkDataS2CPacketInterface;
import net.minecraft.network.protocol.game.ClientboundLightUpdatePacketData;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;

import java.util.BitSet;

import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.lighting.LevelLightEngine;
import org.jspecify.annotations.Nullable;

@Mixin(ClientboundLevelChunkWithLightPacket.class)
public class ClientboundLevelChunkWithLightPacketMixin implements ChunkDataS2CPacketInterface {
Expand All @@ -22,6 +29,20 @@ public class ClientboundLevelChunkWithLightPacketMixin implements ChunkDataS2CPa
this.polymer$worldChunk = chunk;
}

@WrapOperation(
method = "<init>(Lnet/minecraft/world/level/chunk/LevelChunk;Lnet/minecraft/world/level/lighting/LevelLightEngine;Ljava/util/BitSet;Ljava/util/BitSet;)V",
at = @At(
value = "NEW",
target = "(Lnet/minecraft/world/level/ChunkPos;Lnet/minecraft/world/level/lighting/LevelLightEngine;Ljava/util/BitSet;Ljava/util/BitSet;)Lnet/minecraft/network/protocol/game/ClientboundLightUpdatePacketData;"
)
)
private ClientboundLightUpdatePacketData polymer$addPolymerLightContext(
ChunkPos chunkPos, LevelLightEngine lightEngine, @Nullable BitSet skyChangedLightSectionFilter,
@Nullable BitSet blockChangedLightSectionFilter, Operation<ClientboundLightUpdatePacketData> operation,
LevelChunk chunk) {
return ScopedValue.where(PolymerLightUpdateHelper.CHUNK_CONTEXT, chunk).call(() -> operation.call(chunkPos, lightEngine, skyChangedLightSectionFilter, blockChangedLightSectionFilter));
}

public LevelChunk polymer$getWorldChunk() {
return this.polymer$worldChunk;
}
Expand Down
Loading
Loading