diff --git a/common/src/main/java/com/seibel/distanthorizons/common/wrappers/chunk/ChunkLightStorage.java b/common/src/main/java/com/seibel/distanthorizons/common/wrappers/chunk/ChunkLightStorage.java new file mode 100644 index 000000000..ff5a0014e --- /dev/null +++ b/common/src/main/java/com/seibel/distanthorizons/common/wrappers/chunk/ChunkLightStorage.java @@ -0,0 +1,146 @@ +package com.seibel.distanthorizons.common.wrappers.chunk; + +import java.util.ArrayList; +import java.util.Arrays; + +/** +compact, efficient storage for light levels. +all blocks only take up 4 bits in total, +and if a 16x16x16 area is detected to have the same light level in all positions, +then we store a single byte for that light level, instead of 2 kilobytes. + +@author Builderb0y +*/ +public class ChunkLightStorage { + + /** the minimum Y level in the chunk which this storage is storing light levels for (inclusive). */ + public int minY; + /** the maximum Y level in the chunk which this storage is storing light levels for (exclusive). */ + public int maxY; + /** the data stored in this storage, split up into 16x16x16 areas. */ + public Section[] sections; + + public ChunkLightStorage(int minY, int maxY) { + this.minY = minY; + this.maxY = maxY; + } + + public int get(int x, int y, int z) { + if (y < this.minY) return 0; + if (y >= this.maxY) return 15; + if (this.sections != null) { + Section section = this.sections[(y - this.minY) >> 4]; + if (section != null) { + return section.get(x, y, z); + } + } + return 0; + } + + public void set(int x, int y, int z, int lightLevel) { + if (y < this.minY || y >= this.maxY) return; + //populate array if it doesn't exist. + if (this.sections == null) { + this.sections = new Section[(this.maxY - this.minY) >> 4]; + } + int index = (y - this.minY) >> 4; + Section section = this.sections[index]; + //populate section in array if it doesn't exist. + if (section == null) { + section = new Section(0); + this.sections[index] = section; + } + section.set(x, y, z, lightLevel); + } + + public static class Section { + + public byte constantValue; + public long[] data; + public short[] counts; + + public Section(int initialValue) { + this.constantValue = (byte)(initialValue); + this.counts = new short[16]; + this.counts[initialValue] = 16 * 16 * 16; + } + + public int get(int x, int y, int z) { + if (this.constantValue >= 0) return this.constantValue; + x &= 15; + y &= 15; + z &= 15; + long bits = this.data[(z << 4) | x]; + return ((int)(bits >>> (y << 2))) & 15; + } + + public void set(int x, int y, int z, int lightLevel) { + int oldLightLevel = -1; + if (this.constantValue >= 0) { + oldLightLevel = this.constantValue; + + //if the light level didn't change, then there's nothing to do. + if (oldLightLevel == lightLevel) return; + + //if we are a constant value and need to change something, + //then that means we need to convert to a non-constant value. + this.data = DataRecycler.get(); + + //repeat oldLightLevel 16 times as a bit pattern. + long payload = oldLightLevel; + payload |= payload << 4; + payload |= payload << 8; + payload |= payload << 16; + payload |= payload << 32; + + //fill our data with our constant value. + Arrays.fill(this.data, payload); + + //we are no longer a constant value. + this.constantValue = -1; + } + + x &= 15; + y &= 15; + z &= 15; + int index = (z << 4) | x; + long bits = this.data[index]; + //if we weren't a constant value before, now's the time to initialize oldLightLevel. + if (oldLightLevel < 0) { + oldLightLevel = ((int)(bits >>> (y << 2))) & 15; + } + //clear the 4 bits that correspond to the light level at x, y, z... + bits &= ~(15L << (y << 2)); + //...and then re-populate those bits with the new light level. + bits |= ((long)(lightLevel)) << (y << 2); + //store the updated bits in our data. + this.data[index] = bits; + + //we have one less of the old light level... + this.counts[oldLightLevel]--; + //...and one more of the new level. + //if the number associated with the new level is now 4096 (AKA 16 ^ 3), + //then this implies every position in this section has the same light level, + //and therefore we can convert back to a constant value. + if (++this.counts[lightLevel] == 4096) { + this.constantValue = (byte)(lightLevel); + DataRecycler.reclaim(this.data); + this.data = null; + } + } + } + + static class DataRecycler { + + private static final ArrayList recycled = new ArrayList<>(256); + + static synchronized long[] get() { + if (recycled.isEmpty()) return new long[256]; + else return recycled.remove(recycled.size() - 1); + } + + static synchronized void reclaim(long[] data) { + if (recycled.size() < 256) recycled.add(data); + } + } +} \ No newline at end of file diff --git a/common/src/main/java/com/seibel/distanthorizons/common/wrappers/chunk/ChunkWrapper.java b/common/src/main/java/com/seibel/distanthorizons/common/wrappers/chunk/ChunkWrapper.java index 70d81e48b..51103f0b2 100644 --- a/common/src/main/java/com/seibel/distanthorizons/common/wrappers/chunk/ChunkWrapper.java +++ b/common/src/main/java/com/seibel/distanthorizons/common/wrappers/chunk/ChunkWrapper.java @@ -77,8 +77,8 @@ public class ChunkWrapper implements IChunkWrapper /** only used when connected to a dedicated server */ private boolean isMcClientLightingCorrect = false; - private final byte[] blockLightArray; - private final byte[] skyLightArray; + private ChunkLightStorage blockLightArray; + private ChunkLightStorage skyLightArray; private ArrayList blockLightPosList = null; @@ -116,9 +116,7 @@ public class ChunkWrapper implements IChunkWrapper this.useDhLighting = isDhGeneratedChunk; // FIXME +1 is to handle the fact that LodDataBuilder adds +1 to all block lighting calculations, also done in the relative position validator - this.blockLightArray = new byte[LodUtil.CHUNK_WIDTH * LodUtil.CHUNK_WIDTH * (this.getHeight() + 1)]; - this.skyLightArray = new byte[LodUtil.CHUNK_WIDTH * LodUtil.CHUNK_WIDTH * (this.getHeight() + 1)]; - + chunksNeedingClientLightUpdating.add(this); } @@ -256,39 +254,48 @@ public class ChunkWrapper implements IChunkWrapper #endif } - + private ChunkLightStorage getBlockLightArray() + { + if (this.blockLightArray == null) + { + this.blockLightArray = new ChunkLightStorage(this.getMinBuildHeight(), this.getMaxBuildHeight()); + } + return this.blockLightArray; + } + + private ChunkLightStorage getSkyLightArray() + { + if (this.skyLightArray == null) + { + this.skyLightArray = new ChunkLightStorage(this.getMinBuildHeight(), this.getMaxBuildHeight()); + } + return this.skyLightArray; + } + @Override public int getDhBlockLight(int relX, int y, int relZ) { this.throwIndexOutOfBoundsIfRelativePosOutsideChunkBounds(relX, y, relZ); - - int index = this.relativeBlockPosToIndex(relX, y, relZ); - return this.blockLightArray[index]; + return this.getBlockLightArray().get(relX, y, relZ); } @Override public void setDhBlockLight(int relX, int y, int relZ, int lightValue) { this.throwIndexOutOfBoundsIfRelativePosOutsideChunkBounds(relX, y, relZ); - - int index = this.relativeBlockPosToIndex(relX, y, relZ); - this.blockLightArray[index] = (byte) lightValue; + this.getBlockLightArray().set(relX, y, relZ, lightValue); } @Override public int getDhSkyLight(int relX, int y, int relZ) { this.throwIndexOutOfBoundsIfRelativePosOutsideChunkBounds(relX, y, relZ); - - int index = this.relativeBlockPosToIndex(relX, y, relZ); - return this.skyLightArray[index]; + return this.getSkyLightArray().get(relX, y, relZ); } @Override public void setDhSkyLight(int relX, int y, int relZ, int lightValue) { this.throwIndexOutOfBoundsIfRelativePosOutsideChunkBounds(relX, y, relZ); - - int index = this.relativeBlockPosToIndex(relX, y, relZ); - this.skyLightArray[index] = (byte) lightValue; + this.getSkyLightArray().set(relX, y, relZ, lightValue); } @@ -301,8 +308,7 @@ public class ChunkWrapper implements IChunkWrapper if (this.useDhLighting) { // DH lighting method - int index = this.relativeBlockPosToIndex(relX, y, relZ); - return this.blockLightArray[index]; + return this.getBlockLightArray().get(relX, y, relZ); } else { @@ -322,8 +328,7 @@ public class ChunkWrapper implements IChunkWrapper if (this.useDhLighting) { // DH lighting method - int index = this.relativeBlockPosToIndex(relX, y, relZ); - return this.skyLightArray[index]; + return this.getSkyLightArray().get(relX, y, relZ); } else {