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

Commit

Permalink
Starlight 0.0.2-RC1
Browse files Browse the repository at this point in the history
1. Optimise chunk sky generation.
 ChunkSections now store 2 extra bits per block, which let us
 define 3 important states: transparent, full opaque, and unknown.
 The bitset is first stored in increasing y, so we can detect
 the highest non-transparent block per column in constant time.

 We then use this bitset to create a heightmap during chunk
 lighting. Instead of creating a propagation per block
 along each column for sky sources, we use the heightmap
 to detect if we need to create one (i.e, if we're above
 the heightmap for the x/z neighbours), and what
 directions to check. For exampl,e if some x, y, z
 is above the heightmap for its +x, +z, -z neighbours,
 but below the heightmap for its -x neighbour, we only
 tell the source to try and propagate to -x. If we
 were above the -x neighbour, the source would not be
 queued (unless it's 1 block higher than the heightmap
 for its column).

 Some local testing showed that this brought generation
 from 0.66ms/chunk to 0.50ms/chunk, although this was
 not tested on the same terrain (so it could be worse
 or better, will have to check).

 Depending on how some benchmarks go, this system might
 be refactored or removed entirely - I'm a bit worried
 about the memory usage implications.

2. New system for managing skylight
 The old system was pretty bad, it never de-initialised
 nibbles, it had compatibility problems with vanilla &
 vanilla clients, and it over-initialised nibbles.

 The new system is structured very similar to vanilla,
 where all non-empty chunksections have their 1 radius
 neighbour nibbles initialised. So this will be compatible
 with vanilla clients. However, this system relies upon
 saving uninitialised nibbles to disk and has a different
 stratedgy in-place for de-initialising nibbles. We
 maintain vanilla save compatibility by storing chunks
 as lit = false, so when vanilla loads the world it will
 simply re-generate the light.

 De-initialising nibbles is done lazily, whereas
 the initialisation of nibbles is always done immediately.
 In order for a chunk to correctly determine if a nibble
 should be de-initialised, it must have its 1 radius
 neighbours loaded. So, once the 1 radius neighbours
 are loaded into the light engine, de-initialisation
 checks are performed on all of the nibbles on the chunk.
 The vanilla engine doesn't do this, so that's why I had
 to make vanilla re-generate light when loading starlight
 saves. However, de-initialising lazily allows us to
 make nibble initialisation rather simple, and not
 force the initialisation process also light chunks
 (which is what vanilla does, and it's why it
 struggles often when nibbles have to be initialised).

 Initialising nibbles, as previously stated, will not
 perform lighting logic, unlike vanilla. This is a
 really strong point about this light engine, as
 I personally noticed that when crossing chunksections
 while building on platforms at y=255 resulted in a
 terrible 100ms or so spike locally because of vanilla's
 initialisation logic. Obviously with starlight,
 this does not happen. In fact, when I was testing
 out the new nibble management the largest spike
 I noticed for block changes at extreme heights (y = 255)
 was 4.0ms (in starlight 0.0.1, this would have been around
 10.0ms or so). This means that starlight 0.0.2 should be
 pretty indestructable when 1.17 hits and people
 start using worldheights of 1000 blocks. As a sidenote,
 I will be publishing snapshot builds in the coming
 weeks.

 Skylight propagation code had to be modified to account
 for the new management, and I think the solution I introduced is pretty
 solid. Nibbles are always initialised/de-initialised before block changes
 are processed (on a more technical note: vanilla queues section changes
 before block changes, so we are automatically guarded against
 block-change-before- section-change race conditions).
 When propagating skylight downwards (increase or decrease),
 we have to be mindful of null nibbles. In most cases, we can just
 skip over the null nibble. But, the null nibble can have
 initialised neighbour(s). In this case, we know that the neighbours
 are actually empty, which means the skylight we are propagating
 probably affects the light levels of the initialised neighbours. So
 we need to somehow deal with the null nibbles and propagate to the
 initialised neighbour(s). The solution I have is to transiently
 (initialise just for the propagation call, and afterwards discord)
 initialise all of the nibbles on the x/z plane in 1 radius. Since
 we know the neighbours are all empty, we can get avoid initialising
 the nibbles above and below (as we don't need to worry about
 propagation paths through those, as we know the paths on the
 horizontal plane will be shorter and therefore be the ones affecting
 the light levels).

3. Cleanup some of the mixins
 Introduce access wideners to get around visibility issues, use
 at-Unique in areas it should have been used.

4. Fix propagation & render issues
 Skylight generation was broken when blocks were on the
 y-border for the highest non-empty chunk section. This
 has since been fixed.
 Not queueing neighbour chunksections to be re-rendered
 in some edge cases has since been fixed.

5. Fix some mod compatibility issues
 Apparently some mods create instances of the lighting
 provider with a null world. Allow null worlds.
 Some problems still exist, as I think some mods are being
 naughty and calling setBlockState off of the main thread.
 Tuinity's changes will need to be ported to fix this.

