Skip to content
This repository has been archived by the owner on Mar 8, 2024. It is now read-only.

Commit

Permalink
Move to edge checks for loading and generating light
Browse files Browse the repository at this point in the history
While it might appear at first glance that the vanilla
chunk system makes a guarantee that 1 radius chunks are
loaded for lighting, this isn't actually the case. In fact,
no chunks could be loaded! And yet, the light function is
actually required to be completed still! The only way
we can do this is with edge checks.

In the 1.17 snapshots, it looks like chunk loading doesn't
load 1 radius chunks - which means our logic to require 1 radius
just has to go. In order to ensure that this new behavior
doesn't cause lighting problems on chunk edges, chunk loading
now does edge checks.

At this point, the entire codebase is 1.17 ready and likely
ready to be published on curseforge.
  • Loading branch information
Spottedleaf committed Jan 16, 2021
1 parent 16e8819 commit 7dc6c0b
Show file tree
Hide file tree
Showing 10 changed files with 383 additions and 89 deletions.
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ minecraft_version=1.16.4
yarn_mappings=1.16.4+build.7
loader_version=0.10.8
# Mod Properties
mod_version=0.0.2
mod_version=0.0.3
maven_group=ca.spottedleaf.starlight
archives_base_name=starlight
# Dependencies
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
public interface ExtendedChunkSection {

public static final long BLOCK_IS_TRANSPARENT = 0b00;
public static final long BLOCK_UNKNOWN_TRANSPARENCY = 0b01;
// 0b11 is unused
public static final long BLOCK_IS_FULL_OPAQUE = 0b01;
public static final long BLOCK_UNKNOWN_TRANSPARENCY = 0b10;
public static final long BLOCK_SPECIAL_TRANSPARENCY = 0b11;

public boolean hasOpaqueBlocks();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@

import ca.spottedleaf.starlight.common.blockstate.ExtendedAbstractBlockState;
import ca.spottedleaf.starlight.common.chunk.ExtendedChunk;
import ca.spottedleaf.starlight.common.chunk.ExtendedChunkSection;
import ca.spottedleaf.starlight.common.world.ExtendedWorld;
import net.minecraft.block.BlockState;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.shape.VoxelShape;
import net.minecraft.util.shape.VoxelShapes;
import net.minecraft.world.World;
import net.minecraft.world.chunk.Chunk;
import net.minecraft.world.chunk.ChunkProvider;
Expand Down Expand Up @@ -93,6 +96,83 @@ protected final void checkBlock(final ChunkProvider lightAccess, final int world
// re-propagating neighbours (done by the decrease queue) will also account for opacity changes in this block
}

protected final BlockPos.Mutable recalcCenterPos = new BlockPos.Mutable();
protected final BlockPos.Mutable recalcNeighbourPos = new BlockPos.Mutable();

@Override
protected int calculateLightValue(final ChunkProvider lightAccess, final int worldX, final int worldY, final int worldZ,
final int expect, final VariableBlockLightHandler customBlockLight) {
final BlockState centerState = this.getBlockState(worldX, worldY, worldZ);
int level = centerState.getLuminance() & 0xFF;
if (customBlockLight != null) {
level = this.getCustomLightLevel(customBlockLight, worldX, worldY, worldZ, level);
}

if (level >= (15 - 1) || level > expect) {
return level;
}

final int sectionOffset = this.chunkSectionIndexOffset;
final BlockState conditionallyOpaqueState;
int opacity = ((ExtendedAbstractBlockState)centerState).getOpacityIfCached();

if (opacity == -1) {
this.recalcCenterPos.set(worldX, worldY, worldZ);
opacity = centerState.getOpacity(lightAccess.getWorld(), this.recalcCenterPos);
if (((ExtendedAbstractBlockState)centerState).isConditionallyFullOpaque()) {
conditionallyOpaqueState = centerState;
} else {
conditionallyOpaqueState = null;
}
} else if (opacity >= 15) {
return level;
} else {
conditionallyOpaqueState = null;
}
opacity = Math.max(1, opacity);

for (final AxisDirection direction : AXIS_DIRECTIONS) {
final int offX = worldX + direction.x;
final int offY = worldY + direction.y;
final int offZ = worldZ + direction.z;

final int sectionIndex = (offX >> 4) + 5 * (offZ >> 4) + (5 * 5) * (offY >> 4) + sectionOffset;

final int neighbourLevel = this.getLightLevel(sectionIndex, (offX & 15) | ((offZ & 15) << 4) | ((offY & 15) << 8));

if ((neighbourLevel - 1) <= level) {
// don't need to test transparency, we know it wont affect the result.
continue;
}

final long neighbourOpacity = this.getKnownTransparency(sectionIndex, (offY & 15) | ((offX & 15) << 4) | ((offZ & 15) << 8));

if (neighbourOpacity == ExtendedChunkSection.BLOCK_SPECIAL_TRANSPARENCY) {
// here the block can be conditionally opaque (i.e light cannot propagate from it), so we need to test that
// we don't read the blockstate because most of the time this is false, so using the faster
// known transparency lookup results in a net win
final BlockState neighbourState = this.getBlockState(offX, offY, offZ);
this.recalcNeighbourPos.set(offX, offY, offZ);
final VoxelShape neighbourFace = neighbourState.getCullingFace(lightAccess.getWorld(), this.recalcNeighbourPos, direction.opposite.nms);
final VoxelShape thisFace = conditionallyOpaqueState == null ? VoxelShapes.empty() : conditionallyOpaqueState.getCullingFace(lightAccess.getWorld(), this.recalcCenterPos, direction.nms);
if (VoxelShapes.unionCoversFullCube(thisFace, neighbourFace)) {
// not allowed to propagate
continue;
}
}

// passed transparency,

final int calculated = neighbourLevel - opacity;
level = Math.max(calculated, level);
if (level > expect) {
return level;
}
}

return level;
}

@Override
protected void propagateBlockChanges(final ChunkProvider lightAccess, final Chunk atChunk, final Set<BlockPos> positions) {
for (final BlockPos pos : positions) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,21 @@ public SWMRNibbleArray(final byte[] bytes, final boolean isNullNibble) {

// operation type: visible
public boolean isAllZero() {
final byte[] bytes = this.storageVisible;
final int state = this.stateVisible;

if (this.storageVisible == null) {
if (state == INIT_STATE_NULL) {
return false;
} else if (state == INIT_STATE_UNINIT) {
return true;
}

synchronized (this) {
final byte[] bytes = this.storageVisible;

if (bytes == null) {
return this.stateVisible == INIT_STATE_UNINIT;
}

for (int i = 0; i < (ARRAY_SIZE >>> 4); ++i) {
byte whole = bytes[i << 4];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import ca.spottedleaf.starlight.common.chunk.ExtendedChunkSection;
import ca.spottedleaf.starlight.common.util.WorldUtil;
import it.unimi.dsi.fastutil.shorts.ShortCollection;
import it.unimi.dsi.fastutil.shorts.ShortIterator;
import net.minecraft.block.BlockState;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.ChunkPos;
Expand Down Expand Up @@ -345,13 +346,28 @@ protected boolean canUseChunk(final Chunk chunk) {
@Override
protected void checkChunkEdges(final ChunkProvider lightAccess, final Chunk chunk, final int fromSection,
final int toSection) {
Arrays.fill(this.nullPropagationCheckCache, false);
this.rewriteNibbleCacheForSkylight(chunk);
final int chunkX = chunk.getPos().x;
final int chunkZ = chunk.getPos().z;
for (int y = toSection; y >= fromSection; --y) {
this.checkNullSection(chunkX, y, chunkZ, true);
}

super.checkChunkEdges(lightAccess, chunk, fromSection, toSection);
}

@Override
protected void checkChunkEdges(ChunkProvider lightAccess, Chunk chunk, ShortCollection sections) {
protected void checkChunkEdges(final ChunkProvider lightAccess, final Chunk chunk, final ShortCollection sections) {
Arrays.fill(this.nullPropagationCheckCache, false);
this.rewriteNibbleCacheForSkylight(chunk);
final int chunkX = chunk.getPos().x;
final int chunkZ = chunk.getPos().z;
for (final ShortIterator iterator = sections.iterator(); iterator.hasNext();) {
final int y = (int)iterator.nextShort();
this.checkNullSection(chunkX, y, chunkZ, true);
}

super.checkChunkEdges(lightAccess, chunk, sections);
}

Expand Down Expand Up @@ -385,6 +401,88 @@ protected void checkBlock(final ChunkProvider lightAccess, final int worldX, fin
);
}

protected final BlockPos.Mutable recalcCenterPos = new BlockPos.Mutable();
protected final BlockPos.Mutable recalcNeighbourPos = new BlockPos.Mutable();

@Override
protected int calculateLightValue(final ChunkProvider lightAccess, final int worldX, final int worldY, final int worldZ,
final int expect, final VariableBlockLightHandler customBlockLight) {
if (expect == 15) {
return expect;
}

final int sectionOffset = this.chunkSectionIndexOffset;
final int opacity;
final BlockState conditionallyOpaqueState;
switch ((int)this.getKnownTransparency(worldX, worldY, worldZ)) {
case (int)ExtendedChunkSection.BLOCK_IS_TRANSPARENT:
opacity = 1;
conditionallyOpaqueState = null;
break;
case (int)ExtendedChunkSection.BLOCK_IS_FULL_OPAQUE:
return 0;
case (int)ExtendedChunkSection.BLOCK_UNKNOWN_TRANSPARENCY:
opacity = Math.max(1, ((ExtendedAbstractBlockState)this.getBlockState(worldX, worldY, worldZ)).getOpacityIfCached());
conditionallyOpaqueState = null;
if (opacity >= 15) {
return 0;
}
break;
// variable opacity | conditionally full opaque
case (int)ExtendedChunkSection.BLOCK_SPECIAL_TRANSPARENCY:
default:
this.recalcCenterPos.set(worldX, worldY, worldZ);
final BlockState state = this.getBlockState(worldX, worldY, worldZ);
opacity = Math.max(1, state.getOpacity(lightAccess.getWorld(), this.recalcCenterPos));
if (((ExtendedAbstractBlockState)state).isConditionallyFullOpaque()) {
conditionallyOpaqueState = state;
} else {
conditionallyOpaqueState = null;
}
}

int level = 0;

for (final AxisDirection direction : AXIS_DIRECTIONS) {
final int offX = worldX + direction.x;
final int offY = worldY + direction.y;
final int offZ = worldZ + direction.z;

final int sectionIndex = (offX >> 4) + 5 * (offZ >> 4) + (5 * 5) * (offY >> 4) + sectionOffset;

final int neighbourLevel = this.getLightLevel(sectionIndex, (offX & 15) | ((offZ & 15) << 4) | ((offY & 15) << 8));

if ((neighbourLevel - 1) <= level) {
// don't need to test transparency, we know it wont affect the result.
continue;
}

final long neighbourOpacity = this.getKnownTransparency(sectionIndex, (offY & 15) | ((offX & 15) << 4) | ((offZ & 15) << 8));

if (neighbourOpacity == ExtendedChunkSection.BLOCK_SPECIAL_TRANSPARENCY) {
// here the block can be conditionally opaque (i.e light cannot propagate from it), so we need to test that
// we don't read the blockstate because most of the time this is false, so using the faster
// known transparency lookup results in a net win
final BlockState neighbourState = this.getBlockState(offX, offY, offZ);
this.recalcNeighbourPos.set(offX, offY, offZ);
final VoxelShape neighbourFace = neighbourState.getCullingFace(lightAccess.getWorld(), this.recalcNeighbourPos, direction.opposite.nms);
final VoxelShape thisFace = conditionallyOpaqueState == null ? VoxelShapes.empty() : conditionallyOpaqueState.getCullingFace(lightAccess.getWorld(), this.recalcCenterPos, direction.nms);
if (VoxelShapes.unionCoversFullCube(thisFace, neighbourFace)) {
// not allowed to propagate
continue;
}
}

final int calculated = neighbourLevel - opacity;
level = Math.max(calculated, level);
if (level > expect) {
return level;
}
}

return level;
}

@Override
protected void propagateBlockChanges(final ChunkProvider lightAccess, final Chunk atChunk, final Set<BlockPos> positions) {
this.rewriteNibbleCacheForSkylight(atChunk);
Expand Down Expand Up @@ -597,7 +695,7 @@ protected void lightChunk(final ChunkProvider lightAccess, final Chunk chunk, fi
}

final int highestBitSet = 63 ^ Long.numberOfLeadingZeros(bitset); // from [0, 63]
final int highestYValue = highestBitSet; // y = highest bit set / bits per block
final int highestYValue = highestBitSet >>> 1; // y = highest bit set / bits per block
maxY = highestYValue | (sectionY << 4);
break;
}
Expand Down Expand Up @@ -686,10 +784,11 @@ protected void lightChunk(final ChunkProvider lightAccess, final Chunk chunk, fi
// not required to propagate here, but this will reduce the hit of the edge checks
this.performLightIncrease(lightAccess);

for (int y = this.maxLightSection; y >= this.minLightSection; --y) {
for (int y = highestNonEmptySection; y >= this.minLightSection; --y) {
this.checkNullSection(chunkX, y, chunkZ, false);
}
this.checkChunkEdges(lightAccess, chunk, this.minLightSection, this.maxLightSection);
// no need to rewrite the nibble cache again
super.checkChunkEdges(lightAccess, chunk, this.minLightSection, highestNonEmptySection);
} else {
for (int y = highestNonEmptySection; y >= this.minLightSection; --y) {
this.checkNullSection(chunkX, y, chunkZ, false);
Expand Down
Loading

0 comments on commit 7dc6c0b

Please sign in to comment.