PaperMC/patches/unapplied/server/Starlight.patch

5298 lines
242 KiB
Diff
Raw Normal View History

2021-11-27 11:42:09 +01:00
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
Rewrite chunk system (#8177) Patch documentation to come Issues with the old system that are fixed now: - World generation does not scale with cpu cores effectively. - Relies on the main thread for scheduling and maintaining chunk state, dropping chunk load/generate rates at lower tps. - Unreliable prioritisation of chunk gen/load calls that block the main thread. - Shutdown logic is utterly unreliable, as it has to wait for all chunks to unload - is it guaranteed that the chunk system is in a state on shutdown that it can reliably do this? Watchdog shutdown also typically failed due to thread checks, which is now resolved. - Saving of data is not unified (i.e can save chunk data without saving entity data, poses problems for desync if shutdown is really abnormal. - Entities are not loaded with chunks. This caused quite a bit of headache for Chunk#getEntities API, but now the new chunk system loads entities with chunks so that they are ready whenever the chunk loads in. Effectively brings the behavior back to 1.16 era, but still storing entities in their own separate regionfiles. The above list is not complete. The patch documentation will complete it. New chunk system hard relies on starlight and dataconverter, and most importantly the new concurrent utilities in ConcurrentUtil. Some of the old async chunk i/o interface (i.e the old file io thread reroutes _some_ calls to the new file io thread) is kept for plugin compat reasons. It will be removed in the next major version of minecraft. The old legacy chunk system patches have been moved to the removed folder in case we need them again.
2022-09-26 10:02:51 +02:00
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
2021-11-27 11:42:09 +01:00
Date: Wed, 28 Oct 2020 16:51:55 -0700
Rewrite chunk system (#8177) Patch documentation to come Issues with the old system that are fixed now: - World generation does not scale with cpu cores effectively. - Relies on the main thread for scheduling and maintaining chunk state, dropping chunk load/generate rates at lower tps. - Unreliable prioritisation of chunk gen/load calls that block the main thread. - Shutdown logic is utterly unreliable, as it has to wait for all chunks to unload - is it guaranteed that the chunk system is in a state on shutdown that it can reliably do this? Watchdog shutdown also typically failed due to thread checks, which is now resolved. - Saving of data is not unified (i.e can save chunk data without saving entity data, poses problems for desync if shutdown is really abnormal. - Entities are not loaded with chunks. This caused quite a bit of headache for Chunk#getEntities API, but now the new chunk system loads entities with chunks so that they are ready whenever the chunk loads in. Effectively brings the behavior back to 1.16 era, but still storing entities in their own separate regionfiles. The above list is not complete. The patch documentation will complete it. New chunk system hard relies on starlight and dataconverter, and most importantly the new concurrent utilities in ConcurrentUtil. Some of the old async chunk i/o interface (i.e the old file io thread reroutes _some_ calls to the new file io thread) is kept for plugin compat reasons. It will be removed in the next major version of minecraft. The old legacy chunk system patches have been moved to the removed folder in case we need them again.
2022-09-26 10:02:51 +02:00
Subject: [PATCH] Starlight
2021-11-27 11:42:09 +01:00
Rewrite chunk system (#8177) Patch documentation to come Issues with the old system that are fixed now: - World generation does not scale with cpu cores effectively. - Relies on the main thread for scheduling and maintaining chunk state, dropping chunk load/generate rates at lower tps. - Unreliable prioritisation of chunk gen/load calls that block the main thread. - Shutdown logic is utterly unreliable, as it has to wait for all chunks to unload - is it guaranteed that the chunk system is in a state on shutdown that it can reliably do this? Watchdog shutdown also typically failed due to thread checks, which is now resolved. - Saving of data is not unified (i.e can save chunk data without saving entity data, poses problems for desync if shutdown is really abnormal. - Entities are not loaded with chunks. This caused quite a bit of headache for Chunk#getEntities API, but now the new chunk system loads entities with chunks so that they are ready whenever the chunk loads in. Effectively brings the behavior back to 1.16 era, but still storing entities in their own separate regionfiles. The above list is not complete. The patch documentation will complete it. New chunk system hard relies on starlight and dataconverter, and most importantly the new concurrent utilities in ConcurrentUtil. Some of the old async chunk i/o interface (i.e the old file io thread reroutes _some_ calls to the new file io thread) is kept for plugin compat reasons. It will be removed in the next major version of minecraft. The old legacy chunk system patches have been moved to the removed folder in case we need them again.
2022-09-26 10:02:51 +02:00
See https://github.com/PaperMC/Starlight
2021-11-27 11:42:09 +01:00
== AT ==
public net.minecraft.server.level.ChunkHolder broadcast(Lnet/minecraft/network/protocol/Packet;Z)V
public net.minecraft.world.level.chunk.LevelChunkSection states
2021-11-27 11:42:09 +01:00
diff --git a/src/main/java/ca/spottedleaf/starlight/common/light/BlockStarLightEngine.java b/src/main/java/ca/spottedleaf/starlight/common/light/BlockStarLightEngine.java
new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
--- /dev/null
+++ b/src/main/java/ca/spottedleaf/starlight/common/light/BlockStarLightEngine.java
@@ -0,0 +0,0 @@
+package ca.spottedleaf.starlight.common.light;
+
+import net.minecraft.core.BlockPos;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.level.chunk.ChunkAccess;
+import net.minecraft.world.level.chunk.ChunkStatus;
+import net.minecraft.world.level.chunk.ImposterProtoChunk;
+import net.minecraft.world.level.chunk.LevelChunk;
+import net.minecraft.world.level.chunk.LevelChunkSection;
+import net.minecraft.world.level.chunk.LightChunkGetter;
+import net.minecraft.world.level.chunk.PalettedContainer;
+import net.minecraft.world.phys.shapes.Shapes;
+import net.minecraft.world.phys.shapes.VoxelShape;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public final class BlockStarLightEngine extends StarLightEngine {
+
+ public BlockStarLightEngine(final Level world) {
+ super(false, world);
+ }
+
+ @Override
+ protected boolean[] getEmptinessMap(final ChunkAccess chunk) {
+ return chunk.getBlockEmptinessMap();
+ }
+
+ @Override
+ protected void setEmptinessMap(final ChunkAccess chunk, final boolean[] to) {
+ chunk.setBlockEmptinessMap(to);
+ }
+
+ @Override
+ protected SWMRNibbleArray[] getNibblesOnChunk(final ChunkAccess chunk) {
+ return chunk.getBlockNibbles();
+ }
+
+ @Override
+ protected void setNibbles(final ChunkAccess chunk, final SWMRNibbleArray[] to) {
+ chunk.setBlockNibbles(to);
+ }
+
+ @Override
+ protected boolean canUseChunk(final ChunkAccess chunk) {
+ return chunk.getStatus().isOrAfter(ChunkStatus.LIGHT) && (this.isClientSide || chunk.isLightCorrect());
+ }
+
+ @Override
+ protected void setNibbleNull(final int chunkX, final int chunkY, final int chunkZ) {
+ final SWMRNibbleArray nibble = this.getNibbleFromCache(chunkX, chunkY, chunkZ);
+ if (nibble != null) {
+ // de-initialisation is not as straightforward as with sky data, since deinit of block light is typically
+ // because a block was removed - which can decrease light. with sky data, block breaking can only result
+ // in increases, and thus the existing sky block check will actually correctly propagate light through
+ // a null section. so in order to propagate decreases correctly, we can do a couple of things: not remove
+ // the data section, or do edge checks on ALL axis (x, y, z). however I do not want edge checks running
+ // for clients at all, as they are expensive. so we don't remove the section, but to maintain the appearence
+ // of vanilla data management we "hide" them.
+ nibble.setHidden();
+ }
+ }
+
+ @Override
+ protected void initNibble(final int chunkX, final int chunkY, final int chunkZ, final boolean extrude, final boolean initRemovedNibbles) {
+ if (chunkY < this.minLightSection || chunkY > this.maxLightSection || this.getChunkInCache(chunkX, chunkZ) == null) {
+ return;
+ }
+
+ final SWMRNibbleArray nibble = this.getNibbleFromCache(chunkX, chunkY, chunkZ);
+ if (nibble == null) {
+ if (!initRemovedNibbles) {
+ throw new IllegalStateException();
+ } else {
+ this.setNibbleInCache(chunkX, chunkY, chunkZ, new SWMRNibbleArray());
+ }
+ } else {
+ nibble.setNonNull();
+ }
+ }
+
+ @Override
+ protected final void checkBlock(final LightChunkGetter lightAccess, final int worldX, final int worldY, final int worldZ) {
+ // blocks can change opacity
+ // blocks can change emitted light
+ // blocks can change direction of propagation
+
+ final int encodeOffset = this.coordinateOffset;
+ final int emittedMask = this.emittedLightMask;
+
+ final int currentLevel = this.getLightLevel(worldX, worldY, worldZ);
+ final BlockState blockState = this.getBlockState(worldX, worldY, worldZ);
+ final int emittedLevel = blockState.getLightEmission() & emittedMask;
+
+ this.setLightLevel(worldX, worldY, worldZ, emittedLevel);
+ // this accounts for change in emitted light that would cause an increase
+ if (emittedLevel != 0) {
+ this.appendToIncreaseQueue(
+ ((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))
+ | (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.appendToDecreaseQueue(
+ ((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
+ }
+
+ protected final BlockPos.MutableBlockPos recalcCenterPos = new BlockPos.MutableBlockPos();
+ protected final BlockPos.MutableBlockPos recalcNeighbourPos = new BlockPos.MutableBlockPos();
+
+ @Override
+ protected int calculateLightValue(final LightChunkGetter lightAccess, final int worldX, final int worldY, final int worldZ,
+ final int expect) {
+ final BlockState centerState = this.getBlockState(worldX, worldY, worldZ);
+ int level = centerState.getLightEmission() & 0xF;
+
+ if (level >= (15 - 1) || level > expect) {
+ return level;
+ }
+
+ final int sectionOffset = this.chunkSectionIndexOffset;
+ final BlockState conditionallyOpaqueState;
+ int opacity = centerState.getOpacityIfCached();
+
+ if (opacity == -1) {
+ this.recalcCenterPos.set(worldX, worldY, worldZ);
+ opacity = centerState.getLightBlock(lightAccess.getLevel(), this.recalcCenterPos);
+ if (centerState.isConditionallyFullOpaque()) {
+ conditionallyOpaqueState = centerState;
+ } else {
+ conditionallyOpaqueState = null;
+ }
+ } else if (opacity >= 15) {
+ return level;
+ } else {
+ conditionallyOpaqueState = null;
+ }
+ opacity = Math.max(1, opacity);
+
+ for (final AxisDirection direction : AXIS_DIRECTIONS) {
+ final int offX = worldX + direction.x;
+ final int offY = worldY + direction.y;
+ final int offZ = worldZ + direction.z;
+
+ final int sectionIndex = (offX >> 4) + 5 * (offZ >> 4) + (5 * 5) * (offY >> 4) + sectionOffset;
+
+ final int neighbourLevel = this.getLightLevel(sectionIndex, (offX & 15) | ((offZ & 15) << 4) | ((offY & 15) << 8));
+
+ if ((neighbourLevel - 1) <= level) {
+ // don't need to test transparency, we know it wont affect the result.
+ continue;
+ }
+
+ final BlockState neighbourState = this.getBlockState(offX, offY, offZ);
+ if (neighbourState.isConditionallyFullOpaque()) {
+ // here the block can be conditionally opaque (i.e light cannot propagate from it), so we need to test that
+ // we don't read the blockstate because most of the time this is false, so using the faster
+ // known transparency lookup results in a net win
+ this.recalcNeighbourPos.set(offX, offY, offZ);
+ final VoxelShape neighbourFace = neighbourState.getFaceOcclusionShape(lightAccess.getLevel(), this.recalcNeighbourPos, direction.opposite.nms);
+ final VoxelShape thisFace = conditionallyOpaqueState == null ? Shapes.empty() : conditionallyOpaqueState.getFaceOcclusionShape(lightAccess.getLevel(), this.recalcCenterPos, direction.nms);
+ if (Shapes.faceShapeOccludes(thisFace, neighbourFace)) {
+ // not allowed to propagate
+ continue;
+ }
+ }
+
+ // passed transparency,
+
+ final int calculated = neighbourLevel - opacity;
+ level = Math.max(calculated, level);
+ if (level > expect) {
+ return level;
+ }
+ }
+
+ return level;
+ }
+
+ @Override
+ protected void propagateBlockChanges(final LightChunkGetter lightAccess, final ChunkAccess atChunk, final Set<BlockPos> positions) {
+ for (final BlockPos pos : positions) {
+ this.checkBlock(lightAccess, pos.getX(), pos.getY(), pos.getZ());
+ }
+
+ this.performLightDecrease(lightAccess);
+ }
+
+ protected Iterator<BlockPos> getSources(final LightChunkGetter lightAccess, final ChunkAccess chunk) {
+ if (chunk instanceof ImposterProtoChunk || chunk instanceof LevelChunk) {
+ // implementation on Chunk is pretty awful, so write our own here. The big optimisation is
+ // skipping empty sections, and the far more optimised reading of types.
+ List<BlockPos> sources = new ArrayList<>();
+
+ int offX = chunk.getPos().x << 4;
+ int offZ = chunk.getPos().z << 4;
+
+ final LevelChunkSection[] sections = chunk.getSections();
+ for (int sectionY = this.minSection; sectionY <= this.maxSection; ++sectionY) {
+ final LevelChunkSection section = sections[sectionY - this.minSection];
+ if (section == null || section.hasOnlyAir()) {
+ // no sources in empty sections
+ continue;
+ }
+ final PalettedContainer<BlockState> states = section.states;
+ final int offY = sectionY << 4;
+
+ for (int index = 0; index < (16 * 16 * 16); ++index) {
+ final BlockState state = states.get(index);
+ if (state.getLightEmission() <= 0) {
+ continue;
+ }
+
+ // index = x | (z << 4) | (y << 8)
+ sources.add(new BlockPos(offX | (index & 15), offY | (index >>> 8), offZ | ((index >>> 4) & 15)));
+ }
+ }
+
+ return sources.iterator();
+ } else {
+ // world gen and lighting run in parallel, and if lighting keeps up it can be lighting chunks that are
+ // being generated. In the nether, lava will add a lot of sources. This resulted in quite a few CME crashes.
+ // So all we do spinloop until we can collect a list of sources, and even if it is out of date we will pick up
+ // the missing sources from checkBlock.
+ for (;;) {
+ try {
+ return chunk.getLights().collect(Collectors.toList()).iterator();
+ } catch (final Exception cme) {
+ continue;
+ }
+ }
+ }
+ }
+
+ @Override
+ public void lightChunk(final LightChunkGetter lightAccess, final ChunkAccess chunk, final boolean needsEdgeChecks) {
+ // setup sources
+ final int emittedMask = this.emittedLightMask;
+ for (final Iterator<BlockPos> positions = this.getSources(lightAccess, chunk); positions.hasNext();) {
+ final BlockPos pos = positions.next();
+ final BlockState blockState = this.getBlockState(pos.getX(), pos.getY(), pos.getZ());
+ final int emittedLight = blockState.getLightEmission() & emittedMask;
+
+ if (emittedLight <= this.getLightLevel(pos.getX(), pos.getY(), pos.getZ())) {
+ // some other source is brighter
+ continue;
+ }
+
+ this.appendToIncreaseQueue(
+ ((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))
+ | (blockState.isConditionallyFullOpaque() ? FLAG_HAS_SIDED_TRANSPARENT_BLOCKS : 0)
+ );
+
+
+ // propagation wont set this for us
+ this.setLightLevel(pos.getX(), pos.getY(), pos.getZ(), emittedLight);
+ }
+
+ if (needsEdgeChecks) {
+ // not required to propagate here, but this will reduce the hit of the edge checks
+ this.performLightIncrease(lightAccess);
+
+ // verify neighbour edges
+ this.checkChunkEdges(lightAccess, chunk, this.minLightSection, this.maxLightSection);
+ } else {
+ this.propagateNeighbourLevels(lightAccess, chunk, this.minLightSection, this.maxLightSection);
+
+ this.performLightIncrease(lightAccess);
+ }
+ }
+}
diff --git a/src/main/java/ca/spottedleaf/starlight/common/light/SWMRNibbleArray.java b/src/main/java/ca/spottedleaf/starlight/common/light/SWMRNibbleArray.java
new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
--- /dev/null
+++ b/src/main/java/ca/spottedleaf/starlight/common/light/SWMRNibbleArray.java
@@ -0,0 +0,0 @@
+package ca.spottedleaf.starlight.common.light;
+
+import net.minecraft.world.level.chunk.DataLayer;
+import java.util.ArrayDeque;
+import java.util.Arrays;
+
+// 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
+ protected static final int INIT_STATE_HIDDEN = 3; // initialised, but conversion to Vanilla data should be treated as if NULL
+
+ public static final int ARRAY_SIZE = 16 * 16 * 16 / (8/4); // blocks / bytes per block
+ // this allows us to maintain only 1 byte array when we're not updating
+ static final ThreadLocal<ArrayDeque<byte[]>> WORKING_BYTES_POOL = ThreadLocal.withInitial(ArrayDeque::new);
+
+ private static byte[] allocateBytes() {
+ final byte[] inPool = WORKING_BYTES_POOL.get().pollFirst();
+ if (inPool != null) {
+ return inPool;
+ }
+
+ return new byte[ARRAY_SIZE];
+ }
+
+ private static void freeBytes(final byte[] bytes) {
+ WORKING_BYTES_POOL.get().addFirst(bytes);
+ }
+
+ public static SWMRNibbleArray fromVanilla(final DataLayer nibble) {
+ if (nibble == null) {
+ return new SWMRNibbleArray(null, true);
+ } else if (nibble.isEmpty()) {
+ return new SWMRNibbleArray();
+ } else {
+ return new SWMRNibbleArray(nibble.getData().clone()); // make sure we don't write to the parameter later
+ }
+ }
+
+ protected int stateUpdating;
+ protected volatile int stateVisible;
+
+ protected byte[] storageUpdating;
+ protected boolean updatingDirty; // only returns whether storageUpdating is dirty
+ protected volatile byte[] storageVisible;
+
+ public SWMRNibbleArray() {
+ this(null, false); // lazy init
+ }
+
+ public SWMRNibbleArray(final byte[] bytes) {
+ this(bytes, false);
+ }
+
+ public SWMRNibbleArray(final byte[] bytes, final boolean isNullNibble) {
+ if (bytes != null && bytes.length != ARRAY_SIZE) {
+ throw new IllegalArgumentException("Data of wrong length: " + bytes.length);
+ }
+ this.stateVisible = this.stateUpdating = bytes == null ? (isNullNibble ? INIT_STATE_NULL : INIT_STATE_UNINIT) : INIT_STATE_INIT;
+ this.storageUpdating = this.storageVisible = bytes;
+ }
+
+ public SWMRNibbleArray(final byte[] bytes, final int state) {
+ if (bytes != null && bytes.length != ARRAY_SIZE) {
+ throw new IllegalArgumentException("Data of wrong length: " + bytes.length);
+ }
+ if (bytes == null && (state == INIT_STATE_INIT || state == INIT_STATE_HIDDEN)) {
+ throw new IllegalArgumentException("Data cannot be null and have state be initialised");
+ }
+ this.stateUpdating = this.stateVisible = state;
+ this.storageUpdating = this.storageVisible = bytes;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder stringBuilder = new StringBuilder();
+ stringBuilder.append("State: ");
+ switch (this.stateVisible) {
+ case INIT_STATE_NULL:
+ stringBuilder.append("null");
+ break;
+ case INIT_STATE_UNINIT:
+ stringBuilder.append("uninitialised");
+ break;
+ case INIT_STATE_INIT:
+ stringBuilder.append("initialised");
+ break;
+ case INIT_STATE_HIDDEN:
+ stringBuilder.append("hidden");
+ break;
+ default:
+ stringBuilder.append("unknown");
+ break;
+ }
+ stringBuilder.append("\nData:\n");
+
+ final byte[] data = this.storageVisible;
+ if (data != null) {
+ for (int i = 0; i < 4096; ++i) {
+ // Copied from NibbleArray#toString
+ final int level = ((data[i >>> 1] >>> ((i & 1) << 2)) & 0xF);
+
+ stringBuilder.append(Integer.toHexString(level));
+ if ((i & 15) == 15) {
+ stringBuilder.append("\n");
+ }
+
+ if ((i & 255) == 255) {
+ stringBuilder.append("\n");
+ }
+ }
+ } else {
+ stringBuilder.append("null");
+ }
+
+ return stringBuilder.toString();
+ }
+
+ public SaveState getSaveState() {
+ synchronized (this) {
+ final int state = this.stateVisible;
+ final byte[] data = this.storageVisible;
+ if (state == INIT_STATE_NULL) {
+ return null;
+ }
+ if (state == INIT_STATE_UNINIT) {
+ return new SaveState(null, state);
+ }
+ final boolean zero = isAllZero(data);
+ if (zero) {
+ return state == INIT_STATE_INIT ? new SaveState(null, INIT_STATE_UNINIT) : null;
+ } else {
+ return new SaveState(data.clone(), state);
+ }
+ }
+ }
+
+ protected static boolean isAllZero(final byte[] data) {
+ for (int i = 0; i < (ARRAY_SIZE >>> 4); ++i) {
+ byte whole = data[i << 4];
+
+ for (int k = 1; k < (1 << 4); ++k) {
+ whole |= data[(i << 4) | k];
+ }
+
+ if (whole != 0) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ // 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;
+
2022-06-08 13:44:30 +02:00
+ if (!this.updatingDirty) {
+ if (this.storageUpdating != null) {
+ into = this.storageUpdating = allocateBytes();
+ } else {
+ this.storageUpdating = into = allocateBytes();
+ this.stateUpdating = INIT_STATE_INIT;
+ }
+ this.updatingDirty = true;
2021-11-27 11:42:09 +01:00
+ } else {
2022-06-08 13:44:30 +02:00
+ into = this.storageUpdating;
2021-11-27 11:42:09 +01:00
+ }
+
+ 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);
+ }
+ }
+
+ // operation type: updating
+ public void setFull() {
+ if (this.stateUpdating != INIT_STATE_HIDDEN) {
+ this.stateUpdating = INIT_STATE_INIT;
+ }
+ Arrays.fill(this.storageUpdating == null || !this.updatingDirty ? this.storageUpdating = allocateBytes() : this.storageUpdating, (byte)-1);
+ this.updatingDirty = true;
+ }
+
+ // operation type: updating
+ public void setZero() {
+ if (this.stateUpdating != INIT_STATE_HIDDEN) {
+ this.stateUpdating = INIT_STATE_INIT;
+ }
+ Arrays.fill(this.storageUpdating == null || !this.updatingDirty ? this.storageUpdating = allocateBytes() : this.storageUpdating, (byte)0);
+ this.updatingDirty = true;
+ }
+
+ // operation type: updating
+ public void setNonNull() {
+ if (this.stateUpdating == INIT_STATE_HIDDEN) {
+ this.stateUpdating = INIT_STATE_INIT;
+ return;
+ }
+ if (this.stateUpdating != INIT_STATE_NULL) {
+ return;
+ }
+ this.stateUpdating = INIT_STATE_UNINIT;
+ }
+
+ // operation type: updating
+ public void setNull() {
+ this.stateUpdating = INIT_STATE_NULL;
+ if (this.updatingDirty && this.storageUpdating != null) {
+ freeBytes(this.storageUpdating);
+ }
+ this.storageUpdating = null;
+ this.updatingDirty = false;
+ }
+
+ // operation type: updating
+ public void setUninitialised() {
+ this.stateUpdating = INIT_STATE_UNINIT;
+ if (this.storageUpdating != null && this.updatingDirty) {
+ freeBytes(this.storageUpdating);
+ }
+ this.storageUpdating = null;
+ this.updatingDirty = false;
+ }
+
+ // operation type: updating
+ public void setHidden() {
+ if (this.stateUpdating == INIT_STATE_HIDDEN) {
+ return;
+ }
+ if (this.stateUpdating != INIT_STATE_INIT) {
+ this.setNull();
+ } else {
+ this.stateUpdating = INIT_STATE_HIDDEN;
+ }
+ }
+
+ // 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;
+ }
+
+ // operation type: visible
+ public boolean isInitialisedVisible() {
+ return this.stateVisible == INIT_STATE_INIT;
+ }
+
+ // operation type: updating
+ public boolean isHiddenUpdating() {
+ return this.stateUpdating == INIT_STATE_HIDDEN;
+ }
+
+ // operation type: updating
+ public boolean isHiddenVisible() {
+ return this.stateVisible == INIT_STATE_HIDDEN;
+ }
+
+ // operation type: updating
+ protected void swapUpdatingAndMarkDirty() {
+ if (this.updatingDirty) {
+ return;
+ }
+
+ 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);
+ }
+
+ if (this.stateUpdating != INIT_STATE_HIDDEN) {
+ this.stateUpdating = INIT_STATE_INIT;
+ }
+ this.updatingDirty = true;
+ }
+
+ // operation type: updating
+ public boolean updateVisible() {
+ if (!this.isDirty()) {
+ return false;
+ }
+
+ synchronized (this) {
+ if (this.stateUpdating == INIT_STATE_NULL || this.stateUpdating == INIT_STATE_UNINIT) {
+ this.storageVisible = null;
+ } else {
+ if (this.storageVisible == null) {
+ this.storageVisible = this.storageUpdating.clone();
+ } else {
+ if (this.storageUpdating != this.storageVisible) {
+ System.arraycopy(this.storageUpdating, 0, this.storageVisible, 0, ARRAY_SIZE);
+ }
+ }
+
+ if (this.storageUpdating != this.storageVisible) {
+ freeBytes(this.storageUpdating);
+ }
+ this.storageUpdating = this.storageVisible;
+ }
+ this.updatingDirty = false;
+ this.stateVisible = this.stateUpdating;
+ }
+
+ return true;
+ }
+
+ // operation type: visible
+ public DataLayer toVanillaNibble() {
+ synchronized (this) {
+ switch (this.stateVisible) {
+ case INIT_STATE_HIDDEN:
+ case INIT_STATE_NULL:
+ return null;
+ case INIT_STATE_UNINIT:
+ return new DataLayer();
+ case INIT_STATE_INIT:
+ return new DataLayer(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
+ final byte[] bytes = this.storageUpdating;
+ if (bytes == null) {
+ return 0;
+ }
+ final byte value = bytes[index >>> 1];
+
+ // if we are an even index, we want lower 4 bits
+ // if we are an odd index, we want upper 4 bits
+ 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) {
+ // indices range from 0 -> 4096
+ final byte[] visibleBytes = this.storageVisible;
+ if (visibleBytes == null) {
+ return 0;
+ }
+ 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
+ return ((value >>> ((index & 1) << 2)) & 0xF);
+ }
+
+ // 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.updatingDirty) {
+ this.swapUpdatingAndMarkDirty();
+ }
+ final int shift = (index & 1) << 2;
+ final int i = index >>> 1;
+
+ this.storageUpdating[i] = (byte)((this.storageUpdating[i] & (0xF0 >>> shift)) | (value << shift));
+ }
+
+ public static final class SaveState {
+
+ public final byte[] data;
+ public final int state;
+
+ public SaveState(final byte[] data, final int state) {
+ this.data = data;
+ this.state = state;
+ }
+ }
+}
diff --git a/src/main/java/ca/spottedleaf/starlight/common/light/SkyStarLightEngine.java b/src/main/java/ca/spottedleaf/starlight/common/light/SkyStarLightEngine.java
new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
--- /dev/null
+++ b/src/main/java/ca/spottedleaf/starlight/common/light/SkyStarLightEngine.java
@@ -0,0 +0,0 @@
+package ca.spottedleaf.starlight.common.light;
+
+import ca.spottedleaf.starlight.common.util.WorldUtil;
+import it.unimi.dsi.fastutil.shorts.ShortCollection;
+import it.unimi.dsi.fastutil.shorts.ShortIterator;
+import net.minecraft.core.BlockPos;
+import net.minecraft.world.level.BlockGetter;
+import net.minecraft.world.level.ChunkPos;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.level.chunk.ChunkAccess;
+import net.minecraft.world.level.chunk.ChunkStatus;
+import net.minecraft.world.level.chunk.LevelChunkSection;
+import net.minecraft.world.level.chunk.LightChunkGetter;
+import net.minecraft.world.phys.shapes.Shapes;
+import net.minecraft.world.phys.shapes.VoxelShape;
+import java.util.Arrays;
+import java.util.Set;
+
+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.
+ */
+
+ protected final int[] heightMapBlockChange = new int[16 * 16];
+ {
+ Arrays.fill(this.heightMapBlockChange, Integer.MIN_VALUE); // clear heightmap
+ }
+
+ protected final boolean[] nullPropagationCheckCache;
+
+ public SkyStarLightEngine(final Level world) {
+ super(true, world);
+ this.nullPropagationCheckCache = new boolean[WorldUtil.getTotalLightSections(world)];
+ }
+
+ @Override
+ protected void initNibble(final int chunkX, final int chunkY, final int chunkZ, final boolean extrude, final boolean initRemovedNibbles) {
+ if (chunkY < this.minLightSection || chunkY > this.maxLightSection || 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.initNibble(nibble, chunkX, chunkY, chunkZ, extrude);
+ }
+
+ @Override
+ protected void setNibbleNull(final int chunkX, final int chunkY, final int chunkZ) {
+ final SWMRNibbleArray nibble = this.getNibbleFromCache(chunkX, chunkY, chunkZ);
+ if (nibble != null) {
+ nibble.setNull();
+ }
+ }
+
+ protected final void initNibble(final SWMRNibbleArray currNibble, final int chunkX, final int chunkY, final int chunkZ, final boolean extrude) {
+ if (!currNibble.isNullNibbleUpdating()) {
+ // already initialised
+ return;
+ }
+
+ final boolean[] emptinessMap = this.getEmptinessMap(chunkX, chunkZ);
+
+ // are we above this chunk's lowest empty section?
+ int lowestY = this.minLightSection - 1;
+ for (int currY = this.maxSection; currY >= this.minSection; --currY) {
+ if (emptinessMap == null) {
+ // cannot delay nibble init for lit chunks, as we need to init to propagate into them.
+ final LevelChunkSection current = this.getChunkSection(chunkX, currY, chunkZ);
+ if (current == null || current.hasOnlyAir()) {
+ continue;
+ }
+ } else {
+ if (emptinessMap[currY - this.minSection]) {
+ continue;
+ }
+ }
+
+ // should always be full lit here
+ lowestY = currY;
+ break;
+ }
+
+ if (chunkY > lowestY) {
+ // we need to set this one to full
+ final SWMRNibbleArray nibble = this.getNibbleFromCache(chunkX, chunkY, chunkZ);
+ nibble.setNonNull();
+ nibble.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 <= this.maxLightSection; ++currY) {
+ final SWMRNibbleArray nibble = this.getNibbleFromCache(chunkX, currY, chunkZ);
+ if (nibble != null && !nibble.isNullNibbleUpdating()) {
+ currNibble.setNonNull();
+ currNibble.extrudeLower(nibble);
+ break;
+ }
+ }
+ } else {
+ currNibble.setNonNull();
+ }
+ }
+
+ protected final void rewriteNibbleCacheForSkylight(final ChunkAccess 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;
+ nibble.updateVisible();
+ }
+ }
+ }
+
+ // 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 < this.minLightSection || chunkY > this.maxLightSection || this.nullPropagationCheckCache[chunkY - this.minLightSection]) {
+ return false;
+ }
+ this.nullPropagationCheckCache[chunkY - this.minLightSection] = 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.initNibble(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 > this.maxLightSection) {
+ return 15;
+ }
+
+ nibble = this.getNibbleFromCache(chunkX, chunkY, chunkZ);
+
+ if (nibble != null) {
+ return nibble.getUpdating(worldX, 0, worldZ);
+ }
+ }
+ }
+
+ @Override
+ protected boolean[] getEmptinessMap(final ChunkAccess chunk) {
+ return chunk.getSkyEmptinessMap();
+ }
+
+ @Override
+ protected void setEmptinessMap(final ChunkAccess chunk, final boolean[] to) {
+ chunk.setSkyEmptinessMap(to);
+ }
+
+ @Override
+ protected SWMRNibbleArray[] getNibblesOnChunk(final ChunkAccess chunk) {
+ return chunk.getSkyNibbles();
+ }
+
+ @Override
+ protected void setNibbles(final ChunkAccess chunk, final SWMRNibbleArray[] to) {
+ chunk.setSkyNibbles(to);
+ }
+
+ @Override
+ protected boolean canUseChunk(final ChunkAccess chunk) {
+ // can only use chunks for sky stuff if their sections have been init'd
+ return chunk.getStatus().isOrAfter(ChunkStatus.LIGHT) && (this.isClientSide || chunk.isLightCorrect());
+ }
+
+ @Override
+ protected void checkChunkEdges(final LightChunkGetter lightAccess, final ChunkAccess chunk, final int fromSection,
+ final int toSection) {
+ Arrays.fill(this.nullPropagationCheckCache, false);
+ this.rewriteNibbleCacheForSkylight(chunk);
+ final int chunkX = chunk.getPos().x;
+ final int chunkZ = chunk.getPos().z;
+ for (int y = toSection; y >= fromSection; --y) {
+ this.checkNullSection(chunkX, y, chunkZ, true);
+ }
+
+ super.checkChunkEdges(lightAccess, chunk, fromSection, toSection);
+ }
+
+ @Override
+ protected void checkChunkEdges(final LightChunkGetter lightAccess, final ChunkAccess chunk, final ShortCollection sections) {
+ Arrays.fill(this.nullPropagationCheckCache, false);
+ this.rewriteNibbleCacheForSkylight(chunk);
+ final int chunkX = chunk.getPos().x;
+ final int chunkZ = chunk.getPos().z;
+ for (final ShortIterator iterator = sections.iterator(); iterator.hasNext();) {
+ final int y = (int)iterator.nextShort();
+ this.checkNullSection(chunkX, y, chunkZ, true);
+ }
+
+ super.checkChunkEdges(lightAccess, chunk, sections);
+ }
+
+ @Override
+ protected void checkBlock(final LightChunkGetter lightAccess, final int worldX, final int worldY, final int worldZ) {
+ // blocks can change opacity
+ // blocks can change direction of propagation
+
+ // same logic applies from BlockStarLightEngine#checkBlock
+
+ final int encodeOffset = this.coordinateOffset;
+
+ final int currentLevel = this.getLightLevel(worldX, worldY, worldZ);
+
+ if (currentLevel == 15) {
+ // must re-propagate clobbered source
+ this.appendToIncreaseQueue(
+ ((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
+ );
+ } else {
+ this.setLightLevel(worldX, worldY, worldZ, 0);
+ }
+
+ this.appendToDecreaseQueue(
+ ((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 BlockPos.MutableBlockPos recalcCenterPos = new BlockPos.MutableBlockPos();
+ protected final BlockPos.MutableBlockPos recalcNeighbourPos = new BlockPos.MutableBlockPos();
+
+ @Override
+ protected int calculateLightValue(final LightChunkGetter lightAccess, final int worldX, final int worldY, final int worldZ,
+ final int expect) {
+ if (expect == 15) {
+ return expect;
+ }
+
+ final int sectionOffset = this.chunkSectionIndexOffset;
+ final BlockState centerState = this.getBlockState(worldX, worldY, worldZ);
+ int opacity = centerState.getOpacityIfCached();
+
+ final BlockState conditionallyOpaqueState;
+ if (opacity < 0) {
+ this.recalcCenterPos.set(worldX, worldY, worldZ);
+ opacity = Math.max(1, centerState.getLightBlock(lightAccess.getLevel(), this.recalcCenterPos));
+ if (centerState.isConditionallyFullOpaque()) {
+ conditionallyOpaqueState = centerState;
+ } else {
+ conditionallyOpaqueState = null;
+ }
+ } else {
+ conditionallyOpaqueState = null;
+ opacity = Math.max(1, opacity);
+ }
+
+ int level = 0;
+
+ for (final AxisDirection direction : AXIS_DIRECTIONS) {
+ final int offX = worldX + direction.x;
+ final int offY = worldY + direction.y;
+ final int offZ = worldZ + direction.z;
+
+ final int sectionIndex = (offX >> 4) + 5 * (offZ >> 4) + (5 * 5) * (offY >> 4) + sectionOffset;
+
+ final int neighbourLevel = this.getLightLevel(sectionIndex, (offX & 15) | ((offZ & 15) << 4) | ((offY & 15) << 8));
+
+ if ((neighbourLevel - 1) <= level) {
+ // don't need to test transparency, we know it wont affect the result.
+ continue;
+ }
+
+ final BlockState neighbourState = this.getBlockState(offX, offY, offZ);
+
+ if (neighbourState.isConditionallyFullOpaque()) {
+ // here the block can be conditionally opaque (i.e light cannot propagate from it), so we need to test that
+ // we don't read the blockstate because most of the time this is false, so using the faster
+ // known transparency lookup results in a net win
+ this.recalcNeighbourPos.set(offX, offY, offZ);
+ final VoxelShape neighbourFace = neighbourState.getFaceOcclusionShape(lightAccess.getLevel(), this.recalcNeighbourPos, direction.opposite.nms);
+ final VoxelShape thisFace = conditionallyOpaqueState == null ? Shapes.empty() : conditionallyOpaqueState.getFaceOcclusionShape(lightAccess.getLevel(), this.recalcCenterPos, direction.nms);
+ if (Shapes.faceShapeOccludes(thisFace, neighbourFace)) {
+ // not allowed to propagate
+ continue;
+ }
+ }
+
+ final int calculated = neighbourLevel - opacity;
+ level = Math.max(calculated, level);
+ if (level > expect) {
+ return level;
+ }
+ }
+
+ return level;
+ }
+
+ @Override
+ protected void propagateBlockChanges(final LightChunkGetter lightAccess, final ChunkAccess atChunk, final Set<BlockPos> positions) {
+ this.rewriteNibbleCacheForSkylight(atChunk);
+ Arrays.fill(this.nullPropagationCheckCache, false);
+
+ final BlockGetter world = lightAccess.getLevel();
+ final int chunkX = atChunk.getPos().x;
+ final int chunkZ = atChunk.getPos().z;
+ final int heightMapOffset = chunkX * -16 + (chunkZ * (-16 * 16));
+
+ // setup heightmap for changes
+ for (final BlockPos pos : positions) {
+ final int index = pos.getX() + (pos.getZ() << 4) + heightMapOffset;
+ final int curr = this.heightMapBlockChange[index];
+ if (pos.getY() > curr) {
+ this.heightMapBlockChange[index] = pos.getY();
+ }
+ }
+
+ // note: light sets are delayed while processing skylight source changes due to how
+ // nibbles are initialised, as we want to avoid clobbering nibble values so what when
+ // below nibbles are initialised they aren't reading from partially modified nibbles
+
+ // now we can recalculate the sources for the changed columns
+ for (int index = 0; index < (16 * 16); ++index) {
+ final int maxY = this.heightMapBlockChange[index];
+ if (maxY == Integer.MIN_VALUE) {
+ // not changed
+ continue;
+ }
+ this.heightMapBlockChange[index] = Integer.MIN_VALUE; // 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
+ // delay light set until after processing all sources to setup
+ final int maxPropagationY = this.tryPropagateSkylight(world, columnX, maxY, columnZ, true, true);
+
+ // maxPropagationY is now the highest block that could not be propagated to
+
+ // remove all sources below that are 15
+ final long propagateDirection = AxisDirection.POSITIVE_Y.everythingButThisDirection;
+ final int encodeOffset = this.coordinateOffset;
+
+ 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 >= (this.minLightSection << 4); --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;
+ }
+
+ // delay light set until after processing all sources to setup
+ this.appendToDecreaseQueue(
+ ((columnX + (columnZ << 6) + (currY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1))
+ | (15L << (6 + 6 + 16))
+ | (propagateDirection << (6 + 6 + 16 + 4))
+ // do not set transparent blocks for the same reason we don't in the checkBlock method
+ );
+ }
+ }
+ }
+
+ // delayed light sets are processed here, and must be processed before checkBlock as checkBlock reads
+ // immediate light value
+ this.processDelayedIncreases();
+ this.processDelayedDecreases();
+
+ for (final BlockPos pos : positions) {
+ this.checkBlock(lightAccess, pos.getX(), pos.getY(), pos.getZ());
+ }
+
+ this.performLightDecrease(lightAccess);
+ }
+
+ protected final int[] heightMapGen = new int[32 * 32];
+
+ @Override
+ protected void lightChunk(final LightChunkGetter lightAccess, final ChunkAccess chunk, final boolean needsEdgeChecks) {
+ this.rewriteNibbleCacheForSkylight(chunk);
+ Arrays.fill(this.nullPropagationCheckCache, false);
+
+ final BlockGetter world = lightAccess.getLevel();
+ final ChunkPos chunkPos = chunk.getPos();
+ final int chunkX = chunkPos.x;
+ final int chunkZ = chunkPos.z;
+
+ final LevelChunkSection[] sections = chunk.getSections();
+
+ int highestNonEmptySection = this.maxSection;
+ while (highestNonEmptySection == (this.minSection - 1) ||
+ sections[highestNonEmptySection - this.minSection] == null || sections[highestNonEmptySection - this.minSection].hasOnlyAir()) {
+ this.checkNullSection(chunkX, highestNonEmptySection, chunkZ, false);
+ // try propagate FULL to neighbours
+
+ // check neighbours to see if we need to propagate into them
+ for (final AxisDirection direction : ONLY_HORIZONTAL_DIRECTIONS) {
+ final int neighbourX = chunkX + direction.x;
+ final int neighbourZ = chunkZ + direction.z;
+ final SWMRNibbleArray neighbourNibble = this.getNibbleFromCache(neighbourX, highestNonEmptySection, neighbourZ);
+ if (neighbourNibble == null) {
+ // unloaded neighbour
+ // most of the time we fall here
+ continue;
+ }
+
+ // it looks like we need to propagate into the neighbour
+
+ final int incX;
+ final int incZ;
+ final int startX;
+ final int startZ;
+
+ if (direction.x != 0) {
+ // x direction
+ incX = 0;
+ incZ = 1;
+
+ if (direction.x < 0) {
+ // negative
+ startX = chunkX << 4;
+ } else {
+ startX = chunkX << 4 | 15;
+ }
+ startZ = chunkZ << 4;
+ } else {
+ // z direction
+ incX = 1;
+ incZ = 0;
+
+ if (direction.z < 0) {
+ // negative
+ startZ = chunkZ << 4;
+ } else {
+ startZ = chunkZ << 4 | 15;
+ }
+ startX = chunkX << 4;
+ }
+
+ final int encodeOffset = this.coordinateOffset;
+ 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.appendToIncreaseQueue(
+ ((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)
+ );
+ }
+ }
+ }
+
+ if (highestNonEmptySection-- == (this.minSection - 1)) {
+ break;
+ }
+ }
+
+ if (highestNonEmptySection >= this.minSection) {
+ // 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;
+ for (int currZ = minZ; currZ <= maxZ; ++currZ) {
+ for (int currX = minX; currX <= maxX; ++currX) {
+ this.tryPropagateSkylight(world, currX, startY + 1, currZ, false, false);
+ }
+ }
+ } // else: apparently the chunk is empty
+
+ if (needsEdgeChecks) {
+ // not required to propagate here, but this will reduce the hit of the edge checks
+ this.performLightIncrease(lightAccess);
+
+ for (int y = highestNonEmptySection; y >= this.minLightSection; --y) {
+ this.checkNullSection(chunkX, y, chunkZ, false);
+ }
+ // no need to rewrite the nibble cache again
+ super.checkChunkEdges(lightAccess, chunk, this.minLightSection, highestNonEmptySection);
+ } else {
+ for (int y = highestNonEmptySection; y >= this.minLightSection; --y) {
+ this.checkNullSection(chunkX, y, chunkZ, false);
+ }
+ this.propagateNeighbourLevels(lightAccess, chunk, this.minLightSection, highestNonEmptySection);
+
+ this.performLightIncrease(lightAccess);
+ }
+ }
+
+ protected final void processDelayedIncreases() {
+ // copied from performLightIncrease
+ final long[] queue = this.increaseQueue;
+ final int decodeOffsetX = -this.encodeOffsetX;
+ final int decodeOffsetY = -this.encodeOffsetY;
+ final int decodeOffsetZ = -this.encodeOffsetZ;
+
+ for (int i = 0, len = this.increaseQueueInitialLength; i < len; ++i) {
+ final long queueValue = queue[i];
+
+ 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);
+
+ this.setLightLevel(posX, posY, posZ, propagatedLightLevel);
+ }
+ }
+
+ protected final void processDelayedDecreases() {
+ // copied from performLightDecrease
+ final long[] queue = this.decreaseQueue;
+ final int decodeOffsetX = -this.encodeOffsetX;
+ final int decodeOffsetY = -this.encodeOffsetY;
+ final int decodeOffsetZ = -this.encodeOffsetZ;
+
+ for (int i = 0, len = this.decreaseQueueInitialLength; i < len; ++i) {
+ final long queueValue = queue[i];
+
+ 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;
+
+ this.setLightLevel(posX, posY, posZ, 0);
+ }
+ }
+
+ // delaying the light set is useful for block changes since they need to worry about initialising nibblearrays
+ // while also queueing light at the same time (initialising nibblearrays might depend on nibbles above, so
+ // clobbering the light values will result in broken propagation)
+ protected final int tryPropagateSkylight(final BlockGetter world, final int worldX, int startY, final int worldZ,
+ final boolean extrudeInitialised, final boolean delayLightSet) {
+ final BlockPos.MutableBlockPos mutablePos = this.mutablePos3;
+ final int encodeOffset = this.coordinateOffset;
+ final long propagateDirection = AxisDirection.POSITIVE_Y.everythingButThisDirection; // just don't check upwards.
+
+ 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);
+
+ for (;startY >= (this.minLightSection << 4); --startY) {
+ if ((startY & 15) == 15) {
+ // ensure this section is always checked
+ this.checkNullSection(worldX >> 4, startY >> 4, worldZ >> 4, extrudeInitialised);
+ }
+ final BlockState current = this.getBlockState(worldX, startY, worldZ);
+
+ final VoxelShape fromShape;
+ if (above.isConditionallyFullOpaque()) {
+ this.mutablePos2.set(worldX, startY + 1, worldZ);
+ fromShape = above.getFaceOcclusionShape(world, this.mutablePos2, AxisDirection.NEGATIVE_Y.nms);
+ if (Shapes.faceShapeOccludes(Shapes.empty(), fromShape)) {
+ // above wont let us propagate
+ break;
+ }
+ } else {
+ fromShape = Shapes.empty();
+ }
+
+ final int opacityIfCached = current.getOpacityIfCached();
+ // does light propagate from the top down?
+ if (opacityIfCached != -1) {
+ if (opacityIfCached != 0) {
+ // we cannot propagate 15 through this
+ break;
+ }
+ // most of the time it falls here.
+ // add to propagate
+ // light set delayed until we determine if this nibble section is null
+ this.appendToIncreaseQueue(
+ ((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))
+ );
+ } else {
+ mutablePos.set(worldX, startY, worldZ);
+ long flags = 0L;
+ if (current.isConditionallyFullOpaque()) {
+ final VoxelShape cullingFace = current.getFaceOcclusionShape(world, mutablePos, AxisDirection.POSITIVE_Y.nms);
+
+ if (Shapes.faceShapeOccludes(fromShape, cullingFace)) {
+ // can't propagate here, we're done on this column.
+ break;
+ }
+ flags |= FLAG_HAS_SIDED_TRANSPARENT_BLOCKS;
+ }
+
+ final int opacity = current.getLightBlock(world, mutablePos);
+ if (opacity > 0) {
+ // let the queued value (if any) handle it from here.
+ break;
+ }
+
+ // light set delayed until we determine if this nibble section is null
+ this.appendToIncreaseQueue(
+ ((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;
+ } else if (!delayLightSet) {
+ this.setLightLevel(worldX, startY, worldZ, 15);
+ }
+ }
+
+ 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
new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
--- /dev/null
+++ b/src/main/java/ca/spottedleaf/starlight/common/light/StarLightEngine.java
@@ -0,0 +0,0 @@
+package ca.spottedleaf.starlight.common.light;
+
+import ca.spottedleaf.starlight.common.util.CoordinateUtils;
+import ca.spottedleaf.starlight.common.util.IntegerUtil;
+import ca.spottedleaf.starlight.common.util.WorldUtil;
+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
+import it.unimi.dsi.fastutil.shorts.ShortCollection;
+import it.unimi.dsi.fastutil.shorts.ShortIterator;
+import net.minecraft.core.BlockPos;
+import net.minecraft.core.Direction;
+import net.minecraft.core.SectionPos;
+import net.minecraft.world.level.BlockGetter;
+import net.minecraft.world.level.ChunkPos;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.LevelHeightAccessor;
+import net.minecraft.world.level.LightLayer;
+import net.minecraft.world.level.block.Blocks;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.level.chunk.ChunkAccess;
+import net.minecraft.world.level.chunk.LevelChunkSection;
+import net.minecraft.world.level.chunk.LightChunkGetter;
+import net.minecraft.world.phys.shapes.Shapes;
+import net.minecraft.world.phys.shapes.VoxelShape;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.function.IntConsumer;
+
+public abstract class StarLightEngine {
+
+ protected static final BlockState AIR_BLOCK_STATE = Blocks.AIR.defaultBlockState();
+
+ protected static final AxisDirection[] DIRECTIONS = AxisDirection.values();
+ protected static final AxisDirection[] AXIS_DIRECTIONS = DIRECTIONS;
+ protected static final AxisDirection[] ONLY_HORIZONTAL_DIRECTIONS = new AxisDirection[] {
+ AxisDirection.POSITIVE_X, AxisDirection.NEGATIVE_X,
+ AxisDirection.POSITIVE_Z, AxisDirection.NEGATIVE_Z
+ };
+
+ protected static enum AxisDirection {
+
+ // Declaration order is important and relied upon. Do not change without modifying propagation code.
+ POSITIVE_X(1, 0, 0), NEGATIVE_X(-1, 0, 0),
+ POSITIVE_Z(0, 0, 1), NEGATIVE_Z(0, 0, -1),
+ POSITIVE_Y(0, 1, 0), NEGATIVE_Y(0, -1, 0);
+
+ static {
+ POSITIVE_X.opposite = NEGATIVE_X; NEGATIVE_X.opposite = POSITIVE_X;
+ POSITIVE_Z.opposite = NEGATIVE_Z; NEGATIVE_Z.opposite = POSITIVE_Z;
+ POSITIVE_Y.opposite = NEGATIVE_Y; NEGATIVE_Y.opposite = POSITIVE_Y;
+ }
+
+ protected AxisDirection opposite;
+
+ public final int x;
+ 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.fromNormal(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() {
+ return this.opposite;
+ }
+ }
+
+ // I'd like to thank https://www.seedofandromeda.com/blogs/29-fast-flood-fill-lighting-in-a-blocky-voxel-game-pt-1
+ // for explaining how light propagates via breadth-first search
+
+ // While the above is a good start to understanding the general idea of what the general principles are, it's not
+ // exactly how the vanilla light engine should behave for minecraft.
+
+ // similar to the above, except the chunk section indices vary from [-1, 1], or [0, 2]
+ // for the y chunk section it's from [minLightSection, maxLightSection] or [0, maxLightSection - minLightSection]
+ // index = x + (z * 5) + (y * 25)
+ // null index indicates the chunk section doesn't exist (empty or out of bounds)
+ protected final LevelChunkSection[] sectionCache;
+
+ // the exact same as above, except for storing fast access to SWMRNibbleArray
+ // for the y chunk section it's from [minLightSection, maxLightSection] or [0, maxLightSection - minLightSection]
+ // index = x + (z * 5) + (y * 25)
+ protected final SWMRNibbleArray[] nibbleCache;
+
+ // 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 [minLightSection, maxLightSection] or [0, maxLightSection - minLightSection]
+ // index = x + (z * 5) + (y * 25)
+ protected final boolean[] notifyUpdateCache;
+
+ // always initialsed during start of lighting.
+ // index = x + (z * 5)
+ protected final ChunkAccess[] chunkCache = new ChunkAccess[5 * 5];
+
+ // index = x + (z * 5)
+ protected final boolean[][] emptinessMapCache = new boolean[5 * 5][];
+
+ protected final BlockPos.MutableBlockPos mutablePos1 = new BlockPos.MutableBlockPos();
+ protected final BlockPos.MutableBlockPos mutablePos2 = new BlockPos.MutableBlockPos();
+ protected final BlockPos.MutableBlockPos mutablePos3 = new BlockPos.MutableBlockPos();
+
+ protected int encodeOffsetX;
+ protected int encodeOffsetY;
+ protected int encodeOffsetZ;
+
+ protected int coordinateOffset;
+
+ protected int chunkOffsetX;
+ protected int chunkOffsetY;
+ protected int chunkOffsetZ;
+
+ protected int chunkIndexOffset;
+ protected int chunkSectionIndexOffset;
+
+ protected final boolean skylightPropagator;
+ protected final int emittedLightMask;
+ protected final boolean isClientSide;
+
+ protected final Level world;
+ protected final int minLightSection;
+ protected final int maxLightSection;
+ protected final int minSection;
+ protected final int maxSection;
+
+ protected StarLightEngine(final boolean skylightPropagator, final Level world) {
+ this.skylightPropagator = skylightPropagator;
+ this.emittedLightMask = skylightPropagator ? 0 : 0xF;
+ this.isClientSide = world.isClientSide;
+ this.world = world;
+ this.minLightSection = WorldUtil.getMinLightSection(world);
+ this.maxLightSection = WorldUtil.getMaxLightSection(world);
+ this.minSection = WorldUtil.getMinSection(world);
+ this.maxSection = WorldUtil.getMaxSection(world);
+
+ this.sectionCache = new LevelChunkSection[5 * 5 * ((this.maxLightSection - this.minLightSection + 1) + 2)]; // add two extra sections for buffer
+ this.nibbleCache = new SWMRNibbleArray[5 * 5 * ((this.maxLightSection - this.minLightSection + 1) + 2)]; // add two extra sections for buffer
+ this.notifyUpdateCache = new boolean[5 * 5 * ((this.maxLightSection - this.minLightSection + 1) + 2)]; // add two extra sections for buffer
+ }
+
+ protected final void setupEncodeOffset(final int centerX, final int centerY, final int centerZ) {
+ // 31 = center + encodeOffset
+ this.encodeOffsetX = 31 - centerX;
+ this.encodeOffsetY = (-(this.minLightSection - 1) << 4); // we want 0 to be the smallest encoded value
+ this.encodeOffsetZ = 31 - centerZ;
+
+ // coordinateIndex = x | (z << 6) | (y << 12)
+ this.coordinateOffset = this.encodeOffsetX + (this.encodeOffsetZ << 6) + (this.encodeOffsetY << 12);
+
+ // 2 = (centerX >> 4) + chunkOffset
+ this.chunkOffsetX = 2 - (centerX >> 4);
+ this.chunkOffsetY = -(this.minLightSection - 1); // lowest should be 0
+ this.chunkOffsetZ = 2 - (centerZ >> 4);
+
+ // chunk index = x + (5 * z)
+ this.chunkIndexOffset = this.chunkOffsetX + (5 * this.chunkOffsetZ);
+
+ // chunk section index = x + (5 * z) + ((5*5) * y)
+ this.chunkSectionIndexOffset = this.chunkIndexOffset + ((5 * 5) * this.chunkOffsetY);
+ }
+
+ protected final void setupCaches(final LightChunkGetter chunkProvider, final int centerX, final int centerY, final int centerZ,
+ final boolean relaxed, final boolean tryToLoadChunksFor2Radius) {
+ final int centerChunkX = centerX >> 4;
+ final int centerChunkY = centerY >> 4;
+ final int centerChunkZ = centerZ >> 4;
+
+ this.setupEncodeOffset(centerChunkX * 16 + 7, centerChunkY * 16 + 7, centerChunkZ * 16 + 7);
+
+ final int radius = tryToLoadChunksFor2Radius ? 2 : 1;
+
+ for (int dz = -radius; dz <= radius; ++dz) {
+ for (int dx = -radius; dx <= radius; ++dx) {
+ final int cx = centerChunkX + dx;
+ final int cz = centerChunkZ + dz;
+ final boolean isTwoRadius = Math.max(IntegerUtil.branchlessAbs(dx), IntegerUtil.branchlessAbs(dz)) == 2;
+ final ChunkAccess chunk = (ChunkAccess)chunkProvider.getChunkForLighting(cx, cz);
+
+ if (chunk == null) {
+ if (relaxed | isTwoRadius) {
+ continue;
+ }
+ throw new IllegalArgumentException("Trying to propagate light update before 1 radius neighbours ready");
+ }
+
+ if (!this.canUseChunk(chunk)) {
+ continue;
+ }
+
+ this.setChunkInCache(cx, cz, chunk);
+ this.setEmptinessMapCache(cx, cz, this.getEmptinessMap(chunk));
+ if (!isTwoRadius) {
+ this.setBlocksForChunkInCache(cx, cz, chunk.getSections());
+ this.setNibblesForChunkInCache(cx, cz, this.getNibblesOnChunk(chunk));
+ }
+ }
+ }
+ }
+
+ protected final ChunkAccess getChunkInCache(final int chunkX, final int chunkZ) {
+ return this.chunkCache[chunkX + 5*chunkZ + this.chunkIndexOffset];
+ }
+
+ protected final void setChunkInCache(final int chunkX, final int chunkZ, final ChunkAccess chunk) {
+ this.chunkCache[chunkX + 5*chunkZ + this.chunkIndexOffset] = chunk;
+ }
+
+ protected final LevelChunkSection 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 LevelChunkSection section) {
+ this.sectionCache[chunkX + 5*chunkZ + 5*5*chunkY + this.chunkSectionIndexOffset] = section;
+ }
+
+ protected final void setBlocksForChunkInCache(final int chunkX, final int chunkZ, final LevelChunkSection[] sections) {
+ for (int cy = this.minLightSection; cy <= this.maxLightSection; ++cy) {
+ this.setChunkSectionInCache(chunkX, cy, chunkZ,
+ sections == null ? null : (cy >= this.minSection && cy <= this.maxSection ? sections[cy - this.minSection] : null));
+ }
+ }
+
+ protected final SWMRNibbleArray getNibbleFromCache(final int chunkX, final int chunkY, final int chunkZ) {
+ return this.nibbleCache[chunkX + 5*chunkZ + (5 * 5) * chunkY + this.chunkSectionIndexOffset];
+ }
+
+ protected final SWMRNibbleArray[] getNibblesForChunkFromCache(final int chunkX, final int chunkZ) {
+ final SWMRNibbleArray[] ret = new SWMRNibbleArray[this.maxLightSection - this.minLightSection + 1];
+
+ for (int cy = this.minLightSection; cy <= this.maxLightSection; ++cy) {
+ ret[cy - this.minLightSection] = this.nibbleCache[chunkX + 5*chunkZ + (cy * (5 * 5)) + this.chunkSectionIndexOffset];
+ }
+
+ 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 = this.minLightSection; cy <= this.maxLightSection; ++cy) {
+ this.setNibbleInCache(chunkX, cy, chunkZ, nibbles == null ? null : nibbles[cy - this.minLightSection]);
+ }
+ }
+
+ protected final void updateVisible(final LightChunkGetter lightAccess) {
+ for (int index = 0, max = this.nibbleCache.length; index < max; ++index) {
+ final SWMRNibbleArray nibble = this.nibbleCache[index];
+ 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 ySections = (this.maxSection - this.minSection) + 1;
+ final int chunkY = ((index / (5*5)) % (ySections + 2 + 2)) - this.chunkOffsetY;
2021-11-27 11:42:09 +01:00
+ if ((nibble != null && nibble.updateVisible()) || this.notifyUpdateCache[index]) {
+ lightAccess.onLightUpdate(this.skylightPropagator ? LightLayer.SKY : LightLayer.BLOCK, SectionPos.of(chunkX, chunkY, chunkZ));
+ }
+ }
+ }
+
+ protected final void destroyCaches() {
+ Arrays.fill(this.sectionCache, null);
+ Arrays.fill(this.nibbleCache, null);
+ Arrays.fill(this.chunkCache, null);
+ Arrays.fill(this.emptinessMapCache, null);
+ if (this.isClientSide) {
+ Arrays.fill(this.notifyUpdateCache, false);
+ }
+ }
+
+ protected final BlockState getBlockState(final int worldX, final int worldY, final int worldZ) {
+ final LevelChunkSection section = this.sectionCache[(worldX >> 4) + 5 * (worldZ >> 4) + (5 * 5) * (worldY >> 4) + this.chunkSectionIndexOffset];
+
+ if (section != null) {
+ return section.hasOnlyAir() ? AIR_BLOCK_STATE : section.getBlockState(worldX & 15, worldY & 15, worldZ & 15);
+ }
+
+ return AIR_BLOCK_STATE;
+ }
+
+ protected final BlockState getBlockState(final int sectionIndex, final int localIndex) {
+ final LevelChunkSection section = this.sectionCache[sectionIndex];
+
+ if (section != null) {
+ return section.hasOnlyAir() ? AIR_BLOCK_STATE : section.states.get(localIndex);
+ }
+
+ return AIR_BLOCK_STATE;
+ }
+
+ protected final int getLightLevel(final int worldX, final int worldY, final int worldZ) {
+ final SWMRNibbleArray nibble = this.nibbleCache[(worldX >> 4) + 5 * (worldZ >> 4) + (5 * 5) * (worldY >> 4) + this.chunkSectionIndexOffset];
+
+ return nibble == null ? 0 : nibble.getUpdating((worldX & 15) | ((worldZ & 15) << 4) | ((worldY & 15) << 8));
+ }
+
+ protected final int getLightLevel(final int sectionIndex, final int localIndex) {
+ final SWMRNibbleArray nibble = this.nibbleCache[sectionIndex];
+
+ return nibble == null ? 0 : nibble.getUpdating(localIndex);
+ }
+
+ protected final void setLightLevel(final int worldX, final int worldY, final int worldZ, final int level) {
+ 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 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;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ protected final boolean[] getEmptinessMap(final int chunkX, final int chunkZ) {
+ return this.emptinessMapCache[chunkX + 5*chunkZ + this.chunkIndexOffset];
+ }
+
+ protected final void setEmptinessMapCache(final int chunkX, final int chunkZ, final boolean[] emptinessMap) {
+ this.emptinessMapCache[chunkX + 5*chunkZ + this.chunkIndexOffset] = emptinessMap;
+ }
+
+ public static SWMRNibbleArray[] getFilledEmptyLight(final LevelHeightAccessor world) {
+ return getFilledEmptyLight(WorldUtil.getTotalLightSections(world));
+ }
+
+ private static SWMRNibbleArray[] getFilledEmptyLight(final int totalLightSections) {
+ final SWMRNibbleArray[] ret = new SWMRNibbleArray[totalLightSections];
+
+ for (int i = 0, len = ret.length; i < len; ++i) {
+ ret[i] = new SWMRNibbleArray(null, true);
+ }
+
+ return ret;
+ }
+
+ protected abstract boolean[] getEmptinessMap(final ChunkAccess chunk);
+
+ protected abstract void setEmptinessMap(final ChunkAccess chunk, final boolean[] to);
+
+ protected abstract SWMRNibbleArray[] getNibblesOnChunk(final ChunkAccess chunk);
+
+ protected abstract void setNibbles(final ChunkAccess chunk, final SWMRNibbleArray[] to);
+
+ protected abstract boolean canUseChunk(final ChunkAccess chunk);
+
+ public final void blocksChangedInChunk(final LightChunkGetter lightAccess, final int chunkX, final int chunkZ,
+ final Set<BlockPos> positions, final Boolean[] changedSections) {
+ this.setupCaches(lightAccess, chunkX * 16 + 7, 128, chunkZ * 16 + 7, true, true);
+ try {
+ final ChunkAccess chunk = this.getChunkInCache(chunkX, chunkZ);
+ if (chunk == null) {
+ return;
+ }
+ if (changedSections != null) {
+ final boolean[] ret = this.handleEmptySectionChanges(lightAccess, chunk, changedSections, false);
+ if (ret != null) {
+ this.setEmptinessMap(chunk, ret);
+ }
+ }
+ if (!positions.isEmpty()) {
+ this.propagateBlockChanges(lightAccess, chunk, positions);
+ }
+ 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
+ protected abstract void propagateBlockChanges(final LightChunkGetter lightAccess, final ChunkAccess atChunk, final Set<BlockPos> positions);
+
+ protected abstract void checkBlock(final LightChunkGetter lightAccess, final int worldX, final int worldY, final int worldZ);
+
+ // if ret > expect, then the real value is at least ret (early returns if ret > expect, rather than calculating actual)
+ // if ret == expect, then expect is the correct light value for pos
+ // if ret < expect, then ret is the real light value
+ protected abstract int calculateLightValue(final LightChunkGetter lightAccess, final int worldX, final int worldY, final int worldZ,
+ final int expect);
+
+ protected final int[] chunkCheckDelayedUpdatesCenter = new int[16 * 16];
+ protected final int[] chunkCheckDelayedUpdatesNeighbour = new int[16 * 16];
+
+ protected void checkChunkEdge(final LightChunkGetter lightAccess, final ChunkAccess chunk,
+ final int chunkX, final int chunkY, final int chunkZ) {
+ final SWMRNibbleArray currNibble = this.getNibbleFromCache(chunkX, chunkY, chunkZ);
+ if (currNibble == null) {
+ return;
+ }
+
+ for (final AxisDirection direction : ONLY_HORIZONTAL_DIRECTIONS) {
+ final int neighbourOffX = direction.x;
+ final int neighbourOffZ = direction.z;
+
+ final SWMRNibbleArray neighbourNibble = this.getNibbleFromCache(chunkX + neighbourOffX,
+ chunkY, chunkZ + neighbourOffZ);
+
+ if (neighbourNibble == null) {
+ continue;
+ }
+
+ if (!currNibble.isInitialisedUpdating() && !neighbourNibble.isInitialisedUpdating()) {
+ // both are zero, nothing to check.
+ continue;
+ }
+
+ // this chunk
+ final int incX;
+ final int incZ;
+ final int startX;
+ final int startZ;
+
+ if (neighbourOffX != 0) {
+ // x direction
+ incX = 0;
+ incZ = 1;
+
+ if (direction.x < 0) {
+ // negative
+ startX = chunkX << 4;
+ } else {
+ startX = chunkX << 4 | 15;
+ }
+ startZ = chunkZ << 4;
+ } else {
+ // z direction
+ incX = 1;
+ incZ = 0;
+
+ if (neighbourOffZ < 0) {
+ // negative
+ startZ = chunkZ << 4;
+ } else {
+ startZ = chunkZ << 4 | 15;
+ }
+ startX = chunkX << 4;
+ }
+
+ int centerDelayedChecks = 0;
+ int neighbourDelayedChecks = 0;
+ for (int currY = chunkY << 4, maxY = currY | 15; currY <= maxY; ++currY) {
+ for (int i = 0, currX = startX, currZ = startZ; i < 16; ++i, currX += incX, currZ += incZ) {
+ final int neighbourX = currX + neighbourOffX;
+ final int neighbourZ = currZ + neighbourOffZ;
+
+ final int currentIndex = (currX & 15) |
+ ((currZ & 15)) << 4 |
+ ((currY & 15) << 8);
+ final int currentLevel = currNibble.getUpdating(currentIndex);
+
+ final int neighbourIndex =
+ (neighbourX & 15) |
+ ((neighbourZ & 15)) << 4 |
+ ((currY & 15) << 8);
+ final int neighbourLevel = neighbourNibble.getUpdating(neighbourIndex);
+
+ // the checks are delayed because the checkBlock method clobbers light values - which then
+ // affect later calculate light value operations. While they don't affect it in a behaviourly significant
+ // way, they do have a negative performance impact due to simply queueing more values
+
+ if (this.calculateLightValue(lightAccess, currX, currY, currZ, currentLevel) != currentLevel) {
+ this.chunkCheckDelayedUpdatesCenter[centerDelayedChecks++] = currentIndex;
+ }
+
+ if (this.calculateLightValue(lightAccess, neighbourX, currY, neighbourZ, neighbourLevel) != neighbourLevel) {
+ this.chunkCheckDelayedUpdatesNeighbour[neighbourDelayedChecks++] = neighbourIndex;
+ }
+ }
+ }
+
+ final int currentChunkOffX = chunkX << 4;
+ final int currentChunkOffZ = chunkZ << 4;
+ final int neighbourChunkOffX = (chunkX + direction.x) << 4;
+ final int neighbourChunkOffZ = (chunkZ + direction.z) << 4;
+ final int chunkOffY = chunkY << 4;
+ for (int i = 0, len = Math.max(centerDelayedChecks, neighbourDelayedChecks); i < len; ++i) {
+ // try to queue neighbouring data together
+ // index = x | (z << 4) | (y << 8)
+ if (i < centerDelayedChecks) {
+ final int value = this.chunkCheckDelayedUpdatesCenter[i];
+ this.checkBlock(lightAccess, currentChunkOffX | (value & 15),
+ chunkOffY | (value >>> 8),
+ currentChunkOffZ | ((value >>> 4) & 0xF));
+ }
+ if (i < neighbourDelayedChecks) {
+ final int value = this.chunkCheckDelayedUpdatesNeighbour[i];
+ this.checkBlock(lightAccess, neighbourChunkOffX | (value & 15),
+ chunkOffY | (value >>> 8),
+ neighbourChunkOffZ | ((value >>> 4) & 0xF));
+ }
+ }
+ }
+ }
+
+ protected void checkChunkEdges(final LightChunkGetter lightAccess, final ChunkAccess chunk, final ShortCollection sections) {
+ final ChunkPos chunkPos = chunk.getPos();
+ final int chunkX = chunkPos.x;
+ final int chunkZ = chunkPos.z;
+
+ for (final ShortIterator iterator = sections.iterator(); iterator.hasNext();) {
+ this.checkChunkEdge(lightAccess, chunk, chunkX, iterator.nextShort(), chunkZ);
+ }
+
+ this.performLightDecrease(lightAccess);
+ }
+
+ // 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
+ // 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 void checkChunkEdges(final LightChunkGetter lightAccess, final ChunkAccess 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 = toSection; currSectionY >= fromSection; --currSectionY) {
+ this.checkChunkEdge(lightAccess, chunk, chunkX, currSectionY, chunkZ);
+ }
+
+ this.performLightDecrease(lightAccess);
+ }
+
+ // pulls light from neighbours, and adds them into the increase queue. does not actually propagate.
+ protected final void propagateNeighbourLevels(final LightChunkGetter lightAccess, final ChunkAccess 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 = toSection; currSectionY >= fromSection; --currSectionY) {
+ final SWMRNibbleArray currNibble = this.getNibbleFromCache(chunkX, currSectionY, chunkZ);
+ if (currNibble == null) {
+ continue;
+ }
+ for (final AxisDirection direction : ONLY_HORIZONTAL_DIRECTIONS) {
+ final int neighbourOffX = direction.x;
+ final int neighbourOffZ = direction.z;
+
+ final SWMRNibbleArray neighbourNibble = this.getNibbleFromCache(chunkX + neighbourOffX,
+ currSectionY, chunkZ + neighbourOffZ);
+
+ if (neighbourNibble == null || !neighbourNibble.isInitialisedUpdating()) {
+ // can't pull from 0
+ continue;
+ }
+
+ // neighbour chunk
+ final int incX;
+ final int incZ;
+ final int startX;
+ final int startZ;
+
+ if (neighbourOffX != 0) {
+ // x direction
+ incX = 0;
+ incZ = 1;
+
+ if (direction.x < 0) {
+ // negative
+ startX = (chunkX << 4) - 1;
+ } else {
+ startX = (chunkX << 4) + 16;
+ }
+ startZ = chunkZ << 4;
+ } else {
+ // z direction
+ incX = 1;
+ incZ = 0;
+
+ if (neighbourOffZ < 0) {
+ // negative
+ startZ = (chunkZ << 4) - 1;
+ } else {
+ startZ = (chunkZ << 4) + 16;
+ }
+ startX = chunkX << 4;
+ }
+
+ 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) {
+ for (int i = 0, currX = startX, currZ = startZ; i < 16; ++i, currX += incX, currZ += incZ) {
+ final int level = neighbourNibble.getUpdating(
+ (currX & 15)
+ | ((currZ & 15) << 4)
+ | ((currY & 15) << 8)
+ );
+
+ if (level <= 1) {
+ // nothing to propagate
+ continue;
+ }
+
+ this.appendToIncreaseQueue(
+ ((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 ChunkAccess chunk) {
+ final LevelChunkSection[] sections = chunk.getSections();
+ final Boolean[] ret = new Boolean[sections.length];
+
+ for (int i = 0; i < sections.length; ++i) {
+ if (sections[i] == null || sections[i].hasOnlyAir()) {
+ ret[i] = Boolean.TRUE;
+ } else {
+ ret[i] = Boolean.FALSE;
+ }
+ }
+
+ return ret;
+ }
+
+ public final void forceHandleEmptySectionChanges(final LightChunkGetter lightAccess, final ChunkAccess chunk, final Boolean[] emptinessChanges) {
+ final int chunkX = chunk.getPos().x;
+ final int chunkZ = chunk.getPos().z;
+ this.setupCaches(lightAccess, chunkX * 16 + 7, 128, chunkZ * 16 + 7, true, true);
+ try {
+ // force current chunk into cache
+ this.setChunkInCache(chunkX, chunkZ, chunk);
+ this.setBlocksForChunkInCache(chunkX, chunkZ, chunk.getSections());
+ this.setNibblesForChunkInCache(chunkX, chunkZ, this.getNibblesOnChunk(chunk));
+ this.setEmptinessMapCache(chunkX, chunkZ, this.getEmptinessMap(chunk));
+
+ final boolean[] ret = this.handleEmptySectionChanges(lightAccess, chunk, emptinessChanges, false);
+ if (ret != null) {
+ this.setEmptinessMap(chunk, ret);
+ }
+ this.updateVisible(lightAccess);
+ } finally {
+ this.destroyCaches();
+ }
+ }
+
+ public final void handleEmptySectionChanges(final LightChunkGetter lightAccess, final int chunkX, final int chunkZ,
+ final Boolean[] emptinessChanges) {
+ this.setupCaches(lightAccess, chunkX * 16 + 7, 128, chunkZ * 16 + 7, true, true);
+ try {
+ final ChunkAccess chunk = this.getChunkInCache(chunkX, chunkZ);
+ if (chunk == null) {
+ return;
+ }
+ final boolean[] ret = this.handleEmptySectionChanges(lightAccess, chunk, emptinessChanges, false);
+ if (ret != null) {
+ this.setEmptinessMap(chunk, ret);
+ }
+ this.updateVisible(lightAccess);
+ } finally {
+ this.destroyCaches();
+ }
+ }
+
+ protected abstract void initNibble(final int chunkX, final int chunkY, final int chunkZ, final boolean extrude, final boolean initRemovedNibbles);
+
+ protected abstract void setNibbleNull(final int chunkX, final int chunkY, final int chunkZ);
+
+ // 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
+ // rets non-null when the emptiness map changed and needs to be updated
+ protected final boolean[] handleEmptySectionChanges(final LightChunkGetter lightAccess, final ChunkAccess chunk,
+ final Boolean[] emptinessChanges, final boolean unlit) {
+ final Level world = (Level)lightAccess.getLevel();
+ final int chunkX = chunk.getPos().x;
+ final int chunkZ = chunk.getPos().z;
+
+ boolean[] chunkEmptinessMap = this.getEmptinessMap(chunkX, chunkZ);
+ boolean[] ret = null;
+ final boolean needsInit = unlit || chunkEmptinessMap == null;
+ if (needsInit) {
+ this.setEmptinessMapCache(chunkX, chunkZ, ret = chunkEmptinessMap = new boolean[WorldUtil.getTotalSections(world)]);
+ }
+
+ // update emptiness map
+ for (int sectionIndex = (emptinessChanges.length - 1); sectionIndex >= 0; --sectionIndex) {
+ Boolean valueBoxed = emptinessChanges[sectionIndex];
+ if (valueBoxed == null) {
+ if (!needsInit) {
+ continue;
+ }
+ final LevelChunkSection section = this.getChunkSection(chunkX, sectionIndex + this.minSection, chunkZ);
+ emptinessChanges[sectionIndex] = valueBoxed = section == null || section.hasOnlyAir() ? Boolean.TRUE : Boolean.FALSE;
+ }
+ chunkEmptinessMap[sectionIndex] = valueBoxed.booleanValue();
+ }
+
+ // now init neighbour nibbles
+ for (int sectionIndex = (emptinessChanges.length - 1); sectionIndex >= 0; --sectionIndex) {
+ final Boolean valueBoxed = emptinessChanges[sectionIndex];
+ final int sectionY = sectionIndex + this.minSection;
+ if (valueBoxed == null) {
+ continue;
+ }
+
+ final boolean empty = valueBoxed.booleanValue();
+
+ if (empty) {
+ continue;
+ }
+
+ for (int dz = -1; dz <= 1; ++dz) {
+ for (int dx = -1; dx <= 1; ++dx) {
+ // 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.initNibble(dx + chunkX, dy + sectionY, dz + chunkZ, extrude, false);
+ }
+ }
+ }
+ }
+
+ // check for de-init and lazy-init
+ // lazy init is when chunks are being lit, so at the time they weren't loaded when their neighbours were running
+ // init checks.
+ for (int dz = -1; dz <= 1; ++dz) {
+ for (int dx = -1; dx <= 1; ++dx) {
+ // does this neighbour have 1 radius loaded?
+ boolean neighboursLoaded = true;
+ neighbour_loaded_search:
+ for (int dz2 = -1; dz2 <= 1; ++dz2) {
+ for (int dx2 = -1; dx2 <= 1; ++dx2) {
+ if (this.getEmptinessMap(dx + dx2 + chunkX, dz + dz2 + chunkZ) == null) {
+ neighboursLoaded = false;
+ break neighbour_loaded_search;
+ }
+ }
+ }
+
+ for (int sectionY = this.maxLightSection; sectionY >= this.minLightSection; --sectionY) {
+ // check neighbours to see if we need to de-init this one
+ boolean allEmpty = true;
+ neighbour_search:
+ for (int dy2 = -1; dy2 <= 1; ++dy2) {
+ for (int dz2 = -1; dz2 <= 1; ++dz2) {
+ for (int dx2 = -1; dx2 <= 1; ++dx2) {
+ final int y = sectionY + dy2;
+ if (y < this.minSection || y > this.maxSection) {
+ // empty
+ continue;
+ }
+ final boolean[] emptinessMap = this.getEmptinessMap(dx + dx2 + chunkX, dz + dz2 + chunkZ);
+ if (emptinessMap != null) {
+ if (!emptinessMap[y - this.minSection]) {
+ allEmpty = false;
+ break neighbour_search;
+ }
+ } else {
+ final LevelChunkSection section = this.getChunkSection(dx + dx2 + chunkX, y, dz + dz2 + chunkZ);
+ if (section != null && !section.hasOnlyAir()) {
+ allEmpty = false;
+ break neighbour_search;
+ }
+ }
+ }
+ }
+ }
+
+ if (allEmpty & neighboursLoaded) {
+ // can only de-init when neighbours are loaded
+ // de-init is fine to delay, as de-init is just an optimisation - it's not required for lighting
+ // to be correct
+
+ // all were empty, so de-init
+ this.setNibbleNull(dx + chunkX, sectionY, dz + chunkZ);
+ } else if (!allEmpty) {
+ // must init
+ final boolean extrude = (dx | dz) != 0 || !unlit;
+ this.initNibble(dx + chunkX, sectionY, dz + chunkZ, extrude, false);
+ }
+ }
+ }
+ }
+
+ return ret;
+ }
+
+ public final void checkChunkEdges(final LightChunkGetter lightAccess, final int chunkX, final int chunkZ) {
+ this.setupCaches(lightAccess, chunkX * 16 + 7, 128, chunkZ * 16 + 7, true, false);
+ try {
+ final ChunkAccess chunk = this.getChunkInCache(chunkX, chunkZ);
+ if (chunk == null) {
+ return;
+ }
+ this.checkChunkEdges(lightAccess, chunk, this.minLightSection, this.maxLightSection);
+ this.updateVisible(lightAccess);
+ } finally {
+ this.destroyCaches();
+ }
+ }
+
+ public final void checkChunkEdges(final LightChunkGetter lightAccess, final int chunkX, final int chunkZ, final ShortCollection sections) {
+ this.setupCaches(lightAccess, chunkX * 16 + 7, 128, chunkZ * 16 + 7, true, false);
+ try {
+ final ChunkAccess chunk = this.getChunkInCache(chunkX, chunkZ);
+ if (chunk == null) {
+ return;
+ }
+ this.checkChunkEdges(lightAccess, chunk, sections);
+ 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
+ // 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 LightChunkGetter lightAccess, final ChunkAccess chunk, final boolean needsEdgeChecks);
+
+ public final void light(final LightChunkGetter lightAccess, final ChunkAccess chunk, final Boolean[] emptySections) {
+ final int chunkX = chunk.getPos().x;
+ final int chunkZ = chunk.getPos().z;
+ this.setupCaches(lightAccess, chunkX * 16 + 7, 128, chunkZ * 16 + 7, true, true);
+
+ try {
+ final SWMRNibbleArray[] nibbles = getFilledEmptyLight(this.maxLightSection - this.minLightSection + 1);
+ // force current chunk into cache
+ this.setChunkInCache(chunkX, chunkZ, chunk);
+ this.setBlocksForChunkInCache(chunkX, chunkZ, chunk.getSections());
+ this.setNibblesForChunkInCache(chunkX, chunkZ, nibbles);
+ this.setEmptinessMapCache(chunkX, chunkZ, this.getEmptinessMap(chunk));
+
+ final boolean[] ret = this.handleEmptySectionChanges(lightAccess, chunk, emptySections, true);
+ if (ret != null) {
+ this.setEmptinessMap(chunk, ret);
+ }
+ this.lightChunk(lightAccess, chunk, true);
+ this.setNibbles(chunk, nibbles);
+ this.updateVisible(lightAccess);
+ } finally {
+ this.destroyCaches();
+ }
+ }
+
+ public final void relightChunks(final LightChunkGetter lightAccess, final Set<ChunkPos> chunks,
+ final Consumer<ChunkPos> chunkLightCallback, final IntConsumer onComplete) {
+ // it's recommended for maximum performance that the set is ordered according to a BFS from the center of
+ // the region of chunks to relight
+ // it's required that tickets are added for each chunk to keep them loaded
+ final Long2ObjectOpenHashMap<SWMRNibbleArray[]> nibblesByChunk = new Long2ObjectOpenHashMap<>();
+ final Long2ObjectOpenHashMap<boolean[]> emptinessMapByChunk = new Long2ObjectOpenHashMap<>();
+
+ final int[] neighbourLightOrder = new int[] {
+ // d = 0
+ 0, 0,
+ // d = 1
+ -1, 0,
+ 0, -1,
+ 1, 0,
+ 0, 1,
+ // d = 2
+ -1, 1,
+ 1, 1,
+ -1, -1,
+ 1, -1,
+ };
+
+ int lightCalls = 0;
+
+ for (final ChunkPos chunkPos : chunks) {
+ final int chunkX = chunkPos.x;
+ final int chunkZ = chunkPos.z;
+ final ChunkAccess chunk = (ChunkAccess)lightAccess.getChunkForLighting(chunkX, chunkZ);
+ if (chunk == null || !this.canUseChunk(chunk)) {
+ throw new IllegalStateException();
+ }
+
+ for (int i = 0, len = neighbourLightOrder.length; i < len; i += 2) {
+ final int dx = neighbourLightOrder[i];
+ final int dz = neighbourLightOrder[i + 1];
+ final int neighbourX = dx + chunkX;
+ final int neighbourZ = dz + chunkZ;
+
+ final ChunkAccess neighbour = (ChunkAccess)lightAccess.getChunkForLighting(neighbourX, neighbourZ);
+ if (neighbour == null || !this.canUseChunk(neighbour)) {
+ continue;
+ }
+
+ if (nibblesByChunk.get(CoordinateUtils.getChunkKey(neighbourX, neighbourZ)) != null) {
+ // lit already called for neighbour, no need to light it now
+ continue;
+ }
+
+ // light neighbour chunk
+ this.setupEncodeOffset(neighbourX * 16 + 7, 128, neighbourZ * 16 + 7);
+ try {
+ // insert all neighbouring chunks for this neighbour that we have data for
+ for (int dz2 = -1; dz2 <= 1; ++dz2) {
+ for (int dx2 = -1; dx2 <= 1; ++dx2) {
+ final int neighbourX2 = neighbourX + dx2;
+ final int neighbourZ2 = neighbourZ + dz2;
+ final long key = CoordinateUtils.getChunkKey(neighbourX2, neighbourZ2);
+ final ChunkAccess neighbour2 = (ChunkAccess)lightAccess.getChunkForLighting(neighbourX2, neighbourZ2);
+ if (neighbour2 == null || !this.canUseChunk(neighbour2)) {
+ continue;
+ }
+
+ final SWMRNibbleArray[] nibbles = nibblesByChunk.get(key);
+ if (nibbles == null) {
+ // we haven't lit this chunk
+ continue;
+ }
+
+ this.setChunkInCache(neighbourX2, neighbourZ2, neighbour2);
+ this.setBlocksForChunkInCache(neighbourX2, neighbourZ2, neighbour2.getSections());
+ this.setNibblesForChunkInCache(neighbourX2, neighbourZ2, nibbles);
+ this.setEmptinessMapCache(neighbourX2, neighbourZ2, emptinessMapByChunk.get(key));
+ }
+ }
+
+ final long key = CoordinateUtils.getChunkKey(neighbourX, neighbourZ);
+
+ // now insert the neighbour chunk and light it
+ final SWMRNibbleArray[] nibbles = getFilledEmptyLight(this.world);
+ nibblesByChunk.put(key, nibbles);
+
+ this.setChunkInCache(neighbourX, neighbourZ, neighbour);
+ this.setBlocksForChunkInCache(neighbourX, neighbourZ, neighbour.getSections());
+ this.setNibblesForChunkInCache(neighbourX, neighbourZ, nibbles);
+
+ final boolean[] neighbourEmptiness = this.handleEmptySectionChanges(lightAccess, neighbour, getEmptySectionsForChunk(neighbour), true);
+ emptinessMapByChunk.put(key, neighbourEmptiness);
+ if (chunks.contains(new ChunkPos(neighbourX, neighbourZ))) {
+ this.setEmptinessMap(neighbour, neighbourEmptiness);
+ }
+
+ this.lightChunk(lightAccess, neighbour, false);
+ } finally {
+ this.destroyCaches();
+ }
+ }
+
+ // done lighting all neighbours, so the chunk is now fully lit
+
+ // make sure nibbles are fully updated before calling back
+ final SWMRNibbleArray[] nibbles = nibblesByChunk.get(CoordinateUtils.getChunkKey(chunkX, chunkZ));
+ for (final SWMRNibbleArray nibble : nibbles) {
+ nibble.updateVisible();
+ }
+
+ this.setNibbles(chunk, nibbles);
+
+ for (int y = this.minLightSection; y <= this.maxLightSection; ++y) {
+ lightAccess.onLightUpdate(this.skylightPropagator ? LightLayer.SKY : LightLayer.BLOCK, SectionPos.of(chunkX, y, chunkX));
+ }
+
+ // now do callback
+ if (chunkLightCallback != null) {
+ chunkLightCallback.accept(chunkPos);
+ }
+ ++lightCalls;
+ }
+
+ if (onComplete != null) {
+ onComplete.accept(lightCalls);
+ }
+ }
+
+ // contains:
+ // lower (6 + 6 + 16) = 28 bits: encoded coordinate position (x | (z << 6) | (y << (6 + 6))))
+ // next 4 bits: propagated light level (0, 15]
+ // next 6 bits: propagation direction bitset
+ // next 24 bits: unused
+ // last 3 bits: state flags
+ // state flags:
+ // whether the increase propagator needs to write the propagated level to the position, used to avoid cascading light
+ // updates for block sources
+ protected static final long FLAG_WRITE_LEVEL = 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 long FLAG_RECHECK_LEVEL = Long.MIN_VALUE >>> 1;
+ // whether the propagation needs to consider if its block is conditionally transparent
+ protected static final long FLAG_HAS_SIDED_TRANSPARENT_BLOCKS = Long.MIN_VALUE;
+
+ protected long[] increaseQueue = new long[16 * 16 * 16];
+ protected int increaseQueueInitialLength;
+ protected long[] decreaseQueue = new long[16 * 16 * 16];
+ protected int decreaseQueueInitialLength;
+
+ protected final long[] resizeIncreaseQueue() {
+ return this.increaseQueue = Arrays.copyOf(this.increaseQueue, this.increaseQueue.length * 2);
+ }
+
+ protected final long[] resizeDecreaseQueue() {
+ return this.decreaseQueue = Arrays.copyOf(this.decreaseQueue, this.decreaseQueue.length * 2);
+ }
+
+ protected final void appendToIncreaseQueue(final long value) {
+ final int idx = this.increaseQueueInitialLength++;
+ long[] queue = this.increaseQueue;
+ if (idx >= queue.length) {
+ queue = this.resizeIncreaseQueue();
+ queue[idx] = value;
+ } else {
+ queue[idx] = value;
+ }
+ }
+
+ protected final void appendToDecreaseQueue(final long value) {
+ final int idx = this.decreaseQueueInitialLength++;
+ long[] queue = this.decreaseQueue;
+ if (idx >= queue.length) {
+ queue = this.resizeDecreaseQueue();
+ queue[idx] = value;
+ } else {
+ queue[idx] = value;
+ }
+ }
+
+ 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 < OLD_CHECK_DIRECTIONS.length; ++i) {
+ final List<AxisDirection> 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 LightChunkGetter lightAccess) {
+ final BlockGetter world = lightAccess.getLevel();
+ long[] queue = this.increaseQueue;
+ int queueReadIndex = 0;
+ int queueLength = this.increaseQueueInitialLength;
+ this.increaseQueueInitialLength = 0;
+ final int decodeOffsetX = -this.encodeOffsetX;
+ final int decodeOffsetY = -this.encodeOffsetY;
+ final int decodeOffsetZ = -this.encodeOffsetZ;
+ final int encodeOffset = this.coordinateOffset;
+ final int sectionOffset = this.chunkSectionIndexOffset;
+
+ while (queueReadIndex < queueLength) {
+ 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)) & 0xFL);
+ final AxisDirection[] checkDirections = OLD_CHECK_DIRECTIONS[(int)((queueValue >>> (6 + 6 + 16 + 4)) & 63L)];
+
+ if ((queueValue & FLAG_RECHECK_LEVEL) != 0L) {
+ if (this.getLightLevel(posX, posY, posZ) != propagatedLightLevel) {
+ // not at the level we expect, so something changed.
+ continue;
+ }
+ } else if ((queueValue & FLAG_WRITE_LEVEL) != 0L) {
+ // these are used to restore block sources after a propagation decrease
+ this.setLightLevel(posX, posY, posZ, propagatedLightLevel);
+ }
+
+ 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;
+ final int offY = posY + propagate.y;
+ final int offZ = posZ + propagate.z;
+
+ 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 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 = blockState.getOpacityIfCached();
+ if (opacityCached != -1) {
+ final int targetLevel = propagatedLightLevel - Math.max(1, opacityCached);
+ if (targetLevel > currentLevel) {
+ currentNibble.set(localIndex, targetLevel);
+ this.postLightUpdate(offX, offY, offZ);
+
+ if (targetLevel > 1) {
+ if (queueLength >= queue.length) {
+ queue = this.resizeIncreaseQueue();
+ }
+ queue[queueLength++] =
+ ((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);
+ long flags = 0;
+ if (blockState.isConditionallyFullOpaque()) {
+ final VoxelShape cullingFace = blockState.getFaceOcclusionShape(world, this.mutablePos1, propagate.getOpposite().nms);
+
+ if (Shapes.faceShapeOccludes(Shapes.empty(), cullingFace)) {
+ continue;
+ }
+ flags |= FLAG_HAS_SIDED_TRANSPARENT_BLOCKS;
+ }
+
+ final int opacity = blockState.getLightBlock(world, this.mutablePos1);
+ final int targetLevel = propagatedLightLevel - Math.max(1, opacity);
+ if (targetLevel <= currentLevel) {
+ continue;
+ }
+
+ currentNibble.set(localIndex, targetLevel);
+ this.postLightUpdate(offX, offY, offZ);
+
+ if (targetLevel > 1) {
+ if (queueLength >= queue.length) {
+ queue = this.resizeIncreaseQueue();
+ }
+ queue[queueLength++] =
+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1))
+ | ((targetLevel & 0xFL) << (6 + 6 + 16))
+ | (propagate.everythingButTheOppositeDirection << (6 + 6 + 16 + 4))
+ | (flags);
+ }
+ continue;
+ }
+ }
+ } else {
+ // we actually need to worry about our state here
+ final BlockState fromBlock = this.getBlockState(posX, posY, posZ);
+ this.mutablePos2.set(posX, posY, posZ);
+ for (final AxisDirection propagate : checkDirections) {
+ final int offX = posX + propagate.x;
+ final int offY = posY + propagate.y;
+ final int offZ = posZ + propagate.z;
+
+ final VoxelShape fromShape = fromBlock.isConditionallyFullOpaque() ? fromBlock.getFaceOcclusionShape(world, this.mutablePos2, propagate.nms) : Shapes.empty();
+
+ if (fromShape != Shapes.empty() && Shapes.faceShapeOccludes(Shapes.empty(), fromShape)) {
+ continue;
+ }
+
+ 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 SWMRNibbleArray currentNibble = this.nibbleCache[sectionIndex];
+ final int currentLevel;
+
+ if (currentNibble == null || (currentLevel = currentNibble.getUpdating(localIndex)) >= (propagatedLightLevel - 1)) {
+ continue; // already at the level we want
+ }
+
+ final BlockState blockState = this.getBlockState(sectionIndex, localIndex);
+ if (blockState == null) {
+ continue;
+ }
+ final int opacityCached = blockState.getOpacityIfCached();
+ if (opacityCached != -1) {
+ final int targetLevel = propagatedLightLevel - Math.max(1, opacityCached);
+ if (targetLevel > currentLevel) {
+ currentNibble.set(localIndex, targetLevel);
+ this.postLightUpdate(offX, offY, offZ);
+
+ if (targetLevel > 1) {
+ if (queueLength >= queue.length) {
+ queue = this.resizeIncreaseQueue();
+ }
+ queue[queueLength++] =
+ ((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);
+ long flags = 0;
+ if (blockState.isConditionallyFullOpaque()) {
+ final VoxelShape cullingFace = blockState.getFaceOcclusionShape(world, this.mutablePos1, propagate.getOpposite().nms);
+
+ if (Shapes.faceShapeOccludes(fromShape, cullingFace)) {
+ continue;
+ }
+ flags |= FLAG_HAS_SIDED_TRANSPARENT_BLOCKS;
+ }
+
+ final int opacity = blockState.getLightBlock(world, this.mutablePos1);
+ final int targetLevel = propagatedLightLevel - Math.max(1, opacity);
+ if (targetLevel <= currentLevel) {
+ continue;
+ }
+
+ currentNibble.set(localIndex, targetLevel);
+ this.postLightUpdate(offX, offY, offZ);
+
+ if (targetLevel > 1) {
+ if (queueLength >= queue.length) {
+ queue = this.resizeIncreaseQueue();
+ }
+ queue[queueLength++] =
+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1))
+ | ((targetLevel & 0xFL) << (6 + 6 + 16))
+ | (propagate.everythingButTheOppositeDirection << (6 + 6 + 16 + 4))
+ | (flags);
+ }
+ continue;
+ }
+ }
+ }
+ }
+ }
+
+ protected final void performLightDecrease(final LightChunkGetter lightAccess) {
+ final BlockGetter world = lightAccess.getLevel();
+ long[] queue = this.decreaseQueue;
+ long[] increaseQueue = this.increaseQueue;
+ int queueReadIndex = 0;
+ int queueLength = this.decreaseQueueInitialLength;
+ this.decreaseQueueInitialLength = 0;
+ int increaseQueueLength = this.increaseQueueInitialLength;
+ final int decodeOffsetX = -this.encodeOffsetX;
+ final int decodeOffsetY = -this.encodeOffsetY;
+ final int decodeOffsetZ = -this.encodeOffsetZ;
+ final int encodeOffset = this.coordinateOffset;
+ final int sectionOffset = this.chunkSectionIndexOffset;
+ final int emittedMask = this.emittedLightMask;
+
+ while (queueReadIndex < queueLength) {
+ 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_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;
+ final int offY = posY + propagate.y;
+ final int offZ = posZ + propagate.z;
+
+ 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 SWMRNibbleArray currentNibble = this.nibbleCache[sectionIndex];
+ final int lightLevel;
+
+ 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);
+ if (blockState == null) {
+ continue;
+ }
+ final int opacityCached = 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
+ if (increaseQueueLength >= increaseQueue.length) {
+ increaseQueue = this.resizeIncreaseQueue();
+ }
+ increaseQueue[increaseQueueLength++] =
+ ((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 = blockState.getLightEmission() & emittedMask;
+ if (emittedLight != 0) {
+ // re-propagate source
+ // note: do not set recheck level, or else the propagation will fail
+ if (increaseQueueLength >= increaseQueue.length) {
+ increaseQueue = this.resizeIncreaseQueue();
+ }
+ increaseQueue[increaseQueueLength++] =
+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1))
+ | ((emittedLight & 0xFL) << (6 + 6 + 16))
+ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4))
+ | (blockState.isConditionallyFullOpaque() ? (FLAG_WRITE_LEVEL | FLAG_HAS_SIDED_TRANSPARENT_BLOCKS) : FLAG_WRITE_LEVEL);
+ }
+
+ currentNibble.set(localIndex, 0);
+ this.postLightUpdate(offX, offY, offZ);
+
+ if (targetLevel > 0) { // we actually need to propagate 0 just in case we find a neighbour...
+ if (queueLength >= queue.length) {
+ queue = this.resizeDecreaseQueue();
+ }
+ queue[queueLength++] =
+ ((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);
+ long flags = 0;
+ if (blockState.isConditionallyFullOpaque()) {
+ final VoxelShape cullingFace = blockState.getFaceOcclusionShape(world, this.mutablePos1, propagate.getOpposite().nms);
+
+ if (Shapes.faceShapeOccludes(Shapes.empty(), cullingFace)) {
+ continue;
+ }
+ flags |= FLAG_HAS_SIDED_TRANSPARENT_BLOCKS;
+ }
+
+ final int opacity = blockState.getLightBlock(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
+ if (increaseQueueLength >= increaseQueue.length) {
+ increaseQueue = this.resizeIncreaseQueue();
+ }
+ increaseQueue[increaseQueueLength++] =
+ ((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 = blockState.getLightEmission() & emittedMask;
+ if (emittedLight != 0) {
+ // re-propagate source
+ // note: do not set recheck level, or else the propagation will fail
+ if (increaseQueueLength >= increaseQueue.length) {
+ increaseQueue = this.resizeIncreaseQueue();
+ }
+ increaseQueue[increaseQueueLength++] =
+ ((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 | FLAG_WRITE_LEVEL);
+ }
+
+ currentNibble.set(localIndex, 0);
+ this.postLightUpdate(offX, offY, offZ);
+
+ if (targetLevel > 0) {
+ if (queueLength >= queue.length) {
+ queue = this.resizeDecreaseQueue();
+ }
+ queue[queueLength++] =
+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1))
+ | ((targetLevel & 0xFL) << (6 + 6 + 16))
+ | ((propagate.everythingButTheOppositeDirection) << (6 + 6 + 16 + 4))
+ | flags;
+ }
+ continue;
+ }
+ }
+ } else {
+ // we actually need to worry about our state here
+ final BlockState fromBlock = this.getBlockState(posX, posY, posZ);
+ this.mutablePos2.set(posX, posY, posZ);
+ for (final AxisDirection propagate : checkDirections) {
+ final int offX = posX + propagate.x;
+ final int offY = posY + propagate.y;
+ final int offZ = posZ + propagate.z;
+
+ 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 = (fromBlock.isConditionallyFullOpaque()) ? fromBlock.getFaceOcclusionShape(world, this.mutablePos2, propagate.nms) : Shapes.empty();
+
+ if (fromShape != Shapes.empty() && Shapes.faceShapeOccludes(Shapes.empty(), fromShape)) {
+ continue;
+ }
+
+ final SWMRNibbleArray currentNibble = this.nibbleCache[sectionIndex];
+ final int lightLevel;
+
+ 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);
+ if (blockState == null) {
+ continue;
+ }
+ final int opacityCached = 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
+ if (increaseQueueLength >= increaseQueue.length) {
+ increaseQueue = this.resizeIncreaseQueue();
+ }
+ increaseQueue[increaseQueueLength++] =
+ ((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 = blockState.getLightEmission() & emittedMask;
+ if (emittedLight != 0) {
+ // re-propagate source
+ // note: do not set recheck level, or else the propagation will fail
+ if (increaseQueueLength >= increaseQueue.length) {
+ increaseQueue = this.resizeIncreaseQueue();
+ }
+ increaseQueue[increaseQueueLength++] =
+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1))
+ | ((emittedLight & 0xFL) << (6 + 6 + 16))
+ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4))
+ | (blockState.isConditionallyFullOpaque() ? (FLAG_WRITE_LEVEL | FLAG_HAS_SIDED_TRANSPARENT_BLOCKS) : FLAG_WRITE_LEVEL);
+ }
+
+ currentNibble.set(localIndex, 0);
+ this.postLightUpdate(offX, offY, offZ);
+
+ if (targetLevel > 0) { // we actually need to propagate 0 just in case we find a neighbour...
+ if (queueLength >= queue.length) {
+ queue = this.resizeDecreaseQueue();
+ }
+ queue[queueLength++] =
+ ((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);
+ long flags = 0;
+ if (blockState.isConditionallyFullOpaque()) {
+ final VoxelShape cullingFace = blockState.getFaceOcclusionShape(world, this.mutablePos1, propagate.getOpposite().nms);
+
+ if (Shapes.faceShapeOccludes(fromShape, cullingFace)) {
+ continue;
+ }
+ flags |= FLAG_HAS_SIDED_TRANSPARENT_BLOCKS;
+ }
+
+ final int opacity = blockState.getLightBlock(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
+ if (increaseQueueLength >= increaseQueue.length) {
+ increaseQueue = this.resizeIncreaseQueue();
+ }
+ increaseQueue[increaseQueueLength++] =
+ ((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 = blockState.getLightEmission() & emittedMask;
+ if (emittedLight != 0) {
+ // re-propagate source
+ // note: do not set recheck level, or else the propagation will fail
+ if (increaseQueueLength >= increaseQueue.length) {
+ increaseQueue = this.resizeIncreaseQueue();
+ }
+ increaseQueue[increaseQueueLength++] =
+ ((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 | FLAG_WRITE_LEVEL);
+ }
+
+ currentNibble.set(localIndex, 0);
+ this.postLightUpdate(offX, offY, offZ);
+
+ if (targetLevel > 0) { // we actually need to propagate 0 just in case we find a neighbour...
+ if (queueLength >= queue.length) {
+ queue = this.resizeDecreaseQueue();
+ }
+ queue[queueLength++] =
+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1))
+ | ((targetLevel & 0xFL) << (6 + 6 + 16))
+ | ((propagate.everythingButTheOppositeDirection) << (6 + 6 + 16 + 4))
+ | flags;
+ }
+ continue;
+ }
+ }
+ }
+ }
+
+ // propagate sources we clobbered
+ 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
new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
--- /dev/null
+++ b/src/main/java/ca/spottedleaf/starlight/common/light/StarLightInterface.java
@@ -0,0 +0,0 @@
+package ca.spottedleaf.starlight.common.light;
+
+import ca.spottedleaf.starlight.common.util.CoordinateUtils;
+import ca.spottedleaf.starlight.common.util.WorldUtil;
+import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
+import it.unimi.dsi.fastutil.shorts.ShortCollection;
+import it.unimi.dsi.fastutil.shorts.ShortOpenHashSet;
+import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
2021-11-27 11:42:09 +01:00
+import net.minecraft.core.BlockPos;
+import net.minecraft.core.SectionPos;
+import net.minecraft.server.level.ServerChunkCache;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.server.level.TicketType;
+import net.minecraft.world.level.ChunkPos;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.chunk.ChunkAccess;
+import net.minecraft.world.level.chunk.ChunkStatus;
+import net.minecraft.world.level.chunk.DataLayer;
+import net.minecraft.world.level.chunk.LevelChunk;
+import net.minecraft.world.level.chunk.LightChunkGetter;
+import net.minecraft.world.level.lighting.LayerLightEventListener;
+import net.minecraft.world.level.lighting.LevelLightEngine;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Consumer;
+import java.util.function.IntConsumer;
+
+public final class StarLightInterface {
+
+ public static final TicketType<ChunkPos> CHUNK_WORK_TICKET = TicketType.create("starlight_chunk_work_ticket", (p1, p2) -> Long.compare(p1.toLong(), p2.toLong()));
+
+ /**
+ * Can be {@code null}, indicating the light is all empty.
+ */
+ protected final Level world;
+ protected final LightChunkGetter lightAccess;
+
+ protected final ArrayDeque<SkyStarLightEngine> cachedSkyPropagators;
+ protected final ArrayDeque<BlockStarLightEngine> cachedBlockPropagators;
+
+ protected final LightQueue lightQueue = new LightQueue(this);
+
+ protected final LayerLightEventListener skyReader;
+ protected final LayerLightEventListener blockReader;
+ protected final boolean isClientSide;
+
+ protected final int minSection;
+ protected final int maxSection;
+ protected final int minLightSection;
+ protected final int maxLightSection;
+
+ public final LevelLightEngine lightEngine;
+
+ private final boolean hasBlockLight;
+ private final boolean hasSkyLight;
+
2021-11-27 11:42:09 +01:00
+ public StarLightInterface(final LightChunkGetter lightAccess, final boolean hasSkyLight, final boolean hasBlockLight, final LevelLightEngine lightEngine) {
+ this.lightAccess = lightAccess;
+ this.world = lightAccess == null ? null : (Level)lightAccess.getLevel();
+ this.cachedSkyPropagators = hasSkyLight && lightAccess != null ? new ArrayDeque<>() : null;
+ this.cachedBlockPropagators = hasBlockLight && lightAccess != null ? new ArrayDeque<>() : null;
+ this.isClientSide = !(this.world instanceof ServerLevel);
+ if (this.world == null) {
+ this.minSection = -4;
+ this.maxSection = 19;
+ this.minLightSection = -5;
+ this.maxLightSection = 20;
+ } else {
+ this.minSection = WorldUtil.getMinSection(this.world);
+ this.maxSection = WorldUtil.getMaxSection(this.world);
+ this.minLightSection = WorldUtil.getMinLightSection(this.world);
+ this.maxLightSection = WorldUtil.getMaxLightSection(this.world);
+ }
+ this.lightEngine = lightEngine;
+ this.hasBlockLight = hasBlockLight;
+ this.hasSkyLight = hasSkyLight;
2021-11-27 11:42:09 +01:00
+ this.skyReader = !hasSkyLight ? LayerLightEventListener.DummyLightLayerEventListener.INSTANCE : new LayerLightEventListener() {
+ @Override
+ public void checkBlock(final BlockPos blockPos) {
+ StarLightInterface.this.lightEngine.checkBlock(blockPos.immutable());
+ }
+
+ @Override
+ public void onBlockEmissionIncrease(final BlockPos blockPos, final int i) {
+ // skylight doesn't care
+ }
+
+ @Override
+ public boolean hasLightWork() {
+ // not really correct...
+ return StarLightInterface.this.hasUpdates();
+ }
+
+ @Override
+ public int runUpdates(final int i, final boolean bl, final boolean bl2) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void enableLightSources(final ChunkPos chunkPos, final boolean bl) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public DataLayer getDataLayerData(final SectionPos pos) {
+ final ChunkAccess chunk = StarLightInterface.this.getAnyChunkNow(pos.getX(), pos.getZ());
+ if (chunk == null || (!StarLightInterface.this.isClientSide && !chunk.isLightCorrect()) || !chunk.getStatus().isOrAfter(ChunkStatus.LIGHT)) {
+ return null;
+ }
+
+ final int sectionY = pos.getY();
+
+ if (sectionY > StarLightInterface.this.maxLightSection || sectionY < StarLightInterface.this.minLightSection) {
+ return null;
+ }
+
+ if (chunk.getSkyEmptinessMap() == null) {
+ return null;
+ }
+
+ return chunk.getSkyNibbles()[sectionY - StarLightInterface.this.minLightSection].toVanillaNibble();
+ }
+
+ @Override
+ public int getLightValue(final BlockPos blockPos) {
+ return StarLightInterface.this.getSkyLightValue(blockPos, StarLightInterface.this.getAnyChunkNow(blockPos.getX() >> 4, blockPos.getZ() >> 4));
+ }
+
+ @Override
+ public void updateSectionStatus(final SectionPos pos, final boolean notReady) {
+ StarLightInterface.this.sectionChange(pos, notReady);
+ }
+ };
+ this.blockReader = !hasBlockLight ? LayerLightEventListener.DummyLightLayerEventListener.INSTANCE : new LayerLightEventListener() {
+ @Override
+ public void checkBlock(final BlockPos blockPos) {
+ StarLightInterface.this.lightEngine.checkBlock(blockPos.immutable());
+ }
+
+ @Override
+ public void onBlockEmissionIncrease(final BlockPos blockPos, final int i) {
+ this.checkBlock(blockPos);
+ }
+
+ @Override
+ public boolean hasLightWork() {
+ // not really correct...
+ return StarLightInterface.this.hasUpdates();
+ }
+
+ @Override
+ public int runUpdates(final int i, final boolean bl, final boolean bl2) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void enableLightSources(final ChunkPos chunkPos, final boolean bl) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public DataLayer getDataLayerData(final SectionPos pos) {
+ final ChunkAccess chunk = StarLightInterface.this.getAnyChunkNow(pos.getX(), pos.getZ());
+
+ if (chunk == null || pos.getY() < StarLightInterface.this.minLightSection || pos.getY() > StarLightInterface.this.maxLightSection) {
+ return null;
+ }
+
+ return chunk.getBlockNibbles()[pos.getY() - StarLightInterface.this.minLightSection].toVanillaNibble();
+ }
+
+ @Override
+ public int getLightValue(final BlockPos blockPos) {
+ return StarLightInterface.this.getBlockLightValue(blockPos, StarLightInterface.this.getAnyChunkNow(blockPos.getX() >> 4, blockPos.getZ() >> 4));
+ }
+
+ @Override
+ public void updateSectionStatus(final SectionPos pos, final boolean notReady) {
+ StarLightInterface.this.sectionChange(pos, notReady);
+ }
+ };
+ }
+
+ protected int getSkyLightValue(final BlockPos blockPos, final ChunkAccess chunk) {
+ if (!this.hasSkyLight) {
+ return 0;
+ }
2021-11-27 11:42:09 +01:00
+ final int x = blockPos.getX();
+ int y = blockPos.getY();
+ final int z = blockPos.getZ();
+
+ final int minSection = this.minSection;
+ final int maxSection = this.maxSection;
+ final int minLightSection = this.minLightSection;
+ final int maxLightSection = this.maxLightSection;
+
+ if (chunk == null || (!this.isClientSide && !chunk.isLightCorrect()) || !chunk.getStatus().isOrAfter(ChunkStatus.LIGHT)) {
+ return 15;
+ }
+
+ int sectionY = y >> 4;
+
+ if (sectionY > maxLightSection) {
+ return 15;
+ }
+
+ if (sectionY < minLightSection) {
+ sectionY = minLightSection;
+ y = sectionY << 4;
+ }
+
+ final SWMRNibbleArray[] nibbles = chunk.getSkyNibbles();
+ final SWMRNibbleArray immediate = nibbles[sectionY - minLightSection];
+
+ if (!immediate.isNullNibbleVisible()) {
+ return immediate.getVisible(x, y, z);
+ }
+
+ final boolean[] emptinessMap = chunk.getSkyEmptinessMap();
+
+ if (emptinessMap == null) {
+ return 15;
+ }
+
+ // are we above this chunk's lowest empty section?
+ int lowestY = minLightSection - 1;
+ for (int currY = maxSection; currY >= minSection; --currY) {
+ if (emptinessMap[currY - minSection]) {
+ continue;
+ }
+
+ // should always be full lit here
+ lowestY = currY;
+ break;
+ }
+
+ 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 <= maxLightSection; ++currY) {
+ final SWMRNibbleArray nibble = nibbles[currY - minLightSection];
+ if (!nibble.isNullNibbleVisible()) {
+ return nibble.getVisible(x, 0, z);
+ }
+ }
+
+ // should never reach here
+ return 15;
+ }
+
+ protected int getBlockLightValue(final BlockPos blockPos, final ChunkAccess chunk) {
+ if (!this.hasBlockLight) {
+ return 0;
+ }
2021-11-27 11:42:09 +01:00
+ final int y = blockPos.getY();
+ final int cy = y >> 4;
+
+ final int minLightSection = this.minLightSection;
+ final int maxLightSection = this.maxLightSection;
+
+ if (cy < minLightSection || cy > maxLightSection) {
+ return 0;
+ }
+
+ if (chunk == null) {
+ return 0;
+ }
+
+ final SWMRNibbleArray nibble = chunk.getBlockNibbles()[cy - minLightSection];
+ return nibble.getVisible(blockPos.getX(), y, blockPos.getZ());
+ }
+
+ public int getRawBrightness(final BlockPos pos, final int ambientDarkness) {
+ final ChunkAccess chunk = this.getAnyChunkNow(pos.getX() >> 4, pos.getZ() >> 4);
+
+ final int sky = this.getSkyLightValue(pos, chunk) - ambientDarkness;
+ // Don't fetch the block light level if the skylight level is 15, since the value will never be higher.
+ if (sky == 15) return 15;
2021-11-27 11:42:09 +01:00
+ final int block = this.getBlockLightValue(pos, chunk);
+ return Math.max(sky, block);
+ }
+
+ public LayerLightEventListener getSkyReader() {
+ return this.skyReader;
+ }
+
+ public LayerLightEventListener getBlockReader() {
+ return this.blockReader;
+ }
+
+ public boolean isClientSide() {
+ return this.isClientSide;
+ }
+
+ public ChunkAccess getAnyChunkNow(final int chunkX, final int chunkZ) {
+ if (this.world == null) {
+ // empty world
+ return null;
+ }
+
+ final ServerChunkCache chunkProvider = ((ServerLevel)this.world).getChunkSource();
+ final LevelChunk fullLoaded = chunkProvider.getChunkAtIfLoadedImmediately(chunkX, chunkZ);
+ if (fullLoaded != null) {
+ return fullLoaded;
+ }
+
+ return chunkProvider.getChunkAtImmediately(chunkX, chunkZ);
+ }
+
+ public boolean hasUpdates() {
+ return !this.lightQueue.isEmpty();
+ }
+
+ public Level getWorld() {
+ return this.world;
+ }
+
+ public LightChunkGetter getLightAccess() {
+ return this.lightAccess;
+ }
+
+ protected final SkyStarLightEngine getSkyLightEngine() {
+ if (this.cachedSkyPropagators == null) {
+ return null;
+ }
+ final SkyStarLightEngine ret;
+ synchronized (this.cachedSkyPropagators) {
+ ret = this.cachedSkyPropagators.pollFirst();
+ }
+
+ if (ret == null) {
+ return new SkyStarLightEngine(this.world);
+ }
+ return ret;
+ }
+
+ protected final void releaseSkyLightEngine(final SkyStarLightEngine engine) {
+ if (this.cachedSkyPropagators == null) {
+ return;
+ }
+ synchronized (this.cachedSkyPropagators) {
+ this.cachedSkyPropagators.addFirst(engine);
+ }
+ }
+
+ protected final BlockStarLightEngine getBlockLightEngine() {
+ if (this.cachedBlockPropagators == null) {
+ return null;
+ }
+ final BlockStarLightEngine ret;
+ synchronized (this.cachedBlockPropagators) {
+ ret = this.cachedBlockPropagators.pollFirst();
+ }
+
+ if (ret == null) {
+ return new BlockStarLightEngine(this.world);
+ }
+ return ret;
+ }
+
+ protected final void releaseBlockLightEngine(final BlockStarLightEngine engine) {
+ if (this.cachedBlockPropagators == null) {
+ return;
+ }
+ synchronized (this.cachedBlockPropagators) {
+ this.cachedBlockPropagators.addFirst(engine);
+ }
+ }
+
+ public CompletableFuture<Void> blockChange(final BlockPos pos) {
+ if (this.world == null || pos.getY() < WorldUtil.getMinBlockY(this.world) || pos.getY() > WorldUtil.getMaxBlockY(this.world)) { // empty world
+ return null;
+ }
+
+ return this.lightQueue.queueBlockChange(pos);
+ }
+
+ public CompletableFuture<Void> sectionChange(final SectionPos pos, final boolean newEmptyValue) {
+ if (this.world == null) { // empty world
+ return null;
+ }
+
+ return this.lightQueue.queueSectionChange(pos, newEmptyValue);
+ }
+
+ public void forceLoadInChunk(final ChunkAccess chunk, final Boolean[] emptySections) {
+ final SkyStarLightEngine skyEngine = this.getSkyLightEngine();
+ final BlockStarLightEngine blockEngine = this.getBlockLightEngine();
+
+ try {
+ if (skyEngine != null) {
+ skyEngine.forceHandleEmptySectionChanges(this.lightAccess, chunk, emptySections);
+ }
+ if (blockEngine != null) {
+ blockEngine.forceHandleEmptySectionChanges(this.lightAccess, chunk, emptySections);
+ }
+ } finally {
+ this.releaseSkyLightEngine(skyEngine);
+ this.releaseBlockLightEngine(blockEngine);
+ }
+ }
+
+ public void loadInChunk(final int chunkX, final int chunkZ, final Boolean[] emptySections) {
+ final SkyStarLightEngine skyEngine = this.getSkyLightEngine();
+ final BlockStarLightEngine blockEngine = this.getBlockLightEngine();
+
+ try {
+ if (skyEngine != null) {
+ skyEngine.handleEmptySectionChanges(this.lightAccess, chunkX, chunkZ, emptySections);
+ }
+ if (blockEngine != null) {
+ blockEngine.handleEmptySectionChanges(this.lightAccess, chunkX, chunkZ, emptySections);
+ }
+ } finally {
+ this.releaseSkyLightEngine(skyEngine);
+ this.releaseBlockLightEngine(blockEngine);
+ }
+ }
+
+ public void lightChunk(final ChunkAccess chunk, final Boolean[] emptySections) {
+ final SkyStarLightEngine skyEngine = this.getSkyLightEngine();
+ final BlockStarLightEngine blockEngine = this.getBlockLightEngine();
+
+ try {
+ if (skyEngine != null) {
+ skyEngine.light(this.lightAccess, chunk, emptySections);
+ }
+ if (blockEngine != null) {
+ blockEngine.light(this.lightAccess, chunk, emptySections);
+ }
+ } finally {
+ this.releaseSkyLightEngine(skyEngine);
+ this.releaseBlockLightEngine(blockEngine);
+ }
+ }
+
+ public void relightChunks(final Set<ChunkPos> chunks, final Consumer<ChunkPos> chunkLightCallback,
+ final IntConsumer onComplete) {
+ final SkyStarLightEngine skyEngine = this.getSkyLightEngine();
+ final BlockStarLightEngine blockEngine = this.getBlockLightEngine();
+
+ try {
+ if (skyEngine != null) {
+ skyEngine.relightChunks(this.lightAccess, chunks, blockEngine == null ? chunkLightCallback : null,
+ blockEngine == null ? onComplete : null);
+ }
+ if (blockEngine != null) {
+ blockEngine.relightChunks(this.lightAccess, chunks, chunkLightCallback, onComplete);
+ }
+ } finally {
+ this.releaseSkyLightEngine(skyEngine);
+ this.releaseBlockLightEngine(blockEngine);
+ }
+ }
+
+ public void checkChunkEdges(final int chunkX, final int chunkZ) {
+ this.checkSkyEdges(chunkX, chunkZ);
+ this.checkBlockEdges(chunkX, chunkZ);
+ }
+
+ public void checkSkyEdges(final int chunkX, final int chunkZ) {
+ final SkyStarLightEngine skyEngine = this.getSkyLightEngine();
+
+ try {
+ if (skyEngine != null) {
+ skyEngine.checkChunkEdges(this.lightAccess, chunkX, chunkZ);
+ }
+ } finally {
+ this.releaseSkyLightEngine(skyEngine);
+ }
+ }
+
+ public void checkBlockEdges(final int chunkX, final int chunkZ) {
+ final BlockStarLightEngine blockEngine = this.getBlockLightEngine();
+ try {
+ if (blockEngine != null) {
+ blockEngine.checkChunkEdges(this.lightAccess, chunkX, chunkZ);
+ }
+ } finally {
+ this.releaseBlockLightEngine(blockEngine);
+ }
+ }
+
+ public void checkSkyEdges(final int chunkX, final int chunkZ, final ShortCollection sections) {
+ final SkyStarLightEngine skyEngine = this.getSkyLightEngine();
+
+ try {
+ if (skyEngine != null) {
+ skyEngine.checkChunkEdges(this.lightAccess, chunkX, chunkZ, sections);
+ }
+ } finally {
+ this.releaseSkyLightEngine(skyEngine);
+ }
+ }
+
+ public void checkBlockEdges(final int chunkX, final int chunkZ, final ShortCollection sections) {
+ final BlockStarLightEngine blockEngine = this.getBlockLightEngine();
+ try {
+ if (blockEngine != null) {
+ blockEngine.checkChunkEdges(this.lightAccess, chunkX, chunkZ, sections);
+ }
+ } finally {
+ this.releaseBlockLightEngine(blockEngine);
+ }
+ }
+
+ public void scheduleChunkLight(final ChunkPos pos, final Runnable run) {
+ this.lightQueue.queueChunkLighting(pos, run);
+ }
+
+ public void removeChunkTasks(final ChunkPos pos) {
+ this.lightQueue.removeChunk(pos);
+ }
+
+ public void propagateChanges() {
+ if (this.lightQueue.isEmpty()) {
+ return;
+ }
+
+ final SkyStarLightEngine skyEngine = this.getSkyLightEngine();
+ final BlockStarLightEngine blockEngine = this.getBlockLightEngine();
+
+ try {
+ LightQueue.ChunkTasks task;
+ while ((task = this.lightQueue.removeFirstTask()) != null) {
+ if (task.lightTasks != null) {
+ for (final Runnable run : task.lightTasks) {
+ run.run();
+ }
+ }
+
+ final long coordinate = task.chunkCoordinate;
+ final int chunkX = CoordinateUtils.getChunkX(coordinate);
+ final int chunkZ = CoordinateUtils.getChunkZ(coordinate);
+
+ final Set<BlockPos> positions = task.changedPositions;
+ final Boolean[] sectionChanges = task.changedSectionSet;
+
+ if (skyEngine != null && (!positions.isEmpty() || sectionChanges != null)) {
+ skyEngine.blocksChangedInChunk(this.lightAccess, chunkX, chunkZ, positions, sectionChanges);
+ }
+ if (blockEngine != null && (!positions.isEmpty() || sectionChanges != null)) {
+ blockEngine.blocksChangedInChunk(this.lightAccess, chunkX, chunkZ, positions, sectionChanges);
+ }
+
+ if (skyEngine != null && task.queuedEdgeChecksSky != null) {
+ skyEngine.checkChunkEdges(this.lightAccess, chunkX, chunkZ, task.queuedEdgeChecksSky);
+ }
+ if (blockEngine != null && task.queuedEdgeChecksBlock != null) {
+ blockEngine.checkChunkEdges(this.lightAccess, chunkX, chunkZ, task.queuedEdgeChecksBlock);
+ }
+
+ task.onComplete.complete(null);
+ }
+ } finally {
+ this.releaseSkyLightEngine(skyEngine);
+ this.releaseBlockLightEngine(blockEngine);
+ }
+ }
+
+ protected static final class LightQueue {
+
+ protected final Long2ObjectLinkedOpenHashMap<ChunkTasks> chunkTasks = new Long2ObjectLinkedOpenHashMap<>();
+ protected final StarLightInterface manager;
+
+ public LightQueue(final StarLightInterface manager) {
+ this.manager = manager;
+ }
+
+ public synchronized boolean isEmpty() {
+ return this.chunkTasks.isEmpty();
+ }
+
+ public synchronized CompletableFuture<Void> queueBlockChange(final BlockPos pos) {
+ final ChunkTasks tasks = this.chunkTasks.computeIfAbsent(CoordinateUtils.getChunkKey(pos), ChunkTasks::new);
+ tasks.changedPositions.add(pos.immutable());
+ return tasks.onComplete;
+ }
+
+ public synchronized CompletableFuture<Void> queueSectionChange(final SectionPos pos, final boolean newEmptyValue) {
+ final ChunkTasks tasks = this.chunkTasks.computeIfAbsent(CoordinateUtils.getChunkKey(pos), ChunkTasks::new);
+
+ if (tasks.changedSectionSet == null) {
+ tasks.changedSectionSet = new Boolean[this.manager.maxSection - this.manager.minSection + 1];
+ }
+ tasks.changedSectionSet[pos.getY() - this.manager.minSection] = Boolean.valueOf(newEmptyValue);
+
+ return tasks.onComplete;
+ }
+
+ public synchronized CompletableFuture<Void> queueChunkLighting(final ChunkPos pos, final Runnable lightTask) {
+ final ChunkTasks tasks = this.chunkTasks.computeIfAbsent(CoordinateUtils.getChunkKey(pos), ChunkTasks::new);
+ if (tasks.lightTasks == null) {
+ tasks.lightTasks = new ArrayList<>();
+ }
+ tasks.lightTasks.add(lightTask);
+
+ return tasks.onComplete;
+ }
+
+ public synchronized CompletableFuture<Void> queueChunkSkylightEdgeCheck(final SectionPos pos, final ShortCollection sections) {
+ final ChunkTasks tasks = this.chunkTasks.computeIfAbsent(CoordinateUtils.getChunkKey(pos), ChunkTasks::new);
+
+ ShortOpenHashSet queuedEdges = tasks.queuedEdgeChecksSky;
+ if (queuedEdges == null) {
+ queuedEdges = tasks.queuedEdgeChecksSky = new ShortOpenHashSet();
+ }
+ queuedEdges.addAll(sections);
+
+ return tasks.onComplete;
+ }
+
+ public synchronized CompletableFuture<Void> queueChunkBlocklightEdgeCheck(final SectionPos pos, final ShortCollection sections) {
+ final ChunkTasks tasks = this.chunkTasks.computeIfAbsent(CoordinateUtils.getChunkKey(pos), ChunkTasks::new);
+
+ ShortOpenHashSet queuedEdges = tasks.queuedEdgeChecksBlock;
+ if (queuedEdges == null) {
+ queuedEdges = tasks.queuedEdgeChecksBlock = new ShortOpenHashSet();
+ }
+ queuedEdges.addAll(sections);
+
+ return tasks.onComplete;
+ }
+
+ public void removeChunk(final ChunkPos pos) {
+ final ChunkTasks tasks;
+ synchronized (this) {
+ tasks = this.chunkTasks.remove(CoordinateUtils.getChunkKey(pos));
+ }
+ if (tasks != null) {
+ tasks.onComplete.complete(null);
+ }
+ }
+
+ public synchronized ChunkTasks removeFirstTask() {
+ if (this.chunkTasks.isEmpty()) {
+ return null;
+ }
+ return this.chunkTasks.removeFirst();
+ }
+
+ protected static final class ChunkTasks {
+
+ public final Set<BlockPos> changedPositions = new ObjectOpenHashSet<>();
2021-11-27 11:42:09 +01:00
+ public Boolean[] changedSectionSet;
+ public ShortOpenHashSet queuedEdgeChecksSky;
+ public ShortOpenHashSet queuedEdgeChecksBlock;
+ public List<Runnable> lightTasks;
+
+ public final CompletableFuture<Void> onComplete = new CompletableFuture<>();
+
+ public final long chunkCoordinate;
+
+ public ChunkTasks(final long chunkCoordinate) {
+ this.chunkCoordinate = chunkCoordinate;
+ }
+ }
+ }
+}
diff --git a/src/main/java/ca/spottedleaf/starlight/common/util/CoordinateUtils.java b/src/main/java/ca/spottedleaf/starlight/common/util/CoordinateUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
--- /dev/null
+++ b/src/main/java/ca/spottedleaf/starlight/common/util/CoordinateUtils.java
@@ -0,0 +0,0 @@
+package ca.spottedleaf.starlight.common.util;
+
+import net.minecraft.core.BlockPos;
+import net.minecraft.core.SectionPos;
+import net.minecraft.util.Mth;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.level.ChunkPos;
+
+public final class CoordinateUtils {
+
+ // dx, dz are relative to the target chunk
+ // dx, dz in [-radius, radius]
+ public static int getNeighbourMappedIndex(final int dx, final int dz, final int radius) {
+ return (dx + radius) + (2 * radius + 1)*(dz + radius);
+ }
+
+ // the chunk keys are compatible with vanilla
+
+ public static long getChunkKey(final BlockPos pos) {
+ return ((long)(pos.getZ() >> 4) << 32) | ((pos.getX() >> 4) & 0xFFFFFFFFL);
+ }
+
+ public static long getChunkKey(final Entity entity) {
+ return ((long)(Mth.floor(entity.getZ()) >> 4) << 32) | ((Mth.floor(entity.getX()) >> 4) & 0xFFFFFFFFL);
+ }
+
+ public static long getChunkKey(final ChunkPos pos) {
+ return ((long)pos.z << 32) | (pos.x & 0xFFFFFFFFL);
+ }
+
+ public static long getChunkKey(final SectionPos pos) {
+ return ((long)pos.getZ() << 32) | (pos.getX() & 0xFFFFFFFFL);
+ }
+
+ public static long getChunkKey(final int x, final int z) {
+ return ((long)z << 32) | (x & 0xFFFFFFFFL);
+ }
+
+ public static int getChunkX(final long chunkKey) {
+ return (int)chunkKey;
+ }
+
+ public static int getChunkZ(final long chunkKey) {
+ return (int)(chunkKey >>> 32);
+ }
+
+ public static int getChunkCoordinate(final double blockCoordinate) {
+ return Mth.floor(blockCoordinate) >> 4;
+ }
+
+ // the section keys are compatible with vanilla's
+
+ static final int SECTION_X_BITS = 22;
+ static final long SECTION_X_MASK = (1L << SECTION_X_BITS) - 1;
+ static final int SECTION_Y_BITS = 20;
+ static final long SECTION_Y_MASK = (1L << SECTION_Y_BITS) - 1;
+ static final int SECTION_Z_BITS = 22;
+ static final long SECTION_Z_MASK = (1L << SECTION_Z_BITS) - 1;
+ // format is y,z,x (in order of LSB to MSB)
+ static final int SECTION_Y_SHIFT = 0;
+ static final int SECTION_Z_SHIFT = SECTION_Y_SHIFT + SECTION_Y_BITS;
+ static final int SECTION_X_SHIFT = SECTION_Z_SHIFT + SECTION_X_BITS;
+ static final int SECTION_TO_BLOCK_SHIFT = 4;
+
+ public static long getChunkSectionKey(final int x, final int y, final int z) {
+ return ((x & SECTION_X_MASK) << SECTION_X_SHIFT)
+ | ((y & SECTION_Y_MASK) << SECTION_Y_SHIFT)
+ | ((z & SECTION_Z_MASK) << SECTION_Z_SHIFT);
+ }
+
+ public static long getChunkSectionKey(final SectionPos pos) {
+ return ((pos.getX() & SECTION_X_MASK) << SECTION_X_SHIFT)
+ | ((pos.getY() & SECTION_Y_MASK) << SECTION_Y_SHIFT)
+ | ((pos.getZ() & SECTION_Z_MASK) << SECTION_Z_SHIFT);
+ }
+
+ public static long getChunkSectionKey(final ChunkPos pos, final int y) {
+ return ((pos.x & SECTION_X_MASK) << SECTION_X_SHIFT)
+ | ((y & SECTION_Y_MASK) << SECTION_Y_SHIFT)
+ | ((pos.z & SECTION_Z_MASK) << SECTION_Z_SHIFT);
+ }
+
+ public static long getChunkSectionKey(final BlockPos pos) {
+ return (((long)pos.getX() << (SECTION_X_SHIFT - SECTION_TO_BLOCK_SHIFT)) & (SECTION_X_MASK << SECTION_X_SHIFT)) |
+ ((pos.getY() >> SECTION_TO_BLOCK_SHIFT) & (SECTION_Y_MASK << SECTION_Y_SHIFT)) |
+ (((long)pos.getZ() << (SECTION_Z_SHIFT - SECTION_TO_BLOCK_SHIFT)) & (SECTION_Z_MASK << SECTION_Z_SHIFT));
+ }
+
+ public static long getChunkSectionKey(final Entity entity) {
+ return ((Mth.lfloor(entity.getX()) << (SECTION_X_SHIFT - SECTION_TO_BLOCK_SHIFT)) & (SECTION_X_MASK << SECTION_X_SHIFT)) |
+ ((Mth.lfloor(entity.getY()) >> SECTION_TO_BLOCK_SHIFT) & (SECTION_Y_MASK << SECTION_Y_SHIFT)) |
+ ((Mth.lfloor(entity.getZ()) << (SECTION_Z_SHIFT - SECTION_TO_BLOCK_SHIFT)) & (SECTION_Z_MASK << SECTION_Z_SHIFT));
+ }
+
+ public static int getChunkSectionX(final long key) {
+ return (int)(key << (Long.SIZE - (SECTION_X_SHIFT + SECTION_X_BITS)) >> (Long.SIZE - SECTION_X_BITS));
+ }
+
+ public static int getChunkSectionY(final long key) {
+ return (int)(key << (Long.SIZE - (SECTION_Y_SHIFT + SECTION_Y_BITS)) >> (Long.SIZE - SECTION_Y_BITS));
+ }
+
+ public static int getChunkSectionZ(final long key) {
+ return (int)(key << (Long.SIZE - (SECTION_Z_SHIFT + SECTION_Z_BITS)) >> (Long.SIZE - SECTION_Z_BITS));
+ }
+
+ // the block coordinates are not necessarily compatible with vanilla's
+
+ public static int getBlockCoordinate(final double blockCoordinate) {
+ return Mth.floor(blockCoordinate);
+ }
+
+ public static long getBlockKey(final int x, final int y, final int z) {
+ return ((long)x & 0x7FFFFFF) | (((long)z & 0x7FFFFFF) << 27) | ((long)y << 54);
+ }
+
+ public static long getBlockKey(final BlockPos pos) {
+ return ((long)pos.getX() & 0x7FFFFFF) | (((long)pos.getZ() & 0x7FFFFFF) << 27) | ((long)pos.getY() << 54);
+ }
+
+ public static long getBlockKey(final Entity entity) {
+ return ((long)entity.getX() & 0x7FFFFFF) | (((long)entity.getZ() & 0x7FFFFFF) << 27) | ((long)entity.getY() << 54);
+ }
+
+ private CoordinateUtils() {
+ 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
--- /dev/null
+++ b/src/main/java/ca/spottedleaf/starlight/common/util/IntegerUtil.java
@@ -0,0 +0,0 @@
+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/util/SaveUtil.java b/src/main/java/ca/spottedleaf/starlight/common/util/SaveUtil.java
new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
--- /dev/null
+++ b/src/main/java/ca/spottedleaf/starlight/common/util/SaveUtil.java
@@ -0,0 +0,0 @@
+package ca.spottedleaf.starlight.common.util;
+
+import ca.spottedleaf.starlight.common.light.SWMRNibbleArray;
+import ca.spottedleaf.starlight.common.light.StarLightEngine;
2022-03-01 06:43:03 +01:00
+import com.mojang.logging.LogUtils;
2021-11-27 11:42:09 +01:00
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.nbt.ListTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.world.level.ChunkPos;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.chunk.ChunkAccess;
+import net.minecraft.world.level.chunk.ChunkStatus;
2022-03-01 06:43:03 +01:00
+import org.slf4j.Logger;
2021-11-27 11:42:09 +01:00
+
+public final class SaveUtil {
+
2022-03-01 06:43:03 +01:00
+ private static final Logger LOGGER = LogUtils.getLogger();
2021-11-27 11:42:09 +01:00
+
2022-06-08 13:44:30 +02:00
+ private static final int STARLIGHT_LIGHT_VERSION = 8;
+
+ public static int getLightVersion() {
+ return STARLIGHT_LIGHT_VERSION;
+ }
2021-11-27 11:42:09 +01:00
+
+ private static final String BLOCKLIGHT_STATE_TAG = "starlight.blocklight_state";
+ private static final String SKYLIGHT_STATE_TAG = "starlight.skylight_state";
+ private static final String STARLIGHT_VERSION_TAG = "starlight.light_version";
+
+ public static void saveLightHook(final Level world, final ChunkAccess chunk, final CompoundTag nbt) {
+ try {
+ saveLightHookReal(world, chunk, nbt);
+ } catch (final Throwable ex) {
2021-11-27 11:42:09 +01:00
+ // failing to inject is not fatal so we catch anything here. if it fails, it will have correctly set lit to false
+ // for Vanilla to relight on load and it will not set our lit tag so we will relight on load
+ if (ex instanceof ThreadDeath) {
+ throw (ThreadDeath)ex;
+ }
2021-11-27 11:42:09 +01:00
+ LOGGER.warn("Failed to inject light data into save data for chunk " + chunk.getPos() + ", chunk light will be recalculated on its next load", ex);
+ }
+ }
+
2021-11-27 16:07:45 +01:00
+ private static void saveLightHookReal(final Level world, final ChunkAccess chunk, final CompoundTag tag) {
+ if (tag == null) {
2021-11-27 11:42:09 +01:00
+ return;
+ }
+
+ final int minSection = WorldUtil.getMinLightSection(world);
+ final int maxSection = WorldUtil.getMaxLightSection(world);
+
+ SWMRNibbleArray[] blockNibbles = chunk.getBlockNibbles();
+ SWMRNibbleArray[] skyNibbles = chunk.getSkyNibbles();
+
+ boolean lit = chunk.isLightCorrect() || !(world instanceof ServerLevel);
+ // diff start - store our tag for whether light data is init'd
+ if (lit) {
2021-11-27 16:07:45 +01:00
+ tag.putBoolean("isLightOn", false);
2021-11-27 11:42:09 +01:00
+ }
+ // diff end - store our tag for whether light data is init'd
2021-11-27 16:07:45 +01:00
+ ChunkStatus status = ChunkStatus.byName(tag.getString("Status"));
2021-11-27 11:42:09 +01:00
+
+ CompoundTag[] sections = new CompoundTag[maxSection - minSection + 1];
+
2021-11-27 16:07:45 +01:00
+ ListTag sectionsStored = tag.getList("sections", 10);
2021-11-27 11:42:09 +01:00
+
+ 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.isOrAfter(ChunkStatus.LIGHT)) {
+ for (int i = minSection; i <= maxSection; ++i) {
+ SWMRNibbleArray.SaveState blockNibble = blockNibbles[i - minSection].getSaveState();
+ SWMRNibbleArray.SaveState skyNibble = skyNibbles[i - minSection].getSaveState();
+ 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;
+ }
+
+ // 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.
+
+ if (blockNibble != null) {
+ if (blockNibble.data != null) {
+ section.putByteArray("BlockLight", blockNibble.data);
+ }
+ section.putInt(BLOCKLIGHT_STATE_TAG, blockNibble.state);
+ }
+
+ if (skyNibble != null) {
+ if (skyNibble.data != null) {
+ section.putByteArray("SkyLight", skyNibble.data);
+ }
+ section.putInt(SKYLIGHT_STATE_TAG, skyNibble.state);
+ }
+ }
+ }
+ }
+
+ // rewrite section list
+ sectionsStored.clear();
+ for (CompoundTag section : sections) {
+ if (section != null) {
+ sectionsStored.add(section);
+ }
+ }
2021-11-27 16:07:45 +01:00
+ tag.put("sections", sectionsStored);
2021-11-27 11:42:09 +01:00
+ if (lit) {
2021-11-27 16:07:45 +01:00
+ tag.putInt(STARLIGHT_VERSION_TAG, STARLIGHT_LIGHT_VERSION); // only mark as fully lit after we have successfully injected our data
2021-11-27 11:42:09 +01:00
+ }
+ }
+
+ public static void loadLightHook(final Level world, final ChunkPos pos, final CompoundTag tag, final ChunkAccess into) {
+ try {
+ loadLightHookReal(world, pos, tag, into);
+ } catch (final Throwable ex) {
2021-11-27 11:42:09 +01:00
+ // failing to inject is not fatal so we catch anything here. if it fails, then we simply relight. Not a problem, we get correct
+ // lighting in both cases.
+ if (ex instanceof ThreadDeath) {
+ throw (ThreadDeath)ex;
+ }
2021-11-27 11:42:09 +01:00
+ LOGGER.warn("Failed to load light for chunk " + pos + ", light will be recalculated", ex);
+ }
+ }
+
+ private static void loadLightHookReal(final Level world, final ChunkPos pos, final CompoundTag tag, final ChunkAccess into) {
+ if (into == null) {
+ return;
+ }
+ final int minSection = WorldUtil.getMinLightSection(world);
+ final int maxSection = WorldUtil.getMaxLightSection(world);
+
+ into.setLightCorrect(false); // mark as unlit in case we fail parsing
+
+ SWMRNibbleArray[] blockNibbles = StarLightEngine.getFilledEmptyLight(world);
+ SWMRNibbleArray[] skyNibbles = StarLightEngine.getFilledEmptyLight(world);
+
+
2022-06-08 13:44:30 +02:00
+ // start copy from the original method
2021-11-27 16:07:45 +01:00
+ boolean lit = tag.get("isLightOn") != null && tag.getInt(STARLIGHT_VERSION_TAG) == STARLIGHT_LIGHT_VERSION;
2021-11-27 11:42:09 +01:00
+ boolean canReadSky = world.dimensionType().hasSkyLight();
2021-11-27 16:07:45 +01:00
+ ChunkStatus status = ChunkStatus.byName(tag.getString("Status"));
2021-11-27 11:42:09 +01:00
+ if (lit && status.isOrAfter(ChunkStatus.LIGHT)) { // diff - we add the status check here
2021-11-27 16:07:45 +01:00
+ ListTag sections = tag.getList("sections", 10);
2021-11-27 11:42:09 +01:00
+
+ 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(), sectionData.getInt(BLOCKLIGHT_STATE_TAG)); // clone for data safety
+ } else {
+ blockNibbles[y - minSection] = new SWMRNibbleArray(null, sectionData.getInt(BLOCKLIGHT_STATE_TAG));
+ }
+
+ if (canReadSky) {
+ 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(), sectionData.getInt(SKYLIGHT_STATE_TAG)); // clone for data safety
+ } else {
+ skyNibbles[y - minSection] = new SWMRNibbleArray(null, sectionData.getInt(SKYLIGHT_STATE_TAG));
+ }
+ }
+ }
+ }
+ // end copy from vanilla
+
+ into.setBlockNibbles(blockNibbles);
+ into.setSkyNibbles(skyNibbles);
+ into.setLightCorrect(lit); // now we set lit here, only after we've correctly parsed data
+ }
+
+ private SaveUtil() {}
+}
diff --git a/src/main/java/ca/spottedleaf/starlight/common/util/WorldUtil.java b/src/main/java/ca/spottedleaf/starlight/common/util/WorldUtil.java
new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
--- /dev/null
+++ b/src/main/java/ca/spottedleaf/starlight/common/util/WorldUtil.java
@@ -0,0 +0,0 @@
+package ca.spottedleaf.starlight.common.util;
+
+import net.minecraft.world.level.LevelHeightAccessor;
+
+public final class WorldUtil {
+
+ // min, max are inclusive
+
+ public static int getMaxSection(final LevelHeightAccessor world) {
+ return world.getMaxSection() - 1; // getMaxSection() is exclusive
+ }
+
+ public static int getMinSection(final LevelHeightAccessor world) {
+ return world.getMinSection();
+ }
+
+ public static int getMaxLightSection(final LevelHeightAccessor world) {
+ return getMaxSection(world) + 1;
+ }
+
+ public static int getMinLightSection(final LevelHeightAccessor world) {
+ return getMinSection(world) - 1;
+ }
+
+
+
+ public static int getTotalSections(final LevelHeightAccessor world) {
+ return getMaxSection(world) - getMinSection(world) + 1;
+ }
+
+ public static int getTotalLightSections(final LevelHeightAccessor world) {
+ return getMaxLightSection(world) - getMinLightSection(world) + 1;
+ }
+
+ public static int getMinBlockY(final LevelHeightAccessor world) {
+ return getMinSection(world) << 4;
+ }
+
+ public static int getMaxBlockY(final LevelHeightAccessor world) {
+ return (getMaxSection(world) << 4) | 15;
+ }
+
+ private WorldUtil() {
+ throw new RuntimeException();
+ }
+
+}
Rewrite chunk system (#8177) Patch documentation to come Issues with the old system that are fixed now: - World generation does not scale with cpu cores effectively. - Relies on the main thread for scheduling and maintaining chunk state, dropping chunk load/generate rates at lower tps. - Unreliable prioritisation of chunk gen/load calls that block the main thread. - Shutdown logic is utterly unreliable, as it has to wait for all chunks to unload - is it guaranteed that the chunk system is in a state on shutdown that it can reliably do this? Watchdog shutdown also typically failed due to thread checks, which is now resolved. - Saving of data is not unified (i.e can save chunk data without saving entity data, poses problems for desync if shutdown is really abnormal. - Entities are not loaded with chunks. This caused quite a bit of headache for Chunk#getEntities API, but now the new chunk system loads entities with chunks so that they are ready whenever the chunk loads in. Effectively brings the behavior back to 1.16 era, but still storing entities in their own separate regionfiles. The above list is not complete. The patch documentation will complete it. New chunk system hard relies on starlight and dataconverter, and most importantly the new concurrent utilities in ConcurrentUtil. Some of the old async chunk i/o interface (i.e the old file io thread reroutes _some_ calls to the new file io thread) is kept for plugin compat reasons. It will be removed in the next major version of minecraft. The old legacy chunk system patches have been moved to the removed folder in case we need them again.
2022-09-26 10:02:51 +02:00
diff --git a/src/main/java/io/papermc/paper/command/PaperCommand.java b/src/main/java/io/papermc/paper/command/PaperCommand.java
2021-11-27 11:42:09 +01:00
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
Rewrite chunk system (#8177) Patch documentation to come Issues with the old system that are fixed now: - World generation does not scale with cpu cores effectively. - Relies on the main thread for scheduling and maintaining chunk state, dropping chunk load/generate rates at lower tps. - Unreliable prioritisation of chunk gen/load calls that block the main thread. - Shutdown logic is utterly unreliable, as it has to wait for all chunks to unload - is it guaranteed that the chunk system is in a state on shutdown that it can reliably do this? Watchdog shutdown also typically failed due to thread checks, which is now resolved. - Saving of data is not unified (i.e can save chunk data without saving entity data, poses problems for desync if shutdown is really abnormal. - Entities are not loaded with chunks. This caused quite a bit of headache for Chunk#getEntities API, but now the new chunk system loads entities with chunks so that they are ready whenever the chunk loads in. Effectively brings the behavior back to 1.16 era, but still storing entities in their own separate regionfiles. The above list is not complete. The patch documentation will complete it. New chunk system hard relies on starlight and dataconverter, and most importantly the new concurrent utilities in ConcurrentUtil. Some of the old async chunk i/o interface (i.e the old file io thread reroutes _some_ calls to the new file io thread) is kept for plugin compat reasons. It will be removed in the next major version of minecraft. The old legacy chunk system patches have been moved to the removed folder in case we need them again.
2022-09-26 10:02:51 +02:00
--- a/src/main/java/io/papermc/paper/command/PaperCommand.java
+++ b/src/main/java/io/papermc/paper/command/PaperCommand.java
@@ -0,0 +0,0 @@
package io.papermc.paper.command;
import io.papermc.paper.command.subcommands.EntityCommand;
+import io.papermc.paper.command.subcommands.FixLightCommand;
import io.papermc.paper.command.subcommands.HeapDumpCommand;
import io.papermc.paper.command.subcommands.ReloadCommand;
import io.papermc.paper.command.subcommands.VersionCommand;
@@ -0,0 +0,0 @@ public final class PaperCommand extends Command {
commands.put(Set.of("entity"), new EntityCommand());
commands.put(Set.of("reload"), new ReloadCommand());
commands.put(Set.of("version"), new VersionCommand());
+ commands.put(Set.of("fixlight"), new FixLightCommand());
return commands.entrySet().stream()
.flatMap(entry -> entry.getKey().stream().map(s -> Map.entry(s, entry.getValue())))
diff --git a/src/main/java/io/papermc/paper/command/subcommands/FixLightCommand.java b/src/main/java/io/papermc/paper/command/subcommands/FixLightCommand.java
new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
--- /dev/null
+++ b/src/main/java/io/papermc/paper/command/subcommands/FixLightCommand.java
Rewrite chunk system (#8177) Patch documentation to come Issues with the old system that are fixed now: - World generation does not scale with cpu cores effectively. - Relies on the main thread for scheduling and maintaining chunk state, dropping chunk load/generate rates at lower tps. - Unreliable prioritisation of chunk gen/load calls that block the main thread. - Shutdown logic is utterly unreliable, as it has to wait for all chunks to unload - is it guaranteed that the chunk system is in a state on shutdown that it can reliably do this? Watchdog shutdown also typically failed due to thread checks, which is now resolved. - Saving of data is not unified (i.e can save chunk data without saving entity data, poses problems for desync if shutdown is really abnormal. - Entities are not loaded with chunks. This caused quite a bit of headache for Chunk#getEntities API, but now the new chunk system loads entities with chunks so that they are ready whenever the chunk loads in. Effectively brings the behavior back to 1.16 era, but still storing entities in their own separate regionfiles. The above list is not complete. The patch documentation will complete it. New chunk system hard relies on starlight and dataconverter, and most importantly the new concurrent utilities in ConcurrentUtil. Some of the old async chunk i/o interface (i.e the old file io thread reroutes _some_ calls to the new file io thread) is kept for plugin compat reasons. It will be removed in the next major version of minecraft. The old legacy chunk system patches have been moved to the removed folder in case we need them again.
2022-09-26 10:02:51 +02:00
@@ -0,0 +0,0 @@
+package io.papermc.paper.command.subcommands;
+
+import io.papermc.paper.command.PaperSubcommand;
+import java.util.ArrayDeque;
+import java.util.Deque;
+import io.papermc.paper.util.MCUtil;
Rewrite chunk system (#8177) Patch documentation to come Issues with the old system that are fixed now: - World generation does not scale with cpu cores effectively. - Relies on the main thread for scheduling and maintaining chunk state, dropping chunk load/generate rates at lower tps. - Unreliable prioritisation of chunk gen/load calls that block the main thread. - Shutdown logic is utterly unreliable, as it has to wait for all chunks to unload - is it guaranteed that the chunk system is in a state on shutdown that it can reliably do this? Watchdog shutdown also typically failed due to thread checks, which is now resolved. - Saving of data is not unified (i.e can save chunk data without saving entity data, poses problems for desync if shutdown is really abnormal. - Entities are not loaded with chunks. This caused quite a bit of headache for Chunk#getEntities API, but now the new chunk system loads entities with chunks so that they are ready whenever the chunk loads in. Effectively brings the behavior back to 1.16 era, but still storing entities in their own separate regionfiles. The above list is not complete. The patch documentation will complete it. New chunk system hard relies on starlight and dataconverter, and most importantly the new concurrent utilities in ConcurrentUtil. Some of the old async chunk i/o interface (i.e the old file io thread reroutes _some_ calls to the new file io thread) is kept for plugin compat reasons. It will be removed in the next major version of minecraft. The old legacy chunk system patches have been moved to the removed folder in case we need them again.
2022-09-26 10:02:51 +02:00
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.server.level.ChunkHolder;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.server.level.ThreadedLevelLightEngine;
+import net.minecraft.world.level.ChunkPos;
+import net.minecraft.world.level.chunk.ChunkAccess;
Rewrite chunk system (#8177) Patch documentation to come Issues with the old system that are fixed now: - World generation does not scale with cpu cores effectively. - Relies on the main thread for scheduling and maintaining chunk state, dropping chunk load/generate rates at lower tps. - Unreliable prioritisation of chunk gen/load calls that block the main thread. - Shutdown logic is utterly unreliable, as it has to wait for all chunks to unload - is it guaranteed that the chunk system is in a state on shutdown that it can reliably do this? Watchdog shutdown also typically failed due to thread checks, which is now resolved. - Saving of data is not unified (i.e can save chunk data without saving entity data, poses problems for desync if shutdown is really abnormal. - Entities are not loaded with chunks. This caused quite a bit of headache for Chunk#getEntities API, but now the new chunk system loads entities with chunks so that they are ready whenever the chunk loads in. Effectively brings the behavior back to 1.16 era, but still storing entities in their own separate regionfiles. The above list is not complete. The patch documentation will complete it. New chunk system hard relies on starlight and dataconverter, and most importantly the new concurrent utilities in ConcurrentUtil. Some of the old async chunk i/o interface (i.e the old file io thread reroutes _some_ calls to the new file io thread) is kept for plugin compat reasons. It will be removed in the next major version of minecraft. The old legacy chunk system patches have been moved to the removed folder in case we need them again.
2022-09-26 10:02:51 +02:00
+import net.minecraft.world.level.chunk.LevelChunk;
+import org.bukkit.command.CommandSender;
+import org.bukkit.craftbukkit.entity.CraftPlayer;
+import org.bukkit.entity.Player;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.framework.qual.DefaultQualifier;
+
+import static net.kyori.adventure.text.Component.text;
+import static net.kyori.adventure.text.format.NamedTextColor.BLUE;
+import static net.kyori.adventure.text.format.NamedTextColor.DARK_AQUA;
Rewrite chunk system (#8177) Patch documentation to come Issues with the old system that are fixed now: - World generation does not scale with cpu cores effectively. - Relies on the main thread for scheduling and maintaining chunk state, dropping chunk load/generate rates at lower tps. - Unreliable prioritisation of chunk gen/load calls that block the main thread. - Shutdown logic is utterly unreliable, as it has to wait for all chunks to unload - is it guaranteed that the chunk system is in a state on shutdown that it can reliably do this? Watchdog shutdown also typically failed due to thread checks, which is now resolved. - Saving of data is not unified (i.e can save chunk data without saving entity data, poses problems for desync if shutdown is really abnormal. - Entities are not loaded with chunks. This caused quite a bit of headache for Chunk#getEntities API, but now the new chunk system loads entities with chunks so that they are ready whenever the chunk loads in. Effectively brings the behavior back to 1.16 era, but still storing entities in their own separate regionfiles. The above list is not complete. The patch documentation will complete it. New chunk system hard relies on starlight and dataconverter, and most importantly the new concurrent utilities in ConcurrentUtil. Some of the old async chunk i/o interface (i.e the old file io thread reroutes _some_ calls to the new file io thread) is kept for plugin compat reasons. It will be removed in the next major version of minecraft. The old legacy chunk system patches have been moved to the removed folder in case we need them again.
2022-09-26 10:02:51 +02:00
+import static net.kyori.adventure.text.format.NamedTextColor.GREEN;
+import static net.kyori.adventure.text.format.NamedTextColor.RED;
+
+@DefaultQualifier(NonNull.class)
+public final class FixLightCommand implements PaperSubcommand {
+ @Override
+ public boolean execute(final CommandSender sender, final String subCommand, final String[] args) {
+ this.doFixLight(sender, args);
+ return true;
+ }
+
+ private void doFixLight(final CommandSender sender, final String[] args) {
+ if (!(sender instanceof Player)) {
+ sender.sendMessage(text("Only players can use this command", RED));
+ return;
+ }
Rewrite chunk system (#8177) Patch documentation to come Issues with the old system that are fixed now: - World generation does not scale with cpu cores effectively. - Relies on the main thread for scheduling and maintaining chunk state, dropping chunk load/generate rates at lower tps. - Unreliable prioritisation of chunk gen/load calls that block the main thread. - Shutdown logic is utterly unreliable, as it has to wait for all chunks to unload - is it guaranteed that the chunk system is in a state on shutdown that it can reliably do this? Watchdog shutdown also typically failed due to thread checks, which is now resolved. - Saving of data is not unified (i.e can save chunk data without saving entity data, poses problems for desync if shutdown is really abnormal. - Entities are not loaded with chunks. This caused quite a bit of headache for Chunk#getEntities API, but now the new chunk system loads entities with chunks so that they are ready whenever the chunk loads in. Effectively brings the behavior back to 1.16 era, but still storing entities in their own separate regionfiles. The above list is not complete. The patch documentation will complete it. New chunk system hard relies on starlight and dataconverter, and most importantly the new concurrent utilities in ConcurrentUtil. Some of the old async chunk i/o interface (i.e the old file io thread reroutes _some_ calls to the new file io thread) is kept for plugin compat reasons. It will be removed in the next major version of minecraft. The old legacy chunk system patches have been moved to the removed folder in case we need them again.
2022-09-26 10:02:51 +02:00
+ @Nullable Runnable post = null;
+ int radius = 2;
+ if (args.length > 0) {
+ try {
+ final int parsed = Integer.parseInt(args[0]);
+ if (parsed < 0) {
+ sender.sendMessage(text("Radius cannot be negative!", RED));
+ return;
+ }
+ final int maxRadius = 32;
+ radius = Math.min(maxRadius, parsed);
+ if (radius != parsed) {
+ post = () -> sender.sendMessage(text("Radius '" + parsed + "' was not in the required range [0, " + maxRadius + "], it was lowered to the maximum (" + maxRadius + " chunks).", RED));
+ }
+ } catch (final Exception e) {
+ sender.sendMessage(text("'" + args[0] + "' is not a valid number.", RED));
+ return;
+ }
+ }
+
+ CraftPlayer player = (CraftPlayer) sender;
+ ServerPlayer handle = player.getHandle();
+ ServerLevel world = (ServerLevel) handle.level;
+ ThreadedLevelLightEngine lightengine = world.getChunkSource().getLightEngine();
+ this.starlightFixLight(handle, world, lightengine, radius, post);
+ }
+
+ private void starlightFixLight(
+ final ServerPlayer sender,
+ final ServerLevel world,
+ final ThreadedLevelLightEngine lightengine,
+ final int radius,
+ final @Nullable Runnable done
+ ) {
Rewrite chunk system (#8177) Patch documentation to come Issues with the old system that are fixed now: - World generation does not scale with cpu cores effectively. - Relies on the main thread for scheduling and maintaining chunk state, dropping chunk load/generate rates at lower tps. - Unreliable prioritisation of chunk gen/load calls that block the main thread. - Shutdown logic is utterly unreliable, as it has to wait for all chunks to unload - is it guaranteed that the chunk system is in a state on shutdown that it can reliably do this? Watchdog shutdown also typically failed due to thread checks, which is now resolved. - Saving of data is not unified (i.e can save chunk data without saving entity data, poses problems for desync if shutdown is really abnormal. - Entities are not loaded with chunks. This caused quite a bit of headache for Chunk#getEntities API, but now the new chunk system loads entities with chunks so that they are ready whenever the chunk loads in. Effectively brings the behavior back to 1.16 era, but still storing entities in their own separate regionfiles. The above list is not complete. The patch documentation will complete it. New chunk system hard relies on starlight and dataconverter, and most importantly the new concurrent utilities in ConcurrentUtil. Some of the old async chunk i/o interface (i.e the old file io thread reroutes _some_ calls to the new file io thread) is kept for plugin compat reasons. It will be removed in the next major version of minecraft. The old legacy chunk system patches have been moved to the removed folder in case we need them again.
2022-09-26 10:02:51 +02:00
+ final long start = System.nanoTime();
+ final java.util.LinkedHashSet<ChunkPos> chunks = new java.util.LinkedHashSet<>(MCUtil.getSpiralOutChunks(sender.blockPosition(), radius)); // getChunkCoordinates is actually just bad mappings, this function rets position as blockpos
2021-11-27 11:42:09 +01:00
+
Rewrite chunk system (#8177) Patch documentation to come Issues with the old system that are fixed now: - World generation does not scale with cpu cores effectively. - Relies on the main thread for scheduling and maintaining chunk state, dropping chunk load/generate rates at lower tps. - Unreliable prioritisation of chunk gen/load calls that block the main thread. - Shutdown logic is utterly unreliable, as it has to wait for all chunks to unload - is it guaranteed that the chunk system is in a state on shutdown that it can reliably do this? Watchdog shutdown also typically failed due to thread checks, which is now resolved. - Saving of data is not unified (i.e can save chunk data without saving entity data, poses problems for desync if shutdown is really abnormal. - Entities are not loaded with chunks. This caused quite a bit of headache for Chunk#getEntities API, but now the new chunk system loads entities with chunks so that they are ready whenever the chunk loads in. Effectively brings the behavior back to 1.16 era, but still storing entities in their own separate regionfiles. The above list is not complete. The patch documentation will complete it. New chunk system hard relies on starlight and dataconverter, and most importantly the new concurrent utilities in ConcurrentUtil. Some of the old async chunk i/o interface (i.e the old file io thread reroutes _some_ calls to the new file io thread) is kept for plugin compat reasons. It will be removed in the next major version of minecraft. The old legacy chunk system patches have been moved to the removed folder in case we need them again.
2022-09-26 10:02:51 +02:00
+ final int[] pending = new int[1];
+ for (java.util.Iterator<ChunkPos> iterator = chunks.iterator(); iterator.hasNext(); ) {
2021-11-27 11:42:09 +01:00
+ final ChunkPos chunkPos = iterator.next();
+
+ final @Nullable ChunkAccess chunk = (ChunkAccess) world.getChunkSource().getChunkForLighting(chunkPos.x, chunkPos.z);
2021-11-27 11:42:09 +01:00
+ if (chunk == null || !chunk.isLightCorrect() || !chunk.getStatus().isOrAfter(net.minecraft.world.level.chunk.ChunkStatus.LIGHT)) {
+ // cannot relight this chunk
+ iterator.remove();
+ continue;
+ }
+
+ ++pending[0];
+ }
+
Rewrite chunk system (#8177) Patch documentation to come Issues with the old system that are fixed now: - World generation does not scale with cpu cores effectively. - Relies on the main thread for scheduling and maintaining chunk state, dropping chunk load/generate rates at lower tps. - Unreliable prioritisation of chunk gen/load calls that block the main thread. - Shutdown logic is utterly unreliable, as it has to wait for all chunks to unload - is it guaranteed that the chunk system is in a state on shutdown that it can reliably do this? Watchdog shutdown also typically failed due to thread checks, which is now resolved. - Saving of data is not unified (i.e can save chunk data without saving entity data, poses problems for desync if shutdown is really abnormal. - Entities are not loaded with chunks. This caused quite a bit of headache for Chunk#getEntities API, but now the new chunk system loads entities with chunks so that they are ready whenever the chunk loads in. Effectively brings the behavior back to 1.16 era, but still storing entities in their own separate regionfiles. The above list is not complete. The patch documentation will complete it. New chunk system hard relies on starlight and dataconverter, and most importantly the new concurrent utilities in ConcurrentUtil. Some of the old async chunk i/o interface (i.e the old file io thread reroutes _some_ calls to the new file io thread) is kept for plugin compat reasons. It will be removed in the next major version of minecraft. The old legacy chunk system patches have been moved to the removed folder in case we need them again.
2022-09-26 10:02:51 +02:00
+ final int[] relitChunks = new int[1];
2021-11-27 11:42:09 +01:00
+ lightengine.relight(chunks,
Rewrite chunk system (#8177) Patch documentation to come Issues with the old system that are fixed now: - World generation does not scale with cpu cores effectively. - Relies on the main thread for scheduling and maintaining chunk state, dropping chunk load/generate rates at lower tps. - Unreliable prioritisation of chunk gen/load calls that block the main thread. - Shutdown logic is utterly unreliable, as it has to wait for all chunks to unload - is it guaranteed that the chunk system is in a state on shutdown that it can reliably do this? Watchdog shutdown also typically failed due to thread checks, which is now resolved. - Saving of data is not unified (i.e can save chunk data without saving entity data, poses problems for desync if shutdown is really abnormal. - Entities are not loaded with chunks. This caused quite a bit of headache for Chunk#getEntities API, but now the new chunk system loads entities with chunks so that they are ready whenever the chunk loads in. Effectively brings the behavior back to 1.16 era, but still storing entities in their own separate regionfiles. The above list is not complete. The patch documentation will complete it. New chunk system hard relies on starlight and dataconverter, and most importantly the new concurrent utilities in ConcurrentUtil. Some of the old async chunk i/o interface (i.e the old file io thread reroutes _some_ calls to the new file io thread) is kept for plugin compat reasons. It will be removed in the next major version of minecraft. The old legacy chunk system patches have been moved to the removed folder in case we need them again.
2022-09-26 10:02:51 +02:00
+ (final ChunkPos chunkPos) -> {
+ ++relitChunks[0];
+ sender.getBukkitEntity().sendMessage(text().color(DARK_AQUA).append(
+ text("Relit chunk ", BLUE), text(chunkPos.toString()),
+ text(", progress: ", BLUE), text((int) (Math.round(100.0 * (double) (relitChunks[0]) / (double) pending[0])) + "%")
+ ));
+ },
Rewrite chunk system (#8177) Patch documentation to come Issues with the old system that are fixed now: - World generation does not scale with cpu cores effectively. - Relies on the main thread for scheduling and maintaining chunk state, dropping chunk load/generate rates at lower tps. - Unreliable prioritisation of chunk gen/load calls that block the main thread. - Shutdown logic is utterly unreliable, as it has to wait for all chunks to unload - is it guaranteed that the chunk system is in a state on shutdown that it can reliably do this? Watchdog shutdown also typically failed due to thread checks, which is now resolved. - Saving of data is not unified (i.e can save chunk data without saving entity data, poses problems for desync if shutdown is really abnormal. - Entities are not loaded with chunks. This caused quite a bit of headache for Chunk#getEntities API, but now the new chunk system loads entities with chunks so that they are ready whenever the chunk loads in. Effectively brings the behavior back to 1.16 era, but still storing entities in their own separate regionfiles. The above list is not complete. The patch documentation will complete it. New chunk system hard relies on starlight and dataconverter, and most importantly the new concurrent utilities in ConcurrentUtil. Some of the old async chunk i/o interface (i.e the old file io thread reroutes _some_ calls to the new file io thread) is kept for plugin compat reasons. It will be removed in the next major version of minecraft. The old legacy chunk system patches have been moved to the removed folder in case we need them again.
2022-09-26 10:02:51 +02:00
+ (final int totalRelit) -> {
+ final long end = System.nanoTime();
+ final long diff = Math.round(1.0e-6 * (end - start));
+ sender.getBukkitEntity().sendMessage(text().color(DARK_AQUA).append(
+ text("Relit ", BLUE), text(totalRelit),
+ text(" chunks. Took ", BLUE), text(diff + "ms")
+ ));
+ if (done != null) {
+ done.run();
+ }
Rewrite chunk system (#8177) Patch documentation to come Issues with the old system that are fixed now: - World generation does not scale with cpu cores effectively. - Relies on the main thread for scheduling and maintaining chunk state, dropping chunk load/generate rates at lower tps. - Unreliable prioritisation of chunk gen/load calls that block the main thread. - Shutdown logic is utterly unreliable, as it has to wait for all chunks to unload - is it guaranteed that the chunk system is in a state on shutdown that it can reliably do this? Watchdog shutdown also typically failed due to thread checks, which is now resolved. - Saving of data is not unified (i.e can save chunk data without saving entity data, poses problems for desync if shutdown is really abnormal. - Entities are not loaded with chunks. This caused quite a bit of headache for Chunk#getEntities API, but now the new chunk system loads entities with chunks so that they are ready whenever the chunk loads in. Effectively brings the behavior back to 1.16 era, but still storing entities in their own separate regionfiles. The above list is not complete. The patch documentation will complete it. New chunk system hard relies on starlight and dataconverter, and most importantly the new concurrent utilities in ConcurrentUtil. Some of the old async chunk i/o interface (i.e the old file io thread reroutes _some_ calls to the new file io thread) is kept for plugin compat reasons. It will be removed in the next major version of minecraft. The old legacy chunk system patches have been moved to the removed folder in case we need them again.
2022-09-26 10:02:51 +02:00
+ }
+ );
2022-06-03 06:26:56 +02:00
+ sender.getBukkitEntity().sendMessage(text().color(BLUE).append(text("Relighting "), text(pending[0], DARK_AQUA), text(" chunks")));
2021-11-27 11:42:09 +01:00
+ }
Rewrite chunk system (#8177) Patch documentation to come Issues with the old system that are fixed now: - World generation does not scale with cpu cores effectively. - Relies on the main thread for scheduling and maintaining chunk state, dropping chunk load/generate rates at lower tps. - Unreliable prioritisation of chunk gen/load calls that block the main thread. - Shutdown logic is utterly unreliable, as it has to wait for all chunks to unload - is it guaranteed that the chunk system is in a state on shutdown that it can reliably do this? Watchdog shutdown also typically failed due to thread checks, which is now resolved. - Saving of data is not unified (i.e can save chunk data without saving entity data, poses problems for desync if shutdown is really abnormal. - Entities are not loaded with chunks. This caused quite a bit of headache for Chunk#getEntities API, but now the new chunk system loads entities with chunks so that they are ready whenever the chunk loads in. Effectively brings the behavior back to 1.16 era, but still storing entities in their own separate regionfiles. The above list is not complete. The patch documentation will complete it. New chunk system hard relies on starlight and dataconverter, and most importantly the new concurrent utilities in ConcurrentUtil. Some of the old async chunk i/o interface (i.e the old file io thread reroutes _some_ calls to the new file io thread) is kept for plugin compat reasons. It will be removed in the next major version of minecraft. The old legacy chunk system patches have been moved to the removed folder in case we need them again.
2022-09-26 10:02:51 +02:00
+}
2021-11-27 11:42:09 +01:00
diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/level/ChunkHolder.java
+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java
@@ -0,0 +0,0 @@ public class ChunkHolder {
private volatile CompletableFuture<Either<LevelChunk, ChunkHolder.ChunkLoadingFailure>> fullChunkFuture; private int fullChunkCreateCount; private volatile boolean isFullChunkReady; // Paper - cache chunk ticking stage
private volatile CompletableFuture<Either<LevelChunk, ChunkHolder.ChunkLoadingFailure>> tickingChunkFuture; private volatile boolean isTickingReady; // Paper - cache chunk ticking stage
private volatile CompletableFuture<Either<LevelChunk, ChunkHolder.ChunkLoadingFailure>> entityTickingChunkFuture; private volatile boolean isEntityTickingReady; // Paper - cache chunk ticking stage
- private CompletableFuture<ChunkAccess> chunkToSave;
+ public CompletableFuture<ChunkAccess> chunkToSave; // Paper - public
@Nullable
private final DebugBuffer<ChunkHolder.ChunkSaveDebug> chunkToSaveHistory;
public int oldTicketLevel;
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
private final LongSet entitiesInLevel;
2021-11-27 11:42:09 +01:00
public final ServerLevel level;
private final ThreadedLevelLightEngine lightEngine;
- private final BlockableEventLoop<Runnable> mainThreadExecutor;
+ public final BlockableEventLoop<Runnable> mainThreadExecutor; // Paper - public
public ChunkGenerator generator;
2022-06-08 13:44:30 +02:00
private RandomState randomState;
Rewrite chunk system (#8177) Patch documentation to come Issues with the old system that are fixed now: - World generation does not scale with cpu cores effectively. - Relies on the main thread for scheduling and maintaining chunk state, dropping chunk load/generate rates at lower tps. - Unreliable prioritisation of chunk gen/load calls that block the main thread. - Shutdown logic is utterly unreliable, as it has to wait for all chunks to unload - is it guaranteed that the chunk system is in a state on shutdown that it can reliably do this? Watchdog shutdown also typically failed due to thread checks, which is now resolved. - Saving of data is not unified (i.e can save chunk data without saving entity data, poses problems for desync if shutdown is really abnormal. - Entities are not loaded with chunks. This caused quite a bit of headache for Chunk#getEntities API, but now the new chunk system loads entities with chunks so that they are ready whenever the chunk loads in. Effectively brings the behavior back to 1.16 era, but still storing entities in their own separate regionfiles. The above list is not complete. The patch documentation will complete it. New chunk system hard relies on starlight and dataconverter, and most importantly the new concurrent utilities in ConcurrentUtil. Some of the old async chunk i/o interface (i.e the old file io thread reroutes _some_ calls to the new file io thread) is kept for plugin compat reasons. It will be removed in the next major version of minecraft. The old legacy chunk system patches have been moved to the removed folder in case we need them again.
2022-09-26 10:02:51 +02:00
public final Supplier<DimensionDataStorage> overworldDataStorage;
diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/level/DistanceManager.java
+++ b/src/main/java/net/minecraft/server/level/DistanceManager.java
@@ -0,0 +0,0 @@ public abstract class DistanceManager {
}
public void removeTicketsOnClosing() {
Rewrite chunk system (#8177) Patch documentation to come Issues with the old system that are fixed now: - World generation does not scale with cpu cores effectively. - Relies on the main thread for scheduling and maintaining chunk state, dropping chunk load/generate rates at lower tps. - Unreliable prioritisation of chunk gen/load calls that block the main thread. - Shutdown logic is utterly unreliable, as it has to wait for all chunks to unload - is it guaranteed that the chunk system is in a state on shutdown that it can reliably do this? Watchdog shutdown also typically failed due to thread checks, which is now resolved. - Saving of data is not unified (i.e can save chunk data without saving entity data, poses problems for desync if shutdown is really abnormal. - Entities are not loaded with chunks. This caused quite a bit of headache for Chunk#getEntities API, but now the new chunk system loads entities with chunks so that they are ready whenever the chunk loads in. Effectively brings the behavior back to 1.16 era, but still storing entities in their own separate regionfiles. The above list is not complete. The patch documentation will complete it. New chunk system hard relies on starlight and dataconverter, and most importantly the new concurrent utilities in ConcurrentUtil. Some of the old async chunk i/o interface (i.e the old file io thread reroutes _some_ calls to the new file io thread) is kept for plugin compat reasons. It will be removed in the next major version of minecraft. The old legacy chunk system patches have been moved to the removed folder in case we need them again.
2022-09-26 10:02:51 +02:00
- ImmutableSet<TicketType<?>> immutableset = ImmutableSet.of(TicketType.UNKNOWN, TicketType.POST_TELEPORT, TicketType.LIGHT, TicketType.FUTURE_AWAIT); // Paper - add additional tickets to preserve
+ ImmutableSet<TicketType<?>> immutableset = ImmutableSet.of(TicketType.UNKNOWN, TicketType.POST_TELEPORT, TicketType.LIGHT, TicketType.FUTURE_AWAIT, TicketType.CHUNK_RELIGHT, ca.spottedleaf.starlight.common.light.StarLightInterface.CHUNK_WORK_TICKET); // Paper - add additional tickets to preserve
ObjectIterator objectiterator = this.tickets.long2ObjectEntrySet().fastIterator();
while (objectiterator.hasNext()) {
2021-11-27 11:42:09 +01:00
diff --git a/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java b/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java
+++ b/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java
2022-03-01 06:43:03 +01:00
@@ -0,0 +0,0 @@ import net.minecraft.world.level.chunk.LightChunkGetter;
import net.minecraft.world.level.lighting.LevelLightEngine;
import org.slf4j.Logger;
2021-11-27 11:42:09 +01:00
+// Paper start
+import ca.spottedleaf.starlight.common.light.StarLightEngine;
+import io.papermc.paper.util.CoordinateUtils;
+import java.util.function.Supplier;
+import net.minecraft.world.level.lighting.LayerLightEventListener;
+import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
+import it.unimi.dsi.fastutil.longs.LongArrayList;
+import it.unimi.dsi.fastutil.longs.LongIterator;
+import net.minecraft.world.level.chunk.ChunkStatus;
+// Paper end
+
public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCloseable {
2022-03-01 06:43:03 +01:00
private static final Logger LOGGER = LogUtils.getLogger();
2021-11-27 11:42:09 +01:00
private final ProcessorMailbox<Runnable> taskMailbox;
@@ -0,0 +0,0 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl
private volatile int taskPerBatch = 5;
private final AtomicBoolean scheduled = new AtomicBoolean();
+ // Paper start - replace light engine impl
+ protected final ca.spottedleaf.starlight.common.light.StarLightInterface theLightEngine;
+ public final boolean hasBlockLight;
+ public final boolean hasSkyLight;
+ // Paper end - replace light engine impl
+
public ThreadedLevelLightEngine(LightChunkGetter chunkProvider, ChunkMap chunkStorage, boolean hasBlockLight, ProcessorMailbox<Runnable> processor, ProcessorHandle<ChunkTaskPriorityQueueSorter.Message<Runnable>> executor) {
- super(chunkProvider, true, hasBlockLight);
+ super(chunkProvider, false, false); // Paper - destroy vanilla light engine state
Rewrite chunk system (#8177) Patch documentation to come Issues with the old system that are fixed now: - World generation does not scale with cpu cores effectively. - Relies on the main thread for scheduling and maintaining chunk state, dropping chunk load/generate rates at lower tps. - Unreliable prioritisation of chunk gen/load calls that block the main thread. - Shutdown logic is utterly unreliable, as it has to wait for all chunks to unload - is it guaranteed that the chunk system is in a state on shutdown that it can reliably do this? Watchdog shutdown also typically failed due to thread checks, which is now resolved. - Saving of data is not unified (i.e can save chunk data without saving entity data, poses problems for desync if shutdown is really abnormal. - Entities are not loaded with chunks. This caused quite a bit of headache for Chunk#getEntities API, but now the new chunk system loads entities with chunks so that they are ready whenever the chunk loads in. Effectively brings the behavior back to 1.16 era, but still storing entities in their own separate regionfiles. The above list is not complete. The patch documentation will complete it. New chunk system hard relies on starlight and dataconverter, and most importantly the new concurrent utilities in ConcurrentUtil. Some of the old async chunk i/o interface (i.e the old file io thread reroutes _some_ calls to the new file io thread) is kept for plugin compat reasons. It will be removed in the next major version of minecraft. The old legacy chunk system patches have been moved to the removed folder in case we need them again.
2022-09-26 10:02:51 +02:00
this.chunkMap = chunkStorage;
2021-11-27 11:42:09 +01:00
this.sorterMailbox = executor;
this.taskMailbox = processor;
+ // Paper start - replace light engine impl
+ this.hasBlockLight = true;
+ this.hasSkyLight = hasBlockLight; // Nice variable name.
+ this.theLightEngine = new ca.spottedleaf.starlight.common.light.StarLightInterface(chunkProvider, this.hasSkyLight, this.hasBlockLight, this);
+ // Paper end - replace light engine impl
+ }
+
Rewrite chunk system (#8177) Patch documentation to come Issues with the old system that are fixed now: - World generation does not scale with cpu cores effectively. - Relies on the main thread for scheduling and maintaining chunk state, dropping chunk load/generate rates at lower tps. - Unreliable prioritisation of chunk gen/load calls that block the main thread. - Shutdown logic is utterly unreliable, as it has to wait for all chunks to unload - is it guaranteed that the chunk system is in a state on shutdown that it can reliably do this? Watchdog shutdown also typically failed due to thread checks, which is now resolved. - Saving of data is not unified (i.e can save chunk data without saving entity data, poses problems for desync if shutdown is really abnormal. - Entities are not loaded with chunks. This caused quite a bit of headache for Chunk#getEntities API, but now the new chunk system loads entities with chunks so that they are ready whenever the chunk loads in. Effectively brings the behavior back to 1.16 era, but still storing entities in their own separate regionfiles. The above list is not complete. The patch documentation will complete it. New chunk system hard relies on starlight and dataconverter, and most importantly the new concurrent utilities in ConcurrentUtil. Some of the old async chunk i/o interface (i.e the old file io thread reroutes _some_ calls to the new file io thread) is kept for plugin compat reasons. It will be removed in the next major version of minecraft. The old legacy chunk system patches have been moved to the removed folder in case we need them again.
2022-09-26 10:02:51 +02:00
+ // Paper start - replace light engine impl
2021-11-27 11:42:09 +01:00
+ protected final ChunkAccess getChunk(final int chunkX, final int chunkZ) {
+ return ((ServerLevel)this.theLightEngine.getWorld()).getChunkSource().getChunkAtImmediately(chunkX, chunkZ);
}
2021-11-27 11:42:09 +01:00
+ protected long relightCounter;
+
+ public int relight(java.util.Set<ChunkPos> chunks_param,
+ java.util.function.Consumer<ChunkPos> chunkLightCallback,
+ java.util.function.IntConsumer onComplete) {
+ if (!org.bukkit.Bukkit.isPrimaryThread()) {
+ throw new IllegalStateException("Must only be called on the main thread");
+ }
+
+ java.util.Set<ChunkPos> chunks = new java.util.LinkedHashSet<>(chunks_param);
+ // add tickets
+ java.util.Map<ChunkPos, Long> ticketIds = new java.util.HashMap<>();
+ int totalChunks = 0;
+ for (java.util.Iterator<ChunkPos> iterator = chunks.iterator(); iterator.hasNext();) {
+ final ChunkPos chunkPos = iterator.next();
+
+ final ChunkAccess chunk = (ChunkAccess)((ServerLevel)this.theLightEngine.getWorld()).getChunkSource().getChunkForLighting(chunkPos.x, chunkPos.z);
2021-11-27 11:42:09 +01:00
+ if (chunk == null || !chunk.isLightCorrect() || !chunk.getStatus().isOrAfter(ChunkStatus.LIGHT)) {
+ // cannot relight this chunk
+ iterator.remove();
+ continue;
+ }
+
+ final Long id = Long.valueOf(this.relightCounter++);
+
+ ((ServerLevel)this.theLightEngine.getWorld()).getChunkSource().addTicketAtLevel(TicketType.CHUNK_RELIGHT, chunkPos, io.papermc.paper.util.MCUtil.getTicketLevelFor(ChunkStatus.LIGHT), id);
2021-11-27 11:42:09 +01:00
+ ticketIds.put(chunkPos, id);
+
+ ++totalChunks;
+ }
+
+ this.taskMailbox.tell(() -> {
+ this.theLightEngine.relightChunks(chunks, (ChunkPos chunkPos) -> {
+ chunkLightCallback.accept(chunkPos);
+ ((java.util.concurrent.Executor)((ServerLevel)this.theLightEngine.getWorld()).getChunkSource().mainThreadProcessor).execute(() -> {
+ ((ServerLevel)this.theLightEngine.getWorld()).getChunkSource().chunkMap.getUpdatingChunkIfPresent(chunkPos.toLong()).broadcast(new net.minecraft.network.protocol.game.ClientboundLightUpdatePacket(chunkPos, ThreadedLevelLightEngine.this, null, null, true), false);
+ ((ServerLevel)this.theLightEngine.getWorld()).getChunkSource().removeTicketAtLevel(TicketType.CHUNK_RELIGHT, chunkPos, io.papermc.paper.util.MCUtil.getTicketLevelFor(ChunkStatus.LIGHT), ticketIds.get(chunkPos));
2021-11-27 11:42:09 +01:00
+ });
+ }, onComplete);
+ });
+ this.tryScheduleUpdate();
+
+ return totalChunks;
+ }
+
+ private final Long2IntOpenHashMap chunksBeingWorkedOn = new Long2IntOpenHashMap();
+
+ private void queueTaskForSection(final int chunkX, final int chunkY, final int chunkZ, final Supplier<CompletableFuture<Void>> runnable) {
+ final ServerLevel world = (ServerLevel)this.theLightEngine.getWorld();
+
+ final ChunkAccess center = this.theLightEngine.getAnyChunkNow(chunkX, chunkZ);
+ if (center == null || !center.getStatus().isOrAfter(ChunkStatus.LIGHT)) {
+ // do not accept updates in unlit chunks, unless we might be generating a chunk. thanks to the amazing
+ // chunk scheduling, we could be lighting and generating a chunk at the same time
+ return;
+ }
+
+ if (center.getStatus() != ChunkStatus.FULL) {
+ // do not keep chunk loaded, we are probably in a gen thread
+ // if we proceed to add a ticket the chunk will be loaded, which is not what we want (avoid cascading gen)
+ runnable.get();
+ return;
+ }
+
+ if (!world.getChunkSource().chunkMap.mainThreadExecutor.isSameThread()) {
+ // ticket logic is not safe to run off-main, re-schedule
+ world.getChunkSource().chunkMap.mainThreadExecutor.execute(() -> {
+ this.queueTaskForSection(chunkX, chunkY, chunkZ, runnable);
+ });
+ return;
+ }
+
+ final long key = CoordinateUtils.getChunkKey(chunkX, chunkZ);
+
+ final CompletableFuture<Void> updateFuture = runnable.get();
+
+ if (updateFuture == null) {
+ // not scheduled
+ return;
+ }
+
+ final int references = this.chunksBeingWorkedOn.addTo(key, 1);
+ if (references == 0) {
+ final ChunkPos pos = new ChunkPos(chunkX, chunkZ);
+ world.getChunkSource().addRegionTicket(ca.spottedleaf.starlight.common.light.StarLightInterface.CHUNK_WORK_TICKET, pos, 0, pos);
+ }
+
+ updateFuture.thenAcceptAsync((final Void ignore) -> {
+ final int newReferences = this.chunksBeingWorkedOn.get(key);
+ if (newReferences == 1) {
+ this.chunksBeingWorkedOn.remove(key);
+ final ChunkPos pos = new ChunkPos(chunkX, chunkZ);
+ world.getChunkSource().removeRegionTicket(ca.spottedleaf.starlight.common.light.StarLightInterface.CHUNK_WORK_TICKET, pos, 0, pos);
+ } else {
+ this.chunksBeingWorkedOn.put(key, newReferences - 1);
+ }
+ }, world.getChunkSource().chunkMap.mainThreadExecutor).whenComplete((final Void ignore, final Throwable thr) -> {
+ if (thr != null) {
2022-03-01 06:43:03 +01:00
+ LOGGER.error("Failed to remove ticket level for post chunk task " + new ChunkPos(chunkX, chunkZ), thr);
2021-11-27 11:42:09 +01:00
+ }
+ });
+ }
+
+ @Override
+ public boolean hasLightWork() {
+ // route to new light engine
Rewrite chunk system (#8177) Patch documentation to come Issues with the old system that are fixed now: - World generation does not scale with cpu cores effectively. - Relies on the main thread for scheduling and maintaining chunk state, dropping chunk load/generate rates at lower tps. - Unreliable prioritisation of chunk gen/load calls that block the main thread. - Shutdown logic is utterly unreliable, as it has to wait for all chunks to unload - is it guaranteed that the chunk system is in a state on shutdown that it can reliably do this? Watchdog shutdown also typically failed due to thread checks, which is now resolved. - Saving of data is not unified (i.e can save chunk data without saving entity data, poses problems for desync if shutdown is really abnormal. - Entities are not loaded with chunks. This caused quite a bit of headache for Chunk#getEntities API, but now the new chunk system loads entities with chunks so that they are ready whenever the chunk loads in. Effectively brings the behavior back to 1.16 era, but still storing entities in their own separate regionfiles. The above list is not complete. The patch documentation will complete it. New chunk system hard relies on starlight and dataconverter, and most importantly the new concurrent utilities in ConcurrentUtil. Some of the old async chunk i/o interface (i.e the old file io thread reroutes _some_ calls to the new file io thread) is kept for plugin compat reasons. It will be removed in the next major version of minecraft. The old legacy chunk system patches have been moved to the removed folder in case we need them again.
2022-09-26 10:02:51 +02:00
+ return this.theLightEngine.hasUpdates();
+ }
+
2021-11-27 11:42:09 +01:00
+ @Override
+ public LayerLightEventListener getLayerListener(final LightLayer lightType) {
+ return lightType == LightLayer.BLOCK ? this.theLightEngine.getBlockReader() : this.theLightEngine.getSkyReader();
+ }
+
+ @Override
+ public int getRawBrightness(final BlockPos pos, final int ambientDarkness) {
+ // need to use new light hooks for this
+ final int sky = this.theLightEngine.getSkyReader().getLightValue(pos) - ambientDarkness;
+ // Don't fetch the block light level if the skylight level is 15, since the value will never be higher.
+ if (sky == 15) return 15;
2021-11-27 11:42:09 +01:00
+ final int block = this.theLightEngine.getBlockReader().getLightValue(pos);
+ return Math.max(sky, block);
+ }
+ // Paper end - replace light engine imp
+
@Override
public void close() {
}
@@ -0,0 +0,0 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl
@Override
public void checkBlock(BlockPos pos) {
- BlockPos blockPos = pos.immutable();
- this.addTask(SectionPos.blockToSectionCoord(pos.getX()), SectionPos.blockToSectionCoord(pos.getZ()), ThreadedLevelLightEngine.TaskType.POST_UPDATE, Util.name(() -> {
- super.checkBlock(blockPos);
- }, () -> {
- return "checkBlock " + blockPos;
- }));
+ // Paper start - replace light engine impl
+ final BlockPos posCopy = pos.immutable();
+ this.queueTaskForSection(posCopy.getX() >> 4, posCopy.getY() >> 4, posCopy.getZ() >> 4, () -> {
+ return this.theLightEngine.blockChange(posCopy);
+ });
+ // Paper end - replace light engine impl
}
protected void updateChunkStatus(ChunkPos pos) {
+ if (true) return; // Paper - replace light engine impl
this.addTask(pos.x, pos.z, () -> {
return 0;
}, ThreadedLevelLightEngine.TaskType.PRE_UPDATE, Util.name(() -> {
@@ -0,0 +0,0 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl
@Override
public void updateSectionStatus(SectionPos pos, boolean notReady) {
- this.addTask(pos.x(), pos.z(), () -> {
- return 0;
- }, ThreadedLevelLightEngine.TaskType.PRE_UPDATE, Util.name(() -> {
- super.updateSectionStatus(pos, notReady);
- }, () -> {
- return "updateSectionStatus " + pos + " " + notReady;
- }));
+ // Paper start - replace light engine impl
+ this.queueTaskForSection(pos.getX(), pos.getY(), pos.getZ(), () -> {
+ return this.theLightEngine.sectionChange(pos, notReady);
+ });
+ // Paper end - replace light engine impl
}
@Override
public void enableLightSources(ChunkPos pos, boolean retainData) {
+ if (true) return; // Paper - replace light engine impl
this.addTask(pos.x, pos.z, ThreadedLevelLightEngine.TaskType.PRE_UPDATE, Util.name(() -> {
super.enableLightSources(pos, retainData);
}, () -> {
@@ -0,0 +0,0 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl
@Override
public void queueSectionData(LightLayer lightType, SectionPos pos, @Nullable DataLayer nibbles, boolean nonEdge) {
+ if (true) return; // Paper - replace light engine impl
this.addTask(pos.x(), pos.z(), () -> {
return 0;
}, ThreadedLevelLightEngine.TaskType.PRE_UPDATE, Util.name(() -> {
@@ -0,0 +0,0 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl
@Override
public void retainData(ChunkPos pos, boolean retainData) {
+ if (true) return; // Paper - replace light engine impl
this.addTask(pos.x, pos.z, () -> {
return 0;
}, ThreadedLevelLightEngine.TaskType.PRE_UPDATE, Util.name(() -> {
@@ -0,0 +0,0 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl
}
public CompletableFuture<ChunkAccess> lightChunk(ChunkAccess chunk, boolean excludeBlocks) {
+ // Paper start - replace light engine impl
+ if (true) {
+ boolean lit = excludeBlocks;
+ final ChunkPos chunkPos = chunk.getPos();
+
+ return CompletableFuture.supplyAsync(() -> {
+ final Boolean[] emptySections = StarLightEngine.getEmptySectionsForChunk(chunk);
+ if (!lit) {
+ chunk.setLightCorrect(false);
+ this.theLightEngine.lightChunk(chunk, emptySections);
+ chunk.setLightCorrect(true);
+ } else {
+ this.theLightEngine.forceLoadInChunk(chunk, emptySections);
+ // can't really force the chunk to be edged checked, as we need neighbouring chunks - but we don't have
+ // them, so if it's not loaded then i guess we can't do edge checks. later loads of the chunk should
+ // catch what we miss here.
+ this.theLightEngine.checkChunkEdges(chunkPos.x, chunkPos.z);
+ }
+
+ this.chunkMap.releaseLightTicket(chunkPos);
+ return chunk;
+ }, (runnable) -> {
+ this.theLightEngine.scheduleChunkLight(chunkPos, runnable);
+ this.tryScheduleUpdate();
+ }).whenComplete((final ChunkAccess c, final Throwable throwable) -> {
+ if (throwable != null) {
2022-03-01 06:43:03 +01:00
+ LOGGER.error("Failed to light chunk " + chunkPos, throwable);
2021-11-27 11:42:09 +01:00
+ }
+ });
+ }
+ // Paper end - replace light engine impl
ChunkPos chunkPos = chunk.getPos();
Rewrite chunk system (#8177) Patch documentation to come Issues with the old system that are fixed now: - World generation does not scale with cpu cores effectively. - Relies on the main thread for scheduling and maintaining chunk state, dropping chunk load/generate rates at lower tps. - Unreliable prioritisation of chunk gen/load calls that block the main thread. - Shutdown logic is utterly unreliable, as it has to wait for all chunks to unload - is it guaranteed that the chunk system is in a state on shutdown that it can reliably do this? Watchdog shutdown also typically failed due to thread checks, which is now resolved. - Saving of data is not unified (i.e can save chunk data without saving entity data, poses problems for desync if shutdown is really abnormal. - Entities are not loaded with chunks. This caused quite a bit of headache for Chunk#getEntities API, but now the new chunk system loads entities with chunks so that they are ready whenever the chunk loads in. Effectively brings the behavior back to 1.16 era, but still storing entities in their own separate regionfiles. The above list is not complete. The patch documentation will complete it. New chunk system hard relies on starlight and dataconverter, and most importantly the new concurrent utilities in ConcurrentUtil. Some of the old async chunk i/o interface (i.e the old file io thread reroutes _some_ calls to the new file io thread) is kept for plugin compat reasons. It will be removed in the next major version of minecraft. The old legacy chunk system patches have been moved to the removed folder in case we need them again.
2022-09-26 10:02:51 +02:00
chunk.setLightCorrect(false);
this.addTask(chunkPos.x, chunkPos.z, ThreadedLevelLightEngine.TaskType.PRE_UPDATE, Util.name(() -> {
2021-11-27 11:42:09 +01:00
@@ -0,0 +0,0 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl
}
public void tryScheduleUpdate() {
Rewrite chunk system (#8177) Patch documentation to come Issues with the old system that are fixed now: - World generation does not scale with cpu cores effectively. - Relies on the main thread for scheduling and maintaining chunk state, dropping chunk load/generate rates at lower tps. - Unreliable prioritisation of chunk gen/load calls that block the main thread. - Shutdown logic is utterly unreliable, as it has to wait for all chunks to unload - is it guaranteed that the chunk system is in a state on shutdown that it can reliably do this? Watchdog shutdown also typically failed due to thread checks, which is now resolved. - Saving of data is not unified (i.e can save chunk data without saving entity data, poses problems for desync if shutdown is really abnormal. - Entities are not loaded with chunks. This caused quite a bit of headache for Chunk#getEntities API, but now the new chunk system loads entities with chunks so that they are ready whenever the chunk loads in. Effectively brings the behavior back to 1.16 era, but still storing entities in their own separate regionfiles. The above list is not complete. The patch documentation will complete it. New chunk system hard relies on starlight and dataconverter, and most importantly the new concurrent utilities in ConcurrentUtil. Some of the old async chunk i/o interface (i.e the old file io thread reroutes _some_ calls to the new file io thread) is kept for plugin compat reasons. It will be removed in the next major version of minecraft. The old legacy chunk system patches have been moved to the removed folder in case we need them again.
2022-09-26 10:02:51 +02:00
- if ((!this.lightTasks.isEmpty() || super.hasLightWork()) && this.scheduled.compareAndSet(false, true)) {
2021-11-27 11:42:09 +01:00
+ if (this.hasLightWork() && this.scheduled.compareAndSet(false, true)) { // Paper // Paper - rewrite light engine
this.taskMailbox.tell(() -> {
this.runUpdate();
this.scheduled.set(false);
@@ -0,0 +0,0 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl
}
Rewrite chunk system (#8177) Patch documentation to come Issues with the old system that are fixed now: - World generation does not scale with cpu cores effectively. - Relies on the main thread for scheduling and maintaining chunk state, dropping chunk load/generate rates at lower tps. - Unreliable prioritisation of chunk gen/load calls that block the main thread. - Shutdown logic is utterly unreliable, as it has to wait for all chunks to unload - is it guaranteed that the chunk system is in a state on shutdown that it can reliably do this? Watchdog shutdown also typically failed due to thread checks, which is now resolved. - Saving of data is not unified (i.e can save chunk data without saving entity data, poses problems for desync if shutdown is really abnormal. - Entities are not loaded with chunks. This caused quite a bit of headache for Chunk#getEntities API, but now the new chunk system loads entities with chunks so that they are ready whenever the chunk loads in. Effectively brings the behavior back to 1.16 era, but still storing entities in their own separate regionfiles. The above list is not complete. The patch documentation will complete it. New chunk system hard relies on starlight and dataconverter, and most importantly the new concurrent utilities in ConcurrentUtil. Some of the old async chunk i/o interface (i.e the old file io thread reroutes _some_ calls to the new file io thread) is kept for plugin compat reasons. It will be removed in the next major version of minecraft. The old legacy chunk system patches have been moved to the removed folder in case we need them again.
2022-09-26 10:02:51 +02:00
objectListIterator.back(j);
- super.runUpdates(Integer.MAX_VALUE, true, true);
+ this.theLightEngine.propagateChanges(); // Paper - rewrite light engine
for(int var5 = 0; objectListIterator.hasNext() && var5 < i; ++var5) {
Pair<ThreadedLevelLightEngine.TaskType, Runnable> pair2 = objectListIterator.next();
2021-11-27 11:42:09 +01:00
diff --git a/src/main/java/net/minecraft/server/level/TicketType.java b/src/main/java/net/minecraft/server/level/TicketType.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/level/TicketType.java
+++ b/src/main/java/net/minecraft/server/level/TicketType.java
@@ -0,0 +0,0 @@ public class TicketType<T> {
Rewrite chunk system (#8177) Patch documentation to come Issues with the old system that are fixed now: - World generation does not scale with cpu cores effectively. - Relies on the main thread for scheduling and maintaining chunk state, dropping chunk load/generate rates at lower tps. - Unreliable prioritisation of chunk gen/load calls that block the main thread. - Shutdown logic is utterly unreliable, as it has to wait for all chunks to unload - is it guaranteed that the chunk system is in a state on shutdown that it can reliably do this? Watchdog shutdown also typically failed due to thread checks, which is now resolved. - Saving of data is not unified (i.e can save chunk data without saving entity data, poses problems for desync if shutdown is really abnormal. - Entities are not loaded with chunks. This caused quite a bit of headache for Chunk#getEntities API, but now the new chunk system loads entities with chunks so that they are ready whenever the chunk loads in. Effectively brings the behavior back to 1.16 era, but still storing entities in their own separate regionfiles. The above list is not complete. The patch documentation will complete it. New chunk system hard relies on starlight and dataconverter, and most importantly the new concurrent utilities in ConcurrentUtil. Some of the old async chunk i/o interface (i.e the old file io thread reroutes _some_ calls to the new file io thread) is kept for plugin compat reasons. It will be removed in the next major version of minecraft. The old legacy chunk system patches have been moved to the removed folder in case we need them again.
2022-09-26 10:02:51 +02:00
public static final TicketType<ChunkPos> UNKNOWN = TicketType.create("unknown", Comparator.comparingLong(ChunkPos::toLong), 1);
public static final TicketType<Unit> PLUGIN = TicketType.create("plugin", (a, b) -> 0); // CraftBukkit
2021-11-27 11:42:09 +01:00
public static final TicketType<org.bukkit.plugin.Plugin> PLUGIN_TICKET = TicketType.create("plugin_ticket", (plugin1, plugin2) -> plugin1.getClass().getName().compareTo(plugin2.getClass().getName())); // CraftBukkit
+ public static final TicketType<Long> CHUNK_RELIGHT = create("light_update", Long::compareTo); // Paper - ensure chunks stay loaded for lighting
public static <T> TicketType<T> create(String name, Comparator<T> argumentComparator) {
return new TicketType<>(name, argumentComparator, 0L);
diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java
+++ b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java
@@ -0,0 +0,0 @@ public abstract class BlockBehaviour {
this.hasPostProcess = blockbase_info.hasPostProcess;
this.emissiveRendering = blockbase_info.emissiveRendering;
2022-06-08 13:44:30 +02:00
this.offsetType = (BlockBehaviour.OffsetType) blockbase_info.offsetType.apply(this.asState());
2021-11-27 11:42:09 +01:00
+ this.conditionallyFullOpaque = this.isOpaque() & this.isTransparentOnSomeFaces(); // Paper
}
Rewrite chunk system (#8177) Patch documentation to come Issues with the old system that are fixed now: - World generation does not scale with cpu cores effectively. - Relies on the main thread for scheduling and maintaining chunk state, dropping chunk load/generate rates at lower tps. - Unreliable prioritisation of chunk gen/load calls that block the main thread. - Shutdown logic is utterly unreliable, as it has to wait for all chunks to unload - is it guaranteed that the chunk system is in a state on shutdown that it can reliably do this? Watchdog shutdown also typically failed due to thread checks, which is now resolved. - Saving of data is not unified (i.e can save chunk data without saving entity data, poses problems for desync if shutdown is really abnormal. - Entities are not loaded with chunks. This caused quite a bit of headache for Chunk#getEntities API, but now the new chunk system loads entities with chunks so that they are ready whenever the chunk loads in. Effectively brings the behavior back to 1.16 era, but still storing entities in their own separate regionfiles. The above list is not complete. The patch documentation will complete it. New chunk system hard relies on starlight and dataconverter, and most importantly the new concurrent utilities in ConcurrentUtil. Some of the old async chunk i/o interface (i.e the old file io thread reroutes _some_ calls to the new file io thread) is kept for plugin compat reasons. It will be removed in the next major version of minecraft. The old legacy chunk system patches have been moved to the removed folder in case we need them again.
2022-09-26 10:02:51 +02:00
// Paper start
2021-11-27 11:42:09 +01:00
@@ -0,0 +0,0 @@ public abstract class BlockBehaviour {
Rewrite chunk system (#8177) Patch documentation to come Issues with the old system that are fixed now: - World generation does not scale with cpu cores effectively. - Relies on the main thread for scheduling and maintaining chunk state, dropping chunk load/generate rates at lower tps. - Unreliable prioritisation of chunk gen/load calls that block the main thread. - Shutdown logic is utterly unreliable, as it has to wait for all chunks to unload - is it guaranteed that the chunk system is in a state on shutdown that it can reliably do this? Watchdog shutdown also typically failed due to thread checks, which is now resolved. - Saving of data is not unified (i.e can save chunk data without saving entity data, poses problems for desync if shutdown is really abnormal. - Entities are not loaded with chunks. This caused quite a bit of headache for Chunk#getEntities API, but now the new chunk system loads entities with chunks so that they are ready whenever the chunk loads in. Effectively brings the behavior back to 1.16 era, but still storing entities in their own separate regionfiles. The above list is not complete. The patch documentation will complete it. New chunk system hard relies on starlight and dataconverter, and most importantly the new concurrent utilities in ConcurrentUtil. Some of the old async chunk i/o interface (i.e the old file io thread reroutes _some_ calls to the new file io thread) is kept for plugin compat reasons. It will be removed in the next major version of minecraft. The old legacy chunk system patches have been moved to the removed folder in case we need them again.
2022-09-26 10:02:51 +02:00
return this.shapeExceedsCube;
}
2021-11-27 11:42:09 +01:00
// Paper end
Rewrite chunk system (#8177) Patch documentation to come Issues with the old system that are fixed now: - World generation does not scale with cpu cores effectively. - Relies on the main thread for scheduling and maintaining chunk state, dropping chunk load/generate rates at lower tps. - Unreliable prioritisation of chunk gen/load calls that block the main thread. - Shutdown logic is utterly unreliable, as it has to wait for all chunks to unload - is it guaranteed that the chunk system is in a state on shutdown that it can reliably do this? Watchdog shutdown also typically failed due to thread checks, which is now resolved. - Saving of data is not unified (i.e can save chunk data without saving entity data, poses problems for desync if shutdown is really abnormal. - Entities are not loaded with chunks. This caused quite a bit of headache for Chunk#getEntities API, but now the new chunk system loads entities with chunks so that they are ready whenever the chunk loads in. Effectively brings the behavior back to 1.16 era, but still storing entities in their own separate regionfiles. The above list is not complete. The patch documentation will complete it. New chunk system hard relies on starlight and dataconverter, and most importantly the new concurrent utilities in ConcurrentUtil. Some of the old async chunk i/o interface (i.e the old file io thread reroutes _some_ calls to the new file io thread) is kept for plugin compat reasons. It will be removed in the next major version of minecraft. The old legacy chunk system patches have been moved to the removed folder in case we need them again.
2022-09-26 10:02:51 +02:00
+ // Paper start - starlight
2021-11-27 11:42:09 +01:00
+ protected int opacityIfCached = -1;
+ // ret -1 if opacity is dynamic, or -1 if the block is conditionally full opaque, else return opacity in [0, 15]
+ public final int getOpacityIfCached() {
+ return this.opacityIfCached;
+ }
+
+ protected final boolean conditionallyFullOpaque;
+ public final boolean isConditionallyFullOpaque() {
+ return this.conditionallyFullOpaque;
+ }
Rewrite chunk system (#8177) Patch documentation to come Issues with the old system that are fixed now: - World generation does not scale with cpu cores effectively. - Relies on the main thread for scheduling and maintaining chunk state, dropping chunk load/generate rates at lower tps. - Unreliable prioritisation of chunk gen/load calls that block the main thread. - Shutdown logic is utterly unreliable, as it has to wait for all chunks to unload - is it guaranteed that the chunk system is in a state on shutdown that it can reliably do this? Watchdog shutdown also typically failed due to thread checks, which is now resolved. - Saving of data is not unified (i.e can save chunk data without saving entity data, poses problems for desync if shutdown is really abnormal. - Entities are not loaded with chunks. This caused quite a bit of headache for Chunk#getEntities API, but now the new chunk system loads entities with chunks so that they are ready whenever the chunk loads in. Effectively brings the behavior back to 1.16 era, but still storing entities in their own separate regionfiles. The above list is not complete. The patch documentation will complete it. New chunk system hard relies on starlight and dataconverter, and most importantly the new concurrent utilities in ConcurrentUtil. Some of the old async chunk i/o interface (i.e the old file io thread reroutes _some_ calls to the new file io thread) is kept for plugin compat reasons. It will be removed in the next major version of minecraft. The old legacy chunk system patches have been moved to the removed folder in case we need them again.
2022-09-26 10:02:51 +02:00
+ // Paper end - starlight
2021-11-27 11:42:09 +01:00
public void initCache() {
Rewrite chunk system (#8177) Patch documentation to come Issues with the old system that are fixed now: - World generation does not scale with cpu cores effectively. - Relies on the main thread for scheduling and maintaining chunk state, dropping chunk load/generate rates at lower tps. - Unreliable prioritisation of chunk gen/load calls that block the main thread. - Shutdown logic is utterly unreliable, as it has to wait for all chunks to unload - is it guaranteed that the chunk system is in a state on shutdown that it can reliably do this? Watchdog shutdown also typically failed due to thread checks, which is now resolved. - Saving of data is not unified (i.e can save chunk data without saving entity data, poses problems for desync if shutdown is really abnormal. - Entities are not loaded with chunks. This caused quite a bit of headache for Chunk#getEntities API, but now the new chunk system loads entities with chunks so that they are ready whenever the chunk loads in. Effectively brings the behavior back to 1.16 era, but still storing entities in their own separate regionfiles. The above list is not complete. The patch documentation will complete it. New chunk system hard relies on starlight and dataconverter, and most importantly the new concurrent utilities in ConcurrentUtil. Some of the old async chunk i/o interface (i.e the old file io thread reroutes _some_ calls to the new file io thread) is kept for plugin compat reasons. It will be removed in the next major version of minecraft. The old legacy chunk system patches have been moved to the removed folder in case we need them again.
2022-09-26 10:02:51 +02:00
if (!this.getBlock().hasDynamicShape()) {
2021-11-27 11:42:09 +01:00
this.cache = new BlockBehaviour.BlockStateBase.Cache(this.asState());
}
this.shapeExceedsCube = this.cache == null || this.cache.largeCollisionShape; // Paper - moved from actual method to here
Rewrite chunk system (#8177) Patch documentation to come Issues with the old system that are fixed now: - World generation does not scale with cpu cores effectively. - Relies on the main thread for scheduling and maintaining chunk state, dropping chunk load/generate rates at lower tps. - Unreliable prioritisation of chunk gen/load calls that block the main thread. - Shutdown logic is utterly unreliable, as it has to wait for all chunks to unload - is it guaranteed that the chunk system is in a state on shutdown that it can reliably do this? Watchdog shutdown also typically failed due to thread checks, which is now resolved. - Saving of data is not unified (i.e can save chunk data without saving entity data, poses problems for desync if shutdown is really abnormal. - Entities are not loaded with chunks. This caused quite a bit of headache for Chunk#getEntities API, but now the new chunk system loads entities with chunks so that they are ready whenever the chunk loads in. Effectively brings the behavior back to 1.16 era, but still storing entities in their own separate regionfiles. The above list is not complete. The patch documentation will complete it. New chunk system hard relies on starlight and dataconverter, and most importantly the new concurrent utilities in ConcurrentUtil. Some of the old async chunk i/o interface (i.e the old file io thread reroutes _some_ calls to the new file io thread) is kept for plugin compat reasons. It will be removed in the next major version of minecraft. The old legacy chunk system patches have been moved to the removed folder in case we need them again.
2022-09-26 10:02:51 +02:00
+ this.opacityIfCached = this.cache == null || this.isConditionallyFullOpaque() ? -1 : this.cache.lightBlock; // Paper - starlight - cache opacity for light
2021-11-27 11:42:09 +01:00
}
diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java b/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java
+++ b/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java
@@ -0,0 +0,0 @@ public abstract class ChunkAccess implements BlockGetter, BiomeManager.NoiseBiom
private static final org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry DATA_TYPE_REGISTRY = new org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry();
public org.bukkit.craftbukkit.persistence.DirtyCraftPersistentDataContainer persistentDataContainer = new org.bukkit.craftbukkit.persistence.DirtyCraftPersistentDataContainer(ChunkAccess.DATA_TYPE_REGISTRY);
// CraftBukkit end
2021-11-27 11:42:09 +01:00
+ // Paper start - rewrite light engine
+ private volatile ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] blockNibbles;
+
+ private volatile ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] skyNibbles;
+
+ private volatile boolean[] skyEmptinessMap;
+
+ private volatile boolean[] blockEmptinessMap;
+
+ public ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] getBlockNibbles() {
+ return this.blockNibbles;
+ }
+
+ public void setBlockNibbles(final ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] nibbles) {
+ this.blockNibbles = nibbles;
+ }
+
+ public ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] getSkyNibbles() {
+ return this.skyNibbles;
+ }
+
+ public void setSkyNibbles(final ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] nibbles) {
+ this.skyNibbles = nibbles;
+ }
+
+ public boolean[] getSkyEmptinessMap() {
+ return this.skyEmptinessMap;
+ }
+
+ public void setSkyEmptinessMap(final boolean[] emptinessMap) {
+ this.skyEmptinessMap = emptinessMap;
+ }
+
+ public boolean[] getBlockEmptinessMap() {
+ return this.blockEmptinessMap;
+ }
+
+ public void setBlockEmptinessMap(final boolean[] emptinessMap) {
+ this.blockEmptinessMap = emptinessMap;
+ }
+ // Paper end - rewrite light engine
2021-11-27 11:42:09 +01:00
public ChunkAccess(ChunkPos pos, UpgradeData upgradeData, LevelHeightAccessor heightLimitView, Registry<Biome> biome, long inhabitedTime, @Nullable LevelChunkSection[] sectionArrayInitializer, @Nullable BlendingData blendingData) {
this.locX = pos.x; this.locZ = pos.z; // Paper - reduce need for field lookups
Rewrite chunk system (#8177) Patch documentation to come Issues with the old system that are fixed now: - World generation does not scale with cpu cores effectively. - Relies on the main thread for scheduling and maintaining chunk state, dropping chunk load/generate rates at lower tps. - Unreliable prioritisation of chunk gen/load calls that block the main thread. - Shutdown logic is utterly unreliable, as it has to wait for all chunks to unload - is it guaranteed that the chunk system is in a state on shutdown that it can reliably do this? Watchdog shutdown also typically failed due to thread checks, which is now resolved. - Saving of data is not unified (i.e can save chunk data without saving entity data, poses problems for desync if shutdown is really abnormal. - Entities are not loaded with chunks. This caused quite a bit of headache for Chunk#getEntities API, but now the new chunk system loads entities with chunks so that they are ready whenever the chunk loads in. Effectively brings the behavior back to 1.16 era, but still storing entities in their own separate regionfiles. The above list is not complete. The patch documentation will complete it. New chunk system hard relies on starlight and dataconverter, and most importantly the new concurrent utilities in ConcurrentUtil. Some of the old async chunk i/o interface (i.e the old file io thread reroutes _some_ calls to the new file io thread) is kept for plugin compat reasons. It will be removed in the next major version of minecraft. The old legacy chunk system patches have been moved to the removed folder in case we need them again.
2022-09-26 10:02:51 +02:00
diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkStatus.java b/src/main/java/net/minecraft/world/level/chunk/ChunkStatus.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/chunk/ChunkStatus.java
+++ b/src/main/java/net/minecraft/world/level/chunk/ChunkStatus.java
@@ -0,0 +0,0 @@ public class ChunkStatus {
return this.chunkType;
}
+ // Paper start
+ public static ChunkStatus getStatus(String name) {
+ try {
+ // We need this otherwise we return EMPTY for invalid names
+ ResourceLocation key = new ResourceLocation(name);
+ return Registry.CHUNK_STATUS.getOptional(key).orElse(null);
+ } catch (Exception ex) {
+ return null; // invalid name
+ }
+ }
+ // Paper end
public static ChunkStatus byName(String id) {
return (ChunkStatus) Registry.CHUNK_STATUS.get(ResourceLocation.tryParse(id));
}
2021-11-27 11:42:09 +01:00
diff --git a/src/main/java/net/minecraft/world/level/chunk/EmptyLevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/EmptyLevelChunk.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/chunk/EmptyLevelChunk.java
+++ b/src/main/java/net/minecraft/world/level/chunk/EmptyLevelChunk.java
@@ -0,0 +0,0 @@ public class EmptyLevelChunk extends LevelChunk {
2022-11-12 21:57:41 +01:00
this.biome = biomeEntry;
2021-11-27 11:42:09 +01:00
}
Rewrite chunk system (#8177) Patch documentation to come Issues with the old system that are fixed now: - World generation does not scale with cpu cores effectively. - Relies on the main thread for scheduling and maintaining chunk state, dropping chunk load/generate rates at lower tps. - Unreliable prioritisation of chunk gen/load calls that block the main thread. - Shutdown logic is utterly unreliable, as it has to wait for all chunks to unload - is it guaranteed that the chunk system is in a state on shutdown that it can reliably do this? Watchdog shutdown also typically failed due to thread checks, which is now resolved. - Saving of data is not unified (i.e can save chunk data without saving entity data, poses problems for desync if shutdown is really abnormal. - Entities are not loaded with chunks. This caused quite a bit of headache for Chunk#getEntities API, but now the new chunk system loads entities with chunks so that they are ready whenever the chunk loads in. Effectively brings the behavior back to 1.16 era, but still storing entities in their own separate regionfiles. The above list is not complete. The patch documentation will complete it. New chunk system hard relies on starlight and dataconverter, and most importantly the new concurrent utilities in ConcurrentUtil. Some of the old async chunk i/o interface (i.e the old file io thread reroutes _some_ calls to the new file io thread) is kept for plugin compat reasons. It will be removed in the next major version of minecraft. The old legacy chunk system patches have been moved to the removed folder in case we need them again.
2022-09-26 10:02:51 +02:00
+ // Paper start - starlight
2021-11-27 11:42:09 +01:00
+ @Override
+ public ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] getBlockNibbles() {
+ return ca.spottedleaf.starlight.common.light.StarLightEngine.getFilledEmptyLight(this.getLevel());
+ }
+
+ @Override
+ public void setBlockNibbles(final ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] nibbles) {}
+
+ @Override
+ public ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] getSkyNibbles() {
+ return ca.spottedleaf.starlight.common.light.StarLightEngine.getFilledEmptyLight(this.getLevel());
+ }
+
+ @Override
+ public void setSkyNibbles(final ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] nibbles) {}
+
+ @Override
+ public boolean[] getSkyEmptinessMap() {
+ return null;
+ }
+
+ @Override
+ public void setSkyEmptinessMap(final boolean[] emptinessMap) {}
+
+ @Override
+ public boolean[] getBlockEmptinessMap() {
+ return null;
+ }
+
+ @Override
+ public void setBlockEmptinessMap(final boolean[] emptinessMap) {}
Rewrite chunk system (#8177) Patch documentation to come Issues with the old system that are fixed now: - World generation does not scale with cpu cores effectively. - Relies on the main thread for scheduling and maintaining chunk state, dropping chunk load/generate rates at lower tps. - Unreliable prioritisation of chunk gen/load calls that block the main thread. - Shutdown logic is utterly unreliable, as it has to wait for all chunks to unload - is it guaranteed that the chunk system is in a state on shutdown that it can reliably do this? Watchdog shutdown also typically failed due to thread checks, which is now resolved. - Saving of data is not unified (i.e can save chunk data without saving entity data, poses problems for desync if shutdown is really abnormal. - Entities are not loaded with chunks. This caused quite a bit of headache for Chunk#getEntities API, but now the new chunk system loads entities with chunks so that they are ready whenever the chunk loads in. Effectively brings the behavior back to 1.16 era, but still storing entities in their own separate regionfiles. The above list is not complete. The patch documentation will complete it. New chunk system hard relies on starlight and dataconverter, and most importantly the new concurrent utilities in ConcurrentUtil. Some of the old async chunk i/o interface (i.e the old file io thread reroutes _some_ calls to the new file io thread) is kept for plugin compat reasons. It will be removed in the next major version of minecraft. The old legacy chunk system patches have been moved to the removed folder in case we need them again.
2022-09-26 10:02:51 +02:00
+ // Paper end - starlight
2021-11-27 11:42:09 +01:00
+
@Override
Rewrite chunk system (#8177) Patch documentation to come Issues with the old system that are fixed now: - World generation does not scale with cpu cores effectively. - Relies on the main thread for scheduling and maintaining chunk state, dropping chunk load/generate rates at lower tps. - Unreliable prioritisation of chunk gen/load calls that block the main thread. - Shutdown logic is utterly unreliable, as it has to wait for all chunks to unload - is it guaranteed that the chunk system is in a state on shutdown that it can reliably do this? Watchdog shutdown also typically failed due to thread checks, which is now resolved. - Saving of data is not unified (i.e can save chunk data without saving entity data, poses problems for desync if shutdown is really abnormal. - Entities are not loaded with chunks. This caused quite a bit of headache for Chunk#getEntities API, but now the new chunk system loads entities with chunks so that they are ready whenever the chunk loads in. Effectively brings the behavior back to 1.16 era, but still storing entities in their own separate regionfiles. The above list is not complete. The patch documentation will complete it. New chunk system hard relies on starlight and dataconverter, and most importantly the new concurrent utilities in ConcurrentUtil. Some of the old async chunk i/o interface (i.e the old file io thread reroutes _some_ calls to the new file io thread) is kept for plugin compat reasons. It will be removed in the next major version of minecraft. The old legacy chunk system patches have been moved to the removed folder in case we need them again.
2022-09-26 10:02:51 +02:00
public BlockState getBlockState(BlockPos pos) {
return Blocks.VOID_AIR.defaultBlockState();
2021-11-27 11:42:09 +01:00
diff --git a/src/main/java/net/minecraft/world/level/chunk/ImposterProtoChunk.java b/src/main/java/net/minecraft/world/level/chunk/ImposterProtoChunk.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/chunk/ImposterProtoChunk.java
+++ b/src/main/java/net/minecraft/world/level/chunk/ImposterProtoChunk.java
@@ -0,0 +0,0 @@ public class ImposterProtoChunk extends ProtoChunk {
private final LevelChunk wrapped;
private final boolean allowWrites;
+ // Paper start - rewrite light engine
+ @Override
+ public ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] getBlockNibbles() {
+ return this.wrapped.getBlockNibbles();
+ }
+
+ @Override
+ public void setBlockNibbles(final ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] nibbles) {
+ this.wrapped.setBlockNibbles(nibbles);
+ }
+
+ @Override
+ public ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] getSkyNibbles() {
+ return this.wrapped.getSkyNibbles();
+ }
+
+ @Override
+ public void setSkyNibbles(final ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] nibbles) {
+ this.wrapped.setSkyNibbles(nibbles);
+ }
+
+ @Override
+ public boolean[] getSkyEmptinessMap() {
+ return this.wrapped.getSkyEmptinessMap();
+ }
+
+ @Override
+ public void setSkyEmptinessMap(final boolean[] emptinessMap) {
+ this.wrapped.setSkyEmptinessMap(emptinessMap);
+ }
+
+ @Override
+ public boolean[] getBlockEmptinessMap() {
+ return this.wrapped.getBlockEmptinessMap();
+ }
+
+ @Override
+ public void setBlockEmptinessMap(final boolean[] emptinessMap) {
+ this.wrapped.setBlockEmptinessMap(emptinessMap);
+ }
+ // Paper end - rewrite light engine
+
public ImposterProtoChunk(LevelChunk wrapped, boolean bl) {
super(wrapped.getPos(), UpgradeData.EMPTY, wrapped.levelHeightAccessor, wrapped.getLevel().registryAccess().registryOrThrow(Registry.BIOME_REGISTRY), wrapped.getBlendingData());
this.wrapped = wrapped;
diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
@@ -0,0 +0,0 @@ public class LevelChunk extends ChunkAccess {
public LevelChunk(Level world, ChunkPos pos, UpgradeData upgradeData, LevelChunkTicks<Block> blockTickScheduler, LevelChunkTicks<Fluid> fluidTickScheduler, long inhabitedTime, @Nullable LevelChunkSection[] sectionArrayInitializer, @Nullable LevelChunk.PostLoadProcessor entityLoader, @Nullable BlendingData blendingData) {
Rewrite chunk system (#8177) Patch documentation to come Issues with the old system that are fixed now: - World generation does not scale with cpu cores effectively. - Relies on the main thread for scheduling and maintaining chunk state, dropping chunk load/generate rates at lower tps. - Unreliable prioritisation of chunk gen/load calls that block the main thread. - Shutdown logic is utterly unreliable, as it has to wait for all chunks to unload - is it guaranteed that the chunk system is in a state on shutdown that it can reliably do this? Watchdog shutdown also typically failed due to thread checks, which is now resolved. - Saving of data is not unified (i.e can save chunk data without saving entity data, poses problems for desync if shutdown is really abnormal. - Entities are not loaded with chunks. This caused quite a bit of headache for Chunk#getEntities API, but now the new chunk system loads entities with chunks so that they are ready whenever the chunk loads in. Effectively brings the behavior back to 1.16 era, but still storing entities in their own separate regionfiles. The above list is not complete. The patch documentation will complete it. New chunk system hard relies on starlight and dataconverter, and most importantly the new concurrent utilities in ConcurrentUtil. Some of the old async chunk i/o interface (i.e the old file io thread reroutes _some_ calls to the new file io thread) is kept for plugin compat reasons. It will be removed in the next major version of minecraft. The old legacy chunk system patches have been moved to the removed folder in case we need them again.
2022-09-26 10:02:51 +02:00
super(pos, upgradeData, world, world.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY), inhabitedTime, sectionArrayInitializer, blendingData);
2021-11-27 11:42:09 +01:00
+ // Paper start - rewrite light engine
+ this.setBlockNibbles(ca.spottedleaf.starlight.common.light.StarLightEngine.getFilledEmptyLight(world));
+ this.setSkyNibbles(ca.spottedleaf.starlight.common.light.StarLightEngine.getFilledEmptyLight(world));
+ // Paper end - rewrite light engine
this.tickersInLevel = Maps.newHashMap();
this.clientLightReady = false;
this.level = (ServerLevel) world; // CraftBukkit - type
@@ -0,0 +0,0 @@ public class LevelChunk extends ChunkAccess {
public LevelChunk(ServerLevel world, ProtoChunk protoChunk, @Nullable LevelChunk.PostLoadProcessor entityLoader) {
this(world, protoChunk.getPos(), protoChunk.getUpgradeData(), protoChunk.unpackBlockTicks(), protoChunk.unpackFluidTicks(), protoChunk.getInhabitedTime(), protoChunk.getSections(), entityLoader, protoChunk.getBlendingData());
2021-11-27 11:42:09 +01:00
+ // Paper start - rewrite light engine
+ this.setBlockNibbles(protoChunk.getBlockNibbles());
+ this.setSkyNibbles(protoChunk.getSkyNibbles());
+ this.setSkyEmptinessMap(protoChunk.getSkyEmptinessMap());
+ this.setBlockEmptinessMap(protoChunk.getBlockEmptinessMap());
+ // Paper end - rewrite light engine
Iterator iterator = protoChunk.getBlockEntities().values().iterator();
while (iterator.hasNext()) {
diff --git a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java
+++ b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java
2022-06-08 13:44:30 +02:00
@@ -0,0 +0,0 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
2021-11-27 11:42:09 +01:00
return this.get(this.strategy.getIndex(x, y, z));
}
- protected T get(int index) {
+ public T get(int index) { // Paper - public
PalettedContainer.Data<T> data = this.data;
return data.palette.valueFor(data.storage.get(index));
}
diff --git a/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java b/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java
+++ b/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java
@@ -0,0 +0,0 @@ public class ProtoChunk extends ChunkAccess {
public ProtoChunk(ChunkPos pos, UpgradeData upgradeData, @Nullable LevelChunkSection[] sections, ProtoChunkTicks<Block> blockTickScheduler, ProtoChunkTicks<Fluid> fluidTickScheduler, LevelHeightAccessor world, Registry<Biome> biomeRegistry, @Nullable BlendingData blendingData) {
super(pos, upgradeData, world, biomeRegistry, 0L, sections, blendingData);
+ // Paper start - rewrite light engine
+ if (!(this instanceof ImposterProtoChunk)) {
+ this.setBlockNibbles(ca.spottedleaf.starlight.common.light.StarLightEngine.getFilledEmptyLight(world));
+ this.setSkyNibbles(ca.spottedleaf.starlight.common.light.StarLightEngine.getFilledEmptyLight(world));
+ }
+ // Paper end - rewrite light engine
this.blockTicks = blockTickScheduler;
this.fluidTicks = fluidTickScheduler;
}
diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
+++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
@@ -0,0 +0,0 @@ public class ChunkSerializer {
2022-06-08 13:44:30 +02:00
public static final String BLOCK_LIGHT_TAG = "BlockLight";
public static final String SKY_LIGHT_TAG = "SkyLight";
2021-11-27 11:42:09 +01:00
+ // Paper start - replace light engine impl
+ private static final int STARLIGHT_LIGHT_VERSION = 8;
2021-11-27 11:42:09 +01:00
+
+ private static final String BLOCKLIGHT_STATE_TAG = "starlight.blocklight_state";
+ private static final String SKYLIGHT_STATE_TAG = "starlight.skylight_state";
+ private static final String STARLIGHT_VERSION_TAG = "starlight.light_version";
+ // Paper end - replace light engine impl
+
public ChunkSerializer() {}
Rewrite chunk system (#8177) Patch documentation to come Issues with the old system that are fixed now: - World generation does not scale with cpu cores effectively. - Relies on the main thread for scheduling and maintaining chunk state, dropping chunk load/generate rates at lower tps. - Unreliable prioritisation of chunk gen/load calls that block the main thread. - Shutdown logic is utterly unreliable, as it has to wait for all chunks to unload - is it guaranteed that the chunk system is in a state on shutdown that it can reliably do this? Watchdog shutdown also typically failed due to thread checks, which is now resolved. - Saving of data is not unified (i.e can save chunk data without saving entity data, poses problems for desync if shutdown is really abnormal. - Entities are not loaded with chunks. This caused quite a bit of headache for Chunk#getEntities API, but now the new chunk system loads entities with chunks so that they are ready whenever the chunk loads in. Effectively brings the behavior back to 1.16 era, but still storing entities in their own separate regionfiles. The above list is not complete. The patch documentation will complete it. New chunk system hard relies on starlight and dataconverter, and most importantly the new concurrent utilities in ConcurrentUtil. Some of the old async chunk i/o interface (i.e the old file io thread reroutes _some_ calls to the new file io thread) is kept for plugin compat reasons. It will be removed in the next major version of minecraft. The old legacy chunk system patches have been moved to the removed folder in case we need them again.
2022-09-26 10:02:51 +02:00
public static ProtoChunk read(ServerLevel world, PoiManager poiStorage, ChunkPos chunkPos, CompoundTag nbt) {
2021-11-27 11:42:09 +01:00
@@ -0,0 +0,0 @@ public class ChunkSerializer {
}
UpgradeData chunkconverter = nbt.contains("UpgradeData", 10) ? new UpgradeData(nbt.getCompound("UpgradeData"), world) : UpgradeData.EMPTY;
- boolean flag = nbt.getBoolean("isLightOn");
Rewrite chunk system (#8177) Patch documentation to come Issues with the old system that are fixed now: - World generation does not scale with cpu cores effectively. - Relies on the main thread for scheduling and maintaining chunk state, dropping chunk load/generate rates at lower tps. - Unreliable prioritisation of chunk gen/load calls that block the main thread. - Shutdown logic is utterly unreliable, as it has to wait for all chunks to unload - is it guaranteed that the chunk system is in a state on shutdown that it can reliably do this? Watchdog shutdown also typically failed due to thread checks, which is now resolved. - Saving of data is not unified (i.e can save chunk data without saving entity data, poses problems for desync if shutdown is really abnormal. - Entities are not loaded with chunks. This caused quite a bit of headache for Chunk#getEntities API, but now the new chunk system loads entities with chunks so that they are ready whenever the chunk loads in. Effectively brings the behavior back to 1.16 era, but still storing entities in their own separate regionfiles. The above list is not complete. The patch documentation will complete it. New chunk system hard relies on starlight and dataconverter, and most importantly the new concurrent utilities in ConcurrentUtil. Some of the old async chunk i/o interface (i.e the old file io thread reroutes _some_ calls to the new file io thread) is kept for plugin compat reasons. It will be removed in the next major version of minecraft. The old legacy chunk system patches have been moved to the removed folder in case we need them again.
2022-09-26 10:02:51 +02:00
+ boolean flag = getStatus(nbt) != null && getStatus(nbt).isOrAfter(ChunkStatus.LIGHT) && nbt.get("isLightOn") != null && nbt.getInt(STARLIGHT_VERSION_TAG) == STARLIGHT_LIGHT_VERSION; // Paper
2021-11-27 11:42:09 +01:00
ListTag nbttaglist = nbt.getList("sections", 10);
int i = world.getSectionsCount();
LevelChunkSection[] achunksection = new LevelChunkSection[i];
boolean flag1 = world.dimensionType().hasSkyLight();
ServerChunkCache chunkproviderserver = world.getChunkSource();
LevelLightEngine lightengine = chunkproviderserver.getLightEngine();
+ // Paper start
Rewrite chunk system (#8177) Patch documentation to come Issues with the old system that are fixed now: - World generation does not scale with cpu cores effectively. - Relies on the main thread for scheduling and maintaining chunk state, dropping chunk load/generate rates at lower tps. - Unreliable prioritisation of chunk gen/load calls that block the main thread. - Shutdown logic is utterly unreliable, as it has to wait for all chunks to unload - is it guaranteed that the chunk system is in a state on shutdown that it can reliably do this? Watchdog shutdown also typically failed due to thread checks, which is now resolved. - Saving of data is not unified (i.e can save chunk data without saving entity data, poses problems for desync if shutdown is really abnormal. - Entities are not loaded with chunks. This caused quite a bit of headache for Chunk#getEntities API, but now the new chunk system loads entities with chunks so that they are ready whenever the chunk loads in. Effectively brings the behavior back to 1.16 era, but still storing entities in their own separate regionfiles. The above list is not complete. The patch documentation will complete it. New chunk system hard relies on starlight and dataconverter, and most importantly the new concurrent utilities in ConcurrentUtil. Some of the old async chunk i/o interface (i.e the old file io thread reroutes _some_ calls to the new file io thread) is kept for plugin compat reasons. It will be removed in the next major version of minecraft. The old legacy chunk system patches have been moved to the removed folder in case we need them again.
2022-09-26 10:02:51 +02:00
+ ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] blockNibbles = ca.spottedleaf.starlight.common.light.StarLightEngine.getFilledEmptyLight(world);
+ ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] skyNibbles = ca.spottedleaf.starlight.common.light.StarLightEngine.getFilledEmptyLight(world);
2021-11-27 11:42:09 +01:00
+ final int minSection = io.papermc.paper.util.WorldUtil.getMinLightSection(world);
+ final int maxSection = io.papermc.paper.util.WorldUtil.getMaxLightSection(world);
+ boolean canReadSky = world.dimensionType().hasSkyLight();
+ // Paper end
2022-06-08 13:44:30 +02:00
Registry<Biome> iregistry = world.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY);
Codec<PalettedContainer<Holder<Biome>>> codec = ChunkSerializer.makeBiomeCodecRW(iregistry); // CraftBukkit - read/write
boolean flag2 = false;
2021-11-27 11:42:09 +01:00
@@ -0,0 +0,0 @@ public class ChunkSerializer {
DataResult dataresult;
for (int j = 0; j < nbttaglist.size(); ++j) {
- CompoundTag nbttagcompound1 = nbttaglist.getCompound(j);
+ CompoundTag nbttagcompound1 = nbttaglist.getCompound(j); CompoundTag sectionData = nbttagcompound1; // Paper
byte b0 = nbttagcompound1.getByte("Y");
int k = world.getSectionIndexFromSectionY(b0);
@@ -0,0 +0,0 @@ public class ChunkSerializer {
2022-06-08 13:44:30 +02:00
boolean flag3 = nbttagcompound1.contains("BlockLight", 7);
boolean flag4 = flag1 && nbttagcompound1.contains("SkyLight", 7);
- if (flag3 || flag4) {
- if (!flag2) {
Rewrite chunk system (#8177) Patch documentation to come Issues with the old system that are fixed now: - World generation does not scale with cpu cores effectively. - Relies on the main thread for scheduling and maintaining chunk state, dropping chunk load/generate rates at lower tps. - Unreliable prioritisation of chunk gen/load calls that block the main thread. - Shutdown logic is utterly unreliable, as it has to wait for all chunks to unload - is it guaranteed that the chunk system is in a state on shutdown that it can reliably do this? Watchdog shutdown also typically failed due to thread checks, which is now resolved. - Saving of data is not unified (i.e can save chunk data without saving entity data, poses problems for desync if shutdown is really abnormal. - Entities are not loaded with chunks. This caused quite a bit of headache for Chunk#getEntities API, but now the new chunk system loads entities with chunks so that they are ready whenever the chunk loads in. Effectively brings the behavior back to 1.16 era, but still storing entities in their own separate regionfiles. The above list is not complete. The patch documentation will complete it. New chunk system hard relies on starlight and dataconverter, and most importantly the new concurrent utilities in ConcurrentUtil. Some of the old async chunk i/o interface (i.e the old file io thread reroutes _some_ calls to the new file io thread) is kept for plugin compat reasons. It will be removed in the next major version of minecraft. The old legacy chunk system patches have been moved to the removed folder in case we need them again.
2022-09-26 10:02:51 +02:00
- lightengine.retainData(chunkPos, true);
- flag2 = true;
- }
-
2022-06-08 13:44:30 +02:00
+ // Paper start - rewrite the light engine
+ if (flag) {
+ try {
2022-06-08 13:44:30 +02:00
+ int y = sectionData.getByte("Y");
Rewrite chunk system (#8177) Patch documentation to come Issues with the old system that are fixed now: - World generation does not scale with cpu cores effectively. - Relies on the main thread for scheduling and maintaining chunk state, dropping chunk load/generate rates at lower tps. - Unreliable prioritisation of chunk gen/load calls that block the main thread. - Shutdown logic is utterly unreliable, as it has to wait for all chunks to unload - is it guaranteed that the chunk system is in a state on shutdown that it can reliably do this? Watchdog shutdown also typically failed due to thread checks, which is now resolved. - Saving of data is not unified (i.e can save chunk data without saving entity data, poses problems for desync if shutdown is really abnormal. - Entities are not loaded with chunks. This caused quite a bit of headache for Chunk#getEntities API, but now the new chunk system loads entities with chunks so that they are ready whenever the chunk loads in. Effectively brings the behavior back to 1.16 era, but still storing entities in their own separate regionfiles. The above list is not complete. The patch documentation will complete it. New chunk system hard relies on starlight and dataconverter, and most importantly the new concurrent utilities in ConcurrentUtil. Some of the old async chunk i/o interface (i.e the old file io thread reroutes _some_ calls to the new file io thread) is kept for plugin compat reasons. It will be removed in the next major version of minecraft. The old legacy chunk system patches have been moved to the removed folder in case we need them again.
2022-09-26 10:02:51 +02:00
+ // Paper end - rewrite the light engine
2022-06-08 13:44:30 +02:00
if (flag3) {
Rewrite chunk system (#8177) Patch documentation to come Issues with the old system that are fixed now: - World generation does not scale with cpu cores effectively. - Relies on the main thread for scheduling and maintaining chunk state, dropping chunk load/generate rates at lower tps. - Unreliable prioritisation of chunk gen/load calls that block the main thread. - Shutdown logic is utterly unreliable, as it has to wait for all chunks to unload - is it guaranteed that the chunk system is in a state on shutdown that it can reliably do this? Watchdog shutdown also typically failed due to thread checks, which is now resolved. - Saving of data is not unified (i.e can save chunk data without saving entity data, poses problems for desync if shutdown is really abnormal. - Entities are not loaded with chunks. This caused quite a bit of headache for Chunk#getEntities API, but now the new chunk system loads entities with chunks so that they are ready whenever the chunk loads in. Effectively brings the behavior back to 1.16 era, but still storing entities in their own separate regionfiles. The above list is not complete. The patch documentation will complete it. New chunk system hard relies on starlight and dataconverter, and most importantly the new concurrent utilities in ConcurrentUtil. Some of the old async chunk i/o interface (i.e the old file io thread reroutes _some_ calls to the new file io thread) is kept for plugin compat reasons. It will be removed in the next major version of minecraft. The old legacy chunk system patches have been moved to the removed folder in case we need them again.
2022-09-26 10:02:51 +02:00
- lightengine.queueSectionData(LightLayer.BLOCK, SectionPos.of(chunkPos, b0), new DataLayer(nbttagcompound1.getByteArray("BlockLight")), true);
+ // Paper start - rewrite the light engine
2021-11-27 11:42:09 +01:00
+ // this is where our diff is
+ blockNibbles[y - minSection] = new ca.spottedleaf.starlight.common.light.SWMRNibbleArray(sectionData.getByteArray("BlockLight").clone(), sectionData.getInt(BLOCKLIGHT_STATE_TAG)); // clone for data safety
+ } else {
+ blockNibbles[y - minSection] = new ca.spottedleaf.starlight.common.light.SWMRNibbleArray(null, sectionData.getInt(BLOCKLIGHT_STATE_TAG));
Rewrite chunk system (#8177) Patch documentation to come Issues with the old system that are fixed now: - World generation does not scale with cpu cores effectively. - Relies on the main thread for scheduling and maintaining chunk state, dropping chunk load/generate rates at lower tps. - Unreliable prioritisation of chunk gen/load calls that block the main thread. - Shutdown logic is utterly unreliable, as it has to wait for all chunks to unload - is it guaranteed that the chunk system is in a state on shutdown that it can reliably do this? Watchdog shutdown also typically failed due to thread checks, which is now resolved. - Saving of data is not unified (i.e can save chunk data without saving entity data, poses problems for desync if shutdown is really abnormal. - Entities are not loaded with chunks. This caused quite a bit of headache for Chunk#getEntities API, but now the new chunk system loads entities with chunks so that they are ready whenever the chunk loads in. Effectively brings the behavior back to 1.16 era, but still storing entities in their own separate regionfiles. The above list is not complete. The patch documentation will complete it. New chunk system hard relies on starlight and dataconverter, and most importantly the new concurrent utilities in ConcurrentUtil. Some of the old async chunk i/o interface (i.e the old file io thread reroutes _some_ calls to the new file io thread) is kept for plugin compat reasons. It will be removed in the next major version of minecraft. The old legacy chunk system patches have been moved to the removed folder in case we need them again.
2022-09-26 10:02:51 +02:00
+ // Paper end - rewrite the light engine
2021-11-27 11:42:09 +01:00
}
2022-06-08 13:44:30 +02:00
if (flag4) {
Rewrite chunk system (#8177) Patch documentation to come Issues with the old system that are fixed now: - World generation does not scale with cpu cores effectively. - Relies on the main thread for scheduling and maintaining chunk state, dropping chunk load/generate rates at lower tps. - Unreliable prioritisation of chunk gen/load calls that block the main thread. - Shutdown logic is utterly unreliable, as it has to wait for all chunks to unload - is it guaranteed that the chunk system is in a state on shutdown that it can reliably do this? Watchdog shutdown also typically failed due to thread checks, which is now resolved. - Saving of data is not unified (i.e can save chunk data without saving entity data, poses problems for desync if shutdown is really abnormal. - Entities are not loaded with chunks. This caused quite a bit of headache for Chunk#getEntities API, but now the new chunk system loads entities with chunks so that they are ready whenever the chunk loads in. Effectively brings the behavior back to 1.16 era, but still storing entities in their own separate regionfiles. The above list is not complete. The patch documentation will complete it. New chunk system hard relies on starlight and dataconverter, and most importantly the new concurrent utilities in ConcurrentUtil. Some of the old async chunk i/o interface (i.e the old file io thread reroutes _some_ calls to the new file io thread) is kept for plugin compat reasons. It will be removed in the next major version of minecraft. The old legacy chunk system patches have been moved to the removed folder in case we need them again.
2022-09-26 10:02:51 +02:00
- lightengine.queueSectionData(LightLayer.SKY, SectionPos.of(chunkPos, b0), new DataLayer(nbttagcompound1.getByteArray("SkyLight")), true);
+ // Paper start - rewrite the light engine
2022-06-08 13:44:30 +02:00
+ // 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 ca.spottedleaf.starlight.common.light.SWMRNibbleArray(sectionData.getByteArray("SkyLight").clone(), sectionData.getInt(SKYLIGHT_STATE_TAG)); // clone for data safety
+ } else if (flag1) {
2022-06-10 03:09:55 +02:00
+ skyNibbles[y - minSection] = new ca.spottedleaf.starlight.common.light.SWMRNibbleArray(null, sectionData.getInt(SKYLIGHT_STATE_TAG));
Rewrite chunk system (#8177) Patch documentation to come Issues with the old system that are fixed now: - World generation does not scale with cpu cores effectively. - Relies on the main thread for scheduling and maintaining chunk state, dropping chunk load/generate rates at lower tps. - Unreliable prioritisation of chunk gen/load calls that block the main thread. - Shutdown logic is utterly unreliable, as it has to wait for all chunks to unload - is it guaranteed that the chunk system is in a state on shutdown that it can reliably do this? Watchdog shutdown also typically failed due to thread checks, which is now resolved. - Saving of data is not unified (i.e can save chunk data without saving entity data, poses problems for desync if shutdown is really abnormal. - Entities are not loaded with chunks. This caused quite a bit of headache for Chunk#getEntities API, but now the new chunk system loads entities with chunks so that they are ready whenever the chunk loads in. Effectively brings the behavior back to 1.16 era, but still storing entities in their own separate regionfiles. The above list is not complete. The patch documentation will complete it. New chunk system hard relies on starlight and dataconverter, and most importantly the new concurrent utilities in ConcurrentUtil. Some of the old async chunk i/o interface (i.e the old file io thread reroutes _some_ calls to the new file io thread) is kept for plugin compat reasons. It will be removed in the next major version of minecraft. The old legacy chunk system patches have been moved to the removed folder in case we need them again.
2022-09-26 10:02:51 +02:00
+ // Paper end - rewrite the light engine
+ }
+
+ // Paper start - rewrite the light engine
+ } catch (Exception ex) {
+ LOGGER.warn("Failed to load light data for chunk " + chunkPos + " in world '" + world.getWorld().getName() + "', light will be regenerated", ex);
+ flag = false;
Rewrite chunk system (#8177) Patch documentation to come Issues with the old system that are fixed now: - World generation does not scale with cpu cores effectively. - Relies on the main thread for scheduling and maintaining chunk state, dropping chunk load/generate rates at lower tps. - Unreliable prioritisation of chunk gen/load calls that block the main thread. - Shutdown logic is utterly unreliable, as it has to wait for all chunks to unload - is it guaranteed that the chunk system is in a state on shutdown that it can reliably do this? Watchdog shutdown also typically failed due to thread checks, which is now resolved. - Saving of data is not unified (i.e can save chunk data without saving entity data, poses problems for desync if shutdown is really abnormal. - Entities are not loaded with chunks. This caused quite a bit of headache for Chunk#getEntities API, but now the new chunk system loads entities with chunks so that they are ready whenever the chunk loads in. Effectively brings the behavior back to 1.16 era, but still storing entities in their own separate regionfiles. The above list is not complete. The patch documentation will complete it. New chunk system hard relies on starlight and dataconverter, and most importantly the new concurrent utilities in ConcurrentUtil. Some of the old async chunk i/o interface (i.e the old file io thread reroutes _some_ calls to the new file io thread) is kept for plugin compat reasons. It will be removed in the next major version of minecraft. The old legacy chunk system patches have been moved to the removed folder in case we need them again.
2022-09-26 10:02:51 +02:00
}
2021-11-27 11:42:09 +01:00
+ // Paper end - rewrite light engine
}
}
@@ -0,0 +0,0 @@ public class ChunkSerializer {
}, chunkPos);
2022-06-08 13:44:30 +02:00
object1 = new LevelChunk(world.getLevel(), chunkPos, chunkconverter, levelchunkticks, levelchunkticks1, l, achunksection, ChunkSerializer.postLoadChunk(world, nbt), blendingdata);
+ ((LevelChunk)object1).setBlockNibbles(blockNibbles); // Paper - replace light impl
+ ((LevelChunk)object1).setSkyNibbles(skyNibbles); // Paper - replace light impl
2021-11-27 11:42:09 +01:00
} else {
ProtoChunkTicks<Block> protochunkticklist = ProtoChunkTicks.load(nbt.getList("block_ticks", 10), (s) -> {
return Registry.BLOCK.getOptional(ResourceLocation.tryParse(s));
@@ -0,0 +0,0 @@ public class ChunkSerializer {
return Registry.FLUID.getOptional(ResourceLocation.tryParse(s));
}, chunkPos);
ProtoChunk protochunk = new ProtoChunk(chunkPos, chunkconverter, achunksection, protochunkticklist, protochunkticklist1, world, iregistry, blendingdata);
+ protochunk.setBlockNibbles(blockNibbles); // Paper - replace light impl
+ protochunk.setSkyNibbles(skyNibbles); // Paper - replace light impl
2022-06-08 13:44:30 +02:00
object1 = protochunk;
2021-11-27 11:42:09 +01:00
protochunk.setInhabitedTime(l);
@@ -0,0 +0,0 @@ public class ChunkSerializer {
Rewrite chunk system (#8177) Patch documentation to come Issues with the old system that are fixed now: - World generation does not scale with cpu cores effectively. - Relies on the main thread for scheduling and maintaining chunk state, dropping chunk load/generate rates at lower tps. - Unreliable prioritisation of chunk gen/load calls that block the main thread. - Shutdown logic is utterly unreliable, as it has to wait for all chunks to unload - is it guaranteed that the chunk system is in a state on shutdown that it can reliably do this? Watchdog shutdown also typically failed due to thread checks, which is now resolved. - Saving of data is not unified (i.e can save chunk data without saving entity data, poses problems for desync if shutdown is really abnormal. - Entities are not loaded with chunks. This caused quite a bit of headache for Chunk#getEntities API, but now the new chunk system loads entities with chunks so that they are ready whenever the chunk loads in. Effectively brings the behavior back to 1.16 era, but still storing entities in their own separate regionfiles. The above list is not complete. The patch documentation will complete it. New chunk system hard relies on starlight and dataconverter, and most importantly the new concurrent utilities in ConcurrentUtil. Some of the old async chunk i/o interface (i.e the old file io thread reroutes _some_ calls to the new file io thread) is kept for plugin compat reasons. It will be removed in the next major version of minecraft. The old legacy chunk system patches have been moved to the removed folder in case we need them again.
2022-09-26 10:02:51 +02:00
// CraftBukkit end
2021-11-27 11:42:09 +01:00
Rewrite chunk system (#8177) Patch documentation to come Issues with the old system that are fixed now: - World generation does not scale with cpu cores effectively. - Relies on the main thread for scheduling and maintaining chunk state, dropping chunk load/generate rates at lower tps. - Unreliable prioritisation of chunk gen/load calls that block the main thread. - Shutdown logic is utterly unreliable, as it has to wait for all chunks to unload - is it guaranteed that the chunk system is in a state on shutdown that it can reliably do this? Watchdog shutdown also typically failed due to thread checks, which is now resolved. - Saving of data is not unified (i.e can save chunk data without saving entity data, poses problems for desync if shutdown is really abnormal. - Entities are not loaded with chunks. This caused quite a bit of headache for Chunk#getEntities API, but now the new chunk system loads entities with chunks so that they are ready whenever the chunk loads in. Effectively brings the behavior back to 1.16 era, but still storing entities in their own separate regionfiles. The above list is not complete. The patch documentation will complete it. New chunk system hard relies on starlight and dataconverter, and most importantly the new concurrent utilities in ConcurrentUtil. Some of the old async chunk i/o interface (i.e the old file io thread reroutes _some_ calls to the new file io thread) is kept for plugin compat reasons. It will be removed in the next major version of minecraft. The old legacy chunk system patches have been moved to the removed folder in case we need them again.
2022-09-26 10:02:51 +02:00
public static CompoundTag write(ServerLevel world, ChunkAccess chunk) {
2021-11-27 11:42:09 +01:00
+ // Paper start - rewrite light impl
+ final int minSection = io.papermc.paper.util.WorldUtil.getMinLightSection(world);
+ final int maxSection = io.papermc.paper.util.WorldUtil.getMaxLightSection(world);
+ ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] blockNibbles = chunk.getBlockNibbles();
+ ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] skyNibbles = chunk.getSkyNibbles();
+ // Paper end - rewrite light impl
ChunkPos chunkcoordintpair = chunk.getPos();
CompoundTag nbttagcompound = new CompoundTag();
@@ -0,0 +0,0 @@ public class ChunkSerializer {
for (int i = lightenginethreaded.getMinLightSection(); i < lightenginethreaded.getMaxLightSection(); ++i) {
int j = chunk.getSectionIndexFromSectionY(i);
boolean flag1 = j >= 0 && j < achunksection.length;
Rewrite chunk system (#8177) Patch documentation to come Issues with the old system that are fixed now: - World generation does not scale with cpu cores effectively. - Relies on the main thread for scheduling and maintaining chunk state, dropping chunk load/generate rates at lower tps. - Unreliable prioritisation of chunk gen/load calls that block the main thread. - Shutdown logic is utterly unreliable, as it has to wait for all chunks to unload - is it guaranteed that the chunk system is in a state on shutdown that it can reliably do this? Watchdog shutdown also typically failed due to thread checks, which is now resolved. - Saving of data is not unified (i.e can save chunk data without saving entity data, poses problems for desync if shutdown is really abnormal. - Entities are not loaded with chunks. This caused quite a bit of headache for Chunk#getEntities API, but now the new chunk system loads entities with chunks so that they are ready whenever the chunk loads in. Effectively brings the behavior back to 1.16 era, but still storing entities in their own separate regionfiles. The above list is not complete. The patch documentation will complete it. New chunk system hard relies on starlight and dataconverter, and most importantly the new concurrent utilities in ConcurrentUtil. Some of the old async chunk i/o interface (i.e the old file io thread reroutes _some_ calls to the new file io thread) is kept for plugin compat reasons. It will be removed in the next major version of minecraft. The old legacy chunk system patches have been moved to the removed folder in case we need them again.
2022-09-26 10:02:51 +02:00
- DataLayer nibblearray = lightenginethreaded.getLayerListener(LightLayer.BLOCK).getDataLayerData(SectionPos.of(chunkcoordintpair, i));
- DataLayer nibblearray1 = lightenginethreaded.getLayerListener(LightLayer.SKY).getDataLayerData(SectionPos.of(chunkcoordintpair, i));
2021-11-27 11:42:09 +01:00
+ // Paper - replace light engine
- if (flag1 || nibblearray != null || nibblearray1 != null) {
- CompoundTag nbttagcompound1 = new CompoundTag();
+ // Paper start - replace light engine
+ ca.spottedleaf.starlight.common.light.SWMRNibbleArray.SaveState blockNibble = blockNibbles[i - minSection].getSaveState();
+ ca.spottedleaf.starlight.common.light.SWMRNibbleArray.SaveState skyNibble = skyNibbles[i - minSection].getSaveState();
+ if (flag1 || blockNibble != null || skyNibble != null) {
+ // Paper end - replace light engine
+ CompoundTag nbttagcompound1 = new CompoundTag(); CompoundTag section = nbttagcompound1; // Paper
if (flag1) {
LevelChunkSection chunksection = achunksection[j];
@@ -0,0 +0,0 @@ public class ChunkSerializer {
nbttagcompound1.put("biomes", (Tag) dataresult1.getOrThrow(false, logger1::error));
}
- if (nibblearray != null && !nibblearray.isEmpty()) {
- nbttagcompound1.putByteArray("BlockLight", nibblearray.getData());
+ // Paper start
+ // 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.
+
+ if (blockNibble != null) {
+ if (blockNibble.data != null) {
+ section.putByteArray("BlockLight", blockNibble.data);
+ }
+ section.putInt(BLOCKLIGHT_STATE_TAG, blockNibble.state);
}
- if (nibblearray1 != null && !nibblearray1.isEmpty()) {
- nbttagcompound1.putByteArray("SkyLight", nibblearray1.getData());
+ if (skyNibble != null) {
+ if (skyNibble.data != null) {
+ section.putByteArray("SkyLight", skyNibble.data);
+ }
+ section.putInt(SKYLIGHT_STATE_TAG, skyNibble.state);
}
+ // Paper end
if (!nbttagcompound1.isEmpty()) {
nbttagcompound1.putByte("Y", (byte) i);
@@ -0,0 +0,0 @@ public class ChunkSerializer {
nbttagcompound.put("sections", nbttaglist);
if (flag) {
- nbttagcompound.putBoolean("isLightOn", true);
+ nbttagcompound.putInt(STARLIGHT_VERSION_TAG, STARLIGHT_LIGHT_VERSION); // Paper
+ nbttagcompound.putBoolean("isLightOn", false); // Paper - set to false but still store, this allows us to detect --eraseCache (as eraseCache _removes_)
}
Rewrite chunk system (#8177) Patch documentation to come Issues with the old system that are fixed now: - World generation does not scale with cpu cores effectively. - Relies on the main thread for scheduling and maintaining chunk state, dropping chunk load/generate rates at lower tps. - Unreliable prioritisation of chunk gen/load calls that block the main thread. - Shutdown logic is utterly unreliable, as it has to wait for all chunks to unload - is it guaranteed that the chunk system is in a state on shutdown that it can reliably do this? Watchdog shutdown also typically failed due to thread checks, which is now resolved. - Saving of data is not unified (i.e can save chunk data without saving entity data, poses problems for desync if shutdown is really abnormal. - Entities are not loaded with chunks. This caused quite a bit of headache for Chunk#getEntities API, but now the new chunk system loads entities with chunks so that they are ready whenever the chunk loads in. Effectively brings the behavior back to 1.16 era, but still storing entities in their own separate regionfiles. The above list is not complete. The patch documentation will complete it. New chunk system hard relies on starlight and dataconverter, and most importantly the new concurrent utilities in ConcurrentUtil. Some of the old async chunk i/o interface (i.e the old file io thread reroutes _some_ calls to the new file io thread) is kept for plugin compat reasons. It will be removed in the next major version of minecraft. The old legacy chunk system patches have been moved to the removed folder in case we need them again.
2022-09-26 10:02:51 +02:00
ListTag nbttaglist1 = new ListTag();
@@ -0,0 +0,0 @@ public class ChunkSerializer {
}));
}
+ // Paper start
+ public static @Nullable ChunkStatus getStatus(@Nullable CompoundTag compound) {
+ if (compound == null) {
+ return null;
+ }
+
+ // Note: Copied from below
+ return ChunkStatus.getStatus(compound.getString("Status"));
+ }
+ // Paper end
+
public static ChunkStatus.ChunkType getChunkTypeFromTag(@Nullable CompoundTag nbt) {
return nbt != null ? ChunkStatus.byName(nbt.getString("Status")).getChunkType() : ChunkStatus.ChunkType.PROTOCHUNK;
}