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

Commit 7dc6c0b

Browse files
committed
Move to edge checks for loading and generating light
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.
1 parent 16e8819 commit 7dc6c0b

File tree

10 files changed

+383
-89
lines changed

10 files changed

+383
-89
lines changed

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ minecraft_version=1.16.4
66
yarn_mappings=1.16.4+build.7
77
loader_version=0.10.8
88
# Mod Properties
9-
mod_version=0.0.2
9+
mod_version=0.0.3
1010
maven_group=ca.spottedleaf.starlight
1111
archives_base_name=starlight
1212
# Dependencies

src/main/java/ca/spottedleaf/starlight/common/chunk/ExtendedChunkSection.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
public interface ExtendedChunkSection {
44

55
public static final long BLOCK_IS_TRANSPARENT = 0b00;
6-
public static final long BLOCK_UNKNOWN_TRANSPARENCY = 0b01;
7-
// 0b11 is unused
6+
public static final long BLOCK_IS_FULL_OPAQUE = 0b01;
7+
public static final long BLOCK_UNKNOWN_TRANSPARENCY = 0b10;
8+
public static final long BLOCK_SPECIAL_TRANSPARENCY = 0b11;
89

910
public boolean hasOpaqueBlocks();
1011

src/main/java/ca/spottedleaf/starlight/common/light/BlockStarLightEngine.java

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22

33
import ca.spottedleaf.starlight.common.blockstate.ExtendedAbstractBlockState;
44
import ca.spottedleaf.starlight.common.chunk.ExtendedChunk;
5+
import ca.spottedleaf.starlight.common.chunk.ExtendedChunkSection;
56
import ca.spottedleaf.starlight.common.world.ExtendedWorld;
67
import net.minecraft.block.BlockState;
78
import net.minecraft.util.math.BlockPos;
9+
import net.minecraft.util.shape.VoxelShape;
10+
import net.minecraft.util.shape.VoxelShapes;
811
import net.minecraft.world.World;
912
import net.minecraft.world.chunk.Chunk;
1013
import net.minecraft.world.chunk.ChunkProvider;
@@ -93,6 +96,83 @@ protected final void checkBlock(final ChunkProvider lightAccess, final int world
9396
// re-propagating neighbours (done by the decrease queue) will also account for opacity changes in this block
9497
}
9598

99+
protected final BlockPos.Mutable recalcCenterPos = new BlockPos.Mutable();
100+
protected final BlockPos.Mutable recalcNeighbourPos = new BlockPos.Mutable();
101+
102+
@Override
103+
protected int calculateLightValue(final ChunkProvider lightAccess, final int worldX, final int worldY, final int worldZ,
104+
final int expect, final VariableBlockLightHandler customBlockLight) {
105+
final BlockState centerState = this.getBlockState(worldX, worldY, worldZ);
106+
int level = centerState.getLuminance() & 0xFF;
107+
if (customBlockLight != null) {
108+
level = this.getCustomLightLevel(customBlockLight, worldX, worldY, worldZ, level);
109+
}
110+
111+
if (level >= (15 - 1) || level > expect) {
112+
return level;
113+
}
114+
115+
final int sectionOffset = this.chunkSectionIndexOffset;
116+
final BlockState conditionallyOpaqueState;
117+
int opacity = ((ExtendedAbstractBlockState)centerState).getOpacityIfCached();
118+
119+
if (opacity == -1) {
120+
this.recalcCenterPos.set(worldX, worldY, worldZ);
121+
opacity = centerState.getOpacity(lightAccess.getWorld(), this.recalcCenterPos);
122+
if (((ExtendedAbstractBlockState)centerState).isConditionallyFullOpaque()) {
123+
conditionallyOpaqueState = centerState;
124+
} else {
125+
conditionallyOpaqueState = null;
126+
}
127+
} else if (opacity >= 15) {
128+
return level;
129+
} else {
130+
conditionallyOpaqueState = null;
131+
}
132+
opacity = Math.max(1, opacity);
133+
134+
for (final AxisDirection direction : AXIS_DIRECTIONS) {
135+
final int offX = worldX + direction.x;
136+
final int offY = worldY + direction.y;
137+
final int offZ = worldZ + direction.z;
138+
139+
final int sectionIndex = (offX >> 4) + 5 * (offZ >> 4) + (5 * 5) * (offY >> 4) + sectionOffset;
140+
141+
final int neighbourLevel = this.getLightLevel(sectionIndex, (offX & 15) | ((offZ & 15) << 4) | ((offY & 15) << 8));
142+
143+
if ((neighbourLevel - 1) <= level) {
144+
// don't need to test transparency, we know it wont affect the result.
145+
continue;
146+
}
147+
148+
final long neighbourOpacity = this.getKnownTransparency(sectionIndex, (offY & 15) | ((offX & 15) << 4) | ((offZ & 15) << 8));
149+
150+
if (neighbourOpacity == ExtendedChunkSection.BLOCK_SPECIAL_TRANSPARENCY) {
151+
// here the block can be conditionally opaque (i.e light cannot propagate from it), so we need to test that
152+
// we don't read the blockstate because most of the time this is false, so using the faster
153+
// known transparency lookup results in a net win
154+
final BlockState neighbourState = this.getBlockState(offX, offY, offZ);
155+
this.recalcNeighbourPos.set(offX, offY, offZ);
156+
final VoxelShape neighbourFace = neighbourState.getCullingFace(lightAccess.getWorld(), this.recalcNeighbourPos, direction.opposite.nms);
157+
final VoxelShape thisFace = conditionallyOpaqueState == null ? VoxelShapes.empty() : conditionallyOpaqueState.getCullingFace(lightAccess.getWorld(), this.recalcCenterPos, direction.nms);
158+
if (VoxelShapes.unionCoversFullCube(thisFace, neighbourFace)) {
159+
// not allowed to propagate
160+
continue;
161+
}
162+
}
163+
164+
// passed transparency,
165+
166+
final int calculated = neighbourLevel - opacity;
167+
level = Math.max(calculated, level);
168+
if (level > expect) {
169+
return level;
170+
}
171+
}
172+
173+
return level;
174+
}
175+
96176
@Override
97177
protected void propagateBlockChanges(final ChunkProvider lightAccess, final Chunk atChunk, final Set<BlockPos> positions) {
98178
for (final BlockPos pos : positions) {

src/main/java/ca/spottedleaf/starlight/common/light/SWMRNibbleArray.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,13 +67,21 @@ public SWMRNibbleArray(final byte[] bytes, final boolean isNullNibble) {
6767

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

72-
if (this.storageVisible == null) {
72+
if (state == INIT_STATE_NULL) {
73+
return false;
74+
} else if (state == INIT_STATE_UNINIT) {
7375
return true;
7476
}
7577

7678
synchronized (this) {
79+
final byte[] bytes = this.storageVisible;
80+
81+
if (bytes == null) {
82+
return this.stateVisible == INIT_STATE_UNINIT;
83+
}
84+
7785
for (int i = 0; i < (ARRAY_SIZE >>> 4); ++i) {
7886
byte whole = bytes[i << 4];
7987

src/main/java/ca/spottedleaf/starlight/common/light/SkyStarLightEngine.java

Lines changed: 103 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import ca.spottedleaf.starlight.common.chunk.ExtendedChunkSection;
66
import ca.spottedleaf.starlight.common.util.WorldUtil;
77
import it.unimi.dsi.fastutil.shorts.ShortCollection;
8+
import it.unimi.dsi.fastutil.shorts.ShortIterator;
89
import net.minecraft.block.BlockState;
910
import net.minecraft.util.math.BlockPos;
1011
import net.minecraft.util.math.ChunkPos;
@@ -345,13 +346,28 @@ protected boolean canUseChunk(final Chunk chunk) {
345346
@Override
346347
protected void checkChunkEdges(final ChunkProvider lightAccess, final Chunk chunk, final int fromSection,
347348
final int toSection) {
349+
Arrays.fill(this.nullPropagationCheckCache, false);
348350
this.rewriteNibbleCacheForSkylight(chunk);
351+
final int chunkX = chunk.getPos().x;
352+
final int chunkZ = chunk.getPos().z;
353+
for (int y = toSection; y >= fromSection; --y) {
354+
this.checkNullSection(chunkX, y, chunkZ, true);
355+
}
356+
349357
super.checkChunkEdges(lightAccess, chunk, fromSection, toSection);
350358
}
351359

352360
@Override
353-
protected void checkChunkEdges(ChunkProvider lightAccess, Chunk chunk, ShortCollection sections) {
361+
protected void checkChunkEdges(final ChunkProvider lightAccess, final Chunk chunk, final ShortCollection sections) {
362+
Arrays.fill(this.nullPropagationCheckCache, false);
354363
this.rewriteNibbleCacheForSkylight(chunk);
364+
final int chunkX = chunk.getPos().x;
365+
final int chunkZ = chunk.getPos().z;
366+
for (final ShortIterator iterator = sections.iterator(); iterator.hasNext();) {
367+
final int y = (int)iterator.nextShort();
368+
this.checkNullSection(chunkX, y, chunkZ, true);
369+
}
370+
355371
super.checkChunkEdges(lightAccess, chunk, sections);
356372
}
357373

@@ -385,6 +401,88 @@ protected void checkBlock(final ChunkProvider lightAccess, final int worldX, fin
385401
);
386402
}
387403

404+
protected final BlockPos.Mutable recalcCenterPos = new BlockPos.Mutable();
405+
protected final BlockPos.Mutable recalcNeighbourPos = new BlockPos.Mutable();
406+
407+
@Override
408+
protected int calculateLightValue(final ChunkProvider lightAccess, final int worldX, final int worldY, final int worldZ,
409+
final int expect, final VariableBlockLightHandler customBlockLight) {
410+
if (expect == 15) {
411+
return expect;
412+
}
413+
414+
final int sectionOffset = this.chunkSectionIndexOffset;
415+
final int opacity;
416+
final BlockState conditionallyOpaqueState;
417+
switch ((int)this.getKnownTransparency(worldX, worldY, worldZ)) {
418+
case (int)ExtendedChunkSection.BLOCK_IS_TRANSPARENT:
419+
opacity = 1;
420+
conditionallyOpaqueState = null;
421+
break;
422+
case (int)ExtendedChunkSection.BLOCK_IS_FULL_OPAQUE:
423+
return 0;
424+
case (int)ExtendedChunkSection.BLOCK_UNKNOWN_TRANSPARENCY:
425+
opacity = Math.max(1, ((ExtendedAbstractBlockState)this.getBlockState(worldX, worldY, worldZ)).getOpacityIfCached());
426+
conditionallyOpaqueState = null;
427+
if (opacity >= 15) {
428+
return 0;
429+
}
430+
break;
431+
// variable opacity | conditionally full opaque
432+
case (int)ExtendedChunkSection.BLOCK_SPECIAL_TRANSPARENCY:
433+
default:
434+
this.recalcCenterPos.set(worldX, worldY, worldZ);
435+
final BlockState state = this.getBlockState(worldX, worldY, worldZ);
436+
opacity = Math.max(1, state.getOpacity(lightAccess.getWorld(), this.recalcCenterPos));
437+
if (((ExtendedAbstractBlockState)state).isConditionallyFullOpaque()) {
438+
conditionallyOpaqueState = state;
439+
} else {
440+
conditionallyOpaqueState = null;
441+
}
442+
}
443+
444+
int level = 0;
445+
446+
for (final AxisDirection direction : AXIS_DIRECTIONS) {
447+
final int offX = worldX + direction.x;
448+
final int offY = worldY + direction.y;
449+
final int offZ = worldZ + direction.z;
450+
451+
final int sectionIndex = (offX >> 4) + 5 * (offZ >> 4) + (5 * 5) * (offY >> 4) + sectionOffset;
452+
453+
final int neighbourLevel = this.getLightLevel(sectionIndex, (offX & 15) | ((offZ & 15) << 4) | ((offY & 15) << 8));
454+
455+
if ((neighbourLevel - 1) <= level) {
456+
// don't need to test transparency, we know it wont affect the result.
457+
continue;
458+
}
459+
460+
final long neighbourOpacity = this.getKnownTransparency(sectionIndex, (offY & 15) | ((offX & 15) << 4) | ((offZ & 15) << 8));
461+
462+
if (neighbourOpacity == ExtendedChunkSection.BLOCK_SPECIAL_TRANSPARENCY) {
463+
// here the block can be conditionally opaque (i.e light cannot propagate from it), so we need to test that
464+
// we don't read the blockstate because most of the time this is false, so using the faster
465+
// known transparency lookup results in a net win
466+
final BlockState neighbourState = this.getBlockState(offX, offY, offZ);
467+
this.recalcNeighbourPos.set(offX, offY, offZ);
468+
final VoxelShape neighbourFace = neighbourState.getCullingFace(lightAccess.getWorld(), this.recalcNeighbourPos, direction.opposite.nms);
469+
final VoxelShape thisFace = conditionallyOpaqueState == null ? VoxelShapes.empty() : conditionallyOpaqueState.getCullingFace(lightAccess.getWorld(), this.recalcCenterPos, direction.nms);
470+
if (VoxelShapes.unionCoversFullCube(thisFace, neighbourFace)) {
471+
// not allowed to propagate
472+
continue;
473+
}
474+
}
475+
476+
final int calculated = neighbourLevel - opacity;
477+
level = Math.max(calculated, level);
478+
if (level > expect) {
479+
return level;
480+
}
481+
}
482+
483+
return level;
484+
}
485+
388486
@Override
389487
protected void propagateBlockChanges(final ChunkProvider lightAccess, final Chunk atChunk, final Set<BlockPos> positions) {
390488
this.rewriteNibbleCacheForSkylight(atChunk);
@@ -597,7 +695,7 @@ protected void lightChunk(final ChunkProvider lightAccess, final Chunk chunk, fi
597695
}
598696

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

689-
for (int y = this.maxLightSection; y >= this.minLightSection; --y) {
787+
for (int y = highestNonEmptySection; y >= this.minLightSection; --y) {
690788
this.checkNullSection(chunkX, y, chunkZ, false);
691789
}
692-
this.checkChunkEdges(lightAccess, chunk, this.minLightSection, this.maxLightSection);
790+
// no need to rewrite the nibble cache again
791+
super.checkChunkEdges(lightAccess, chunk, this.minLightSection, highestNonEmptySection);
693792
} else {
694793
for (int y = highestNonEmptySection; y >= this.minLightSection; --y) {
695794
this.checkNullSection(chunkX, y, chunkZ, false);

0 commit comments

Comments
 (0)