diff --git a/gradle.properties b/gradle.properties index 193c3537..00ba7f41 100644 --- a/gradle.properties +++ b/gradle.properties @@ -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 diff --git a/src/main/java/ca/spottedleaf/starlight/common/chunk/ExtendedChunkSection.java b/src/main/java/ca/spottedleaf/starlight/common/chunk/ExtendedChunkSection.java index b1bb96ad..7fc98bef 100644 --- a/src/main/java/ca/spottedleaf/starlight/common/chunk/ExtendedChunkSection.java +++ b/src/main/java/ca/spottedleaf/starlight/common/chunk/ExtendedChunkSection.java @@ -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(); diff --git a/src/main/java/ca/spottedleaf/starlight/common/light/BlockStarLightEngine.java b/src/main/java/ca/spottedleaf/starlight/common/light/BlockStarLightEngine.java index 72b7fbd3..54f7bc26 100644 --- a/src/main/java/ca/spottedleaf/starlight/common/light/BlockStarLightEngine.java +++ b/src/main/java/ca/spottedleaf/starlight/common/light/BlockStarLightEngine.java @@ -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; @@ -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 positions) { for (final BlockPos pos : positions) { diff --git a/src/main/java/ca/spottedleaf/starlight/common/light/SWMRNibbleArray.java b/src/main/java/ca/spottedleaf/starlight/common/light/SWMRNibbleArray.java index 653dbddf..21fa93a4 100644 --- a/src/main/java/ca/spottedleaf/starlight/common/light/SWMRNibbleArray.java +++ b/src/main/java/ca/spottedleaf/starlight/common/light/SWMRNibbleArray.java @@ -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]; diff --git a/src/main/java/ca/spottedleaf/starlight/common/light/SkyStarLightEngine.java b/src/main/java/ca/spottedleaf/starlight/common/light/SkyStarLightEngine.java index 4a269a3c..b4d4c09d 100644 --- a/src/main/java/ca/spottedleaf/starlight/common/light/SkyStarLightEngine.java +++ b/src/main/java/ca/spottedleaf/starlight/common/light/SkyStarLightEngine.java @@ -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; @@ -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); } @@ -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 positions) { this.rewriteNibbleCacheForSkylight(atChunk); @@ -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; } @@ -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); diff --git a/src/main/java/ca/spottedleaf/starlight/common/light/StarLightEngine.java b/src/main/java/ca/spottedleaf/starlight/common/light/StarLightEngine.java index 3549526a..0df5eb1d 100644 --- a/src/main/java/ca/spottedleaf/starlight/common/light/StarLightEngine.java +++ b/src/main/java/ca/spottedleaf/starlight/common/light/StarLightEngine.java @@ -1,6 +1,7 @@ package ca.spottedleaf.starlight.common.light; import ca.spottedleaf.starlight.common.blockstate.ExtendedAbstractBlockState; +import ca.spottedleaf.starlight.common.chunk.ExtendedChunkSection; import ca.spottedleaf.starlight.common.util.CoordinateUtils; import ca.spottedleaf.starlight.common.util.IntegerUtil; import ca.spottedleaf.starlight.common.util.WorldUtil; @@ -393,6 +394,28 @@ protected final int getCustomLightLevel(final VariableBlockLightHandler customBl return ret == -1 ? dfl : ret; } + protected final long getKnownTransparency(final int worldX, final int worldY, final int worldZ) { + final ChunkSection section = this.sectionCache[(worldX >> 4) + 5 * (worldZ >> 4) + (5 * 5) * (worldY >> 4) + this.chunkSectionIndexOffset]; + + if (section != null) { + return section == EMPTY_CHUNK_SECTION ? ExtendedChunkSection.BLOCK_IS_TRANSPARENT : + ((ExtendedChunkSection)section).getKnownTransparency((worldY & 15) | ((worldX & 15) << 4) | ((worldZ & 15) << 8)); + } + + return ExtendedChunkSection.BLOCK_IS_TRANSPARENT; + } + + // warn: localIndex = y | (x << 4) | (z << 8) + protected final long getKnownTransparency(final int sectionIndex, final int localIndex) { + final ChunkSection section = this.sectionCache[sectionIndex]; + + if (section != null) { + return section == EMPTY_CHUNK_SECTION ? ExtendedChunkSection.BLOCK_IS_TRANSPARENT : ((ExtendedChunkSection)section).getKnownTransparency(localIndex); + } + + return ExtendedChunkSection.BLOCK_IS_TRANSPARENT; + } + /** * @deprecated To be removed in 1.17 due to variable section count */ @@ -427,7 +450,7 @@ private static SWMRNibbleArray[] getFilledEmptyLight(final int totalLightSection public final void blocksChangedInChunk(final ChunkProvider lightAccess, final int chunkX, final int chunkZ, final Set positions, final Boolean[] changedSections) { - this.setupCaches(lightAccess, chunkX * 16 + 7, 128, chunkZ * 16 + 7, this.isClientSide, true); + this.setupCaches(lightAccess, chunkX * 16 + 7, 128, chunkZ * 16 + 7, true, true); try { final Chunk chunk = this.getChunkInCache(chunkX, chunkZ); if (this.isClientSide && chunk == null) { @@ -454,6 +477,15 @@ public final void blocksChangedInChunk(final ChunkProvider lightAccess, final in protected abstract void checkBlock(final ChunkProvider lightAccess, final int worldX, final int worldY, final int worldZ); + // if ret > expect, then the real value is at least ret (early returns if ret > expect, rather than calculating actual) + // if ret == expect, then expect is the correct light value for pos + // if ret < expect, then ret is the real light value + protected abstract int calculateLightValue(final ChunkProvider lightAccess, final int worldX, final int worldY, final int worldZ, + final int expect, final VariableBlockLightHandler customBlockLight); + + protected final int[] chunkCheckDelayedUpdatesCenter = new int[16 * 16]; + protected final int[] chunkCheckDelayedUpdatesNeighbour = new int[16 * 16]; + protected void checkChunkEdge(final ChunkProvider lightAccess, final Chunk chunk, final int chunkX, final int chunkY, final int chunkZ) { final SWMRNibbleArray currNibble = this.getNibbleFromCache(chunkX, chunkY, chunkZ); @@ -477,6 +509,7 @@ protected void checkChunkEdge(final ChunkProvider lightAccess, final Chunk chunk continue; } + // this chunk final int incX; final int incZ; final int startX; @@ -508,41 +541,58 @@ protected void checkChunkEdge(final ChunkProvider lightAccess, final Chunk chunk startX = chunkX << 4; } + final VariableBlockLightHandler customLightHandler = ((ExtendedWorld)lightAccess.getWorld()).getCustomLightHandler(); + int centerDelayedChecks = 0; + int neighbourDelayedChecks = 0; for (int currY = chunkY << 4, maxY = currY | 15; currY <= maxY; ++currY) { for (int i = 0, currX = startX, currZ = startZ; i < 16; ++i, currX += incX, currZ += incZ) { final int neighbourX = currX + neighbourOffX; final int neighbourZ = currZ + neighbourOffZ; - final int currentLevel = currNibble.getUpdating((currX & 15) | + final int currentIndex = (currX & 15) | ((currZ & 15)) << 4 | - ((currY & 15) << 8) - ); - final int neighbourLevel = neighbourNibble.getUpdating((neighbourX & 15) | + ((currY & 15) << 8); + final int currentLevel = currNibble.getUpdating(currentIndex); + + final int neighbourIndex = + (neighbourX & 15) | ((neighbourZ & 15)) << 4 | - ((currY & 15) << 8) - ); + ((currY & 15) << 8); + final int neighbourLevel = neighbourNibble.getUpdating(neighbourIndex); - if (currentLevel == neighbourLevel && (currentLevel == 0 || currentLevel == 15)) { - // nothing to check here - continue; - } + // the checks are delayed because the checkBlock method clobbers light values - which then + // affect later calculate light value operations. While they don't affect it in a behaviourly significant + // way, they do have a negative performance impact due to simply queueing more values - if (Math.abs(currentLevel - neighbourLevel) == 1) { - final BlockState currentBlock = this.getBlockState(currX, currY, currZ); - final BlockState neighbourBlock = this.getBlockState(neighbourX, currY, neighbourZ); + if (this.calculateLightValue(lightAccess, currX, currY, currZ, currentLevel, customLightHandler) != currentLevel) { + this.chunkCheckDelayedUpdatesCenter[centerDelayedChecks++] = currentIndex; + } - final int currentOpacity = ((ExtendedAbstractBlockState)currentBlock).getOpacityIfCached(); - final int neighbourOpacity = ((ExtendedAbstractBlockState)neighbourBlock).getOpacityIfCached(); - if (currentOpacity == 0 || currentOpacity == 1 || - neighbourOpacity == 0 || neighbourOpacity == 1) { - // looks good - continue; - } + if (this.calculateLightValue(lightAccess, neighbourX, currY, neighbourZ, neighbourLevel, customLightHandler) != neighbourLevel) { + this.chunkCheckDelayedUpdatesNeighbour[neighbourDelayedChecks++] = neighbourIndex; } + } + } - // setup queue, it looks like something could be inconsistent - this.checkBlock(lightAccess, currX, currY, currZ); - this.checkBlock(lightAccess, neighbourX, currY, neighbourZ); + final int currentChunkOffX = chunkX << 4; + final int currentChunkOffZ = chunkZ << 4; + final int neighbourChunkOffX = (chunkX + direction.x) << 4; + final int neighbourChunkOffZ = (chunkZ + direction.z) << 4; + final int chunkOffY = chunkY << 4; + for (int i = 0, len = Math.max(centerDelayedChecks, neighbourDelayedChecks); i < len; ++i) { + // try to queue neighbouring data together + // index = x | (z << 4) | (y << 8) + if (i < centerDelayedChecks) { + final int value = this.chunkCheckDelayedUpdatesCenter[i]; + this.checkBlock(lightAccess, currentChunkOffX | (value & 15), + chunkOffY | (value >>> 8), + currentChunkOffZ | ((value >>> 4) & 0xF)); + } + if (i < neighbourDelayedChecks) { + final int value = this.chunkCheckDelayedUpdatesNeighbour[i]; + this.checkBlock(lightAccess, neighbourChunkOffX | (value & 15), + chunkOffY | (value >>> 8), + neighbourChunkOffZ | ((value >>> 4) & 0xF)); } } } @@ -600,6 +650,7 @@ protected final void propagateNeighbourLevels(final ChunkProvider lightAccess, f continue; } + // neighbour chunk final int incX; final int incZ; final int startX; @@ -674,22 +725,43 @@ public static Boolean[] getEmptySectionsForChunk(final Chunk chunk) { return ret; } - public final void handleEmptySectionChanges(final ChunkProvider lightAccess, final int chunkX, final int chunkZ, - final Boolean[] emptinessChanges) { - this.setupCaches(lightAccess, chunkX * 16 + 7, 128, chunkZ * 16 + 7, this.isClientSide, true); - if (this.isClientSide) { + public final void forceHandleEmptySectionChanges(final ChunkProvider lightAccess, final Chunk chunk, final Boolean[] emptinessChanges) { + final int chunkX = chunk.getPos().x; + final int chunkZ = chunk.getPos().z; + this.setupCaches(lightAccess, chunkX * 16 + 7, 128, chunkZ * 16 + 7, true, true); + try { // force current chunk into cache - final Chunk chunk = (Chunk)lightAccess.getChunk(chunkX, chunkZ); - if (chunk == null) { - // unloaded this frame (or last), and we were still queued - return; - } this.setChunkInCache(chunkX, chunkZ, chunk); this.setBlocksForChunkInCache(chunkX, chunkZ, chunk.getSectionArray()); this.setNibblesForChunkInCache(chunkX, chunkZ, this.getNibblesOnChunk(chunk)); this.setEmptinessMapCache(chunkX, chunkZ, this.getEmptinessMap(chunk)); + + final boolean[] ret = this.handleEmptySectionChanges(lightAccess, chunk, emptinessChanges, false); + if (ret != null) { + this.setEmptinessMap(chunk, ret); + } + this.updateVisible(lightAccess); + } finally { + this.destroyCaches(); } + } + + public final void handleEmptySectionChanges(final ChunkProvider lightAccess, final int chunkX, final int chunkZ, + final Boolean[] emptinessChanges) { + this.setupCaches(lightAccess, chunkX * 16 + 7, 128, chunkZ * 16 + 7, true, true); try { + if (this.isClientSide) { + // force current chunk into cache + final Chunk chunk = (Chunk)lightAccess.getChunk(chunkX, chunkZ); + if (chunk == null) { + // unloaded this frame (or last), and we were still queued + return; + } + this.setChunkInCache(chunkX, chunkZ, chunk); + this.setBlocksForChunkInCache(chunkX, chunkZ, chunk.getSectionArray()); + this.setNibblesForChunkInCache(chunkX, chunkZ, this.getNibblesOnChunk(chunk)); + this.setEmptinessMapCache(chunkX, chunkZ, this.getEmptinessMap(chunk)); + } final Chunk chunk = this.getChunkInCache(chunkX, chunkZ); if (chunk == null) { return; @@ -719,7 +791,7 @@ public final void checkChunkEdges(final ChunkProvider lightAccess, final int chu if (chunk == null) { return; } - this.checkChunkEdges(lightAccess, chunk, -1, 16); + this.checkChunkEdges(lightAccess, chunk, this.minLightSection, this.maxLightSection); this.updateVisible(lightAccess); } finally { this.destroyCaches(); @@ -748,21 +820,25 @@ public final void checkChunkEdges(final ChunkProvider lightAccess, final int chu // does not need to detect empty chunks itself (and it should do no handling for them either!) protected abstract void lightChunk(final ChunkProvider lightAccess, final Chunk chunk, final boolean needsEdgeChecks); - public final void light(final ChunkProvider lightAccess, final int chunkX, final int chunkZ, final Boolean[] emptySections) { - this.setupCaches(lightAccess, chunkX * 16 + 7, 128, chunkZ * 16 + 7, false, true); - // force current chunk into cache - final Chunk chunk = (Chunk)lightAccess.getChunk(chunkX, chunkZ); - this.setChunkInCache(chunkX, chunkZ, chunk); - this.setBlocksForChunkInCache(chunkX, chunkZ, chunk.getSectionArray()); - this.setNibblesForChunkInCache(chunkX, chunkZ, this.getNibblesOnChunk(chunk)); - this.setEmptinessMapCache(chunkX, chunkZ, this.getEmptinessMap(chunk)); + public final void light(final ChunkProvider lightAccess, final Chunk chunk, final Boolean[] emptySections) { + final int chunkX = chunk.getPos().x; + final int chunkZ = chunk.getPos().z; + this.setupCaches(lightAccess, chunkX * 16 + 7, 128, chunkZ * 16 + 7, true, true); try { + final SWMRNibbleArray[] nibbles = getFilledEmptyLight(this.maxLightSection - this.minLightSection + 1); + // force current chunk into cache + this.setChunkInCache(chunkX, chunkZ, chunk); + this.setBlocksForChunkInCache(chunkX, chunkZ, chunk.getSectionArray()); + this.setNibblesForChunkInCache(chunkX, chunkZ, nibbles); + this.setEmptinessMapCache(chunkX, chunkZ, this.getEmptinessMap(chunk)); + final boolean[] ret = this.handleEmptySectionChanges(lightAccess, chunk, emptySections, true); if (ret != null) { this.setEmptinessMap(chunk, ret); } - this.lightChunk(lightAccess, chunk, false); + this.lightChunk(lightAccess, chunk, true); // TODO + this.setNibbles(chunk, nibbles); this.updateVisible(lightAccess); } finally { this.destroyCaches(); diff --git a/src/main/java/ca/spottedleaf/starlight/common/light/StarLightInterface.java b/src/main/java/ca/spottedleaf/starlight/common/light/StarLightInterface.java index 0f4b86d2..1871c533 100644 --- a/src/main/java/ca/spottedleaf/starlight/common/light/StarLightInterface.java +++ b/src/main/java/ca/spottedleaf/starlight/common/light/StarLightInterface.java @@ -27,7 +27,7 @@ public final class StarLightInterface { - public static final ChunkTicketType CHUNK_WORK_TICKET = ChunkTicketType.create("starlight_chunk_work_ticket", Long::compareTo); + public static final ChunkTicketType CHUNK_WORK_TICKET = ChunkTicketType.create("starlight_chunk_work_ticket", (p1, p2) -> Long.compare(p1.toLong(), p2.toLong())); /** * Can be {@code null}, indicating the light is all empty. @@ -322,6 +322,23 @@ public void sectionChange(final ChunkSectionPos pos, final boolean newEmptyValue } } + public void forceLoadInChunk(final Chunk chunk, final Boolean[] emptySections) { + final SkyStarLightEngine skyEngine = this.getSkyLightEngine(); + final BlockStarLightEngine blockEngine = this.getBlockLightEngine(); + + try { + if (skyEngine != null) { + skyEngine.forceHandleEmptySectionChanges(this.lightAccess, chunk, emptySections); + } + if (blockEngine != null) { + blockEngine.forceHandleEmptySectionChanges(this.lightAccess, chunk, emptySections); + } + } finally { + this.releaseSkyLightEngine(skyEngine); + this.releaseBlockLightEngine(blockEngine); + } + } + public void loadInChunk(final int chunkX, final int chunkZ, final Boolean[] emptySections) { final SkyStarLightEngine skyEngine = this.getSkyLightEngine(); final BlockStarLightEngine blockEngine = this.getBlockLightEngine(); @@ -339,16 +356,16 @@ public void loadInChunk(final int chunkX, final int chunkZ, final Boolean[] empt } } - public void lightChunk(final int chunkX, final int chunkZ, final Boolean[] emptySections) { + public void lightChunk(final Chunk chunk, final Boolean[] emptySections) { final SkyStarLightEngine skyEngine = this.getSkyLightEngine(); final BlockStarLightEngine blockEngine = this.getBlockLightEngine(); try { if (skyEngine != null) { - skyEngine.light(this.lightAccess, chunkX, chunkZ, emptySections); + skyEngine.light(this.lightAccess, chunk, emptySections); } if (blockEngine != null) { - blockEngine.light(this.lightAccess, chunkX, chunkZ, emptySections); + blockEngine.light(this.lightAccess, chunk, emptySections); } } finally { this.releaseSkyLightEngine(skyEngine); diff --git a/src/main/java/ca/spottedleaf/starlight/mixin/common/chunk/ChunkSectionMixin.java b/src/main/java/ca/spottedleaf/starlight/mixin/common/chunk/ChunkSectionMixin.java index 4ac02852..508e063e 100644 --- a/src/main/java/ca/spottedleaf/starlight/mixin/common/chunk/ChunkSectionMixin.java +++ b/src/main/java/ca/spottedleaf/starlight/mixin/common/chunk/ChunkSectionMixin.java @@ -28,7 +28,7 @@ public abstract class ChunkSectionMixin implements ExtendedChunkSection { protected int transparentBlockCount; @Unique - private final long[] knownBlockTransparencies = new long[16 * 16 * 16 / Long.SIZE]; // blocks * bits per block / bits per long + private final long[] knownBlockTransparencies = new long[16 * 16 * 16 * 2 / Long.SIZE]; // blocks * bits per block / bits per long @Unique private static long getKnownTransparency(final BlockState state) { @@ -37,19 +37,22 @@ private static long getKnownTransparency(final BlockState state) { if (opacityIfCached == 0) { return ExtendedChunkSection.BLOCK_IS_TRANSPARENT; } + if (opacityIfCached == 15) { + return ExtendedChunkSection.BLOCK_IS_FULL_OPAQUE; + } - return ExtendedChunkSection.BLOCK_UNKNOWN_TRANSPARENCY; + return opacityIfCached == -1 ? ExtendedChunkSection.BLOCK_SPECIAL_TRANSPARENCY : ExtendedChunkSection.BLOCK_UNKNOWN_TRANSPARENCY; } /* NOTE: Index is y | (x << 4) | (z << 8) */ @Unique private void updateTransparencyInfo(final int blockIndex, final long transparency) { - final int arrayIndex = (blockIndex >>> 6); // blockIndex / 64 - final int valueShift = (blockIndex & (Long.SIZE - 1)); + final int arrayIndex = (blockIndex >>> (6 - 1)); // blockIndex / (64/2) + final int valueShift = (blockIndex & (Long.SIZE / 2 - 1)) << 1; long value = this.knownBlockTransparencies[arrayIndex]; - value &= ~(0b1L << valueShift); + value &= ~(0b11L << valueShift); value |= (transparency << valueShift); this.knownBlockTransparencies[arrayIndex] = value; @@ -127,12 +130,12 @@ public final boolean hasOpaqueBlocks() { @Override public final long getKnownTransparency(final int blockIndex) { // index = y | (x << 4) | (z << 8) - final int arrayIndex = (blockIndex >>> 6); // blockIndex / 64 - final int valueShift = (blockIndex & (Long.SIZE - 1)); + final int arrayIndex = (blockIndex >>> (6 - 1)); // blockIndex / (64/2) + final int valueShift = (blockIndex & (Long.SIZE / 2 - 1)) << 1; final long value = this.knownBlockTransparencies[arrayIndex]; - return (value >>> valueShift) & 0b1L; + return (value >>> valueShift) & 0b11L; } @@ -140,10 +143,10 @@ public final long getKnownTransparency(final int blockIndex) { public final long getBitsetForColumn(final int columnX, final int columnZ) { // index = y | (x << 4) | (z << 8) final int columnIndex = (columnX << 4) | (columnZ << 8); - final long value = this.knownBlockTransparencies[columnIndex >>> 6]; // columnIndex / 64 + final long value = this.knownBlockTransparencies[columnIndex >>> (6 - 1)]; // columnIndex / (64/2) - final int startIndex = (columnIndex & (Long.SIZE - 1)); + final int startIndex = (columnIndex & (Long.SIZE / 2 - 1)) << 1; - return (value >>> startIndex) & ((1L << 16) - 1); + return (value >>> startIndex) & ((1L << (16 * 2)) - 1); } } diff --git a/src/main/java/ca/spottedleaf/starlight/mixin/common/lightengine/ServerLightingProviderMixin.java b/src/main/java/ca/spottedleaf/starlight/mixin/common/lightengine/ServerLightingProviderMixin.java index 14a0eae6..258469ac 100644 --- a/src/main/java/ca/spottedleaf/starlight/mixin/common/lightengine/ServerLightingProviderMixin.java +++ b/src/main/java/ca/spottedleaf/starlight/mixin/common/lightengine/ServerLightingProviderMixin.java @@ -3,6 +3,8 @@ import ca.spottedleaf.starlight.common.light.StarLightEngine; import ca.spottedleaf.starlight.common.light.StarLightInterface; import ca.spottedleaf.starlight.common.light.StarLightLightingProvider; +import ca.spottedleaf.starlight.common.util.CoordinateUtils; +import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; import net.minecraft.server.world.ServerLightingProvider; import net.minecraft.server.world.ServerWorld; import net.minecraft.server.world.ThreadedAnvilChunkStorage; @@ -42,44 +44,47 @@ public ServerLightingProviderMixin(final ChunkProvider chunkProvider, final bool } @Unique - private long workTicketCounts = 0L; + private final Long2IntOpenHashMap chunksBeingWorkedOn = new Long2IntOpenHashMap(); @Unique private void queueTaskForSection(final int chunkX, final int chunkY, final int chunkZ, final Runnable runnable) { - // TODO this impl is actually fucking awful for checking neighbours and keeping neighbours, for the love of god rewrite it - final ServerWorld world = (ServerWorld)this.getLightEngine().getWorld(); + final Chunk center = this.getLightEngine().getAnyChunkNow(chunkX, chunkZ); + if (center == null || !center.getStatus().isAtLeast(ChunkStatus.LIGHT) || !center.isLightOn()) { + // do not accept updates in unlit chunks + return; + } + if (!world.getChunkManager().threadedAnvilChunkStorage.mainThreadExecutor.isOnThread()) { - // this is not safe to run off-main, re-schedule + // ticket logic is not safe to run off-main, re-schedule world.getChunkManager().threadedAnvilChunkStorage.mainThreadExecutor.execute(() -> { this.queueTaskForSection(chunkX, chunkY, chunkZ, runnable); }); return; } - Chunk center = this.getLightEngine().getAnyChunkNow(chunkX, chunkZ); - if (center == null || !center.getStatus().isAtLeast(ChunkStatus.LIGHT) || !center.isLightOn()) { - // do not accept updates in unlit chunks - return; - } + final long key = CoordinateUtils.getChunkKey(chunkX, chunkZ); - final Long ticketId = Long.valueOf(this.workTicketCounts++); - - // ensure 1 radius features is loaded (yes they can UNLOAD) - for (int dz = -1; dz <= 1; ++dz) { - for (int dx = -1; dx <= 1; ++dx) { - world.getChunk(dx + chunkX, dz + chunkZ, (dx | dz) == 0 ? ChunkStatus.LIGHT : ChunkStatus.FEATURES, true); - } + final int references = this.chunksBeingWorkedOn.get(key); + this.chunksBeingWorkedOn.put(key, references + 1); + if (references == 0) { + final ChunkPos pos = new ChunkPos(chunkX, chunkZ); + world.getChunkManager().addTicket(StarLightInterface.CHUNK_WORK_TICKET, pos, 0, pos); } - world.getChunkManager().addTicket(StarLightInterface.CHUNK_WORK_TICKET, new ChunkPos(chunkX, chunkZ), 0, ticketId); - this.enqueue(chunkX, chunkZ, ServerLightingProvider.Stage.PRE_UPDATE, () -> { runnable.run(); this.enqueue(chunkX, chunkZ, ServerLightingProvider.Stage.POST_UPDATE, () -> { world.getChunkManager().threadedAnvilChunkStorage.mainThreadExecutor.execute(() -> { - world.getChunkManager().removeTicket(StarLightInterface.CHUNK_WORK_TICKET, new ChunkPos(chunkX, chunkZ), 0, ticketId); + final int newReferences = this.chunksBeingWorkedOn.get(key); + if (newReferences == 1) { + this.chunksBeingWorkedOn.remove(key); + final ChunkPos pos = new ChunkPos(chunkX, chunkZ); + world.getChunkManager().removeTicket(StarLightInterface.CHUNK_WORK_TICKET, pos, 0, pos); + } else { + this.chunksBeingWorkedOn.put(key, newReferences - 1); + } }); }); }); @@ -158,10 +163,15 @@ public CompletableFuture light(final Chunk chunk, final boolean lit) { return CompletableFuture.supplyAsync(() -> { final Boolean[] emptySections = StarLightEngine.getEmptySectionsForChunk(chunk); if (!lit) { - this.getLightEngine().lightChunk(chunkPos.x, chunkPos.z, emptySections); + chunk.setLightOn(false); + this.getLightEngine().lightChunk(chunk, emptySections); chunk.setLightOn(true); } else { - this.getLightEngine().loadInChunk(chunkPos.x, chunkPos.z, emptySections); + this.getLightEngine().forceLoadInChunk(chunk, emptySections); + // can't really force the chunk to be edged checked, as we need neighbouring chunks - but we don't have + // them, so if it's not loaded then i guess we can't do edge checks. later loads of the chunk should + // catch what we miss here. + this.getLightEngine().checkChunkEdges(chunkPos.x, chunkPos.z); } this.chunkStorage.releaseLightTicket(chunkPos); diff --git a/src/main/java/ca/spottedleaf/starlight/mixin/common/world/ChunkSerializerMixin.java b/src/main/java/ca/spottedleaf/starlight/mixin/common/world/ChunkSerializerMixin.java index 1208ae86..4475055d 100644 --- a/src/main/java/ca/spottedleaf/starlight/mixin/common/world/ChunkSerializerMixin.java +++ b/src/main/java/ca/spottedleaf/starlight/mixin/common/world/ChunkSerializerMixin.java @@ -23,7 +23,7 @@ @Mixin(ChunkSerializer.class) public abstract class ChunkSerializerMixin { - private static final int STARLIGHT_LIGHT_VERSION = 3; + private static final int STARLIGHT_LIGHT_VERSION = 4; private static final String UNINITIALISED_SKYLIGHT_TAG = "starlight.skylight_uninit"; private static final String STARLIGHT_VERSION_TAG = "starlight.light_version";