Attempt to improve LOD building speed and reduce broken lighting on servers

This commit is contained in:
James Seibel
2024-08-03 17:11:18 -05:00
parent 801a126de0
commit 8abefdcfd5
5 changed files with 134 additions and 20 deletions
@@ -24,6 +24,7 @@ import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.generation.DhLightingEngine;
import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.level.IDhServerLevel;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.logging.f3.F3Screen;
import com.seibel.distanthorizons.core.pos.DhBlockPos2D;
@@ -31,6 +32,7 @@ import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
import com.seibel.distanthorizons.core.sql.dto.BeaconBeamDTO;
import com.seibel.distanthorizons.core.sql.repo.AbstractDhRepo;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.TimerUtil;
import com.seibel.distanthorizons.core.util.objects.Pair;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
@@ -46,6 +48,7 @@ import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.atomic.AtomicInteger;
/** Contains code and variables used by both {@link ClientApi} and {@link ServerApi} */
public class SharedApi
@@ -328,29 +331,44 @@ public class SharedApi
}
// 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 onlyUseDhLighting = Config.Client.Advanced.LodBuilding.onlyUseDhLightingEngine.get();
if (!onlyUseDhLighting && chunkWrapper.isLightCorrect())
// 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)
{
try
// 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
chunkWrapper.bakeDhLightingUsingMcLightingEngine();
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();
}
}
catch (IllegalStateException e)
// something went wrong during the baking process so we have to generate the lighting ourselves
if (!chunkLightPopulated)
{
LOGGER.warn("Chunk light baking error: " + e.getMessage(), e);
DhLightingEngine.INSTANCE.lightChunk(chunkWrapper, nearbyChunkList, dhLevel.hasSkyLight() ? LodUtil.MAX_MC_LIGHT : LodUtil.MIN_MC_LIGHT);
}
}
else
{
// generate the chunk's lighting, using neighboring chunks if present
DhLightingEngine.INSTANCE.lightChunk(chunkWrapper, nearbyChunkList, dhLevel.hasSkyLight() ? 15 : 0);
DhLightingEngine.INSTANCE.lightChunk(chunkWrapper, nearbyChunkList, dhLevel.hasSkyLight() ? LodUtil.MAX_MC_LIGHT : LodUtil.MIN_MC_LIGHT);
}
// get this chunk's active beacons
List<BeaconBeamDTO> beaconBeamList = chunkWrapper.getAllActiveBeacons(nearbyChunkList);
dhLevel.setBeaconBeamsForChunk(chunkWrapper.getChunkPos(), beaconBeamList);
@@ -781,8 +781,10 @@ public class Config
+ "")
.build();
@Deprecated
public static ConfigEntry<Boolean> onlyUseDhLightingEngine = new ConfigEntry.Builder<Boolean>()
.set(false)
.setAppearance(EConfigEntryAppearance.ONLY_IN_API)
.comment(""
+ "If false LODs will be lit by Minecraft's lighting engine when possible \n"
+ "and fall back to the DH lighting engine only when necessary. \n"
@@ -141,6 +141,25 @@ public class ChunkLightStorage
lightSection.set(x, y, z, lightLevel);
}
public void clear()
{
if (this.lightSections != null)
{
for (int i = 0; i < this.lightSections.length; i++)
{
LightSection section = this.lightSections[i];
if (section != null)
{
section.constantValue = LodUtil.MIN_MC_LIGHT;
if (section.data != null)
{
Arrays.fill(section.data, 0L);
}
}
}
}
}
//================//
@@ -25,6 +25,7 @@ import com.seibel.distanthorizons.core.pos.DhBlockPos2D;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.sql.dto.BeaconBeamDTO;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.coreapi.ModInfo;
import com.seibel.distanthorizons.coreapi.interfaces.dependencyInjection.IBindable;
import com.seibel.distanthorizons.core.util.LodUtil;
@@ -79,9 +80,11 @@ public interface IChunkWrapper extends IBindable
int getDhSkyLight(int relX, int relY, int relZ);
void setDhSkyLight(int relX, int relY, int relZ, int lightValue);
void clearDhSkyLighting();
int getDhBlockLight(int relX, int relY, int relZ);
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);
@@ -150,29 +153,77 @@ public interface IChunkWrapper extends IBindable
* 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
*/
default void bakeDhLightingUsingMcLightingEngine() throws IllegalStateException
@Deprecated
default boolean bakeDhLightingUsingMcLightingEngine(ILevelWrapper levelWrapper) throws IllegalStateException
{
if (!this.isLightCorrect())
{
throw new IllegalStateException("Unable to bake lighting for for chunk [" + this.getChunkPos() + "], Minecraft lighting not valid.");
return false;
}
// get the lighting for every relative block pos
//=======================//
// 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++)
{
this.setDhSkyLight(relX, y, relZ, this.getSkyLight(relX, y, relZ));
this.setDhBlockLight(relX, y, relZ, this.getBlockLight(relX, y, relZ));
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;
}
@@ -230,17 +281,37 @@ public interface IChunkWrapper extends IBindable
int primeBlockMultiplier = 227;
int primeBiomeMultiplier = 701;
int minBuildHeight = this.getMinBuildHeight();
int maxBuildHeight = this.getMaxBuildHeight();
int minBuildHeight = this.getMaxNonEmptyHeight();
int maxBuildHeight = this.getMinNonEmptyHeight();
// most blocks (only some blocks are sampled since checking every block is a very slow operation)
for (int x = 0; x < LodUtil.CHUNK_WIDTH; x+=2)
{
for (int z = 0; z < LodUtil.CHUNK_WIDTH; z+=2)
{
for (int y = minBuildHeight; y < maxBuildHeight; y+=8)
{
hash = (hash * primeBlockMultiplier) + this.getBlockState(x, y, z).hashCode();
hash = (hash * primeBiomeMultiplier) + this.getBiome(x, y, z).hashCode();
}
}
}
// surface (this should cover most cases for when users modify chunks)
for (int x = 0; x < LodUtil.CHUNK_WIDTH; x++)
{
for (int z = 0; z < LodUtil.CHUNK_WIDTH; z++)
{
for (int y = minBuildHeight; y < maxBuildHeight; y++)
int lightBlockingY = this.getLightBlockingHeightMapValue(x, z);
hash = (hash * primeBlockMultiplier) + this.getBlockState(x, lightBlockingY, z).hashCode();
hash = (hash * primeBiomeMultiplier) + this.getBiome(x, lightBlockingY, z).hashCode();
int solidY = this.getSolidHeightMapValue(x, z);
if (solidY != lightBlockingY)
{
hash = (hash * primeBlockMultiplier) + this.getBlockState(x, y, z).hashCode();
hash = (hash * primeBiomeMultiplier) + this.getBiome(x, y, z).hashCode();
hash = (hash * primeBlockMultiplier) + this.getBlockState(x, solidY, z).hashCode();
hash = (hash * primeBiomeMultiplier) + this.getBiome(x, solidY, z).hashCode();
}
}
}
@@ -350,6 +350,8 @@ public class LightingTestChunkWrapper implements IChunkWrapper
}
return this.blockLightStorage;
}
@Override
public void clearDhBlockLighting() { throw new UnsupportedOperationException("Not implemented"); }
@Override
@@ -364,6 +366,8 @@ public class LightingTestChunkWrapper implements IChunkWrapper
this.throwIndexOutOfBoundsIfRelativePosOutsideChunkBounds(relX, y, relZ);
this.getSkyLightStorage().set(relX, y, relZ, lightValue);
}
@Override
public void clearDhSkyLighting() { throw new UnsupportedOperationException("Not implemented"); }
private ChunkLightStorage getSkyLightStorage()
{