Fixes #8
Fixes #7
Fixes #2
Fixes #1

For those of you with lighting issues from any of the above
issues, this new build will erase all light data and re-calculate.
  • Loading branch information
Spottedleaf committed Dec 21, 2020
1 parent 834a3c7 commit 02e6333
Show file tree
Hide file tree
Showing 39 changed files with 2,483 additions and 1,015 deletions.
4 changes: 4 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ tasks.withType(JavaCompile) {
options.encoding = "UTF-8"
}

minecraft {
accessWidener "src/main/resources/starlight.accesswidener"
}

// Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task
// if it is present.
// If you remove this task, sources will not be generated.
Expand Down
6 changes: 3 additions & 3 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ org.gradle.jvmargs=-Xmx1G
# Fabric Properties
# check these on https://modmuss50.me/fabric.html
minecraft_version=1.16.4
yarn_mappings=1.16.4+build.1
loader_version=0.10.6+build.214
yarn_mappings=1.16.4+build.7
loader_version=0.10.8
# Mod Properties
mod_version=0.0.1-SNAPSHOT
mod_version=0.0.2-RC1
maven_group=ca.spottedleaf.starlight
archives_base_name=starlight
# Dependencies
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package ca.spottedleaf.starlight.common.blockstate;

public interface LightAccessBlockState {
public interface ExtendedAbstractBlockState {

public boolean isConditionallyFullOpaque();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package ca.spottedleaf.starlight.common.chunk;

import ca.spottedleaf.starlight.common.light.SWMRNibbleArray;

public interface ExtendedChunk {

public SWMRNibbleArray[] getBlockNibbles();
public void setBlockNibbles(final SWMRNibbleArray[] nibbles);

public SWMRNibbleArray[] getSkyNibbles();
public void setSkyNibbles(final SWMRNibbleArray[] nibbles);

// cx, cz are relative to the target chunk's map
public static int getEmptinessMapIndex(final int cx, final int cz) {
//return (cx + 1) + 3*(cz + 1);
return (1 + 3 * 1) + (cx) + 3*(cz);
}
public boolean[][] getEmptinessMap();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package ca.spottedleaf.starlight.common.chunk;

public interface ExtendedChunkSection {

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

public boolean hasOpaqueBlocks();

/* NOTE: Index is y | (x << 4) | (z << 8) */
public long getKnownTransparency(final int blockIndex);

public long getBitsetForColumn(final int columnX, final int columnZ);
}

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
package ca.spottedleaf.starlight.common.light;

import ca.spottedleaf.starlight.common.blockstate.LightAccessBlockState;
import ca.spottedleaf.starlight.common.chunk.NibbledChunk;
import ca.spottedleaf.starlight.common.blockstate.ExtendedAbstractBlockState;
import ca.spottedleaf.starlight.common.chunk.ExtendedChunk;
import net.minecraft.block.BlockState;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.chunk.Chunk;
import net.minecraft.world.chunk.ChunkProvider;
import net.minecraft.world.chunk.ChunkSection;
import net.minecraft.world.chunk.ChunkStatus;
import net.minecraft.world.chunk.PalettedContainer;
import net.minecraft.world.chunk.ReadOnlyChunk;
import net.minecraft.world.chunk.WorldChunk;
import java.util.ArrayList;
Expand All @@ -23,19 +24,23 @@ public BlockStarLightEngine(final boolean isClientSide) {

@Override
protected SWMRNibbleArray[] getNibblesOnChunk(final Chunk chunk) {
return ((NibbledChunk)chunk).getBlockNibbles();
return ((ExtendedChunk)chunk).getBlockNibbles();
}

@Override
protected void setNibbles(final Chunk chunk, final SWMRNibbleArray[] to) {
((NibbledChunk)chunk).setBlockNibbles(to);
((ExtendedChunk)chunk).setBlockNibbles(to);
}

@Override
protected boolean canUseChunk(final Chunk chunk) {
return chunk.getStatus().isAtLeast(ChunkStatus.LIGHT) && (this.isClientSide || chunk.isLightOn());
}

@Override
protected void handleEmptySectionChanges(final ChunkProvider lightAccess, final Chunk chunk,
final Boolean[] emptinessChanges, final boolean unlit) {}

@Override
protected final void checkBlock(final int worldX, final int worldY, final int worldZ) {
// blocks can change opacity
Expand All @@ -52,27 +57,28 @@ protected final void checkBlock(final int worldX, final int worldY, final int wo
this.setLightLevel(worldX, worldY, worldZ, emittedLevel);
// this accounts for change in emitted light that would cause an increase
if (emittedLevel != 0) {
this.increaseQueue[this.increaseQueueInitialLength++] = (worldX + (worldZ << 6) + (worldY << (6 + 6)) + encodeOffset) |
emittedLevel << (6 + 6 + 9) |
((AxisDirection.POSITIVE_X.ordinal() | 8) << (6 + 6 + 9 + 4)) |
(((LightAccessBlockState)blockState).isConditionallyFullOpaque() ? FLAG_HAS_SIDED_TRANSPARENT_BLOCKS : 0);
this.increaseQueue[this.increaseQueueInitialLength++] =
((worldX + (worldZ << 6) + (worldY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1))
| (emittedLevel & 0xFL) << (6 + 6 + 16)
| (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4))
| (((ExtendedAbstractBlockState)blockState).isConditionallyFullOpaque() ? FLAG_HAS_SIDED_TRANSPARENT_BLOCKS : 0);
}
// this also accounts for a change in emitted light that would cause a decrease
// this also accounts for the change of direction of propagation (i.e old block was full transparent, new block is full opaque or vice versa)
// as it checks all neighbours (even if current level is 0)
this.decreaseQueue[this.decreaseQueueInitialLength++] = (worldX + (worldZ << 6) + (worldY << (6 + 6)) + encodeOffset) |
currentLevel << (6 + 6 + 9) |
((AxisDirection.POSITIVE_X.ordinal() | 8) << (6 + 6 + 9 + 4));
// always keep sided transparent false here, new block might be conditionally transparent which would
// prevent us from decreasing sources in the directions where the new block is opaque
// if it turns out we were wrong to de-propagate the source, the re-propagate logic WILL always
// catch that and fix it.
this.decreaseQueue[this.decreaseQueueInitialLength++] =
((worldX + (worldZ << 6) + (worldY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1))
| (currentLevel & 0xFL) << (6 + 6 + 16)
| (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4));
// always keep sided transparent false here, new block might be conditionally transparent which would
// prevent us from decreasing sources in the directions where the new block is opaque
// if it turns out we were wrong to de-propagate the source, the re-propagate logic WILL always
// catch that and fix it.
// re-propagating neighbours (done by the decrease queue) will also account for opacity changes in this block
}

@Override
protected void propagateBlockChanges(final ChunkProvider lightAccess, final Chunk atChunk,
final Set<BlockPos> positions) {
protected void propagateBlockChanges(final ChunkProvider lightAccess, final Chunk atChunk, final Set<BlockPos> positions) {
for (final BlockPos pos : positions) {
this.checkBlock(pos.getX(), pos.getY(), pos.getZ());
}
Expand All @@ -95,20 +101,17 @@ protected Iterator<BlockPos> getSources(final Chunk chunk) {
// no sources in empty sections
continue;
}
final ChunkSection section = sections[sectionY];

for (int localY = 0; localY <= 15; ++localY) {
final int realY = localY | (sectionY << 4);
for (int localZ = 0; localZ <= 15; ++localZ) {
for (int localX = 0; localX <= 15; ++localX) {
final BlockState blockState = section.getBlockState(localX, localY, localZ);
if (blockState.getLuminance() <= 0) {
continue;
}

sources.add(new BlockPos(offX + localX, realY, offZ + localZ));
}
final PalettedContainer<BlockState> section = sections[sectionY].container;
final int offY = sectionY << 4;

for (int index = 0; index < (16 * 16 * 16); ++index) {
final BlockState state = section.get(index);
if (state.getLuminance() <= 0) {
continue;
}

// index = x | (z << 4) | (y << 8)
sources.add(new BlockPos(offX | (index & 15), offY | (index >>> 8), offZ | ((index >>> 4) & 15)));
}
}

Expand All @@ -132,10 +135,11 @@ public void lightChunk(final ChunkProvider lightAccess, final Chunk chunk, final
continue;
}

this.increaseQueue[this.increaseQueueInitialLength++] = (pos.getX() + (pos.getZ() << 6) + (pos.getY() << (6 + 6)) + this.coordinateOffset) |
(emittedLight) << (6 + 6 + 9) |
((AxisDirection.POSITIVE_X.ordinal() | 8) << (6 + 6 + 9 + 4)) |
(((LightAccessBlockState)blockState).isConditionallyFullOpaque() ? FLAG_HAS_SIDED_TRANSPARENT_BLOCKS : 0);
this.increaseQueue[this.increaseQueueInitialLength++] =
((pos.getX() + (pos.getZ() << 6) + (pos.getY() << (6 + 6)) + this.coordinateOffset) & ((1L << (6 + 6 + 16)) - 1))
| (emittedLight & 0xFL) << (6 + 6 + 16)
| (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4))
| (((ExtendedAbstractBlockState)blockState).isConditionallyFullOpaque() ? FLAG_HAS_SIDED_TRANSPARENT_BLOCKS : 0);


// propagation wont set this for us
Expand All @@ -147,7 +151,7 @@ public void lightChunk(final ChunkProvider lightAccess, final Chunk chunk, final
this.performLightIncrease(lightAccess);

// verify neighbour edges
this.checkChunkEdges(lightAccess, chunk);
this.checkChunkEdges(lightAccess, chunk, -1, 16);
} else {
this.propagateNeighbourLevels(lightAccess, chunk, -1, 16);

Expand Down
Loading

0 comments on commit 02e6333

Please sign in to comment.