Add faster sky light engine from Builderb0y

Closes !67
This commit is contained in:
James Seibel
2024-09-07 12:07:48 -05:00
parent 3bee25053f
commit d96ba5ae54
14 changed files with 347 additions and 835 deletions
@@ -316,41 +316,8 @@ public class SharedApi
}
// chunk light baking is disabled since profiling revealed it used
// roughly the same amount of time as generating the lighting ourselves and
// was much more likely to have issues with corrupt (all black or all bright) chunks
boolean tryUsingMcLightingEngine = false;
if (tryUsingMcLightingEngine)
{
// Save or populate the chunk wrapper's lighting
// this is done so we don't have to worry about MC unloading the lighting data for this chunk
boolean chunkLightPopulated = false;
boolean onlyUseDhLighting = Config.Client.Advanced.LodBuilding.onlyUseDhLightingEngine.get();
if (!onlyUseDhLighting && chunkWrapper.isLightCorrect())
{
// If MC's lighting engine isn't thread safe this may cause the server thread to lag
chunkLightPopulated = chunkWrapper.bakeDhLightingUsingMcLightingEngine(dhLevel.getLevelWrapper());
if (!chunkLightPopulated)
{
// clear any existing data to prevent partial or corrupt lighting
// when re-generating it
chunkWrapper.clearDhBlockLighting();
chunkWrapper.clearDhSkyLighting();
}
}
// something went wrong during the baking process so we have to generate the lighting ourselves
if (!chunkLightPopulated)
{
DhLightingEngine.INSTANCE.lightChunk(chunkWrapper, nearbyChunkList, dhLevel.hasSkyLight() ? LodUtil.MAX_MC_LIGHT : LodUtil.MIN_MC_LIGHT);
}
}
else
{
DhLightingEngine.INSTANCE.lightChunk(chunkWrapper, nearbyChunkList, dhLevel.hasSkyLight() ? LodUtil.MAX_MC_LIGHT : LodUtil.MIN_MC_LIGHT);
}
// sky lighting is populated later at the data source level
DhLightingEngine.INSTANCE.bakeChunkBlockLighting(chunkWrapper, nearbyChunkList, dhLevel.hasSkyLight() ? LodUtil.MAX_MC_LIGHT : LodUtil.MIN_MC_LIGHT);
dhLevel.updateBeaconBeamsForChunk(chunkWrapper, nearbyChunkList);
dhLevel.updateChunkAsync(chunkWrapper, newChunkHash);
@@ -144,7 +144,7 @@ public class FullDataSourceV2 implements IDataSource<IDhLevel>
this.columnWorldCompressionMode = columnWorldCompressionMode;
}
public static FullDataSourceV2 createFromChunk(IChunkWrapper chunkWrapper) { return LodDataBuilder.createGeneratedDataSource(chunkWrapper); }
public static FullDataSourceV2 createFromChunk(IChunkWrapper chunkWrapper) { return LodDataBuilder.createFromChunk(chunkWrapper); }
public static FullDataSourceV2 createFromLegacyDataSourceV1(FullDataSourceV1 legacyData)
{
@@ -59,12 +59,10 @@ public class LodDataBuilder
// converters //
//============//
public static FullDataSourceV2 createGeneratedDataSource(IChunkWrapper chunkWrapper)
public static FullDataSourceV2 createFromChunk(IChunkWrapper chunkWrapper)
{
if (!canGenerateLodFromChunk(chunkWrapper))
{
return null;
}
// only block lighting is needed here, sky lighting is populated at the data source stage
LodUtil.assertTrue(chunkWrapper.isDhBlockLightingCorrect());
@@ -153,8 +151,8 @@ public class LodDataBuilder
if (lastY < chunkWrapper.getMaxBuildHeight())
{
// FIXME: The lastY +1 offset is to reproduce the old behavior. Remove this when we get per-face lighting
blockLight = (byte) chunkWrapper.getBlockLight(relBlockX, lastY + 1, relBlockZ);
skyLight = (byte) chunkWrapper.getSkyLight(relBlockX, lastY + 1, relBlockZ);
blockLight = (byte) chunkWrapper.getDhBlockLight(relBlockX, lastY + 1, relBlockZ);
skyLight = (byte) chunkWrapper.getDhSkyLight(relBlockX, lastY + 1, relBlockZ);
}
else
{
@@ -195,8 +193,8 @@ public class LodDataBuilder
{
IBiomeWrapper newBiome = chunkWrapper.getBiome(relBlockX, y, relBlockZ);
IBlockStateWrapper newBlockState = previousBlockState = chunkWrapper.getBlockState(relBlockX, y, relBlockZ, mcBlockPos, previousBlockState);
byte newBlockLight = (byte) chunkWrapper.getBlockLight(relBlockX, y + 1, relBlockZ);
byte newSkyLight = (byte) chunkWrapper.getSkyLight(relBlockX, y + 1, relBlockZ);
byte newBlockLight = (byte) chunkWrapper.getDhBlockLight(relBlockX, y + 1, relBlockZ);
byte newSkyLight = (byte) chunkWrapper.getDhSkyLight(relBlockX, y + 1, relBlockZ);
// save the biome/block change
if (!newBiome.equals(biome) || !newBlockState.equals(blockState))
@@ -439,8 +437,6 @@ public class LodDataBuilder
// helper methods //
//================//
public static boolean canGenerateLodFromChunk(IChunkWrapper chunk) { return chunk != null && chunk.isLightCorrect(); }
public static int getXOrZSectionPosFromChunkPos(int chunkXOrZPos)
{
// get the section position
@@ -23,6 +23,7 @@ import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiWorldGeneratio
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.file.structure.AbstractSaveStructure;
import com.seibel.distanthorizons.core.generation.DhLightingEngine;
import com.seibel.distanthorizons.core.generation.IFullDataSourceRetrievalQueue;
import com.seibel.distanthorizons.core.generation.tasks.IWorldGenTaskTracker;
import com.seibel.distanthorizons.core.generation.tasks.WorldGenResult;
@@ -394,7 +395,14 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
}
}
private void onDataSourceSave(FullDataSourceV2 fullDataSource)
{ GeneratedFullDataSourceProvider.this.updateDataSourceAsync(fullDataSource); }
{
// block lights should have been populated at the chunkWrapper stage
// waiting to populate the data source's skylight at this stage prevents re-lighting and
// allows us to reduce cross-chunk lighting issues by lighting the whole 4x4 LOD at once
DhLightingEngine.INSTANCE.bakeDataSourceSkyLight(fullDataSource, LodUtil.MAX_MC_LIGHT);
GeneratedFullDataSourceProvider.this.updateDataSourceAsync(fullDataSource);
}
@@ -184,11 +184,6 @@ public class SubDimensionLevelMatcher implements AutoCloseable
DhLightingEngine.INSTANCE.lightChunk(newlyLoadedChunk, new ArrayList<>(), MC_CLIENT.getWrappedClientLevel().hasSkyLight() ? 15 : 0);
// build the chunk LOD
if (!LodDataBuilder.canGenerateLodFromChunk(newlyLoadedChunk))
{
LOGGER.warn("unable to build lod for chunk:"+newlyLoadedChunk.getChunkPos());
return null;
}
FullDataSourceV2 newChunkSizedFullDataView = FullDataSourceV2.createFromChunk(newlyLoadedChunk);
// convert to a data source for easier comparing
FullDataSourceV2 newDataSource = FullDataSourceV2.createEmpty(DhSectionPos.encodeContaining(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL, this.playerData.playerBlockPos));
@@ -60,11 +60,23 @@ public class DhLightingEngine
private static final ThreadLocal<DhBlockPosMutable> PRIMARY_BLOCK_POS_REF = ThreadLocal.withInitial(() -> new DhBlockPosMutable());
private static final ThreadLocal<DhBlockPosMutable> SECONDARY_BLOCK_POS_REF = ThreadLocal.withInitial(() -> new DhBlockPosMutable());
/** if enabled will render each block light value when the lighting engine is run */
/** if enabled will render each block light value when the chunk lighting engine is run */
private static final boolean RENDER_BLOCK_LIGHT_WIREFRAME = false;
/** if enabled will render each sky light value when the lighting engine is run */
/** if enabled will render each sky light value when the chunk lighting engine is run */
private static final boolean RENDER_SKY_LIGHT_WIREFRAME = false;
/**
* Used for dataSource lighting. <br>
* Packed as alternating x and z offsets.
*/
private static final byte[] ADJACENT_DIRECTION_OFFSETS = new byte[]
{
-1, 0,
+1, 0,
0, -1,
0, +1
};
//=============//
@@ -75,9 +87,28 @@ public class DhLightingEngine
//=========//
// methods //
//=========//
//================//
// chunk lighting //
//================//
/**
* Populates both block and sky lighting.
* @see DhLightingEngine#lightChunk(IChunkWrapper, ArrayList, int, boolean, boolean)
*/
public void lightChunk(
@NotNull IChunkWrapper centerChunk, @NotNull ArrayList<IChunkWrapper> nearbyChunkList,
int maxSkyLight)
{ this.lightChunk(centerChunk, nearbyChunkList, maxSkyLight, true, true); }
/**
* Only populates block lights.
* @see DhLightingEngine#lightChunk(IChunkWrapper, ArrayList, int, boolean, boolean)
*/
public void bakeChunkBlockLighting(
@NotNull IChunkWrapper centerChunk, @NotNull ArrayList<IChunkWrapper> nearbyChunkList,
int maxSkyLight)
{ this.lightChunk(centerChunk, nearbyChunkList, maxSkyLight, true, false); }
/**
* Note: depending on the implementation of {@link IChunkWrapper#setDhBlockLight(int, int, int, int)} and {@link IChunkWrapper#setDhSkyLight(int, int, int, int)}
@@ -88,13 +119,13 @@ public class DhLightingEngine
* @param nearbyChunkList should also contain centerChunk
* @param maxSkyLight should be a value between 0 and 15
*/
public void lightChunk(@NotNull IChunkWrapper centerChunk, @NotNull ArrayList<IChunkWrapper> nearbyChunkList, int maxSkyLight)
private void lightChunk(
@NotNull IChunkWrapper centerChunk, @NotNull ArrayList<IChunkWrapper> nearbyChunkList,
int maxSkyLight, boolean updateBlockLight, boolean updateSkyLight)
{
DhChunkPos centerChunkPos = centerChunk.getChunkPos();
AdjacentChunkHolder adjacentChunkHolder = new AdjacentChunkHolder(centerChunk);
long startTimeNs = System.nanoTime();
// try-finally to handle the stableArray resources
StableLightPosStack blockLightWorldPosQueue = null;
@@ -121,7 +152,6 @@ public class DhLightingEngine
// find all adjacent chunks
// and get any necessary info from them
boolean warningLogged = false;
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);
@@ -133,28 +163,31 @@ public class DhLightingEngine
// add the adjacent chunk
adjacentChunkHolder.add(chunk);
// get and set the adjacent chunk's initial block lights
final DhBlockPosMutable relLightBlockPos = PRIMARY_BLOCK_POS_REF.get();
//==================//
// set block lights //
//==================//
// get and set the adjacent chunk's initial block lights
final DhBlockPosMutable relLightBlockPos = PRIMARY_BLOCK_POS_REF.get();
ArrayList<DhBlockPos> blockLightPosList = chunk.getWorldBlockLightPosList();
for (int blockLightIndex = 0; blockLightIndex < blockLightPosList.size(); blockLightIndex++) // using iterators in high traffic areas can cause GC issues due to allocating a bunch of iterators, use an indexed for-loop instead
if (updateBlockLight)
{
DhBlockPos blockLightPos = blockLightPosList.get(blockLightIndex);
blockLightPos.mutateToChunkRelativePos(relLightBlockPos);
// get the light
IBlockStateWrapper blockState = chunk.getBlockState(relLightBlockPos);
int lightValue = blockState.getLightEmission();
blockLightWorldPosQueue.push(blockLightPos.getX(), blockLightPos.getY(), blockLightPos.getZ(), lightValue);
// set the light
chunk.setDhBlockLight(relLightBlockPos.getX(), relLightBlockPos.getY(), relLightBlockPos.getZ(), lightValue);
ArrayList<DhBlockPos> blockLightPosList = chunk.getWorldBlockLightPosList();
for (int blockLightIndex = 0; blockLightIndex < blockLightPosList.size(); blockLightIndex++) // using iterators in high traffic areas can cause GC issues due to allocating a bunch of iterators, use an indexed for-loop instead
{
DhBlockPos blockLightPos = blockLightPosList.get(blockLightIndex);
blockLightPos.mutateToChunkRelativePos(relLightBlockPos);
// get the light
IBlockStateWrapper blockState = chunk.getBlockState(relLightBlockPos);
int lightValue = blockState.getLightEmission();
blockLightWorldPosQueue.push(blockLightPos.getX(), blockLightPos.getY(), blockLightPos.getZ(), lightValue);
// set the light
chunk.setDhBlockLight(relLightBlockPos.getX(), relLightBlockPos.getY(), relLightBlockPos.getZ(), lightValue);
}
}
@@ -165,7 +198,7 @@ public class DhLightingEngine
// get and set the adjacent chunk's initial skylights,
// if the dimension has skylights
if (maxSkyLight > 0)
if (updateSkyLight && maxSkyLight > 0)
{
IMutableBlockPosWrapper mcBlockPos = chunk.getMutableBlockPosWrapper();
IBlockStateWrapper previousBlockState = null;
@@ -211,16 +244,22 @@ public class DhLightingEngine
}
// block light
this.propagateLightPosList(blockLightWorldPosQueue, adjacentChunkHolder,
(neighbourChunk, relBlockPos) -> neighbourChunk.getDhBlockLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ()),
(neighbourChunk, relBlockPos, newLightValue) -> neighbourChunk.setDhBlockLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ(), newLightValue),
true);
if (updateBlockLight)
{
this.propagateChunkLightPosList(blockLightWorldPosQueue, adjacentChunkHolder,
(neighbourChunk, relBlockPos) -> neighbourChunk.getDhBlockLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ()),
(neighbourChunk, relBlockPos, newLightValue) -> neighbourChunk.setDhBlockLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ(), newLightValue),
true);
}
// sky light
this.propagateLightPosList(skyLightWorldPosQueue, adjacentChunkHolder,
(neighbourChunk, relBlockPos) -> neighbourChunk.getDhSkyLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ()),
(neighbourChunk, relBlockPos, newLightValue) -> neighbourChunk.setDhSkyLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ(), newLightValue),
false);
if (updateSkyLight)
{
this.propagateChunkLightPosList(skyLightWorldPosQueue, adjacentChunkHolder,
(neighbourChunk, relBlockPos) -> neighbourChunk.getDhSkyLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ()),
(neighbourChunk, relBlockPos, newLightValue) -> neighbourChunk.setDhSkyLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ(), newLightValue),
false);
}
}
catch (Exception e)
{
@@ -233,17 +272,18 @@ public class DhLightingEngine
}
centerChunk.setIsDhLightCorrect(true);
centerChunk.setUseDhLighting(true);
long endTimeNs = System.nanoTime();
float totalTimeMs = (endTimeNs - startTimeNs) / 1_000_000.0f;
//LOGGER.trace("Finished generating lighting for chunk: [" + centerChunkPos + "] in ["+totalTimeMs+"] milliseconds");
if (updateBlockLight)
{
centerChunk.setIsDhBlockLightCorrect(true);
}
if (updateSkyLight)
{
centerChunk.setIsDhSkyLightCorrect(true);
}
}
/** Applies each {@link LightPos} from the queue to the given set of {@link IChunkWrapper}'s. */
private void propagateLightPosList(
private void propagateChunkLightPosList(
StableLightPosStack lightPosQueue, AdjacentChunkHolder adjacentChunkHolder,
IGetLightFunc getLightFunc, ISetLightFunc setLightFunc,
boolean propagatingBlockLights)
@@ -339,10 +379,239 @@ public class DhLightingEngine
//======================//
// data source lighting //
//======================//
/** @author BuilderB0y */
public void bakeDataSourceSkyLight(FullDataSourceV2 dataSource, int maxSkyLight)
{
// create a cache of all the IDs which are completely transparent.
// FullDataPointIdMap is thread-safe with locks, and is also a map lookup,
// and both of these things add a bit of overhead which is not necessary
// in this context.
// note: since IDs map to both biomes and blocks, there can be more than
// one ID which corresponds to air.
BitSet airIDs = new BitSet(dataSource.mapping.size());
for (int id = 0, size = dataSource.mapping.size(); id < size; id++)
{
if (dataSource.mapping.getBlockStateWrapper(id).getOpacity() == 0)
{
airIDs.set(id, true);
}
}
for (int z = 0; z < FullDataSourceV2.WIDTH; z++)
{
for (int x = 0; x < FullDataSourceV2.WIDTH; x++)
{
LongArrayList dataPoints = dataSource.get(x, z);
if (dataPoints != null && !dataPoints.isEmpty())
{
// iterate through the data points in this column top-down
// until we reach light level 0 in some way. at this point,
// no more propagation needs to be performed for this column.
int size = dataPoints.size();
for (int index = 0; index < size; index++)
{
long point = dataPoints.getLong(index);
// if the data point in the column is transparent,
// then fill it with light and then propagate
// that light both horizontally and downwards.
if (airIDs.get(FullDataPointUtil.getId(point)))
{
int skylight;
if (index == 0)
{
// top-most data point in the column.
skylight = maxSkyLight;
}
else
{
// handle down propagation here. sort of.
// down propagation is also handled partially elsewhere.
// basically if the data point above is transparent,
// we copy its light level.
// otherwise, if the data point above is opaque,
// then no light can propagate downwards from it.
// therefore, this data point should be light level 0*
// and no more propagation needs to be performed for this column.
//
// *unless light propagates into it horizontally,
// but that is handled separately.
long above = dataPoints.getLong(index - 1);
if (airIDs.get(FullDataPointUtil.getId(above)))
{
skylight = FullDataPointUtil.getSkyLight(above);
}
else
{
continue;
}
}
// update the data point to contain the correct starting skylight level.
point = FullDataPointUtil.setSkyLight(point, skylight);
dataPoints.set(index, point);
// now for the propagation.
recursivelyLightAdjacentDataPoints(dataSource, airIDs, x, z, point);
}
}
}
}
}
// at this point, all transparent data points have been lit,
// but opaque ones still have light level 0.
// in this loop we make opaque data points copy the light level
// above them if, and only if, the data point above is translucent.
// with one exception: if the data point above is only partially translucent,
// we use a slightly different way of computing how much light it absorbed.
// this is how we handle water and ocean floors.
// note that this alternate logic assumes the
// data point above is being lit from the top.
// this is a fine assumption for water and oceans.
for (LongArrayList list : dataSource.dataPoints)
{
if (list != null)
{
for (int index = 0, size = list.size(); index < size; index++)
{
long dataPoint = list.getLong(index);
if (index == 0)
{
// top data point, assume "above" has the max sky light.
dataPoint = FullDataPointUtil.setSkyLight(dataPoint, maxSkyLight);
list.set(index, dataPoint);
}
else
{
// there is another data point above this one.
// check to see how opaque this data point is first.
// we will check the above one after that.
if (!airIDs.get(FullDataPointUtil.getId(dataPoint)))
{
// this data point is not transparent.
// it should be lit from above.
long above = list.getLong(index - 1);
int aboveLight = FullDataPointUtil.getSkyLight(above);
if (airIDs.get(FullDataPointUtil.getId(above)))
{
// the above data point is transparent,
// and does not absorb any light.
// its light level can be copied as-is.
dataPoint = FullDataPointUtil.setSkyLight(dataPoint, aboveLight);
list.set(index, dataPoint);
}
else
{
// determine how much light should be absorbed by this column
int absorption = dataSource.mapping.getBlockStateWrapper(FullDataPointUtil.getId(above)).getOpacity() * FullDataPointUtil.getHeight(above);
if (absorption < aboveLight)
{
// the above data point is partially translucent,
// and absorbs some light. however, it did not absorb
// enough light to bring the light level down to 0.
// so, the remaining light can still be copied.
dataPoint = FullDataPointUtil.setSkyLight(dataPoint, aboveLight - absorption);
list.set(index, dataPoint);
}
}
}
}
}
}
}
}
/** @author BuilderB0y */
public void recursivelyLightAdjacentDataPoints(
FullDataSourceV2 chunk,
BitSet airIDs,
int relativeX,
int relativeZ,
long dataPoint
)
{
int lightLevel = FullDataPointUtil.getSkyLight(dataPoint);
// early exit condition:
// in this case, propagating light is guaranteed to be 0 at adjacent positions,
// and therefore we do not need to waste time propagating it.
if (lightLevel <= 1)
{
return;
}
int minY = FullDataPointUtil.getBottomY(dataPoint);
int maxY = FullDataPointUtil.getHeight(dataPoint) + minY;
// try to propagate in all 4 directions.
for (int offsetIndex = 0; offsetIndex < ADJACENT_DIRECTION_OFFSETS.length; )
{
int adjacentX = relativeX + ADJACENT_DIRECTION_OFFSETS[offsetIndex++];
int adjacentZ = relativeZ + ADJACENT_DIRECTION_OFFSETS[offsetIndex++];
// check if the adjacent position is within the bounds of this data source...
if (adjacentX >= 0 && adjacentX < FullDataSourceV2.WIDTH && adjacentZ >= 0 && adjacentZ < FullDataSourceV2.WIDTH)
{
LongArrayList adjacentDataPoints = chunk.get(adjacentX, adjacentZ);
// ...and also check to make sure we have some data points
// (potentially transparent ones) to propagate through in the adjacent column.
if (adjacentDataPoints != null)
{
// try to find adjacent data points we can propagate into.
// we go top-down for this, which will be important for some
// later conditions.
int size = adjacentDataPoints.size();
for (int adjacentIndex = 0; adjacentIndex < size; adjacentIndex++)
{
long adjacentDataPoint = adjacentDataPoints.getLong(adjacentIndex);
int adjacentMinY = FullDataPointUtil.getBottomY(adjacentDataPoint);
int adjacentMaxY = FullDataPointUtil.getHeight(adjacentDataPoint) + adjacentMinY;
if (adjacentMinY >= maxY)
{
// if the adjacent data point is completely above this one,
// then there is no overlap between this one and the adjacent one,
// and therefore light cannot propagate here.
// try to propagate to the next data point down from the adjacent one.
continue;
}
else if (adjacentMaxY <= minY)
{
// if the adjacent data point is completely below this one,
// then it also has no overlap and can't propagate,
// but since we're going top-down, neither can any subsequent adjacent data points.
break;
}
else if (!airIDs.get(FullDataPointUtil.getId(adjacentDataPoint)))
{
// assume for now that we cannot propagate into non-transparent data points.
continue; // TODO how does this work with water? Do we care?
}
else
{
// now we can try to propagate.
int adjacentLightLevel = FullDataPointUtil.getSkyLight(adjacentDataPoint);
// if the resulting light level after propagation would INCREASE
// the light level of the adjacent data point, then propagate to it.
// otherwise, don't do that.
if (lightLevel - 1 > adjacentLightLevel)
{
adjacentDataPoint = FullDataPointUtil.setSkyLight(adjacentDataPoint, lightLevel - 1);
adjacentDataPoints.set(adjacentIndex, adjacentDataPoint);
// if propagation succeeded, recursively propagate again starting at the adjacent data point.
recursivelyLightAdjacentDataPoints(chunk, airIDs, adjacentX, adjacentZ, adjacentDataPoint);
}
}
}
}
}
}
}
//
// test
//
//===========//
@@ -467,7 +467,7 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
try
{
IChunkWrapper chunk = WRAPPER_FACTORY.createChunkWrapper(generatedObjectArray);
FullDataSourceV2 dataSource = LodDataBuilder.createGeneratedDataSource(chunk);
FullDataSourceV2 dataSource = LodDataBuilder.createFromChunk(chunk);
LodUtil.assertTrue(dataSource != null);
chunkDataConsumer.accept(dataSource);
}
@@ -22,6 +22,7 @@ package com.seibel.distanthorizons.core.level;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiChunkModifiedEvent;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.file.fullDatafile.DelayedFullDataSourceSaveCache;
import com.seibel.distanthorizons.core.generation.DhLightingEngine;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
@@ -32,6 +33,7 @@ import com.seibel.distanthorizons.core.sql.dto.BeaconBeamDTO;
import com.seibel.distanthorizons.core.sql.dto.ChunkHashDTO;
import com.seibel.distanthorizons.core.sql.repo.BeaconBeamRepo;
import com.seibel.distanthorizons.core.sql.repo.ChunkHashRepo;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector;
import org.apache.logging.log4j.Logger;
@@ -168,6 +170,12 @@ public abstract class AbstractDhLevel implements IDhLevel
private void onDataSourceSave(FullDataSourceV2 fullDataSource)
{
// block lights should have been populated at the chunkWrapper stage
// waiting to populate the data source's skylight at this stage prevents re-lighting and
// allows us to reduce cross-chunk lighting issues by lighting the whole 4x4 LOD at once
DhLightingEngine.INSTANCE.bakeDataSourceSkyLight(fullDataSource, this.hasSkyLight() ? LodUtil.MAX_MC_LIGHT : LodUtil.MIN_MC_LIGHT);
this.updateDataSourcesAsync(fullDataSource).thenRun(() ->
{
HashSet<DhChunkPos> updatedChunkPosSet = this.updatedChunkPosSetBySectionPos.remove(fullDataSource.getPos());
@@ -157,6 +157,7 @@ public class FullDataPointUtil
public static int getSkyLight(long data) { return (int) ((data >> SKY_LIGHT_OFFSET) & SKY_LIGHT_MASK); }
public static long setBlockLight(long data, byte blockLight) { return (data & ~((long) BLOCK_LIGHT_MASK << BLOCK_LIGHT_OFFSET) | (long) blockLight << BLOCK_LIGHT_OFFSET); }
public static long setSkyLight(long data, int skyLight) { return (data & ~((long) SKY_LIGHT_MASK << SKY_LIGHT_OFFSET) | (long) skyLight << SKY_LIGHT_OFFSET); }
public static String toString(long data) { return "[ID:" + getId(data) + ",Y:" + getBottomY(data) + ",Height:" + getHeight(data) + ",BlockLight:" + getBlockLight(data) + ",SkyLight:" + getSkyLight(data) + "]"; }
@@ -75,9 +75,11 @@ public interface IChunkWrapper extends IBindable
int getMinBlockX();
int getMinBlockZ();
void setIsDhLightCorrect(boolean isDhLightCorrect);
void setUseDhLighting(boolean useDhLighting);
boolean isLightCorrect();
void setIsDhBlockLightCorrect(boolean isDhLightCorrect);
void setIsDhSkyLightCorrect(boolean isDhLightCorrect);
boolean isDhBlockLightingCorrect();
boolean isDhSkyLightCorrect();
int getDhSkyLight(int relX, int relY, int relZ);
@@ -88,9 +90,6 @@ public interface IChunkWrapper extends IBindable
void setDhBlockLight(int relX, int relY, int relZ, int lightValue);
void clearDhBlockLighting();
int getBlockLight(int relX, int relY, int relZ);
int getSkyLight(int relX, int relY, int relZ);
/** Note: don't modify this array, it will only be generated once and then shared between uses */
ArrayList<DhBlockPos> getWorldBlockLightPosList();
@@ -160,85 +159,6 @@ public interface IChunkWrapper extends IBindable
}
/**
* Populates DH's saved lighting using MC's lighting engine.
* This is generally done in cases where MC's lighting is correct now, but may not be later (like when a chunk is unloading).
*
* @throws IllegalStateException if the chunk's lighting isn't valid. This is done to prevent accidentally baking broken lighting.
* @return true if the chunk's lighting was successfully populated, false otherwise
*/
@Deprecated
default boolean bakeDhLightingUsingMcLightingEngine(ILevelWrapper levelWrapper) throws IllegalStateException
{
if (!this.isLightCorrect())
{
return false;
}
//=======================//
// get lighting for each //
// relative block pos //
//=======================//
boolean lightingFound = false;
// if the level doesn't have sky lights, then this check can be ignored
// since all sky light values will be 0 anyway
boolean skyLightingFound = !levelWrapper.hasSkyLight();
for (int relX = 0; relX < LodUtil.CHUNK_WIDTH; relX++)
{
for (int relZ = 0; relZ < LodUtil.CHUNK_WIDTH; relZ++)
{
for (int y = this.getMinBuildHeight(); y < this.getMaxBuildHeight(); y++)
{
int skyLight = this.getSkyLight(relX, y, relZ);
this.setDhSkyLight(relX, y, relZ, skyLight);
int blockLight = this.getBlockLight(relX, y, relZ);
this.setDhBlockLight(relX, y, relZ, blockLight);
// MC defaults to max sky light and no block light, including underground blocks.
// If any position has something different then those default values, it's likely that the
// lighting was properly populated for at least part of the chunk
if (!lightingFound &&
(skyLight != LodUtil.MAX_MC_LIGHT || blockLight != LodUtil.MIN_MC_LIGHT))
{
lightingFound = true;
}
if (!skyLightingFound
&& skyLight != LodUtil.MIN_MC_LIGHT)
{
skyLightingFound = true;
}
}
}
}
//=================//
// validate result //
//=================//
// if no lighting was found or the sky is always black, the lighting is likely broken
if (!lightingFound || !skyLightingFound
// if lighting is no longer correct or doesn't match the saved values
// its very likely it broke halfway through and will need regenerating
|| !this.isLightCorrect()
|| this.getSkyLight(0, 0, 0) != this.getDhSkyLight(0,0,0)
|| this.getBlockLight(0, 0, 0) != this.getDhBlockLight(0,0,0))
{
return false;
}
// lighting is valid
this.setIsDhLightCorrect(true);
this.setUseDhLighting(true);
return true;
}
/**
* Converts a 3D position into a 1D array index. <br><br>
*
@@ -1,121 +0,0 @@
/*
* 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 testItems.lightingEngine;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
import tests.LightingEngineTest;
import java.awt.*;
/**
* @see LightingEngineTest
* @see LightingTestChunkWrapper
*/
public class LightingTestBlockStateWrapper implements IBlockStateWrapper
{
private int opacity = -1;
private int lightEmission = -1;
//=============//
// constructor //
//=============//
public LightingTestBlockStateWrapper(int opacity, int lightEmission)
{
this.opacity = opacity;
this.lightEmission = lightEmission;
}
//=================//
// wrapper methods //
//=================//
@Override
public int getOpacity() { return this.opacity; }
@Override
public int getLightEmission() { return this.lightEmission; }
//===============//
// unimplemented //
//===============//
//@Override
//public boolean equals(Object obj)
//{
// if (this == obj)
// {
// return true;
// }
//
// if (obj == null || this.getClass() != obj.getClass())
// {
// return false;
// }
//
// BlockStateTestWrapper that = (BlockStateTestWrapper) obj;
// // the serialized value is used so we can test the contents instead of the references
// return Objects.equals(this.getSerialString(), that.getSerialString());
//}
//@Override
//public int hashCode() { return this.hashCode; }
//@Override
//public String toString() { return this.getSerialString(); }
@Override
public String getSerialString() { throw new UnsupportedOperationException("Not Implemented"); }
@Override
public Object getWrappedMcObject() { throw new UnsupportedOperationException("Not Implemented"); }
@Override
public boolean isAir() { throw new UnsupportedOperationException("Not Implemented"); }
@Override
public boolean isSolid() { throw new UnsupportedOperationException("Not Implemented"); }
@Override
public boolean isLiquid() { throw new UnsupportedOperationException("Not Implemented"); }
@Override
public byte getMaterialId() { throw new UnsupportedOperationException("Not Implemented"); }
@Override
public boolean isBeaconBlock() { throw new UnsupportedOperationException("Not Implemented"); }
@Override
public boolean isBeaconBaseBlock() { throw new UnsupportedOperationException("Not Implemented"); }
@Override
public Color getMapColor() { throw new UnsupportedOperationException("Not Implemented"); }
@Override
public boolean isBeaconTintBlock() { throw new UnsupportedOperationException("Not Implemented"); }
@Override
public Color getBeaconTintColor() { throw new UnsupportedOperationException("Not Implemented"); }
}
@@ -1,435 +0,0 @@
/*
* 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 testItems.lightingEngine;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.objects.DataCorruptedException;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.ChunkLightStorage;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IMutableBlockPosWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import org.apache.logging.log4j.Logger;
import tests.LightingEngineTest;
import java.io.*;
import java.util.ArrayList;
/**
* @see LightingEngineTest
* @see LightingTestBlockStateWrapper
*/
public class LightingTestChunkWrapper implements IChunkWrapper
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
// chunk values //
private final DhChunkPos chunkPos;
private ChunkLightStorage blockLightStorage;
private ChunkLightStorage skyLightStorage;
private ArrayList<DhBlockPos> blockLightPosList = null;
private boolean useDhLighting;
private int minNonEmptyHeight = Integer.MIN_VALUE;
private int maxNonEmptyHeight = Integer.MAX_VALUE;
// test values //
private final Int2IntOpenHashMap blockOpacityStorage;
private final Int2IntOpenHashMap blockEmissionStorage;
private final int[][] solidHeightMap = new int[LodUtil.CHUNK_WIDTH][LodUtil.CHUNK_WIDTH];
private final int[][] lightBlockingHeightMap = new int[LodUtil.CHUNK_WIDTH][LodUtil.CHUNK_WIDTH];
//=============//
// constructor //
//=============//
public LightingTestChunkWrapper(IChunkWrapper chunkWrapper)
{
this.chunkPos = chunkWrapper.getChunkPos();
this.blockOpacityStorage = new Int2IntOpenHashMap();
this.blockEmissionStorage = new Int2IntOpenHashMap();
this.blockLightPosList = new ArrayList<>();
for (int x = 0; x < LodUtil.CHUNK_WIDTH; x++)
{
for (int z = 0; z < LodUtil.CHUNK_WIDTH; z++)
{
for (int y = this.getMinBuildHeight(); y < this.getMaxBuildHeight(); y++)
{
IBlockStateWrapper block = chunkWrapper.getBlockState(x,y,z);
int opacity = block.getOpacity();
if (opacity >= LodUtil.BLOCK_FULLY_OPAQUE)
{
opacity = 3;
}
this.blockOpacityStorage.put(new DhBlockPos(x, y, z).hashCode(), opacity);
this.blockEmissionStorage.put(new DhBlockPos(x, y, z).hashCode(), block.getLightEmission());
if (block.getLightEmission() != 0)
{
this.blockLightPosList.add(new DhBlockPos(x,y,z));
}
}
this.lightBlockingHeightMap[x][z] = chunkWrapper.getLightBlockingHeightMapValue(x, z);
this.solidHeightMap[x][z] = chunkWrapper.getSolidHeightMapValue(x, z);
}
}
}
//===============//
// file handling //
//===============//
public LightingTestChunkWrapper(DhChunkPos pos, File saveFile) throws DataCorruptedException
{
this.chunkPos = pos;
this.blockOpacityStorage = new Int2IntOpenHashMap();
this.blockEmissionStorage = new Int2IntOpenHashMap();
this.blockLightPosList = new ArrayList<>();
try(FileInputStream inputStream = new FileInputStream(saveFile);
BufferedInputStream bufferedStream = new BufferedInputStream(inputStream);
DataInputStream stream = new DataInputStream(bufferedStream))
{
for (int x = 0; x < LodUtil.CHUNK_WIDTH; x++)
{
for (int z = 0; z < LodUtil.CHUNK_WIDTH; z++)
{
for (int y = this.getMinBuildHeight(); y < this.getMaxBuildHeight(); y++)
{
this.blockOpacityStorage.put(new DhBlockPos(x, y, z).hashCode(), stream.readInt());
int blockEmission = stream.readInt();
this.blockEmissionStorage.put(new DhBlockPos(x, y, z).hashCode(), blockEmission);
if (blockEmission != 0)
{
this.blockLightPosList.add(new DhBlockPos(x,y,z));
}
}
if (stream.readChar() != ';')
{
throw new DataCorruptedException("bad height map");
}
this.lightBlockingHeightMap[x][z] = stream.readInt();
this.solidHeightMap[x][z] = stream.readInt();
if (stream.readChar() != '\n')
{
throw new DataCorruptedException(" bad col ending");
}
}
}
}
catch (IOException e)
{
LOGGER.error("Unable to write to file: ["+e.getMessage()+"].", e);
}
}
public void writeToFile(File file)
{
try(FileOutputStream fileStream = new FileOutputStream(file);
BufferedOutputStream bufferedStream = new BufferedOutputStream(fileStream);
DataOutputStream stream = new DataOutputStream(bufferedStream))
{
for (int x = 0; x < LodUtil.CHUNK_WIDTH; x++)
{
for (int z = 0; z < LodUtil.CHUNK_WIDTH; z++)
{
for (int y = this.getMinBuildHeight(); y < this.getMaxBuildHeight(); y++)
{
stream.writeInt(this.blockOpacityStorage.get(new DhBlockPos(x, y, z).hashCode()));
stream.writeInt(this.blockEmissionStorage.get(new DhBlockPos(x, y, z).hashCode()));
}
stream.writeChar(';');
stream.writeInt(this.lightBlockingHeightMap[x][z]);
stream.writeInt(this.solidHeightMap[x][z]);
stream.writeChar('\n');
}
}
stream.flush();
}
catch (IOException e)
{
LOGGER.error("Unable to write to file: ["+e.getMessage()+"].", e);
}
}
/**
* Can be added into {@link com.seibel.distanthorizons.core.api.internal.SharedApi#applyChunkUpdate(IChunkWrapper, ILevelWrapper, boolean)}
* to save chunks to file for future testing.
*/
public void tryConvertingAndSavingChunkWrapper(IChunkWrapper chunkWrapper)
{
try
{
File chunkFile = new File(LightingEngineTest.TEST_DATA_PATH + "/" + chunkWrapper.getChunkPos().toString());
if (!chunkFile.exists())
{
LightingTestChunkWrapper testWrapper = new LightingTestChunkWrapper(chunkWrapper);
testWrapper.writeToFile(chunkFile);
}
}
catch (Exception e)
{
LOGGER.error(e.getMessage(), e);
}
}
//===============//
// chunk methods //
//===============//
@Override
public int getHeight() { return 255; }
@Override
public int getMinBuildHeight() { return -64; }
@Override
public int getMaxBuildHeight() { return 255; }
@Override
public int getMinNonEmptyHeight()
{
if (this.minNonEmptyHeight != Integer.MIN_VALUE)
{
return this.minNonEmptyHeight;
}
// default if every section is empty or missing
this.minNonEmptyHeight = this.getMinBuildHeight();
// determine the lowest empty section (bottom up)
int maxYHeight = this.getMaxBuildHeight();
for (int y = this.getMinBuildHeight(); y < maxYHeight; y++)
{
if (this.blockOpacityStorage.get(new DhBlockPos(0, y, 0).hashCode()) != 0)
{
// -16 to simulate having to populate the full chunk section
this.minNonEmptyHeight = Math.min(y - 16, maxYHeight);
break;
}
}
return this.minNonEmptyHeight;
}
@Override
public int getMaxNonEmptyHeight()
{
if (this.maxNonEmptyHeight != Integer.MAX_VALUE)
{
return this.maxNonEmptyHeight;
}
// default if every section is empty or missing
this.maxNonEmptyHeight = this.getMaxBuildHeight();
// determine the highest empty section (top down)
int minYHeight = this.getMinBuildHeight();
for (int y = this.getMaxBuildHeight(); y >= minYHeight; y--)
{
if (this.blockOpacityStorage.get(new DhBlockPos(0, y, 0).hashCode()) != 0)
{
// -16 to simulate having to populate the full chunk section
this.maxNonEmptyHeight = Math.max(y - 16, minYHeight);
break;
}
}
return this.maxNonEmptyHeight;
}
@Override
public int getSolidHeightMapValue(int xRel, int zRel) { return this.solidHeightMap[xRel][zRel]; }
@Override
public int getLightBlockingHeightMapValue(int xRel, int zRel) { return this.lightBlockingHeightMap[xRel][zRel]; }
@Override
public IBiomeWrapper getBiome(int relX, int relY, int relZ) { throw new UnsupportedOperationException("Not implemented"); }
@Override
public DhChunkPos getChunkPos() { return this.chunkPos; }
@Override
public int getMaxBlockX() { return 0; }
@Override
public int getMaxBlockZ() { return 0; }
@Override
public int getMinBlockX() { return LodUtil.CHUNK_WIDTH; }
@Override
public int getMinBlockZ() { return LodUtil.CHUNK_WIDTH; }
@Override
public void setIsDhLightCorrect(boolean isDhLightCorrect) { }
@Override
public void setUseDhLighting(boolean useDhLighting) { this.useDhLighting = useDhLighting; }
@Override
public boolean isLightCorrect() { return false; }
@Override
public int getDhBlockLight(int relX, int y, int relZ)
{
this.throwIndexOutOfBoundsIfRelativePosOutsideChunkBounds(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.getBlockLightStorage().set(relX, y, relZ, lightValue);
}
private ChunkLightStorage getBlockLightStorage()
{
if (this.blockLightStorage == null)
{
this.blockLightStorage = new ChunkLightStorage(
// +/- 16 is to fix an issue with the test chunk where the storage isn't big enough,
// James probably just screwed up the min/max height slightly
this.getMinBuildHeight() - 16, this.getMaxBuildHeight() + 16,
// positions above and below the handled area should be unlit
LodUtil.MIN_MC_LIGHT, LodUtil.MIN_MC_LIGHT);
}
return this.blockLightStorage;
}
@Override
public void clearDhBlockLighting() { throw new UnsupportedOperationException("Not implemented"); }
@Override
public int getDhSkyLight(int relX, int y, int relZ)
{
this.throwIndexOutOfBoundsIfRelativePosOutsideChunkBounds(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.getSkyLightStorage().set(relX, y, relZ, lightValue);
}
@Override
public void clearDhSkyLighting() { throw new UnsupportedOperationException("Not implemented"); }
private ChunkLightStorage getSkyLightStorage()
{
if (this.skyLightStorage == null)
{
this.skyLightStorage = new ChunkLightStorage(
// +/- 16 is to fix an issue with the test chunk where the storage isn't big enough,
// James probably just screwed up the min/max height slightly
this.getMinBuildHeight() - 16, this.getMaxBuildHeight() + 16,
// positions above should be lit but positions below should be unlit
LodUtil.MAX_MC_LIGHT, LodUtil.MIN_MC_LIGHT);
}
return this.skyLightStorage;
}
@Override
public int getBlockLight(int relX, int y, int relZ)
{
this.throwIndexOutOfBoundsIfRelativePosOutsideChunkBounds(relX, y, relZ);
return this.getBlockLightStorage().get(relX, y, relZ);
}
@Override
public int getSkyLight(int relX, int y, int relZ)
{
this.throwIndexOutOfBoundsIfRelativePosOutsideChunkBounds(relX, y, relZ);
return this.getSkyLightStorage().get(relX, y, relZ);
}
@Override
public ArrayList<DhBlockPos> getWorldBlockLightPosList() { return this.blockLightPosList; }
@Override
public boolean doNearbyChunksExist() { return false; }
@Override
public String toString() { return this.chunkPos.toString(); }
@Override
public IBlockStateWrapper getBlockState(int relX, int relY, int relZ)
{
this.throwIndexOutOfBoundsIfRelativePosOutsideChunkBounds(relX, relY, relZ);
int opacity = this.blockOpacityStorage.get(new DhBlockPos(relX, relY, relZ).hashCode());
int lightEmission = this.blockEmissionStorage.get(new DhBlockPos(relX, relY, relZ).hashCode());
return new LightingTestBlockStateWrapper(opacity, lightEmission);
}
@Override
public IBlockStateWrapper getBlockState(int relX, int relY, int relZ, IMutableBlockPosWrapper mcBlockPos, IBlockStateWrapper guess)
{ return this.getBlockState(relX, relY, relZ); }
@Override
public IMutableBlockPosWrapper getMutableBlockPosWrapper()
{
return null;
}
@Override
public boolean isStillValid() { return true; }
}
@@ -1,96 +0,0 @@
/*
* 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 tests;
import com.seibel.distanthorizons.core.generation.DhLightingEngine;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.objects.DataCorruptedException;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import testItems.lightingEngine.LightingTestChunkWrapper;
import java.io.File;
import java.util.ArrayList;
/**
* Can be used to A/B Test lighting engine performance changes. <br><br>
*
* normal - chunks: [1595], total Time: [1490], avg time: [0.9341692789968652] <br>
* only surface light prop - chunks: [1595], total Time: [984], avg time: [0.6169278996865204] <br>
*
* @author James Seibel
* @version 2024-6-11
*/
public class LightingEngineTest
{
/**
* There should be test data in the following core repo folder: <br>
* <code> Core\_Misc Files\test files\Lighting engine test chunk data.7z </code>
*/
public static final String TEST_DATA_PATH = "C:/Users/James_Seibel/Desktop/test chunk data";
//@Test
public void TestLightingEngine() throws DataCorruptedException
{
long totalNanoTime = 0;
int chunkCount = 0;
File testFolder = new File(TEST_DATA_PATH);
File[] chunkFiles = testFolder.listFiles();
for (int i = 0; i < chunkFiles.length; i++)
{
// chunk file parsing //
File chunkFile = chunkFiles[i];
String fileName = chunkFile.getName(); // C[0,-3]
fileName = fileName.replace("C", "").replace("[", "").replace("]", "");
int xPos = Integer.parseInt(fileName.split(",")[0]);
int zPos = Integer.parseInt(fileName.split(",")[1]);
DhChunkPos pos = new DhChunkPos(xPos, zPos);
if (i % 100 == 0)
{
System.out.println(i + "/" + chunkFiles.length);
}
LightingTestChunkWrapper chunk = new LightingTestChunkWrapper(pos, chunkFile);
chunkCount++;
ArrayList<IChunkWrapper> nearbyChunkList = new ArrayList<>(1);
nearbyChunkList.add(chunk);
// lighting //
long startTime = System.nanoTime();
DhLightingEngine.INSTANCE.lightChunk(chunk, nearbyChunkList, LodUtil.MAX_MC_LIGHT);
long endTime = System.nanoTime();
totalNanoTime += endTime - startTime;
}
long timeMs = totalNanoTime / 1_000_000;
System.out.println("chunks: ["+chunkCount+"], total Time: ["+timeMs+"], avg time: ["+(timeMs/(double)chunkCount)+"]");
}
}