Make down sampling average values instead of grabbing the value closest to -inf

This commit is contained in:
James Seibel
2024-03-03 19:17:24 -06:00
parent a734bb6a69
commit 0f04453134
3 changed files with 270 additions and 35 deletions
@@ -30,6 +30,7 @@ import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.util.FullDataPointUtil;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.RenderDataPointUtil;
import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataOutputStream;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.coreapi.ModInfo;
@@ -41,10 +42,10 @@ import java.util.Arrays;
import java.util.concurrent.locks.ReentrantLock;
/**
* This data source contains every datapoint over its given {@link DhSectionPos}.
*
* This data source contains every datapoint over its given {@link DhSectionPos}. <br><br>
*
* TODO create a child object that extends AutoClosable
* that can be pooled so we don't have GC overhead
* that can be pooled to reduce GC overhead
*
* @see FullDataPointUtil
* @see CompleteFullDataSource
@@ -52,6 +53,8 @@ import java.util.concurrent.locks.ReentrantLock;
public class NewFullDataSource implements IDataSource<IDhLevel>
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
/** useful for debugging, but can slow down update operations quite a bit due to being called so often. */
private static final boolean RUN_UPDATE_DEV_VALIDATION = false; //ModInfo.IS_DEV_BUILD;
/** measured in data columns */
public static final int WIDTH = 64;
@@ -76,7 +79,10 @@ public class NewFullDataSource implements IDataSource<IDhLevel>
*/
public byte[] columnGenerationSteps;
/** stored x/z, y */
/**
* stored x/z, y
* The y data should be sorted from bottom to top
*/
public long[][] dataPoints;
private boolean isEmpty;
@@ -154,6 +160,15 @@ public class NewFullDataSource implements IDataSource<IDhLevel>
public boolean update(NewFullDataSource inputDataSource, @Nullable IDhLevel level) { return this.update(inputDataSource); }
public boolean update(NewFullDataSource inputDataSource)
{
// shouldn't happen, but James saw it happen once
if (inputDataSource.mapping.getMaxValidId() == 0)
{
LOGGER.warn("Invalid mapping given from input update data source at pos: ["+inputDataSource.pos+"], skipping update.");
return false;
}
byte thisDetailLevel = this.pos.getDetailLevel();
byte inputDetailLevel = inputDataSource.pos.getDetailLevel();
@@ -263,38 +278,184 @@ public class NewFullDataSource implements IDataSource<IDhLevel>
{
for (int z = 0; z < WIDTH; z += 2)
{
int inputIndex = relativePosToIndex(x, z);
long[] mergedInputDataArray = mergeInputTwoByTwoDataColumn(inputDataSource, x, z);
byte inputGenStep = inputDataSource.columnGenerationSteps[0]; // TODO
long[] inputDataArray = inputDataSource.dataPoints[inputIndex];
if (inputDataArray != null)
int recipientX = (x / 2) + recipientOffsetX;
int recipientZ = (z / 2) + recipientOffsetZ;
int recipientIndex = relativePosToIndex(recipientX, recipientZ);
long[] oldDataArray = this.dataPoints[recipientIndex];
this.columnGenerationSteps[recipientIndex] = inputGenStep;
this.dataPoints[recipientIndex] = mergedInputDataArray;
this.remapDataColumn(recipientIndex, remappedIds);
// we only need to see if the data was changed in one column
if (!dataChanged)
{
byte inputGenStep = inputDataSource.columnGenerationSteps[inputIndex];
// TODO downsample instad of grabbing the column nearest to (-inf, -inf)
int recipientX = (x / 2) + recipientOffsetX;
int recipientZ = (z / 2) + recipientOffsetZ;
int recipientIndex = relativePosToIndex(recipientX, recipientZ);
long[] oldDataArray = this.dataPoints[recipientIndex];
this.columnGenerationSteps[recipientIndex] = inputGenStep;
this.dataPoints[recipientIndex] = inputDataArray;
this.remapDataColumn(recipientIndex, remappedIds);
// we only need to see if the data was changed in one column
if (!dataChanged)
{
// needs to be done after the ID's have been remapped otherwise the ID's won't match even if the data is the same
dataChanged = areDataColumnsDifferent(oldDataArray, this.dataPoints[recipientIndex]);
}
this.isEmpty = false;
// needs to be done after the ID's have been remapped otherwise the ID's won't match even if the data is the same
dataChanged = areDataColumnsDifferent(oldDataArray, this.dataPoints[recipientIndex]);
}
this.isEmpty = false;
}
}
return dataChanged;
}
private static long[] mergeInputTwoByTwoDataColumn(NewFullDataSource inputDataSource, int x, int z)
{
ArrayList<Long> newColumnList = new ArrayList<>();
int[] currentDatapointIndex = new int[] { 0, 0, 0, 0 };
int lastId = 0;
byte lastBlockLight = 0;
byte lastSkyLight = 0;
int height = 0;
int minY = 0;
for (int blockY = 0; blockY < RenderDataPointUtil.MAX_WORLD_Y_SIZE; blockY++, height++)
{
//if (x == 38 && z == 2 && blockY == 65+72) // nether portal
if (x == 32 && z == 0 && blockY == 138) // glowstone
{
int k = 0; // TODO remove, just for testing
}
// if each column has reached the end of their data, nothing more needs to be done
if (currentDatapointIndex[0] == -1
&& currentDatapointIndex[1] == -1
&& currentDatapointIndex[2] == -1
&& currentDatapointIndex[3] == -1
)
{
break;
}
long[] datapointsForYSlice = new long[4];
// scary double loop but,
// this will only ever loop 4 times,
// once for each of the 4 input columns
int colIndex = 0;
for (int inputX = x; inputX < x +2; inputX++)
{
for (int inputZ = z; inputZ < z + 2; inputZ++, colIndex++)
{
long[] inputDataArray = inputDataSource.dataPoints[relativePosToIndex(inputX, inputZ)];
if (inputDataArray == null || inputDataArray.length == 0)
{
currentDatapointIndex[colIndex] = -1;
continue;
}
int dataPointIndex = currentDatapointIndex[colIndex];
if (dataPointIndex == -1)
{
// went over the end
continue;
}
long datapoint = inputDataArray[dataPointIndex];
int datapointMinY = FullDataPointUtil.getBottomY(datapoint);
int numbOfBlocksTall = FullDataPointUtil.getHeight(datapoint);
int datapointMaxY = (datapointMinY + numbOfBlocksTall);
// check if y position is inside this datapoint
if (blockY < datapointMinY)
{
// this y-slice is below this datapoint, nothing can be added
continue;
}
else if (blockY >= datapointMaxY)
{
// this y-slice is above this datapoint,
// try the next data point
int newDatapointIndex = currentDatapointIndex[colIndex] + 1;
if (newDatapointIndex >= inputDataArray.length)
{
// went to far, no additional data present
newDatapointIndex = -1;
}
currentDatapointIndex[colIndex] = newDatapointIndex;
// try again with the next data point
inputZ--;
colIndex--;
continue;
}
datapointsForYSlice[colIndex] = datapoint;
}
}
int[] mergeIds = new int[4];
int[] mergeBlockLights = new int[4];
int[] mergeSkyLights = new int[4];
for (int i = 0; i < 4; i++)
{
mergeIds[i] = FullDataPointUtil.getId(datapointsForYSlice[i]);
mergeBlockLights[i] = FullDataPointUtil.getBlockLight(datapointsForYSlice[i]);
mergeSkyLights[i] = FullDataPointUtil.getSkyLight(datapointsForYSlice[i]);
}
// determine the most common values for this slice
int id = determineMostValueInColumnSlice(mergeIds, inputDataSource.mapping);
byte blockLight = (byte) determineAverageValueInColumnSlice(mergeBlockLights);
byte skyLight = (byte) determineAverageValueInColumnSlice(mergeSkyLights);
// if this slice is different then the last one, create a new one
if (id != lastId
// block and sky light might not be necessary
|| blockLight != lastBlockLight
|| skyLight != lastSkyLight)
{
if (height != 0)
{
newColumnList.add(FullDataPointUtil.encode(lastId, height, minY, lastBlockLight, lastSkyLight));
}
lastId = id;
lastBlockLight = blockLight;
lastSkyLight = skyLight;
height = 0;
minY = blockY;
}
}
// add the last slice if present
if (height != 0)
{
newColumnList.add(FullDataPointUtil.encode(lastId, height, minY, lastBlockLight, lastSkyLight));
}
long[]mergedInputDataArray = new long[newColumnList.size()];
for (int i = 0; i < mergedInputDataArray.length; i++)
{
mergedInputDataArray[i] = newColumnList.get(i);
}
return mergedInputDataArray;
}
/**
* Only update the ID once it's been added to this data source.
* Updating the incoming data source will cause issues if it is applied
@@ -323,6 +484,72 @@ public class NewFullDataSource implements IDataSource<IDhLevel>
return (newArrayHash != oldArrayHash);
}
}
/** @param mapping can be included to ignore air ID's, otherwise all 4 values are treated equally */
private static int determineMostValueInColumnSlice(int[] sliceArray, @Nullable FullDataPointIdMap mapping)
{
if (RUN_UPDATE_DEV_VALIDATION)
{
LodUtil.assertTrue(sliceArray.length == 4, "Column Slice should only contain 4 values.");
}
int value0 = sliceArray[0];
int count0 = 0;
int value1 = sliceArray[1];
int count1 = 0;
int value2 = sliceArray[2];
int count2 = 0;
int value3 = sliceArray[3];
int count3 = 0;
// count the occurrences of each value
for (int i = 0; i < 4; i++)
{
int value = sliceArray[i];
if (mapping != null && mapping.getBlockStateWrapper(value).isAir())
{
// always overwrite air to prevent holes in hollow structures
continue;
}
if (value == value0)
count0++;
else if (value == value1)
count1++;
else if (value == value2)
count2++;
else
count3++;
}
// return the most common occurance
int maxCount = Math.max(count0, Math.max(count1, Math.max(count2, count3)));
if (maxCount == count0)
// if the max count is 1 then we'll just go with the first column
return value0;
else if (maxCount == count1)
return value1;
else if (maxCount == count2)
return value2;
else
return value3;
}
private static int determineAverageValueInColumnSlice(int[] sliceArray)
{
if (RUN_UPDATE_DEV_VALIDATION)
{
LodUtil.assertTrue(sliceArray.length == 4, "Column Slice should only contain 4 values.");
}
int value = 0;
for (int i = 0; i < 4; i++)
{
value += sliceArray[i];
}
value /= 4;
return value;
}
@@ -377,10 +604,9 @@ public class NewFullDataSource implements IDataSource<IDhLevel>
this.columnGenerationSteps[index] = worldGenStep.value;
// validate the incoming ID's
// shouldn't normally happen and can be disabled for release builds
if (ModInfo.IS_DEV_BUILD)
if (RUN_UPDATE_DEV_VALIDATION)
{
// validate the incoming ID's
int maxValidId = this.mapping.getMaxValidId();
for (int i = 0; i < longArray.length; i++)
{
@@ -188,6 +188,16 @@ public class LodDataBuilder
}
longs.add(FullDataPointUtil.encode(mappedId, lastY - y, y + 1 - chunkWrapper.getMinBuildHeight(), blockLight, skyLight));
// reverse the array so index 0 is the lowest,
// this is necessary for later logic
// source: https://stackoverflow.com/questions/2137755/how-do-i-reverse-an-int-array-in-java
for(int i = 0; i < longs.size() / 2; i++)
{
long temp = longs.getLong(i);
longs.set(i, longs.getLong(longs.size() - i - 1));
longs.set(longs.size() - i - 1, temp);
}
dataSource.setSingleColumn(longs.toLongArray(),
chunkX + chunkOffsetX,
chunkZ + chunkOffsetZ,
@@ -53,15 +53,14 @@ public class NewFullDataFileHandler
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
private static final int NUMBER_OF_PARENT_UPDATE_TASKS_PER_THREAD = 20;
private static final int NUMBER_OF_PARENT_UPDATE_TASKS_PER_THREAD = 50;
/** how many parent update tasks can be in the queue at once */
private static final int MAX_PARENT_UPDATE_TASK_COUNT = NUMBER_OF_PARENT_UPDATE_TASKS_PER_THREAD * Config.Client.Advanced.MultiThreading.numberOfFileHandlerThreads.get();
/** indicates how long the update queue thread should wait between queuing ticks */
private static final int UPDATE_QUEUE_THREAD_DELAY_IN_MS = 1_000;
private static final int UPDATE_QUEUE_THREAD_DELAY_IN_MS = 500;
// TODO add a debug view
/** the list of queued positions that need to update their parents */
Set<DhSectionPos> parentApplicationPositionSet = ConcurrentHashMap.newKeySet();
private final ThreadPoolExecutor updateQueueProcessor = ThreadUtil.makeSingleThreadPool("Update Queue Processor");
private final AtomicBoolean updateQueueThreadRunningRef = new AtomicBoolean(false);