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 index ff5a0014e..8d0a465c5 100644 --- 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 @@ -1,146 +1,216 @@ +/* + * This file is part of the Distant Horizons mod + * licensed under the GNU LGPL v3 License. + * + * Copyright (C) 2020-2023 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + package com.seibel.distanthorizons.common.wrappers.chunk; +import com.seibel.distanthorizons.coreapi.util.BitShiftUtil; + 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 + * 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 { - +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) { + public LightSection[] lightSections; + + + + 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); + + + + public int get(int x, int y, int z) + { + if (y < this.minY) + { + return 0; + } + if (y >= this.maxY) + { + return 15; + } + + if (this.lightSections != null) + { + LightSection lightSection = this.lightSections[BitShiftUtil.divideByPowerOfTwo(y - this.minY, 4)]; + if (lightSection != null) + { + return lightSection.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; + + 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]; + if (this.lightSections == null) + { + this.lightSections = new LightSection[BitShiftUtil.divideByPowerOfTwo(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; + LightSection lightSection = this.lightSections[index]; + + //populate lightSection in array if it doesn't exist. + if (lightSection == null) + { + lightSection = new LightSection(0); + this.lightSections[index] = lightSection; } - section.set(x, y, z, lightLevel); + lightSection.set(x, y, z, lightLevel); } - - public static class Section { - + + + + //================// + // helper classes // + //================// + + public static class LightSection + { public byte constantValue; public long[] data; public short[] counts; - - public Section(int initialValue) { - this.constantValue = (byte)(initialValue); + + public LightSection(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; + + 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; + return ((int) (bits >>> (y << 2))) & 15; } - - public void set(int x, int y, int z, int lightLevel) { + + public void set(int x, int y, int z, int lightLevel) + { int oldLightLevel = -1; - if (this.constantValue >= 0) { + 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; + 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); + 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); + if (++this.counts[lightLevel] == 4096) + { + this.constantValue = (byte) (lightLevel); DataRecycler.reclaim(this.data); this.data = null; } } + } - - static class DataRecycler { - + + 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); + + 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 51103f0b2..755877581 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 @@ -31,7 +31,6 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; -import com.seibel.distanthorizons.coreapi.util.BitShiftUtil; import net.minecraft.client.multiplayer.ClientChunkCache; import net.minecraft.client.multiplayer.ClientLevel; import net.minecraft.core.BlockPos; @@ -77,8 +76,8 @@ public class ChunkWrapper implements IChunkWrapper /** only used when connected to a dedicated server */ private boolean isMcClientLightingCorrect = false; - private ChunkLightStorage blockLightArray; - private ChunkLightStorage skyLightArray; + private ChunkLightStorage blockLightStorage; + private ChunkLightStorage skyLightStorage; private ArrayList blockLightPosList = null; @@ -254,48 +253,50 @@ 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); - return this.getBlockLightArray().get(relX, y, relZ); + return this.getBlockLightStorage().get(relX, y, relZ); } @Override public void setDhBlockLight(int relX, int y, int relZ, int lightValue) { this.throwIndexOutOfBoundsIfRelativePosOutsideChunkBounds(relX, y, relZ); - this.getBlockLightArray().set(relX, y, relZ, lightValue); + this.getBlockLightStorage().set(relX, y, relZ, lightValue); } + private ChunkLightStorage getBlockLightStorage() + { + if (this.blockLightStorage == null) + { + this.blockLightStorage = new ChunkLightStorage(this.getMinBuildHeight(), this.getMaxBuildHeight()); + } + return this.blockLightStorage; + } + + @Override public int getDhSkyLight(int relX, int y, int relZ) { this.throwIndexOutOfBoundsIfRelativePosOutsideChunkBounds(relX, y, relZ); - return this.getSkyLightArray().get(relX, y, relZ); + return this.getSkyLightStorage().get(relX, y, relZ); } @Override public void setDhSkyLight(int relX, int y, int relZ, int lightValue) { this.throwIndexOutOfBoundsIfRelativePosOutsideChunkBounds(relX, y, relZ); - this.getSkyLightArray().set(relX, y, relZ, lightValue); + this.getSkyLightStorage().set(relX, y, relZ, lightValue); + } + + private ChunkLightStorage getSkyLightStorage() + { + if (this.skyLightStorage == null) + { + this.skyLightStorage = new ChunkLightStorage(this.getMinBuildHeight(), this.getMaxBuildHeight()); + } + return this.skyLightStorage; } @@ -308,7 +309,7 @@ public class ChunkWrapper implements IChunkWrapper if (this.useDhLighting) { // DH lighting method - return this.getBlockLightArray().get(relX, y, relZ); + return this.getBlockLightStorage().get(relX, y, relZ); } else { @@ -328,7 +329,7 @@ public class ChunkWrapper implements IChunkWrapper if (this.useDhLighting) { // DH lighting method - return this.getSkyLightArray().get(relX, y, relZ); + return this.getSkyLightStorage().get(relX, y, relZ); } else {