Move shared ChunkWrapper code form Main to Core
This commit is contained in:
@@ -33,6 +33,7 @@ import java.util.*;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* This logic was roughly based on
|
||||
@@ -67,7 +68,7 @@ public class DhLightingEngine
|
||||
* @param nearbyChunkList should also contain centerChunk
|
||||
* @param maxSkyLight should be a value between 0 and 15
|
||||
*/
|
||||
public void lightChunk(IChunkWrapper centerChunk, ArrayList<IChunkWrapper> nearbyChunkList, int maxSkyLight)
|
||||
public void lightChunk(@NotNull IChunkWrapper centerChunk, @NotNull ArrayList<IChunkWrapper> nearbyChunkList, int maxSkyLight)
|
||||
{
|
||||
DhChunkPos centerChunkPos = centerChunk.getChunkPos();
|
||||
AdjacentChunkHolder adjacentChunkHolder = new AdjacentChunkHolder(centerChunk);
|
||||
@@ -104,7 +105,6 @@ public class DhLightingEngine
|
||||
for (int chunkIndex = 0; chunkIndex < nearbyChunkList.size(); chunkIndex++) // using iterators in high traffic areas can cause GC issues due to allocating a bunch of iterators, use an indexed for-loop instead
|
||||
{
|
||||
IChunkWrapper chunk = nearbyChunkList.get(chunkIndex);
|
||||
|
||||
if (chunk != null && requestedAdjacentPositions.contains(chunk.getChunkPos()))
|
||||
{
|
||||
// remove the newly found position
|
||||
@@ -179,7 +179,7 @@ public class DhLightingEngine
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// block light
|
||||
this.propagateLightPosList(blockLightPosQueue, adjacentChunkHolder,
|
||||
(neighbourChunk, relBlockPos) -> neighbourChunk.getDhBlockLight(relBlockPos.x, relBlockPos.y, relBlockPos.z),
|
||||
|
||||
@@ -30,6 +30,13 @@ public class ArrayGridList<T> extends ArrayList<T>
|
||||
{
|
||||
public final int gridSize;
|
||||
|
||||
|
||||
|
||||
//==============//
|
||||
// constructors //
|
||||
//==============//
|
||||
|
||||
/** @param filler the function called for each index to set the initial values */
|
||||
public ArrayGridList(int gridSize, BiFunction<Integer, Integer, T> filler)
|
||||
{
|
||||
super((gridSize) * (gridSize));
|
||||
@@ -67,6 +74,12 @@ public class ArrayGridList<T> extends ArrayList<T>
|
||||
// "==========================================\n");
|
||||
}
|
||||
|
||||
|
||||
|
||||
//=========//
|
||||
// methods //
|
||||
//=========//
|
||||
|
||||
protected int _indexOf(int x, int y)
|
||||
{
|
||||
return x + y * gridSize;
|
||||
|
||||
+257
@@ -0,0 +1,257 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.seibel.distanthorizons.core.wrapperInterfaces.chunk;
|
||||
|
||||
import com.seibel.distanthorizons.core.util.LodUtil;
|
||||
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
|
||||
*/
|
||||
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 LightSection[] lightSections;
|
||||
|
||||
/**
|
||||
* If the get method is called on a Y position above what's stored
|
||||
* this value will be returned. <br><br>
|
||||
*
|
||||
* This needs to be manually defined since sky and block lights behave differently
|
||||
* for values both above and below what's defined.
|
||||
*/
|
||||
public int aboveMaxYValue;
|
||||
/** @see ChunkLightStorage#aboveMaxYValue */
|
||||
public int belowMinYValue;
|
||||
|
||||
|
||||
|
||||
//=============//
|
||||
// constructor //
|
||||
//=============//
|
||||
|
||||
public static ChunkLightStorage createSkyLightStorage(IChunkWrapper chunkWrapper) { return createSkyLightStorage(chunkWrapper.getMinBuildHeight(), chunkWrapper.getMaxBuildHeight()); }
|
||||
public static ChunkLightStorage createSkyLightStorage(int minY, int maxY)
|
||||
{
|
||||
return new ChunkLightStorage(
|
||||
minY, maxY,
|
||||
// positions above should be lit but positions below should be unlit
|
||||
LodUtil.MAX_MC_LIGHT, LodUtil.MIN_MC_LIGHT);
|
||||
}
|
||||
public static ChunkLightStorage createBlockLightStorage(IChunkWrapper chunkWrapper) { return createBlockLightStorage(chunkWrapper.getMinBuildHeight(), chunkWrapper.getMaxBuildHeight()); }
|
||||
public static ChunkLightStorage createBlockLightStorage(int minY, int maxY)
|
||||
{
|
||||
return new ChunkLightStorage(
|
||||
minY, maxY,
|
||||
// positions above and below the handled area should be unlit
|
||||
LodUtil.MIN_MC_LIGHT, LodUtil.MIN_MC_LIGHT);
|
||||
}
|
||||
|
||||
public ChunkLightStorage(int minY, int maxY, int aboveMaxYValue, int belowMinYValue)
|
||||
{
|
||||
this.minY = minY;
|
||||
this.maxY = maxY;
|
||||
|
||||
this.aboveMaxYValue = aboveMaxYValue;
|
||||
this.belowMinYValue = belowMinYValue;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//=====================//
|
||||
// getters and setters //
|
||||
//=====================//
|
||||
|
||||
public int get(int x, int y, int z)
|
||||
{
|
||||
if (y < this.minY)
|
||||
{
|
||||
return this.belowMinYValue;
|
||||
}
|
||||
else if (y >= this.maxY)
|
||||
{
|
||||
return this.aboveMaxYValue;
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
//populate array if it doesn't exist.
|
||||
if (this.lightSections == null)
|
||||
{
|
||||
this.lightSections = new LightSection[BitShiftUtil.divideByPowerOfTwo(this.maxY - this.minY, 4)];
|
||||
}
|
||||
|
||||
int index = (y - this.minY) >> 4;
|
||||
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;
|
||||
}
|
||||
lightSection.set(x, y, z, lightLevel);
|
||||
}
|
||||
|
||||
|
||||
|
||||
//================//
|
||||
// helper classes //
|
||||
//================//
|
||||
|
||||
public static class LightSection
|
||||
{
|
||||
public byte constantValue;
|
||||
public long[] data;
|
||||
public short[] counts;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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<long[]> 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); }
|
||||
}
|
||||
|
||||
}
|
||||
+67
-2
@@ -23,6 +23,7 @@ import com.seibel.distanthorizons.core.pos.DhBlockPos;
|
||||
import com.seibel.distanthorizons.core.pos.DhBlockPos2D;
|
||||
import com.seibel.distanthorizons.core.pos.DhChunkPos;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
|
||||
import com.seibel.distanthorizons.coreapi.ModInfo;
|
||||
import com.seibel.distanthorizons.coreapi.interfaces.dependencyInjection.IBindable;
|
||||
import com.seibel.distanthorizons.core.util.LodUtil;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper;
|
||||
@@ -31,6 +32,11 @@ import java.util.ArrayList;
|
||||
|
||||
public interface IChunkWrapper extends IBindable
|
||||
{
|
||||
/** useful for debugging, but can slow down chunk operations quite a bit due to being called every time. */
|
||||
boolean RUN_RELATIVE_POS_INDEX_VALIDATION = ModInfo.IS_DEV_BUILD;
|
||||
|
||||
|
||||
|
||||
DhChunkPos getChunkPos();
|
||||
|
||||
default int getHeight() { return this.getMaxBuildHeight() - this.getMinBuildHeight(); }
|
||||
@@ -61,8 +67,6 @@ public interface IChunkWrapper extends IBindable
|
||||
int getMinBlockX();
|
||||
int getMinBlockZ();
|
||||
|
||||
long getLongChunkPos();
|
||||
|
||||
void setIsDhLightCorrect(boolean isDhLightCorrect);
|
||||
void setUseDhLighting(boolean useDhLighting);
|
||||
boolean isLightCorrect();
|
||||
@@ -153,4 +157,65 @@ public interface IChunkWrapper extends IBindable
|
||||
|
||||
boolean isStillValid();
|
||||
|
||||
|
||||
|
||||
//================//
|
||||
// helper methods //
|
||||
//================//
|
||||
|
||||
/** used to prevent accidentally attempting to get/set values outside this chunk's boundaries */
|
||||
default void throwIndexOutOfBoundsIfRelativePosOutsideChunkBounds(int x, int y, int z) throws IndexOutOfBoundsException
|
||||
{
|
||||
if (!RUN_RELATIVE_POS_INDEX_VALIDATION)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// FIXME +1 is to handle the fact that LodDataBuilder adds +1 to all block lighting calculations, also done in the constructor
|
||||
int minHeight = this.getMinBuildHeight();
|
||||
int maxHeight = this.getMaxBuildHeight() + 1;
|
||||
|
||||
if (x < 0 || x >= LodUtil.CHUNK_WIDTH
|
||||
|| z < 0 || z >= LodUtil.CHUNK_WIDTH
|
||||
|| y < minHeight || y > maxHeight)
|
||||
{
|
||||
String errorMessage = "Relative position [" + x + "," + y + "," + z + "] out of bounds. \n" +
|
||||
"X/Z must be between 0 and 15 (inclusive) \n" +
|
||||
"Y must be between [" + minHeight + "] and [" + maxHeight + "] (inclusive).";
|
||||
throw new IndexOutOfBoundsException(errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts a 3D position into a 1D array index. <br><br>
|
||||
*
|
||||
* Source: <br>
|
||||
* <a href="https://stackoverflow.com/questions/7367770/how-to-flatten-or-index-3d-array-in-1d-array">stackoverflow</a>
|
||||
*/
|
||||
default int relativeBlockPosToIndex(int xRel, int y, int zRel)
|
||||
{
|
||||
int yRel = y - this.getMinBuildHeight();
|
||||
return (zRel * LodUtil.CHUNK_WIDTH * this.getHeight()) + (yRel * LodUtil.CHUNK_WIDTH) + xRel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a 3D position into a 1D array index. <br><br>
|
||||
*
|
||||
* Source: <br>
|
||||
* <a href="https://stackoverflow.com/questions/7367770/how-to-flatten-or-index-3d-array-in-1d-array">stackoverflow</a>
|
||||
*/
|
||||
default DhBlockPos indexToRelativeBlockPos(int index)
|
||||
{
|
||||
final int zRel = index / (LodUtil.CHUNK_WIDTH * this.getHeight());
|
||||
index -= (zRel * LodUtil.CHUNK_WIDTH * this.getHeight());
|
||||
|
||||
final int y = index / LodUtil.CHUNK_WIDTH;
|
||||
final int yRel = y + this.getMinBuildHeight();
|
||||
|
||||
final int xRel = index % LodUtil.CHUNK_WIDTH;
|
||||
return new DhBlockPos(xRel, yRel, zRel);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user