diff --git a/build.gradle b/build.gradle index 783319fa..a0890fa9 100644 --- a/build.gradle +++ b/build.gradle @@ -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. diff --git a/gradle.properties b/gradle.properties index 0d866eb6..0b964a7c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -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 diff --git a/src/main/java/ca/spottedleaf/starlight/common/blockstate/LightAccessBlockState.java b/src/main/java/ca/spottedleaf/starlight/common/blockstate/ExtendedAbstractBlockState.java similarity index 75% rename from src/main/java/ca/spottedleaf/starlight/common/blockstate/LightAccessBlockState.java rename to src/main/java/ca/spottedleaf/starlight/common/blockstate/ExtendedAbstractBlockState.java index eea6b933..a681b524 100644 --- a/src/main/java/ca/spottedleaf/starlight/common/blockstate/LightAccessBlockState.java +++ b/src/main/java/ca/spottedleaf/starlight/common/blockstate/ExtendedAbstractBlockState.java @@ -1,6 +1,6 @@ package ca.spottedleaf.starlight.common.blockstate; -public interface LightAccessBlockState { +public interface ExtendedAbstractBlockState { public boolean isConditionallyFullOpaque(); diff --git a/src/main/java/ca/spottedleaf/starlight/common/chunk/ExtendedChunk.java b/src/main/java/ca/spottedleaf/starlight/common/chunk/ExtendedChunk.java new file mode 100644 index 00000000..e7e8fec1 --- /dev/null +++ b/src/main/java/ca/spottedleaf/starlight/common/chunk/ExtendedChunk.java @@ -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(); +} diff --git a/src/main/java/ca/spottedleaf/starlight/common/chunk/ExtendedChunkSection.java b/src/main/java/ca/spottedleaf/starlight/common/chunk/ExtendedChunkSection.java new file mode 100644 index 00000000..495d5d30 --- /dev/null +++ b/src/main/java/ca/spottedleaf/starlight/common/chunk/ExtendedChunkSection.java @@ -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); +} diff --git a/src/main/java/ca/spottedleaf/starlight/common/chunk/NibbledChunk.java b/src/main/java/ca/spottedleaf/starlight/common/chunk/NibbledChunk.java deleted file mode 100644 index 6a2b6732..00000000 --- a/src/main/java/ca/spottedleaf/starlight/common/chunk/NibbledChunk.java +++ /dev/null @@ -1,12 +0,0 @@ -package ca.spottedleaf.starlight.common.chunk; - -import ca.spottedleaf.starlight.common.light.SWMRNibbleArray; - -public interface NibbledChunk { - - public SWMRNibbleArray[] getBlockNibbles(); - public void setBlockNibbles(SWMRNibbleArray[] nibbles); - - public SWMRNibbleArray[] getSkyNibbles(); - public void setSkyNibbles(SWMRNibbleArray[] nibbles); -} diff --git a/src/main/java/ca/spottedleaf/starlight/common/chunk/ThreadedAnvilChunkStorageMethods.java b/src/main/java/ca/spottedleaf/starlight/common/chunk/ThreadedAnvilChunkStorageMethods.java deleted file mode 100644 index bab11c5d..00000000 --- a/src/main/java/ca/spottedleaf/starlight/common/chunk/ThreadedAnvilChunkStorageMethods.java +++ /dev/null @@ -1,16 +0,0 @@ -package ca.spottedleaf.starlight.common.chunk; - -import net.minecraft.server.world.ChunkHolder; -import net.minecraft.util.math.ChunkPos; -import java.util.function.IntSupplier; - -public interface ThreadedAnvilChunkStorageMethods { - - public IntSupplier getCompletedLevelSupplierPublic(long pos); - - public void releaseLightTicketPublic(ChunkPos pos); - - public void scheduleOntoMain(Runnable runnable); - - public ChunkHolder getOffMainChunkHolder(long pos); -} 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 8d21e567..e72ccef9 100644 --- a/src/main/java/ca/spottedleaf/starlight/common/light/BlockStarLightEngine.java +++ b/src/main/java/ca/spottedleaf/starlight/common/light/BlockStarLightEngine.java @@ -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; @@ -23,12 +24,12 @@ 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 @@ -36,6 +37,10 @@ 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 @@ -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 positions) { + protected void propagateBlockChanges(final ChunkProvider lightAccess, final Chunk atChunk, final Set positions) { for (final BlockPos pos : positions) { this.checkBlock(pos.getX(), pos.getY(), pos.getZ()); } @@ -95,20 +101,17 @@ protected Iterator 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 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))); } } @@ -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 @@ -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); 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 91bf4f07..653dbddf 100644 --- a/src/main/java/ca/spottedleaf/starlight/common/light/SWMRNibbleArray.java +++ b/src/main/java/ca/spottedleaf/starlight/common/light/SWMRNibbleArray.java @@ -7,6 +7,20 @@ // SWMR -> Single Writer Multi Reader Nibble Array public final class SWMRNibbleArray { + /* + * Null nibble - nibble does not exist, and should not be written to. Just like vanilla - null + * nibbles are always 0 - and they are never written to directly. Only initialised/uninitialised + * nibbles can be written to. + * + * Uninitialised nibble - They are all 0, but the backing array isn't initialised. + * + * Initialised nibble - Has light data. + */ + + protected static final int INIT_STATE_NULL = 0; // null + protected static final int INIT_STATE_UNINIT = 1; // uninitialised + protected static final int INIT_STATE_INIT = 2; // initialised + public static final int ARRAY_SIZE = 16 * 16 * 16 / (8/4); // blocks / bytes per block protected static final byte[] FULL_LIT = new byte[ARRAY_SIZE]; static { @@ -28,132 +42,234 @@ private static void freeBytes(final byte[] bytes) { WORKING_BYTES_POOL.get().addFirst(bytes); } - protected byte[] workingBytes; - protected byte[] visibleBytes; - protected final int defaultNullValue; - private boolean isNullNibble; + protected int stateUpdating; + protected volatile int stateVisible; - public SWMRNibbleArray(final boolean isNullNibble, final int defaultNullValue) { - this(null, defaultNullValue); - this.isNullNibble = isNullNibble; - } + protected byte[] storageUpdating; + protected boolean updatingDirty; // only returns whether storageUpdating is dirty + protected byte[] storageVisible; public SWMRNibbleArray() { - this(null, 0); // lazy init + this(null, false); // lazy init } public SWMRNibbleArray(final byte[] bytes) { - this(bytes, 0); + this(bytes, false); } - protected SWMRNibbleArray(final byte[] bytes, final int defaultNullValue) { + public SWMRNibbleArray(final byte[] bytes, final boolean isNullNibble) { if (bytes != null && bytes.length != ARRAY_SIZE) { throw new IllegalArgumentException(); } - this.defaultNullValue = defaultNullValue; - this.visibleBytes = bytes != null ? bytes.clone() : null; + this.stateVisible = this.stateUpdating = bytes == null ? (isNullNibble ? INIT_STATE_NULL : INIT_STATE_UNINIT) : INIT_STATE_INIT; + this.storageUpdating = this.storageVisible = bytes; } - public boolean isDirty() { - return this.workingBytes != null; - } + // operation type: visible + public boolean isAllZero() { + final byte[] bytes = this.storageVisible; - public boolean isNullNibbleUpdating() { - return this.workingBytes == null && this.isNullNibble; - } + if (this.storageVisible == null) { + return true; + } - public boolean isNullNibbleVisible() { synchronized (this) { - return this.isNullNibble; + for (int i = 0; i < (ARRAY_SIZE >>> 4); ++i) { + byte whole = bytes[i << 4]; + + for (int k = 1; k < (1 << 4); ++k) { + whole |= bytes[(i << 4) | k]; + } + + if (whole != 0) { + return false; + } + } } + + return true; } - public void markNonNull() { - synchronized (this) { - this.isNullNibble = false; + // operation type: updating on src, updating on other + public void extrudeLower(final SWMRNibbleArray other) { + if (other.stateUpdating == INIT_STATE_NULL) { + throw new IllegalArgumentException(); + } + + if (other.storageUpdating == null) { + this.setUninitialised(); + return; + } + + final byte[] src = other.storageUpdating; + final byte[] into; + + if (this.storageUpdating != null) { + into = this.storageUpdating; + } else { + this.storageUpdating = into = allocateBytes(); + this.stateUpdating = INIT_STATE_INIT; + } + this.updatingDirty = true; + + final int start = 0; + final int end = (15 | (15 << 4)) >>> 1; + + /* x | (z << 4) | (y << 8) */ + for (int y = 0; y <= 15; ++y) { + System.arraycopy(src, start, into, y << (8 - 1), end - start + 1); } } - public boolean isInitialisedUpdating() { - return this.workingBytes != null || this.visibleBytes != null; + // operation type: updating + public void setFull() { + this.stateUpdating = INIT_STATE_INIT; + Arrays.fill(this.storageUpdating == null || !this.updatingDirty ? this.storageUpdating = allocateBytes() : this.storageUpdating, (byte)-1); + this.updatingDirty = true; } - public boolean isInitialisedVisible() { - synchronized (this) { - return this.visibleBytes != null; - } + // operation type: updating + public void setZero() { + this.stateUpdating = INIT_STATE_INIT; + Arrays.fill(this.storageUpdating == null || !this.updatingDirty ? this.storageUpdating = allocateBytes() : this.storageUpdating, (byte)0); + this.updatingDirty = true; } - public void initialiseWorking() { - if (this.workingBytes != null) { + // operation type: updating + public void setNonNull() { + if (this.stateUpdating != INIT_STATE_NULL) { return; } - final byte[] working = allocateBytes(); - this.copyIntoImpl(working, 0); - this.workingBytes = working; + this.stateUpdating = INIT_STATE_UNINIT; } - public void copyFrom(final byte[] src, final int off) { - if (this.workingBytes == null) { - this.workingBytes = allocateBytes(); + // operation type: updating + public void setNull() { + this.stateUpdating = INIT_STATE_NULL; + if (this.updatingDirty && this.storageUpdating != null) { + freeBytes(this.storageUpdating); } - System.arraycopy(src, off, this.workingBytes, 0, ARRAY_SIZE); + this.storageUpdating = null; + this.updatingDirty = false; } - public boolean updateVisible() { - if (this.workingBytes == null) { - return false; - + // operation type: updating + public void setUninitialised() { + this.stateUpdating = INIT_STATE_UNINIT; + if (this.storageUpdating != null && this.updatingDirty) { + freeBytes(this.storageUpdating); } - final byte[] oldVisible = this.visibleBytes; + this.storageUpdating = null; + this.updatingDirty = false; + } - synchronized (this) { - this.isNullNibble = false; + // operation type: updating + public boolean isDirty() { + return this.stateUpdating != this.stateVisible || this.updatingDirty; + } + + // operation type: updating + public boolean isNullNibbleUpdating() { + return this.stateUpdating == INIT_STATE_NULL; + } + + // operation type: visible + public boolean isNullNibbleVisible() { + return this.stateVisible == INIT_STATE_NULL; + } + + // opeartion type: updating + public boolean isUninitialisedUpdating() { + return this.stateUpdating == INIT_STATE_UNINIT; + } + + // operation type: visible + public boolean isUninitialisedVisible() { + return this.stateVisible == INIT_STATE_UNINIT; + } + + // operation type: updating + public boolean isInitialisedUpdating() { + return this.stateUpdating == INIT_STATE_INIT; + } - this.visibleBytes = this.workingBytes; - this.workingBytes = null; + // operation type: visible + public boolean isInitialisedVisible() { + return this.stateVisible == INIT_STATE_INIT; + } + + // operation type: updating + protected void swapUpdatingAndMarkDirty() { + if (this.updatingDirty) { + return; } - if (oldVisible != null) { - freeBytes(oldVisible); + if (this.storageUpdating == null) { + this.storageUpdating = allocateBytes(); + Arrays.fill(this.storageUpdating, (byte)0); + } else { + System.arraycopy(this.storageUpdating, 0, this.storageUpdating = allocateBytes(), 0, ARRAY_SIZE); } - return true; + this.stateUpdating = INIT_STATE_INIT; + this.updatingDirty = true; } - public void copyInto(final byte[] bytes, final int off) { - synchronized (this) { - this.copyIntoImpl(bytes, off); + // operation type: updating + public boolean updateVisible() { + if (!this.isDirty()) { + return false; } - } - protected void copyIntoImpl(final byte[] bytes, final int off) { - if (this.visibleBytes != null) { - System.arraycopy(this.visibleBytes, 0, bytes, off, ARRAY_SIZE); - } else { - if (this.isNullNibble && this.defaultNullValue != 0) { - Arrays.fill(bytes, off, off + ARRAY_SIZE, (byte)(this.defaultNullValue | (this.defaultNullValue << 4))); + synchronized (this) { + if (this.stateUpdating == INIT_STATE_NULL || this.stateUpdating == INIT_STATE_UNINIT) { + this.storageVisible = null; } else { - Arrays.fill(bytes, off, off + ARRAY_SIZE, (byte)0); + if (this.storageVisible == null) { + this.storageVisible = this.storageUpdating.clone(); + } else { + System.arraycopy(this.storageUpdating, 0, this.storageVisible, 0, ARRAY_SIZE); + } + + freeBytes(this.storageUpdating); + this.storageUpdating = this.storageVisible; } + this.updatingDirty = false; + this.stateVisible = this.stateUpdating; } + + return true; } - public ChunkNibbleArray asNibble() { + // operation type: visible + public ChunkNibbleArray toVanillaNibble() { synchronized (this) { - return this.visibleBytes == null ? (this.isNullNibble ? null : new ChunkNibbleArray()) : new ChunkNibbleArray(this.visibleBytes.clone()); + switch (this.stateVisible) { + case INIT_STATE_NULL: + return null; + case INIT_STATE_UNINIT: + return new ChunkNibbleArray(); + case INIT_STATE_INIT: + return new ChunkNibbleArray(this.storageVisible.clone()); + default: + throw new IllegalStateException(); + } } } + /* x | (z << 4) | (y << 8) */ + + // operation type: updating public int getUpdating(final int x, final int y, final int z) { return this.getUpdating((x & 15) | ((z & 15) << 4) | ((y & 15) << 8)); } + // operation type: updating public int getUpdating(final int index) { // indices range from 0 -> 4096 - byte[] bytes = this.workingBytes == null ? this.visibleBytes : this.workingBytes; + final byte[] bytes = this.storageUpdating; if (bytes == null) { - return this.isNullNibble ? this.defaultNullValue : 0; + return 0; } final byte value = bytes[index >>> 1]; @@ -162,17 +278,20 @@ public int getUpdating(final int index) { return ((value >>> ((index & 1) << 2)) & 0xF); } + // operation type: visible public int getVisible(final int x, final int y, final int z) { return this.getVisible((x & 15) | ((z & 15) << 4) | ((y & 15) << 8)); } + // operation type: visible public int getVisible(final int index) { synchronized (this) { // indices range from 0 -> 4096 - if (this.visibleBytes == null) { - return this.isNullNibble ? this.defaultNullValue : 0; + final byte[] visibleBytes = this.storageVisible; + if (visibleBytes == null) { + return 0; } - final byte value = this.visibleBytes[index >>> 1]; + final byte value = visibleBytes[index >>> 1]; // if we are an even index, we want lower 4 bits // if we are an odd index, we want upper 4 bits @@ -180,17 +299,19 @@ public int getVisible(final int index) { } } + // operation type: updating public void set(final int x, final int y, final int z, final int value) { this.set((x & 15) | ((z & 15) << 4) | ((y & 15) << 8), value); } + // operation type: updating public void set(final int index, final int value) { - if (this.workingBytes == null) { - this.initialiseWorking(); + if (!this.updatingDirty) { + this.swapUpdatingAndMarkDirty(); } final int shift = (index & 1) << 2; final int i = index >>> 1; - this.workingBytes[i] = (byte)((this.workingBytes[i] & (0xF0 >>> shift)) | (value << shift)); + this.storageUpdating[i] = (byte)((this.storageUpdating[i] & (0xF0 >>> shift)) | (value << shift)); } } 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 2d493ade..ba5f872e 100644 --- a/src/main/java/ca/spottedleaf/starlight/common/light/SkyStarLightEngine.java +++ b/src/main/java/ca/spottedleaf/starlight/common/light/SkyStarLightEngine.java @@ -1,7 +1,8 @@ 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 ca.spottedleaf.starlight.common.chunk.ExtendedChunkSection; import net.minecraft.block.BlockState; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.ChunkPos; @@ -17,27 +18,362 @@ public final class SkyStarLightEngine extends StarLightEngine { + /* + Specification for managing the initialisation and de-initialisation of skylight nibble arrays: + + Skylight nibble initialisation requires that non-empty chunk sections have 1 radius nibbles non-null. + + This presents some problems, as vanilla is only guaranteed to have 0 radius neighbours loaded when editing blocks. + However starlight fixes this so that it has 1 radius loaded. Still, we don't actually have guarantees + that we have the necessary chunks loaded to de-initialise neighbour sections (but we do have enough to de-initialise + our own) - we need a radius of 2 to de-initialise neighbour nibbles. + How do we solve this? + + Each chunk will store the last known "emptiness" of sections for each of their 1 radius neighbour chunk sections. + If the chunk does not have full data, then its nibbles are NOT de-initialised. This is because obviously the + chunk did not go through the light stage yet - or its neighbours are not lit. In either case, once the last + known "emptiness" of neighbouring sections is filled with data, the chunk will run a full check of the data + to see if any of its nibbles need to be de-initialised. + + The emptiness map allows us to de-initialise neighbour nibbles if the neighbour has it filled with data, + and if it doesn't have data then we know it will correctly de-initialise once it fills up. + + Unlike vanilla, we store whether nibbles are uninitialised on disk - so we don't need any dumb hacking + around those. + */ + + @Override + protected void handleEmptySectionChanges(final ChunkProvider lightAccess, final Chunk chunk, + final Boolean[] emptinessChanges, final boolean unlit) { + final int chunkX = chunk.getPos().x; + final int chunkZ = chunk.getPos().z; + + // index = (cx + 2) + 5*(cz + 2) + long loadedNeighboursBitset = 0L; + long unloadedNeighbourBitset = 0L; + + for (int dz = -1; dz <= 1; ++dz) { + for (int dx = -1; dx <= 1; ++dx) { + final Chunk neighbour = this.getChunkInCache(dx + chunkX, dz + chunkZ); + if (neighbour == null) { + continue; + } + + final boolean[][] neighbourEmptinessMap = ((ExtendedChunk)neighbour).getEmptinessMap(); + for (int i = 0; i < neighbourEmptinessMap.length; ++i) { + // index = (cx + 1) + 3*(cz + 1) + final int dx2 = (i % 3) - 1; + final int dz2 = (i / 3) - 1; + + final int bitsetIndex = (dx2 + dx + 2) + 5*(dz2 + dz + 2); + if (neighbourEmptinessMap[i] == null) { + unloadedNeighbourBitset |= 1L << bitsetIndex; + } else { + loadedNeighboursBitset |= 1L << bitsetIndex; + } + } + } + } + + loadedNeighboursBitset &= ~unloadedNeighbourBitset; + loadedNeighboursBitset |= 1L << ((0 + 2) + 5*(0 + 2)); + + final boolean[] needsDeInitCheck = new boolean[9]; + final boolean needsInit = ((ExtendedChunk)chunk).getEmptinessMap()[ExtendedChunk.getEmptinessMapIndex(0, 0)] == null; + if (needsInit) { + ((ExtendedChunk)chunk).getEmptinessMap()[ExtendedChunk.getEmptinessMapIndex(0, 0)] = new boolean[16]; + } + + // index = (cx + 2) + 5*(cz + 2) + final boolean[][] chunkEmptinessMap = ((ExtendedChunk)chunk).getEmptinessMap(); + + // this chunk is new, so we need to init neighbours + // because this chunk might have been modified inbetween loading/saving, we have to rewrite the emptiness map + // for our neighbours, so don't bother checking if they exist & whether they even needed a de-init recalc + for (int dz = -1; dz <= 1; ++dz) { + for (int dx = -1; dx <= 1; ++dx) { + final ExtendedChunk neighbour = ((ExtendedChunk)this.getChunkInCache(dx + chunkX, dz + chunkZ)); + if (neighbour == null) { + // if the neighbour hasn't initialised its own empty map, we can't use it + // when it does though, it'll come by and initialise our map for it + continue; + } + + if (needsInit && (dx | dz) != 0) { + // init neighbour + neighbour.getEmptinessMap()[ExtendedChunk.getEmptinessMapIndex(-dx, -dz)] = new boolean[16]; + + if (neighbour.getEmptinessMap()[ExtendedChunk.getEmptinessMapIndex(0, 0)] != null) { + // init ourselves + System.arraycopy( + neighbour.getEmptinessMap()[ExtendedChunk.getEmptinessMapIndex(0, 0)], + 0, + chunkEmptinessMap[ExtendedChunk.getEmptinessMapIndex(dx, dz)] = new boolean[16], + 0, + 9 + ); + } + } + + // check if our neighbours are ready for a recalc + + long neighboursMask = 0L; + for (int dz2 = -1; dz2 <= 1; ++dz2) { + for (int dx2 = -1; dx2 <= 1; ++dx2) { + neighboursMask |= 1L << ((dx2 + dx + 2) + 5*(dz2 + dz + 2)); + } + } + + if ((loadedNeighboursBitset & neighboursMask) == neighboursMask) { + // can check for de-init + needsDeInitCheck[(dx + 1) + 3 * (dz + 1)] = true; + } + } + } + + for (int sectionY = (emptinessChanges.length - 1); sectionY >= 0; --sectionY) { + final Boolean valueBoxed = emptinessChanges[sectionY]; + if (valueBoxed == null) { + continue; + } + + final boolean empty = valueBoxed.booleanValue(); + + for (int dz = -1; dz <= 1; ++dz) { + for (int dx = -1; dx <= 1; ++dx) { + final ExtendedChunk neighbour = (ExtendedChunk)this.getChunkInCache(dx + chunkX, dz + chunkZ); + if (neighbour == null) { + // this is the case on the client, we _assume_ the server inits and sends to us. + // or we're on the server, and the neighbours haven't generated light yet. + continue; + } + + // init nibbles as needed + + if (!empty) { + // if we're not empty, we also need to initialise nibbles + // note: if we're unlit, we absolutely do not want to extrude, as light data isn't set up + final boolean extrude = (dx | dz) != 0 || !unlit; + for (int dy = 1; dy >= -1; --dy) { + this.initNibbleForLitChunk(dx + chunkX, dy + sectionY, dz + chunkZ, extrude, false); + } + } + + // update neighbour map + neighbour.getEmptinessMap()[ExtendedChunk.getEmptinessMapIndex(-dx, -dz)][sectionY] = empty; + } + } + } + + // check for de-init, only runs if this just had data loaded in (or is being lit) + for (int i = 0; i < needsDeInitCheck.length; ++i) { + if (!needsDeInitCheck[i]) { + continue; + } + + // index = (cx + 1) + 3*(cz + 1) + final int neighbourX = (i % 3) - 1 + chunkX; + final int neighbourZ = (i / 3) - 1 + chunkZ; + + final boolean[][] neighbourEmptinessMap = ((ExtendedChunk)this.getChunkInCache(neighbourX, neighbourZ)).getEmptinessMap(); + + for (int sectionY = 16; sectionY >= -1; --sectionY) { + final SWMRNibbleArray nibble = this.getNibbleFromCache(neighbourX, sectionY, neighbourZ); + if (nibble.isNullNibbleUpdating()) { + // already null + continue; + } + + // check neighbours to see if we need to de-init this one + boolean allEmpty = true; + neighbour_search: + for (int dy = -1; dy <= 1; ++dy) { + for (int dz = -1; dz <= 1; ++dz) { + for (int dx = -1; dx <= 1; ++dx) { + final int y = sectionY + dy; + if (y < 0 || y > 15) { + // empty + continue; + } + if (!neighbourEmptinessMap[ExtendedChunk.getEmptinessMapIndex(dx, dz)][y]) { + allEmpty = false; + break neighbour_search; + } + } + } + } + + if (allEmpty) { + // all were empty, so de-init + nibble.setNull(); + } + } + } + } + + protected final void initNibbleForLitChunk(final int chunkX, final int chunkY, final int chunkZ, final boolean extrude, + final boolean initRemovedNibbles) { + if (chunkY < -1 || chunkY > 16 || this.getChunkInCache(chunkX, chunkZ) == null) { + return; + } + SWMRNibbleArray nibble = this.getNibbleFromCache(chunkX, chunkY, chunkZ); + if (nibble == null) { + if (!initRemovedNibbles) { + throw new IllegalStateException(); + } else { + this.setNibbleInCache(chunkX, chunkY, chunkZ, nibble = new SWMRNibbleArray(null, true)); + } + } + this.initNibbleForLitChunk(nibble, chunkX, chunkY, chunkZ, extrude); + } + + protected final void initNibbleForLitChunk(final SWMRNibbleArray currNibble, final int chunkX, final int chunkY, final int chunkZ, final boolean extrude) { + if (!currNibble.isNullNibbleUpdating()) { + // already initialised + return; + } + + final boolean[] emptinessMap = ((ExtendedChunk)this.getChunkInCache(chunkX, chunkZ)) + .getEmptinessMap()[ExtendedChunk.getEmptinessMapIndex(0, 0)]; + + // are we above this chunk's lowest empty section? + int lowestY = -2; + for (int currY = 15; currY >= 0; --currY) { + if (emptinessMap == null) { + // cannot delay nibble init for lit chunks, as we need to init to propagate into them. + final ChunkSection current = this.getChunkSection(chunkX, currY, chunkZ); + if (current == null || current == EMPTY_CHUNK_SECTION) { + continue; + } + } else { + if (emptinessMap[currY]) { + continue; + } + } + + // should always be full lit here + lowestY = currY; + break; + } + + if (chunkY > lowestY) { + // we need to set this one to full + this.getNibbleFromCache(chunkX, chunkY, chunkZ).setFull(); + return; + } + + if (extrude) { + // this nibble is going to depend solely on the skylight data above it + // find first non-null data above (there does exist one, as we just found it above) + for (int currY = chunkY + 1; currY <= 16; ++currY) { + final SWMRNibbleArray nibble = this.getNibbleFromCache(chunkX, currY, chunkZ); + if (nibble != null && !nibble.isNullNibbleUpdating()) { + currNibble.extrudeLower(nibble); + break; + } + } + } else { + currNibble.setNonNull(); + } + } + + protected final void rewriteNibbleCacheForSkylight(final Chunk chunk) { + for (int index = 0, max = this.nibbleCache.length; index < max; ++index) { + final SWMRNibbleArray nibble = this.nibbleCache[index]; + if (nibble == null || nibble.isNullNibbleUpdating()) { + // stop propagation in these areas + this.nibbleCache[index] = null; + } + } + } + + protected final boolean[] nullPropagationCheckCache = new boolean[16 - (-1) + 1]; + + // rets whether neighbours were init'd + + protected final boolean checkNullSection(final int chunkX, final int chunkY, final int chunkZ, + final boolean extrudeInitialised) { + // null chunk sections may have nibble neighbours in the horizontal 1 radius that are + // non-null. Propagation to these neighbours is necessary. + // What makes this easy is we know none of these neighbours are non-empty (otherwise + // this nibble would be initialised). So, we don't have to initialise + // the neighbours in the full 1 radius, because there's no worry that any "paths" + // to the neighbours on this horizontal plane are blocked. + if (chunkY < -1 || chunkY > 16 || this.nullPropagationCheckCache[chunkY + 1]) { + return false; + } + this.nullPropagationCheckCache[chunkY + 1] = true; + + // check horizontal neighbours + boolean needInitNeighbours = false; + neighbour_search: + for (int dz = -1; dz <= 1; ++dz) { + for (int dx = -1; dx <= 1; ++dx) { + final SWMRNibbleArray nibble = this.getNibbleFromCache(dx + chunkX, chunkY, dz + chunkZ); + if (nibble != null && !nibble.isNullNibbleUpdating()) { + needInitNeighbours = true; + break neighbour_search; + } + } + } + + if (needInitNeighbours) { + for (int dz = -1; dz <= 1; ++dz) { + for (int dx = -1; dx <= 1; ++dx) { + this.initNibbleForLitChunk(dx + chunkX, chunkY, dz + chunkZ, (dx | dz) == 0 ? extrudeInitialised : true, true); + } + } + } + + return needInitNeighbours; + } + + protected final int getLightLevelExtruded(final int worldX, final int worldY, final int worldZ) { + final int chunkX = worldX >> 4; + int chunkY = worldY >> 4; + final int chunkZ = worldZ >> 4; + + SWMRNibbleArray nibble = this.getNibbleFromCache(chunkX, chunkY, chunkZ); + if (nibble != null) { + return nibble.getUpdating(worldX, worldY, worldZ); + } + + for (;;) { + if (++chunkY > 16) { + return 15; + } + + nibble = this.getNibbleFromCache(chunkX, chunkY, chunkZ); + + if (nibble != null) { + return nibble.getUpdating(worldX, 0, worldZ); + } + } + } + public SkyStarLightEngine(final boolean isClientSide) { super(true, isClientSide); } @Override protected SWMRNibbleArray[] getNibblesOnChunk(final Chunk chunk) { - return ((NibbledChunk)chunk).getSkyNibbles(); + return ((ExtendedChunk)chunk).getSkyNibbles(); } @Override protected void setNibbles(final Chunk chunk, final SWMRNibbleArray[] to) { - ((NibbledChunk)chunk).setSkyNibbles(to); + ((ExtendedChunk)chunk).setSkyNibbles(to); } @Override protected boolean canUseChunk(final Chunk chunk) { - return chunk.getStatus().isAtLeast(ChunkStatus.LIGHT) && (this.isClientSide || chunk.isLightOn()); + // can only use chunks for sky stuff if their sections have been init'd + return chunk.getStatus().isAtLeast(ChunkStatus.LIGHT) + && (this.isClientSide ? ((ExtendedChunk)chunk).getEmptinessMap()[ExtendedChunk.getEmptinessMapIndex(0, 0)] != null : chunk.isLightOn()); } @Override - protected final void checkBlock(final int worldX, final int worldY, final int worldZ) { + protected void checkBlock(final int worldX, final int worldY, final int worldZ) { // blocks can change opacity // blocks can change direction of propagation @@ -49,27 +385,32 @@ protected final void checkBlock(final int worldX, final int worldY, final int wo if (currentLevel == 15) { // must re-propagate clobbered source - this.increaseQueue[this.increaseQueueInitialLength++] = (worldX + (worldZ << 6) + (worldY << (6 + 6)) + encodeOffset) | - currentLevel << (6 + 6 + 9) | - ((AxisDirection.POSITIVE_X.ordinal() | 8) << (6 + 6 + 9 + 4)) | - (FLAG_HAS_SIDED_TRANSPARENT_BLOCKS); // don't know if the block is conditionally transparent + this.increaseQueue[this.increaseQueueInitialLength++] = + ((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)) + | FLAG_HAS_SIDED_TRANSPARENT_BLOCKS // don't know if the block is conditionally transparent + | (FLAG_RECHECK_LEVEL); // this source might be set up to decrease } else { this.setLightLevel(worldX, worldY, worldZ, 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)); + 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)); } - protected final int[] heightMap = new int[16 * 16]; + protected final int[] heightMapBlockChange = new int[16 * 16]; { - Arrays.fill(this.heightMap, -1024); // clear heightmap + Arrays.fill(this.heightMapBlockChange, -1024); // clear heightmap } @Override - protected void propagateBlockChanges(final ChunkProvider lightAccess, final Chunk atChunk, - final Set positions) { + protected void propagateBlockChanges(final ChunkProvider lightAccess, final Chunk atChunk, final Set positions) { + this.rewriteNibbleCacheForSkylight(atChunk); + Arrays.fill(this.nullPropagationCheckCache, false); + final BlockView world = lightAccess.getWorld(); final int chunkX = atChunk.getPos().x; final int chunkZ = atChunk.getPos().z; @@ -79,9 +420,9 @@ protected void propagateBlockChanges(final ChunkProvider lightAccess, final Chun int highestBlockY = -1024; for (final BlockPos pos : positions) { final int index = pos.getX() + (pos.getZ() << 4) + heightMapOffset; - final int curr = this.heightMap[index]; + final int curr = this.heightMapBlockChange[index]; if (pos.getY() > curr) { - this.heightMap[index] = pos.getY(); + this.heightMapBlockChange[index] = pos.getY(); } if (pos.getY() > highestBlockY) { highestBlockY = pos.getY(); @@ -90,42 +431,59 @@ protected void propagateBlockChanges(final ChunkProvider lightAccess, final Chun // now we can recalculate the sources for the changed columns for (int index = 0; index < (16 * 16); ++index) { - final int maxY = this.heightMap[index]; + final int maxY = this.heightMapBlockChange[index]; if (maxY == -1024) { // not changed continue; } - this.heightMap[index] = -1024; // restore default for next caller + this.heightMapBlockChange[index] = -1024; // restore default for next caller final int columnX = (index & 15) | (chunkX << 4); final int columnZ = (index >>> 4) | (chunkZ << 4); // try and propagate from the above y - int maxPropagationY = this.tryPropagateSkylight(world, columnX, maxY, columnZ); + final int maxPropagationY = this.tryPropagateSkylight(world, columnX, maxY, columnZ, true); // maxPropagationY is now the highest block that could not be propagated to // remove all sources below that are 15 - final int propagateDirection = AxisDirection.NEGATIVE_Y.ordinal(); + final long propagateDirection = AxisDirection.POSITIVE_Y.everythingButThisDirection; final int encodeOffset = this.coordinateOffset; - for (int currY = maxPropagationY; currY >= -15; --currY) { - if (this.getLightLevel(columnX, currY, columnZ) != 15) { - break; + + if (this.getLightLevelExtruded(columnX, maxPropagationY, columnZ) == 15) { + // ensure section is checked + this.checkNullSection(columnX >> 4, maxPropagationY >> 4, columnZ >> 4, true); + + for (int currY = maxPropagationY; currY >= -15; --currY) { + if ((currY & 15) == 15) { + // ensure section is checked + this.checkNullSection(columnX >> 4, (currY >> 4), columnZ >> 4, true); + } + + // ensure section below is always checked + final SWMRNibbleArray nibble = this.getNibbleFromCache(columnX >> 4, currY >> 4, columnZ >> 4); + if (nibble == null) { + // advance currY to the the top of the section below + currY = (currY) & (~15); + // note: this value ^ is actually 1 above the top, but the loop decrements by 1 so we actually + // end up there + continue; + } + + if (nibble.getUpdating(columnX, currY, columnZ) != 15) { + break; + } + + this.decreaseQueue[this.decreaseQueueInitialLength++] = + ((columnX + (columnZ << 6) + (currY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) + | (15L << (6 + 6 + 16)) + | (propagateDirection << (6 + 6 + 16 + 4)) + | FLAG_FORCE_WRITE; // overwriting the nibble value affects init of dummy nibbles + // do not set transparent blocks for the same reason we don't in the checkBlock method } - this.setLightLevel(columnX, currY, columnZ, 0); - this.decreaseQueue[this.decreaseQueueInitialLength++] = (columnX + (columnZ << 6) + (currY << (6 + 6)) + encodeOffset) | - (15 << (6 + 6 + 9)) | - ((propagateDirection) << (6 + 6 + 9 + 4)); - // do not set transparent blocks for the same reason we don't in the checkBlock method } } - // we need to initialise nibbles up to the highest section (we don't save null nibbles) - for (int y = -1; y <= Math.min(16, (highestBlockY >> 4)); ++y) { - final SWMRNibbleArray nibble = this.getNibbleFromCache(chunkX, y, chunkZ); - nibble.markNonNull(); - } - for (final BlockPos pos : positions) { this.checkBlock(pos.getX(), pos.getY(), pos.getZ()); } @@ -133,52 +491,24 @@ protected void propagateBlockChanges(final ChunkProvider lightAccess, final Chun this.performLightDecrease(lightAccess); } - protected void initLightNeighbours(final int chunkX, final int chunkZ) { - // vanilla requires that written nibble data has initialised nibble data in 1 radius - for (int dz = -1; dz <= 1; ++dz) { - for (int dx = -1; dx <= 1; ++dx) { - Chunk chunk = this.getChunkInCache(dx + chunkX, dz + chunkZ); - if (chunk == null) { - continue; - } - // find lowest section - int lowest = 15; - ChunkSection[] sections = chunk.getSectionArray(); - for (;lowest > 0 && (sections[lowest] == null || sections[lowest].isEmpty()); --lowest) {} - - if (lowest == -1) { - continue; - } - - for (int y = lowest; y >= -1; --y) { - SWMRNibbleArray nibble = this.getNibbleFromCache(dx + chunkX, y, dz + chunkZ); - if (nibble != null && !nibble.isDirty() && nibble.isInitialisedUpdating()) { - for (int dy = -1; dy <= 1; ++dy) { - SWMRNibbleArray ours = this.getNibbleFromCache(chunkX, dy + y, chunkZ); - if (ours != null && !ours.isDirty() && ours.isNullNibbleUpdating()) { - ours.initialiseWorking(); - ours.updateVisible(); - } - } - } - } - } - } - } + protected final int[] heightMapGen = new int[32 * 32]; @Override protected void lightChunk(final ChunkProvider lightAccess, final Chunk chunk, final boolean needsEdgeChecks) { + this.rewriteNibbleCacheForSkylight(chunk); + Arrays.fill(this.nullPropagationCheckCache, false); + final BlockView world = lightAccess.getWorld(); final ChunkPos chunkPos = chunk.getPos(); final int chunkX = chunkPos.x; final int chunkZ = chunkPos.z; final ChunkSection[] sections = chunk.getSectionArray(); - final SWMRNibbleArray[] originalNibbles = this.getNibblesForChunkFromCache(chunkX, chunkZ); - int highestNonEmptySection = 16; - while (highestNonEmptySection == -1 || highestNonEmptySection == 16 || + int highestNonEmptySection = 15; + while (highestNonEmptySection == -1 || sections[highestNonEmptySection] == null || sections[highestNonEmptySection].isEmpty()) { + this.checkNullSection(chunkX, highestNonEmptySection, chunkZ, false); // try propagate FULL to neighbours // check neighbours to see if we need to propagate into them @@ -188,11 +518,7 @@ protected void lightChunk(final ChunkProvider lightAccess, final Chunk chunk, fi final SWMRNibbleArray neighbourNibble = this.getNibbleFromCache(neighbourX, highestNonEmptySection, neighbourZ); if (neighbourNibble == null) { // unloaded neighbour - continue; - } - if (neighbourNibble.isNullNibbleUpdating()) { // most of the time we fall here - // no point of propagating full light into full light continue; } @@ -230,14 +556,15 @@ protected void lightChunk(final ChunkProvider lightAccess, final Chunk chunk, fi } final int encodeOffset = this.coordinateOffset; - final int propagateDirection = direction.ordinal() | 16; // we only want to check in this direction + final long propagateDirection = 1L << direction.ordinal(); // we only want to check in this direction for (int currY = highestNonEmptySection << 4, maxY = currY | 15; currY <= maxY; ++currY) { for (int i = 0, currX = startX, currZ = startZ; i < 16; ++i, currX += incX, currZ += incZ) { - this.increaseQueue[this.increaseQueueInitialLength++] = (currX + (currZ << 6) + (currY << (6 + 6)) + encodeOffset) | - (15 << (6 + 6 + 9)) | // we know we're at full lit here - ((propagateDirection) << (6 + 6 + 9 + 4)); - // no transparent flag, we know for a fact there are no blocks here that could be directionally transparent (as the section is EMPTY) + this.increaseQueue[this.increaseQueueInitialLength++] = + ((currX + (currZ << 6) + (currY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) + | (15L << (6 + 6 + 16)) // we know we're at full lit here + | (propagateDirection << (6 + 6 + 16 + 4)); + // no transparent flag, we know for a fact there are no blocks here that could be directionally transparent (as the section is EMPTY) } } } @@ -248,20 +575,125 @@ protected void lightChunk(final ChunkProvider lightAccess, final Chunk chunk, fi } if (highestNonEmptySection >= 0) { - // mark the rest of our nibbles as 0 - for (int currY = highestNonEmptySection; currY >= -1; --currY) { - this.getNibbleFromCache(chunkX, currY, chunkZ).markNonNull(); - } - // fill out our other sources - final int minX = chunkPos.x << 4; - final int maxX = chunkPos.x << 4 | 15; - final int minZ = chunkPos.z << 4; - final int maxZ = chunkPos.z << 4 | 15; - final int startY = highestNonEmptySection << 4 | 15; + + // init heightmap + // index = (x + 1) + ((z + 1) << 5) + final int[] heightMap = this.heightMapGen; + final int worldChunkX = chunkPos.x << 4; + final int worldChunkZ = chunkPos.z << 4; + final int minX = worldChunkX - 1; + final int maxX = worldChunkX + 16; + final int minZ = worldChunkZ - 1; + final int maxZ = worldChunkZ + 16; for (int currZ = minZ; currZ <= maxZ; ++currZ) { for (int currX = minX; currX <= maxX; ++currX) { - this.tryPropagateSkylight(world, currX, startY, currZ); + int maxY = -33; + + // ensure the section below is always checked + this.checkNullSection(currX >> 4, highestNonEmptySection, currZ >> 4, false); + this.checkNullSection(currX >> 4, highestNonEmptySection - 1, currZ >> 4, false); + for (int sectionY = highestNonEmptySection; sectionY >= 0; --sectionY) { + final ChunkSection section = this.getChunkSection(currX >> 4, sectionY, currZ >> 4); + + if (section == null) { + // unloaded neighbour + continue; + } + + // ensure the section below is always checked + this.checkNullSection(currX >> 4, sectionY - 1, currZ >> 4, false); + + final long bitset = ((ExtendedChunkSection)section).getBitsetForColumn(currX & 15, currZ & 15); + if (bitset == 0) { + continue; + } + + final int highestBitSet = 63 ^ Long.numberOfLeadingZeros(bitset); // from [0, 63] + final int highestYValue = highestBitSet >> 1; + maxY = highestYValue | (sectionY << 4); + break; + } + heightMap[(currX - worldChunkX + 1) | ((currZ - worldChunkZ + 1) << 5)] = maxY; + } + } + + // now setup sources + final long[] queue = this.increaseQueue; + final int encodeOffset = this.coordinateOffset; + for (int currZ = 0; currZ <= 15; ++currZ) { + for (int currX = 0; currX <= 15; ++currX) { + final int worldX = currX | worldChunkX; + final int worldZ = currZ | worldChunkZ; + // NX = -1 on x + // PX = +1 on x + // NZ = -1 on z + // PZ = +1 on z + // C = center + + // index = (x + 1) | ((z + 1) << 5) + + // X = 0, Z = 0 + final int heightMapC = heightMap[(currX + 1) | ((currZ + 1) << 5)]; + + // X = -1 + final int heightMapNX = heightMap[(currX - 1 + 1) | ((currZ + 1) << 5)]; + + // X = 1 + final int heightMapPX = heightMap[(currX + 1 + 1) | ((currZ + 1) << 5)]; + + // Z = -1 + final int heightMapNZ = heightMap[(currX + 1) | ((currZ - 1 + 1) << 5)]; + + // Z = 1 + final int heightMapPZ = heightMap[(currX + 1) | ((currZ + 1 + 1) << 5)]; + + int queueLength = this.increaseQueueInitialLength; + + for (int currY = (highestNonEmptySection << 4) + 16; currY > heightMapC;) { + final SWMRNibbleArray nibble = this.getNibbleFromCache(chunkX, currY >> 4, chunkZ); + if (nibble == null) { + // skip this section, has no data + currY = (currY - 16) & (~15); + continue; + } + + long propagateDirectionBitset = 0L; + // +X + propagateDirectionBitset |= ((currY <= heightMapPX) ? 1L : 0L) << AxisDirection.POSITIVE_X.ordinal(); + + // -X + propagateDirectionBitset |= ((currY <= heightMapNX) ? 1L : 0L) << AxisDirection.NEGATIVE_X.ordinal(); + + // +Z + propagateDirectionBitset |= ((currY <= heightMapPZ) ? 1L : 0L) << AxisDirection.POSITIVE_Z.ordinal(); + + // -Z + propagateDirectionBitset |= ((currY <= heightMapNZ) ? 1L : 0L) << AxisDirection.NEGATIVE_Z.ordinal(); + + // +Y is always 0 since we don't want to check upwards + + // -Y: + propagateDirectionBitset |= ((currY == (heightMapC + 1)) ? 1L : 0L) << AxisDirection.NEGATIVE_Y.ordinal(); + + // now setup source + // unlike block checks, we don't use FORCE_WRITE here because our init doesn't rely on above nibbles + // when initialising + nibble.set((worldX & 15) | ((worldZ & 15) << 4) | ((currY & 15) << 8), 15); + if (propagateDirectionBitset != 0L) { + queue[queueLength++] = + ((worldX + (worldZ << 6) + (currY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) + | (15L << (6 + 6 + 16)) + | propagateDirectionBitset << (6 + 6 + 16 + 4); + // above heightmap, so not sidedly transparent + } + + --currY; + } + + this.increaseQueueInitialLength = queueLength; + // Just in case there's a conditionally transparent block at the top. + this.tryPropagateSkylight(world, worldX, heightMapC, worldZ, false); } } } // else: apparently the chunk is empty @@ -270,40 +702,51 @@ 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); - this.checkChunkEdges(lightAccess, chunk); + for (int y = -1; y <= 16; ++y) { + this.checkNullSection(chunkX, y, chunkZ, false); + } + this.checkChunkEdges(lightAccess, chunk, -1, 16); } else { + for (int y = -1; y <= highestNonEmptySection; ++y) { + this.checkNullSection(chunkX, y, chunkZ, false); + } this.propagateNeighbourLevels(lightAccess, chunk, -1, highestNonEmptySection); this.performLightIncrease(lightAccess); } - - this.initLightNeighbours(chunkPos.x, chunkPos.z); } - protected final int tryPropagateSkylight(final BlockView world, final int worldX, final int startY, final int worldZ) { + protected final int tryPropagateSkylight(final BlockView world, final int worldX, int startY, final int worldZ, + final boolean extrudeInitialised) { final BlockPos.Mutable mutablePos = this.mutablePos3; final int encodeOffset = this.coordinateOffset; - final int propagateDirection = AxisDirection.NEGATIVE_Y.ordinal(); // just don't check upwards. + final long propagateDirection = AxisDirection.POSITIVE_Y.everythingButThisDirection; // just don't check upwards. - if (this.getLightLevel(worldX, startY + 1, worldZ) != 15) { + if (this.getLightLevelExtruded(worldX, startY + 1, worldZ) != 15) { return startY; } + // ensure this section is always checked + this.checkNullSection(worldX >> 4, startY >> 4, worldZ >> 4, extrudeInitialised); + BlockState above = this.getBlockState(worldX, startY + 1, worldZ); if (above == null) { above = AIR_BLOCK_STATE; } - int maxPropagationY; - for (maxPropagationY = startY; maxPropagationY >= -15; --maxPropagationY) { - BlockState current = this.getBlockState(worldX, maxPropagationY, worldZ); + for (;startY >= -15; --startY) { + if ((startY & 15) == 15) { + // ensure this section is always checked + this.checkNullSection(worldX >> 4, startY >> 4, worldZ >> 4, extrudeInitialised); + } + BlockState current = this.getBlockState(worldX, startY, worldZ); if (current == null) { current = AIR_BLOCK_STATE; } final VoxelShape fromShape; - if (((LightAccessBlockState)above).isConditionallyFullOpaque()) { - this.mutablePos2.set(worldX, maxPropagationY + 1, worldZ); + if (((ExtendedAbstractBlockState)above).isConditionallyFullOpaque()) { + this.mutablePos2.set(worldX, startY + 1, worldZ); fromShape = above.getCullingFace(world, this.mutablePos2, AxisDirection.NEGATIVE_Y.nms); if (VoxelShapes.unionCoversFullCube(VoxelShapes.empty(), fromShape)) { // above wont let us propagate @@ -313,7 +756,7 @@ protected final int tryPropagateSkylight(final BlockView world, final int worldX fromShape = VoxelShapes.empty(); } - final int opacityIfCached = ((LightAccessBlockState)current).getOpacityIfCached(); + final int opacityIfCached = ((ExtendedAbstractBlockState)current).getOpacityIfCached(); // does light propagate from the top down? if (opacityIfCached != -1) { if (opacityIfCached != 0) { @@ -321,15 +764,16 @@ protected final int tryPropagateSkylight(final BlockView world, final int worldX break; } // most of the time it falls here. - this.setLightLevel(worldX, maxPropagationY, worldZ, 15); // add to propagate - this.increaseQueue[this.increaseQueueInitialLength++] = (worldX + (worldZ << 6) + (maxPropagationY << (6 + 6)) + encodeOffset) | - (15 << (6 + 6 + 9)) | // we know we're at full lit here - ((propagateDirection) << (6 + 6 + 9 + 4)); + this.increaseQueue[this.increaseQueueInitialLength++] = + ((worldX + (worldZ << 6) + (startY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) + | (15L << (6 + 6 + 16)) // we know we're at full lit here + | (propagateDirection << (6 + 6 + 16 + 4)) + | FLAG_FORCE_WRITE; // overwriting the nibble value affects init of dummy nibbles } else { - mutablePos.set(worldX, maxPropagationY, worldZ); - int flags = 0; - if (((LightAccessBlockState)current).isConditionallyFullOpaque()) { + mutablePos.set(worldX, startY, worldZ); + long flags = FLAG_FORCE_WRITE; // overwriting the nibble value affects init of dummy nibbles + if (((ExtendedAbstractBlockState)current).isConditionallyFullOpaque()) { final VoxelShape cullingFace = current.getCullingFace(world, mutablePos, AxisDirection.POSITIVE_Y.nms); if (VoxelShapes.unionCoversFullCube(fromShape, cullingFace)) { @@ -345,16 +789,32 @@ protected final int tryPropagateSkylight(final BlockView world, final int worldX break; } - this.setLightLevel(worldX, maxPropagationY, worldZ, 15); - this.increaseQueue[this.increaseQueueInitialLength++] = (worldX + (worldZ << 6) + (maxPropagationY << (6 + 6)) + encodeOffset) | - (15 << (6 + 6 + 9)) | // we know we're at full lit here - ((propagateDirection) << (6 + 6 + 9 + 4)) | - flags; + this.increaseQueue[this.increaseQueueInitialLength++] = + ((worldX + (worldZ << 6) + (startY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) + | (15L << (6 + 6 + 16)) // we know we're at full lit here + | (propagateDirection << (6 + 6 + 16 + 4)) + | flags; } above = current; + + if (this.getNibbleFromCache(worldX >> 4, startY >> 4, worldZ >> 4) == null) { + // we skip empty sections here, as this is just an easy way of making sure the above block + // can propagate through air. + + // nothing can propagate in null sections, remove the queue entry for it + --this.increaseQueueInitialLength; + + // advance currY to the the top of the section below + startY = (startY) & (~15); + // note: this value ^ is actually 1 above the top, but the loop decrements by 1 so we actually + // end up there + + // make sure this is marked as AIR + above = AIR_BLOCK_STATE; + } } - return maxPropagationY; + return startY; } } 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 21f889b1..d32dd5e8 100644 --- a/src/main/java/ca/spottedleaf/starlight/common/light/StarLightEngine.java +++ b/src/main/java/ca/spottedleaf/starlight/common/light/StarLightEngine.java @@ -1,10 +1,9 @@ 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.util.IntegerUtil; import net.minecraft.block.BlockState; import net.minecraft.block.Blocks; -import net.minecraft.server.world.ChunkTicketType; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.ChunkPos; import net.minecraft.util.math.ChunkSectionPos; @@ -53,12 +52,17 @@ protected static enum AxisDirection { public final int y; public final int z; public final Direction nms; + public final long everythingButThisDirection; + public final long everythingButTheOppositeDirection; AxisDirection(final int x, final int y, final int z) { this.x = x; this.y = y; this.z = z; this.nms = Direction.fromVector(x, y, z); + this.everythingButThisDirection = (long)(ALL_DIRECTIONS_BITSET ^ (1 << this.ordinal())); + // positive is always even, negative is always odd. Flip the 1 bit to get the negative direction. + this.everythingButTheOppositeDirection = (long)(ALL_DIRECTIONS_BITSET ^ (1 << (this.ordinal() ^ 1))); } public AxisDirection getOpposite() { @@ -83,6 +87,11 @@ public AxisDirection getOpposite() { // index = x + (z * 5) + (y * 25) protected final SWMRNibbleArray[] nibbleCache = new SWMRNibbleArray[5 * 5 * (16 + 2 + 2)]; // add two extra sections for buffer + // the exact same as above, except for storing fast access to nibbles to call change callbacks for + // for the y chunk section it's from [-1, 16] or [0, 17] + // index = x + (z * 5) + (y * 25) + protected final boolean[] notifyUpdateCache = new boolean[5 * 5 * (16 + 2 + 2)]; + // always initialsed during start of lighting. no index is null. // index = x + (z * 5) protected final Chunk[] chunkCache = new Chunk[5 * 5]; @@ -177,10 +186,18 @@ protected final void setChunkInCache(final int chunkX, final int chunkZ, final C this.chunkCache[chunkX + 5*chunkZ + this.chunkIndexOffset] = chunk; } + protected final ChunkSection getChunkSection(final int chunkX, final int chunkY, final int chunkZ) { + return this.sectionCache[chunkX + 5*chunkZ + (5 * 5) * chunkY + this.chunkSectionIndexOffset]; + } + + protected final void setChunkSectionInCache(final int chunkX, final int chunkY, final int chunkZ, final ChunkSection section) { + this.sectionCache[chunkX + 5*chunkZ + 5*5*chunkY + this.chunkSectionIndexOffset] = section; + } + protected final void setBlocksForChunkInCache(final int chunkX, final int chunkZ, final ChunkSection[] sections) { - final int chunkIndex = chunkX + 5*chunkZ; for (int cy = -1; cy <= 16; ++cy) { - this.sectionCache[chunkIndex + (cy * (5 * 5)) + this.chunkSectionIndexOffset] = sections == null ? null : (cy >= 0 && cy <= 15 ? (sections[cy] == null || sections[cy].isEmpty() ? EMPTY_CHUNK_SECTION : sections[cy]) : EMPTY_CHUNK_SECTION); + this.setChunkSectionInCache(chunkX, cy, chunkZ, + sections == null ? null : (cy >= 0 && cy <= 15 ? (sections[cy] == null || sections[cy].isEmpty() ? EMPTY_CHUNK_SECTION : sections[cy]) : EMPTY_CHUNK_SECTION)); } } @@ -198,40 +215,28 @@ protected final SWMRNibbleArray[] getNibblesForChunkFromCache(final int chunkX, return ret; } + protected final void setNibbleInCache(final int chunkX, final int chunkY, final int chunkZ, final SWMRNibbleArray nibble) { + this.nibbleCache[chunkX + 5*chunkZ + (5 * 5) * chunkY + this.chunkSectionIndexOffset] = nibble; + } + protected final void setNibblesForChunkInCache(final int chunkX, final int chunkZ, final SWMRNibbleArray[] nibbles) { for (int cy = -1; cy <= 16; ++cy) { this.setNibbleInCache(chunkX, cy, chunkZ, nibbles == null ? null : nibbles[cy + 1]); } } - protected final void setNibbleInCache(final int chunkX, final int chunkY, final int chunkZ, final SWMRNibbleArray nibble) { - this.nibbleCache[chunkX + 5*chunkZ + (5 * 5) * chunkY + this.chunkSectionIndexOffset] = nibble; - } - protected final void updateVisible(final ChunkProvider lightAccess) { for (int index = 0, max = this.nibbleCache.length; index < max; ++index) { final SWMRNibbleArray nibble = this.nibbleCache[index]; - if (nibble != null && nibble.updateVisible()) { - final int chunkX = (index % 5) - this.chunkOffsetX; - final int chunkZ = ((index / 5) % 5) - this.chunkOffsetZ; - final int chunkY = ((index / (5*5)) % (16 + 2 + 2)) - this.chunkOffsetY; + if (!this.notifyUpdateCache[index] && (nibble == null || !nibble.isDirty())) { + continue; + } + + final int chunkX = (index % 5) - this.chunkOffsetX; + final int chunkZ = ((index / 5) % 5) - this.chunkOffsetZ; + final int chunkY = ((index / (5*5)) % (16 + 2 + 2)) - this.chunkOffsetY; + if ((nibble != null && nibble.updateVisible()) || this.notifyUpdateCache[index]) { lightAccess.onLightUpdate(this.skylightPropagator ? LightType.SKY : LightType.BLOCK, ChunkSectionPos.from(chunkX, chunkY, chunkZ)); - // initialise 1 radius neighbours - if (this.skylightPropagator) { - for (int dy = -1; dy <= 1; ++dy) { - for (int dz = -1; dz <= 1; ++dz) { - for (int dx = -1; dx <= 1; ++dx) { - SWMRNibbleArray neighbour = this.getNibbleFromCache(chunkX + dx, chunkY + dy, chunkZ + dz); - if (neighbour != null && !neighbour.isDirty() && neighbour.isNullNibbleUpdating()) { - neighbour.initialiseWorking(); - neighbour.updateVisible(); - lightAccess.onLightUpdate(this.skylightPropagator ? LightType.SKY : LightType.BLOCK, - ChunkSectionPos.from(chunkX + dx, chunkY + dy, chunkZ + dz)); - } - } - } - } - } } } } @@ -240,6 +245,9 @@ protected final void destroyCaches() { Arrays.fill(this.sectionCache, null); Arrays.fill(this.nibbleCache, null); Arrays.fill(this.chunkCache, null); + if (this.isClientSide) { + Arrays.fill(this.notifyUpdateCache, false); + } } protected final BlockState getBlockState(final int worldX, final int worldY, final int worldZ) { @@ -256,11 +264,7 @@ protected final BlockState getBlockState(final int sectionIndex, final int local final ChunkSection section = this.sectionCache[sectionIndex]; if (section != null) { - return section == EMPTY_CHUNK_SECTION ? AIR_BLOCK_STATE : section.getBlockState( - localIndex & 15, - (localIndex >>> 8) & 15, - (localIndex >>> 4) & 15 - ); // TODO + return section == EMPTY_CHUNK_SECTION ? AIR_BLOCK_STATE : section.container.get(localIndex); } return null; @@ -279,33 +283,82 @@ protected final int getLightLevel(final int sectionIndex, final int localIndex) } protected final void setLightLevel(final int worldX, final int worldY, final int worldZ, final int level) { - final SWMRNibbleArray nibble = this.nibbleCache[(worldX >> 4) + 5 * (worldZ >> 4) + (5 * 5) * (worldY >> 4) + this.chunkSectionIndexOffset]; + final int sectionIndex = (worldX >> 4) + 5 * (worldZ >> 4) + (5 * 5) * (worldY >> 4) + this.chunkSectionIndexOffset; + final SWMRNibbleArray nibble = this.nibbleCache[sectionIndex]; if (nibble != null) { nibble.set((worldX & 15) | ((worldZ & 15) << 4) | ((worldY & 15) << 8), level); + if (this.isClientSide) { + int cx1 = (worldX - 1) >> 4; + int cx2 = (worldX + 1) >> 4; + int cy1 = (worldY - 1) >> 4; + int cy2 = (worldY + 1) >> 4; + int cz1 = (worldZ - 1) >> 4; + int cz2 = (worldZ + 1) >> 4; + for (int x = cx1; x <= cx2; ++x) { + for (int y = cy1; y <= cy2; ++y) { + for (int z = cz1; z <= cz2; ++z) { + this.notifyUpdateCache[x + 5 * z + (5 * 5) * y + this.chunkSectionIndexOffset] = true; + } + } + } + } } } - protected final void setLightLevel(final int sectionIndex, final int localIndex, final int level) { + protected final void postLightUpdate(final int worldX, final int worldY, final int worldZ) { + if (this.isClientSide) { + int cx1 = (worldX - 1) >> 4; + int cx2 = (worldX + 1) >> 4; + int cy1 = (worldY - 1) >> 4; + int cy2 = (worldY + 1) >> 4; + int cz1 = (worldZ - 1) >> 4; + int cz2 = (worldZ + 1) >> 4; + for (int x = cx1; x <= cx2; ++x) { + for (int y = cy1; y <= cy2; ++y) { + for (int z = cz1; z <= cz2; ++z) { + this.notifyUpdateCache[x + (5 * z) + (5 * 5 * y) + this.chunkSectionIndexOffset] = true; + } + } + } + } + } + + protected final void setLightLevel(final int sectionIndex, final int localIndex, final int worldX, final int worldY, final int worldZ, final int level) { final SWMRNibbleArray nibble = this.nibbleCache[sectionIndex]; if (nibble != null) { nibble.set(localIndex, level); + if (this.isClientSide) { + int cx1 = (worldX - 1) >> 4; + int cx2 = (worldX + 1) >> 4; + int cy1 = (worldY - 1) >> 4; + int cy2 = (worldY + 1) >> 4; + int cz1 = (worldZ - 1) >> 4; + int cz2 = (worldZ + 1) >> 4; + for (int x = cx1; x <= cx2; ++x) { + for (int y = cy1; y <= cy2; ++y) { + for (int z = cz1; z <= cz2; ++z) { + this.notifyUpdateCache[x + (5 * z) + (5 * 5 * y) + this.chunkSectionIndexOffset] = true; + } + } + } + } } } - public static SWMRNibbleArray[] getFilledEmptyLight(final boolean skylight) { + public static SWMRNibbleArray[] getFilledEmptyLight() { final SWMRNibbleArray[] ret = getEmptyLightArray(); for (int i = 0, len = ret.length; i < len; ++i) { - ret[i] = new SWMRNibbleArray(true, skylight ? 15 : 0); + ret[i] = new SWMRNibbleArray(null, true); } return ret; } public static SWMRNibbleArray[] getEmptyLightArray() { - return new SWMRNibbleArray[16 + 2]; + return new SWMRNibbleArray[16 - (-1) + 1]; } protected abstract SWMRNibbleArray[] getNibblesOnChunk(final Chunk chunk); @@ -314,15 +367,21 @@ public static SWMRNibbleArray[] getEmptyLightArray() { protected abstract boolean canUseChunk(final Chunk chunk); + // TODO include section changes public final void blocksChangedInChunk(final ChunkProvider lightAccess, final int chunkX, final int chunkZ, - final Set positions) { + final Set positions, final Boolean[] changedSections) { this.setupCaches(lightAccess, chunkX * 16 + 7, 128, chunkZ * 16 + 7, this.isClientSide); try { final Chunk chunk = this.getChunkInCache(chunkX, chunkZ); if (this.isClientSide && chunk == null) { return; } - this.propagateBlockChanges(lightAccess, chunk, positions); + if (changedSections != null) { + this.handleEmptySectionChanges(lightAccess, chunk, changedSections, false); + } + if (!positions.isEmpty()) { + this.propagateBlockChanges(lightAccess, chunk, positions); + } this.updateVisible(lightAccess); } finally { this.destroyCaches(); @@ -331,19 +390,21 @@ public final void blocksChangedInChunk(final ChunkProvider lightAccess, final in // subclasses should not initialise caches, as this will always be done by the super call // subclasses should not invoke updateVisible, as this will always be done by the super call - protected abstract void propagateBlockChanges(final ChunkProvider lightAccess, final Chunk atChunk, - final Set positions); + protected abstract void propagateBlockChanges(final ChunkProvider lightAccess, final Chunk atChunk, final Set positions); protected abstract void checkBlock(final int worldX, final int worldY, final int worldZ); // subclasses should not initialise caches, as this will always be done by the super call // subclasses should not invoke updateVisible, as this will always be done by the super call - protected final void checkChunkEdges(final ChunkProvider lightAccess, final Chunk chunk) { + // verifies that light levels on this chunks edges are consistent with this chunk's neighbours + // edges. if they are not, they are decreased (effectively performing the logic in checkBlock). + // This does not resolve skylight source problems. + protected final void checkChunkEdges(final ChunkProvider lightAccess, final Chunk chunk, final int fromSection, final int toSection) { final ChunkPos chunkPos = chunk.getPos(); final int chunkX = chunkPos.x; final int chunkZ = chunkPos.z; - for (int currSectionY = 16; currSectionY >= -1; --currSectionY) { + for (int currSectionY = toSection; currSectionY >= fromSection; --currSectionY) { final SWMRNibbleArray currNibble = this.getNibbleFromCache(chunkX, currSectionY, chunkZ); for (final AxisDirection direction : ONLY_HORIZONTAL_DIRECTIONS) { final int neighbourOffX = direction.x; @@ -352,18 +413,18 @@ protected final void checkChunkEdges(final ChunkProvider lightAccess, final Chun final SWMRNibbleArray neighbourNibble = this.getNibbleFromCache(chunkX + neighbourOffX, currSectionY, chunkZ + neighbourOffZ); - if (neighbourNibble == null) { + if (neighbourNibble == null || neighbourNibble.isNullNibbleUpdating()) { + continue; + } + + if (this.skylightPropagator && currNibble.isNullNibbleUpdating()) { + // TODO in what situation does this happen? Pretty sure it's erroneous. continue; } if (!currNibble.isInitialisedUpdating() && !neighbourNibble.isInitialisedUpdating()) { - if (this.skylightPropagator) { - if (currNibble.isNullNibbleUpdating() == neighbourNibble.isNullNibbleUpdating()) { - continue; - } // else fall through to edge checks - } else { - continue; - } + // both are zero, nothing to check. + continue; } final int incX; @@ -420,8 +481,8 @@ protected final void checkChunkEdges(final ChunkProvider lightAccess, final Chun final BlockState currentBlock = this.getBlockState(currX, currY, currZ); final BlockState neighbourBlock = this.getBlockState(neighbourX, currY, neighbourZ); - final int currentOpacity = ((LightAccessBlockState)currentBlock).getOpacityIfCached(); - final int neighbourOpacity = ((LightAccessBlockState)neighbourBlock).getOpacityIfCached(); + final int currentOpacity = ((ExtendedAbstractBlockState)currentBlock).getOpacityIfCached(); + final int neighbourOpacity = ((ExtendedAbstractBlockState)neighbourBlock).getOpacityIfCached(); if (currentOpacity == 0 || currentOpacity == 1 || neighbourOpacity == 0 || neighbourOpacity == 1) { // looks good @@ -440,6 +501,7 @@ protected final void checkChunkEdges(final ChunkProvider lightAccess, final Chun this.performLightDecrease(lightAccess); } + // pulls light from neighbours, and adds them into the increase queue. does not actually propagate. protected final void propagateNeighbourLevels(final ChunkProvider lightAccess, final Chunk chunk, final int fromSection, final int toSection) { final ChunkPos chunkPos = chunk.getPos(); final int chunkX = chunkPos.x; @@ -447,6 +509,9 @@ protected final void propagateNeighbourLevels(final ChunkProvider lightAccess, f for (int currSectionY = toSection; currSectionY >= fromSection; --currSectionY) { final SWMRNibbleArray currNibble = this.getNibbleFromCache(chunkX, currSectionY, chunkZ); + if (this.skylightPropagator && currNibble == null) { + continue; + } for (final AxisDirection direction : ONLY_HORIZONTAL_DIRECTIONS) { final int neighbourOffX = direction.x; final int neighbourOffZ = direction.z; @@ -454,18 +519,18 @@ protected final void propagateNeighbourLevels(final ChunkProvider lightAccess, f final SWMRNibbleArray neighbourNibble = this.getNibbleFromCache(chunkX + neighbourOffX, currSectionY, chunkZ + neighbourOffZ); - if (neighbourNibble == null) { + if (neighbourNibble == null || neighbourNibble.isNullNibbleUpdating()) { + continue; + } + + if (this.skylightPropagator && currNibble.isNullNibbleUpdating()) { + // TODO in what situation does this happen? Pretty sure it's erroneous. continue; } if (!neighbourNibble.isInitialisedUpdating()) { - if (this.skylightPropagator) { - if (currNibble.isNullNibbleUpdating() == neighbourNibble.isNullNibbleUpdating() || !neighbourNibble.isNullNibbleUpdating()) { - continue; - } // else fall through to edge checks - } else { - continue; - } + // can't pull from 0 + continue; } final int incX; @@ -499,7 +564,7 @@ protected final void propagateNeighbourLevels(final ChunkProvider lightAccess, f startX = chunkX << 4; } - final int propagateDirection = direction.getOpposite().ordinal() | 16; // we only want to check in this direction towards this chunk + final long propagateDirection = 1L << direction.getOpposite().ordinal(); // we only want to check in this direction towards this chunk final int encodeOffset = this.coordinateOffset; for (int currY = currSectionY << 4, maxY = currY | 15; currY <= maxY; ++currY) { @@ -515,23 +580,74 @@ protected final void propagateNeighbourLevels(final ChunkProvider lightAccess, f continue; } - this.increaseQueue[this.increaseQueueInitialLength++] = (currX + (currZ << 6) + (currY << (6 + 6)) + encodeOffset) | - (level << (6 + 6 + 9)) | - ((propagateDirection) << (6 + 6 + 9 + 4)) | - FLAG_HAS_SIDED_TRANSPARENT_BLOCKS; // don't know if the current block is transparent, must check. + this.increaseQueue[this.increaseQueueInitialLength++] = + ((currX + (currZ << 6) + (currY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) + | ((level & 0xFL) << (6 + 6 + 16)) + | (propagateDirection << (6 + 6 + 16 + 4)) + | FLAG_HAS_SIDED_TRANSPARENT_BLOCKS; // don't know if the current block is transparent, must check. } } } } } + public static Boolean[] getEmptySectionsForChunk(final Chunk chunk) { + final Boolean[] ret = new Boolean[16]; + + final ChunkSection[] sections = chunk.getSectionArray(); + + for (int i = 0; i < sections.length; ++i) { + if (sections[i] == null || sections[i].isEmpty()) { + ret[i] = Boolean.TRUE; + } else { + ret[i] = Boolean.FALSE; + } + } + + 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, true); + 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)); + } + try { + final Chunk chunk = this.getChunkInCache(chunkX, chunkZ); + if (chunk == null) { + return; + } + this.handleEmptySectionChanges(lightAccess, chunk, emptinessChanges, false); + this.updateVisible(lightAccess); + } finally { + this.destroyCaches(); + } + } + + // subclasses should not initialise caches, as this will always be done by the super call + // subclasses should not invoke updateVisible, as this will always be done by the super call + // subclasses are guaranteed that this is always called before a changed block set + // newChunk specifies whether the changes describe a "first load" of a chunk or changes to existing, already loaded chunks + protected abstract void handleEmptySectionChanges(final ChunkProvider lightAccess, final Chunk chunk, + final Boolean[] emptinessChanges, final boolean unlit); + public final void checkChunkEdges(final ChunkProvider lightAccess, final int chunkX, final int chunkZ) { this.setupCaches(lightAccess, chunkX * 16 + 7, 128, chunkZ * 16 + 7, false); try { - if (this.getChunkInCache(chunkX, chunkZ) == null) { + final Chunk chunk = this.getChunkInCache(chunkX, chunkZ); + if (chunk == null) { return; } - this.checkChunkEdges(lightAccess, this.getChunkInCache(chunkX, chunkZ)); + this.checkChunkEdges(lightAccess, chunk, -1, 16); this.updateVisible(lightAccess); } finally { this.destroyCaches(); @@ -542,9 +658,11 @@ public final void checkChunkEdges(final ChunkProvider lightAccess, final int chu // subclasses should not invoke updateVisible, as this will always be done by the super call // needsEdgeChecks applies when possibly loading vanilla data, which means we need to validate the current // chunks light values with respect to neighbours + // subclasses should note that the emptiness changes are propagated BEFORE this is called, so this function + // 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) { + 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); // force current chunk into cache final Chunk chunk = (Chunk)lightAccess.getChunk(chunkX, chunkZ); @@ -553,6 +671,7 @@ public final void light(final ChunkProvider lightAccess, final int chunkX, final this.setNibblesForChunkInCache(chunkX, chunkZ, this.getNibblesOnChunk(chunk)); try { + this.handleEmptySectionChanges(lightAccess, chunk, emptySections, true); this.lightChunk(lightAccess, chunk, false); this.updateVisible(lightAccess); } finally { @@ -572,8 +691,9 @@ protected final void relightChunk(final ChunkProvider lightAccess, final Chunk c try { this.setChunkInCache(chunkPos.x, chunkPos.z, chunk); this.setBlocksForChunkInCache(chunkPos.x, chunkPos.z, chunk.getSectionArray()); - final SWMRNibbleArray[] chunkNibbles = getFilledEmptyLight(this.skylightPropagator); + final SWMRNibbleArray[] chunkNibbles = getFilledEmptyLight(); this.setNibblesForChunkInCache(chunkPos.x, chunkPos.z, chunkNibbles); + this.handleEmptySectionChanges(lightAccess, chunk, getEmptySectionsForChunk(chunk), true); this.lightChunk(lightAccess, chunk, false); for (int dz = -1; dz <= 1; ++dz) { @@ -592,7 +712,8 @@ protected final void relightChunk(final ChunkProvider lightAccess, final Chunk c this.setChunkInCache(cx, cz, neighbourChunk); this.setBlocksForChunkInCache(cx, cz, neighbourChunk.getSectionArray()); - this.setNibblesForChunkInCache(cx, cz, getFilledEmptyLight(this.skylightPropagator)); + this.setNibblesForChunkInCache(cx, cz, getFilledEmptyLight()); + this.handleEmptySectionChanges(lightAccess, chunk, getEmptySectionsForChunk(neighbourChunk), true); this.lightChunk(lightAccess, neighbourChunk, false); } } @@ -608,38 +729,42 @@ protected final void relightChunk(final ChunkProvider lightAccess, final Chunk c // this is also the basic algorithm, the optimised algorithm is always going to be tested against this one // and this one is always tested against vanilla // contains: - // lower 21 bits: encoded coordinate position (x | (z << 6) | (y << (6 + 6)))) + // lower (6 + 6 + 16) = 28 bits: encoded coordinate position (x | (z << 6) | (y << (6 + 6)))) // next 4 bits: propagated light level (0, 15] - // next 5 bits: direction propagated from - // next 0 bits: unused - // last 2 bits: state flags + // next 6 bits: propagation direction bitset + // next 23 bits: unused + // last 3 bits: state flags // state flags: + // whether the propagation must set the current position's light value (0 if decrease, propagated light level if increase) + // used when wanting to delay the setting of a level + // this will also re-schedule the queued value, so it will be shuffled to the end of the queue + protected static final long FLAG_FORCE_WRITE = Long.MIN_VALUE >>> 2; // whether the propagation needs to check if its current level is equal to the expected level // used only in increase propagation - protected static final int FLAG_RECHECK_LEVEL = Integer.MIN_VALUE >>> 1; + protected static final long FLAG_RECHECK_LEVEL = Long.MIN_VALUE >>> 1; // whether the propagation needs to consider if its block is conditionally transparent - protected static final int FLAG_HAS_SIDED_TRANSPARENT_BLOCKS = Integer.MIN_VALUE; + protected static final long FLAG_HAS_SIDED_TRANSPARENT_BLOCKS = Long.MIN_VALUE; - protected final int[] increaseQueue = new int[16 * 16 * (16 * (16 + 2)) * 9 + 1]; + protected final long[] increaseQueue = new long[16 * 16 * (16 * (16 + 2)) * 9 + 1]; protected int increaseQueueInitialLength; - protected final int[] decreaseQueue = new int[16 * 16 * (16 * (16 + 2)) * 9 + 1]; + protected final long[] decreaseQueue = new long[16 * 16 * (16 * (16 + 2)) * 9 + 1]; protected int decreaseQueueInitialLength; - protected static final AxisDirection[][] OLD_CHECK_DIRECTIONS = new AxisDirection[4 * 8][]; + protected static final AxisDirection[][] OLD_CHECK_DIRECTIONS = new AxisDirection[1 << 6][]; + protected static final int ALL_DIRECTIONS_BITSET = (1 << 6) - 1; static { - for (int i = 0; i < AXIS_DIRECTIONS.length; ++i) { - final AxisDirection direction = AXIS_DIRECTIONS[i]; - final List directions = new ArrayList<>(Arrays.asList(AXIS_DIRECTIONS)); - directions.remove(direction.getOpposite()); - OLD_CHECK_DIRECTIONS[direction.ordinal()] = directions.toArray(new AxisDirection[0]); - OLD_CHECK_DIRECTIONS[direction.ordinal() | 8] = AXIS_DIRECTIONS; // flag ALL_DIRECTIONS - OLD_CHECK_DIRECTIONS[direction.ordinal() | 16] = new AxisDirection[] { direction }; // flag ONLY_THIS_DIRECTION + for (int i = 0; i < OLD_CHECK_DIRECTIONS.length; ++i) { + final List directions = new ArrayList<>(); + for (int bitset = i, len = Integer.bitCount(i), index = 0; index < len; ++index, bitset ^= IntegerUtil.getTrailingBit(bitset)) { + directions.add(AXIS_DIRECTIONS[IntegerUtil.trailingZeros(bitset)]); + } + OLD_CHECK_DIRECTIONS[i] = directions.toArray(new AxisDirection[0]); } } protected final void performLightIncrease(final ChunkProvider lightAccess) { final BlockView world = lightAccess.getWorld(); - final int[] queue = this.increaseQueue; + final long[] queue = this.increaseQueue; int queueReadIndex = 0; int queueLength = this.increaseQueueInitialLength; this.increaseQueueInitialLength = 0; @@ -650,23 +775,27 @@ protected final void performLightIncrease(final ChunkProvider lightAccess) { final int sectionOffset = this.chunkSectionIndexOffset; while (queueReadIndex < queueLength) { - final int queueValue = queue[queueReadIndex++]; + final long queueValue = queue[queueReadIndex++]; - final int posX = ((queueValue & 63) + decodeOffsetX); - final int posZ = (((queueValue >>> 6) & 63) + decodeOffsetZ); - final int posY = (((queueValue >>> 12) & 511) + decodeOffsetY); - final int propagatedLightLevel = ((queueValue >>> (6 + 6 + 9)) & 0xF); - final int fromDirection = ((queueValue >>> (6 + 6 + 9 + 4)) & 0x1F); - final AxisDirection[] checkDirections = OLD_CHECK_DIRECTIONS[fromDirection]; + final int posX = ((int)queueValue & 63) + decodeOffsetX; + final int posZ = (((int)queueValue >>> 6) & 63) + decodeOffsetZ; + final int posY = (((int)queueValue >>> 12) & ((1 << 16) - 1)) + decodeOffsetY; + final int propagatedLightLevel = (int)((queueValue >>> (6 + 6 + 16)) & 0xFL); + final AxisDirection[] checkDirections = OLD_CHECK_DIRECTIONS[(int)((queueValue >>> (6 + 6 + 16 + 4)) & 63L)]; - if ((queueValue & FLAG_RECHECK_LEVEL) != 0) { + if ((queueValue & FLAG_RECHECK_LEVEL) != 0L) { if (this.getLightLevel(posX, posY, posZ) != propagatedLightLevel) { // not at the level we expect, so something changed. continue; } } + if ((queueValue & FLAG_FORCE_WRITE) != 0L) { + this.setLightLevel(posX, posY, posZ, propagatedLightLevel); + queue[queueLength++] = queueValue ^ FLAG_FORCE_WRITE; + continue; + } - if ((queueValue & FLAG_HAS_SIDED_TRANSPARENT_BLOCKS) == 0) { + if ((queueValue & FLAG_HAS_SIDED_TRANSPARENT_BLOCKS) == 0L) { // we don't need to worry about our state here. for (final AxisDirection propagate : checkDirections) { final int offX = posX + propagate.x; @@ -676,34 +805,37 @@ protected final void performLightIncrease(final ChunkProvider lightAccess) { final int sectionIndex = (offX >> 4) + 5 * (offZ >> 4) + (5 * 5) * (offY >> 4) + sectionOffset; final int localIndex = (offX & 15) | ((offZ & 15) << 4) | ((offY & 15) << 8); - final int currentLevel = this.getLightLevel(sectionIndex, localIndex); - - if (currentLevel >= (propagatedLightLevel - 1)) { - continue; // already at the level we want + final SWMRNibbleArray currentNibble = this.nibbleCache[sectionIndex]; + final int currentLevel; + if (currentNibble == null || (currentLevel = currentNibble.getUpdating(localIndex)) >= (propagatedLightLevel - 1)) { + continue; // already at the level we want or unloaded } final BlockState blockState = this.getBlockState(sectionIndex, localIndex); if (blockState == null) { continue; } - final int opacityCached = ((LightAccessBlockState)blockState).getOpacityIfCached(); + final int opacityCached = ((ExtendedAbstractBlockState)blockState).getOpacityIfCached(); if (opacityCached != -1) { final int targetLevel = propagatedLightLevel - Math.max(1, opacityCached); if (targetLevel > currentLevel) { - this.setLightLevel(sectionIndex, localIndex, targetLevel); + + currentNibble.set(localIndex, targetLevel); + this.postLightUpdate(offX, offY, offZ); + if (targetLevel > 1) { queue[queueLength++] = - (offX + (offZ << 6) + (offY << 12) + encodeOffset) - | (targetLevel << (6 + 6 + 9)) - | (propagate.ordinal() << (6 + 6 + 9 + 4)); + ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) + | ((targetLevel & 0xFL) << (6 + 6 + 16)) + | (propagate.everythingButTheOppositeDirection << (6 + 6 + 16 + 4)); continue; } } continue; } else { this.mutablePos1.set(offX, offY, offZ); - int flags = 0; - if (((LightAccessBlockState)blockState).isConditionallyFullOpaque()) { + long flags = 0; + if (((ExtendedAbstractBlockState)blockState).isConditionallyFullOpaque()) { final VoxelShape cullingFace = blockState.getCullingFace(world, this.mutablePos1, propagate.getOpposite().nms); if (VoxelShapes.unionCoversFullCube(VoxelShapes.empty(), cullingFace)) { @@ -717,12 +849,15 @@ protected final void performLightIncrease(final ChunkProvider lightAccess) { if (targetLevel <= currentLevel) { continue; } - this.setLightLevel(sectionIndex, localIndex, targetLevel); + + currentNibble.set(localIndex, targetLevel); + this.postLightUpdate(offX, offY, offZ); + if (targetLevel > 1) { queue[queueLength++] = - (offX + (offZ << 6) + (offY << 12) + encodeOffset) - | (targetLevel << (6 + 6 + 9)) - | (propagate.ordinal() << (6 + 6 + 9 + 4)) + ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) + | ((targetLevel & 0xFL) << (6 + 6 + 16)) + | (propagate.everythingButTheOppositeDirection << (6 + 6 + 16 + 4)) | (flags); } continue; @@ -737,7 +872,7 @@ protected final void performLightIncrease(final ChunkProvider lightAccess) { final int offY = posY + propagate.y; final int offZ = posZ + propagate.z; - final VoxelShape fromShape = (((LightAccessBlockState)fromBlock).isConditionallyFullOpaque()) ? fromBlock.getCullingFace(world, this.mutablePos2, propagate.nms) : VoxelShapes.empty(); + final VoxelShape fromShape = (((ExtendedAbstractBlockState)fromBlock).isConditionallyFullOpaque()) ? fromBlock.getCullingFace(world, this.mutablePos2, propagate.nms) : VoxelShapes.empty(); if (fromShape != VoxelShapes.empty() && VoxelShapes.unionCoversFullCube(VoxelShapes.empty(), fromShape)) { continue; @@ -746,9 +881,10 @@ protected final void performLightIncrease(final ChunkProvider lightAccess) { final int sectionIndex = (offX >> 4) + 5 * (offZ >> 4) + (5 * 5) * (offY >> 4) + sectionOffset; final int localIndex = (offX & 15) | ((offZ & 15) << 4) | ((offY & 15) << 8); - final int currentLevel = this.getLightLevel(sectionIndex, localIndex); + final SWMRNibbleArray currentNibble = this.nibbleCache[sectionIndex]; + final int currentLevel; - if (currentLevel >= (propagatedLightLevel - 1)) { + if (currentNibble == null || (currentLevel = currentNibble.getUpdating(localIndex)) >= (propagatedLightLevel - 1)) { continue; // already at the level we want } @@ -756,24 +892,27 @@ protected final void performLightIncrease(final ChunkProvider lightAccess) { if (blockState == null) { continue; } - final int opacityCached = ((LightAccessBlockState)blockState).getOpacityIfCached(); + final int opacityCached = ((ExtendedAbstractBlockState)blockState).getOpacityIfCached(); if (opacityCached != -1) { final int targetLevel = propagatedLightLevel - Math.max(1, opacityCached); if (targetLevel > currentLevel) { - this.setLightLevel(sectionIndex, localIndex, targetLevel); + + currentNibble.set(localIndex, targetLevel); + this.postLightUpdate(offX, offY, offZ); + if (targetLevel > 1) { queue[queueLength++] = - (offX + (offZ << 6) + (offY << 12) + encodeOffset) - | (targetLevel << (6 + 6 + 9)) - | (propagate.ordinal() << (6 + 6 + 9 + 4)); + ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) + | ((targetLevel & 0xFL) << (6 + 6 + 16)) + | (propagate.everythingButTheOppositeDirection << (6 + 6 + 16 + 4)); continue; } } continue; } else { this.mutablePos1.set(offX, offY, offZ); - int flags = 0; - if (((LightAccessBlockState)blockState).isConditionallyFullOpaque()) { + long flags = 0; + if (((ExtendedAbstractBlockState)blockState).isConditionallyFullOpaque()) { final VoxelShape cullingFace = blockState.getCullingFace(world, this.mutablePos1, propagate.getOpposite().nms); if (VoxelShapes.unionCoversFullCube(fromShape, cullingFace)) { @@ -787,12 +926,15 @@ protected final void performLightIncrease(final ChunkProvider lightAccess) { if (targetLevel <= currentLevel) { continue; } - this.setLightLevel(sectionIndex, localIndex, targetLevel); + + currentNibble.set(localIndex, targetLevel); + this.postLightUpdate(offX, offY, offZ); + if (targetLevel > 1) { queue[queueLength++] = - (offX + (offZ << 6) + (offY << 12) + encodeOffset) - | (targetLevel << (6 + 6 + 9)) - | (propagate.ordinal() << (6 + 6 + 9 + 4)) + ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) + | ((targetLevel & 0xFL) << (6 + 6 + 16)) + | (propagate.everythingButTheOppositeDirection << (6 + 6 + 16 + 4)) | (flags); } continue; @@ -804,8 +946,8 @@ protected final void performLightIncrease(final ChunkProvider lightAccess) { protected final void performLightDecrease(final ChunkProvider lightAccess) { final BlockView world = lightAccess.getWorld(); - final int[] queue = this.decreaseQueue; - final int[] increaseQueue = this.increaseQueue; + final long[] queue = this.decreaseQueue; + final long[] increaseQueue = this.increaseQueue; int queueReadIndex = 0; int queueLength = this.decreaseQueueInitialLength; this.decreaseQueueInitialLength = 0; @@ -818,16 +960,21 @@ protected final void performLightDecrease(final ChunkProvider lightAccess) { final int emittedMask = this.emittedLightMask; while (queueReadIndex < queueLength) { - final int queueValue = queue[queueReadIndex++]; - - final int posX = ((queueValue & 63) + decodeOffsetX); - final int posZ = (((queueValue >>> 6) & 63) + decodeOffsetZ); - final int posY = (((queueValue >>> 12) & 511) + decodeOffsetY); - final int propagatedLightLevel = ((queueValue >>> (6 + 6 + 9)) & 0xF); - final int fromDirection = ((queueValue >>> (6 + 6 + 9 + 4)) & 0x1F); - final AxisDirection[] checkDirections = OLD_CHECK_DIRECTIONS[fromDirection]; + final long queueValue = queue[queueReadIndex++]; + + final int posX = ((int)queueValue & 63) + decodeOffsetX; + final int posZ = (((int)queueValue >>> 6) & 63) + decodeOffsetZ; + final int posY = (((int)queueValue >>> 12) & ((1 << 16) - 1)) + decodeOffsetY; + final int propagatedLightLevel = (int)((queueValue >>> (6 + 6 + 16)) & 0xF); + final AxisDirection[] checkDirections = OLD_CHECK_DIRECTIONS[(int)((queueValue >>> (6 + 6 + 16 + 4)) & 63)]; + + if ((queueValue & FLAG_FORCE_WRITE) != 0L) { + this.setLightLevel(posX, posY, posZ, 0); + queue[queueLength++] = queueValue ^ FLAG_FORCE_WRITE; + continue; + } - if ((queueValue & FLAG_HAS_SIDED_TRANSPARENT_BLOCKS) == 0) { + if ((queueValue & FLAG_HAS_SIDED_TRANSPARENT_BLOCKS) == 0L) { // we don't need to worry about our state here. for (final AxisDirection propagate : checkDirections) { final int offX = posX + propagate.x; @@ -837,24 +984,27 @@ protected final void performLightDecrease(final ChunkProvider lightAccess) { final int sectionIndex = (offX >> 4) + 5 * (offZ >> 4) + (5 * 5) * (offY >> 4) + sectionOffset; final int localIndex = (offX & 15) | ((offZ & 15) << 4) | ((offY & 15) << 8); - final int lightLevel = this.getLightLevel(sectionIndex, localIndex); + final SWMRNibbleArray currentNibble = this.nibbleCache[sectionIndex]; + final int lightLevel; - if (lightLevel == 0) { - // already at lowest, nothing we can do + if (currentNibble == null || (lightLevel = currentNibble.getUpdating(localIndex)) == 0) { + // already at lowest (or unloaded), nothing we can do continue; } final BlockState blockState = this.getBlockState(sectionIndex, localIndex); - final int opacityCached = ((LightAccessBlockState)blockState).getOpacityIfCached(); - // no null check, blockState cannot be null if level != 0 + if (blockState == null) { + continue; + } + final int opacityCached = ((ExtendedAbstractBlockState)blockState).getOpacityIfCached(); if (opacityCached != -1) { final int targetLevel = Math.max(0, propagatedLightLevel - Math.max(1, opacityCached)); if (lightLevel > targetLevel) { // it looks like another source propagated here, so re-propagate it increaseQueue[increaseQueueLength++] = - (offX + (offZ << 6) + (offY << 12) + encodeOffset) - | (lightLevel << (6 + 6 + 9)) - | ((propagate.ordinal() | 8) << (6 + 6 + 9 + 4)) + ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) + | ((lightLevel & 0xFL) << (6 + 6 + 16)) + | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) | FLAG_RECHECK_LEVEL; continue; } @@ -862,24 +1012,27 @@ protected final void performLightDecrease(final ChunkProvider lightAccess) { if (emittedLight != 0) { // re-propagate source increaseQueue[increaseQueueLength++] = - (offX + (offZ << 6) + (offY << 12) + encodeOffset) - | (emittedLight << (6 + 6 + 9)) - | ((propagate.ordinal() | 8) << (6 + 6 + 9 + 4)) - | (((LightAccessBlockState)blockState).isConditionallyFullOpaque() ? FLAG_HAS_SIDED_TRANSPARENT_BLOCKS : 0); + ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((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 : 0L); } - this.setLightLevel(sectionIndex, localIndex, emittedLight); + + currentNibble.set(localIndex, emittedLight); + this.postLightUpdate(offX, offY, offZ); + if (targetLevel > 0) { // we actually need to propagate 0 just in case we find a neighbour... queue[queueLength++] = - (offX + (offZ << 6) + (offY << 12) + encodeOffset) - | (targetLevel << (6 + 6 + 9)) - | (propagate.ordinal() << (6 + 6 + 9 + 4)); + ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) + | ((targetLevel & 0xFL) << (6 + 6 + 16)) + | ((propagate.everythingButTheOppositeDirection) << (6 + 6 + 16 + 4)); continue; } continue; } else { this.mutablePos1.set(offX, offY, offZ); - int flags = 0; - if (((LightAccessBlockState)blockState).isConditionallyFullOpaque()) { + long flags = 0; + if (((ExtendedAbstractBlockState)blockState).isConditionallyFullOpaque()) { final VoxelShape cullingFace = blockState.getCullingFace(world, this.mutablePos1, propagate.getOpposite().nms); if (VoxelShapes.unionCoversFullCube(VoxelShapes.empty(), cullingFace)) { @@ -893,9 +1046,9 @@ protected final void performLightDecrease(final ChunkProvider lightAccess) { if (lightLevel > targetLevel) { // it looks like another source propagated here, so re-propagate it increaseQueue[increaseQueueLength++] = - (offX + (offZ << 6) + (offY << 12) + encodeOffset) - | (lightLevel << (6 + 6 + 9)) - | ((propagate.ordinal() | 8) << (6 + 6 + 9 + 4)) + ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) + | ((lightLevel & 0xFL) << (6 + 6 + 16)) + | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) | (FLAG_RECHECK_LEVEL | flags); continue; } @@ -903,17 +1056,20 @@ protected final void performLightDecrease(final ChunkProvider lightAccess) { if (emittedLight != 0) { // re-propagate source increaseQueue[increaseQueueLength++] = - (offX + (offZ << 6) + (offY << 12) + encodeOffset) - | (emittedLight << (6 + 6 + 9)) - | ((propagate.ordinal() | 8) << (6 + 6 + 9 + 4)) + ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) + | ((emittedLight & 0xFL) << (6 + 6 + 16)) + | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) | flags; } - this.setLightLevel(sectionIndex, localIndex, emittedLight); + + currentNibble.set(localIndex, emittedLight); + this.postLightUpdate(offX, offY, offZ); + if (targetLevel > 0) { queue[queueLength++] = - (offX + (offZ << 6) + (offY << 12) + encodeOffset) - | (targetLevel << (6 + 6 + 9)) - | (propagate.ordinal() << (6 + 6 + 9 + 4)) + ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) + | ((targetLevel & 0xFL) << (6 + 6 + 16)) + | ((propagate.everythingButTheOppositeDirection) << (6 + 6 + 16 + 4)) | flags; } continue; @@ -931,56 +1087,62 @@ protected final void performLightDecrease(final ChunkProvider lightAccess) { final int sectionIndex = (offX >> 4) + 5 * (offZ >> 4) + (5 * 5) * (offY >> 4) + sectionOffset; final int localIndex = (offX & 15) | ((offZ & 15) << 4) | ((offY & 15) << 8); - final VoxelShape fromShape = (((LightAccessBlockState)fromBlock).isConditionallyFullOpaque()) ? fromBlock.getCullingFace(world, this.mutablePos2, propagate.nms) : VoxelShapes.empty(); + final VoxelShape fromShape = (((ExtendedAbstractBlockState)fromBlock).isConditionallyFullOpaque()) ? fromBlock.getCullingFace(world, this.mutablePos2, propagate.nms) : VoxelShapes.empty(); if (fromShape != VoxelShapes.empty() && VoxelShapes.unionCoversFullCube(VoxelShapes.empty(), fromShape)) { continue; } - final int lightLevel = this.getLightLevel(sectionIndex, localIndex); + final SWMRNibbleArray currentNibble = this.nibbleCache[sectionIndex]; + final int lightLevel; - if (lightLevel == 0) { - // already at lowest, nothing we can do + if (currentNibble == null || (lightLevel = currentNibble.getUpdating(localIndex)) == 0) { + // already at lowest (or unloaded), nothing we can do continue; } - final BlockState blockData = this.getBlockState(sectionIndex, localIndex); - final int opacityCached = ((LightAccessBlockState)blockData).getOpacityIfCached(); - // no null check, blockData cannot be null if level != 0 + final BlockState blockState = this.getBlockState(sectionIndex, localIndex); + if (blockState == null) { + continue; + } + final int opacityCached = ((ExtendedAbstractBlockState)blockState).getOpacityIfCached(); if (opacityCached != -1) { final int targetLevel = Math.max(0, propagatedLightLevel - Math.max(1, opacityCached)); if (lightLevel > targetLevel) { // it looks like another source propagated here, so re-propagate it increaseQueue[increaseQueueLength++] = - (offX + (offZ << 6) + (offY << 12) + encodeOffset) - | (lightLevel << (6 + 6 + 9)) - | ((propagate.ordinal() | 8) << (6 + 6 + 9 + 4)) + ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) + | ((lightLevel & 0xFL) << (6 + 6 + 16)) + | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) | FLAG_RECHECK_LEVEL; continue; } - final int emittedLight = blockData.getLuminance() & emittedMask; + final int emittedLight = blockState.getLuminance() & emittedMask; if (emittedLight != 0) { // re-propagate source increaseQueue[increaseQueueLength++] = - (offX + (offZ << 6) + (offY << 12) + encodeOffset) - | (emittedLight << (6 + 6 + 9)) - | ((propagate.ordinal() | 8) << (6 + 6 + 9 + 4)) - | (((LightAccessBlockState)blockData).isConditionallyFullOpaque() ? FLAG_HAS_SIDED_TRANSPARENT_BLOCKS : 0); + ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((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 : 0L); } - this.setLightLevel(sectionIndex, localIndex, emittedLight); + + currentNibble.set(localIndex, emittedLight); + this.postLightUpdate(offX, offY, offZ); + if (targetLevel > 0) { // we actually need to propagate 0 just in case we find a neighbour... queue[queueLength++] = - (offX + (offZ << 6) + (offY << 12) + encodeOffset) - | (targetLevel << (6 + 6 + 9)) - | (propagate.ordinal() << (6 + 6 + 9 + 4)); + ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) + | ((targetLevel & 0xFL) << (6 + 6 + 16)) + | ((propagate.everythingButTheOppositeDirection) << (6 + 6 + 16 + 4)); continue; } continue; } else { this.mutablePos1.set(offX, offY, offZ); - int flags = 0; - if (((LightAccessBlockState)blockData).isConditionallyFullOpaque()) { - final VoxelShape cullingFace = blockData.getCullingFace(world, this.mutablePos1, propagate.getOpposite().nms); + long flags = 0; + if (((ExtendedAbstractBlockState)blockState).isConditionallyFullOpaque()) { + final VoxelShape cullingFace = blockState.getCullingFace(world, this.mutablePos1, propagate.getOpposite().nms); if (VoxelShapes.unionCoversFullCube(fromShape, cullingFace)) { continue; @@ -988,32 +1150,35 @@ protected final void performLightDecrease(final ChunkProvider lightAccess) { flags |= FLAG_HAS_SIDED_TRANSPARENT_BLOCKS; } - final int opacity = blockData.getOpacity(world, this.mutablePos1); + final int opacity = blockState.getOpacity(world, this.mutablePos1); final int targetLevel = Math.max(0, propagatedLightLevel - Math.max(1, opacity)); if (lightLevel > targetLevel) { // it looks like another source propagated here, so re-propagate it increaseQueue[increaseQueueLength++] = - (offX + (offZ << 6) + (offY << 12) + encodeOffset) - | (lightLevel << (6 + 6 + 9)) - | ((propagate.ordinal() | 8) << (6 + 6 + 9 + 4)) + ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) + | ((lightLevel & 0xFL) << (6 + 6 + 16)) + | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) | (FLAG_RECHECK_LEVEL | flags); continue; } - final int emittedLight = blockData.getLuminance() & emittedMask; + final int emittedLight = blockState.getLuminance() & emittedMask; if (emittedLight != 0) { // re-propagate source increaseQueue[increaseQueueLength++] = - (offX + (offZ << 6) + (offY << 12) + encodeOffset) - | (emittedLight << (6 + 6 + 9)) - | ((propagate.ordinal() | 8) << (6 + 6 + 9 + 4)) + ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) + | ((emittedLight & 0xFL) << (6 + 6 + 16)) + | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) | flags; } - this.setLightLevel(sectionIndex, localIndex, emittedLight); + + currentNibble.set(localIndex, emittedLight); + this.postLightUpdate(offX, offY, offZ); + if (targetLevel > 0) { // we actually need to propagate 0 just in case we find a neighbour... queue[queueLength++] = - (offX + (offZ << 6) + (offY << 12) + encodeOffset) - | (targetLevel << (6 + 6 + 9)) - | (propagate.ordinal() << (6 + 6 + 9 + 4)) + ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) + | ((targetLevel & 0xFL) << (6 + 6 + 16)) + | ((propagate.everythingButTheOppositeDirection) << (6 + 6 + 16 + 4)) | flags; } continue; @@ -1026,5 +1191,4 @@ protected final void performLightDecrease(final ChunkProvider lightAccess) { this.increaseQueueInitialLength = increaseQueueLength; this.performLightIncrease(lightAccess); } - } 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 8c985280..6c02fa3e 100644 --- a/src/main/java/ca/spottedleaf/starlight/common/light/StarLightInterface.java +++ b/src/main/java/ca/spottedleaf/starlight/common/light/StarLightInterface.java @@ -1,11 +1,10 @@ package ca.spottedleaf.starlight.common.light; -import ca.spottedleaf.starlight.common.chunk.ThreadedAnvilChunkStorageMethods; -import ca.spottedleaf.starlight.common.chunk.NibbledChunk; +import ca.spottedleaf.starlight.common.chunk.ExtendedChunk; import ca.spottedleaf.starlight.common.util.CoordinateUtils; +import ca.spottedleaf.starlight.common.world.ExtendedWorld; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; -import net.minecraft.server.world.ChunkHolder; import net.minecraft.server.world.ChunkTicketType; import net.minecraft.server.world.ServerWorld; import net.minecraft.util.math.BlockPos; @@ -14,9 +13,9 @@ import net.minecraft.world.chunk.Chunk; import net.minecraft.world.chunk.ChunkNibbleArray; import net.minecraft.world.chunk.ChunkProvider; +import net.minecraft.world.chunk.ChunkSection; import net.minecraft.world.chunk.ChunkStatus; import net.minecraft.world.chunk.light.ChunkLightingView; -import org.jetbrains.annotations.Nullable; import java.util.ArrayDeque; import java.util.HashSet; import java.util.Iterator; @@ -26,13 +25,16 @@ public final class StarLightInterface { public static final ChunkTicketType CHUNK_WORK_TICKET = ChunkTicketType.create("starlight_chunk_work_ticket", Long::compareTo); + /** + * Can be {@code null}, indicating the light is all empty. + */ protected final World world; protected final ChunkProvider lightAccess; protected final ArrayDeque cachedSkyPropagators; protected final ArrayDeque cachedBlockPropagators; - protected final Long2ObjectOpenHashMap> changedBlocks = new Long2ObjectOpenHashMap<>(); + protected final Long2ObjectOpenHashMap changedBlocks = new Long2ObjectOpenHashMap<>(); protected final ChunkLightingView skyReader; protected final ChunkLightingView blockReader; @@ -40,65 +42,138 @@ public final class StarLightInterface { public StarLightInterface(final ChunkProvider lightAccess, final boolean hasSkyLight, final boolean hasBlockLight) { this.lightAccess = lightAccess; - this.world = (World)lightAccess.getWorld(); - this.cachedSkyPropagators = hasSkyLight ? new ArrayDeque<>() : null; - this.cachedBlockPropagators = hasBlockLight ? new ArrayDeque<>() : null; - this.isClientSide = !(world instanceof ServerWorld); + this.world = lightAccess == null ? null : (World)lightAccess.getWorld(); + this.cachedSkyPropagators = hasSkyLight && lightAccess != null ? new ArrayDeque<>() : null; + this.cachedBlockPropagators = hasBlockLight && lightAccess != null ? new ArrayDeque<>() : null; + this.isClientSide = !(this.world instanceof ServerWorld); this.skyReader = !hasSkyLight ? ChunkLightingView.Empty.INSTANCE : new ChunkLightingView() { @Override - public @Nullable ChunkNibbleArray getLightSection(ChunkSectionPos pos) { - Chunk chunk = StarLightInterface.this.getAnyChunkNow(pos.getX(), pos.getZ()); - return chunk != null ? ((NibbledChunk)chunk).getSkyNibbles()[pos.getY() + 1].asNibble() : null; + public ChunkNibbleArray getLightSection(final ChunkSectionPos pos) { + final Chunk chunk = StarLightInterface.this.getAnyChunkNow(pos.getX(), pos.getZ()); + if (chunk == null || (!StarLightInterface.this.isClientSide && !chunk.isLightOn()) || !chunk.getStatus().isAtLeast(ChunkStatus.LIGHT)) { + return null; + } + + final int sectionY = pos.getY(); + + if (sectionY > 16 || sectionY < -1) { + return null; + } + + if (((ExtendedChunk)chunk).getEmptinessMap()[ExtendedChunk.getEmptinessMapIndex(0, 0)] == null) { + return null; + } + + return ((ExtendedChunk)chunk).getSkyNibbles()[pos.getY() + 1].toVanillaNibble(); } @Override - public int getLightLevel(BlockPos blockPos) { - int cx = blockPos.getX() >> 4; - int cy = blockPos.getY() >> 4; - int cz = blockPos.getZ() >> 4; - Chunk chunk = StarLightInterface.this.getAnyChunkNow(cx, cz); - if (chunk == null) { + public int getLightLevel(final BlockPos blockPos) { + final Chunk chunk = StarLightInterface.this.getAnyChunkNow(blockPos.getX() >> 4, blockPos.getZ() >> 4); + if (chunk == null || (!StarLightInterface.this.isClientSide && !chunk.isLightOn()) || !chunk.getStatus().isAtLeast(ChunkStatus.LIGHT)) { + return 15; + } + + final int sectionY = blockPos.getY() >> 4; + + if (sectionY > 16) { + return 15; + } else if (sectionY < -1) { + return 0; + } + + final SWMRNibbleArray[] nibbles = ((ExtendedChunk)chunk).getSkyNibbles(); + final SWMRNibbleArray immediate = nibbles[sectionY + 1]; + + if (StarLightInterface.this.isClientSide) { + if (!immediate.isNullNibbleUpdating()) { + return immediate.getUpdating(blockPos.getX(), blockPos.getY(), blockPos.getZ()); + } + } else { + if (!immediate.isNullNibbleVisible()) { + return immediate.getVisible(blockPos.getX(), blockPos.getY(), blockPos.getZ()); + } + } + + final boolean[] emptinessMap = ((ExtendedChunk)chunk).getEmptinessMap()[ExtendedChunk.getEmptinessMapIndex(0, 0)]; + + if (emptinessMap == null) { return 15; } - if (cy < -1) { - cy = -1; - } else if (cy > 16) { - cy = 16; + + // are we above this chunk's lowest empty section? + int lowestY = -2; + for (int currY = 15; currY >= 0; --currY) { + if (emptinessMap[currY]) { + continue; + } + + // should always be full lit here + lowestY = currY; + break; } - SWMRNibbleArray nibble = ((NibbledChunk)chunk).getSkyNibbles()[cy + 1]; - return nibble.getVisible(blockPos.getX(), blockPos.getY(), blockPos.getZ()); + + if (sectionY > lowestY) { + return 15; + } + + // this nibble is going to depend solely on the skylight data above it + // find first non-null data above (there does exist one, as we just found it above) + for (int currY = sectionY + 1; currY <= 16; ++currY) { + final SWMRNibbleArray nibble = nibbles[currY + 1]; + if (StarLightInterface.this.isClientSide) { + if (!nibble.isNullNibbleUpdating()) { + return nibble.getUpdating(blockPos.getX(), 0, blockPos.getZ()); + } + } else { + if (!nibble.isNullNibbleVisible()) { + return nibble.getVisible(blockPos.getX(), 0, blockPos.getZ()); + } + } + } + + // should never reach here + return 15; } @Override - public void setSectionStatus(ChunkSectionPos pos, boolean notReady) { + public void setSectionStatus(final ChunkSectionPos pos, final boolean notReady) { return; // don't care. } }; this.blockReader = !hasBlockLight ? ChunkLightingView.Empty.INSTANCE : new ChunkLightingView() { @Override - public @Nullable ChunkNibbleArray getLightSection(ChunkSectionPos pos) { - Chunk chunk = StarLightInterface.this.getAnyChunkNow(pos.getX(), pos.getZ()); - return chunk != null ? ((NibbledChunk)chunk).getBlockNibbles()[pos.getY() + 1].asNibble() : null; + public ChunkNibbleArray getLightSection(ChunkSectionPos pos) { + final Chunk chunk = StarLightInterface.this.getAnyChunkNow(pos.getX(), pos.getZ()); + return chunk != null ? ((ExtendedChunk)chunk).getBlockNibbles()[pos.getY() + 1].toVanillaNibble() : null; } @Override public int getLightLevel(BlockPos blockPos) { - int cx = blockPos.getX() >> 4; - int cy = blockPos.getY() >> 4; - int cz = blockPos.getZ() >> 4; - Chunk chunk = StarLightInterface.this.getAnyChunkNow(cx, cz); - if (chunk == null) { + final int cx = blockPos.getX() >> 4; + final int cy = blockPos.getY() >> 4; + final int cz = blockPos.getZ() >> 4; + + if (cy < -1 || cy > 16) { return 0; } - if (cy < -1 || cy > 16) { + + final Chunk chunk = StarLightInterface.this.getAnyChunkNow(cx, cz); + + if (chunk == null) { return 0; } - SWMRNibbleArray nibble = ((NibbledChunk)chunk).getBlockNibbles()[cy + 1]; - return nibble.getVisible(blockPos.getX(), blockPos.getY(), blockPos.getZ()); + + final SWMRNibbleArray nibble = ((ExtendedChunk)chunk).getBlockNibbles()[cy + 1]; + if (StarLightInterface.this.isClientSide) { + return nibble.getUpdating(blockPos.getX(), blockPos.getY(), blockPos.getZ()); + } else { + return nibble.getVisible(blockPos.getX(), blockPos.getY(), blockPos.getZ()); + } } @Override - public void setSectionStatus(ChunkSectionPos pos, boolean notReady) { + public void setSectionStatus(final ChunkSectionPos pos, final boolean notReady) { return; // don't care. } }; @@ -116,16 +191,12 @@ public boolean isClientSide() { return this.isClientSide; } - public Chunk getAnyChunkNow(int chunkX, int chunkZ) { - if (this.isClientSide) { - return this.world.getChunk(chunkX, chunkZ, ChunkStatus.EMPTY, false); - } else { - ServerWorld world = (ServerWorld)this.world; - ChunkHolder chunkHolder = ((ThreadedAnvilChunkStorageMethods)world.getChunkManager().threadedAnvilChunkStorage) - .getOffMainChunkHolder(CoordinateUtils.getChunkKey(chunkX, chunkZ)); - - return chunkHolder == null ? null : chunkHolder.getCurrentChunk(); + public Chunk getAnyChunkNow(final int chunkX, final int chunkZ) { + if (this.world == null) { + // empty world + return null; } + return ((ExtendedWorld)this.world).getAnyChunkImmediately(chunkX, chunkZ); } public boolean hasUpdates() { @@ -191,28 +262,61 @@ protected final void releaseBlockLightEngine(final BlockStarLightEngine engine) } public void blockChange(BlockPos pos) { - if (pos.getY() < 0 || pos.getY() > 255) { + if (pos.getY() < 0 || pos.getY() > 255 || this.world == null) { // empty world return; } pos = pos.toImmutable(); synchronized (this.changedBlocks) { this.changedBlocks.computeIfAbsent(CoordinateUtils.getChunkKey(pos), (final long keyInMap) -> { - return new HashSet<>(); - }).add(pos); + return new ChunkChanges(); + }).changedPositions.add(pos); } } - public void lightChunk(final int chunkX, final int chunkZ) { + public void sectionChange(final ChunkSectionPos pos, final boolean newEmptyValue) { + if (this.world == null) { // empty world + return; + } + + synchronized (this.changedBlocks) { + final ChunkChanges changes = this.changedBlocks.computeIfAbsent(CoordinateUtils.getChunkKey(pos), (final long keyInMap) -> { + return new ChunkChanges(); + }); + if (changes.changedSectionSet == null) { + changes.changedSectionSet = new Boolean[16]; + } + changes.changedSectionSet[pos.getY()] = Boolean.valueOf(newEmptyValue); + } + } + + public void loadInChunk(final int chunkX, final int chunkZ, final Boolean[] emptySections) { final SkyStarLightEngine skyEngine = this.getSkyLightEngine(); final BlockStarLightEngine blockEngine = this.getBlockLightEngine(); try { if (skyEngine != null) { - skyEngine.light(this.lightAccess, chunkX, chunkZ); + skyEngine.handleEmptySectionChanges(this.lightAccess, chunkX, chunkZ, emptySections); } if (blockEngine != null) { - blockEngine.light(this.lightAccess, chunkX, chunkZ); + blockEngine.handleEmptySectionChanges(this.lightAccess, chunkX, chunkZ, emptySections); + } + } finally { + this.releaseSkyLightEngine(skyEngine); + this.releaseBlockLightEngine(blockEngine); + } + } + + public void lightChunk(final int chunkX, final int chunkZ, final Boolean[] emptySections) { + final SkyStarLightEngine skyEngine = this.getSkyLightEngine(); + final BlockStarLightEngine blockEngine = this.getBlockLightEngine(); + + try { + if (skyEngine != null) { + skyEngine.light(this.lightAccess, chunkX, chunkZ, emptySections); + } + if (blockEngine != null) { + blockEngine.light(this.lightAccess, chunkX, chunkZ, emptySections); } } finally { this.releaseSkyLightEngine(skyEngine); @@ -265,22 +369,27 @@ public void propagateChanges() { try { // TODO be smarter about this in the future - final Long2ObjectOpenHashMap> changedBlocks; + final Long2ObjectOpenHashMap changedBlocks; synchronized (this.changedBlocks) { changedBlocks = this.changedBlocks.clone(); this.changedBlocks.clear(); } - for (final Iterator>> iterator = changedBlocks.long2ObjectEntrySet().fastIterator(); iterator.hasNext();) { - final Long2ObjectMap.Entry> entry = iterator.next(); + for (final Iterator> iterator = changedBlocks.long2ObjectEntrySet().fastIterator(); iterator.hasNext();) { + final Long2ObjectMap.Entry entry = iterator.next(); final long coordinate = entry.getLongKey(); - final Set positions = entry.getValue(); + final ChunkChanges changes = entry.getValue(); + final Set positions = changes.changedPositions; + final Boolean[] sectionChanges = changes.changedSectionSet; + + final int chunkX = CoordinateUtils.getChunkX(coordinate); + final int chunkZ = CoordinateUtils.getChunkZ(coordinate); if (skyEngine != null) { - skyEngine.blocksChangedInChunk(this.lightAccess, CoordinateUtils.getChunkX(coordinate), CoordinateUtils.getChunkZ(coordinate), positions); + skyEngine.blocksChangedInChunk(this.lightAccess, chunkX, chunkZ, positions, sectionChanges); } if (blockEngine != null) { - blockEngine.blocksChangedInChunk(this.lightAccess, CoordinateUtils.getChunkX(coordinate), CoordinateUtils.getChunkZ(coordinate), positions); + blockEngine.blocksChangedInChunk(this.lightAccess, chunkX, chunkZ, positions, sectionChanges); } } } finally { @@ -288,4 +397,15 @@ public void propagateChanges() { this.releaseBlockLightEngine(blockEngine); } } + + protected static final class ChunkChanges { + + // note: on the main thread, empty section changes are queued before block changes. This means we don't need + // to worry about cases where a block change is called inside an empty chunk section, according to the "emptiness" map per chunk, + // for example. + public final Set changedPositions = new HashSet<>(); + + public Boolean[] changedSectionSet; + + } } diff --git a/src/main/java/ca/spottedleaf/starlight/common/util/BlockUtils.java b/src/main/java/ca/spottedleaf/starlight/common/util/BlockUtils.java deleted file mode 100644 index 906d6f8c..00000000 --- a/src/main/java/ca/spottedleaf/starlight/common/util/BlockUtils.java +++ /dev/null @@ -1,8 +0,0 @@ -package ca.spottedleaf.starlight.common.util; - -public final class BlockUtils { - - private BlockUtils() { - throw new RuntimeException(); - } -} diff --git a/src/main/java/ca/spottedleaf/starlight/common/util/IntegerUtil.java b/src/main/java/ca/spottedleaf/starlight/common/util/IntegerUtil.java new file mode 100644 index 00000000..177d0a96 --- /dev/null +++ b/src/main/java/ca/spottedleaf/starlight/common/util/IntegerUtil.java @@ -0,0 +1,226 @@ +package ca.spottedleaf.starlight.common.util; + +public final class IntegerUtil { + + public static final int HIGH_BIT_U32 = Integer.MIN_VALUE; + public static final long HIGH_BIT_U64 = Long.MIN_VALUE; + + public static int ceilLog2(final int value) { + return Integer.SIZE - Integer.numberOfLeadingZeros(value - 1); // see doc of numberOfLeadingZeros + } + + public static long ceilLog2(final long value) { + return Long.SIZE - Long.numberOfLeadingZeros(value - 1); // see doc of numberOfLeadingZeros + } + + public static int floorLog2(final int value) { + // xor is optimized subtract for 2^n -1 + // note that (2^n -1) - k = (2^n -1) ^ k for k <= (2^n - 1) + return (Integer.SIZE - 1) ^ Integer.numberOfLeadingZeros(value); // see doc of numberOfLeadingZeros + } + + public static int floorLog2(final long value) { + // xor is optimized subtract for 2^n -1 + // note that (2^n -1) - k = (2^n -1) ^ k for k <= (2^n - 1) + return (Long.SIZE - 1) ^ Long.numberOfLeadingZeros(value); // see doc of numberOfLeadingZeros + } + + public static int roundCeilLog2(final int value) { + // optimized variant of 1 << (32 - leading(val - 1)) + // given + // 1 << n = HIGH_BIT_32 >>> (31 - n) for n [0, 32) + // 1 << (32 - leading(val - 1)) = HIGH_BIT_32 >>> (31 - (32 - leading(val - 1))) + // HIGH_BIT_32 >>> (31 - (32 - leading(val - 1))) + // HIGH_BIT_32 >>> (31 - 32 + leading(val - 1)) + // HIGH_BIT_32 >>> (-1 + leading(val - 1)) + return HIGH_BIT_U32 >>> (Integer.numberOfLeadingZeros(value - 1) - 1); + } + + public static long roundCeilLog2(final long value) { + // see logic documented above + return HIGH_BIT_U64 >>> (Long.numberOfLeadingZeros(value - 1) - 1); + } + + public static int roundFloorLog2(final int value) { + // optimized variant of 1 << (31 - leading(val)) + // given + // 1 << n = HIGH_BIT_32 >>> (31 - n) for n [0, 32) + // 1 << (31 - leading(val)) = HIGH_BIT_32 >> (31 - (31 - leading(val))) + // HIGH_BIT_32 >> (31 - (31 - leading(val))) + // HIGH_BIT_32 >> (31 - 31 + leading(val)) + return HIGH_BIT_U32 >>> Integer.numberOfLeadingZeros(value); + } + + public static long roundFloorLog2(final long value) { + // see logic documented above + return HIGH_BIT_U64 >>> Long.numberOfLeadingZeros(value); + } + + public static boolean isPowerOfTwo(final int n) { + // 2^n has one bit + // note: this rets true for 0 still + return IntegerUtil.getTrailingBit(n) == n; + } + + public static boolean isPowerOfTwo(final long n) { + // 2^n has one bit + // note: this rets true for 0 still + return IntegerUtil.getTrailingBit(n) == n; + } + + public static int getTrailingBit(final int n) { + return -n & n; + } + + public static long getTrailingBit(final long n) { + return -n & n; + } + + public static int trailingZeros(final int n) { + return Integer.numberOfTrailingZeros(n); + } + + public static int trailingZeros(final long n) { + return Long.numberOfTrailingZeros(n); + } + + // from hacker's delight (signed division magic value) + public static int getDivisorMultiple(final long numbers) { + return (int)(numbers >>> 32); + } + + // from hacker's delight (signed division magic value) + public static int getDivisorShift(final long numbers) { + return (int)numbers; + } + + // copied from hacker's delight (signed division magic value) + // http://www.hackersdelight.org/hdcodetxt/magic.c.txt + public static long getDivisorNumbers(final int d) { + final int ad = IntegerUtil.branchlessAbs(d); + + if (ad < 2) { + throw new IllegalArgumentException("|number| must be in [2, 2^31 -1], not: " + d); + } + + final int two31 = 0x80000000; + final long mask = 0xFFFFFFFFL; // mask for enforcing unsigned behaviour + + int p = 31; + + // all these variables are UNSIGNED! + int t = two31 + (d >>> 31); + int anc = t - 1 - t%ad; + int q1 = (int)((two31 & mask)/(anc & mask)); + int r1 = two31 - q1*anc; + int q2 = (int)((two31 & mask)/(ad & mask)); + int r2 = two31 - q2*ad; + int delta; + + do { + p = p + 1; + q1 = 2*q1; // Update q1 = 2**p/|nc|. + r1 = 2*r1; // Update r1 = rem(2**p, |nc|). + if ((r1 & mask) >= (anc & mask)) {// (Must be an unsigned comparison here) + q1 = q1 + 1; + r1 = r1 - anc; + } + q2 = 2*q2; // Update q2 = 2**p/|d|. + r2 = 2*r2; // Update r2 = rem(2**p, |d|). + if ((r2 & mask) >= (ad & mask)) {// (Must be an unsigned comparison here) + q2 = q2 + 1; + r2 = r2 - ad; + } + delta = ad - r2; + } while ((q1 & mask) < (delta & mask) || (q1 == delta && r1 == 0)); + + int magicNum = q2 + 1; + if (d < 0) { + magicNum = -magicNum; + } + int shift = p - 32; + return ((long)magicNum << 32) | shift; + } + + public static int branchlessAbs(final int val) { + // -n = -1 ^ n + 1 + final int mask = val >> (Integer.SIZE - 1); // -1 if < 0, 0 if >= 0 + return (mask ^ val) - mask; // if val < 0, then (0 ^ val) - 0 else (-1 ^ val) + 1 + } + + public static long branchlessAbs(final long val) { + // -n = -1 ^ n + 1 + final long mask = val >> (Long.SIZE - 1); // -1 if < 0, 0 if >= 0 + return (mask ^ val) - mask; // if val < 0, then (0 ^ val) - 0 else (-1 ^ val) + 1 + } + + //https://github.com/skeeto/hash-prospector for hash functions + + //score = ~590.47984224483832 + public static int hash0(int x) { + x *= 0x36935555; + x ^= x >>> 16; + return x; + } + + //score = ~310.01596637036749 + public static int hash1(int x) { + x ^= x >>> 15; + x *= 0x356aaaad; + x ^= x >>> 17; + return x; + } + + public static int hash2(int x) { + x ^= x >>> 16; + x *= 0x7feb352d; + x ^= x >>> 15; + x *= 0x846ca68b; + x ^= x >>> 16; + return x; + } + + public static int hash3(int x) { + x ^= x >>> 17; + x *= 0xed5ad4bb; + x ^= x >>> 11; + x *= 0xac4c1b51; + x ^= x >>> 15; + x *= 0x31848bab; + x ^= x >>> 14; + return x; + } + + //score = ~365.79959673201887 + public static long hash1(long x) { + x ^= x >>> 27; + x *= 0xb24924b71d2d354bL; + x ^= x >>> 28; + return x; + } + + //h2 hash + public static long hash2(long x) { + x ^= x >>> 32; + x *= 0xd6e8feb86659fd93L; + x ^= x >>> 32; + x *= 0xd6e8feb86659fd93L; + x ^= x >>> 32; + return x; + } + + public static long hash3(long x) { + x ^= x >>> 45; + x *= 0xc161abe5704b6c79L; + x ^= x >>> 41; + x *= 0xe3e5389aedbc90f7L; + x ^= x >>> 56; + x *= 0x1f9aba75a52db073L; + x ^= x >>> 53; + return x; + } + + private IntegerUtil() { + throw new RuntimeException(); + } +} diff --git a/src/main/java/ca/spottedleaf/starlight/common/world/ExtendedWorld.java b/src/main/java/ca/spottedleaf/starlight/common/world/ExtendedWorld.java new file mode 100644 index 00000000..bcd129e9 --- /dev/null +++ b/src/main/java/ca/spottedleaf/starlight/common/world/ExtendedWorld.java @@ -0,0 +1,14 @@ +package ca.spottedleaf.starlight.common.world; + +import net.minecraft.world.chunk.Chunk; +import net.minecraft.world.chunk.WorldChunk; + +public interface ExtendedWorld { + + // rets full chunk without blocking + public WorldChunk getChunkAtImmediately(final int chunkX, final int chunkZ); + + // rets chunk at any stage, if it exists, immediately + public Chunk getAnyChunkImmediately(final int chunkX, final int chunkZ); + +} diff --git a/src/main/java/ca/spottedleaf/starlight/mixin/blockstate/AbstractBlockStateMixin.java b/src/main/java/ca/spottedleaf/starlight/mixin/blockstate/AbstractBlockStateMixin.java new file mode 100644 index 00000000..0b9566c6 --- /dev/null +++ b/src/main/java/ca/spottedleaf/starlight/mixin/blockstate/AbstractBlockStateMixin.java @@ -0,0 +1,65 @@ +package ca.spottedleaf.starlight.mixin.blockstate; + +import ca.spottedleaf.starlight.common.blockstate.ExtendedAbstractBlockState; +import com.google.common.collect.ImmutableMap; +import com.mojang.serialization.MapCodec; +import net.minecraft.block.AbstractBlock; +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.state.State; +import net.minecraft.state.property.Property; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +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; + +@Mixin(AbstractBlock.AbstractBlockState.class) +public abstract class AbstractBlockStateMixin extends State implements ExtendedAbstractBlockState { + + protected AbstractBlockStateMixin(final Block owner, final ImmutableMap, Comparable> entries, + final MapCodec codec) { + super(owner, entries, codec); + } + + @Shadow + @Final + private boolean hasSidedTransparency; + + @Shadow + @Final + private boolean opaque; + + @Shadow + protected AbstractBlock.AbstractBlockState.ShapeCache shapeCache; + + @Unique + private int opacityIfCached; + + @Unique + private boolean isConditionallyFullOpaque; + + /** + * Initialises our light state for this block. + */ + @Inject( + method = "initShapeCache", + at = @At("RETURN") + ) + public void initLightAccessState(final CallbackInfo ci) { + this.isConditionallyFullOpaque = this.opaque & this.hasSidedTransparency; + this.opacityIfCached = this.shapeCache == null || this.isConditionallyFullOpaque ? -1 : this.shapeCache.lightSubtracted; + } + + @Override + public final boolean isConditionallyFullOpaque() { + return this.isConditionallyFullOpaque; + } + + @Override + public final int getOpacityIfCached() { + return this.opacityIfCached; + } +} diff --git a/src/main/java/ca/spottedleaf/starlight/mixin/chunk/ChunkMixin.java b/src/main/java/ca/spottedleaf/starlight/mixin/chunk/ChunkMixin.java new file mode 100644 index 00000000..fdba416f --- /dev/null +++ b/src/main/java/ca/spottedleaf/starlight/mixin/chunk/ChunkMixin.java @@ -0,0 +1,8 @@ +package ca.spottedleaf.starlight.mixin.chunk; + +import ca.spottedleaf.starlight.common.chunk.ExtendedChunk; +import net.minecraft.world.chunk.Chunk; +import org.spongepowered.asm.mixin.Mixin; + +@Mixin(Chunk.class) +public interface ChunkMixin extends ExtendedChunk {} \ No newline at end of file diff --git a/src/main/java/ca/spottedleaf/starlight/mixin/chunk/ChunkSectionMixin.java b/src/main/java/ca/spottedleaf/starlight/mixin/chunk/ChunkSectionMixin.java new file mode 100644 index 00000000..5c7beb86 --- /dev/null +++ b/src/main/java/ca/spottedleaf/starlight/mixin/chunk/ChunkSectionMixin.java @@ -0,0 +1,150 @@ +package ca.spottedleaf.starlight.mixin.chunk; + +import ca.spottedleaf.starlight.common.blockstate.ExtendedAbstractBlockState; +import ca.spottedleaf.starlight.common.chunk.ExtendedChunkSection; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.block.BlockState; +import net.minecraft.world.chunk.ChunkSection; +import net.minecraft.world.chunk.PalettedContainer; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +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 org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(ChunkSection.class) +public abstract class ChunkSectionMixin implements ExtendedChunkSection { + + @Final + @Shadow + public PalettedContainer container; + + + @Unique + protected int transparentBlockCount; + + @Unique + 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) { + final int opacityIfCached = ((ExtendedAbstractBlockState)state).getOpacityIfCached(); + + if (opacityIfCached == 0) { + return ExtendedChunkSection.BLOCK_IS_TRANSPARENT; + } + if (opacityIfCached == 15) { + return ExtendedChunkSection.BLOCK_IS_FULL_OPAQUE; + } + + return 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 - 1)); // blockIndex / (64/2) + final int valueShift = (blockIndex & (Long.SIZE / 2 - 1)) << 1; + + long value = this.knownBlockTransparencies[arrayIndex]; + + value &= ~(0b11L << valueShift); + value |= (transparency << valueShift); + + this.knownBlockTransparencies[arrayIndex] = value; + } + + @Unique + private void initKnownTransparenciesData() { + this.transparentBlockCount = 0; + for (int y = 0; y <= 15; ++y) { + for (int z = 0; z <= 15; ++z) { + for (int x = 0; x <= 15; ++x) { + final long transparency = getKnownTransparency(this.container.get(x, y, z)); + if (transparency == ExtendedChunkSection.BLOCK_IS_TRANSPARENT) { + ++this.transparentBlockCount; + } + this.updateTransparencyInfo(y | (x << 4) | (z << 8), transparency); + } + } + } + } + + /** + * Callback used to initialise the transparency data serverside. This only is for the server side since + * calculateCounts is not called clientside. + */ + @Inject( + method = "calculateCounts", + at = @At("RETURN") + ) + private void initKnownTransparenciesDataServerSide(final CallbackInfo ci) { + this.initKnownTransparenciesData(); + } + + /** + * Callback used to initialise the transparency data clientside. This is only for the client side as + * calculateCounts is called server side, and fromPacket is only used clientside. + */ + @Environment(EnvType.CLIENT) + @Inject( + method = "fromPacket", + at = @At("RETURN") + ) + private void initKnownTransparenciesDataClientSide(final CallbackInfo ci) { + this.initKnownTransparenciesData(); + } + + /** + * Callback used to update the transparency data on block update. + */ + @Inject( + method = "setBlockState(IIILnet/minecraft/block/BlockState;Z)Lnet/minecraft/block/BlockState;", + at = @At("RETURN") + ) + private void updateBlockCallback(final int x, final int y, final int z, final BlockState state, final boolean lock, + final CallbackInfoReturnable cir) { + final BlockState oldState = cir.getReturnValue(); + final long oldTransparency = getKnownTransparency(oldState); + final long newTransparency = getKnownTransparency(state); + + if (oldTransparency == ExtendedChunkSection.BLOCK_IS_TRANSPARENT) { + --this.transparentBlockCount; + } + if (newTransparency == ExtendedChunkSection.BLOCK_IS_TRANSPARENT) { + ++this.transparentBlockCount; + } + + this.updateTransparencyInfo(y | (x << 4) | (z << 8), newTransparency); + } + + @Override + public final boolean hasOpaqueBlocks() { + return this.transparentBlockCount != 4096; + } + + @Override + public final long getKnownTransparency(final int blockIndex) { + final int arrayIndex = (blockIndex >>> (6 - 1)); // blockIndex / (64/2) + final int valueShift = (blockIndex & (Long.SIZE / 2 - 1)); + + final long value = this.knownBlockTransparencies[arrayIndex]; + + return (value >>> (valueShift << 1)) & 0b11L; + } + + + @Override + public final long getBitsetForColumn(final int columnX, final int columnZ) { + final int columnIndex = (columnX << 4) | (columnZ << 8); + final long value = this.knownBlockTransparencies[columnIndex >>> (6 - 1)]; // columnIndex / (64/2) + + final int startIndex = (columnIndex & (Long.SIZE / 2 - 1)) << 1; + + return (value >>> startIndex) & ((1L << (2 * 16)) - 1); + } +} diff --git a/src/main/java/ca/spottedleaf/starlight/mixin/chunk/EmptyChunkMixin.java b/src/main/java/ca/spottedleaf/starlight/mixin/chunk/EmptyChunkMixin.java new file mode 100644 index 00000000..23daecad --- /dev/null +++ b/src/main/java/ca/spottedleaf/starlight/mixin/chunk/EmptyChunkMixin.java @@ -0,0 +1,40 @@ +package ca.spottedleaf.starlight.mixin.chunk; + +import ca.spottedleaf.starlight.common.chunk.ExtendedChunk; +import ca.spottedleaf.starlight.common.light.SWMRNibbleArray; +import ca.spottedleaf.starlight.common.light.StarLightEngine; +import net.minecraft.world.World; +import net.minecraft.world.chunk.Chunk; +import net.minecraft.world.chunk.EmptyChunk; +import net.minecraft.world.chunk.ProtoChunk; +import net.minecraft.world.chunk.WorldChunk; +import org.spongepowered.asm.mixin.Mixin; + +@Mixin(EmptyChunk.class) +public abstract class EmptyChunkMixin extends WorldChunk implements Chunk, ExtendedChunk { + + public EmptyChunkMixin(final World world, final ProtoChunk protoChunk) { + super(world, protoChunk); + } + + @Override + public SWMRNibbleArray[] getBlockNibbles() { + return StarLightEngine.getFilledEmptyLight(); + } + + @Override + public void setBlockNibbles(final SWMRNibbleArray[] nibbles) {} + + @Override + public SWMRNibbleArray[] getSkyNibbles() { + return StarLightEngine.getFilledEmptyLight(); + } + + @Override + public void setSkyNibbles(final SWMRNibbleArray[] nibbles) {} + + @Override + public boolean[][] getEmptinessMap() { + return new boolean[9][]; + } +} diff --git a/src/main/java/ca/spottedleaf/starlight/mixin/common/chunk/ProtoChunkMixin.java b/src/main/java/ca/spottedleaf/starlight/mixin/chunk/ProtoChunkMixin.java similarity index 54% rename from src/main/java/ca/spottedleaf/starlight/mixin/common/chunk/ProtoChunkMixin.java rename to src/main/java/ca/spottedleaf/starlight/mixin/chunk/ProtoChunkMixin.java index 00110713..1e4c00af 100644 --- a/src/main/java/ca/spottedleaf/starlight/mixin/common/chunk/ProtoChunkMixin.java +++ b/src/main/java/ca/spottedleaf/starlight/mixin/chunk/ProtoChunkMixin.java @@ -1,7 +1,7 @@ -package ca.spottedleaf.starlight.mixin.common.chunk; +package ca.spottedleaf.starlight.mixin.chunk; import ca.spottedleaf.starlight.common.light.StarLightEngine; -import ca.spottedleaf.starlight.common.chunk.NibbledChunk; +import ca.spottedleaf.starlight.common.chunk.ExtendedChunk; import ca.spottedleaf.starlight.common.light.SWMRNibbleArray; import net.minecraft.block.Block; import net.minecraft.fluid.Fluid; @@ -11,7 +11,6 @@ import net.minecraft.world.chunk.ChunkSection; import net.minecraft.world.chunk.ProtoChunk; import net.minecraft.world.chunk.UpgradeData; -import org.jetbrains.annotations.Nullable; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; @@ -19,18 +18,16 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Mixin(ProtoChunk.class) -public abstract class ProtoChunkMixin implements NibbledChunk, Chunk { +public abstract class ProtoChunkMixin implements Chunk, ExtendedChunk { @Unique private volatile SWMRNibbleArray[] blockNibbles; + @Unique private volatile SWMRNibbleArray[] skyNibbles; - @Inject(method = "(Lnet/minecraft/util/math/ChunkPos;Lnet/minecraft/world/chunk/UpgradeData;[Lnet/minecraft/world/chunk/ChunkSection;Lnet/minecraft/world/ChunkTickScheduler;Lnet/minecraft/world/ChunkTickScheduler;)V", at = @At("TAIL")) - public void onConstruct(ChunkPos pos, UpgradeData upgradeData, @Nullable ChunkSection[] sections, ChunkTickScheduler blockTickScheduler, ChunkTickScheduler fluidTickScheduler, CallbackInfo ci) { - this.blockNibbles = StarLightEngine.getFilledEmptyLight(false); - this.skyNibbles = StarLightEngine.getFilledEmptyLight(true); - } + @Unique + private boolean[][] emptinessMap; @Override public SWMRNibbleArray[] getBlockNibbles() { @@ -38,7 +35,7 @@ public SWMRNibbleArray[] getBlockNibbles() { } @Override - public void setBlockNibbles(SWMRNibbleArray[] nibbles) { + public void setBlockNibbles(final SWMRNibbleArray[] nibbles) { this.blockNibbles = nibbles; } @@ -48,7 +45,27 @@ public SWMRNibbleArray[] getSkyNibbles() { } @Override - public void setSkyNibbles(SWMRNibbleArray[] nibbles) { + public void setSkyNibbles(final SWMRNibbleArray[] nibbles) { this.skyNibbles = nibbles; } + + @Override + public boolean[][] getEmptinessMap() { + return this.emptinessMap; + } + + /** + * Initialises the nibble arrays to default values. + * TODO since this is a constructor inject, check for new constructors on update. + */ + @Inject( + method = "(Lnet/minecraft/util/math/ChunkPos;Lnet/minecraft/world/chunk/UpgradeData;[Lnet/minecraft/world/chunk/ChunkSection;Lnet/minecraft/world/ChunkTickScheduler;Lnet/minecraft/world/ChunkTickScheduler;)V", + at = @At("TAIL") + ) + public void onConstruct(final ChunkPos pos, final UpgradeData upgradeData, final ChunkSection[] sections, final ChunkTickScheduler blockTickScheduler, + final ChunkTickScheduler fluidTickScheduler, final CallbackInfo ci) { + this.blockNibbles = StarLightEngine.getFilledEmptyLight(); + this.skyNibbles = StarLightEngine.getFilledEmptyLight(); + this.emptinessMap = new boolean[9][]; + } } diff --git a/src/main/java/ca/spottedleaf/starlight/mixin/chunk/ReadOnlyChunkMixin.java b/src/main/java/ca/spottedleaf/starlight/mixin/chunk/ReadOnlyChunkMixin.java new file mode 100644 index 00000000..45c87ea2 --- /dev/null +++ b/src/main/java/ca/spottedleaf/starlight/mixin/chunk/ReadOnlyChunkMixin.java @@ -0,0 +1,50 @@ +package ca.spottedleaf.starlight.mixin.chunk; + +import ca.spottedleaf.starlight.common.light.SWMRNibbleArray; +import ca.spottedleaf.starlight.common.chunk.ExtendedChunk; +import net.minecraft.util.math.ChunkPos; +import net.minecraft.world.chunk.Chunk; +import net.minecraft.world.chunk.ProtoChunk; +import net.minecraft.world.chunk.ReadOnlyChunk; +import net.minecraft.world.chunk.UpgradeData; +import net.minecraft.world.chunk.WorldChunk; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +@Mixin(ReadOnlyChunk.class) +public abstract class ReadOnlyChunkMixin extends ProtoChunk implements Chunk, ExtendedChunk { + + @Final + @Shadow + private WorldChunk wrapped; + + public ReadOnlyChunkMixin(final ChunkPos pos, final UpgradeData upgradeData) { + super(pos, upgradeData); + } + + @Override + public SWMRNibbleArray[] getBlockNibbles() { + return ((ExtendedChunk)this.wrapped).getBlockNibbles(); + } + + @Override + public void setBlockNibbles(final SWMRNibbleArray[] nibbles) { + ((ExtendedChunk)this.wrapped).setBlockNibbles(nibbles); + } + + @Override + public SWMRNibbleArray[] getSkyNibbles() { + return ((ExtendedChunk)this.wrapped).getSkyNibbles(); + } + + @Override + public void setSkyNibbles(final SWMRNibbleArray[] nibbles) { + ((ExtendedChunk)this.wrapped).setSkyNibbles(nibbles); + } + + @Override + public boolean[][] getEmptinessMap() { + return ((ExtendedChunk)this.wrapped).getEmptinessMap(); + } +} diff --git a/src/main/java/ca/spottedleaf/starlight/mixin/chunk/WorldChunkMixin.java b/src/main/java/ca/spottedleaf/starlight/mixin/chunk/WorldChunkMixin.java new file mode 100644 index 00000000..22bc105f --- /dev/null +++ b/src/main/java/ca/spottedleaf/starlight/mixin/chunk/WorldChunkMixin.java @@ -0,0 +1,91 @@ +package ca.spottedleaf.starlight.mixin.chunk; + +import ca.spottedleaf.starlight.common.light.SWMRNibbleArray; +import ca.spottedleaf.starlight.common.light.StarLightEngine; +import ca.spottedleaf.starlight.common.chunk.ExtendedChunk; +import net.minecraft.block.Block; +import net.minecraft.fluid.Fluid; +import net.minecraft.util.math.ChunkPos; +import net.minecraft.world.TickScheduler; +import net.minecraft.world.World; +import net.minecraft.world.biome.source.BiomeArray; +import net.minecraft.world.chunk.Chunk; +import net.minecraft.world.chunk.ChunkSection; +import net.minecraft.world.chunk.ProtoChunk; +import net.minecraft.world.chunk.UpgradeData; +import net.minecraft.world.chunk.WorldChunk; +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.function.Consumer; + +@Mixin(WorldChunk.class) +public abstract class WorldChunkMixin implements ExtendedChunk, Chunk { + + @Unique + private volatile SWMRNibbleArray[] blockNibbles; + + @Unique + private volatile SWMRNibbleArray[] skyNibbles; + + @Unique + private boolean[][] emptinessMap; + + @Override + public SWMRNibbleArray[] getBlockNibbles() { + return this.blockNibbles; + } + + @Override + public void setBlockNibbles(final SWMRNibbleArray[] nibbles) { + this.blockNibbles = nibbles; + } + + @Override + public SWMRNibbleArray[] getSkyNibbles() { + return this.skyNibbles; + } + + @Override + public void setSkyNibbles(final SWMRNibbleArray[] nibbles) { + this.skyNibbles = nibbles; + } + + @Override + public boolean[][] getEmptinessMap() { + return this.emptinessMap; + } + + /** + * Copies the nibble data from the protochunk. + * TODO since this is a constructor inject, check for new constructors on update. + */ + @Inject( + method = "(Lnet/minecraft/world/World;Lnet/minecraft/world/chunk/ProtoChunk;)V", + at = @At("TAIL") + ) + public void onTransitionToFull(final World world, final ProtoChunk protoChunk, final CallbackInfo ci) { + this.setBlockNibbles(((ExtendedChunk)protoChunk).getBlockNibbles()); + this.setSkyNibbles(((ExtendedChunk)protoChunk).getSkyNibbles()); + this.emptinessMap = ((ExtendedChunk)protoChunk).getEmptinessMap(); + } + + /** + * Initialises the nibble arrays to default values. + * TODO since this is a constructor inject, check for new constructors on update. + */ + @Inject( + method = "(Lnet/minecraft/world/World;Lnet/minecraft/util/math/ChunkPos;Lnet/minecraft/world/biome/source/BiomeArray;Lnet/minecraft/world/chunk/UpgradeData;Lnet/minecraft/world/TickScheduler;Lnet/minecraft/world/TickScheduler;J[Lnet/minecraft/world/chunk/ChunkSection;Ljava/util/function/Consumer;)V", + at = @At("TAIL") + ) + public void onConstruct(final World world, final ChunkPos pos, final BiomeArray biomes, final UpgradeData upgradeData, + final TickScheduler blockTickScheduler, final TickScheduler fluidTickScheduler, + final long inhabitedTime, final ChunkSection[] sections, final Consumer loadToWorldConsumer, + final CallbackInfo ci) { + this.blockNibbles = StarLightEngine.getFilledEmptyLight(); + this.skyNibbles = StarLightEngine.getFilledEmptyLight(); + this.emptinessMap = new boolean[9][]; + } +} diff --git a/src/main/java/ca/spottedleaf/starlight/mixin/common/blockstate/AbstractBlockStateMixin.java b/src/main/java/ca/spottedleaf/starlight/mixin/common/blockstate/AbstractBlockStateMixin.java deleted file mode 100644 index 242b73b3..00000000 --- a/src/main/java/ca/spottedleaf/starlight/mixin/common/blockstate/AbstractBlockStateMixin.java +++ /dev/null @@ -1,54 +0,0 @@ -package ca.spottedleaf.starlight.mixin.common.blockstate; - -import ca.spottedleaf.starlight.common.blockstate.LightAccessBlockState; -import net.minecraft.block.AbstractBlock; -import net.minecraft.block.Block; -import net.minecraft.util.math.BlockPos; -import net.minecraft.world.BlockView; -import org.spongepowered.asm.mixin.Final; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; -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; - -@Mixin(AbstractBlock.AbstractBlockState.class) -public abstract class AbstractBlockStateMixin implements LightAccessBlockState { - - @Shadow - @Final - private boolean hasSidedTransparency; - - @Shadow - @Final - private boolean opaque; - - @Shadow - public abstract Block getBlock(); - - @Shadow - public abstract int getOpacity(BlockView world, BlockPos pos); - - @Unique - private int opacityIfCached = -1; - - @Unique - private boolean isConditionallyFullOpaque = true; - - @Inject(method = "initShapeCache", at = @At("RETURN")) - public void initLightAccessState(CallbackInfo ci) { - this.isConditionallyFullOpaque = this.opaque & this.hasSidedTransparency; - this.opacityIfCached = this.getBlock().hasDynamicBounds() || this.isConditionallyFullOpaque() ? -1 : this.getOpacity(null, null); - } - - @Override - public final boolean isConditionallyFullOpaque() { - return this.isConditionallyFullOpaque; - } - - @Override - public final int getOpacityIfCached() { - return this.opacityIfCached; - } -} diff --git a/src/main/java/ca/spottedleaf/starlight/mixin/common/chunk/ChunkMixin.java b/src/main/java/ca/spottedleaf/starlight/mixin/common/chunk/ChunkMixin.java deleted file mode 100644 index d30f2476..00000000 --- a/src/main/java/ca/spottedleaf/starlight/mixin/common/chunk/ChunkMixin.java +++ /dev/null @@ -1,8 +0,0 @@ -package ca.spottedleaf.starlight.mixin.common.chunk; - -import ca.spottedleaf.starlight.common.chunk.NibbledChunk; -import net.minecraft.world.chunk.Chunk; -import org.spongepowered.asm.mixin.Mixin; - -@Mixin(Chunk.class) -public interface ChunkMixin extends NibbledChunk {} \ No newline at end of file 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 deleted file mode 100644 index 619a3e3d..00000000 --- a/src/main/java/ca/spottedleaf/starlight/mixin/common/chunk/ChunkSectionMixin.java +++ /dev/null @@ -1,11 +0,0 @@ -package ca.spottedleaf.starlight.mixin.common.chunk; - -import net.minecraft.world.chunk.ChunkSection; -import org.spongepowered.asm.mixin.Mixin; - -@Mixin(ChunkSection.class) -public class ChunkSectionMixin { - - - -} diff --git a/src/main/java/ca/spottedleaf/starlight/mixin/common/chunk/EmptyChunkMixin.java b/src/main/java/ca/spottedleaf/starlight/mixin/common/chunk/EmptyChunkMixin.java deleted file mode 100644 index 85f3a233..00000000 --- a/src/main/java/ca/spottedleaf/starlight/mixin/common/chunk/EmptyChunkMixin.java +++ /dev/null @@ -1,28 +0,0 @@ -package ca.spottedleaf.starlight.mixin.common.chunk; - -import ca.spottedleaf.starlight.common.chunk.NibbledChunk; -import ca.spottedleaf.starlight.common.light.SWMRNibbleArray; -import ca.spottedleaf.starlight.common.light.StarLightEngine; -import net.minecraft.world.chunk.Chunk; -import net.minecraft.world.chunk.EmptyChunk; -import org.spongepowered.asm.mixin.Mixin; - -@Mixin(EmptyChunk.class) -public abstract class EmptyChunkMixin implements NibbledChunk, Chunk { - - @Override - public SWMRNibbleArray[] getBlockNibbles() { - return StarLightEngine.getFilledEmptyLight(false); - } - - @Override - public void setBlockNibbles(SWMRNibbleArray[] nibbles) {} - - @Override - public SWMRNibbleArray[] getSkyNibbles() { - return StarLightEngine.getFilledEmptyLight(true); - } - - @Override - public void setSkyNibbles(SWMRNibbleArray[] nibbles) {} -} diff --git a/src/main/java/ca/spottedleaf/starlight/mixin/common/chunk/ReadOnlyChunkMixin.java b/src/main/java/ca/spottedleaf/starlight/mixin/common/chunk/ReadOnlyChunkMixin.java deleted file mode 100644 index 4c66b5da..00000000 --- a/src/main/java/ca/spottedleaf/starlight/mixin/common/chunk/ReadOnlyChunkMixin.java +++ /dev/null @@ -1,36 +0,0 @@ -package ca.spottedleaf.starlight.mixin.common.chunk; - -import ca.spottedleaf.starlight.common.light.SWMRNibbleArray; -import ca.spottedleaf.starlight.common.chunk.NibbledChunk; -import net.minecraft.world.chunk.Chunk; -import net.minecraft.world.chunk.ReadOnlyChunk; -import net.minecraft.world.chunk.WorldChunk; -import org.spongepowered.asm.mixin.Final; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; - -@Mixin(ReadOnlyChunk.class) -public abstract class ReadOnlyChunkMixin implements NibbledChunk, Chunk { - - @Shadow @Final private WorldChunk wrapped; - - @Override - public SWMRNibbleArray[] getBlockNibbles() { - return ((NibbledChunk)this.wrapped).getBlockNibbles(); - } - - @Override - public void setBlockNibbles(SWMRNibbleArray[] nibbles) { - ((NibbledChunk)this.wrapped).setBlockNibbles(nibbles); - } - - @Override - public SWMRNibbleArray[] getSkyNibbles() { - return ((NibbledChunk)this.wrapped).getSkyNibbles(); - } - - @Override - public void setSkyNibbles(SWMRNibbleArray[] nibbles) { - ((NibbledChunk)this.wrapped).setSkyNibbles(nibbles); - } -} diff --git a/src/main/java/ca/spottedleaf/starlight/mixin/common/chunk/ThreadedAnvilChunkStorageMixin.java b/src/main/java/ca/spottedleaf/starlight/mixin/common/chunk/ThreadedAnvilChunkStorageMixin.java deleted file mode 100644 index d8b53173..00000000 --- a/src/main/java/ca/spottedleaf/starlight/mixin/common/chunk/ThreadedAnvilChunkStorageMixin.java +++ /dev/null @@ -1,49 +0,0 @@ -package ca.spottedleaf.starlight.mixin.common.chunk; - -import ca.spottedleaf.starlight.common.chunk.ThreadedAnvilChunkStorageMethods; -import net.minecraft.server.world.ChunkHolder; -import net.minecraft.server.world.ThreadedAnvilChunkStorage; -import net.minecraft.util.math.ChunkPos; -import net.minecraft.util.thread.ThreadExecutor; -import org.jetbrains.annotations.Nullable; -import org.spongepowered.asm.mixin.Final; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; -import java.util.function.IntSupplier; - -@Mixin(ThreadedAnvilChunkStorage.class) -public abstract class ThreadedAnvilChunkStorageMixin implements ThreadedAnvilChunkStorageMethods { - - @Final - @Shadow - private ThreadExecutor mainThreadExecutor; - - @Shadow - protected abstract IntSupplier getCompletedLevelSupplier(long pos); - - @Shadow - protected abstract void releaseLightTicket(ChunkPos pos); - - @Shadow - protected @Nullable abstract ChunkHolder getChunkHolder(long pos); - - @Override - public final IntSupplier getCompletedLevelSupplierPublic(long pos) { - return this.getCompletedLevelSupplier(pos); - } - - @Override - public final void releaseLightTicketPublic(ChunkPos pos) { - this.releaseLightTicket(pos); - } - - @Override - public final void scheduleOntoMain(Runnable runnable) { - this.mainThreadExecutor.execute(runnable); - } - - @Override - public final ChunkHolder getOffMainChunkHolder(long pos) { - return this.getChunkHolder(pos); - } -} diff --git a/src/main/java/ca/spottedleaf/starlight/mixin/common/chunk/WorldChunkMixin.java b/src/main/java/ca/spottedleaf/starlight/mixin/common/chunk/WorldChunkMixin.java deleted file mode 100644 index f3a2aad2..00000000 --- a/src/main/java/ca/spottedleaf/starlight/mixin/common/chunk/WorldChunkMixin.java +++ /dev/null @@ -1,65 +0,0 @@ -package ca.spottedleaf.starlight.mixin.common.chunk; - -import ca.spottedleaf.starlight.common.light.SWMRNibbleArray; -import ca.spottedleaf.starlight.common.light.StarLightEngine; -import ca.spottedleaf.starlight.common.chunk.NibbledChunk; -import net.minecraft.block.Block; -import net.minecraft.fluid.Fluid; -import net.minecraft.server.world.ServerWorld; -import net.minecraft.util.math.ChunkPos; -import net.minecraft.world.TickScheduler; -import net.minecraft.world.World; -import net.minecraft.world.biome.source.BiomeArray; -import net.minecraft.world.chunk.Chunk; -import net.minecraft.world.chunk.ChunkSection; -import net.minecraft.world.chunk.ProtoChunk; -import net.minecraft.world.chunk.UpgradeData; -import net.minecraft.world.chunk.WorldChunk; -import org.jetbrains.annotations.Nullable; -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.function.Consumer; - -@Mixin(WorldChunk.class) -public abstract class WorldChunkMixin implements NibbledChunk, Chunk { - - @Unique - private volatile SWMRNibbleArray[] blockNibbles; - @Unique - private volatile SWMRNibbleArray[] skyNibbles; - - @Override - public SWMRNibbleArray[] getBlockNibbles() { - return this.blockNibbles; - } - - @Override - public void setBlockNibbles(SWMRNibbleArray[] nibbles) { - this.blockNibbles = nibbles; - } - - @Override - public SWMRNibbleArray[] getSkyNibbles() { - return this.skyNibbles; - } - - @Override - public void setSkyNibbles(SWMRNibbleArray[] nibbles) { - this.skyNibbles = nibbles; - } - - @Inject(method = "(Lnet/minecraft/world/World;Lnet/minecraft/world/chunk/ProtoChunk;)V", at = @At("TAIL")) - public void onTransitionToFull(World world, ProtoChunk protoChunk, CallbackInfo ci) { - this.setBlockNibbles(((NibbledChunk)protoChunk).getBlockNibbles()); - this.setSkyNibbles(((NibbledChunk)protoChunk).getSkyNibbles()); - } - - @Inject(method = "(Lnet/minecraft/world/World;Lnet/minecraft/util/math/ChunkPos;Lnet/minecraft/world/biome/source/BiomeArray;Lnet/minecraft/world/chunk/UpgradeData;Lnet/minecraft/world/TickScheduler;Lnet/minecraft/world/TickScheduler;J[Lnet/minecraft/world/chunk/ChunkSection;Ljava/util/function/Consumer;)V", at = @At("TAIL")) - public void onConstruct(World world, ChunkPos pos, BiomeArray biomes, UpgradeData upgradeData, TickScheduler blockTickScheduler, TickScheduler fluidTickScheduler, long inhabitedTime, @Nullable ChunkSection[] sections, @Nullable Consumer loadToWorldConsumer, CallbackInfo ci) { - this.skyNibbles = StarLightEngine.getFilledEmptyLight(true); - this.blockNibbles = StarLightEngine.getFilledEmptyLight(false); - } -} 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 deleted file mode 100644 index 307cdcac..00000000 --- a/src/main/java/ca/spottedleaf/starlight/mixin/common/world/ChunkSerializerMixin.java +++ /dev/null @@ -1,143 +0,0 @@ -package ca.spottedleaf.starlight.mixin.common.world; - -import ca.spottedleaf.starlight.common.light.StarLightEngine; -import ca.spottedleaf.starlight.common.chunk.NibbledChunk; -import ca.spottedleaf.starlight.common.light.SWMRNibbleArray; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.nbt.ListTag; -import net.minecraft.server.world.ServerWorld; -import net.minecraft.structure.StructureManager; -import net.minecraft.util.math.ChunkPos; -import net.minecraft.world.ChunkSerializer; -import net.minecraft.world.chunk.Chunk; -import net.minecraft.world.chunk.ChunkNibbleArray; -import net.minecraft.world.chunk.ChunkStatus; -import net.minecraft.world.chunk.ProtoChunk; -import net.minecraft.world.poi.PointOfInterestStorage; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; - -@Mixin(ChunkSerializer.class) -public abstract class ChunkSerializerMixin { - - @Inject(method = "serialize", at = @At("RETURN")) - private static void saveLightHook(ServerWorld world, Chunk chunk, CallbackInfoReturnable cir) { - CompoundTag ret = cir.getReturnValue(); - if (ret == null || world.getChunk(chunk.getPos().x, chunk.getPos().z, ChunkStatus.EMPTY, false) != null) { - return; - } - - SWMRNibbleArray[] blockNibbles = ((NibbledChunk)chunk).getBlockNibbles(); - SWMRNibbleArray[] skyNibbles = ((NibbledChunk)chunk).getSkyNibbles(); - CompoundTag level = ret.getCompound("Level"); - boolean lit = chunk.isLightOn(); - // diff start - store our tag for whether light data is init'd - if (lit) { - level.putBoolean("starlight.lit", lit); - } - // diff end - store our tag for whether light data is init'd - ChunkStatus status = ChunkStatus.byId(level.getString("Status")); - - CompoundTag[] sections = new CompoundTag[18]; - - ListTag sectionsStored = level.getList("Sections", 10); - - for (int i = 0; i < sectionsStored.size(); ++i) { - CompoundTag sectionStored = sectionsStored.getCompound(i); - int k = sectionStored.getByte("Y"); - - // strip light data - sectionStored.remove("BlockLight"); - sectionStored.remove("SkyLight"); - - if (!sectionStored.isEmpty()) { - sections[k + 1] = sectionStored; - } - } - - if (lit && status.isAtLeast(ChunkStatus.LIGHT)) { - for (int i = -1; i <= 16; ++i) { - ChunkNibbleArray blockNibble = blockNibbles[i + 1].asNibble(); - ChunkNibbleArray skyNibble = skyNibbles[i + 1].asNibble(); - if (blockNibble != null || skyNibble != null) { - CompoundTag section = sections[i + 1]; - if (section == null) { - section = new CompoundTag(); - section.putByte("Y", (byte)i); - sections[i + 1] = section; - } - - if (blockNibble != null && !blockNibble.isUninitialized()) { - section.putByteArray("BlockLight", blockNibble.asByteArray()); - } - - if (skyNibble != null && !skyNibble.isUninitialized()) { - section.putByteArray("SkyLight", skyNibble.asByteArray()); - } - } - } - } - - // rewrite section list - sectionsStored.clear(); - for (CompoundTag section : sections) { - if (section != null) { - sectionsStored.add(section); - } - } - level.put("Sections", sectionsStored); - } - - @Inject(method = "deserialize", at = @At("RETURN")) - private static void loadLightHook(ServerWorld world, StructureManager structureManager, PointOfInterestStorage poiStorage, ChunkPos pos, CompoundTag tag, CallbackInfoReturnable cir) { - ProtoChunk ret = cir.getReturnValue(); - if (ret == null) { - return; - } - - SWMRNibbleArray[] blockNibbles = StarLightEngine.getFilledEmptyLight(false); - SWMRNibbleArray[] skyNibbles = StarLightEngine.getFilledEmptyLight(true); - // start copy from from the original method - CompoundTag levelTag = tag.getCompound("Level"); - boolean lit = levelTag.getBoolean("isLightOn"); - lit = lit && levelTag.getBoolean("starlight.lit"); ret.setLightOn(lit); // diff - override lit with our value - boolean canReadSky = world.getDimension().hasSkyLight(); - ChunkStatus status = ChunkStatus.byId(tag.getCompound("Level").getString("Status")); - if (lit && status.isAtLeast(ChunkStatus.LIGHT)) { // diff - we add the status check here - ListTag sections = levelTag.getList("Sections", 10); - - for (int i = 0; i < sections.size(); ++i) { - CompoundTag sectionData = sections.getCompound(i); - int y = sectionData.getByte("Y"); - - if (sectionData.contains("BlockLight", 7)) { - // this is where our diff is - blockNibbles[y + 1] = new SWMRNibbleArray(sectionData.getByteArray("BlockLight")); - } - - if (canReadSky && sectionData.contains("SkyLight", 7)) { - // this is where our diff is - skyNibbles[y + 1] = new SWMRNibbleArray(sectionData.getByteArray("SkyLight")); - } - } - } - // end copy from vanilla - - boolean nullableSky = true; - for (int y = 16; y >= -1; --y) { - SWMRNibbleArray nibble = skyNibbles[y + 1]; - if (nibble.isNullNibbleUpdating()) { - nullableSky = false; - continue; - } - if (!nullableSky) { - nibble.markNonNull(); - } - } - - ((NibbledChunk)ret).setBlockNibbles(blockNibbles); - ((NibbledChunk)ret).setSkyNibbles(skyNibbles); - } -} diff --git a/src/main/java/ca/spottedleaf/starlight/mixin/common/lightengine/LightingProviderMixin.java b/src/main/java/ca/spottedleaf/starlight/mixin/lightengine/LightingProviderMixin.java similarity index 62% rename from src/main/java/ca/spottedleaf/starlight/mixin/common/lightengine/LightingProviderMixin.java rename to src/main/java/ca/spottedleaf/starlight/mixin/lightengine/LightingProviderMixin.java index b0b66e6e..6f064e58 100644 --- a/src/main/java/ca/spottedleaf/starlight/mixin/common/lightengine/LightingProviderMixin.java +++ b/src/main/java/ca/spottedleaf/starlight/mixin/lightengine/LightingProviderMixin.java @@ -1,9 +1,9 @@ -package ca.spottedleaf.starlight.mixin.common.lightengine; +package ca.spottedleaf.starlight.mixin.lightengine; import ca.spottedleaf.starlight.common.light.StarLightInterface; import ca.spottedleaf.starlight.common.light.StarLightLightingProvider; import ca.spottedleaf.starlight.common.util.CoordinateUtils; -import ca.spottedleaf.starlight.common.chunk.NibbledChunk; +import ca.spottedleaf.starlight.common.chunk.ExtendedChunk; import ca.spottedleaf.starlight.common.light.SWMRNibbleArray; import ca.spottedleaf.starlight.common.light.StarLightEngine; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; @@ -14,10 +14,10 @@ import net.minecraft.world.chunk.Chunk; import net.minecraft.world.chunk.ChunkNibbleArray; import net.minecraft.world.chunk.ChunkProvider; +import net.minecraft.world.chunk.ChunkSection; import net.minecraft.world.chunk.light.ChunkLightingView; import net.minecraft.world.chunk.light.LightingProvider; import net.minecraft.world.chunk.light.LightingView; -import org.jetbrains.annotations.Nullable; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Overwrite; import org.spongepowered.asm.mixin.Unique; @@ -32,12 +32,15 @@ public abstract class LightingProviderMixin implements LightingView, StarLightLi protected StarLightInterface lightEngine; @Override - public StarLightInterface getLightEngine() { + public final StarLightInterface getLightEngine() { return this.lightEngine; } - @Inject(method = "", at = @At("TAIL")) - public void construct(ChunkProvider chunkProvider, boolean hasBlockLight, boolean hasSkyLight, CallbackInfo ci) { + @Inject( + method = "", at = @At("TAIL") + ) + public void construct(final ChunkProvider chunkProvider, final boolean hasBlockLight, final boolean hasSkyLight, + final CallbackInfo ci) { this.lightEngine = new StarLightInterface(chunkProvider, hasSkyLight, hasBlockLight); } @@ -45,7 +48,7 @@ public void construct(ChunkProvider chunkProvider, boolean hasBlockLight, boolea * @author Spottedleaf */ @Overwrite - public void checkBlock(BlockPos pos) { + public void checkBlock(final BlockPos pos) { this.lightEngine.blockChange(pos.toImmutable()); } @@ -53,7 +56,7 @@ public void checkBlock(BlockPos pos) { * @author Spottedleaf */ @Overwrite - public void addLightSource(BlockPos pos, int level) { + public void addLightSource(final BlockPos pos, final int level) { // this light engine only reads levels from blocks, so this is a no-op } @@ -70,9 +73,9 @@ public boolean hasUpdates() { * @author Spottedleaf */ @Overwrite - public int doLightUpdates(int maxUpdateCount, boolean doSkylight, boolean skipEdgeLightPropagation) { + public int doLightUpdates(final int maxUpdateCount, final boolean doSkylight, final boolean skipEdgeLightPropagation) { // replace impl - boolean hadUpdates = this.hasUpdates(); + final boolean hadUpdates = this.hasUpdates(); this.lightEngine.propagateChanges(); return hadUpdates ? 1 : 0; } @@ -81,28 +84,34 @@ public int doLightUpdates(int maxUpdateCount, boolean doSkylight, boolean skipEd * @author Spottedleaf */ @Overwrite - public void setSectionStatus(ChunkSectionPos pos, boolean notReady) { - // no longer required with new light impl + public void setSectionStatus(final ChunkSectionPos pos, final boolean notReady) { + this.lightEngine.sectionChange(pos, notReady); } + @Unique protected final Long2ObjectOpenHashMap blockLight = new Long2ObjectOpenHashMap<>(); + + @Unique protected final Long2ObjectOpenHashMap skyLight = new Long2ObjectOpenHashMap<>(); /** * @author Spottedleaf */ @Overwrite - public void setColumnEnabled(ChunkPos pos, boolean lightEnabled) { - Chunk chunk = this.getLightEngine().getAnyChunkNow(pos.x, pos.z); + public void setColumnEnabled(final ChunkPos pos, final boolean lightEnabled) { + final Chunk chunk = this.getLightEngine().getAnyChunkNow(pos.x, pos.z); if (chunk != null) { - SWMRNibbleArray[] blockNibbles = this.blockLight.get(CoordinateUtils.getChunkKey(pos)); - SWMRNibbleArray[] skyNibbles = this.skyLight.get(CoordinateUtils.getChunkKey(pos)); + final SWMRNibbleArray[] blockNibbles = this.blockLight.get(CoordinateUtils.getChunkKey(pos)); + final SWMRNibbleArray[] skyNibbles = this.skyLight.get(CoordinateUtils.getChunkKey(pos)); if (blockNibbles != null) { - ((NibbledChunk)chunk).setBlockNibbles(blockNibbles); + ((ExtendedChunk)chunk).setBlockNibbles(blockNibbles); } if (skyNibbles != null) { - ((NibbledChunk)chunk).setSkyNibbles(skyNibbles); + ((ExtendedChunk)chunk).setSkyNibbles(skyNibbles); } + + // TODO queue this shit, yo + this.getLightEngine().loadInChunk(pos.x, pos.z, StarLightEngine.getEmptySectionsForChunk(chunk)); } else if (!lightEnabled) { this.blockLight.remove(CoordinateUtils.getChunkKey(pos)); this.skyLight.remove(CoordinateUtils.getChunkKey(pos)); @@ -113,7 +122,7 @@ public void setColumnEnabled(ChunkPos pos, boolean lightEnabled) { * @author Spottedleaf */ @Overwrite - public ChunkLightingView get(LightType lightType) { + public ChunkLightingView get(final LightType lightType) { return lightType == LightType.BLOCK ? this.lightEngine.getBlockReader() : this.lightEngine.getSkyReader(); } @@ -121,50 +130,51 @@ public ChunkLightingView get(LightType lightType) { * @author Spottedleaf */ @Overwrite - public void enqueueSectionData(LightType lightType, ChunkSectionPos pos, @Nullable ChunkNibbleArray nibble, boolean bl) { + public void enqueueSectionData(final LightType lightType, final ChunkSectionPos pos, final ChunkNibbleArray nibble, + final boolean bl) { // data storage changed with new light impl - Chunk chunk = this.getLightEngine().getAnyChunkNow(pos.getX(), pos.getZ()); + final Chunk chunk = this.getLightEngine().getAnyChunkNow(pos.getX(), pos.getZ()); switch (lightType) { case BLOCK: { SWMRNibbleArray[] blockNibbles = this.blockLight.computeIfAbsent(CoordinateUtils.getChunkKey(pos), (long keyInMap) -> { - return StarLightEngine.getFilledEmptyLight(false); + return StarLightEngine.getFilledEmptyLight(); }); - SWMRNibbleArray replacement; - if (nibble == null || nibble.isUninitialized()) { + + final SWMRNibbleArray replacement; + if (nibble == null) { + replacement = new SWMRNibbleArray(null, true); + } else if (nibble.isUninitialized()) { replacement = new SWMRNibbleArray(); } else { replacement = new SWMRNibbleArray(nibble.asByteArray()); } - replacement.updateVisible(); - blockNibbles[pos.getY() + 1] = replacement; if (chunk != null) { - ((NibbledChunk)chunk).setBlockNibbles(blockNibbles); + ((ExtendedChunk)chunk).setBlockNibbles(blockNibbles); this.lightEngine.getLightAccess().onLightUpdate(LightType.BLOCK, pos); } break; } case SKY: { SWMRNibbleArray[] skyNibbles = this.skyLight.computeIfAbsent(CoordinateUtils.getChunkKey(pos), (long keyInMap) -> { - return StarLightEngine.getFilledEmptyLight(true); + return StarLightEngine.getFilledEmptyLight(); }); - SWMRNibbleArray replacement; + + final SWMRNibbleArray replacement; if (nibble == null) { - replacement = new SWMRNibbleArray(true, 15); + replacement = new SWMRNibbleArray(null, true); } else if (nibble.isUninitialized()) { replacement = new SWMRNibbleArray(); } else { replacement = new SWMRNibbleArray(nibble.asByteArray()); } - replacement.updateVisible(); - skyNibbles[pos.getY() + 1] = replacement; if (chunk != null) { - ((NibbledChunk)chunk).setSkyNibbles(skyNibbles); + ((ExtendedChunk)chunk).setSkyNibbles(skyNibbles); this.lightEngine.getLightAccess().onLightUpdate(LightType.SKY, pos); } break; @@ -176,7 +186,7 @@ public void enqueueSectionData(LightType lightType, ChunkSectionPos pos, @Nullab * @author Spottedleaf */ @Overwrite - public void setRetainData(ChunkPos pos, boolean retainData) { + public void setRetainData(final ChunkPos pos, final boolean retainData) { // not used by new light impl } @@ -184,10 +194,10 @@ public void setRetainData(ChunkPos pos, boolean retainData) { * @author Spottedleaf */ @Overwrite - public int getLight(BlockPos pos, int ambientDarkness) { + public int getLight(final BlockPos pos, final int ambientDarkness) { // need to use new light hooks for this - int sky = this.lightEngine.getSkyReader().getLightLevel(pos) - ambientDarkness; - int block = this.lightEngine.getBlockReader().getLightLevel(pos); + final int sky = this.lightEngine.getSkyReader().getLightLevel(pos) - ambientDarkness; + final int block = this.lightEngine.getBlockReader().getLightLevel(pos); return Math.max(sky, block); } } diff --git a/src/main/java/ca/spottedleaf/starlight/mixin/common/lightengine/ServerLightingProviderMixin.java b/src/main/java/ca/spottedleaf/starlight/mixin/lightengine/ServerLightingProviderMixin.java similarity index 69% rename from src/main/java/ca/spottedleaf/starlight/mixin/common/lightengine/ServerLightingProviderMixin.java rename to src/main/java/ca/spottedleaf/starlight/mixin/lightengine/ServerLightingProviderMixin.java index e22578b0..e78a8571 100644 --- a/src/main/java/ca/spottedleaf/starlight/mixin/common/lightengine/ServerLightingProviderMixin.java +++ b/src/main/java/ca/spottedleaf/starlight/mixin/lightengine/ServerLightingProviderMixin.java @@ -1,11 +1,9 @@ -package ca.spottedleaf.starlight.mixin.common.lightengine; +package ca.spottedleaf.starlight.mixin.lightengine; -import ca.spottedleaf.starlight.common.chunk.ThreadedAnvilChunkStorageMethods; +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.light.StarLightEngine; import net.minecraft.server.world.ChunkTaskPrioritySystem; -import net.minecraft.server.world.ChunkTicketType; import net.minecraft.server.world.ServerLightingProvider; import net.minecraft.server.world.ServerWorld; import net.minecraft.server.world.ThreadedAnvilChunkStorage; @@ -21,7 +19,6 @@ import net.minecraft.world.chunk.ChunkStatus; import net.minecraft.world.chunk.light.LightingProvider; import org.apache.logging.log4j.Logger; -import org.jetbrains.annotations.Nullable; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Overwrite; @@ -61,15 +58,19 @@ public abstract class ServerLightingProviderMixin extends LightingProvider imple @Unique protected final ConcurrentLinkedQueue postTasks = new ConcurrentLinkedQueue<>(); - private void enqueue(int x, int z, Runnable task) { + @Unique + private void enqueue(final int x, final int z, final Runnable task) { this.enqueue(x, z, task, false); } - private void enqueue(int x, int z, Runnable task, boolean postTask) { - this.enqueue(x, z, ((ThreadedAnvilChunkStorageMethods)this.chunkStorage).getCompletedLevelSupplierPublic(ChunkPos.toLong(x, z)), postTask, task); + @Unique + private void enqueue(final int x, final int z, final Runnable task, final boolean postTask) { + this.enqueue(x, z, this.chunkStorage.getCompletedLevelSupplier(ChunkPos.toLong(x, z)), postTask, task); } - private void enqueue(int x, int z, IntSupplier completedLevelSupplier, boolean postTask, Runnable task) { + @Unique + private void enqueue(final int x, final int z, final IntSupplier completedLevelSupplier, final boolean postTask, + final Runnable task) { this.executor.send(ChunkTaskPrioritySystem.createMessage(() -> { if (postTask) { this.postTasks.add(task); @@ -79,21 +80,15 @@ private void enqueue(int x, int z, IntSupplier completedLevelSupplier, boolean p }, ChunkPos.toLong(x, z), completedLevelSupplier)); } - public ServerLightingProviderMixin(ChunkProvider chunkProvider, boolean hasBlockLight, boolean hasSkyLight) { + public ServerLightingProviderMixin(final ChunkProvider chunkProvider, final boolean hasBlockLight, final boolean hasSkyLight) { super(chunkProvider, hasBlockLight, hasSkyLight); } private long workTicketCounts = 0L; - /** - * @author Spottedleaf - */ - @Overwrite - public void checkBlock(BlockPos pos) { - BlockPos immutable = pos.toImmutable(); + @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 - int chunkX = pos.getX() >> 4; - int chunkZ = pos.getZ() >> 4; final ServerWorld world = (ServerWorld)this.getLightEngine().getWorld(); @@ -111,11 +106,13 @@ public void checkBlock(BlockPos pos) { world.getChunk(dx + chunkX, dz + chunkZ, (dx | dz) == 0 ? ChunkStatus.LIGHT : ChunkStatus.FEATURES, true); } } + world.getChunkManager().addTicket(StarLightInterface.CHUNK_WORK_TICKET, new ChunkPos(chunkX, chunkZ), 0, ticketId); + this.enqueue(chunkX, chunkZ, () -> { - super.checkBlock(immutable); + runnable.run(); this.enqueue(chunkX, chunkZ, () -> { - ((ThreadedAnvilChunkStorageMethods)world.getChunkManager().threadedAnvilChunkStorage).scheduleOntoMain(() -> { + world.getChunkManager().threadedAnvilChunkStorage.mainThreadExecutor.execute(() -> { world.getChunkManager().removeTicket(StarLightInterface.CHUNK_WORK_TICKET, new ChunkPos(chunkX, chunkZ), 0, ticketId); }); }, true); @@ -126,7 +123,18 @@ public void checkBlock(BlockPos pos) { * @author Spottedleaf */ @Overwrite - public void updateChunkStatus(ChunkPos pos) { + public void checkBlock(final BlockPos pos) { + final BlockPos posCopy = pos.toImmutable(); + this.queueTaskForSection(posCopy.getX() >> 4, posCopy.getY() >> 4, posCopy.getZ() >> 4, () -> { + super.checkBlock(posCopy); + }); + } + + /** + * @author Spottedleaf + */ + @Overwrite + public void updateChunkStatus(final ChunkPos pos) { // Do nothing, we don't care } @@ -134,15 +142,17 @@ public void updateChunkStatus(ChunkPos pos) { * @author Spottedleaf */ @Overwrite - public void setSectionStatus(ChunkSectionPos pos, boolean notReady) { - // light impl does not need to do this + public void setSectionStatus(final ChunkSectionPos pos, final boolean notReady) { + this.queueTaskForSection(pos.getX(), pos.getY(), pos.getZ(), () -> { + super.setSectionStatus(pos, notReady); + }); } /** * @author Spottedleaf */ @Overwrite - public void setColumnEnabled(ChunkPos pos, boolean lightEnabled) { + public void setColumnEnabled(final ChunkPos pos, final boolean lightEnabled) { // light impl does not need to do this } @@ -150,7 +160,8 @@ public void setColumnEnabled(ChunkPos pos, boolean lightEnabled) { * @author Spottedleaf */ @Overwrite - public void enqueueSectionData(LightType lightType, ChunkSectionPos pos, @Nullable ChunkNibbleArray nibbles, boolean bl) { + public void enqueueSectionData(final LightType lightType, final ChunkSectionPos pos, final ChunkNibbleArray nibbles, + final boolean bl) { // hook for loading light data is changed, as chunk is no longer loaded at this stage } @@ -158,7 +169,7 @@ public void enqueueSectionData(LightType lightType, ChunkSectionPos pos, @Nullab * @author Spottedleaf */ @Overwrite - public void setRetainData(ChunkPos pos, boolean retainData) { + public void setRetainData(final ChunkPos pos, final boolean retainData) { // light impl does not need to do this } @@ -194,23 +205,23 @@ private void runTasks() { * @author Spottedleaf */ @Overwrite - public CompletableFuture light(Chunk chunk, boolean lit) { - ChunkPos chunkPos = chunk.getPos(); - - if (lit) { - ((ThreadedAnvilChunkStorageMethods)this.chunkStorage).releaseLightTicketPublic(chunkPos); - return CompletableFuture.completedFuture(chunk); - } + public CompletableFuture light(final Chunk chunk, final boolean lit) { + final ChunkPos chunkPos = chunk.getPos(); return CompletableFuture.supplyAsync(() -> { - this.getLightEngine().lightChunk(chunkPos.x, chunkPos.z); - chunk.setLightOn(true); + final Boolean[] emptySections = StarLightEngine.getEmptySectionsForChunk(chunk); + if (!lit) { + this.getLightEngine().lightChunk(chunkPos.x, chunkPos.z, emptySections); + chunk.setLightOn(true); + } else { + this.getLightEngine().loadInChunk(chunkPos.x, chunkPos.z, emptySections); + } - ((ThreadedAnvilChunkStorageMethods)this.chunkStorage).releaseLightTicketPublic(chunkPos); + this.chunkStorage.releaseLightTicket(chunkPos); return chunk; }, (runnable) -> { this.enqueue(chunkPos.x, chunkPos.z, runnable); - }).whenComplete((Chunk c, Throwable throwable) -> { + }).whenComplete((final Chunk c, final Throwable throwable) -> { if (throwable != null) { LOGGER.fatal("Failed to light chunk " + chunkPos, throwable); } diff --git a/src/main/java/ca/spottedleaf/starlight/mixin/world/ChunkSerializerMixin.java b/src/main/java/ca/spottedleaf/starlight/mixin/world/ChunkSerializerMixin.java new file mode 100644 index 00000000..92063b06 --- /dev/null +++ b/src/main/java/ca/spottedleaf/starlight/mixin/world/ChunkSerializerMixin.java @@ -0,0 +1,178 @@ +package ca.spottedleaf.starlight.mixin.world; + +import ca.spottedleaf.starlight.common.light.StarLightEngine; +import ca.spottedleaf.starlight.common.chunk.ExtendedChunk; +import ca.spottedleaf.starlight.common.light.SWMRNibbleArray; +import ca.spottedleaf.starlight.common.world.ExtendedWorld; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.structure.StructureManager; +import net.minecraft.util.math.ChunkPos; +import net.minecraft.world.ChunkSerializer; +import net.minecraft.world.chunk.Chunk; +import net.minecraft.world.chunk.ChunkNibbleArray; +import net.minecraft.world.chunk.ChunkStatus; +import net.minecraft.world.chunk.ProtoChunk; +import net.minecraft.world.poi.PointOfInterestStorage; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(ChunkSerializer.class) +public abstract class ChunkSerializerMixin { + + private static final int STARLIGHT_LIGHT_VERSION = 1; + + private static final String UNINITIALISED_SKYLIGHT_TAG = "starlight.skylight_"; + + /** + * Overwrites vanilla's light data with our own. + * TODO this needs to be checked on update to account for format changes + */ + @Inject( + method = "serialize", + at = @At("RETURN") + ) + private static void saveLightHook(final ServerWorld world, final Chunk chunk, final CallbackInfoReturnable cir) { + final int minSection = -1; + final int maxSection = 16; + CompoundTag ret = cir.getReturnValue(); + if (ret == null || ((ExtendedWorld)world).getAnyChunkImmediately(chunk.getPos().x, chunk.getPos().z) != null) { + return; + } + + SWMRNibbleArray[] blockNibbles = ((ExtendedChunk)chunk).getBlockNibbles(); + SWMRNibbleArray[] skyNibbles = ((ExtendedChunk)chunk).getSkyNibbles(); + + CompoundTag level = ret.getCompound("Level"); + boolean lit = chunk.isLightOn(); + // diff start - store our tag for whether light data is init'd + if (lit) { + level.putBoolean("isLightOn", false); + level.putInt("starlight.light_versiom", STARLIGHT_LIGHT_VERSION); + } + // diff end - store our tag for whether light data is init'd + ChunkStatus status = ChunkStatus.byId(level.getString("Status")); + + CompoundTag[] sections = new CompoundTag[maxSection - minSection + 1]; + + ListTag sectionsStored = level.getList("Sections", 10); + + for (int i = 0; i < sectionsStored.size(); ++i) { + CompoundTag sectionStored = sectionsStored.getCompound(i); + int k = sectionStored.getByte("Y"); + + // strip light data + sectionStored.remove("BlockLight"); + sectionStored.remove("SkyLight"); + + if (!sectionStored.isEmpty()) { + sections[k - minSection] = sectionStored; + } + } + + if (lit && status.isAtLeast(ChunkStatus.LIGHT)) { + for (int i = minSection; i <= maxSection; ++i) { + ChunkNibbleArray blockNibble = blockNibbles[i - minSection].isAllZero() ? new ChunkNibbleArray() : blockNibbles[i - minSection].toVanillaNibble(); + ChunkNibbleArray skyNibble = skyNibbles[i - minSection].isAllZero() ? new ChunkNibbleArray() : skyNibbles[i - minSection].toVanillaNibble(); + if (blockNibble != null || skyNibble != null) { + CompoundTag section = sections[i - minSection]; + if (section == null) { + section = new CompoundTag(); + section.putByte("Y", (byte)i); + sections[i - minSection] = section; + } + + if (blockNibble != null && !blockNibble.isUninitialized()) { + section.putByteArray("BlockLight", blockNibble.asByteArray()); + } + + if (skyNibble != null) { + // for skylight compatibility with vanilla, we store our data in a different key. + if (skyNibble.isUninitialized()) { + // different key lets use do this + section.putBoolean("starlight.skylight_uninit", true); + } else { + // we store under the same key so mod programs editing nbt + // can still read the data, hopefully. + // however, for compatibility we store chunks as unlit so vanilla + // is forced to re-light them if it encounters our data. It's too much of a burden + // to try and maintain compatibility with a broken and inferior skylight management system. + section.putByteArray("SkyLight", skyNibble.asByteArray()); + } + } + } + } + } + + // rewrite section list + sectionsStored.clear(); + for (CompoundTag section : sections) { + if (section != null) { + sectionsStored.add(section); + } + } + level.put("Sections", sectionsStored); + } + + /** + * Loads our light data into the returned chunk object from the tag. + * TODO this needs to be checked on update to account for format changes + */ + @Inject( + method = "deserialize", + at = @At("RETURN") + ) + private static void loadLightHook(final ServerWorld world, final StructureManager structureManager, final PointOfInterestStorage poiStorage, + final ChunkPos pos, final CompoundTag tag, final CallbackInfoReturnable cir) { + final int minSection = -1; + final int maxSection = 16; + ProtoChunk ret = cir.getReturnValue(); + if (ret == null) { + return; + } + + SWMRNibbleArray[] blockNibbles = StarLightEngine.getFilledEmptyLight(); + SWMRNibbleArray[] skyNibbles = StarLightEngine.getFilledEmptyLight(); + + + // start copy from from the original method + CompoundTag levelTag = tag.getCompound("Level"); + boolean lit = levelTag.getInt("starlight.light_versiom") == STARLIGHT_LIGHT_VERSION; ret.setLightOn(lit); // diff - override lit with our value + boolean canReadSky = world.getDimension().hasSkyLight(); + ChunkStatus status = ChunkStatus.byId(tag.getCompound("Level").getString("Status")); + if (lit && status.isAtLeast(ChunkStatus.LIGHT)) { // diff - we add the status check here + ListTag sections = levelTag.getList("Sections", 10); + + for (int i = 0; i < sections.size(); ++i) { + CompoundTag sectionData = sections.getCompound(i); + int y = sectionData.getByte("Y"); + + if (sectionData.contains("BlockLight", 7)) { + // this is where our diff is + blockNibbles[y - minSection] = new SWMRNibbleArray(sectionData.getByteArray("BlockLight").clone()); // clone for data safety + } + + if (canReadSky) { + // for skylight compatibility with vanilla, we store our data in a different key. + if (sectionData.contains("SkyLight", 7)) { + // we store under the same key so mod programs editing nbt + // can still read the data, hopefully. + // however, for compatibility we store chunks as unlit so vanilla + // is forced to re-light them if it encounters our data. It's too much of a burden + // to try and maintain compatibility with a broken and inferior skylight management system. + skyNibbles[y - minSection] = new SWMRNibbleArray(sectionData.getByteArray("SkyLight").clone()); // clone for data safety + } else if (sectionData.getBoolean("starlight.skylight_uninit")) { + skyNibbles[y - minSection] = new SWMRNibbleArray(); + } + } + } + } + // end copy from vanilla + + ((ExtendedChunk)ret).setBlockNibbles(blockNibbles); + ((ExtendedChunk)ret).setSkyNibbles(skyNibbles); + } +} diff --git a/src/main/java/ca/spottedleaf/starlight/mixin/world/ClientWorldMixin.java b/src/main/java/ca/spottedleaf/starlight/mixin/world/ClientWorldMixin.java new file mode 100644 index 00000000..8cb5fc2f --- /dev/null +++ b/src/main/java/ca/spottedleaf/starlight/mixin/world/ClientWorldMixin.java @@ -0,0 +1,35 @@ +package ca.spottedleaf.starlight.mixin.world; + +import ca.spottedleaf.starlight.common.world.ExtendedWorld; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.client.world.ClientWorld; +import net.minecraft.util.profiler.Profiler; +import net.minecraft.util.registry.RegistryKey; +import net.minecraft.world.MutableWorldProperties; +import net.minecraft.world.World; +import net.minecraft.world.chunk.Chunk; +import net.minecraft.world.chunk.WorldChunk; +import net.minecraft.world.dimension.DimensionType; +import org.spongepowered.asm.mixin.Mixin; +import java.util.function.Supplier; + +@Environment(EnvType.CLIENT) +@Mixin(ClientWorld.class) +public abstract class ClientWorldMixin extends World implements ExtendedWorld { + + protected ClientWorldMixin(final MutableWorldProperties properties, final RegistryKey registryRef, final DimensionType dimensionType, + final Supplier profiler, final boolean isClient, final boolean debugWorld, final long seed) { + super(properties, registryRef, dimensionType, profiler, isClient, debugWorld, seed); + } + + @Override + public final WorldChunk getChunkAtImmediately(final int chunkX, final int chunkZ) { + return this.getChunkManager().getWorldChunk(chunkX, chunkZ, false); + } + + @Override + public final Chunk getAnyChunkImmediately(int chunkX, int chunkZ) { + return this.getChunkManager().getWorldChunk(chunkX, chunkZ, false); + } +} diff --git a/src/main/java/ca/spottedleaf/starlight/mixin/world/ServerWorldMixin.java b/src/main/java/ca/spottedleaf/starlight/mixin/world/ServerWorldMixin.java new file mode 100644 index 00000000..36647083 --- /dev/null +++ b/src/main/java/ca/spottedleaf/starlight/mixin/world/ServerWorldMixin.java @@ -0,0 +1,57 @@ +package ca.spottedleaf.starlight.mixin.world; + +import ca.spottedleaf.starlight.common.util.CoordinateUtils; +import ca.spottedleaf.starlight.common.world.ExtendedWorld; +import com.mojang.datafixers.util.Either; +import net.minecraft.server.world.ChunkHolder; +import net.minecraft.server.world.ServerChunkManager; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.server.world.ThreadedAnvilChunkStorage; +import net.minecraft.util.profiler.Profiler; +import net.minecraft.util.registry.RegistryKey; +import net.minecraft.world.MutableWorldProperties; +import net.minecraft.world.StructureWorldAccess; +import net.minecraft.world.World; +import net.minecraft.world.chunk.Chunk; +import net.minecraft.world.chunk.ChunkStatus; +import net.minecraft.world.chunk.WorldChunk; +import net.minecraft.world.dimension.DimensionType; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import java.util.function.Supplier; + +@Mixin(ServerWorld.class) +public abstract class ServerWorldMixin extends World implements StructureWorldAccess, ExtendedWorld { + + @Final + @Shadow + private ServerChunkManager serverChunkManager; + + protected ServerWorldMixin(final MutableWorldProperties properties, final RegistryKey registryRef, final DimensionType dimensionType, + final Supplier profiler, final boolean isClient, final boolean debugWorld, final long seed) { + super(properties, registryRef, dimensionType, profiler, isClient, debugWorld, seed); + } + + @Override + public final WorldChunk getChunkAtImmediately(final int chunkX, final int chunkZ) { + final ThreadedAnvilChunkStorage storage = this.serverChunkManager.threadedAnvilChunkStorage; + final ChunkHolder holder = storage.getChunkHolder(CoordinateUtils.getChunkKey(chunkX, chunkZ)); + + if (holder == null) { + return null; + } + + final Either either = holder.getFutureFor(ChunkStatus.FULL).getNow(null); + + return either == null ? null : (WorldChunk)either.left().orElse(null); + } + + @Override + public final Chunk getAnyChunkImmediately(final int chunkX, final int chunkZ) { + final ThreadedAnvilChunkStorage storage = this.serverChunkManager.threadedAnvilChunkStorage; + final ChunkHolder holder = storage.getChunkHolder(CoordinateUtils.getChunkKey(chunkX, chunkZ)); + + return holder == null ? null : holder.getCurrentChunk(); + } +} diff --git a/src/main/java/ca/spottedleaf/starlight/mixin/world/WorldMixin.java b/src/main/java/ca/spottedleaf/starlight/mixin/world/WorldMixin.java new file mode 100644 index 00000000..8050a002 --- /dev/null +++ b/src/main/java/ca/spottedleaf/starlight/mixin/world/WorldMixin.java @@ -0,0 +1,9 @@ +package ca.spottedleaf.starlight.mixin.world; + +import ca.spottedleaf.starlight.common.world.ExtendedWorld; +import net.minecraft.world.World; +import net.minecraft.world.WorldAccess; +import org.spongepowered.asm.mixin.Mixin; + +@Mixin(World.class) +public abstract class WorldMixin implements WorldAccess, AutoCloseable, ExtendedWorld {} diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 93306db0..a2b6069b 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -15,8 +15,9 @@ "mixins": [ "starlight.mixins.json" ], + "accessWidener" : "starlight.accesswidener", "depends": { - "fabricloader": ">=0.10.6+build.214", + "fabricloader": ">=0.10.8", "minecraft": "1.16.4" } } diff --git a/src/main/resources/starlight.accesswidener b/src/main/resources/starlight.accesswidener new file mode 100644 index 00000000..b6e8b7ec --- /dev/null +++ b/src/main/resources/starlight.accesswidener @@ -0,0 +1,26 @@ +accessWidener v1 named + +# AbstractBlockState +accessible class net/minecraft/block/AbstractBlock$AbstractBlockState$ShapeCache + + +# AbstractBlockState.ShapeCache +accessible field net/minecraft/block/AbstractBlock$AbstractBlockState$ShapeCache lightSubtracted I + + +# ChunkSection +accessible field net/minecraft/world/chunk/ChunkSection container Lnet/minecraft/world/chunk/PalettedContainer; + + +# PalettedContainer +accessible method net/minecraft/world/chunk/PalettedContainer get (I)Ljava/lang/Object; + + +# ThreadedAnvilChunkStorage +accessible field net/minecraft/server/world/ThreadedAnvilChunkStorage world Lnet/minecraft/server/world/ServerWorld; +accessible field net/minecraft/server/world/ThreadedAnvilChunkStorage mainThreadExecutor Lnet/minecraft/util/thread/ThreadExecutor; + +accessible method net/minecraft/server/world/ThreadedAnvilChunkStorage getCurrentChunkHolder (J)Lnet/minecraft/server/world/ChunkHolder; +accessible method net/minecraft/server/world/ThreadedAnvilChunkStorage getChunkHolder (J)Lnet/minecraft/server/world/ChunkHolder; +accessible method net/minecraft/server/world/ThreadedAnvilChunkStorage getCompletedLevelSupplier (J)Ljava/util/function/IntSupplier; +accessible method net/minecraft/server/world/ThreadedAnvilChunkStorage releaseLightTicket (Lnet/minecraft/util/math/ChunkPos;)V \ No newline at end of file diff --git a/src/main/resources/starlight.mixins.json b/src/main/resources/starlight.mixins.json index 1afd69e0..815e83f5 100644 --- a/src/main/resources/starlight.mixins.json +++ b/src/main/resources/starlight.mixins.json @@ -4,19 +4,21 @@ "package": "ca.spottedleaf.starlight.mixin", "compatibilityLevel": "JAVA_8", "mixins": [ - "common.blockstate.AbstractBlockStateMixin", - "common.chunk.ChunkMixin", - "common.chunk.ChunkSectionMixin", - "common.chunk.EmptyChunkMixin", - "common.chunk.ProtoChunkMixin", - "common.chunk.ReadOnlyChunkMixin", - "common.chunk.ThreadedAnvilChunkStorageMixin", - "common.chunk.WorldChunkMixin", - "common.lightengine.LightingProviderMixin", - "common.lightengine.ServerLightingProviderMixin", - "common.world.ChunkSerializerMixin" + "blockstate.AbstractBlockStateMixin", + "chunk.ChunkMixin", + "chunk.ChunkSectionMixin", + "chunk.EmptyChunkMixin", + "chunk.ProtoChunkMixin", + "chunk.ReadOnlyChunkMixin", + "chunk.WorldChunkMixin", + "lightengine.LightingProviderMixin", + "lightengine.ServerLightingProviderMixin", + "world.ChunkSerializerMixin", + "world.ServerWorldMixin", + "world.WorldMixin" ], "client": [ + "world.ClientWorldMixin" ], "injectors": { "defaultRequire": 1