Compare commits

..

39 Commits

Author SHA1 Message Date
James Seibel 894a2dbe7d Fix incorrect DhSectionPos method var order 2024-05-16 22:14:34 -05:00
James Seibel 172e7560fd Replace DhSectionPos with long primitives to reduce GC 2024-05-16 22:07:57 -05:00
James Seibel cd5ff8ce35 Start adding new DhSectionPos and unit tests 2024-05-15 18:48:25 -05:00
James Seibel 6e717a383d Remove unused OldSectionPos methods and add serialize methods to V1 repo 2024-05-15 18:47:51 -05:00
James Seibel bef873b875 Add comments to EDhDirection 2024-05-15 18:47:09 -05:00
James Seibel 331d75a3bc Rename DhSectionPos -> OldDhSectionPos before rewrite 2024-05-15 07:50:52 -04:00
James Seibel cec6438602 Replace QuadTree iterator linked list with ArrayDeque
Thanks JustALittleWolf!
2024-05-15 07:36:32 -04:00
James Seibel f5e0c112e3 Revert max world gen tasks 2 -> 20
I was hoping it would fix LODs not appearing, although it appears there was a different problem which has since been fixed.
2024-05-11 16:23:20 -05:00
James Seibel 899c4aca91 Add Quad Tree Render Status debug wireframe 2024-05-11 16:21:53 -05:00
James Seibel a4ac483e5b Potentially fix LODs not loading in 2024-05-11 16:19:10 -05:00
James Seibel 723f67ea0c Attempt to prevent thread starvation due to world gen
Hopefully this should help prevent issues on low end machines not loading in LODs
2024-05-10 22:27:22 -05:00
James Seibel 4575701bd4 disable sql timeout 2024-05-09 23:21:35 -05:00
James Seibel 7cfcfb0695 Handle missing/corrupted block/biome ID's in the full data 2024-05-09 19:45:54 -05:00
James Seibel f33bfa1d69 Fix monoliths due to duplicate IDs
This would specifically happen if moving from one MC version that has more blockstate attributes to one with fewer. If that was done then the both blockstates would act like they were the same, screwing up the ID map.
2024-05-09 19:44:33 -05:00
James Seibel a3c1f1563d minor FullDataToRenderDataTransformer reformat 2024-05-09 19:35:43 -05:00
James Seibel 361d197c5e Add DataCorruptedException(message, exception) constructor 2024-05-09 19:04:07 -05:00
James Seibel 04379691bc Potential fix for NaN multiverse similarity 2024-05-09 07:34:47 -05:00
James Seibel e1ca398b8f Fix updating chunk count not clearing on world close 2024-05-04 15:35:29 -05:00
James Seibel f34e67e6bb Fix F3 levels not closing with multiverse 2024-05-04 15:12:26 -05:00
James Seibel aad095ca1a Fix #670 Remove outdated world gen options from tooltip 2024-05-04 09:48:35 -05:00
James Seibel 950c951c2d minor ConfigBasedLogger cleanup 2024-05-02 17:31:53 -05:00
cola98765 37eaa2656a Update file FullDataToRenderDataTransformer.java 2024-05-01 10:31:43 +00:00
James Seibel 945853d014 Improve nightly build warning message 2024-04-30 21:53:00 -05:00
James Seibel 6bb38ad500 Improve migration queue messages 2024-04-30 21:52:13 -05:00
James Seibel 980086c533 Fix debug wireframes rendering on top of LODs 2024-04-30 21:23:54 -05:00
James Seibel c8f1154831 Remove ZStd compression option
Any ZStd data will be automatically deleted and re-generated
2024-04-30 21:17:26 -05:00
James Seibel 9196480e50 Remove references to FastUtil 8.5.13 2024-04-30 20:28:34 -05:00
James Seibel f0506d28e5 Fix incorrect refernce to fastutil in LzmaArrayCache 2024-04-30 20:26:34 -05:00
James Seibel 8ecd5dd9cb Fix optifine 1.16 support 2024-04-30 18:57:07 -05:00
James Seibel c83140a2d0 add IClientLevelWrapper.getPlainsBiomeWrapper() 2024-04-28 17:31:08 -05:00
James Seibel 3b600ce800 Add corrupt data read handling 2024-04-28 15:52:08 -05:00
James Seibel 7f874b4dc5 Revert a613540b 2024-04-27 12:56:08 -05:00
James Seibel 23e857a20d Fix some lib shading issues 2024-04-27 11:35:08 -05:00
James Seibel 2298ef0e0d up the version number 2.0.3 -> 2.0.4 2024-04-26 07:33:42 -05:00
James Seibel 7470455e50 Fix issues with compressors not appearing at runtime 2024-04-26 07:33:14 -05:00
James Seibel 614884c29e Merge Data_source_rewrite into main 2024-04-26 07:21:48 -05:00
coolGi cb0c294df6 Removed test code being included in main repo code 2024-03-24 12:26:10 +10:30
cola98765 7714569251 Merge branch 'blending_change' into 'main'
Update LodRenderer.java

See merge request jeseibel/distant-horizons-core!53
2024-03-17 23:47:26 +00:00
cola98765 8b9e48d4d3 Update LodRenderer.java 2024-03-17 23:47:25 +00:00
68 changed files with 1710 additions and 1417 deletions
@@ -56,14 +56,15 @@ public enum EDhApiDataCompressionMode
*/
LZ4(1),
/**
/*
* Decent speed and good compression. <br><br>
*
* Read Speed: 11.78 MS / DTO <br>
* Write Speed: 16.76 MS / DTO <br>
* Compression ratio: 0.2199 <br>
*/
Z_STD(2),
//@Deprecated
//Z_STD(2),
/**
* Extremely slow, but very good compression. <br><br>
@@ -82,7 +83,8 @@ public enum EDhApiDataCompressionMode
EDhApiDataCompressionMode(int value) { this.value = (byte) value; }
public static EDhApiDataCompressionMode getFromValue(byte value)
/** @throws IllegalArgumentException if the value doesn't map to a value */
public static EDhApiDataCompressionMode getFromValue(byte value) throws IllegalArgumentException
{
EDhApiDataCompressionMode[] enumList = EDhApiDataCompressionMode.values();
for (int i = 0; i < enumList.length; i++)
@@ -34,7 +34,7 @@ public final class ModInfo
public static final String NAME = "DistantHorizons";
/** Human-readable version of NAME */
public static final String READABLE_NAME = "Distant Horizons";
public static final String VERSION = "2.0.3-a-dev";
public static final String VERSION = "2.0.4-a-dev";
/** Returns true if the current build is an unstable developer build, false otherwise. */
public static boolean IS_DEV_BUILD = VERSION.toLowerCase().contains("dev");
+4 -63
View File
@@ -39,36 +39,9 @@ dependencies { // All of these dependencies are in Vanilla Minecraft, but we nee
runtimeOnly "org.lwjgl:lwjgl-opengl::$lwjglNatives"
runtimeOnly "org.lwjgl:lwjgl-stb::$lwjglNatives"
runtimeOnly "org.lwjgl:lwjgl-tinyfd::$lwjglNatives"
// fast util
shade("it.unimi.dsi:fastutil:${rootProject.fastutil_version}")
// Compression
shade("org.lz4:lz4-java:${rootProject.lz4_version}") // LZ4
shade("com.github.luben:zstd-jni:${rootProject.zstd_version}") // Zstd
shade("org.tukaani:xz:${rootProject.xz_version}") // LZMA
// Sqlite Database
shade("org.xerial:sqlite-jdbc:${rootProject.sqlite_jdbc_version}")
// Netty
shade("io.netty:netty-all:${rootProject.netty_version}")
// NightConfig (includes Toml & Json)
// needed in both common and core
shade("com.electronwill.night-config:toml:${rootProject.nightconfig_version}")
shade("com.electronwill.night-config:json:${rootProject.nightconfig_version}")
// needed for the standalone jar
shade("org.apache.logging.log4j:log4j-core:2.23.1")
shade("org.apache.logging.log4j:log4j-api:2.23.1")
// SVG (not needed atm)
//shade("com.formdev:svgSalamander:${rootProject.svgSalamander_version}")
// FIXME for some reason this line doesn't actually shade in the library
// shade "it.unimi.dsi:fastutil:${rootProject.fastutil_version}" // Add our own fastutil version
// Some other dependencies
@@ -80,43 +53,11 @@ dependencies { // All of these dependencies are in Vanilla Minecraft, but we nee
}
artifacts {
shade shadowJar
shadowedArtifact shadowJar // Setup the configuration shadowedArtifact to be the shadowJar
}
shadowJar {
configurations = [project.configurations.shade]
def librariesLocation = "distanthorizons.libraries"
relocate "it.unimi.dsi.fastutil", "${librariesLocation}.unimi.dsi.fastutil"
// LWJGL
// Only ever shadow the dependencies we use otherwise some stuff would break when running on an external client
relocate "org.lwjgl.system.jawt", "${librariesLocation}.lwjgl.system.jawt"
// Compression
relocate "net.jpountz", "${librariesLocation}.jpountz"
relocate "com.github.luben", "${librariesLocation}.github.luben"
relocate "org.tukaani", "${librariesLocation}.tukaani"
// Sqlite Database
//At the moment, there is a bug in this library which doesnt allow it to be relocated
// relocate "org.sqlite", "${librariesLocation}.sqlite"
// JOML
if (project.hasProperty("embed_joml") && embed_joml == "true")
relocate "org.joml", "${librariesLocation}.joml"
// NightConfig (includes Toml & Json)
relocate "com.electronwill.nightconfig", "${librariesLocation}.electronwill.nightconfig"
// Netty
relocate "io.netty", "${librariesLocation}.netty"
relocate "org.apache.logging", "${librariesLocation}.apache.logging"
// relocate "it.unimi.dsi.fastutil", "${librariesLocation}.unimi.dsi.fastutil"
mergeServiceFiles()
}
}
@@ -28,10 +28,10 @@ import com.seibel.distanthorizons.core.api.external.methods.config.DhApiConfig;
import com.seibel.distanthorizons.core.api.external.methods.data.DhApiTerrainDataRepo;
import com.seibel.distanthorizons.api.DhApi;
import com.seibel.distanthorizons.core.render.DhApiRenderProxy;
import io.netty.buffer.ByteBuf;
import net.jpountz.lz4.LZ4FrameOutputStream;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.tukaani.xz.XZOutputStream;
import java.awt.*;
@@ -47,13 +47,17 @@ public class Initializer
{
// if any library isn't present in the jar its class
// will throw an error (not an exception)
Class<?> compressor = LZ4FrameOutputStream.class;
Class<?> networking = ByteBuf.class;
Class<?> toml = com.electronwill.nightconfig.core.Config.class;
Class<?> fastCompressor = LZ4FrameOutputStream.class;
Class<?> smallCompressor = XZOutputStream.class;
//Class<?> networking = ByteBuf.class;
Class<?> config = com.electronwill.nightconfig.core.Config.class;
Class<?> oldFastUtil = it.unimi.dsi.fastutil.longs.LongArrayList.class; // available in 8.2.1
//Class<?> newFastUtil = it.unimi.dsi.fastutil.ints.IntUnaryOperator.class; // available in 8.5.13
}
catch (Throwable e)
{
LOGGER.fatal("Critical programmer error: One or more libraries aren't present. Error: [" + e.getMessage() + "].");
// throwing here should crash the game, notifying the developer that something is wrong
throw e;
}
@@ -205,7 +205,7 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
byte sectionDetailLevel = (byte) (requestedDetailLevel + DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL);
// get the positions for this request
DhSectionPos sectionPos = requestedColumnPos.getSectionPosWithSectionDetailLevel(sectionDetailLevel);
long sectionPos = requestedColumnPos.getSectionPosWithSectionDetailLevel(sectionDetailLevel);
DhLodPos relativePos = requestedColumnPos.getDhSectionRelativePositionForDetailLevel();
@@ -215,7 +215,7 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
FullDataSourceV2 dataSource = level.getFullDataProvider().getAsync(sectionPos).get();
if (dataSource == null)
{
return DhApiResult.createFail("Unable to find/generate any data at the " + DhSectionPos.class.getSimpleName() + " [" + sectionPos + "].");
return DhApiResult.createFail("Unable to find/generate any data at the " + DhSectionPos.class.getSimpleName() + " [" + DhSectionPos.toString(sectionPos) + "].");
}
else
{
@@ -55,6 +55,8 @@ import org.lwjgl.glfw.GLFW;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Queue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
/**
@@ -79,8 +81,7 @@ public class ClientApi
private boolean configOverrideReminderPrinted = false;
private boolean migrationMessageShown = false;
private boolean showMigrationMessageNextFrame = false;
private final Queue<String> chatMessageQueueForNextFrame = new LinkedBlockingQueue<>();
public boolean rendererDisabledBecauseOfExceptions = false;
@@ -480,23 +481,22 @@ public class ClientApi
this.configOverrideReminderPrinted = true;
// remind the user that this is a development build
MC.sendChatMessage("Distant Horizons nightly experimental build version [" + ModInfo.VERSION+"].");
MC.sendChatMessage("You are running an unsupported version of Distant Horizons!");
MC.sendChatMessage("Distant Horizons nightly/unstable build, version: [" + ModInfo.VERSION+"].");
MC.sendChatMessage("Issues may occur with this version.");
MC.sendChatMessage("Here be dragons!");
MC.sendChatMessage("");
}
// data migration
if (this.showMigrationMessageNextFrame
&& !this.migrationMessageShown
&& Config.Client.Advanced.LodBuilding.showMigrationChatWarning.get())
// generic messages
while (!this.chatMessageQueueForNextFrame.isEmpty())
{
this.showMigrationMessageNextFrame = false;
this.migrationMessageShown = true;
MC.sendChatMessage("Old Distant Horizons data is being migrated.");
MC.sendChatMessage("During migration LODs may load slowly and DH world gen is disabled.");
MC.sendChatMessage("");
String message = this.chatMessageQueueForNextFrame.poll();
if (message == null)
{
// done to prevent potential null pointers
message = "";
}
MC.sendChatMessage(message);
}
IProfilerWrapper profiler = MC.getProfiler();
@@ -648,7 +648,10 @@ public class ClientApi
}
}
// TODO there's probably a better way of handling chat messages
public void showMigrationMessageOnNextFrame() { this.showMigrationMessageNextFrame = true; }
/**
* Queues the given message to appear in chat the next valid frame.
* Useful for queueing up messages that may be triggered before the user has loaded into the world.
*/
public void showChatMessageNextFrame(String chatMessage) { this.chatMessageQueueForNextFrame.add(chatMessage); }
}
@@ -110,6 +110,8 @@ public class SharedApi
ThreadPoolUtil.shutdownThreadPools();
DebugRenderer.clearRenderables();
MC_RENDER.clearTargetFrameBuffer();
// needs to be closed on world shutdown to clear out un-processed chunks
UPDATING_CHUNK_POS_SET.clear();
// recommend that the garbage collector cleans up any objects from the old world and thread pools
System.gc();
@@ -771,12 +771,6 @@ public class Config
+ "Estimated average DTO read speed: 1.85 ms\n"
+ "Estimated average DTO write speed: 9.46 ms\n"
+ "\n"
+ EDhApiDataCompressionMode.Z_STD + " \n"
+ "A good middle ground between speed and compression.\n"
+ "Expected Compression Ratio: 0.21\n"
+ "Estimated average DTO read speed: 11.78 ms\n"
+ "Estimated average DTO write speed: 16.77 ms\n"
+ "\n"
+ EDhApiDataCompressionMode.LZMA2 + " \n"
+ "Slow but very good compression.\n"
+ "Expected Compression Ratio: 0.14\n"
@@ -1258,6 +1252,11 @@ public class Config
.comment("Render LOD section status?")
.build();
public static ConfigEntry<Boolean> showQuadTreeRenderStatus = new ConfigEntry.Builder<Boolean>()
.set(false)
.comment("Render Quad Tree Rendering status?")
.build();
public static ConfigEntry<Boolean> showFullDataUpdateStatus = new ConfigEntry.Builder<Boolean>()
.set(false)
.comment("Render full data update/lock status?")
@@ -21,6 +21,8 @@ package com.seibel.distanthorizons.core.dataObjects.fullData;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.objects.DataCorruptedException;
import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataInputStream;
import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataOutputStream;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
@@ -65,7 +67,7 @@ public class FullDataPointIdMap
private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
/** should only be used for debugging */
private DhSectionPos pos;
private long pos;
/** The index should be the same as the Entry's ID */
private final ArrayList<Entry> entryList = new ArrayList<>();
@@ -77,7 +79,7 @@ public class FullDataPointIdMap
// constructor //
//=============//
public FullDataPointIdMap(DhSectionPos pos) { this.pos = pos; }
public FullDataPointIdMap(long pos) { this.pos = pos; }
@@ -117,9 +119,11 @@ public class FullDataPointIdMap
/** @return -1 if the list is empty */
public int getMaxValidId() { return this.entryList.size() - 1; }
public int size() { return this.entryList.size(); }
public boolean isEmpty() { return this.entryList.isEmpty(); }
public DhSectionPos getPos() { return this.pos; }
public long getPos() { return this.pos; }
@@ -168,9 +172,67 @@ public class FullDataPointIdMap
}
}
/** allows for adding duplicate {@link Entry} */
private void add(Entry biomeBlockStateEntry, boolean useWriteLocks)
{
try
{
if (useWriteLocks)
{
this.readWriteLock.writeLock().lock();
}
int id = this.entryList.size();
this.entryList.add(biomeBlockStateEntry);
this.idMap.put(biomeBlockStateEntry, id);
}
finally
{
if (useWriteLocks)
{
this.readWriteLock.writeLock().unlock();
}
}
}
/**
* Adds each entry from the given map to this map.
* Adds every {@link Entry} from inputMap into this map. <br>
* Allows duplicate entries. <br><br>
*
* Allowing duplicate entries should be done if a datasource is just being read in and
* a merge step isn't being done afterwards. If duplicates are removed it may cause
* the ID's to get out of sync since everything will be shifted down after the removed
* ID(s).
*/
public void addAll(FullDataPointIdMap inputMap)
{
try
{
//LOGGER.trace("adding {" + this.pos + ", " + this.entryList.size() + "} and {" + inputMap.pos + ", " + inputMap.entryList.size() + "}");
inputMap.readWriteLock.readLock().lock();
this.readWriteLock.writeLock().lock();
ArrayList<Entry> entriesToMerge = inputMap.entryList;
for (int i = 0; i < entriesToMerge.size(); i++)
{
Entry entity = entriesToMerge.get(i);
this.add(entity, false);
}
}
finally
{
this.readWriteLock.writeLock().unlock();
inputMap.readWriteLock.readLock().unlock();
//LOGGER.trace("finished merging {" + this.pos + ", " + this.entryList.size() + "} and {" + inputMap.pos + ", " + inputMap.entryList.size() + "}");
}
}
/**
* Adds each entry from the given map to this map. <br><br>
*
* Note: when using this function be careful about re-mapping the
* same data source multiple times.
@@ -208,7 +270,7 @@ public class FullDataPointIdMap
}
/** Should only be used if this map is going to be reused, otherwise bad things will happen. */
public void clear(DhSectionPos pos)
public void clear(long pos)
{
this.pos = pos;
this.entryList.clear();
@@ -259,9 +321,14 @@ public class FullDataPointIdMap
}
/** Creates a new IdBiomeBlockStateMap from the given UTF formatted stream */
public static FullDataPointIdMap deserialize(DhDataInputStream inputStream, DhSectionPos pos, ILevelWrapper levelWrapper) throws IOException, InterruptedException
public static FullDataPointIdMap deserialize(DhDataInputStream inputStream, long pos, ILevelWrapper levelWrapper) throws IOException, InterruptedException, DataCorruptedException
{
int entityCount = inputStream.readInt();
if (entityCount < 0)
{
throw new DataCorruptedException("FullDataPointIdMap deserialize entry count should have a number greater than or equal to 0, returned value ["+entityCount+"].");
}
// only used when debugging
HashMap<String, FullDataPointIdMap.Entry> dataPointEntryBySerialization = new HashMap<>();
@@ -269,6 +336,13 @@ public class FullDataPointIdMap
FullDataPointIdMap newMap = new FullDataPointIdMap(pos);
for (int i = 0; i < entityCount; i++)
{
// necessary to prevent issues with deserializing objects after the level has been closed
if (Thread.interrupted())
{
throw new InterruptedException(FullDataPointIdMap.class.getSimpleName() + " task interrupted.");
}
String entryString = inputStream.readUTF();
Entry newEntry = Entry.deserialize(entryString, levelWrapper);
newMap.entryList.add(newEntry);
@@ -287,7 +361,11 @@ public class FullDataPointIdMap
}
}
//LOGGER.trace("deserialized " + pos + " " + newMap.entryList.size() + "-" + entityCount);
if (newMap.size() != entityCount)
{
// if the mappings are out of sync then the LODs will render incorrectly due to IDs being wrong
LodUtil.assertNotReach("ID maps failed to deserialize for pos: ["+ DhSectionPos.toString(pos)+"], incorrect entity count. Expected count ["+entityCount+"], actual count ["+newMap.size()+"]");
}
return newMap;
}
@@ -457,22 +535,16 @@ public class FullDataPointIdMap
public String serialize() { return this.biome.getSerialString() + BLOCK_STATE_SEPARATOR_STRING + this.blockState.getSerialString(); }
public static Entry deserialize(String str, ILevelWrapper levelWrapper) throws IOException, InterruptedException
public static Entry deserialize(String str, ILevelWrapper levelWrapper) throws IOException, DataCorruptedException
{
String[] stringArray = str.split(BLOCK_STATE_SEPARATOR_STRING);
if (stringArray.length != 2)
{
throw new IOException("Failed to deserialize BiomeBlockStateEntry");
throw new DataCorruptedException("Failed to deserialize BiomeBlockStateEntry");
}
// necessary to prevent issues with deserializing objects after the level has been closed
if (Thread.interrupted())
{
throw new InterruptedException(FullDataPointIdMap.class.getSimpleName() + " task interrupted.");
}
IBiomeWrapper biome = WRAPPER_FACTORY.deserializeBiomeWrapper(stringArray[0], levelWrapper);
IBlockStateWrapper blockState = WRAPPER_FACTORY.deserializeBlockStateWrapper(stringArray[1], levelWrapper);
IBiomeWrapper biome = WRAPPER_FACTORY.deserializeBiomeWrapperOrGetDefault(stringArray[0], levelWrapper);
IBlockStateWrapper blockState = WRAPPER_FACTORY.deserializeBlockStateWrapperOrGetDefault(stringArray[1], levelWrapper);
return Entry.getEntry(biome, blockState);
}
@@ -27,6 +27,7 @@ import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV1DTO;
import com.seibel.distanthorizons.core.util.FullDataPointUtil;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.objects.DataCorruptedException;
import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataInputStream;
import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataOutputStream;
import com.seibel.distanthorizons.core.dataObjects.fullData.FullDataPointIdMap;
@@ -74,7 +75,7 @@ public class FullDataSourceV1 implements IDataSource<IDhLevel>
/** A flattened 2D array (for the X and Z directions) containing an array for the Y direction. */
private final long[][] dataArrays;
private DhSectionPos sectionPos;
private long sectionPos;
private boolean isEmpty = true;
@@ -84,8 +85,8 @@ public class FullDataSourceV1 implements IDataSource<IDhLevel>
// constructors //
//==============//
public static FullDataSourceV1 createEmpty(DhSectionPos pos) { return new FullDataSourceV1(pos); }
private FullDataSourceV1(DhSectionPos sectionPos)
public static FullDataSourceV1 createEmpty(long pos) { return new FullDataSourceV1(pos); }
private FullDataSourceV1(long sectionPos)
{
this.dataArrays = new long[WIDTH * WIDTH][0];
this.mapping = new FullDataPointIdMap(sectionPos);
@@ -110,19 +111,19 @@ public class FullDataSourceV1 implements IDataSource<IDhLevel>
//=====================//
@Override
public DhSectionPos getKey() { return this.sectionPos; }
public Long getKey() { return this.sectionPos; }
@Override
public DhSectionPos getPos() { return this.sectionPos; }
public Long getPos() { return this.sectionPos; }
public void resizeDataStructuresForRepopulation(DhSectionPos pos)
public void resizeDataStructuresForRepopulation(long pos)
{
// no data structures need to be changed, only the source's position
this.sectionPos = pos;
}
@Override
public byte getDataDetailLevel() { return (byte) (this.sectionPos.getDetailLevel() - SECTION_SIZE_OFFSET); }
public byte getDataDetailLevel() { return (byte) (DhSectionPos.getDetailLevel(this.sectionPos) - SECTION_SIZE_OFFSET); }
public boolean isEmpty() { return this.isEmpty; }
@@ -152,7 +153,7 @@ public class FullDataSourceV1 implements IDataSource<IDhLevel>
* Clears and then overwrites any data in this object with the data from the given file and stream.
* This is expected to be used with an existing {@link FullDataSourceV1} and can be used in place of a constructor to reuse an existing {@link FullDataSourceV1} object.
*/
public void repopulateFromStream(FullDataSourceV1DTO dto, DhDataInputStream inputStream, IDhLevel level) throws IOException, InterruptedException
public void repopulateFromStream(FullDataSourceV1DTO dto, DhDataInputStream inputStream, IDhLevel level) throws IOException, InterruptedException, DataCorruptedException
{
// clear/overwrite the old data
this.resizeDataStructuresForRepopulation(dto.pos);
@@ -166,7 +167,7 @@ public class FullDataSourceV1 implements IDataSource<IDhLevel>
* Overwrites any data in this object with the data from the given file and stream.
* This is expected to be used with an empty {@link FullDataSourceV1} and functions similar to a constructor.
*/
public void populateFromStream(FullDataSourceV1DTO dto, DhDataInputStream inputStream, IDhLevel level) throws IOException, InterruptedException
public void populateFromStream(FullDataSourceV1DTO dto, DhDataInputStream inputStream, IDhLevel level) throws IOException, InterruptedException, DataCorruptedException
{
FullDataSourceSummaryData summaryData = this.readSourceSummaryInfo(dto, inputStream, level);
this.setSourceSummaryData(summaryData);
@@ -361,7 +362,7 @@ public class FullDataSourceV1 implements IDataSource<IDhLevel>
outputStream.writeInt(DATA_GUARD_BYTE);
this.mapping.serialize(outputStream);
}
public FullDataPointIdMap readIdMappings(DhDataInputStream inputStream, ILevelWrapper levelWrapper) throws IOException, InterruptedException
public FullDataPointIdMap readIdMappings(DhDataInputStream inputStream, ILevelWrapper levelWrapper) throws IOException, InterruptedException, DataCorruptedException
{
int guardByte = inputStream.readInt();
if (guardByte != DATA_GUARD_BYTE)
@@ -32,6 +32,7 @@ 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.DataCorruptedException;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.coreapi.ModInfo;
import it.unimi.dsi.fastutil.longs.LongArrayList;
@@ -70,9 +71,9 @@ public class FullDataSourceV2 implements IDataSource<IDhLevel>
private int cachedHashCode = 0;
private DhSectionPos pos;
private long pos;
@Override
public DhSectionPos getKey() { return this.pos; }
public Long getKey() { return this.pos; }
public final FullDataPointIdMap mapping;
@@ -111,8 +112,8 @@ public class FullDataSourceV2 implements IDataSource<IDhLevel>
// constructors //
//==============//
public static FullDataSourceV2 createEmpty(DhSectionPos pos) { return new FullDataSourceV2(pos); }
private FullDataSourceV2(DhSectionPos pos)
public static FullDataSourceV2 createEmpty(long pos) { return new FullDataSourceV2(pos); }
private FullDataSourceV2(long pos)
{
this.pos = pos;
this.dataPoints = new LongArrayList[WIDTH * WIDTH];
@@ -125,8 +126,8 @@ public class FullDataSourceV2 implements IDataSource<IDhLevel>
this.columnWorldCompressionMode = new byte[WIDTH * WIDTH];
}
public static FullDataSourceV2 createWithData(DhSectionPos pos, FullDataPointIdMap mapping, LongArrayList[] data, byte[] columnGenerationStep, byte[] columnWorldCompressionMode) { return new FullDataSourceV2(pos, mapping, data, columnGenerationStep, columnWorldCompressionMode); }
private FullDataSourceV2(DhSectionPos pos, FullDataPointIdMap mapping, LongArrayList[] data, byte[] columnGenerationSteps, byte[] columnWorldCompressionMode)
public static FullDataSourceV2 createWithData(long pos, FullDataPointIdMap mapping, LongArrayList[] data, byte[] columnGenerationStep, byte[] columnWorldCompressionMode) { return new FullDataSourceV2(pos, mapping, data, columnGenerationStep, columnWorldCompressionMode); }
private FullDataSourceV2(long pos, FullDataPointIdMap mapping, LongArrayList[] data, byte[] columnGenerationSteps, byte[] columnWorldCompressionMode)
{
LodUtil.assertTrue(data.length == WIDTH * WIDTH);
@@ -229,8 +230,8 @@ public class FullDataSourceV2 implements IDataSource<IDhLevel>
}
byte thisDetailLevel = this.pos.getDetailLevel();
byte inputDetailLevel = inputDataSource.pos.getDetailLevel();
byte thisDetailLevel = DhSectionPos.getDetailLevel(this.pos);
byte inputDetailLevel = DhSectionPos.getDetailLevel(inputDataSource.pos);
// determine the mapping changes necessary for the input to map onto this datasource
@@ -255,7 +256,7 @@ public class FullDataSourceV2 implements IDataSource<IDhLevel>
}
// determine if this data source should be applied to its parent
this.applyToParent = (dataChanged && this.pos.getDetailLevel() < AbstractDataSourceHandler.TOP_SECTION_DETAIL_LEVEL);
this.applyToParent = (dataChanged && DhSectionPos.getDetailLevel(this.pos) < AbstractDataSourceHandler.TOP_SECTION_DETAIL_LEVEL);
if (dataChanged)
{
@@ -268,9 +269,9 @@ public class FullDataSourceV2 implements IDataSource<IDhLevel>
public boolean updateFromSameDetailLevel(FullDataSourceV2 inputDataSource, int[] remappedIds)
{
// both data sources should have the same detail level
if (inputDataSource.pos.getDetailLevel() != this.pos.getDetailLevel())
if (DhSectionPos.getDetailLevel(inputDataSource.pos) != DhSectionPos.getDetailLevel(this.pos))
{
throw new IllegalArgumentException("Both data sources must have the same detail level. Expected ["+this.pos.getDetailLevel()+"], received ["+inputDataSource.pos.getDetailLevel()+"].");
throw new IllegalArgumentException("Both data sources must have the same detail level. Expected ["+ DhSectionPos.getDetailLevel(this.pos)+"], received ["+ DhSectionPos.getDetailLevel(inputDataSource.pos)+"].");
}
// copy over everything from the input data source into this one
@@ -350,9 +351,9 @@ public class FullDataSourceV2 implements IDataSource<IDhLevel>
}
public boolean updateFromOneBelowDetailLevel(FullDataSourceV2 inputDataSource, int[] remappedIds)
{
if (inputDataSource.pos.getDetailLevel() + 1 != this.pos.getDetailLevel())
if (DhSectionPos.getDetailLevel(inputDataSource.pos) + 1 != DhSectionPos.getDetailLevel(this.pos))
{
throw new IllegalArgumentException("Input data source must be exactly 1 detail level below this data source. Expected [" + (this.pos.getDetailLevel() - 1) + "], received [" + inputDataSource.pos.getDetailLevel() + "].");
throw new IllegalArgumentException("Input data source must be exactly 1 detail level below this data source. Expected [" + (DhSectionPos.getDetailLevel(this.pos) - 1) + "], received [" + DhSectionPos.getDetailLevel(inputDataSource.pos) + "].");
}
// input is one detail level lower (higher detail)
@@ -361,10 +362,10 @@ public class FullDataSourceV2 implements IDataSource<IDhLevel>
// determine where in the input data source should be written to
// since the input is one detail level below it will be one of this position's 4 children
int minChildXPos = this.pos.getChildByIndex(0).getX();
int recipientOffsetX = (inputDataSource.pos.getX() == minChildXPos) ? 0 : (WIDTH / 2);
int minChildZPos = this.pos.getChildByIndex(0).getZ();
int recipientOffsetZ = (inputDataSource.pos.getZ() == minChildZPos) ? 0 : (WIDTH / 2);
int minChildXPos = DhSectionPos.getX(DhSectionPos.getChildByIndex(this.pos, 0));
int recipientOffsetX = (DhSectionPos.getX(inputDataSource.pos) == minChildXPos) ? 0 : (WIDTH / 2);
int minChildZPos = DhSectionPos.getZ(DhSectionPos.getChildByIndex(this.pos, 0));
int recipientOffsetZ = (DhSectionPos.getZ(inputDataSource.pos) == minChildZPos) ? 0 : (WIDTH / 2);
@@ -616,7 +617,16 @@ public class FullDataSourceV2 implements IDataSource<IDhLevel>
{
if (height != 0)
{
newColumnList.add(FullDataPointUtil.encode(lastId, height, minY, lastBlockLight, lastSkyLight));
try
{
long datapoint = FullDataPointUtil.encode(lastId, height, minY, lastBlockLight, lastSkyLight);
newColumnList.add(datapoint);
}
catch (DataCorruptedException e)
{
// shouldn't happen, (especially if validation is disabled) but just in case
LOGGER.warn("Skipping corrupt datapoint for pos "+inputDataSource.pos+" at relative position ["+x+","+z+"] with data: ID["+lastId+"], Height["+height+"], minY["+minY+"], lastBlockLight["+lastBlockLight+"], lastSkyLight["+lastSkyLight+"].");
}
}
lastId = id;
@@ -630,7 +640,15 @@ public class FullDataSourceV2 implements IDataSource<IDhLevel>
// add the last slice if present
if (height != 0)
{
newColumnList.add(FullDataPointUtil.encode(lastId, height, minY, lastBlockLight, lastSkyLight));
try
{
newColumnList.add(FullDataPointUtil.encode(lastId, height, minY, lastBlockLight, lastSkyLight));
}
catch (DataCorruptedException e)
{
// shouldn't happen, (especially if validation is disabled) but just in case
LOGGER.warn("Skipping corrupt datapoint for pos "+inputDataSource.pos+" at relative position ["+x+","+z+"] with data: ID["+lastId+"], Height["+height+"], minY["+minY+"], lastBlockLight["+lastBlockLight+"], lastSkyLight["+lastSkyLight+"].");
}
}
@@ -765,7 +783,7 @@ public class FullDataSourceV2 implements IDataSource<IDhLevel>
*
* @see FullDataSourceV2#dataPoints
*/
public static void throwIfDataColumnInWrongOrder(DhSectionPos pos, LongArrayList dataArray) throws IllegalStateException
public static void throwIfDataColumnInWrongOrder(long pos, LongArrayList dataArray) throws IllegalStateException
{
long firstDataPoint = dataArray.getLong(0);
int firstBottomY = FullDataPointUtil.getBottomY(firstDataPoint);
@@ -775,7 +793,7 @@ public class FullDataSourceV2 implements IDataSource<IDhLevel>
if (firstBottomY < lastBottomY)
{
throw new IllegalStateException("Incorrect data point order at pos: "+pos+", first datapoint bottom Y ["+firstBottomY+"], last datapoint bottom Y ["+lastBottomY+"].");
throw new IllegalStateException("Incorrect data point order at pos: ["+ DhSectionPos.toString(pos)+"], first datapoint bottom Y ["+firstBottomY+"], last datapoint bottom Y ["+lastBottomY+"].");
}
}
@@ -811,7 +829,7 @@ public class FullDataSourceV2 implements IDataSource<IDhLevel>
// pooling //
//=========//
private static void prepPooledDataSource(DhSectionPos pos, boolean clearData, FullDataSourceV2 dataSource)
private static void prepPooledDataSource(long pos, boolean clearData, FullDataSourceV2 dataSource)
{
dataSource.pos = pos;
@@ -839,10 +857,10 @@ public class FullDataSourceV2 implements IDataSource<IDhLevel>
//=====================//
@Override
public DhSectionPos getPos() { return this.pos; }
public Long getPos() { return this.pos; }
@Override
public byte getDataDetailLevel() { return (byte) (this.pos.getDetailLevel() - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL); }
public byte getDataDetailLevel() { return (byte) (DhSectionPos.getDetailLevel(this.pos) - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL); }
public EDhApiWorldGenerationStep getWorldGenStepAtRelativePos(int relX, int relZ)
{
@@ -881,7 +899,7 @@ public class FullDataSourceV2 implements IDataSource<IDhLevel>
//================//
@Override
public String toString() { return this.pos.toString(); }
public String toString() { return DhSectionPos.toString(this.pos); }
@Override
public int hashCode()
@@ -894,7 +912,7 @@ public class FullDataSourceV2 implements IDataSource<IDhLevel>
}
private void generateHashCode()
{
int result = this.pos.hashCode();
int result = DhSectionPos.hashCode(this.pos);
result = 31 * result + Arrays.deepHashCode(this.dataPoints);
result = 17 * result + Arrays.hashCode(this.columnGenerationSteps);
result = 43 * result + Arrays.hashCode(this.columnWorldCompressionMode);
@@ -911,7 +929,7 @@ public class FullDataSourceV2 implements IDataSource<IDhLevel>
}
FullDataSourceV2 other = (FullDataSourceV2) obj;
if (!other.pos.equals(this.pos))
if (other.pos != this.pos)
{
return false;
}
@@ -59,7 +59,7 @@ public class ColumnRenderSource implements IDataSource<IDhClientLevel>
/** will be zero if an empty data source was created */
public int verticalDataCount;
public DhSectionPos pos;
public long pos;
public int yOffset;
public LongArrayList renderDataContainer;
@@ -77,11 +77,11 @@ public class ColumnRenderSource implements IDataSource<IDhClientLevel>
//==============//
/**
* This is separate from {@link DataSourcePool#getPooledSource(DhSectionPos, boolean)}
* This is separate from {@link DataSourcePool#getPooledSource(long, boolean)}
* because we need to pass in a couple extra values,
* specifically maxVerticalSize and yOffset.
*/
public static ColumnRenderSource getPooledRenderSource(DhSectionPos pos, int maxVerticalSize, int yOffset, boolean clearData)
public static ColumnRenderSource getPooledRenderSource(long pos, int maxVerticalSize, int yOffset, boolean clearData)
{
ColumnRenderSource renderSource = DATA_SOURCE_POOL.getPooledSource(pos);
@@ -109,14 +109,14 @@ public class ColumnRenderSource implements IDataSource<IDhClientLevel>
}
private static ColumnRenderSource createEmptyRenderSource(DhSectionPos sectionPos) { return new ColumnRenderSource(sectionPos, 0, 0); }
private static ColumnRenderSource createEmptyRenderSource(long sectionPos) { return new ColumnRenderSource(sectionPos, 0, 0); }
/**
* Creates an empty ColumnRenderSource.
*
* @param pos the relative position of the container
* @param maxVerticalSize the maximum vertical size of the container
*/
private ColumnRenderSource(DhSectionPos pos, int maxVerticalSize, int yOffset)
private ColumnRenderSource(long pos, int maxVerticalSize, int yOffset)
{
this.verticalDataCount = maxVerticalSize;
this.renderDataContainer = new LongArrayList(new long[SECTION_SIZE * SECTION_SIZE * this.verticalDataCount]);
@@ -155,7 +155,7 @@ public class ColumnRenderSource implements IDataSource<IDhClientLevel>
final String errorMessagePrefix = "Unable to complete update for RenderSource pos: [" + this.pos + "] and pos: [" + inputFullDataSource.getPos() + "]. Error:";
boolean dataChanged = false;
if (inputFullDataSource.getPos().getDetailLevel() == this.pos.getDetailLevel())
if (DhSectionPos.getDetailLevel(inputFullDataSource.getPos()) == DhSectionPos.getDetailLevel(this.pos))
{
try
{
@@ -167,8 +167,8 @@ public class ColumnRenderSource implements IDataSource<IDhClientLevel>
DhBlockPos2D centerBlockPos = inputFullDataSource.getPos().getCenterBlockPos();
int halfBlockWidth = inputFullDataSource.getPos().getBlockWidth() / 2;
DhBlockPos2D centerBlockPos = DhSectionPos.getCenterBlockPos(inputFullDataSource.getPos());
int halfBlockWidth = DhSectionPos.getBlockWidth(inputFullDataSource.getPos()) / 2;
DhBlockPos2D minBlockPos = new DhBlockPos2D(centerBlockPos.x - halfBlockWidth, centerBlockPos.z - halfBlockWidth);
for (int x = 0; x < FullDataSourceV2.WIDTH; x++)
@@ -215,11 +215,11 @@ public class ColumnRenderSource implements IDataSource<IDhClientLevel>
// data helper methods //
//=====================//
public DhSectionPos getPos() { return this.pos; }
public Long getPos() { return this.pos; }
@Override
public DhSectionPos getKey() { return this.pos; }
public Long getKey() { return this.pos; }
public byte getDataDetailLevel() { return (byte) (this.pos.getDetailLevel() - SECTION_SIZE_OFFSET); }
public byte getDataDetailLevel() { return (byte) (DhSectionPos.getDetailLevel(this.pos) - SECTION_SIZE_OFFSET); }
public boolean isEmpty() { return this.isEmpty; }
public void markNotEmpty() { this.isEmpty = false; }
@@ -343,17 +343,24 @@ public class ColumnRenderBuffer implements AutoCloseable
//==============//
/** can be used when debugging */
public boolean hasNonEmptyBuffers()
public boolean hasNonNullVbos() { return this.vbos != null || this.vbosTransparent != null; }
/** can be used when debugging */
public int vboBufferCount()
{
for (GLVertexBuffer vertexBuffer : this.vbos)
int count = 0;
if (this.vbos != null)
{
if (vertexBuffer != null && vertexBuffer.getSize() != 0)
{
return true;
}
count += this.vbos.length;
}
return false;
if (this.vbosTransparent != null)
{
count += this.vbosTransparent.length;
}
return count;
}
public void debugDumpStats(StatsMap statsMap)
@@ -98,7 +98,7 @@ public class ColumnRenderBufferBuilder
&& !clientLevel.getLevelWrapper().getDimensionType().isTheEnd()
// FIXME temporary fix
// Cave culling is currently broken for any detail level above 0
&& renderSource.pos.getDetailLevel() == DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL
&& DhSectionPos.getDetailLevel(renderSource.pos) == DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL
);
int skyLightCullingBelow = Config.Client.Advanced.Graphics.AdvancedGraphics.caveCullingHeight.get();
@@ -131,7 +131,7 @@ public class ColumnRenderBufferBuilder
{
try
{
ColumnRenderBuffer buffer = new ColumnRenderBuffer(new DhBlockPos(renderSource.pos.getMinCornerLodPos().getCornerBlockPos(), clientLevel.getMinY()));
ColumnRenderBuffer buffer = new ColumnRenderBuffer(new DhBlockPos(DhSectionPos.getMinCornerBlockX(renderSource.pos), clientLevel.getMinY(), DhSectionPos.getMinCornerBlockZ(renderSource.pos)));
try
{
buffer.uploadBuffer(quadBuilder, GLProxy.getInstance().getGpuUploadMethod());
@@ -174,9 +174,9 @@ public class ColumnRenderBufferBuilder
boolean enableColumnBufferLimit = Config.Client.Advanced.Debugging.columnBuilderDebugEnable.get();
if (enableColumnBufferLimit)
{
if (renderSource.pos.getDetailLevel() == Config.Client.Advanced.Debugging.columnBuilderDebugDetailLevel.get()
&& renderSource.pos.getX() == Config.Client.Advanced.Debugging.columnBuilderDebugXPos.get()
&& renderSource.pos.getZ() == Config.Client.Advanced.Debugging.columnBuilderDebugZPos.get())
if (DhSectionPos.getDetailLevel(renderSource.pos) == Config.Client.Advanced.Debugging.columnBuilderDebugDetailLevel.get()
&& DhSectionPos.getX(renderSource.pos) == Config.Client.Advanced.Debugging.columnBuilderDebugXPos.get()
&& DhSectionPos.getZ(renderSource.pos) == Config.Client.Advanced.Debugging.columnBuilderDebugZPos.get())
{
int test = 0;
}
@@ -19,6 +19,8 @@
package com.seibel.distanthorizons.core.dataObjects.render.columnViews;
import it.unimi.dsi.fastutil.longs.LongIterator;
import java.util.Iterator;
public interface IColumnDataView
@@ -28,18 +30,18 @@ public interface IColumnDataView
// FIXME probably horizontal size in blocks?
int size();
default Iterator<Long> iterator()
default LongIterator iterator()
{
return new Iterator<Long>()
return new LongIterator()
{
private int index = 0;
private final int size = size();
private final int size = IColumnDataView.this.size();
@Override
public boolean hasNext() { return this.index < this.size; }
@Override
public Long next() { return get(this.index++); }
public long nextLong() { return IColumnDataView.this.get(this.index++); }
};
}
@@ -38,6 +38,7 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrappe
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper;
import it.unimi.dsi.fastutil.longs.LongArrayList;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import org.apache.logging.log4j.Logger;
import java.util.HashSet;
@@ -52,6 +53,8 @@ public class FullDataToRenderDataTransformer
private static final IWrapperFactory WRAPPER_FACTORY = SingletonInjector.INSTANCE.get(IWrapperFactory.class);
private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
private static final LongOpenHashSet brokenPos = new LongOpenHashSet();
//==============================//
@@ -96,7 +99,7 @@ public class FullDataToRenderDataTransformer
*/
private static ColumnRenderSource transformCompleteFullDataToColumnData(IDhClientLevel level, FullDataSourceV2 fullDataSource) throws InterruptedException
{
final DhSectionPos pos = fullDataSource.getPos();
final long pos = fullDataSource.getPos();
final byte dataDetail = fullDataSource.getDataDetailLevel();
final int vertSize = Config.Client.Advanced.Graphics.Quality.verticalQuality.get().calculateMaxVerticalData(fullDataSource.getDataDetailLevel());
final ColumnRenderSource columnSource = ColumnRenderSource.getPooledRenderSource(pos, vertSize, level.getMinY(), true);
@@ -109,12 +112,12 @@ public class FullDataToRenderDataTransformer
if (dataDetail == columnSource.getDataDetailLevel())
{
int baseX = pos.getMinCornerLodPos().getCornerBlockPos().x;
int baseZ = pos.getMinCornerLodPos().getCornerBlockPos().z;
int baseX = DhSectionPos.getMinCornerBlockX(pos);
int baseZ = DhSectionPos.getMinCornerBlockZ(pos);
for (int x = 0; x < pos.getWidthCountForLowerDetailedSection(dataDetail); x++)
for (int x = 0; x < DhSectionPos.getWidthCountForLowerDetailedSection(pos, dataDetail); x++)
{
for (int z = 0; z < pos.getWidthCountForLowerDetailedSection(dataDetail); z++)
for (int z = 0; z < DhSectionPos.getWidthCountForLowerDetailedSection(pos, dataDetail); z++)
{
throwIfThreadInterrupted();
@@ -154,8 +157,6 @@ public class FullDataToRenderDataTransformer
}
}
private static HashSet<DhSectionPos> brokenPos = new HashSet<>();
// TODO what does this mean?
private static void iterateAndConvert(
@@ -239,8 +240,14 @@ public class FullDataToRenderDataTransformer
{
if (colorBelowWithAvoidedBlocks)
{
//mare sure to not trnasfer alpha if for some reason grass is transparent
colorToApplyToNextBlock = ColorUtil.setAlpha(level.computeBaseColor(new DhBlockPos(blockX, bottomY + level.getMinY(), blockZ), biome, block),255);
int tempColor = level.computeBaseColor(new DhBlockPos(blockX, bottomY + level.getMinY(), blockZ), biome, block);
if (ColorUtil.getAlpha(tempColor) == 0)
{
//make sure to not transfer the color when alpha is 0
continue;
}
//mare sure to not trnasfer alpha if for some reason grass is semi transparent
colorToApplyToNextBlock = ColorUtil.setAlpha(tempColor,255);
skylightToApplyToNextBlock = skyLight;
blocklightToApplyToNextBlock = blockLight;
}
@@ -35,6 +35,7 @@ import com.seibel.distanthorizons.core.pos.DhChunkPos;
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.objects.DataCorruptedException;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper;
@@ -72,7 +73,7 @@ public class LodDataBuilder
sectionPosX = (sectionPosX < 0) ? ((sectionPosX + 1) / NUMB_OF_CHUNKS_WIDE) - 1 : (sectionPosX / NUMB_OF_CHUNKS_WIDE);
int sectionPosZ = chunkWrapper.getChunkPos().z;
sectionPosZ = (sectionPosZ < 0) ? ((sectionPosZ + 1) / NUMB_OF_CHUNKS_WIDE) - 1 : (sectionPosZ / NUMB_OF_CHUNKS_WIDE);
DhSectionPos pos = new DhSectionPos(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL, sectionPosX, sectionPosZ);
long pos = DhSectionPos.encode(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL, sectionPosX, sectionPosZ);
FullDataSourceV2 dataSource = FullDataSourceV2.createEmpty(pos);
dataSource.isEmpty = false;
@@ -133,98 +134,106 @@ public class LodDataBuilder
EDhApiWorldCompressionMode worldCompressionMode = Config.Client.Advanced.LodBuilding.worldCompression.get();
boolean ignoreHiddenBlocks = (worldCompressionMode != EDhApiWorldCompressionMode.MERGE_SAME_BLOCKS);
int minBuildHeight = chunkWrapper.getMinNonEmptyHeight();
for (int relBlockX = 0; relBlockX < LodUtil.CHUNK_WIDTH; relBlockX++)
try
{
for (int relBlockZ = 0; relBlockZ < LodUtil.CHUNK_WIDTH; relBlockZ++)
int minBuildHeight = chunkWrapper.getMinNonEmptyHeight();
for (int relBlockX = 0; relBlockX < LodUtil.CHUNK_WIDTH; relBlockX++)
{
LongArrayList longs = new LongArrayList(chunkWrapper.getHeight() / 4);
int lastY = chunkWrapper.getMaxBuildHeight();
IBiomeWrapper biome = chunkWrapper.getBiome(relBlockX, lastY, relBlockZ);
IBlockStateWrapper blockState = AIR;
int mappedId = dataSource.mapping.addIfNotPresentAndGetId(biome, blockState);
byte blockLight;
byte skyLight;
if (lastY < chunkWrapper.getMaxBuildHeight())
for (int relBlockZ = 0; relBlockZ < LodUtil.CHUNK_WIDTH; relBlockZ++)
{
// 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);
}
else
{
//we are at the height limit. There are no torches here, and sky is not obscured.
blockLight = 0;
skyLight = 15;
}
// determine the starting Y Pos
int y = chunkWrapper.getLightBlockingHeightMapValue(relBlockX,relBlockZ);
// go up until we reach open air or the world limit
IBlockStateWrapper topBlockState = chunkWrapper.getBlockState(relBlockX, y, relBlockZ);
while (!topBlockState.isAir() && y < chunkWrapper.getMaxBuildHeight())
{
try
{
// This is necessary in some edge cases with snow layers and some other blocks that may not appear in the height map but do block light.
// Interestingly this doesn't appear to be the case in the DhLightingEngine, if this same logic is added there the lighting breaks for the affected blocks.
y++;
topBlockState = chunkWrapper.getBlockState(relBlockX, y, relBlockZ);
}
catch (Exception e)
{
if (!getTopErrorLogged)
{
LOGGER.warn("Unexpected issue in LodDataBuilder, future errors won't be logged. Chunk [" + chunkWrapper.getChunkPos() + "] with max height: [" + chunkWrapper.getMaxBuildHeight() + "] had issue getting block at pos [" + relBlockX + "," + y + "," + relBlockZ + "] error: " + e.getMessage(), e);
getTopErrorLogged = true;
}
y--;
break;
}
}
for (; y >= minBuildHeight; y--)
{
IBiomeWrapper newBiome = chunkWrapper.getBiome(relBlockX, y, relBlockZ);
IBlockStateWrapper newBlockState = chunkWrapper.getBlockState(relBlockX, y, relBlockZ);
byte newBlockLight = (byte) chunkWrapper.getBlockLight(relBlockX, y + 1, relBlockZ);
byte newSkyLight = (byte) chunkWrapper.getSkyLight(relBlockX, y + 1, relBlockZ);
LongArrayList longs = new LongArrayList(chunkWrapper.getHeight() / 4);
int lastY = chunkWrapper.getMaxBuildHeight();
IBiomeWrapper biome = chunkWrapper.getBiome(relBlockX, lastY, relBlockZ);
IBlockStateWrapper blockState = AIR;
int mappedId = dataSource.mapping.addIfNotPresentAndGetId(biome, blockState);
// save the biome/block change
if (!newBiome.equals(biome) || !newBlockState.equals(blockState))
byte blockLight;
byte skyLight;
if (lastY < chunkWrapper.getMaxBuildHeight())
{
// if we ignore hidden blocks, don't save this biome/block change
// wait until the block is visible and then save the new datapoint
if (!ignoreHiddenBlocks
// if the last block is air, this block will always be visible
|| blockState.isAir()
// check if this block is visible from any direction
|| blockVisible(chunkWrapper, relBlockX, y, relBlockZ))
// 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);
}
else
{
//we are at the height limit. There are no torches here, and sky is not obscured.
blockLight = 0;
skyLight = 15;
}
// determine the starting Y Pos
int y = chunkWrapper.getLightBlockingHeightMapValue(relBlockX, relBlockZ);
// go up until we reach open air or the world limit
IBlockStateWrapper topBlockState = chunkWrapper.getBlockState(relBlockX, y, relBlockZ);
while (!topBlockState.isAir() && y < chunkWrapper.getMaxBuildHeight())
{
try
{
longs.add(FullDataPointUtil.encode(mappedId, lastY - y, y + 1 - chunkWrapper.getMinBuildHeight(), blockLight, skyLight));
biome = newBiome;
blockState = newBlockState;
mappedId = dataSource.mapping.addIfNotPresentAndGetId(biome, blockState);
blockLight = newBlockLight;
skyLight = newSkyLight;
lastY = y;
// This is necessary in some edge cases with snow layers and some other blocks that may not appear in the height map but do block light.
// Interestingly this doesn't appear to be the case in the DhLightingEngine, if this same logic is added there the lighting breaks for the affected blocks.
y++;
topBlockState = chunkWrapper.getBlockState(relBlockX, y, relBlockZ);
}
catch (Exception e)
{
if (!getTopErrorLogged)
{
LOGGER.warn("Unexpected issue in LodDataBuilder, future errors won't be logged. Chunk [" + chunkWrapper.getChunkPos() + "] with max height: [" + chunkWrapper.getMaxBuildHeight() + "] had issue getting block at pos [" + relBlockX + "," + y + "," + relBlockZ + "] error: " + e.getMessage(), e);
getTopErrorLogged = true;
}
y--;
break;
}
}
for (; y >= minBuildHeight; y--)
{
IBiomeWrapper newBiome = chunkWrapper.getBiome(relBlockX, y, relBlockZ);
IBlockStateWrapper newBlockState = chunkWrapper.getBlockState(relBlockX, y, relBlockZ);
byte newBlockLight = (byte) chunkWrapper.getBlockLight(relBlockX, y + 1, relBlockZ);
byte newSkyLight = (byte) chunkWrapper.getSkyLight(relBlockX, y + 1, relBlockZ);
// save the biome/block change
if (!newBiome.equals(biome) || !newBlockState.equals(blockState))
{
// if we ignore hidden blocks, don't save this biome/block change
// wait until the block is visible and then save the new datapoint
if (!ignoreHiddenBlocks
// if the last block is air, this block will always be visible
|| blockState.isAir()
// check if this block is visible from any direction
|| blockVisible(chunkWrapper, relBlockX, y, relBlockZ))
{
longs.add(FullDataPointUtil.encode(mappedId, lastY - y, y + 1 - chunkWrapper.getMinBuildHeight(), blockLight, skyLight));
biome = newBiome;
blockState = newBlockState;
mappedId = dataSource.mapping.addIfNotPresentAndGetId(biome, blockState);
blockLight = newBlockLight;
skyLight = newSkyLight;
lastY = y;
}
}
}
longs.add(FullDataPointUtil.encode(mappedId, lastY - y, y + 1 - chunkWrapper.getMinBuildHeight(), blockLight, skyLight));
dataSource.setSingleColumn(longs,
relBlockX + chunkOffsetX,
relBlockZ + chunkOffsetZ,
EDhApiWorldGenerationStep.LIGHT,
worldCompressionMode);
}
longs.add(FullDataPointUtil.encode(mappedId, lastY - y, y + 1 - chunkWrapper.getMinBuildHeight(), blockLight, skyLight));
dataSource.setSingleColumn(longs,
relBlockX + chunkOffsetX,
relBlockZ + chunkOffsetZ,
EDhApiWorldGenerationStep.LIGHT,
worldCompressionMode);
}
}
catch (DataCorruptedException e)
{
LOGGER.error("Unable to convert chunk at pos ["+chunkWrapper.getChunkPos()+"] to an LOD. Error: "+e.getMessage(), e);
return null;
}
LodUtil.assertTrue(!dataSource.isEmpty);
return dataSource;
@@ -292,9 +301,9 @@ public class LodDataBuilder
/** @throws ClassCastException if an API user returns the wrong object type(s) */
public static FullDataSourceV2 createFromApiChunkData(DhApiChunk dataPoints) throws ClassCastException
public static FullDataSourceV2 createFromApiChunkData(DhApiChunk dataPoints) throws ClassCastException, DataCorruptedException
{
FullDataSourceV2 accessor = FullDataSourceV2.createEmpty(new DhSectionPos(new DhChunkPos(dataPoints.chunkPosX, dataPoints.chunkPosZ)));
FullDataSourceV2 accessor = FullDataSourceV2.createEmpty(DhSectionPos.encode(new DhChunkPos(dataPoints.chunkPosX, dataPoints.chunkPosZ)));
for (int relZ = 0; relZ < LodUtil.CHUNK_WIDTH; relZ++)
{
for (int relX = 0; relX < LodUtil.CHUNK_WIDTH; relX++)
@@ -43,11 +43,17 @@ import com.seibel.distanthorizons.coreapi.util.math.Vec3i;
*/
public enum EDhDirection
{
/** negative Y */
DOWN(0, 1, -1, "down", EDhDirection.AxisDirection.NEGATIVE, EDhDirection.Axis.Y, new Vec3i(0, -1, 0)),
/** positive Y */
UP(1, 0, -1, "up", EDhDirection.AxisDirection.POSITIVE, EDhDirection.Axis.Y, new Vec3i(0, 1, 0)),
/** negative Z */
NORTH(2, 3, 2, "north", EDhDirection.AxisDirection.NEGATIVE, EDhDirection.Axis.Z, new Vec3i(0, 0, -1)),
/** positive Z */
SOUTH(3, 2, 0, "south", EDhDirection.AxisDirection.POSITIVE, EDhDirection.Axis.Z, new Vec3i(0, 0, 1)),
/** negative X */
WEST(4, 5, 1, "west", EDhDirection.AxisDirection.NEGATIVE, EDhDirection.Axis.X, new Vec3i(-1, 0, 0)),
/** positive X */
EAST(5, 4, 3, "east", EDhDirection.AxisDirection.POSITIVE, EDhDirection.Axis.X, new Vec3i(1, 0, 0));
/**
@@ -8,6 +8,7 @@ import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.sql.repo.AbstractDhRepo;
import com.seibel.distanthorizons.core.sql.dto.IBaseDTO;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.objects.DataCorruptedException;
import com.seibel.distanthorizons.core.util.threading.PositionalLockProvider;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import org.apache.logging.log4j.Logger;
@@ -25,12 +26,14 @@ import java.util.concurrent.locks.ReentrantLock;
// We shouldn't need multiple data source handlers
public abstract class AbstractDataSourceHandler
<TDataSource extends IDataSource<TDhLevel>,
TDTO extends IBaseDTO<DhSectionPos>,
TRepo extends AbstractDhRepo<DhSectionPos, TDTO>,
TDTO extends IBaseDTO<Long>,
TRepo extends AbstractDhRepo<Long, TDTO>,
TDhLevel extends IDhLevel>
implements AutoCloseable
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
private static final Set<String> CORRUPT_DATA_ERRORS_LOGGED = Collections.newSetFromMap(new ConcurrentHashMap<>());
/**
* The highest numerical detail level possible.
@@ -52,8 +55,8 @@ public abstract class AbstractDataSourceHandler
* generally just used for debugging,
* keeps track of which positions are currently locked.
*/
public final Set<DhSectionPos> lockedPosSet = ConcurrentHashMap.newKeySet();
public final ConcurrentHashMap<DhSectionPos, AtomicInteger> queuedUpdateCountsByPos = new ConcurrentHashMap<>();
public final Set<Long> lockedPosSet = ConcurrentHashMap.newKeySet();
public final ConcurrentHashMap<Long, AtomicInteger> queuedUpdateCountsByPos = new ConcurrentHashMap<>();
protected final ReentrantLock closeLock = new ReentrantLock();
@@ -95,10 +98,10 @@ public abstract class AbstractDataSourceHandler
/** When this is called the parent folders should be created */
protected abstract TRepo createRepo();
protected abstract TDataSource createDataSourceFromDto(TDTO dto) throws InterruptedException, IOException;
protected abstract TDataSource createDataSourceFromDto(TDTO dto) throws InterruptedException, IOException, DataCorruptedException;
protected abstract TDTO createDtoFromDataSource(TDataSource dataSource);
protected abstract TDataSource makeEmptyDataSource(DhSectionPos pos);
protected abstract TDataSource makeEmptyDataSource(long pos);
@@ -112,7 +115,7 @@ public abstract class AbstractDataSourceHandler
*
* This call is concurrent. I.e. it supports being called by multiple threads at the same time.
*/
public CompletableFuture<TDataSource> getAsync(DhSectionPos pos)
public CompletableFuture<TDataSource> getAsync(long pos)
{
ThreadPoolExecutor executor = ThreadPoolUtil.getFileHandlerExecutor();
if (executor == null || executor.isTerminated())
@@ -134,10 +137,10 @@ public abstract class AbstractDataSourceHandler
/**
* Should only be used in internal file handler methods where we are already running on a file handler thread.
* Can return null if the repo is in the process of being shut down
* @see AbstractDataSourceHandler#getAsync(DhSectionPos)
* @see AbstractDataSourceHandler#getAsync(long)
*/
@Nullable
public TDataSource get(DhSectionPos pos)
public TDataSource get(long pos)
{
TDataSource dataSource = null;
try
@@ -145,8 +148,23 @@ public abstract class AbstractDataSourceHandler
TDTO dto = this.repo.getByKey(pos);
if (dto != null)
{
// load from database
dataSource = this.createDataSourceFromDto(dto);
try
{
// load from database
dataSource = this.createDataSourceFromDto(dto);
}
catch (DataCorruptedException e)
{
// Only log each message type once.
// This is done to prevent logging "No compression mode with the value [2]" 10,000 times
// if the user is migrating from a nightly build and used ZStd.
if (CORRUPT_DATA_ERRORS_LOGGED.add(e.getMessage()))
{
LOGGER.warn("Corrupted data found at pos [" + DhSectionPos.toString(pos) + "]. Data at position will be deleted so it can be re-generated to prevent issues. Future errors with this same message won't be logged. Error: " + e.getMessage(), e);
}
this.repo.deleteWithKey(pos);
}
}
else
{
@@ -159,7 +177,7 @@ public abstract class AbstractDataSourceHandler
catch (InterruptedException ignore) { }
catch (IOException e)
{
LOGGER.warn("File read Error for pos ["+pos+"], error: "+e.getMessage(), e);
LOGGER.warn("File read Error for pos ["+ DhSectionPos.toString(pos)+"], error: "+e.getMessage(), e);
}
return dataSource;
@@ -212,7 +230,7 @@ public abstract class AbstractDataSourceHandler
* After this method returns the inputData will be written to file.
* @param updatePos the position to update
*/
protected void updateDataSourceAtPos(DhSectionPos updatePos, @NotNull FullDataSourceV2 inputData, boolean lockOnUpdatePos)
protected void updateDataSourceAtPos(long updatePos, @NotNull FullDataSourceV2 inputData, boolean lockOnUpdatePos)
{
boolean methodLocked = false;
// a lock is necessary to prevent two threads from writing to the same position at once,
@@ -274,7 +292,7 @@ public abstract class AbstractDataSourceHandler
//================//
/** used for debugging to track which positions are queued for updating */
private void markUpdateStart(DhSectionPos dataSourcePos)
private void markUpdateStart(long dataSourcePos)
{
this.queuedUpdateCountsByPos.compute(dataSourcePos, (pos, atomicCount) ->
{
@@ -287,7 +305,7 @@ public abstract class AbstractDataSourceHandler
});
}
/** used for debugging to track which positions are queued for updating */
private void markUpdateEnd(DhSectionPos dataSourcePos)
private void markUpdateEnd(long dataSourcePos)
{
this.queuedUpdateCountsByPos.compute(dataSourcePos, (pos, atomicCount) ->
{
@@ -1,8 +1,6 @@
package com.seibel.distanthorizons.core.file;
import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.util.ThreadUtil;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
@@ -27,7 +25,7 @@ public class DataSourcePool<TDataSource extends IDataSource<TDhLevel>, TDhLevel
private final ArrayList<TDataSource> pooledDataSources = new ArrayList<>();
private final ReentrantLock poolLock = new ReentrantLock();
private final Function<DhSectionPos, TDataSource> createEmptyDatasourceFunc;
private final Function<Long, TDataSource> createEmptyDatasourceFunc;
@Nullable
private final IPrepPooledDataSourceFunc<TDataSource, TDhLevel> prepDatasourceFunc;
@@ -37,7 +35,7 @@ public class DataSourcePool<TDataSource extends IDataSource<TDhLevel>, TDhLevel
// constructor //
//=============//
public DataSourcePool(Function<DhSectionPos, TDataSource> createEmptyDatasourceFunc, @Nullable IPrepPooledDataSourceFunc<TDataSource, TDhLevel> prepDatasourceFunc)
public DataSourcePool(Function<Long, TDataSource> createEmptyDatasourceFunc, @Nullable IPrepPooledDataSourceFunc<TDataSource, TDhLevel> prepDatasourceFunc)
{
this.createEmptyDatasourceFunc = createEmptyDatasourceFunc;
this.prepDatasourceFunc = prepDatasourceFunc;
@@ -51,12 +49,12 @@ public class DataSourcePool<TDataSource extends IDataSource<TDhLevel>, TDhLevel
/**
* Returns a cleared data source.
* @see DataSourcePool#getPooledSource(DhSectionPos, boolean)
* @see DataSourcePool#getPooledSource(long, boolean)
*/
public TDataSource getPooledSource(DhSectionPos pos) { return this.getPooledSource(pos, true);}
public TDataSource getPooledSource(long pos) { return this.getPooledSource(pos, true);}
/** @return an empty data source if non are cached */
public TDataSource getPooledSource(DhSectionPos pos, boolean clearData)
public TDataSource getPooledSource(long pos, boolean clearData)
{
try
{
@@ -133,7 +131,7 @@ public class DataSourcePool<TDataSource extends IDataSource<TDhLevel>, TDhLevel
public interface IPrepPooledDataSourceFunc<TDataSource extends IDataSource<TDhLevel>, TDhLevel extends IDhLevel>
{
/** @param clearData will be false if the data will be immediately overwritten anyway */
void prepDataSource(DhSectionPos pos, boolean clearData, TDataSource dataSource);
void prepDataSource(long pos, boolean clearData, TDataSource dataSource);
}
}
@@ -3,7 +3,6 @@ package com.seibel.distanthorizons.core.file;
import com.seibel.distanthorizons.api.enums.EDhApiDetailLevel;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.sql.dto.IBaseDTO;
/**
@@ -13,9 +12,9 @@ import com.seibel.distanthorizons.core.sql.dto.IBaseDTO;
*
* @param <TDhLevel> there are times when we need specifically a client level vs a more generic level
*/
public interface IDataSource<TDhLevel extends IDhLevel> extends IBaseDTO<DhSectionPos>, AutoCloseable
public interface IDataSource<TDhLevel extends IDhLevel> extends IBaseDTO<Long>, AutoCloseable
{
DhSectionPos getPos();
Long getPos();
/** @return true if the data was changed */
boolean update(FullDataSourceV2 chunkData, TDhLevel level);
@@ -2,7 +2,6 @@ package com.seibel.distanthorizons.core.file.fullDatafile;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.util.TimerUtil;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.NotNull;
@@ -22,8 +21,8 @@ public class DelayedFullDataSourceSaveCache
private static final Timer DELAY_UPDATE_TIMER = TimerUtil.CreateTimer("Delayed Full Datasource Save Timer");
public final ConcurrentHashMap<DhSectionPos, FullDataSourceV2> dataSourceByPosition = new ConcurrentHashMap<>();
private final ConcurrentHashMap<DhSectionPos, TimerTask> saveTimerTasksBySectionPos = new ConcurrentHashMap<>();
public final ConcurrentHashMap<Long, FullDataSourceV2> dataSourceByPosition = new ConcurrentHashMap<>();
private final ConcurrentHashMap<Long, TimerTask> saveTimerTasksBySectionPos = new ConcurrentHashMap<>();
private final ISaveDataSourceFunc onSaveTimeoutFunc;
private final int saveDelayInMs;
@@ -48,7 +47,7 @@ public class DelayedFullDataSourceSaveCache
public void queueDataSourceForUpdateAndSave(FullDataSourceV2 inputDataSource)
{
DhSectionPos dataSourcePos = inputDataSource.getPos();
long dataSourcePos = inputDataSource.getPos();
this.dataSourceByPosition.compute(dataSourcePos, (inputPos, temporaryDataSource) ->
{
if (temporaryDataSource == null)
@@ -7,7 +7,9 @@ import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV1DTO;
import com.seibel.distanthorizons.core.sql.repo.FullDataSourceV1Repo;
import com.seibel.distanthorizons.core.util.objects.DataCorruptedException;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import it.unimi.dsi.fastutil.longs.LongArrayList;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable;
@@ -72,7 +74,7 @@ public class FullDataSourceProviderV1<TDhLevel extends IDhLevel>
}
}
protected FullDataSourceV1 createDataSourceFromDto(FullDataSourceV1DTO dto) throws InterruptedException, IOException
protected FullDataSourceV1 createDataSourceFromDto(FullDataSourceV1DTO dto) throws InterruptedException, IOException, DataCorruptedException
{
FullDataSourceV1 dataSource = FullDataSourceV1.createEmpty(dto.pos);
dataSource.populateFromStream(dto, dto.getInputStream(), this.level);
@@ -91,7 +93,7 @@ public class FullDataSourceProviderV1<TDhLevel extends IDhLevel>
*
* This call is concurrent. I.e. it supports being called by multiple threads at the same time.
*/
public CompletableFuture<FullDataSourceV1> getAsync(DhSectionPos pos)
public CompletableFuture<FullDataSourceV1> getAsync(long pos)
{
ThreadPoolExecutor executor = ThreadPoolUtil.getFileHandlerExecutor();
if (executor == null || executor.isTerminated())
@@ -112,10 +114,10 @@ public class FullDataSourceProviderV1<TDhLevel extends IDhLevel>
/**
* Should only be used in internal file handler methods where we are already running on a file handler thread.
* Can return null.
* @see FullDataSourceProviderV1#getAsync(DhSectionPos)
* @see FullDataSourceProviderV1#getAsync(long)
*/
@Nullable
public FullDataSourceV1 get(DhSectionPos pos)
public FullDataSourceV1 get(Long pos)
{
FullDataSourceV1 dataSource = null;
try
@@ -128,9 +130,16 @@ public class FullDataSourceProviderV1<TDhLevel extends IDhLevel>
}
}
catch (InterruptedException ignore) { }
catch (DataCorruptedException e)
{
// stack trace not included since a lot of corrupt data would cause the log to get quite messy,
// and it should be fairly easy to see what the problem was from the message
LOGGER.warn("Corrupted data found at pos ["+ DhSectionPos.toString(pos)+"]. Data at position will be deleted so it can be re-generated and to prevent future issues. Error: "+e.getMessage());
this.repo.deleteWithKey(pos);
}
catch (IOException e)
{
LOGGER.warn("File read Error for pos ["+pos+"], error: "+e.getMessage(), e);
LOGGER.warn("File read Error for pos ["+ DhSectionPos.toString(pos)+"], error: "+e.getMessage(), e);
}
return dataSource;
@@ -148,10 +157,10 @@ public class FullDataSourceProviderV1<TDhLevel extends IDhLevel>
{
ArrayList<FullDataSourceV1> dataSourceList = new ArrayList<>();
ArrayList<DhSectionPos> migrationPosList = this.repo.getPositionsToMigrate(limit);
LongArrayList migrationPosList = this.repo.getPositionsToMigrate(limit);
for (int i = 0; i < migrationPosList.size(); i++)
{
DhSectionPos pos = migrationPosList.get(i);
Long pos = migrationPosList.getLong(i);
FullDataSourceV1 dataSource = this.get(pos);
if (dataSource != null)
{
@@ -162,7 +171,7 @@ public class FullDataSourceProviderV1<TDhLevel extends IDhLevel>
return dataSourceList;
}
public void markMigrationFailed(DhSectionPos pos) { ((FullDataSourceV1Repo) this.repo).markMigrationFailed(pos); }
public void markMigrationFailed(long pos) { ((FullDataSourceV1Repo) this.repo).markMigrationFailed(pos); }
@@ -35,8 +35,10 @@ import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable;
import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV2DTO;
import com.seibel.distanthorizons.core.sql.repo.FullDataSourceV2Repo;
import com.seibel.distanthorizons.core.util.ThreadUtil;
import com.seibel.distanthorizons.core.util.objects.DataCorruptedException;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import it.unimi.dsi.fastutil.longs.LongArrayList;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable;
@@ -48,7 +50,6 @@ import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Function;
/**
* Handles reading/writing {@link FullDataSourceV2}
@@ -87,6 +88,8 @@ public class FullDataSourceProviderV2
protected final AtomicBoolean migrationThreadRunning = new AtomicBoolean(true);
protected final FullDataSourceProviderV1<IDhLevel> legacyFileHandler;
protected boolean migrationStartMessageQueued = false;
protected long legacyDeletionCount = -1;
protected long migrationCount = -1;
@@ -94,7 +97,7 @@ public class FullDataSourceProviderV2
* Tracks which positions are currently being updated
* to prevent duplicate concurrent updates.
*/
public final Set<DhSectionPos> parentUpdatingPosSet = ConcurrentHashMap.newKeySet();
public final Set<Long> parentUpdatingPosSet = ConcurrentHashMap.newKeySet();
// TODO only run thread if modifications happened recently
/**
@@ -165,11 +168,11 @@ public class FullDataSourceProviderV2
}
@Override
protected FullDataSourceV2 createDataSourceFromDto(FullDataSourceV2DTO dto) throws InterruptedException, IOException
protected FullDataSourceV2 createDataSourceFromDto(FullDataSourceV2DTO dto) throws InterruptedException, IOException, DataCorruptedException
{ return dto.createPooledDataSource(this.level.getLevelWrapper()); }
@Override
protected FullDataSourceV2 makeEmptyDataSource(DhSectionPos pos) { return FullDataSourceV2.DATA_SOURCE_POOL.getPooledSource(pos, true); }
protected FullDataSourceV2 makeEmptyDataSource(long pos) { return FullDataSourceV2.DATA_SOURCE_POOL.getPooledSource(pos, true); }
@@ -198,13 +201,13 @@ public class FullDataSourceProviderV2
&& this.parentUpdatingPosSet.size() < MAX_UPDATE_TASK_COUNT)
{
// get the positions that need to be applied to their parents
ArrayList<DhSectionPos> parentUpdatePosList = this.repo.getPositionsToUpdate(MAX_UPDATE_TASK_COUNT);
LongArrayList parentUpdatePosList = this.repo.getPositionsToUpdate(MAX_UPDATE_TASK_COUNT);
// combine updates together based on their parent
HashMap<DhSectionPos, HashSet<DhSectionPos>> updatePosByParentPos = new HashMap<>();
for (DhSectionPos pos : parentUpdatePosList)
HashMap<Long, HashSet<Long>> updatePosByParentPos = new HashMap<>();
for (Long pos : parentUpdatePosList)
{
updatePosByParentPos.compute(pos.getParentPos(), (parentPos, updatePosSet) ->
updatePosByParentPos.compute(DhSectionPos.getParentPos(pos), (parentPos, updatePosSet) ->
{
if (updatePosSet == null)
{
@@ -216,7 +219,7 @@ public class FullDataSourceProviderV2
}
// queue the updates
for (DhSectionPos parentUpdatePos : updatePosByParentPos.keySet())
for (Long parentUpdatePos : updatePosByParentPos.keySet())
{
// stop if there are already a bunch of updates queued
if (this.parentUpdatingPosSet.size() > MAX_UPDATE_TASK_COUNT
@@ -243,7 +246,7 @@ public class FullDataSourceProviderV2
this.lockedPosSet.add(parentUpdatePos);
// apply each child pos to the parent
for (DhSectionPos childPos : updatePosByParentPos.get(parentUpdatePos))
for (Long childPos : updatePosByParentPos.get(parentUpdatePos))
{
ReentrantLock childReadLock = this.updateLockProvider.getLock(childPos);
try
@@ -263,7 +266,7 @@ public class FullDataSourceProviderV2
}
catch (Exception e)
{
LOGGER.error("issue in update for parent pos: " + parentUpdatePos);
LOGGER.error("issue in update for parent pos: " + parentUpdatePos+ " Error: "+e.getMessage(), e);
}
finally
{
@@ -330,7 +333,7 @@ public class FullDataSourceProviderV2
{
// this should only be shown once per session but should be shown during
// either when the deletion or migration phases start
ClientApi.INSTANCE.showMigrationMessageOnNextFrame();
this.showMigrationStartMessage();
LOGGER.info("deleting [" + dimensionName + "] - ["+totalDeleteCount+"] unused data sources...");
@@ -381,7 +384,7 @@ public class FullDataSourceProviderV2
ArrayList<FullDataSourceV1> legacyDataSourceList = this.legacyFileHandler.getDataSourcesToMigrate(MIGRATION_BATCH_COUNT);
if (!legacyDataSourceList.isEmpty())
{
ClientApi.INSTANCE.showMigrationMessageOnNextFrame();
this.showMigrationStartMessage();
// keep going until every data source has been migrated
@@ -420,7 +423,7 @@ public class FullDataSourceProviderV2
}
catch (Exception e)
{
DhSectionPos migrationPos = legacyDataSource.getPos();
Long migrationPos = legacyDataSource.getPos();
LOGGER.warn("Unexpected issue migrating data source at pos " + migrationPos + ". Error: " + e.getMessage(), e);
this.legacyFileHandler.markMigrationFailed(migrationPos);
}
@@ -452,11 +455,13 @@ public class FullDataSourceProviderV2
if (this.migrationThreadRunning.get())
{
LOGGER.info("migration complete for: ["+dimensionName+"]-["+this.saveDir+"].");
this.showMigrationEndMessage(true);
this.migrationCount = 0;
}
else
{
LOGGER.info("migration stopped for: ["+dimensionName+"]-["+this.saveDir+"].");
this.showMigrationEndMessage(false);
}
}
else
@@ -471,6 +476,41 @@ public class FullDataSourceProviderV2
public long getTotalMigrationCount() { return this.migrationCount; }
private void showMigrationStartMessage()
{
if (this.migrationStartMessageQueued)
{
return;
}
this.migrationStartMessageQueued = true;
String dimName = this.level.getLevelWrapper().getDimensionType().getDimensionName();
ClientApi.INSTANCE.showChatMessageNextFrame(
"Old Distant Horizons data is being migrated for ["+dimName+"]. \n" +
"While migrating LODs may load slowly \n" +
"and DH world gen will be disabled. \n" +
"You can see migration progress in the F3 menu."
);
}
private void showMigrationEndMessage(boolean success)
{
String dimName = this.level.getLevelWrapper().getDimensionType().getDimensionName();
if (success)
{
ClientApi.INSTANCE.showChatMessageNextFrame("Distant Horizons data migration for ["+dimName+"] completed.");
}
else
{
ClientApi.INSTANCE.showChatMessageNextFrame(
"Distant Horizons data migration for ["+dimName+"] stopped. \n" +
"Some data may not have been migrated."
);
}
}
//=======================//
// retrieval (world gen) //
@@ -515,18 +555,18 @@ public class FullDataSourceProviderV2
* an empty array if all positions were generated
*/
@Nullable
public ArrayList<DhSectionPos> getPositionsToRetrieve(DhSectionPos pos) { return null; }
public LongArrayList getPositionsToRetrieve(Long pos) { return null; }
/**
* Returns how many positions could potentially be generated for this position assuming the position is empty.
* Used when estimating the total number of retrieval requests.
*/
public int getMaxPossibleRetrievalPositionCountForPos(DhSectionPos pos) { return -1; }
public int getMaxPossibleRetrievalPositionCountForPos(Long pos) { return -1; }
/** @return true if the position was queued, false if not */
public boolean queuePositionForRetrieval(DhSectionPos genPos) { return false; }
public boolean queuePositionForRetrieval(Long genPos) { return false; }
/** does nothing if the given position isn't present in the queue */
public void removeRetrievalRequestIf(Function<DhSectionPos, Boolean> removeIf) { }
public void removeRetrievalRequestIf(DhSectionPos.ICancelablePrimitiveLongConsumer removeIf) { }
public void clearRetrievalQueue() { }
@@ -34,6 +34,7 @@ import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import com.seibel.distanthorizons.coreapi.util.BitShiftUtil;
import it.unimi.dsi.fastutil.longs.LongArrayList;
import org.apache.logging.log4j.Logger;
import java.awt.*;
@@ -41,12 +42,19 @@ import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.function.Function;
public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 implements IDebugRenderable
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
/**
* Having this number too high causes the system to become overwhelmed by
* world gen requests and other jobs won't be done. <br>
* IE: LODs won't update or render because world gen is hogging the CPU.
* <br><br>
* TODO this should be dynamically allocated based on CPU load
* and abilities.
*/
public static final int MAX_WORLD_GEN_REQUESTS_PER_THREAD = 20;
@@ -108,7 +116,7 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
}
// TODO only fire after the section has finished generated or once every X seconds
private void fireOnGenPosSuccessListeners(DhSectionPos pos)
private void fireOnGenPosSuccessListeners(long pos)
{
// fire the event listeners
for (IOnWorldGenCompleteListener listener : this.onWorldGenTaskCompleteListeners)
@@ -172,6 +180,15 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
}
ThreadPoolExecutor fileExecutor = ThreadPoolUtil.getFileHandlerExecutor();
if (fileExecutor == null || fileExecutor.getQueue().size() >= MAX_UPDATE_TASK_COUNT / 2)
{
// don't queue additional world gen requests if the file handler is overwhelmed,
// otherwise LODs may not load in properly
return false;
}
int maxQueueCount = MAX_WORLD_GEN_REQUESTS_PER_THREAD * Config.Client.Advanced.MultiThreading.numberOfWorldGenerationThreads.get();
if (this.delayedFullDataSourceSaveCache.getUnsavedCount() >= maxQueueCount)
@@ -188,7 +205,7 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
}
@Override
public boolean queuePositionForRetrieval(DhSectionPos genPos)
public boolean queuePositionForRetrieval(Long genPos)
{
IFullDataSourceRetrievalQueue worldGenQueue = this.worldGenQueueRef.get();
if (worldGenQueue == null)
@@ -197,14 +214,14 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
}
GenTask genTask = new GenTask(genPos);
CompletableFuture<WorldGenResult> worldGenFuture = worldGenQueue.submitGenTask(genPos, (byte) (genPos.getDetailLevel() - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL), genTask);
CompletableFuture<WorldGenResult> worldGenFuture = worldGenQueue.submitGenTask(genPos, (byte) (DhSectionPos.getDetailLevel(genPos) - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL), genTask);
worldGenFuture.whenComplete((genTaskResult, ex) -> this.onWorldGenTaskComplete(genTaskResult, ex));
return true;
}
@Override
public void removeRetrievalRequestIf(Function<DhSectionPos, Boolean> removeIf)
public void removeRetrievalRequestIf(DhSectionPos.ICancelablePrimitiveLongConsumer removeIf)
{
IFullDataSourceRetrievalQueue worldGenQueue = this.worldGenQueueRef.get();
if (worldGenQueue != null)
@@ -221,7 +238,7 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
@Override
public ArrayList<DhSectionPos> getPositionsToRetrieve(DhSectionPos pos)
public LongArrayList getPositionsToRetrieve(Long pos)
{
IFullDataSourceRetrievalQueue worldGenQueue = this.worldGenQueueRef.get();
if (worldGenQueue == null)
@@ -251,7 +268,7 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
if (positionFullyGenerated)
{
return new ArrayList<>();
return new LongArrayList();
}
}
}
@@ -260,9 +277,9 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
// this section is missing one or more columns, queue the missing ones for generation.
// TODO speed up this logic by only checking ungenerated columns
ArrayList<DhSectionPos> generationList = new ArrayList<>();
LongArrayList generationList = new LongArrayList();
byte minGeneratorSectionDetailLevel = (byte) (worldGenQueue.highestDataDetail() + DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL);
pos.forEachChildAtDetailLevel(minGeneratorSectionDetailLevel, (genPos) ->
DhSectionPos.forEachChildAtDetailLevel(pos, minGeneratorSectionDetailLevel, (genPos) ->
{
if (!this.repo.existsWithKey(genPos))
{
@@ -319,7 +336,7 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
}
@Override
public int getMaxPossibleRetrievalPositionCountForPos(DhSectionPos pos)
public int getMaxPossibleRetrievalPositionCountForPos(Long pos)
{
IFullDataSourceRetrievalQueue worldGenQueue = this.worldGenQueueRef.get();
if (worldGenQueue == null)
@@ -328,7 +345,7 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
}
int minGeneratorSectionDetailLevel = worldGenQueue.highestDataDetail() + DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL;
int detailLevelDiff = pos.getDetailLevel() - minGeneratorSectionDetailLevel;
int detailLevelDiff = DhSectionPos.getDetailLevel(pos) - minGeneratorSectionDetailLevel;
return BitShiftUtil.powerOfTwo(detailLevelDiff);
}
@@ -358,12 +375,9 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
// TODO may not be needed
private class GenTask implements IWorldGenTaskTracker
{
private final DhSectionPos pos;
private final long pos;
public GenTask(DhSectionPos pos)
{
this.pos = pos;
}
public GenTask(long pos) { this.pos = pos; }
@@ -389,7 +403,7 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
public interface IOnWorldGenCompleteListener
{
/** Fired whenever a section has completed generating */
void onWorldGenTaskComplete(DhSectionPos pos);
void onWorldGenTaskComplete(long pos);
}
@@ -57,7 +57,18 @@ public class SubDimCompare implements Comparable<SubDimCompare>
}
/** returns a number between 0 (no equal datapoint) and 1 (totally equal) */
public double getPercentEqual() { return (double) this.equalDataPoints / (double) this.totalDataPoints; }
public double getPercentEqual()
{
// its possible the comparison didn't find any data points
if (this.totalDataPoints != 0)
{
return (double) this.equalDataPoints / (double) this.totalDataPoints;
}
else
{
return 0;
}
}
@Override
@@ -190,7 +190,7 @@ public class SubDimensionLevelMatcher implements AutoCloseable
}
FullDataSourceV2 newChunkSizedFullDataView = FullDataSourceV2.createFromChunk(newlyLoadedChunk);
// convert to a data source for easier comparing
FullDataSourceV2 newDataSource = FullDataSourceV2.createEmpty(new DhSectionPos(this.playerData.playerBlockPos));
FullDataSourceV2 newDataSource = FullDataSourceV2.createEmpty(DhSectionPos.encode(this.playerData.playerBlockPos));
newDataSource.update(newChunkSizedFullDataView);
@@ -208,21 +208,25 @@ public class SubDimensionLevelMatcher implements AutoCloseable
for (File testLevelFolder : this.potentialLevelFolders)
{
LOGGER.info("Testing level folder: [" + LodUtil.shortenString(testLevelFolder.getName(), 8) + "]");
FullDataSourceV2 testFullDataSource = null;
try
{
// get the data source to compare against
IDhLevel tempLevel = new DhClientLevel(new ClientOnlySaveStructure(), this.currentClientLevel, testLevelFolder, false);
FullDataSourceV2 testFullDataSource = tempLevel.getFullDataProvider().getAsync(new DhSectionPos(this.playerData.playerBlockPos)).join();
if (testFullDataSource == null)
try (IDhLevel tempLevel = new DhClientLevel(new ClientOnlySaveStructure(), this.currentClientLevel, testLevelFolder, false))
{
continue;
testFullDataSource = tempLevel.getFullDataProvider().getAsync(DhSectionPos.encode(this.playerData.playerBlockPos)).join();
if (testFullDataSource == null)
{
continue;
}
}
// confirm both data sources have the same section pos
DhSectionPos newSectionChunkPos = newDataSource.getPos().convertNewToDetailLevel(DhSectionPos.SECTION_CHUNK_DETAIL_LEVEL);
DhSectionPos testSectionChunkPos = testFullDataSource.getPos().convertNewToDetailLevel(DhSectionPos.SECTION_CHUNK_DETAIL_LEVEL);
LodUtil.assertTrue(newSectionChunkPos.equals(testSectionChunkPos), "data source positions don't match");
long newSectionChunkPos = DhSectionPos.convertToDetailLevel(newDataSource.getPos(), DhSectionPos.SECTION_CHUNK_DETAIL_LEVEL);
long testSectionChunkPos = DhSectionPos.convertToDetailLevel(testFullDataSource.getPos(), DhSectionPos.SECTION_CHUNK_DETAIL_LEVEL);
LodUtil.assertTrue(newSectionChunkPos == testSectionChunkPos, "data source positions don't match");
@@ -334,6 +338,13 @@ public class SubDimensionLevelMatcher implements AutoCloseable
// for now we are just assuming it is an unrelated file
LOGGER.warn("Error checking level: "+e.getMessage(), e);
}
finally
{
if (testFullDataSource != null)
{
try { testFullDataSource.close(); } catch (Exception ignore) {}
}
}
}
@@ -27,7 +27,6 @@ import com.seibel.distanthorizons.core.render.LodQuadTree;
import java.io.Closeable;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
/**
* Used to track what full data sources the system currently
@@ -80,9 +79,9 @@ public interface IFullDataSourceRetrievalQueue extends Closeable
* Generally the retrieval queue should be fairly small, so its faster to iterate over the existing list
* and check if each one is valid vs dumbly attempting to remove every position that just went out of range.
*/
void removeRetrievalRequestIf(Function<DhSectionPos, Boolean> removeIf);
void removeRetrievalRequestIf(DhSectionPos.ICancelablePrimitiveLongConsumer removeIf);
CompletableFuture<WorldGenResult> submitGenTask(DhSectionPos pos, byte requiredDataDetail, IWorldGenTaskTracker tracker);
CompletableFuture<WorldGenResult> submitGenTask(long pos, byte requiredDataDetail, IWorldGenTaskTracker tracker);
@@ -36,18 +36,19 @@ import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable;
import com.seibel.distanthorizons.core.util.LodUtil.AssertFailureException;
import com.seibel.distanthorizons.core.util.ThreadUtil;
import com.seibel.distanthorizons.core.util.objects.DataCorruptedException;
import com.seibel.distanthorizons.core.util.objects.UncheckedInterruptedException;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue;
import org.apache.logging.log4j.Logger;
import java.awt.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.function.Consumer;
import java.util.function.Function;
public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDebugRenderable
{
@@ -57,9 +58,9 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
private final IDhApiWorldGenerator generator;
/** contains the positions that need to be generated */
private final ConcurrentHashMap<DhSectionPos, WorldGenTask> waitingTasks = new ConcurrentHashMap<>();
private final ConcurrentHashMap<Long, WorldGenTask> waitingTasks = new ConcurrentHashMap<>();
private final ConcurrentHashMap<DhSectionPos, InProgressWorldGenTaskGroup> inProgressGenTasksByLodPos = new ConcurrentHashMap<>();
private final ConcurrentHashMap<Long, InProgressWorldGenTaskGroup> inProgressGenTasksByLodPos = new ConcurrentHashMap<>();
// granularity is the detail level for batching world generator requests together
public final byte maxGranularity;
@@ -88,8 +89,8 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
// debug variables to test for duplicate world generator requests //
/** limits how many of the previous world gen requests we should track */
private static final int MAX_ALREADY_GENERATED_COUNT = 100;
private final HashMap<DhSectionPos, StackTraceElement[]> alreadyGeneratedPosHashSet = new HashMap<>(MAX_ALREADY_GENERATED_COUNT);
private final Queue<DhSectionPos> alreadyGeneratedPosQueue = new LinkedList<>();
private final HashMap<Long, StackTraceElement[]> alreadyGeneratedPosHashSet = new HashMap<>(MAX_ALREADY_GENERATED_COUNT);
private final LongArrayFIFOQueue alreadyGeneratedPosQueue = new LongArrayFIFOQueue();
/** just used for rendering to the F3 menu */
private int estimatedTotalTaskCount = 0;
@@ -130,7 +131,7 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
//=================//
@Override
public CompletableFuture<WorldGenResult> submitGenTask(DhSectionPos pos, byte requiredDataDetail, IWorldGenTaskTracker tracker)
public CompletableFuture<WorldGenResult> submitGenTask(long pos, byte requiredDataDetail, IWorldGenTaskTracker tracker)
{
// the generator is shutting down, don't add new tasks
if (this.generatorClosingFuture != null)
@@ -150,7 +151,7 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
}
// Assert that the data at least can fill in 1 single ChunkSizedFullDataAccessor
LodUtil.assertTrue(pos.getDetailLevel() > requiredDataDetail + LodUtil.CHUNK_DETAIL_LEVEL);
LodUtil.assertTrue(DhSectionPos.getDetailLevel(pos) > requiredDataDetail + LodUtil.CHUNK_DETAIL_LEVEL);
CompletableFuture<WorldGenResult> future = new CompletableFuture<>();
@@ -159,11 +160,11 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
}
@Override
public void removeRetrievalRequestIf(Function<DhSectionPos, Boolean> removeIf)
public void removeRetrievalRequestIf(DhSectionPos.ICancelablePrimitiveLongConsumer removeIf)
{
this.waitingTasks.forEachKey(100, (genPos) ->
{
if (removeIf.apply(genPos))
if (removeIf.accept(genPos))
{
this.waitingTasks.remove(genPos);
}
@@ -253,7 +254,7 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
Mapper closestTaskMap = this.waitingTasks.reduceEntries(1024,
entry -> new Mapper(entry.getValue(), entry.getValue().pos.getSectionBBoxPos().getCenterBlockPos().toPos2D().chebyshevDist(targetPos.toPos2D())),
entry -> new Mapper(entry.getValue(), DhSectionPos.getSectionBBoxPos(entry.getValue().pos).getCenterBlockPos().toPos2D().chebyshevDist(targetPos.toPos2D())),
(aMapper, bMapper) -> aMapper.dist < bMapper.dist ? aMapper : bMapper);
if (closestTaskMap == null)
@@ -306,14 +307,14 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
// split up the task and add each one to the tree
LinkedList<CompletableFuture<WorldGenResult>> childFutures = new LinkedList<>();
DhSectionPos sectionPos = new DhSectionPos(closestTask.pos.getDetailLevel(), closestTask.pos.getX(), closestTask.pos.getZ());
long sectionPos = closestTask.pos;
WorldGenTask finalClosestTask = closestTask;
sectionPos.forEachChild((childDhSectionPos) ->
DhSectionPos.forEachChild(sectionPos, (childDhSectionPos) ->
{
CompletableFuture<WorldGenResult> newFuture = new CompletableFuture<>();
childFutures.add(newFuture);
WorldGenTask newGenTask = new WorldGenTask(childDhSectionPos, childDhSectionPos.getDetailLevel(), finalClosestTask.taskTracker, newFuture);
WorldGenTask newGenTask = new WorldGenTask(childDhSectionPos, DhSectionPos.getDetailLevel(childDhSectionPos), finalClosestTask.taskTracker, newFuture);
this.waitingTasks.put(newGenTask.pos, newGenTask);
});
@@ -328,12 +329,12 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
private boolean tryStartingWorldGenTaskGroup(InProgressWorldGenTaskGroup newTaskGroup)
{
byte taskDetailLevel = newTaskGroup.group.dataDetail;
DhSectionPos taskPos = newTaskGroup.group.pos;
byte granularity = (byte) (taskPos.getDetailLevel() - taskDetailLevel);
long taskPos = newTaskGroup.group.pos;
byte granularity = (byte) (DhSectionPos.getDetailLevel(taskPos) - taskDetailLevel);
LodUtil.assertTrue(granularity >= this.minGranularity && granularity <= this.maxGranularity);
LodUtil.assertTrue(taskDetailLevel >= this.highestDataDetail && taskDetailLevel <= this.lowestDataDetail);
DhChunkPos chunkPosMin = new DhChunkPos(taskPos.getSectionBBoxPos().getCornerBlockPos());
DhChunkPos chunkPosMin = new DhChunkPos(DhSectionPos.getSectionBBoxPos(taskPos).getCornerBlockPos());
// check if this is a duplicate generation task
if (this.alreadyGeneratedPosHashSet.containsKey(newTaskGroup.group.pos))
@@ -342,16 +343,16 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
//LOGGER.trace("Duplicate generation section " + taskPos + " with granularity [" + granularity + "] at " + chunkPosMin + ". Skipping...");
// sending a success result is necessary to make sure the render sections are reloaded correctly
newTaskGroup.group.worldGenTasks.forEach(worldGenTask -> worldGenTask.future.complete(WorldGenResult.CreateSuccess(new DhSectionPos(granularity, taskPos.getX(), taskPos.getZ()))));
newTaskGroup.group.worldGenTasks.forEach(worldGenTask -> worldGenTask.future.complete(WorldGenResult.CreateSuccess(DhSectionPos.encode(granularity, DhSectionPos.getX(taskPos), DhSectionPos.getZ(taskPos)))));
return false;
}
this.alreadyGeneratedPosHashSet.put(newTaskGroup.group.pos, Thread.currentThread().getStackTrace());
this.alreadyGeneratedPosQueue.add(newTaskGroup.group.pos);
this.alreadyGeneratedPosQueue.enqueue(newTaskGroup.group.pos);
// remove extra tracked duplicate positions
while (this.alreadyGeneratedPosQueue.size() > MAX_ALREADY_GENERATED_COUNT)
{
DhSectionPos posToRemove = this.alreadyGeneratedPosQueue.poll();
long posToRemove = this.alreadyGeneratedPosQueue.dequeueLong();
this.alreadyGeneratedPosHashSet.remove(posToRemove);
}
@@ -379,8 +380,7 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
}
else
{
//LOGGER.info("Section generation at "+pos+" completed");
newTaskGroup.group.worldGenTasks.forEach(worldGenTask -> worldGenTask.future.complete(WorldGenResult.CreateSuccess(new DhSectionPos(granularity, taskPos.getX(), taskPos.getZ()))));
newTaskGroup.group.worldGenTasks.forEach(worldGenTask -> worldGenTask.future.complete(WorldGenResult.CreateSuccess(DhSectionPos.encode(granularity, DhSectionPos.getX(taskPos), DhSectionPos.getZ(taskPos)))));
}
boolean worked = this.inProgressGenTasksByLodPos.remove(taskPos, newTaskGroup);
LodUtil.assertTrue(worked);
@@ -469,6 +469,11 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
FullDataSourceV2 dataSource = LodDataBuilder.createFromApiChunkData(dataPoints);
chunkDataConsumer.accept(dataSource);
}
catch (DataCorruptedException e)
{
LOGGER.error("World generator returned a corrupt chunk. Error: [" + e.getMessage() + "]. World generator disabled.", e);
Config.Client.Advanced.WorldGenerator.enableDistantGeneration.set(false);
}
catch (ClassCastException e)
{
LOGGER.error("World generator return type incorrect. Error: [" + e.getMessage() + "]. World generator disabled.", e);
@@ -618,9 +623,9 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
// helper methods //
//================//
private boolean canGeneratePos(byte worldGenTaskGroupDetailLevel /*when in doubt use 0*/ , DhSectionPos taskPos)
private boolean canGeneratePos(byte worldGenTaskGroupDetailLevel /*when in doubt use 0*/ , long taskPos)
{
byte granularity = (byte) (taskPos.getDetailLevel() - worldGenTaskGroupDetailLevel);
byte granularity = (byte) (DhSectionPos.getDetailLevel(taskPos) - worldGenTaskGroupDetailLevel);
return (granularity >= this.minGranularity && granularity <= this.maxGranularity);
}
@@ -19,8 +19,6 @@
package com.seibel.distanthorizons.core.generation.tasks;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import java.util.Collection;
import java.util.LinkedList;
import java.util.concurrent.CompletableFuture;
@@ -30,15 +28,15 @@ public class WorldGenResult
/** true if terrain was generated */
public final boolean success;
/** the position that was generated, will be null if nothing was generated */
public final DhSectionPos pos;
public final long pos;
/** if a position is too high detail for world generator to handle it, these futures are for its 4 children positions after being split up. */
public final LinkedList<CompletableFuture<WorldGenResult>> childFutures = new LinkedList<>();
public static WorldGenResult CreateSplit(Collection<CompletableFuture<WorldGenResult>> siblingFutures) { return new WorldGenResult(false, null, siblingFutures); }
public static WorldGenResult CreateFail() { return new WorldGenResult(false, null, null); }
public static WorldGenResult CreateSuccess(DhSectionPos pos) { return new WorldGenResult(true, pos, null); }
private WorldGenResult(boolean success, DhSectionPos pos, Collection<CompletableFuture<WorldGenResult>> childFutures)
public static WorldGenResult CreateSplit(Collection<CompletableFuture<WorldGenResult>> siblingFutures) { return new WorldGenResult(false, 0, siblingFutures); }
public static WorldGenResult CreateFail() { return new WorldGenResult(false, 0, null); }
public static WorldGenResult CreateSuccess(long pos) { return new WorldGenResult(true, pos, null); }
private WorldGenResult(boolean success, long pos, Collection<CompletableFuture<WorldGenResult>> childFutures)
{
this.success = success;
this.pos = pos;
@@ -19,9 +19,6 @@
package com.seibel.distanthorizons.core.generation.tasks;
import com.seibel.distanthorizons.core.pos.DhLodPos;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import java.util.concurrent.CompletableFuture;
/**
@@ -30,14 +27,14 @@ import java.util.concurrent.CompletableFuture;
*/
public final class WorldGenTask
{
public final DhSectionPos pos;
public final long pos;
public final byte dataDetailLevel;
public final IWorldGenTaskTracker taskTracker;
public final CompletableFuture<WorldGenResult> future;
public WorldGenTask(DhSectionPos pos, byte dataDetail, IWorldGenTaskTracker taskTracker, CompletableFuture<WorldGenResult> future)
public WorldGenTask(long pos, byte dataDetail, IWorldGenTaskTracker taskTracker, CompletableFuture<WorldGenResult> future)
{
this.dataDetailLevel = dataDetail;
this.pos = pos;
@@ -20,7 +20,6 @@
package com.seibel.distanthorizons.core.generation.tasks;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import java.util.Iterator;
import java.util.LinkedList;
@@ -33,14 +32,14 @@ import java.util.function.Consumer;
@Deprecated // TODO look into how these are used and if they should continue to be used
public final class WorldGenTaskGroup
{
public final DhSectionPos pos;
public final long pos;
public byte dataDetail;
/** Only accessed by the generator polling thread */
public final LinkedList<WorldGenTask> worldGenTasks = new LinkedList<>();
public WorldGenTaskGroup(DhSectionPos pos, byte dataDetail)
public WorldGenTaskGroup(long pos, byte dataDetail)
{
this.pos = pos;
this.dataDetail = dataDetail;
@@ -37,7 +37,7 @@ public abstract class AbstractDhLevel implements IDhLevel
protected final DelayedFullDataSourceSaveCache delayedFullDataSourceSaveCache = new DelayedFullDataSourceSaveCache(this::onDataSourceSave, 2_000);
/** contains the {@link DhChunkPos} for each {@link DhSectionPos} that are queued to save via {@link AbstractDhLevel#delayedFullDataSourceSaveCache} */
protected final ConcurrentHashMap<DhSectionPos, HashSet<DhChunkPos>> updatedChunkPosSetBySectionPos = new ConcurrentHashMap<>();
protected final ConcurrentHashMap<Long, HashSet<DhChunkPos>> updatedChunkPosSetBySectionPos = new ConcurrentHashMap<>();
@@ -29,7 +29,6 @@ import com.seibel.distanthorizons.core.file.fullDatafile.FullDataSourceProviderV
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.logging.f3.F3Screen;
import com.seibel.distanthorizons.core.pos.DhBlockPos2D;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.render.LodQuadTree;
import com.seibel.distanthorizons.core.render.RenderBufferHandler;
import com.seibel.distanthorizons.core.render.renderer.LodRenderer;
@@ -313,7 +312,7 @@ public class ClientLevelModule implements Closeable, AbstractDataSourceHandler.I
}
}
public void reloadPos(DhSectionPos pos)
public void reloadPos(long pos)
{
ClientRenderState clientRenderState = this.ClientRenderStateRef.get();
if (clientRenderState != null && clientRenderState.quadtree != null)
@@ -28,7 +28,6 @@ import com.seibel.distanthorizons.core.file.structure.AbstractSaveStructure;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.DhBlockPos2D;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
@@ -199,7 +198,7 @@ public class DhClientServerLevel extends AbstractDhLevel implements IDhClientLev
}
@Override
public void onWorldGenTaskComplete(DhSectionPos pos)
public void onWorldGenTaskComplete(long pos)
{
DebugRenderer.makeParticle(
new DebugRenderer.BoxParticle(
@@ -23,7 +23,6 @@ import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSour
import com.seibel.distanthorizons.core.file.fullDatafile.FullDataSourceProviderV2;
import com.seibel.distanthorizons.core.file.structure.AbstractSaveStructure;
import com.seibel.distanthorizons.core.pos.DhBlockPos2D;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
@@ -105,7 +104,7 @@ public class DhServerLevel extends AbstractDhLevel implements IDhServerLevel
public boolean hasSkyLight() { return this.serverLevelWrapper.hasSkyLight(); }
@Override
public void onWorldGenTaskComplete(DhSectionPos pos)
public void onWorldGenTaskComplete(long pos)
{
//TODO: Send packet to client
}
@@ -111,29 +111,10 @@ public class ConfigBasedLogger
}
}
public void error(String str, Object... param)
{
log(Level.ERROR, str, param);
}
public void warn(String str, Object... param)
{
log(Level.WARN, str, param);
}
public void info(String str, Object... param)
{
log(Level.INFO, str, param);
}
public void debug(String str, Object... param)
{
log(Level.DEBUG, str, param);
}
public void trace(String str, Object... param)
{
log(Level.TRACE, str, param);
}
public void error(String str, Object... param) { this.log(Level.ERROR, str, param); }
public void warn(String str, Object... param) { this.log(Level.WARN, str, param); }
public void info(String str, Object... param) { this.log(Level.INFO, str, param); }
public void debug(String str, Object... param) { this.log(Level.DEBUG, str, param); }
public void trace(String str, Object... param) { this.log(Level.TRACE, str, param); }
}
@@ -53,7 +53,7 @@ public class DhLodPos implements Comparable<DhLodPos>
this.x = x;
this.z = z;
}
public DhLodPos(DhSectionPos sectionPos) { this(sectionPos.getDetailLevel(), sectionPos.getX(), sectionPos.getZ()); }
public DhLodPos(long sectionPos) { this(DhSectionPos.getDetailLevel(sectionPos), DhSectionPos.getX(sectionPos), DhSectionPos.getZ(sectionPos)); }
@@ -165,7 +165,7 @@ public class DhLodPos implements Comparable<DhLodPos>
* @param sectionDetailLevel This is different from the normal LOD Detail level, see {@link DhSectionPos} for more information
* @throws IllegalArgumentException if this position's detail level is lower than the output detail level
*/
public DhSectionPos getSectionPosWithSectionDetailLevel(byte sectionDetailLevel) throws IllegalArgumentException
public long getSectionPosWithSectionDetailLevel(byte sectionDetailLevel) throws IllegalArgumentException
{
if (sectionDetailLevel < this.detailLevel)
{
@@ -174,7 +174,7 @@ public class DhLodPos implements Comparable<DhLodPos>
DhLodPos lodPos = new DhLodPos(this.detailLevel, this.x, this.z);
lodPos = lodPos.convertToDetailLevel(sectionDetailLevel);
return new DhSectionPos(lodPos.detailLevel, lodPos.x, lodPos.z);
return DhSectionPos.encode(lodPos.detailLevel, lodPos.x, lodPos.z);
}
@@ -20,12 +20,10 @@
package com.seibel.distanthorizons.core.pos;
import com.seibel.distanthorizons.core.enums.EDhDirection;
import com.seibel.distanthorizons.coreapi.util.BitShiftUtil;
import com.seibel.distanthorizons.core.util.LodUtil;
import org.jetbrains.annotations.Nullable;
import com.seibel.distanthorizons.coreapi.util.BitShiftUtil;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.LongConsumer;
/**
* The position object used to define LOD objects in the quad trees. <br><br>
@@ -49,19 +47,29 @@ public class DhSectionPos
* The lowest detail level a Section position can hold.
* This section DetailLevel holds 64 x 64 Block level (detail level 0) LODs.
*/
public final static byte SECTION_MINIMUM_DETAIL_LEVEL = 6;
public static final byte SECTION_MINIMUM_DETAIL_LEVEL = 6;
public final static byte SECTION_BLOCK_DETAIL_LEVEL = SECTION_MINIMUM_DETAIL_LEVEL + LodUtil.BLOCK_DETAIL_LEVEL;
public final static byte SECTION_CHUNK_DETAIL_LEVEL = SECTION_MINIMUM_DETAIL_LEVEL + LodUtil.CHUNK_DETAIL_LEVEL;
public final static byte SECTION_REGION_DETAIL_LEVEL = SECTION_MINIMUM_DETAIL_LEVEL + LodUtil.REGION_DETAIL_LEVEL;
public static final byte SECTION_BLOCK_DETAIL_LEVEL = SECTION_MINIMUM_DETAIL_LEVEL + LodUtil.BLOCK_DETAIL_LEVEL;
public static final byte SECTION_CHUNK_DETAIL_LEVEL = SECTION_MINIMUM_DETAIL_LEVEL + LodUtil.CHUNK_DETAIL_LEVEL;
public static final byte SECTION_REGION_DETAIL_LEVEL = SECTION_MINIMUM_DETAIL_LEVEL + LodUtil.REGION_DETAIL_LEVEL;
protected byte detailLevel;
/** in a sectionDetailLevel grid */
protected int x;
/** in a sectionDetailLevel grid */
protected int z;
public static final int DETAIL_LEVEL_WIDTH = 8;
public static final int X_POS_WIDTH = 28;
public static final int Z_POS_WIDTH = 28;
public static final int X_POS_MISSING_WIDTH = 32 - 28;
public static final int Z_POS_MISSING_WIDTH = 32 - 28;
public static final int DETAIL_LEVEL_OFFSET = 0;
public static final int POS_X_OFFSET = DETAIL_LEVEL_OFFSET + DETAIL_LEVEL_WIDTH;
/** indicates the Y position where the LOD starts relative to the level's minimum height */
public static final int POS_Z_OFFSET = POS_X_OFFSET + X_POS_WIDTH;
public static final long DETAIL_LEVEL_MASK = Byte.MAX_VALUE;
public static final int POS_X_MASK = (int) Math.pow(2, X_POS_WIDTH) - 1;
public static final int POS_Z_MASK = (int) Math.pow(2, Z_POS_WIDTH) - 1;
@@ -69,35 +77,45 @@ public class DhSectionPos
// constructors //
//==============//
public DhSectionPos(byte detailLevel, int x, int z)
/**
* This class just holds utility methods for handling a packed
* {@link DhSectionPos} and shouldn't be constructed. <Br><br>
*
* Use one of the {@link DhSectionPos#encode(byte, int, int)} methods instead
*/
private DhSectionPos() { }
/**
* Note:
* no validation is done for whether the detail level is positive
* or if the X/Z positions can be represented by available bits.
*/
public static long encode(byte detailLevel, int x, int z)
{
this.detailLevel = detailLevel;
this.x = x;
this.z = z;
long data = 0;
data |= detailLevel & DETAIL_LEVEL_MASK;
data |= (long) (x & POS_X_MASK) << POS_X_OFFSET;
data |= (long) (z & POS_Z_MASK) << POS_Z_OFFSET;
return data;
}
public DhSectionPos(DhBlockPos blockPos)
public static long encode(DhBlockPos pos) { return encodeBlockPos(pos.x, pos.z); }
public static long encode(DhBlockPos2D pos) { return encodeBlockPos(pos.x, pos.z); }
public static long encodeBlockPos(int blockX, int blockZ)
{
this(LodUtil.BLOCK_DETAIL_LEVEL, blockPos.x, blockPos.z);
this.convertSelfToDetailLevel(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL);
}
public DhSectionPos(DhBlockPos2D blockPos)
{
this(LodUtil.BLOCK_DETAIL_LEVEL, blockPos.x, blockPos.z);
this.convertSelfToDetailLevel(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL);
long pos = encode(LodUtil.BLOCK_DETAIL_LEVEL, blockX, blockZ);
pos = convertToDetailLevel(pos, DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL);
return pos;
}
public DhSectionPos(DhChunkPos chunkPos)
public static long encode(DhChunkPos pos) { return encodeChunkPos(pos.x, pos.z); }
public static long encodeChunkPos(int chunkX, int chunkZ)
{
this(LodUtil.CHUNK_DETAIL_LEVEL, chunkPos.x, chunkPos.z);
this.convertSelfToDetailLevel(DhSectionPos.SECTION_CHUNK_DETAIL_LEVEL);
}
public DhSectionPos(byte detailLevel, DhLodPos dhLodPos)
{
this.detailLevel = detailLevel;
this.x = dhLodPos.x;
this.z = dhLodPos.z;
long pos = encode(LodUtil.CHUNK_DETAIL_LEVEL, chunkX, chunkZ);
pos = convertToDetailLevel(pos, DhSectionPos.SECTION_CHUNK_DETAIL_LEVEL);
return pos;
}
@@ -105,36 +123,27 @@ public class DhSectionPos
//============//
// converters //
//============//
/**
* uses the absolute detail level aka detail levels like {@link LodUtil#CHUNK_DETAIL_LEVEL} instead of the dhSectionPos detailLevels.
*
* @return the new position closest to negative infinity with the new detail level
*/
public DhSectionPos convertNewToDetailLevel(byte newSectionDetailLevel)
{
DhSectionPos newPos = new DhSectionPos(this.detailLevel, this.x, this.z);
newPos.convertSelfToDetailLevel(newSectionDetailLevel);
return newPos;
}
/** uses the absolute detail level aka detail levels like {@link LodUtil#CHUNK_DETAIL_LEVEL} instead of the dhSectionPos detailLevels. */
protected void convertSelfToDetailLevel(byte newDetailLevel)
public static long convertToDetailLevel(long pos, byte newDetailLevel)
{
byte detailLevel = getDetailLevel(pos);
int x = getX(pos);
int z = getZ(pos);
// logic originally taken from DhLodPos
if (newDetailLevel >= this.detailLevel)
if (newDetailLevel >= detailLevel)
{
this.x = Math.floorDiv(this.x, BitShiftUtil.powerOfTwo(newDetailLevel - this.detailLevel));
this.z = Math.floorDiv(this.z, BitShiftUtil.powerOfTwo(newDetailLevel - this.detailLevel));
x = Math.floorDiv(x, BitShiftUtil.powerOfTwo(newDetailLevel - detailLevel));
z = Math.floorDiv(z, BitShiftUtil.powerOfTwo(newDetailLevel - detailLevel));
}
else
{
this.x = this.x * BitShiftUtil.powerOfTwo(this.detailLevel - newDetailLevel);
this.z = this.z * BitShiftUtil.powerOfTwo(this.detailLevel - newDetailLevel);
x = x * BitShiftUtil.powerOfTwo(detailLevel - newDetailLevel);
z = z * BitShiftUtil.powerOfTwo(detailLevel - newDetailLevel);
}
this.detailLevel = newDetailLevel;
return encode(newDetailLevel, x, z);
}
@@ -143,46 +152,41 @@ public class DhSectionPos
// property getters //
//==================//
public byte getDetailLevel() { return this.detailLevel; }
public int getX() { return this.x; }
public int getZ() { return this.z; }
public static byte getDetailLevel(long pos) { return (byte) ((pos >> DETAIL_LEVEL_OFFSET) & DETAIL_LEVEL_MASK); }
public static int getX(long pos)
{
// unpack the position
int x = (int) ((pos >> POS_X_OFFSET) & POS_X_MASK);
// add the missing 2's compliment most-significant bits (if not done negative numbers will parse incorrectly)
x = (x << X_POS_MISSING_WIDTH) >> X_POS_MISSING_WIDTH;
return x;
}
public static int getZ(long pos)
{
int Z = (int) ((pos >> POS_Z_OFFSET) & POS_Z_MASK);
Z = (Z << Z_POS_MISSING_WIDTH) >> Z_POS_MISSING_WIDTH;
return Z;
}
//=========//
// getters //
//=========//
/**
* @deprecated use DhSectionPos instead
* @return the corner with the smallest X and Z coordinate
*/
@Deprecated
public DhLodPos getMinCornerLodPos() { return this.getMinCornerLodPos((byte) (this.detailLevel - 1)); }
/**
* @deprecated use DhSectionPos instead
* @return the corner with the smallest X and Z coordinate
*/
@Deprecated
public DhLodPos getMinCornerLodPos(byte returnDetailLevel)
/** @return the block X pos that represents the smallest X coordinate of this section */
public static int getMinCornerBlockX(long pos)
{
LodUtil.assertTrue(returnDetailLevel <= this.detailLevel, "returnDetailLevel must be less than sectionDetail");
byte offset = (byte) (this.detailLevel - returnDetailLevel);
return new DhLodPos(returnDetailLevel,
this.x * BitShiftUtil.powerOfTwo(offset),
this.z * BitShiftUtil.powerOfTwo(offset));
// detail level 1 (2x2 blocks) is a special case,
// if this isn't done it will return (1,1) instead of (0,0)
int halfBlockWidth = (getDetailLevel(pos) != 1) ? (DhSectionPos.getBlockWidth(pos) / 2) : 0;
return DhSectionPos.getCenterBlockPosX(pos) - halfBlockWidth;
}
public DhSectionPos getMinCornerPos(byte returnDetailLevel)
/** @return the block Z pos that represents the smallest Z coordinate of this section */
public static int getMinCornerBlockZ(long pos)
{
LodUtil.assertTrue(returnDetailLevel <= this.detailLevel, "returnDetailLevel must be less than sectionDetail");
byte offset = (byte) (this.detailLevel - returnDetailLevel);
return new DhSectionPos(returnDetailLevel,
this.x * BitShiftUtil.powerOfTwo(offset),
this.z * BitShiftUtil.powerOfTwo(offset));
int halfBlockWidth = (getDetailLevel(pos) != 1) ? (DhSectionPos.getBlockWidth(pos) / 2) : 0;
return DhSectionPos.getCenterBlockPosZ(pos) - halfBlockWidth;
}
/**
@@ -195,26 +199,33 @@ public class DhSectionPos
*
* @return how many {@link DhSectionPos}'s at the given detail level it would take to span the width of this section.
*/
public int getWidthCountForLowerDetailedSection(byte returnDetailLevel)
public static int getWidthCountForLowerDetailedSection(long pos, byte returnDetailLevel)
{
LodUtil.assertTrue(returnDetailLevel <= this.detailLevel, "returnDetailLevel must be less than sectionDetail");
byte offset = (byte) (this.detailLevel - returnDetailLevel);
byte detailLevel = getDetailLevel(pos);
LodUtil.assertTrue(returnDetailLevel <= detailLevel, "returnDetailLevel must be less than sectionDetail");
byte offset = (byte) (detailLevel - returnDetailLevel);
return BitShiftUtil.powerOfTwo(offset);
}
/** @return how wide this section is in blocks */
public int getBlockWidth() { return BitShiftUtil.powerOfTwo(this.detailLevel); }
public DhBlockPos2D getCenterBlockPos() { return new DhBlockPos2D(this.getCenterBlockPosX(), this.getCenterBlockPosZ()); }
public int getCenterBlockPosX() { return this.getCenterBlockPos(true); }
public int getCenterBlockPosZ() { return this.getCenterBlockPos(false); }
private int getCenterBlockPos(boolean returnX)
public static int getBlockWidth(long pos) { return BitShiftUtil.powerOfTwo(getDetailLevel(pos)); }
public static DhBlockPos2D getCenterBlockPos(long pos) { return new DhBlockPos2D(getCenterBlockPosX(pos), getCenterBlockPosZ(pos)); }
public static int getCenterBlockPosX(long pos) { return getCenterBlockPosXOrZ(pos, true); }
public static int getCenterBlockPosZ(long pos) { return getCenterBlockPosXOrZ(pos, false); }
private static int getCenterBlockPosXOrZ(long pos, boolean returnX)
{
int centerBlockPos = returnX ? this.x : this.z;
byte detailLevel = getDetailLevel(pos);
int x = getX(pos);
int z = getZ(pos);
if (this.detailLevel == 0)
int centerBlockPos = returnX ? x : z;
if (detailLevel == 0)
{
// already at block detail level, no conversion necessary
return centerBlockPos;
@@ -222,18 +233,18 @@ public class DhSectionPos
// we can't get the center of the position at block level, only attempt to get the position offset for detail levels above 0
int positionOffset = 0;
if (this.detailLevel != 1)
if (detailLevel != 1)
{
positionOffset = BitShiftUtil.powerOfTwo(this.detailLevel - 1);
positionOffset = BitShiftUtil.powerOfTwo(detailLevel - 1);
}
return (centerBlockPos * BitShiftUtil.powerOfTwo(this.detailLevel)) + positionOffset;
return (centerBlockPos * BitShiftUtil.powerOfTwo(detailLevel)) + positionOffset;
}
public int getManhattanBlockDistance(DhBlockPos2D blockPos)
public static int getManhattanBlockDistance(long pos, DhBlockPos2D blockPos)
{
return Math.abs(this.getCenterBlockPosX() - blockPos.x)
+ Math.abs(this.getCenterBlockPosZ() - blockPos.z);
return Math.abs(getCenterBlockPosX(pos) - blockPos.x)
+ Math.abs(getCenterBlockPosZ(pos) - blockPos.z);
}
@@ -241,9 +252,9 @@ public class DhSectionPos
//==================//
// parent child pos //
//==================//
/**
* Returns the DhLodPos 1 detail level lower <br><br>
* Returns a position 1 detail level lower. <br><br>
*
* Relative child positions returned for each index: <br>
* 0 = (0,0) - North West <br>
@@ -253,248 +264,147 @@ public class DhSectionPos
*
* @param child0to3 must be an int between 0 and 3
*/
public DhSectionPos getChildByIndex(int child0to3) throws IllegalArgumentException, IllegalStateException
public static long getChildByIndex(long pos, int child0to3) throws IllegalArgumentException, IllegalStateException
{
byte detailLevel = getDetailLevel(pos);
int x = getX(pos);
int z = getZ(pos);
if (child0to3 < 0 || child0to3 > 3)
{
throw new IllegalArgumentException("child0to3 must be between 0 and 3");
}
if (this.detailLevel <= 0)
if (detailLevel <= 0)
{
throw new IllegalStateException("section detail must be greater than 0");
}
return new DhSectionPos((byte) (this.detailLevel - 1),
this.x * 2 + (child0to3 & 1),
this.z * 2 + BitShiftUtil.half(child0to3 & 2));
return DhSectionPos.encode((byte) (detailLevel - 1),
x * 2 + (child0to3 & 1),
z * 2 + BitShiftUtil.half(child0to3 & 2));
}
/** Returns this position's child index in its parent */
public int getChildIndexOfParent() { return (this.x & 1) + BitShiftUtil.square(this.z & 1); }
public static int getChildIndexOfParent(long pos) { return (getX(pos) & 1) + BitShiftUtil.square(getZ(pos) & 1); }
public DhSectionPos getParentPos() { return new DhSectionPos((byte) (this.detailLevel + 1), BitShiftUtil.half(this.x), BitShiftUtil.half(this.z)); }
public static long getParentPos(long pos) { return DhSectionPos.encode((byte) (getDetailLevel(pos) + 1), BitShiftUtil.half(getX(pos)), BitShiftUtil.half(getZ(pos))); }
public DhSectionPos getAdjacentPos(EDhDirection dir)
public static long getAdjacentPos(long pos, EDhDirection dir) throws IllegalArgumentException
{
return new DhSectionPos(this.detailLevel,
this.x + dir.getNormal().x,
this.z + dir.getNormal().z);
if (dir == EDhDirection.UP || dir == EDhDirection.DOWN)
{
throw new IllegalArgumentException("getAdjacentPos can't be UP or DOWN, direction given: ["+dir.name()+"].");
}
return DhSectionPos.encode(getDetailLevel(pos),
getX(pos) + dir.getNormal().x,
getZ(pos) + dir.getNormal().z);
}
public DhLodPos getSectionBBoxPos() { return new DhLodPos(this.detailLevel, this.x, this.z); }
@Deprecated
public static DhLodPos getSectionBBoxPos(long pos) { return new DhLodPos(getDetailLevel(pos), getX(pos), getZ(pos)); }
//=============//
// comparisons //
//=============//
public boolean overlapsExactly(DhSectionPos other)
public static boolean contains(long aPos, long bPos)
{
// original logic from DhLodPos
if (this.equals(other))
{
return true;
}
else if (this.detailLevel == other.detailLevel)
{
return false;
}
else if (this.detailLevel > other.detailLevel)
{
return this.equals(other.convertNewToDetailLevel(this.detailLevel));
}
else
{
return other.equals(this.convertNewToDetailLevel(other.detailLevel));
}
}
public boolean contains(DhSectionPos otherPos)
{
DhBlockPos2D thisMinBlockPos = this.getMinCornerLodPos(LodUtil.BLOCK_DETAIL_LEVEL).getCornerBlockPos();
DhBlockPos2D otherCornerBlockPos = otherPos.getMinCornerLodPos(LodUtil.BLOCK_DETAIL_LEVEL).getCornerBlockPos();
int aMinX = getMinCornerBlockX(aPos);
int aMinZ = getMinCornerBlockZ(aPos);
int thisBlockWidth = this.getBlockWidth() - 1; // minus 1 to account for zero based positional indexing
DhBlockPos2D thisMaxBlockPos = new DhBlockPos2D(thisMinBlockPos.x + thisBlockWidth, thisMinBlockPos.z + thisBlockWidth);
return thisMinBlockPos.x <= otherCornerBlockPos.x && otherCornerBlockPos.x <= thisMaxBlockPos.x &&
thisMinBlockPos.z <= otherCornerBlockPos.z && otherCornerBlockPos.z <= thisMaxBlockPos.z;
int bMinX = getMinCornerBlockX(bPos);
int bMinZ = getMinCornerBlockZ(bPos);
int aBlockWidth = getBlockWidth(aPos) - 1; // minus 1 to account for zero based positional indexing
int aMaxX = aMinX + aBlockWidth;
int aMaxZ = aMinZ + aBlockWidth;
return aMinX <= bMinX && bMinX <= aMaxX &&
aMinZ <= bMinZ && bMinZ <= aMaxZ;
}
//===========//
// iterators //
//===========//
/** Applies the given consumer to all 4 of this position's children. */
public void forEachChild(Consumer<DhSectionPos> callback)
public static void forEachChild(long pos, LongConsumer callback) throws IllegalArgumentException, IllegalStateException
{
for (int i = 0; i < 4; i++)
{
callback.accept(this.getChildByIndex(i));
callback.accept(getChildByIndex(pos, i));
}
}
/** Applies the given consumer to all children of the position at the given section detail level. */
public void forEachChildDownToDetailLevel(byte minSectionDetailLevel, Function<DhSectionPos, Boolean> callback)
public static void forEachChildDownToDetailLevel(long pos, byte minSectionDetailLevel, ICancelablePrimitiveLongConsumer callback) throws IllegalArgumentException, IllegalStateException
{
boolean stop = callback.apply(this);
if (stop || minSectionDetailLevel == this.detailLevel)
boolean stop = callback.accept(pos);
if (stop || minSectionDetailLevel == getDetailLevel(pos))
{
return;
}
for (int i = 0; i < 4; i++)
{
this.getChildByIndex(i).forEachChildDownToDetailLevel(minSectionDetailLevel, callback);
forEachChildDownToDetailLevel(getChildByIndex(pos, i), minSectionDetailLevel, callback);
}
}
/** Applies the given consumer to all children of the position at the given section detail level. */
public void forEachChildAtDetailLevel(byte sectionDetailLevel, Consumer<DhSectionPos> callback)
public static void forEachChildAtDetailLevel(long pos, byte sectionDetailLevel, LongConsumer callback) throws IllegalArgumentException, IllegalStateException
{
if (sectionDetailLevel == this.detailLevel)
if (sectionDetailLevel == getDetailLevel(pos))
{
callback.accept(this);
callback.accept(pos);
return;
}
for (int i = 0; i < 4; i++)
{
this.getChildByIndex(i).forEachChildAtDetailLevel(sectionDetailLevel, callback);
forEachChildAtDetailLevel(getChildByIndex(pos, i), sectionDetailLevel, callback);
}
}
/** Applies the given consumer to all children of the position at the given section detail level. */
public void forEachPosUpToDetailLevel(byte maxSectionDetailLevel, Consumer<DhSectionPos> callback)
public static void forEachPosUpToDetailLevel(long pos, byte maxSectionDetailLevel, LongConsumer callback)
{
callback.accept(this);
if (maxSectionDetailLevel == this.detailLevel)
callback.accept(pos);
if (maxSectionDetailLevel == getDetailLevel(pos))
{
return;
}
this.getParentPos().forEachPosUpToDetailLevel(maxSectionDetailLevel, callback);
forEachPosUpToDetailLevel(getParentPos(pos), maxSectionDetailLevel, callback);
}
//==============//
// Base methods //
//==============//
public static String toString(long pos) { return getDetailLevel(pos) + "*" + getX(pos) + "," + getZ(pos); }
public static int hashCode(long pos) { return Long.hashCode(pos); }
//===============//
// serialization //
//===============//
/** Serialize() is different from toString() as it must NEVER be changed, and should be in a short format */
public String serialize() { return "[" + this.detailLevel + ',' + this.x + ',' + this.z + ']'; }
//================//
// helper methods //
//================//
@Nullable
public static DhSectionPos deserialize(String value)
/** Used instead of {@link java.util.function.Function} to prevent unnecessary (un)wrapping. */
@FunctionalInterface
public interface ICancelablePrimitiveLongConsumer
{
if (value.charAt(0) != '[' || value.charAt(value.length() - 1) != ']') return null;
String[] split = value.substring(1, value.length() - 1).split(",");
if (split.length != 3) return null;
return new DhSectionPos(Byte.parseByte(split[0]), Integer.parseInt(split[1]), Integer.parseInt(split[2]));
}
//===========//
// overrides //
//===========//
@Override
public String toString() { return "{" + this.detailLevel + "*" + this.x + "," + this.z + "}"; }
@Override
public boolean equals(Object obj)
{
if (this == obj)
{
return true;
}
if (obj == null || obj.getClass() != DhSectionPos.class)
{
return false;
}
DhSectionPos that = (DhSectionPos) obj;
return this.detailLevel == that.detailLevel &&
this.x == that.x &&
this.z == that.z;
}
@Override
public int hashCode()
{
return Integer.hashCode(this.detailLevel) ^ // XOR
Integer.hashCode(this.x) ^ // XOR
Integer.hashCode(this.z);
}
//=============//
// sub classes //
//=============//
/**
* Identical to {@link DhSectionPos} except it is mutable.
* See {@link DhSectionPos} for full documentation.
*
* @see DhSectionPos
*/
public static class DhMutableSectionPos extends DhSectionPos
{
//==============//
// constructors //
//==============//
public DhMutableSectionPos(byte sectionDetailLevel, int sectionX, int sectionZ) { super(sectionDetailLevel, sectionX, sectionZ); }
public DhMutableSectionPos(DhBlockPos blockPos) { super(blockPos); }
public DhMutableSectionPos(DhBlockPos2D blockPos) { super(blockPos); }
public DhMutableSectionPos(DhChunkPos chunkPos) { super(chunkPos); }
public DhMutableSectionPos(byte detailLevel, DhLodPos dhLodPos) { super(detailLevel, dhLodPos); }
//============//
// converters //
//============//
/**
* Overwrites this section pos with the given input. <br>
* Can be useful to prevent duplicate allocations in high traffic loops but should
* be used sparingly as it could accidentally cause bugs due to unexpected modifications.
*/
public void mutate(byte sectionDetailLevel, int sectionX, int sectionZ)
{
this.detailLevel = sectionDetailLevel;
this.x = sectionX;
this.z = sectionZ;
}
@Override
public void convertSelfToDetailLevel(byte newDetailLevel) { super.convertSelfToDetailLevel(newDetailLevel); }
//==================//
// property getters //
//==================//
public void setDetailLevel(byte sectionDetailLevel) { this.detailLevel = sectionDetailLevel; }
public void setX(int sectionX) { this.x = sectionX; }
public void setZ(int sectionZ) { this.z = sectionZ; }
/** @return true if this method should cancel further consumers. */
boolean accept(long value);
}
}
@@ -30,18 +30,20 @@ import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhBlockPos2D;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.ThreadUtil;
import com.seibel.distanthorizons.core.util.objects.quadTree.QuadNode;
import com.seibel.distanthorizons.core.util.objects.quadTree.QuadTree;
import com.seibel.distanthorizons.coreapi.util.MathUtil;
import it.unimi.dsi.fastutil.longs.LongIterator;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import javax.annotation.WillNotClose;
import java.awt.*;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Objects;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -51,7 +53,7 @@ import java.util.concurrent.locks.ReentrantLock;
* This quadTree structure is our core data structure and holds
* all rendering data.
*/
public class LodQuadTree extends QuadTree<LodRenderSection> implements AutoCloseable
public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRenderable, AutoCloseable
{
public static final byte TREE_LOWEST_DETAIL_LEVEL = ColumnRenderSource.SECTION_SIZE_OFFSET;
@@ -69,12 +71,16 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements AutoClose
* This holds every {@link DhSectionPos} that should be reloaded next tick. <br>
* This is a {@link ConcurrentLinkedQueue} because new sections can be added to this list via the world generator threads.
*/
private final ConcurrentLinkedQueue<DhSectionPos> sectionsToReload = new ConcurrentLinkedQueue<>();
private final ConcurrentLinkedQueue<Long> sectionsToReload = new ConcurrentLinkedQueue<>();
private final IDhClientLevel level; //FIXME: Proper hierarchy to remove this reference!
private final ConfigChangeListener<EDhApiHorizontalQuality> horizontalScaleChangeListener;
private final ReentrantLock treeReadWriteLock = new ReentrantLock();
private final AtomicBoolean fullDataRetrievalQueueRunning = new AtomicBoolean(false);
private ArrayList<LodRenderSection> debugRenderSections = new ArrayList<>();
private ArrayList<LodRenderSection> altDebugRenderSections = new ArrayList<>();
private final ReentrantLock debugRenderSectionLock = new ReentrantLock();
/** the smallest numerical detail level number that can be rendered */
private byte maxRenderDetailLevel;
/** the largest numerical detail level number that can be rendered */
@@ -98,6 +104,8 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements AutoClose
{
super(viewDiameterInBlocks, new DhBlockPos2D(initialPlayerBlockX, initialPlayerBlockZ), TREE_LOWEST_DETAIL_LEVEL);
DebugRenderer.register(this, Config.Client.Advanced.Debugging.DebugWireframe.showQuadTreeRenderStatus);
this.level = level;
this.fullDataSourceProvider = fullDataSourceProvider;
this.blockRenderDistanceDiameter = viewDiameterInBlocks;
@@ -152,13 +160,33 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements AutoClose
}
private void updateAllRenderSections(DhBlockPos2D playerPos)
{
if (Config.Client.Advanced.Debugging.DebugWireframe.showQuadTreeRenderStatus.get())
{
try
{
// lock to prevent accidentally rendering an array that's being populated/cleared
this.debugRenderSectionLock.lock();
// swap the debug arrays
this.debugRenderSections.clear();
ArrayList<LodRenderSection> temp = this.debugRenderSections;
this.debugRenderSections = this.altDebugRenderSections;
this.altDebugRenderSections = temp;
}
finally
{
this.debugRenderSectionLock.unlock();
}
}
// reload any sections that need it
DhSectionPos pos;
Long pos;
while ((pos = this.sectionsToReload.poll()) != null)
{
// walk up the tree until we hit the root node
// this is done so any high detail changes flow up to the lower detail render sections as well
while (pos.getDetailLevel() <= this.treeMinDetailLevel)
while (DhSectionPos.getDetailLevel(pos) <= this.treeMinDetailLevel)
{
try
{
@@ -171,7 +199,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements AutoClose
catch (IndexOutOfBoundsException e)
{ /* the section is now out of bounds, it doesn't need to be reloaded */ }
pos = pos.getParentPos();
pos = DhSectionPos.getParentPos(pos);
}
}
@@ -179,11 +207,11 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements AutoClose
// walk through each root node
ArrayList<LodRenderSection> nodesNeedingRetrieval = new ArrayList<>();
ArrayList<LodRenderSection> nodesNeedingLoading = new ArrayList<>();
Iterator<DhSectionPos> rootPosIterator = this.rootNodePosIterator();
LongIterator rootPosIterator = this.rootNodePosIterator();
while (rootPosIterator.hasNext())
{
// make sure all root nodes have been created
DhSectionPos rootPos = rootPosIterator.next();
long rootPos = rootPosIterator.nextLong();
if (this.getNode(rootPos) == null)
{
this.setValue(rootPos, new LodRenderSection(rootPos, this, this.level, this.fullDataSourceProvider));
@@ -204,8 +232,8 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements AutoClose
nodesNeedingLoading.sort((a, b) ->
{
int aDist = a.pos.getManhattanBlockDistance(playerPos);
int bDist = b.pos.getManhattanBlockDistance(playerPos);
int aDist = DhSectionPos.getManhattanBlockDistance(a.pos, playerPos);
int bDist = DhSectionPos.getManhattanBlockDistance(b.pos, playerPos);
return Integer.compare(aDist, bDist);
});
@@ -222,7 +250,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements AutoClose
/** @return whether the current position is able to render (note: not if it IS rendering, just if it is ABLE to.) */
private boolean recursivelyUpdateRenderSectionNode(
DhBlockPos2D playerPos,
QuadNode<LodRenderSection> rootNode, QuadNode<LodRenderSection> quadNode, DhSectionPos sectionPos,
QuadNode<LodRenderSection> rootNode, QuadNode<LodRenderSection> quadNode, long sectionPos,
boolean parentSectionIsRendering,
ArrayList<LodRenderSection> nodesNeedingRetrieval,
ArrayList<LodRenderSection> nodesNeedingLoading)
@@ -267,17 +295,17 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements AutoClose
expectedDetailLevel += DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL;
if (sectionPos.getDetailLevel() > expectedDetailLevel)
if (DhSectionPos.getDetailLevel(sectionPos) > expectedDetailLevel)
{
// section detail level too high //
boolean thisPosIsRendering = renderSection.renderingEnabled;
boolean allChildrenSectionsAreLoaded = true;
// recursively update all child render sections
Iterator<DhSectionPos> childPosIterator = quadNode.getChildPosIterator();
LongIterator childPosIterator = quadNode.getChildPosIterator();
while (childPosIterator.hasNext())
{
DhSectionPos childPos = childPosIterator.next();
long childPos = childPosIterator.nextLong();
QuadNode<LodRenderSection> childNode = rootNode.getNode(childPos);
boolean childSectionLoaded = this.recursivelyUpdateRenderSectionNode(playerPos, rootNode, childNode, childPos, thisPosIsRendering || parentSectionIsRendering, nodesNeedingRetrieval, nodesNeedingLoading);
@@ -310,7 +338,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements AutoClose
childPosIterator = quadNode.getChildPosIterator();
while (childPosIterator.hasNext())
{
DhSectionPos childPos = childPosIterator.next();
long childPos = childPosIterator.nextLong();
QuadNode<LodRenderSection> childNode = rootNode.getNode(childPos);
boolean childSectionLoaded = this.recursivelyUpdateRenderSectionNode(playerPos, rootNode, childNode, childPos, parentSectionIsRendering, nodesNeedingRetrieval, nodesNeedingLoading);
@@ -320,7 +348,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements AutoClose
{
// FIXME having world generation enabled in a pre-generated world that doesn't have any DH data can cause this to happen
// surprisingly reloadPos() doesn't appear to be the culprit, maybe there is an issue with reloading/changing the full data source?
//LOGGER.warn("Potential QuadTree concurrency issue. All child sections should be enabled and ready to render for pos: "+sectionPos);
//LOGGER.warn("Potential QuadTree concurrency issue. All child sections should be enabled and ready to render for pos: "+DhSectionPos.toString(sectionPos));
}
// this section is now being rendered via its children
@@ -328,7 +356,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements AutoClose
}
}
// TODO this should only equal the expected detail level, the (expectedDetailLevel-1) is a temporary fix to prevent corners from being cut out
else if (sectionPos.getDetailLevel() == expectedDetailLevel || sectionPos.getDetailLevel() == expectedDetailLevel - 1)
else if (DhSectionPos.getDetailLevel(sectionPos) == expectedDetailLevel || DhSectionPos.getDetailLevel(sectionPos) == expectedDetailLevel - 1)
{
// this is the detail level we want to render //
@@ -349,6 +377,11 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements AutoClose
nodesNeedingLoading.add(renderSection);
}
if (Config.Client.Advanced.Debugging.DebugWireframe.showQuadTreeRenderStatus.get())
{
this.debugRenderSections.add(renderSection);
}
// wait for the parent to disable before enabling this section, so we don't overdraw/overlap render sections
if (!parentSectionIsRendering && renderSection.canRender())
{
@@ -412,7 +445,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements AutoClose
* @param sectionPos section position
* @return detail level of this section pos
*/
public byte calculateExpectedDetailLevel(DhBlockPos2D playerPos, DhSectionPos sectionPos) { return this.getDetailLevelFromDistance(playerPos.dist(sectionPos.getCenterBlockPosX(), sectionPos.getCenterBlockPosZ())); }
public byte calculateExpectedDetailLevel(DhBlockPos2D playerPos, long sectionPos) { return this.getDetailLevelFromDistance(playerPos.dist(DhSectionPos.getCenterBlockPosX(sectionPos), DhSectionPos.getCenterBlockPosZ(sectionPos))); }
private byte getDetailLevelFromDistance(double distance)
{
double maxDetailDistance = this.getDrawDistanceFromDetail(Byte.MAX_VALUE - 1);
@@ -504,16 +537,8 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements AutoClose
* Can be called whenever a render section's data needs to be refreshed. <br>
* This should be called whenever a world generation task is completed or if the connected server has new data to show.
*/
public void reloadPos(@NotNull DhSectionPos pos)
public void reloadPos(long pos)
{
if (pos == null)
{
// shouldn't happen, but James saw it happen once, so this is here just in case
LOGGER.warn("reloadPos given a null pos.");
return;
}
this.sectionsToReload.add(pos);
// the adjacent locations also need to be updated to make sure lighting
@@ -521,7 +546,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements AutoClose
// and lights may not show up over LOD borders
for (EDhDirection direction : EDhDirection.ADJ_DIRECTIONS)
{
this.sectionsToReload.add(pos.getAdjacentPos(direction));
this.sectionsToReload.add(DhSectionPos.getAdjacentPos(pos, direction));
}
}
@@ -541,8 +566,8 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements AutoClose
// sort the nodes from nearest to farthest
nodesNeedingRetrieval.sort((a, b) ->
{
int aDist = a.pos.getManhattanBlockDistance(playerPos);
int bDist = b.pos.getManhattanBlockDistance(playerPos);
int aDist = DhSectionPos.getManhattanBlockDistance(a.pos, playerPos);
int bDist = DhSectionPos.getManhattanBlockDistance(b.pos, playerPos);
return Integer.compare(aDist, bDist);
});
@@ -593,6 +618,56 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements AutoClose
private void onHorizontalQualityChange() { this.clearRenderDataCache(); }
//===========//
// debugging //
//===========//
@Override
public void debugRender(DebugRenderer debugRenderer)
{
try
{
// lock to prevent accidentally rendering the array that's being cleared
this.debugRenderSectionLock.lock();
for (int i = 0; i < this.debugRenderSections.size(); i++)
{
LodRenderSection renderSection = this.debugRenderSections.get(i);
Color color = Color.BLACK;
if (renderSection.gpuUploadInProgress())
{
color = Color.ORANGE;
}
else if (renderSection.renderBuffer == null)
{
// uploaded but the buffer is missing
color = Color.PINK;
}
else if (renderSection.renderBuffer.hasNonNullVbos())
{
if (renderSection.renderBuffer.vboBufferCount() != 0)
{
color = Color.GREEN;
}
else
{
// This section is probably rendering an empty chunk
color = Color.RED;
}
}
debugRenderer.renderBox(new DebugRenderer.Box(renderSection.pos, 400, 400f, Objects.hashCode(this), 0.05f, color));
}
}
finally
{
this.debugRenderSectionLock.unlock();
}
}
//==============//
// base methods //
@@ -605,6 +680,8 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements AutoClose
this.horizontalScaleChangeListener.close();
DebugRenderer.unregister(this, Config.Client.Advanced.Debugging.DebugWireframe.showQuadTreeRenderStatus);
Iterator<QuadNode<LodRenderSection>> nodeIterator = this.nodeIterator();
while (nodeIterator.hasNext())
{
@@ -34,6 +34,7 @@ import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable;
import com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding.ColumnRenderBuffer;
import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import it.unimi.dsi.fastutil.longs.LongArrayList;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable;
@@ -56,7 +57,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
public final DhSectionPos pos;
public final long pos;
private final IDhClientLevel level;
@WillNotClose
@@ -85,7 +86,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
private boolean missingPositionsCalculated = false;
/** should be an empty array if no positions need to be generated */
private ArrayList<DhSectionPos> missingGenerationPos = null;
private LongArrayList missingGenerationPos = null;
@@ -93,7 +94,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
// constructor //
//=============//
public LodRenderSection(DhSectionPos pos, LodQuadTree quadTree, IDhClientLevel level, FullDataSourceProviderV2 fullDataSourceProvider)
public LodRenderSection(long pos, LodQuadTree quadTree, IDhClientLevel level, FullDataSourceProviderV2 fullDataSourceProvider)
{
this.pos = pos;
this.quadTree = quadTree;
@@ -168,6 +169,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
// nothing needs to be rendered
this.canRender = false;
this.uploadRenderDataToGpuFuture = null;
return;
}
@@ -235,7 +237,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
EDhDirection direction = EDhDirection.ADJ_DIRECTIONS[i];
int arrayIndex = direction.ordinal() - 2;
DhSectionPos adjPos = this.pos.getAdjacentPos(direction);
long adjPos = DhSectionPos.getAdjacentPos(this.pos, direction);
try
{
LodRenderSection adjRenderSection = this.quadTree.getValue(adjPos);
@@ -349,7 +351,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
break;
}
DhSectionPos pos = this.missingGenerationPos.remove(i);
long pos = this.missingGenerationPos.removeLong(i);
boolean positionQueued = this.fullDataSourceProvider.queuePositionForRetrieval(pos);
if (!positionQueued)
{
@@ -442,7 +444,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
try
{
executor.execute(() -> this.fullDataSourceProvider.removeRetrievalRequestIf((genPos) -> this.pos.contains(genPos)));
executor.execute(() -> this.fullDataSourceProvider.removeRetrievalRequestIf((genPos) -> DhSectionPos.contains(this.pos, genPos)));
}
catch (RejectedExecutionException ignore)
{ /* If this happens that means everything is already shut down and no additional cleanup will be necessary */ }
@@ -205,8 +205,8 @@ public class RenderBufferHandler implements AutoCloseable
// Now that we have the axis directions, we can sort the render list
Comparator<LoadedRenderBuffer> farToNearComparator = (loadedBufferA, loadedBufferB) ->
{
Pos2D aPos = loadedBufferA.pos.getCenterBlockPos().toPos2D();
Pos2D bPos = loadedBufferB.pos.getCenterBlockPos().toPos2D();
Pos2D aPos = DhSectionPos.getCenterBlockPos(loadedBufferA.pos).toPos2D();
Pos2D bPos = DhSectionPos.getCenterBlockPos(loadedBufferB.pos).toPos2D();
if (true)
{
int aManhattanDistance = aPos.manhattanDist(cPos);
@@ -243,7 +243,7 @@ public class RenderBufferHandler implements AutoCloseable
return abPosDifference;
}
return loadedBufferA.pos.getDetailLevel() - loadedBufferB.pos.getDetailLevel(); // If all else fails, sort by detail
return DhSectionPos.getDetailLevel(loadedBufferA.pos) - DhSectionPos.getDetailLevel(loadedBufferB.pos); // If all else fails, sort by detail
};
this.loadedNearToFarBuffers = new SortedArraySet<>((a, b) -> -farToNearComparator.compare(a, b)); // TODO is the comparator named wrong?
@@ -309,7 +309,7 @@ public class RenderBufferHandler implements AutoCloseable
{
QuadNode<LodRenderSection> node = nodeIterator.next();
DhSectionPos sectionPos = node.sectionPos;
long sectionPos = node.sectionPos;
LodRenderSection renderSection = node.value;
if (renderSection == null)
{
@@ -320,7 +320,7 @@ public class RenderBufferHandler implements AutoCloseable
{
if (enableFrustumCulling)
{
DhLodPos lodBounds = renderSection.pos.getSectionBBoxPos();
DhLodPos lodBounds = DhSectionPos.getSectionBBoxPos(renderSection.pos);
int blockMinX = lodBounds.getMinX().toBlockWidth();
int blockMinZ = lodBounds.getMinZ().toBlockWidth();
int lodBlockWidth = lodBounds.getBlockWidth();
@@ -344,7 +344,7 @@ public class RenderBufferHandler implements AutoCloseable
{
continue;
}
this.loadedNearToFarBuffers.add(new LoadedRenderBuffer(buffer, sectionPos));
}
@@ -425,9 +425,9 @@ public class RenderBufferHandler implements AutoCloseable
private static class LoadedRenderBuffer
{
public final ColumnRenderBuffer buffer;
public final DhSectionPos pos;
public final long pos;
LoadedRenderBuffer(ColumnRenderBuffer buffer, DhSectionPos pos)
LoadedRenderBuffer(ColumnRenderBuffer buffer, long pos)
{
this.buffer = buffer;
this.pos = pos;
@@ -247,6 +247,8 @@ public class DebugRenderer
public Vec3f b;
public Color color;
public Box(Vec3f a, Vec3f b, Color color)
{
this.a = a;
@@ -288,14 +290,14 @@ public class DebugRenderer
this.color = color;
}
public Box(DhSectionPos pos, float minY, float maxY, float marginPercent, Color color)
public Box(long pos, float minY, float maxY, float marginPercent, Color color)
{
this(pos.getSectionBBoxPos(), minY, maxY, marginPercent, color);
this(DhSectionPos.getSectionBBoxPos(pos), minY, maxY, marginPercent, color);
}
public Box(DhSectionPos pos, float y, float yDiff, Object hash, float marginPercent, Color color)
public Box(long pos, float y, float yDiff, Object hash, float marginPercent, Color color)
{
this(pos.getSectionBBoxPos(), y, yDiff, hash, marginPercent, color);
this(DhSectionPos.getSectionBBoxPos(pos), y, yDiff, hash, marginPercent, color);
}
}
@@ -57,6 +57,9 @@ import org.apache.logging.log4j.LogManager;
import org.lwjgl.opengl.GL32;
import java.awt.*;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.time.Duration;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
@@ -402,17 +405,10 @@ public class LodRenderer
this.renderTransparentBuffers(profiler, renderEventParam, renderEventParam.partialTicks);
}
if (this.usingMcFrameBuffer)
{
// If MC's framebuffer is being used the depth needs to be cleared to prevent rendering on top of MC.
// This should only happen when Optifine shaders are being used.
GL32.glClear(GL32.GL_DEPTH_BUFFER_BIT);
}
drawLagSpikeCatcher.end("LodDraw");
//=================//
// debug rendering //
//=================//
@@ -425,12 +421,21 @@ public class LodRenderer
combinedMatrix.multiply(renderEventParam.dhModelViewMatrix);
// Note: this can be very slow if a lot of boxes are being rendered
DebugRenderer.INSTANCE.render(combinedMatrix);
DebugRenderer.INSTANCE.render(combinedMatrix);
profiler.popPush("LOD cleanup");
}
if (this.usingMcFrameBuffer)
{
// If MC's framebuffer is being used the depth needs to be cleared to prevent rendering on top of MC.
// This should only happen when Optifine shaders are being used.
GL32.glClear(GL32.GL_DEPTH_BUFFER_BIT);
}
//=============================//
// Apply to the MC FrameBuffer //
//=============================//
@@ -591,8 +596,6 @@ public class LodRenderer
{
if (this.usingMcFrameBuffer && framebufferOverride == null)
{
// recreating the GL State at this point is necessary in order to get the correct depth texture
minecraftGlState.saveState();
if (ENABLE_DUMP_GL_STATE)
{
tickLogger.debug("Re-saving GL state due to Optifine presence: " + minecraftGlState);
@@ -22,21 +22,18 @@ package com.seibel.distanthorizons.core.sql.dto;
import com.seibel.distanthorizons.api.enums.config.EDhApiDataCompressionMode;
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiWorldGenerationStep;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV1;
import com.seibel.distanthorizons.core.dataObjects.render.ColumnRenderSource;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.atomic.AtomicLong;
/**
* Handles storing{@link FullDataSourceV1}'s in the database.
*/
public class FullDataSourceV1DTO implements IBaseDTO<DhSectionPos>
public class FullDataSourceV1DTO implements IBaseDTO<Long>
{
public DhSectionPos pos;
public long pos;
public int checksum;
public byte dataDetailLevel;
public EDhApiWorldGenerationStep worldGenStep;
@@ -54,7 +51,7 @@ public class FullDataSourceV1DTO implements IBaseDTO<DhSectionPos>
// constructor //
//=============//
public FullDataSourceV1DTO(DhSectionPos pos, int checksum, byte dataDetailLevel, EDhApiWorldGenerationStep worldGenStep, String dataType, byte binaryDataFormatVersion, byte[] dataArray)
public FullDataSourceV1DTO(long pos, int checksum, byte dataDetailLevel, EDhApiWorldGenerationStep worldGenStep, String dataType, byte binaryDataFormatVersion, byte[] dataArray)
{
this.pos = pos;
this.checksum = checksum;
@@ -83,7 +80,7 @@ public class FullDataSourceV1DTO implements IBaseDTO<DhSectionPos>
//===========//
@Override
public DhSectionPos getKey() { return this.pos; }
public Long getKey() { return this.pos; }
}
@@ -24,23 +24,26 @@ import com.seibel.distanthorizons.api.enums.config.EDhApiWorldCompressionMode;
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiWorldGenerationStep;
import com.seibel.distanthorizons.core.dataObjects.fullData.FullDataPointIdMap;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
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.objects.DataCorruptedException;
import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataInputStream;
import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataOutputStream;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import it.unimi.dsi.fastutil.longs.LongArrayList;
import org.jetbrains.annotations.NotNull;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.*;
import java.util.zip.Adler32;
import java.util.zip.CheckedOutputStream;
/** handles storing {@link FullDataSourceV2}'s in the database. */
public class FullDataSourceV2DTO implements IBaseDTO<DhSectionPos>
public class FullDataSourceV2DTO implements IBaseDTO<Long>
{
public DhSectionPos pos;
public static final boolean VALIDATE_INPUT_DATAPOINTS = true;
public long pos;
public int levelMinY;
@@ -57,7 +60,7 @@ public class FullDataSourceV2DTO implements IBaseDTO<DhSectionPos>
public byte[] compressedMappingByteArray;
public byte dataFormatVersion;
public EDhApiDataCompressionMode compressionModeEnum;
public byte compressionModeValue;
public boolean applyToParent;
@@ -79,7 +82,7 @@ public class FullDataSourceV2DTO implements IBaseDTO<DhSectionPos>
return new FullDataSourceV2DTO(
dataSource.getPos(),
checkedDataPointArray.checksum, compressedWorldGenStepByteArray, compressedWorldCompressionModeByteArray, FullDataSourceV2.DATA_FORMAT_VERSION, compressionModeEnum, checkedDataPointArray.byteArray,
checkedDataPointArray.checksum, compressedWorldGenStepByteArray, compressedWorldCompressionModeByteArray, FullDataSourceV2.DATA_FORMAT_VERSION, compressionModeEnum.value, checkedDataPointArray.byteArray,
dataSource.lastModifiedUnixDateTime, dataSource.createdUnixDateTime,
mappingByteArray, dataSource.applyToParent,
dataSource.levelMinY
@@ -87,8 +90,8 @@ public class FullDataSourceV2DTO implements IBaseDTO<DhSectionPos>
}
public FullDataSourceV2DTO(
DhSectionPos pos,
int dataChecksum, byte[] compressedColumnGenStepByteArray, byte[] compressedWorldCompressionModeByteArray, byte dataFormatVersion, EDhApiDataCompressionMode compressionModeEnum, byte[] compressedDataByteArray,
long pos,
int dataChecksum, byte[] compressedColumnGenStepByteArray, byte[] compressedWorldCompressionModeByteArray, byte dataFormatVersion, byte compressionModeValue, byte[] compressedDataByteArray,
long lastModifiedUnixDateTime, long createdUnixDateTime,
byte[] compressedMappingByteArray, boolean applyToParent,
int levelMinY)
@@ -99,7 +102,7 @@ public class FullDataSourceV2DTO implements IBaseDTO<DhSectionPos>
this.compressedWorldCompressionModeByteArray = compressedWorldCompressionModeByteArray;
this.dataFormatVersion = dataFormatVersion;
this.compressionModeEnum = compressionModeEnum;
this.compressionModeValue = compressionModeValue;
this.compressedDataByteArray = compressedDataByteArray;
this.compressedMappingByteArray = compressedMappingByteArray;
@@ -118,32 +121,46 @@ public class FullDataSourceV2DTO implements IBaseDTO<DhSectionPos>
// data source population //
//========================//
public FullDataSourceV2 createPooledDataSource(@NotNull ILevelWrapper levelWrapper) throws IOException, InterruptedException
public FullDataSourceV2 createPooledDataSource(@NotNull ILevelWrapper levelWrapper) throws IOException, InterruptedException, DataCorruptedException
{
FullDataSourceV2 dataSource = FullDataSourceV2.DATA_SOURCE_POOL.getPooledSource(this.pos, false);
return this.populateDataSource(dataSource, levelWrapper);
}
public FullDataSourceV2 populateDataSource(FullDataSourceV2 dataSource, @NotNull ILevelWrapper levelWrapper) throws IOException, InterruptedException
public FullDataSourceV2 populateDataSource(FullDataSourceV2 dataSource, @NotNull ILevelWrapper levelWrapper) throws IOException, InterruptedException, DataCorruptedException
{ return this.internalPopulateDataSource(dataSource, levelWrapper, false); }
/**
* May be missing one or more data fields. <br>
* Designed to be used without access to Minecraft or any supporting objects.
*/
public FullDataSourceV2 createUnitTestDataSource() throws IOException, InterruptedException
public FullDataSourceV2 createUnitTestDataSource() throws IOException, InterruptedException, DataCorruptedException
{ return this.internalPopulateDataSource(FullDataSourceV2.createEmpty(this.pos), null, true); }
private FullDataSourceV2 internalPopulateDataSource(FullDataSourceV2 dataSource, ILevelWrapper levelWrapper, boolean unitTest) throws IOException, InterruptedException
private FullDataSourceV2 internalPopulateDataSource(FullDataSourceV2 dataSource, ILevelWrapper levelWrapper, boolean unitTest) throws IOException, InterruptedException, DataCorruptedException
{
if (FullDataSourceV2.DATA_FORMAT_VERSION != this.dataFormatVersion)
{
throw new IllegalStateException("There should only be one data format ["+FullDataSourceV2.DATA_FORMAT_VERSION+"].");
}
dataSource.columnGenerationSteps = readBlobToGenerationSteps(this.compressedColumnGenStepByteArray, this.compressionModeEnum);
dataSource.columnWorldCompressionMode = readBlobToGenerationSteps(this.compressedWorldCompressionModeByteArray, this.compressionModeEnum);
dataSource.dataPoints = readBlobToDataSourceDataArray(this.compressedDataByteArray, this.compressionModeEnum);
EDhApiDataCompressionMode compressionModeEnum;
try
{
compressionModeEnum = this.getCompressionMode();
}
catch (IllegalArgumentException e)
{
// may happen if ZStd was used (which was added and removed during the nightly builds)
// or if the compressor value is changed to an invalid option
throw new DataCorruptedException(e);
}
dataSource.columnGenerationSteps = readBlobToGenerationSteps(this.compressedColumnGenStepByteArray, compressionModeEnum);
dataSource.columnWorldCompressionMode = readBlobToGenerationSteps(this.compressedWorldCompressionModeByteArray, compressionModeEnum);
dataSource.dataPoints = readBlobToDataSourceDataArray(this.compressedDataByteArray, compressionModeEnum);
dataSource.mapping.clear(dataSource.getPos());
// should only be null when used in a unit test
@@ -154,7 +171,13 @@ public class FullDataSourceV2DTO implements IBaseDTO<DhSectionPos>
throw new NullPointerException("No level wrapper present, unable to deserialize data map. This should only be used for unit tests.");
}
dataSource.mapping.mergeAndReturnRemappedEntityIds(readBlobToDataMapping(this.compressedMappingByteArray, dataSource.getPos(), levelWrapper, this.compressionModeEnum));
FullDataPointIdMap newMap = readBlobToDataMapping(this.compressedMappingByteArray, dataSource.getPos(), levelWrapper, compressionModeEnum);
dataSource.mapping.addAll(newMap);
if (dataSource.mapping.size() != newMap.size())
{
// if the mappings are out of sync then the LODs will render incorrectly due to IDs being wrong
LodUtil.assertNotReach("ID maps out of sync for pos: "+this.pos);
}
}
dataSource.lastModifiedUnixDateTime = this.lastModifiedUnixDateTime;
@@ -212,7 +235,7 @@ public class FullDataSourceV2DTO implements IBaseDTO<DhSectionPos>
return new CheckedByteArray(checksum, byteArrayOutputStream.toByteArray());
}
private static LongArrayList[] readBlobToDataSourceDataArray(byte[] compressedDataByteArray, EDhApiDataCompressionMode compressionModeEnum) throws IOException
private static LongArrayList[] readBlobToDataSourceDataArray(byte[] compressedDataByteArray, EDhApiDataCompressionMode compressionModeEnum) throws IOException, DataCorruptedException
{
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(compressedDataByteArray);
DhDataInputStream compressedIn = new DhDataInputStream(byteArrayInputStream, compressionModeEnum);
@@ -225,12 +248,21 @@ public class FullDataSourceV2DTO implements IBaseDTO<DhSectionPos>
{
// read the column length
short dataColumnLength = compressedIn.readShort(); // separate variables are used for debugging and in case validation wants to be added later
if (dataColumnLength < 0)
{
throw new DataCorruptedException("Read DataSource Blob data at index ["+xz+"], column length ["+dataColumnLength+"] should be greater than zero.");
}
LongArrayList dataColumn = new LongArrayList(new long[dataColumnLength]);
// read column data (will be skipped if no data was present)
for (int y = 0; y < dataColumnLength; y++)
{
long dataPoint = compressedIn.readLong();
if (VALIDATE_INPUT_DATAPOINTS)
{
FullDataPointUtil.validateDatapoint(dataPoint);
}
dataColumn.set(y, dataPoint);
}
@@ -254,15 +286,22 @@ public class FullDataSourceV2DTO implements IBaseDTO<DhSectionPos>
return byteArrayOutputStream.toByteArray();
}
private static byte[] readBlobToGenerationSteps(byte[] dataByteArray, EDhApiDataCompressionMode compressionModeEnum) throws IOException, InterruptedException
private static byte[] readBlobToGenerationSteps(byte[] dataByteArray, EDhApiDataCompressionMode compressionModeEnum) throws IOException, DataCorruptedException
{
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(dataByteArray);
DhDataInputStream compressedIn = new DhDataInputStream(byteArrayInputStream, compressionModeEnum);
byte[] columnGenStepByteArray = new byte[FullDataSourceV2.WIDTH * FullDataSourceV2.WIDTH];
compressedIn.readFully(columnGenStepByteArray);
return columnGenStepByteArray;
try
{
byte[] columnGenStepByteArray = new byte[FullDataSourceV2.WIDTH * FullDataSourceV2.WIDTH];
compressedIn.readFully(columnGenStepByteArray);
return columnGenStepByteArray;
}
catch (EOFException e)
{
throw new DataCorruptedException(e);
}
}
@@ -302,7 +341,7 @@ public class FullDataSourceV2DTO implements IBaseDTO<DhSectionPos>
return byteArrayOutputStream.toByteArray();
}
private static FullDataPointIdMap readBlobToDataMapping(byte[] compressedMappingByteArray, DhSectionPos pos, @NotNull ILevelWrapper levelWrapper, EDhApiDataCompressionMode compressionModeEnum) throws IOException, InterruptedException
private static FullDataPointIdMap readBlobToDataMapping(byte[] compressedMappingByteArray, long pos, @NotNull ILevelWrapper levelWrapper, EDhApiDataCompressionMode compressionModeEnum) throws IOException, InterruptedException, DataCorruptedException
{
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(compressedMappingByteArray);
DhDataInputStream compressedIn = new DhDataInputStream(byteArrayInputStream, compressionModeEnum);
@@ -318,10 +357,17 @@ public class FullDataSourceV2DTO implements IBaseDTO<DhSectionPos>
//===========//
@Override
public DhSectionPos getKey() { return this.pos; }
public Long getKey() { return this.pos; }
//================//
// helper methods //
//================//
public EDhApiDataCompressionMode getCompressionMode() throws IllegalArgumentException { return EDhApiDataCompressionMode.getFromValue(this.compressionModeValue); }
//================//
// helper classes //
//================//
@@ -20,7 +20,6 @@
package com.seibel.distanthorizons.core.sql.repo;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.sql.DatabaseUpdater;
import com.seibel.distanthorizons.core.sql.DbConnectionClosedException;
import com.seibel.distanthorizons.core.sql.dto.IBaseDTO;
@@ -39,7 +38,8 @@ import java.util.concurrent.locks.ReentrantLock;
*/
public abstract class AbstractDhRepo<TKey, TDTO extends IBaseDTO<TKey>> implements AutoCloseable
{
public static final int TIMEOUT_SECONDS = 30;
/** a value of 0 means there's no timeout */
public static final int TIMEOUT_SECONDS = 0;
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
private static final ConcurrentHashMap<String, Connection> CONNECTIONS_BY_CONNECTION_STRING = new ConcurrentHashMap<>();
@@ -23,6 +23,8 @@ import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiWorldGeneratio
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV1DTO;
import com.seibel.distanthorizons.coreapi.util.StringUtil;
import it.unimi.dsi.fastutil.longs.LongArrayList;
import org.jetbrains.annotations.Nullable;
import java.sql.PreparedStatement;
import java.sql.SQLException;
@@ -30,7 +32,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class FullDataSourceV1Repo extends AbstractDhRepo<DhSectionPos, FullDataSourceV1DTO>
public class FullDataSourceV1Repo extends AbstractDhRepo<Long, FullDataSourceV1DTO>
{
public static final String TABLE_NAME = "Legacy_FullData_V1";
@@ -55,7 +57,7 @@ public class FullDataSourceV1Repo extends AbstractDhRepo<DhSectionPos, FullDataS
public String getTableName() { return TABLE_NAME; }
@Override
public String createWhereStatement(DhSectionPos pos) { return "DhSectionPos = '"+pos.serialize()+"'"; }
public String createWhereStatement(Long pos) { return "DhSectionPos = '"+serializeSectionPos(pos)+"'"; }
@@ -67,7 +69,7 @@ public class FullDataSourceV1Repo extends AbstractDhRepo<DhSectionPos, FullDataS
public FullDataSourceV1DTO convertDictionaryToDto(Map<String, Object> objectMap) throws ClassCastException
{
String posString = (String) objectMap.get("DhSectionPos");
DhSectionPos pos = DhSectionPos.deserialize(posString);
Long pos = deserializeSectionPos(posString);
// meta data
int checksum = (Integer) objectMap.get("Checksum");
@@ -106,7 +108,7 @@ public class FullDataSourceV1Repo extends AbstractDhRepo<DhSectionPos, FullDataS
PreparedStatement statement = this.createPreparedStatement(sql);
int i = 1;
statement.setObject(i++, dto.pos.serialize());
statement.setObject(i++, serializeSectionPos(dto.pos));
statement.setObject(i++, dto.checksum);
statement.setObject(i++, 0 /*dto.dataVersion*/);
@@ -149,7 +151,7 @@ public class FullDataSourceV1Repo extends AbstractDhRepo<DhSectionPos, FullDataS
statement.setObject(i++, dto.dataArray);
statement.setObject(i++, dto.pos.serialize());
statement.setObject(i++, serializeSectionPos(dto.pos));
return statement;
}
@@ -205,9 +207,9 @@ public class FullDataSourceV1Repo extends AbstractDhRepo<DhSectionPos, FullDataS
}
/** Returns the new "returnCount" positions that need to be migrated */
public ArrayList<DhSectionPos> getPositionsToMigrate(int returnCount)
public LongArrayList getPositionsToMigrate(int returnCount)
{
ArrayList<DhSectionPos> list = new ArrayList<>();
LongArrayList list = new LongArrayList();
List<Map<String, Object>> resultMapList = this.queryDictionary(
"select DhSectionPos " +
@@ -218,19 +220,19 @@ public class FullDataSourceV1Repo extends AbstractDhRepo<DhSectionPos, FullDataS
for (Map<String, Object> resultMap : resultMapList)
{
// returned in the format [sectionDetailLevel,x,z] IE [6,0,0]
DhSectionPos sectionPos = DhSectionPos.deserialize((String) resultMap.get("DhSectionPos"));
long sectionPos = deserializeSectionPos((String) resultMap.get("DhSectionPos"));
list.add(sectionPos);
}
return list;
}
public void markMigrationFailed(DhSectionPos pos)
public void markMigrationFailed(long pos)
{
String sql =
"UPDATE "+this.getTableName()+" \n" +
"SET MigrationFailed = 1 \n" +
"WHERE DhSectionPos = '"+pos.serialize()+"'";
"WHERE DhSectionPos = '"+serializeSectionPos(pos)+"'";
this.queryDictionaryFirst(sql);
}
@@ -283,4 +285,30 @@ public class FullDataSourceV1Repo extends AbstractDhRepo<DhSectionPos, FullDataS
this.queryDictionaryFirst("delete from " + this.getTableName() + " where DhSectionPos in (" + sectionPosCsv + ")");
}
//=====================//
// section pos helpers //
//=====================//
private static String serializeSectionPos(long pos) { return "[" + DhSectionPos.getDetailLevel(pos) + ',' + DhSectionPos.getX(pos) + ',' + DhSectionPos.getZ(pos) + ']'; }
@Nullable
private static Long deserializeSectionPos(String value)
{
if (value.charAt(0) != '[' || value.charAt(value.length() - 1) != ']')
{
return null;
}
String[] split = value.substring(1, value.length() - 1).split(",");
if (split.length != 3)
{
return null;
}
return DhSectionPos.encode(Byte.parseByte(split[0]), Integer.parseInt(split[1]), Integer.parseInt(split[2]));
}
}
@@ -25,17 +25,17 @@ import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV2DTO;
import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataInputStream;
import it.unimi.dsi.fastutil.longs.LongArrayList;
import org.apache.logging.log4j.Logger;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class FullDataSourceV2Repo extends AbstractDhRepo<DhSectionPos, FullDataSourceV2DTO>
public class FullDataSourceV2Repo extends AbstractDhRepo<Long, FullDataSourceV2DTO>
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
@@ -60,10 +60,10 @@ public class FullDataSourceV2Repo extends AbstractDhRepo<DhSectionPos, FullDataS
public String getTableName() { return "FullData"; }
@Override
public String createWhereStatement(DhSectionPos pos)
public String createWhereStatement(Long pos)
{
int detailLevel = pos.getDetailLevel() - DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL;
return "DetailLevel = '"+detailLevel+"' AND PosX = '"+pos.getX()+"' AND PosZ = '"+pos.getZ()+"'";
int detailLevel = DhSectionPos.getDetailLevel(pos) - DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL;
return "DetailLevel = '"+detailLevel+"' AND PosX = '"+ DhSectionPos.getX(pos)+"' AND PosZ = '"+ DhSectionPos.getZ(pos)+"'";
}
@@ -79,7 +79,7 @@ public class FullDataSourceV2Repo extends AbstractDhRepo<DhSectionPos, FullDataS
byte sectionDetailLevel = (byte) (detailLevel + DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL);
int posX = (Integer) objectMap.get("PosX");
int posZ = (Integer) objectMap.get("PosZ");
DhSectionPos pos = new DhSectionPos(sectionDetailLevel, posX, posZ);
long pos = DhSectionPos.encode(sectionDetailLevel, posX, posZ);
int minY = (Integer) objectMap.get("MinY");
int dataChecksum = (Integer) objectMap.get("DataChecksum");
@@ -91,8 +91,7 @@ public class FullDataSourceV2Repo extends AbstractDhRepo<DhSectionPos, FullDataS
byte dataFormatVersion = (Byte) objectMap.get("DataFormatVersion");
byte compressionMode = (Byte) objectMap.get("CompressionMode");
EDhApiDataCompressionMode compressionModeEnum = EDhApiDataCompressionMode.getFromValue(compressionMode);
byte compressionModeValue = (Byte) objectMap.get("CompressionMode");
boolean applyToParent = ((int) objectMap.get("ApplyToParent")) == 1;
@@ -101,7 +100,7 @@ public class FullDataSourceV2Repo extends AbstractDhRepo<DhSectionPos, FullDataS
FullDataSourceV2DTO dto = new FullDataSourceV2DTO(
pos,
dataChecksum, columnGenStepByteArray, columnWorldCompressionByteArray, dataFormatVersion, compressionModeEnum, dataByteArray,
dataChecksum, columnGenStepByteArray, columnWorldCompressionByteArray, dataFormatVersion, compressionModeValue, dataByteArray,
lastModifiedUnixDateTime, createdUnixDateTime,
mappingByteArray, applyToParent,
minY);
@@ -128,9 +127,9 @@ public class FullDataSourceV2Repo extends AbstractDhRepo<DhSectionPos, FullDataS
PreparedStatement statement = this.createPreparedStatement(sql);
int i = 1;
statement.setObject(i++, dto.pos.getDetailLevel() - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL);
statement.setObject(i++, dto.pos.getX());
statement.setObject(i++, dto.pos.getZ());
statement.setObject(i++, DhSectionPos.getDetailLevel(dto.pos) - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL);
statement.setObject(i++, DhSectionPos.getX(dto.pos));
statement.setObject(i++, DhSectionPos.getZ(dto.pos));
statement.setObject(i++, dto.levelMinY);
statement.setObject(i++, dto.dataChecksum);
@@ -141,7 +140,7 @@ public class FullDataSourceV2Repo extends AbstractDhRepo<DhSectionPos, FullDataS
statement.setObject(i++, dto.compressedMappingByteArray);
statement.setObject(i++, dto.dataFormatVersion);
statement.setObject(i++, dto.compressionModeEnum.value);
statement.setObject(i++, dto.compressionModeValue);
statement.setObject(i++, dto.applyToParent);
statement.setObject(i++, System.currentTimeMillis()); // last modified unix time
@@ -184,15 +183,15 @@ public class FullDataSourceV2Repo extends AbstractDhRepo<DhSectionPos, FullDataS
statement.setObject(i++, dto.compressedMappingByteArray);
statement.setObject(i++, dto.dataFormatVersion);
statement.setObject(i++, dto.compressionModeEnum.value);
statement.setObject(i++, dto.compressionModeValue);
statement.setObject(i++, dto.applyToParent);
statement.setObject(i++, System.currentTimeMillis()); // last modified unix time
statement.setObject(i++, dto.createdUnixDateTime);
statement.setObject(i++, dto.pos.getDetailLevel() - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL);
statement.setObject(i++, dto.pos.getX());
statement.setObject(i++, dto.pos.getZ());
statement.setObject(i++, DhSectionPos.getDetailLevel(dto.pos) - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL);
statement.setObject(i++, DhSectionPos.getX(dto.pos));
statement.setObject(i++, DhSectionPos.getZ(dto.pos));
return statement;
}
@@ -201,21 +200,21 @@ public class FullDataSourceV2Repo extends AbstractDhRepo<DhSectionPos, FullDataS
// updates //
public void setApplyToParent(DhSectionPos pos, boolean applyToParent) throws SQLException
public void setApplyToParent(long pos, boolean applyToParent) throws SQLException
{
int detailLevel = pos.getDetailLevel() - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL;
int detailLevel = DhSectionPos.getDetailLevel(pos) - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL;
String sql =
"UPDATE "+this.getTableName()+" \n" +
"SET ApplyToParent = "+applyToParent+" \n" +
"WHERE DetailLevel = "+detailLevel+" AND PosX = "+pos.getX()+" AND PosZ = "+pos.getZ();
"WHERE DetailLevel = "+detailLevel+" AND PosX = "+ DhSectionPos.getX(pos)+" AND PosZ = "+ DhSectionPos.getZ(pos);
this.queryDictionaryFirst(sql);
}
public ArrayList<DhSectionPos> getPositionsToUpdate(int returnCount)
public LongArrayList getPositionsToUpdate(int returnCount)
{
ArrayList<DhSectionPos> list = new ArrayList<>();
LongArrayList list = new LongArrayList();
List<Map<String, Object>> resultMapList = this.queryDictionary(
"select DetailLevel, PosX, PosZ " +
@@ -230,7 +229,7 @@ public class FullDataSourceV2Repo extends AbstractDhRepo<DhSectionPos, FullDataS
int posX = (Integer) resultMap.get("PosX");
int posZ = (Integer) resultMap.get("PosZ");
DhSectionPos pos = new DhSectionPos(sectionDetailLevel, posX, posZ);
long pos = DhSectionPos.encode(sectionDetailLevel, posX, posZ);
list.add(pos);
}
@@ -238,14 +237,14 @@ public class FullDataSourceV2Repo extends AbstractDhRepo<DhSectionPos, FullDataS
}
/** @return null if nothing exists for this position */
public byte[] getColumnGenerationStepForPos(DhSectionPos pos)
public byte[] getColumnGenerationStepForPos(long pos)
{
int detailLevel = pos.getDetailLevel() - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL;
int detailLevel = DhSectionPos.getDetailLevel(pos) - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL;
Map<String, Object> resultMap = this.queryDictionaryFirst(
"select ColumnGenerationStep, CompressionMode " +
"from "+this.getTableName()+" " +
"WHERE DetailLevel = "+detailLevel+" AND PosX = "+pos.getX()+" AND PosZ = "+pos.getZ());
"WHERE DetailLevel = "+detailLevel+" AND PosX = "+ DhSectionPos.getX(pos)+" AND PosZ = "+ DhSectionPos.getZ(pos));
if (resultMap != null)
{
@@ -267,7 +266,7 @@ public class FullDataSourceV2Repo extends AbstractDhRepo<DhSectionPos, FullDataS
}
catch (IOException e)
{
LOGGER.warn("Decompression issue when getting column gen steps for pos: "+pos, e);
LOGGER.warn("Decompression issue when getting column gen steps for pos: [" + DhSectionPos.toString(pos) + "]", e);
return null;
}
}
@@ -284,9 +283,9 @@ public class FullDataSourceV2Repo extends AbstractDhRepo<DhSectionPos, FullDataS
//===================//
/** @return every position in this database */
public ArrayList<DhSectionPos> getAllPositions()
public LongArrayList getAllPositions()
{
ArrayList<DhSectionPos> list = new ArrayList<>();
LongArrayList list = new LongArrayList();
List<Map<String, Object>> resultMapList = this.queryDictionary(
"select DetailLevel, PosX, PosZ " +
@@ -299,7 +298,7 @@ public class FullDataSourceV2Repo extends AbstractDhRepo<DhSectionPos, FullDataS
int posX = (Integer) resultMap.get("PosX");
int posZ = (Integer) resultMap.get("PosZ");
DhSectionPos pos = new DhSectionPos(sectionDetailLevel, posX, posZ);
long pos = DhSectionPos.encode(sectionDetailLevel, posX, posZ);
list.add(pos);
}
@@ -310,14 +309,14 @@ public class FullDataSourceV2Repo extends AbstractDhRepo<DhSectionPos, FullDataS
* @return the size of the full data at the given position
* (doesn't include the size of the mapping or any other column)
*/
public long getDataSizeInBytes(DhSectionPos pos)
public long getDataSizeInBytes(long pos)
{
int detailLevel = pos.getDetailLevel() - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL;
int detailLevel = DhSectionPos.getDetailLevel(pos) - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL;
Map<String, Object> resultMap = this.queryDictionaryFirst(
"select LENGTH(Data) as dataSize " +
"from "+this.getTableName()+" " +
"WHERE DetailLevel = "+detailLevel+" AND PosX = "+pos.getX()+" AND PosZ = "+pos.getZ());
"WHERE DetailLevel = "+detailLevel+" AND PosX = "+ DhSectionPos.getX(pos)+" AND PosZ = "+ DhSectionPos.getZ(pos));
if (resultMap != null && resultMap.get("dataSize") != null)
{
@@ -1,141 +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 com.seibel.distanthorizons.core.util;
import it.unimi.dsi.fastutil.booleans.BooleanObjectImmutablePair;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicReferenceArray;
import java.util.function.Predicate;
/**
* While java 8 does have built in atomic operations, there doesn't seem to be any Compare And Exchange operation... <br>
* So here we implement our own.
*/
public class AtomicsUtil
{
public static <T> T conditionalAndExchange(AtomicReference<T> atomic, Predicate<T> requirement, T newValue)
{
while (true)
{
T oldValue = atomic.get();
if (!requirement.test(oldValue)) return oldValue;
if (atomic.weakCompareAndSet(oldValue, newValue)) return oldValue;
}
}
public static <T> BooleanObjectImmutablePair<T> conditionalAndExchangeWeak(AtomicReference<T> atomic, Predicate<T> requirement, T newValue)
{
T oldValue = atomic.get();
if (requirement.test(oldValue) && atomic.weakCompareAndSet(oldValue, newValue))
{
return new BooleanObjectImmutablePair<>(true, oldValue);
}
else
{
return new BooleanObjectImmutablePair<>(false, oldValue);
}
}
/**
* If the {@link AtomicReference}'s current value matches the expected value, the newValue will be swapped in and the expected value returned. <br>
* If the {@link AtomicReference}'s current value DOESN'T match the expected value, the {@link AtomicReference}'s current value will be returned without modification.
*/
public static <T> T compareAndExchange(AtomicReference<T> atomic, T expected, T newValue)
{
while (true)
{
T oldValue = atomic.get();
if (oldValue != expected)
{
return oldValue;
}
else if (atomic.weakCompareAndSet(expected, newValue))
{
return expected;
}
}
}
public static <T> BooleanObjectImmutablePair<T> compareAndExchangeWeak(AtomicReference<T> atomic, T expected, T newValue)
{
T oldValue = atomic.get();
if (oldValue == expected && atomic.weakCompareAndSet(expected, newValue))
{
return new BooleanObjectImmutablePair<>(true, expected);
}
else
{
return new BooleanObjectImmutablePair<>(false, oldValue);
}
}
// Additionally, we implement some helper methods for frequently used atomic operations. //
// Compare with expected value and set new value if equal. Then return whatever value the atomic now contains.
public static <T> T compareAndSetThenGet(AtomicReference<T> atomic, T expected, T newValue)
{
while (true)
{
T oldValue = atomic.get();
if (oldValue != expected) return oldValue;
if (atomic.weakCompareAndSet(expected, newValue)) return newValue;
}
}
// Below is the array version of the above. //
public static <T> T compareAndExchange(AtomicReferenceArray<T> array, int index, T expected, T newValue)
{
while (true)
{
T oldValue = array.get(index);
if (oldValue != expected) return oldValue;
if (array.weakCompareAndSet(index, expected, newValue)) return expected;
}
}
public static <T> BooleanObjectImmutablePair<T> compareAndExchangeWeak(AtomicReferenceArray<T> array, int index, T expected, T newValue)
{
T oldValue = array.get(index);
if (oldValue == expected && array.weakCompareAndSet(index, expected, newValue))
{
return new BooleanObjectImmutablePair<>(true, expected);
}
else
{
return new BooleanObjectImmutablePair<>(false, oldValue);
}
}
public static <T> T compareAndSetThenGet(AtomicReferenceArray<T> array, int index, T expected, T newValue)
{
while (true)
{
T oldValue = array.get(index);
if (oldValue != expected) return oldValue;
if (array.weakCompareAndSet(index, expected, newValue)) return newValue;
}
}
}
@@ -2,6 +2,7 @@ package com.seibel.distanthorizons.core.util;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV1;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.util.objects.DataCorruptedException;
import com.seibel.distanthorizons.coreapi.ModInfo;
import org.jetbrains.annotations.Contract;
@@ -72,23 +73,11 @@ public class FullDataPointUtil
* creates a new datapoint with the given values
* @param relMinY relative to the minimum level Y value
*/
public static long encode(int id, int height, int relMinY, byte blockLight, byte skyLight)
public static long encode(int id, int height, int relMinY, byte blockLight, byte skyLight) throws DataCorruptedException
{
if (RUN_VALIDATION)
{
// assertions are inside if-blocks to prevent unnecessary string concatenations
if (relMinY < 0 || relMinY >= RenderDataPointUtil.MAX_WORLD_Y_SIZE)
{
LodUtil.assertNotReach("Trying to create datapoint with y[" + relMinY + "] out of range!");
}
if (height <= 0 || height >= RenderDataPointUtil.MAX_WORLD_Y_SIZE)
{
LodUtil.assertNotReach("Trying to create datapoint with height[" + height + "] out of range!");
}
if (relMinY + height > RenderDataPointUtil.MAX_WORLD_Y_SIZE)
{
LodUtil.assertNotReach("Trying to create datapoint with y+depth[" + (relMinY + height) + "] out of range!");
}
validateData(id, height, relMinY, blockLight, skyLight);
}
@@ -116,6 +105,44 @@ public class FullDataPointUtil
return data;
}
public static void validateDatapoint(long datapoint) throws DataCorruptedException { validateData(getId(datapoint), getHeight(datapoint), getBottomY(datapoint), (byte)getBlockLight(datapoint), (byte)getSkyLight(datapoint)); }
/**
* Throws {@link DataCorruptedException} if any of the given values are outside
* their expected range.
*/
public static void validateData(int id, int height, int relMinY, byte blockLight, byte skyLight) throws DataCorruptedException
{
// ID
if (id < 0)
{
throw new DataCorruptedException("Full datapoint ID [" + relMinY + "] must be greater than zero.");
}
// height
if (relMinY < 0 || relMinY >= RenderDataPointUtil.MAX_WORLD_Y_SIZE)
{
throw new DataCorruptedException("Full datapoint relative min y [" + relMinY + "] must be in the range [0 - "+RenderDataPointUtil.MAX_WORLD_Y_SIZE+"] (inclusive).");
}
if (height <= 0 || height >= RenderDataPointUtil.MAX_WORLD_Y_SIZE)
{
throw new DataCorruptedException("Full datapoint height [" + height + "] must be in the range [1 - "+RenderDataPointUtil.MAX_WORLD_Y_SIZE+"] (inclusive).");
}
if (relMinY + height > RenderDataPointUtil.MAX_WORLD_Y_SIZE)
{
throw new DataCorruptedException("Full datapoint y+depth [" + (relMinY + height) + "] is higher than the maximum world Y height ["+RenderDataPointUtil.MAX_WORLD_Y_SIZE+"].");
}
// lighting
if (blockLight < LodUtil.MIN_MC_LIGHT || blockLight > LodUtil.MAX_MC_LIGHT)
{
throw new DataCorruptedException("Full datapoint block light [" + blockLight + "] must be in the range ["+LodUtil.MIN_MC_LIGHT+" - "+LodUtil.MAX_MC_LIGHT+"] (inclusive).");
}
if (skyLight < LodUtil.MIN_MC_LIGHT || skyLight > LodUtil.MAX_MC_LIGHT)
{
throw new DataCorruptedException("Full datapoint sky light [" + skyLight + "] must be in the range ["+LodUtil.MIN_MC_LIGHT+" - "+LodUtil.MAX_MC_LIGHT+"] (inclusive).");
}
}
/** Returns the BlockState/Biome pair ID used to identify this LOD's color */
public static int getId(long data) { return (int) (data & ID_MASK); }
@@ -0,0 +1,28 @@
package com.seibel.distanthorizons.core.util.objects;
/**
* Thrown when a DH handled resource or datasource isn't in the
* correct format. <Br><Br>
*
* IE: a blocklight with the value -4 when it should be between 0 and 15.
*/
public class DataCorruptedException extends Exception
{
/** replaces this exception's stack trace with the incoming one */
public DataCorruptedException(Exception e)
{
super(e.getMessage());
this.setStackTrace(e.getStackTrace());
this.addSuppressed(e);
}
public DataCorruptedException(String message) { super(message); }
public DataCorruptedException(String message, Exception e)
{
super(message);
this.setStackTrace(e.getStackTrace());
this.addSuppressed(e);
}
}
@@ -19,9 +19,8 @@
package com.seibel.distanthorizons.core.util.objects.dataStreams;
import com.github.luben.zstd.RecyclingBufferPool;
import com.github.luben.zstd.ZstdInputStream;
import com.seibel.distanthorizons.api.enums.config.EDhApiDataCompressionMode;
import com.seibel.distanthorizons.core.util.objects.DataCorruptedException;
import net.jpountz.lz4.LZ4FrameInputStream;
import org.tukaani.xz.XZInputStream;
@@ -51,8 +50,6 @@ public class DhDataInputStream extends DataInputStream
return stream;
case LZ4:
return new LZ4FrameInputStream(stream);
case Z_STD:
return new ZstdInputStream(stream, RecyclingBufferPool.INSTANCE);
case LZMA2:
// Note: all LZMA/XZ compressors can be decompressed using this same InputStream
return new XZInputStream(stream);
@@ -19,14 +19,10 @@
package com.seibel.distanthorizons.core.util.objects.dataStreams;
import com.github.luben.zstd.RecyclingBufferPool;
import com.github.luben.zstd.ZstdOutputStream;
import com.seibel.distanthorizons.api.enums.config.EDhApiDataCompressionMode;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import net.jpountz.lz4.LZ4Factory;
import net.jpountz.lz4.LZ4FrameOutputStream;
import net.jpountz.xxhash.XXHashFactory;
import org.apache.logging.log4j.Logger;
import org.tukaani.xz.*;
import java.io.*;
@@ -59,8 +55,6 @@ public class DhDataOutputStream extends DataOutputStream
LZ4Factory.nativeInstance().fastCompressor(),
XXHashFactory.nativeInstance().hash32(),
LZ4FrameOutputStream.FLG.Bits.BLOCK_INDEPENDENCE);
case Z_STD:
return new ZstdOutputStream(stream, RecyclingBufferPool.INSTANCE);
case LZMA2:
// using an array cache significantly reduces GC pressure
ResettableArrayCache arrayCache = LZMA_RESETTABLE_ARRAY_CACHE_GETTER.get();
@@ -2,12 +2,12 @@ package com.seibel.distanthorizons.core.util.objects.dataStreams;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import it.unimi.dsi.fastutil.ints.Int2ReferenceArrayMap;
import it.unimi.dsi.fastutil.ints.IntUnaryOperator;
import org.apache.logging.log4j.Logger;
import org.tukaani.xz.ArrayCache;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.function.IntUnaryOperator;
import java.util.concurrent.atomic.AtomicInteger;
/**
@@ -25,6 +25,7 @@ import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.objects.quadTree.iterators.QuadNodeDirectChildIterator;
import com.seibel.distanthorizons.core.util.objects.quadTree.iterators.QuadNodeDirectChildPosIterator;
import com.seibel.distanthorizons.core.util.objects.quadTree.iterators.QuadTreeNodeIterator;
import it.unimi.dsi.fastutil.longs.LongIterator;
import org.apache.logging.log4j.Logger;
import java.util.Iterator;
@@ -35,7 +36,7 @@ public class QuadNode<T>
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
public final DhSectionPos sectionPos;
public final long sectionPos;
public final byte minimumDetailLevel;
public T value;
@@ -67,7 +68,7 @@ public class QuadNode<T>
public QuadNode(DhSectionPos sectionPos, byte minimumDetailLevel)
public QuadNode(long sectionPos, byte minimumDetailLevel)
{
this.sectionPos = sectionPos;
this.minimumDetailLevel = minimumDetailLevel;
@@ -145,14 +146,14 @@ public class QuadNode<T>
* @return the node at the given position
* @throws IllegalArgumentException if childSectionPos has the wrong detail level or is outside the bounds of this node
*/
public QuadNode<T> getNode(DhSectionPos sectionPos) throws IllegalArgumentException { return this.getOrSetValue(sectionPos, false, null); }
public QuadNode<T> getNode(long sectionPos) throws IllegalArgumentException { return this.getOrSetValue(sectionPos, false, null); }
/**
* @param sectionPos must be 1 detail level lower than this node's detail level
* @return the value at the given position before the new value was set
* @throws IllegalArgumentException if childSectionPos has the wrong detail level or is outside the bounds of this node
*/
public T setValue(DhSectionPos sectionPos, T newValue) throws IllegalArgumentException
public T setValue(long sectionPos, T newValue) throws IllegalArgumentException
{
QuadNode<T> previousNode = this.getNode(sectionPos);
if (previousNode != null)
@@ -173,27 +174,27 @@ public class QuadNode<T>
* @return the node at the given position before the new node was set (if the new node should be set)
* @throws IllegalArgumentException if childSectionPos has the wrong detail level or is outside the bounds of this
*/
private QuadNode<T> getOrSetValue(DhSectionPos inputSectionPos, boolean replaceValue, T newValue) throws IllegalArgumentException
private QuadNode<T> getOrSetValue(long inputSectionPos, boolean replaceValue, T newValue) throws IllegalArgumentException
{
// debug validation
if (!this.sectionPos.contains(inputSectionPos))
if (!DhSectionPos.contains(this.sectionPos, inputSectionPos))
{
LOGGER.error((replaceValue ? "set " : "get ") + inputSectionPos + " center block: " + inputSectionPos.getCenterBlockPos() + ", this pos: " + this.sectionPos + " this center block: " + this.sectionPos.getCenterBlockPos());
throw new IllegalArgumentException("Input section pos " + inputSectionPos + " outside of this quadNode's pos: " + this.sectionPos + ", this node's blockPos: " + this.sectionPos.convertNewToDetailLevel(LodUtil.BLOCK_DETAIL_LEVEL) + " block width: " + this.sectionPos.getBlockWidth() + " input detail level: " + inputSectionPos.convertNewToDetailLevel(LodUtil.BLOCK_DETAIL_LEVEL) + " width: " + inputSectionPos.getBlockWidth());
LOGGER.error((replaceValue ? "set " : "get ") + inputSectionPos + " center block: " + DhSectionPos.getCenterBlockPos(inputSectionPos) + ", this pos: " + this.sectionPos + " this center block: " + DhSectionPos.getCenterBlockPos(this.sectionPos));
throw new IllegalArgumentException("Input section pos " + inputSectionPos + " outside of this quadNode's pos: " + this.sectionPos + ", this node's blockPos: " + DhSectionPos.convertToDetailLevel(this.sectionPos, LodUtil.BLOCK_DETAIL_LEVEL) + " block width: " + DhSectionPos.getBlockWidth(this.sectionPos) + " input detail level: " + DhSectionPos.convertToDetailLevel(inputSectionPos, LodUtil.BLOCK_DETAIL_LEVEL) + " width: " + DhSectionPos.getBlockWidth(inputSectionPos));
}
if (inputSectionPos.getDetailLevel() > this.sectionPos.getDetailLevel())
if (DhSectionPos.getDetailLevel(inputSectionPos) > DhSectionPos.getDetailLevel(this.sectionPos))
{
throw new IllegalArgumentException("detail level higher than this node. Node Detail level: " + this.sectionPos.getDetailLevel() + " input detail level: " + inputSectionPos.getDetailLevel());
throw new IllegalArgumentException("detail level higher than this node. Node Detail level: " + DhSectionPos.getDetailLevel(this.sectionPos) + " input detail level: " + DhSectionPos.getDetailLevel(inputSectionPos));
}
if (inputSectionPos.getDetailLevel() == this.sectionPos.getDetailLevel() && !inputSectionPos.equals(this.sectionPos))
if (DhSectionPos.getDetailLevel(inputSectionPos) == DhSectionPos.getDetailLevel(this.sectionPos) && inputSectionPos != this.sectionPos)
{
throw new IllegalArgumentException("Node and input detail level are equal, however positions are not; this tree doesn't contain the requested position. Node pos: " + this.sectionPos + ", input pos: " + inputSectionPos);
}
if (inputSectionPos.getDetailLevel() < this.minimumDetailLevel)
if (DhSectionPos.getDetailLevel(inputSectionPos) < this.minimumDetailLevel)
{
throw new IllegalArgumentException("Input position is requesting a detail level lower than what this node can provide. Node minimum detail level: " + this.minimumDetailLevel + ", input pos: " + inputSectionPos);
}
@@ -201,7 +202,7 @@ public class QuadNode<T>
// get/set logic
if (inputSectionPos.getDetailLevel() == this.sectionPos.getDetailLevel())
if (DhSectionPos.getDetailLevel(inputSectionPos) == DhSectionPos.getDetailLevel(this.sectionPos))
{
// this node is the requested position
if (replaceValue)
@@ -217,14 +218,14 @@ public class QuadNode<T>
// LOGGER.info((replaceValue ? "set " : "get ")+inputSectionPos+" center block: "+inputSectionPos.getCenter().getCornerBlockPos()+", this pos: "+this.sectionPos+" this center block: "+this.sectionPos.getCenter().getCornerBlockPos());
DhSectionPos nwPos = this.sectionPos.getChildByIndex(0);
DhSectionPos swPos = this.sectionPos.getChildByIndex(1);
DhSectionPos nePos = this.sectionPos.getChildByIndex(2);
DhSectionPos sePos = this.sectionPos.getChildByIndex(3);
long nwPos = DhSectionPos.getChildByIndex(this.sectionPos, 0);
long swPos = DhSectionPos.getChildByIndex(this.sectionPos, 1);
long nePos = DhSectionPos.getChildByIndex(this.sectionPos, 2);
long sePos = DhSectionPos.getChildByIndex(this.sectionPos, 3);
// look for the child that contains the input position (there may be a faster way to do this, but this works for now)
QuadNode<T> childNode;
if (nwPos.contains(inputSectionPos))
if (DhSectionPos.contains(nwPos, inputSectionPos))
{
// TODO merge duplicate code
if (replaceValue && this.nwChild == null)
@@ -237,7 +238,7 @@ public class QuadNode<T>
// childNode should only be null when replaceValue = false and the end of a node chain has been reached
return (childNode != null) ? childNode.getOrSetValue(inputSectionPos, replaceValue, newValue) : null;
}
else if (swPos.contains(inputSectionPos))
else if (DhSectionPos.contains(swPos, inputSectionPos))
{
// TODO merge duplicate code
if (replaceValue && this.swChild == null)
@@ -250,7 +251,7 @@ public class QuadNode<T>
// childNode should only be null when replaceValue = false and the end of a node chain has been reached
return (childNode != null) ? childNode.getOrSetValue(inputSectionPos, replaceValue, newValue) : null;
}
else if (nePos.contains(inputSectionPos))
else if (DhSectionPos.contains(nePos, inputSectionPos))
{
// TODO merge duplicate code
if (replaceValue && this.neChild == null)
@@ -263,7 +264,7 @@ public class QuadNode<T>
// childNode should only be null when replaceValue = false and the end of a node chain has been reached
return (childNode != null) ? childNode.getOrSetValue(inputSectionPos, replaceValue, newValue) : null;
}
else if (sePos.contains(inputSectionPos))
else if (DhSectionPos.contains(sePos, inputSectionPos))
{
// TODO merge duplicate code
if (replaceValue && this.seChild == null)
@@ -293,7 +294,7 @@ public class QuadNode<T>
public Iterator<QuadNode<T>> getLeafNodeIterator() { return new QuadTreeNodeIterator<>(this, true); }
/** positions can point to null children */
public Iterator<DhSectionPos> getChildPosIterator() { return new QuadNodeDirectChildPosIterator<>(this); }
public LongIterator getChildPosIterator() { return new QuadNodeDirectChildPosIterator<>(this); }
public Iterator<QuadNode<T>> getChildNodeIterator() { return new QuadNodeDirectChildIterator<>(this); }
@@ -28,13 +28,13 @@ import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.coreapi.util.BitShiftUtil;
import com.seibel.distanthorizons.coreapi.util.MathUtil;
import com.seibel.distanthorizons.core.util.gridList.MovableGridRingList;
import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue;
import it.unimi.dsi.fastutil.longs.LongIterator;
import org.apache.logging.log4j.Logger;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.NoSuchElementException;
import java.util.Queue;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.LongConsumer;
/**
* This class represents a quadTree of T type values.
@@ -96,9 +96,9 @@ public class QuadTree<T>
//=====================//
/** @return the node at the given section position */
public final QuadNode<T> getNode(DhSectionPos pos) throws IndexOutOfBoundsException { return this.getOrSetNode(pos, false, null, true); }
public final QuadNode<T> getNode(long pos) throws IndexOutOfBoundsException { return this.getOrSetNode(pos, false, null, true); }
/** @return the value at the given section position */
public final T getValue(DhSectionPos pos) throws IndexOutOfBoundsException
public final T getValue(long pos) throws IndexOutOfBoundsException
{
QuadNode<T> node = this.getNode(pos);
if (node != null)
@@ -109,7 +109,7 @@ public class QuadTree<T>
}
/** @return the value that was previously in the given position, null if nothing */
public final T setValue(DhSectionPos pos, T value) throws IndexOutOfBoundsException
public final T setValue(long pos, T value) throws IndexOutOfBoundsException
{
T previousValue = this.getValue(pos);
this.getOrSetNode(pos, true, value, true);
@@ -117,21 +117,21 @@ public class QuadTree<T>
}
/** @param runBoundaryChecks should only ever be set to true internally for removing out of bound nodes */
protected final QuadNode<T> getOrSetNode(DhSectionPos pos, boolean setNewValue, T newValue, boolean runBoundaryChecks) throws IndexOutOfBoundsException
protected final QuadNode<T> getOrSetNode(long pos, boolean setNewValue, T newValue, boolean runBoundaryChecks) throws IndexOutOfBoundsException
{
if (runBoundaryChecks && !this.isSectionPosInBounds(pos))
{
int radius = this.diameterInBlocks() / 2;
DhBlockPos2D minPos = this.getCenterBlockPos().add(new DhBlockPos2D(-radius, -radius));
DhBlockPos2D maxPos = this.getCenterBlockPos().add(new DhBlockPos2D(radius, radius));
throw new IndexOutOfBoundsException("QuadTree GetOrSet failed. Position out of bounds, min pos: " + minPos + ", max pos: " + maxPos + ", min detail level: " + this.treeMaxDetailLevel + ", max detail level: " + this.treeMinDetailLevel + ". Given Position: " + pos + " = block pos: " + pos.convertNewToDetailLevel(LodUtil.BLOCK_DETAIL_LEVEL));
throw new IndexOutOfBoundsException("QuadTree GetOrSet failed. Position out of bounds, min pos: " + minPos + ", max pos: " + maxPos + ", min detail level: " + this.treeMaxDetailLevel + ", max detail level: " + this.treeMinDetailLevel + ". Given Position: [" + DhSectionPos.toString(pos) + "] = block pos: " + DhSectionPos.convertToDetailLevel(pos, LodUtil.BLOCK_DETAIL_LEVEL));
}
DhSectionPos rootPos = pos.convertNewToDetailLevel(this.treeMinDetailLevel);
int ringListPosX = rootPos.getX();
int ringListPosZ = rootPos.getZ();
long rootPos = DhSectionPos.convertToDetailLevel(pos, this.treeMinDetailLevel);
int ringListPosX = DhSectionPos.getX(rootPos);
int ringListPosZ = DhSectionPos.getZ(rootPos);
QuadNode<T> topQuadNode = this.topRingList.get(ringListPosX, ringListPosZ);
if (topQuadNode == null)
@@ -149,7 +149,7 @@ public class QuadTree<T>
}
}
if (!topQuadNode.sectionPos.contains(pos))
if (!DhSectionPos.contains(topQuadNode.sectionPos, pos))
{
LodUtil.assertNotReach("failed to get a root node that contains the input position: " + pos + " root node pos: " + topQuadNode.sectionPos);
}
@@ -163,10 +163,10 @@ public class QuadTree<T>
return returnNode;
}
public boolean isSectionPosInBounds(DhSectionPos testPos)
public boolean isSectionPosInBounds(long testPos)
{
// check if the testPos is within the detail level limits of the tree
boolean detailLevelWithinBounds = this.treeMaxDetailLevel <= testPos.getDetailLevel() && testPos.getDetailLevel() <= this.treeMinDetailLevel;
boolean detailLevelWithinBounds = this.treeMaxDetailLevel <= DhSectionPos.getDetailLevel(testPos) && DhSectionPos.getDetailLevel(testPos) <= this.treeMinDetailLevel;
if (!detailLevelWithinBounds)
{
return false;
@@ -177,9 +177,9 @@ public class QuadTree<T>
DhBlockPos2D treeBlockCorner = this.centerBlockPos.add(new DhBlockPos2D(-this.diameterInBlocks / 2, -this.diameterInBlocks / 2));
DhLodPos treeCornerPos = new DhLodPos((byte) 0, treeBlockCorner.x, treeBlockCorner.z);
DhSectionPos inputSectionCorner = testPos.convertNewToDetailLevel((byte) 0);
DhLodPos inputCornerPos = new DhLodPos((byte) 0, inputSectionCorner.getX(), inputSectionCorner.getZ());
int inputBlockWidth = BitShiftUtil.powerOfTwo(testPos.getDetailLevel());
long inputSectionCorner = DhSectionPos.convertToDetailLevel(testPos, (byte) 0);
DhLodPos inputCornerPos = new DhLodPos((byte) 0, DhSectionPos.getX(inputSectionCorner), DhSectionPos.getZ(inputSectionCorner));
int inputBlockWidth = BitShiftUtil.powerOfTwo(DhSectionPos.getDetailLevel(testPos));
return DoSquaresOverlap(treeCornerPos, this.diameterInBlocks, inputCornerPos, inputBlockWidth);
}
@@ -205,13 +205,13 @@ public class QuadTree<T>
}
public int getNonNullChildCountAtPos(DhSectionPos pos) { return this.getChildCountAtPos(pos, false); }
public int getChildCountAtPos(DhSectionPos pos, boolean includeNullValues)
public int getNonNullChildCountAtPos(long pos) { return this.getChildCountAtPos(pos, false); }
public int getChildCountAtPos(long pos, boolean includeNullValues)
{
int childCount = 0;
for (int i = 0; i < 4; i++)
{
DhSectionPos childPos = pos.getChildByIndex(i);
long childPos = DhSectionPos.getChildByIndex(pos, i);
if (this.isSectionPosInBounds(childPos))
{
T value = this.getValue(childPos);
@@ -232,7 +232,7 @@ public class QuadTree<T>
//===========//
/** can include null nodes */
public Iterator<DhSectionPos> rootNodePosIterator() { return new QuadTreeRootPosIterator(true); }
public LongIterator rootNodePosIterator() { return new QuadTreeRootPosIterator(true); }
public Iterator<QuadNode<T>> nodeIterator() { return new QuadTreeNodeIterator(false); }
public Iterator<QuadNode<T>> leafNodeIterator() { return new QuadTreeNodeIterator(true); }
@@ -397,9 +397,9 @@ public class QuadTree<T>
// iterator classes //
//==================//
private class QuadTreeRootPosIterator implements Iterator<DhSectionPos>
private class QuadTreeRootPosIterator implements LongIterator
{
private final Queue<DhSectionPos> iteratorPosQueue = new LinkedList<>();
private final LongArrayFIFOQueue iteratorPosQueue = new LongArrayFIFOQueue();
@@ -409,11 +409,10 @@ public class QuadTree<T>
{
if (node != null || includeNullNodes)
{
// TODO can these DhSectionPos be pooled?
DhSectionPos rootPos = new DhSectionPos(QuadTree.this.treeMinDetailLevel, pos2D.x, pos2D.y);
long rootPos = DhSectionPos.encode(QuadTree.this.treeMinDetailLevel, pos2D.x, pos2D.y);
if (QuadTree.this.isSectionPosInBounds(rootPos))
{
this.iteratorPosQueue.add(rootPos);
this.iteratorPosQueue.enqueue(rootPos);
}
}
});
@@ -425,7 +424,7 @@ public class QuadTree<T>
public boolean hasNext() { return this.iteratorPosQueue.size() != 0; }
@Override
public DhSectionPos next()
public long nextLong()
{
if (this.iteratorPosQueue.size() == 0)
{
@@ -433,7 +432,7 @@ public class QuadTree<T>
}
DhSectionPos sectionPos = this.iteratorPosQueue.poll();
long sectionPos = this.iteratorPosQueue.dequeueLong();
return sectionPos;
}
@@ -443,7 +442,7 @@ public class QuadTree<T>
public void remove() { throw new UnsupportedOperationException("remove"); }
@Override
public void forEachRemaining(Consumer<? super DhSectionPos> action) { Iterator.super.forEachRemaining(action); }
public void forEachRemaining(LongConsumer action) { LongIterator.super.forEachRemaining(action); }
}
@@ -501,7 +500,7 @@ public class QuadTree<T>
Iterator<QuadNode<T>> nodeIterator = null;
while ((nodeIterator == null || !nodeIterator.hasNext()) && this.rootNodeIterator.hasNext())
{
DhSectionPos sectionPos = this.rootNodeIterator.next();
long sectionPos = this.rootNodeIterator.nextLong();
QuadNode<T> rootNode = QuadTree.this.getNode(sectionPos);
if (rootNode != null)
{
@@ -19,6 +19,7 @@
package com.seibel.distanthorizons.core.util.objects.quadTree.iterators;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.util.objects.quadTree.QuadNode;
import java.util.Iterator;
@@ -36,7 +37,7 @@ public class QuadNodeChildIndexIterator<T> implements Iterator<Integer>
public QuadNodeChildIndexIterator(QuadNode<T> parentNode, boolean returnNullChildPos)
{
// only get the children if this section isn't at the bottom of the tree
if (parentNode.sectionPos.getDetailLevel() > parentNode.minimumDetailLevel)
if (DhSectionPos.getDetailLevel(parentNode.sectionPos) > parentNode.minimumDetailLevel)
{
// go over each child pos
for (int i = 0; i < 4; i++)
@@ -21,12 +21,12 @@ package com.seibel.distanthorizons.core.util.objects.quadTree.iterators;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.util.objects.quadTree.QuadNode;
import it.unimi.dsi.fastutil.longs.LongIterator;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.function.Consumer;
import java.util.function.LongConsumer;
public class QuadNodeDirectChildPosIterator<T> implements Iterator<DhSectionPos>
public class QuadNodeDirectChildPosIterator<T> implements LongIterator
{
private final QuadNodeChildIndexIterator<T> childIndexIterator;
private final QuadNode<T> parentNode;
@@ -44,7 +44,7 @@ public class QuadNodeDirectChildPosIterator<T> implements Iterator<DhSectionPos>
public boolean hasNext() { return this.childIndexIterator.hasNext(); }
@Override
public DhSectionPos next()
public long nextLong()
{
if (!this.hasNext())
{
@@ -53,7 +53,7 @@ public class QuadNodeDirectChildPosIterator<T> implements Iterator<DhSectionPos>
int childIndex = this.childIndexIterator.next();
DhSectionPos sectionPos = this.parentNode.sectionPos.getChildByIndex(childIndex);
long sectionPos = DhSectionPos.getChildByIndex(this.parentNode.sectionPos, childIndex);
return sectionPos;
}
@@ -63,6 +63,6 @@ public class QuadNodeDirectChildPosIterator<T> implements Iterator<DhSectionPos>
public void remove() { throw new UnsupportedOperationException("remove"); }
@Override
public void forEachRemaining(Consumer<? super DhSectionPos> action) { Iterator.super.forEachRemaining(action); }
public void forEachRemaining(LongConsumer action) { LongIterator.super.forEachRemaining(action); }
}
@@ -19,6 +19,7 @@
package com.seibel.distanthorizons.core.util.objects.quadTree.iterators;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.util.objects.quadTree.QuadNode;
import java.util.Iterator;
@@ -46,7 +47,7 @@ public class QuadTreeNodeIterator<T> implements Iterator<QuadNode<T>>
this.onlyReturnLeafValues = onlyReturnLeafValues;
// TODO the naming conversion for these are flipped in a lot of places
this.highestDetailLevel = rootNode.minimumDetailLevel;
this.iteratorDetailLevel = rootNode.sectionPos.getDetailLevel();
this.iteratorDetailLevel = DhSectionPos.getDetailLevel(rootNode.sectionPos);
if (!this.onlyReturnLeafValues)
@@ -32,7 +32,7 @@ public class PositionalLockProvider
private static final int MAX_NUMBER_OF_LOCKS = 100;
private final ConcurrentHashMap<DhSectionPos, ExpiringLock> lockByPos = new ConcurrentHashMap<>();
private final ConcurrentHashMap<Long, ExpiringLock> lockByPos = new ConcurrentHashMap<>();
private final AtomicBoolean lockRemovalThreadRunning = new AtomicBoolean(false);
@@ -50,7 +50,7 @@ public class PositionalLockProvider
// getter //
//========//
public ReentrantLock getLock(DhSectionPos pos)
public ReentrantLock getLock(long pos)
{
return this.lockByPos.compute(pos, (ignorePos, lock) ->
{
@@ -76,14 +76,14 @@ public class PositionalLockProvider
Thread.sleep(CLEANUP_THREAD_MAX_FREQUENCY_IN_MS);
// walk over every lock and check which ones need to be removed
Iterator<DhSectionPos> keySet = this.lockByPos.keySet().iterator();
Iterator<Long> keySet = this.lockByPos.keySet().iterator();
while (keySet.hasNext())
{
try
{
long currentTime = System.currentTimeMillis();
DhSectionPos pos = keySet.next();
long pos = keySet.next();
ExpiringLock lock = this.lockByPos.get(pos);
// don't try removing a lock that's currently in use
@@ -92,7 +92,7 @@ public class PositionalLockProvider
if (currentTime > lock.expirationTimeInMs)
{
this.lockByPos.remove(pos);
//LOGGER.info("removed lock: "+pos);
//LOGGER.info("removed lock: "+DhSectionPos.toString(pos));
}
lock.unlock();
}
@@ -40,9 +40,43 @@ import java.util.HashSet;
public interface IWrapperFactory extends IDhApiWrapperFactory, IBindable
{
AbstractBatchGenerationEnvironmentWrapper createBatchGenerator(IDhLevel targetLevel);
IBiomeWrapper deserializeBiomeWrapper(String str, ILevelWrapper levelWrapper) throws IOException;
IBiomeWrapper getPlainsBiomeWrapper(ILevelWrapper levelWrapper); // TODO it would be nice to remove the level wrapper if possible to put this in line with getAirBlockStateWrapper() but it isn't necessary
default IBiomeWrapper deserializeBiomeWrapperOrGetDefault(String str, ILevelWrapper levelWrapper)
{
IBiomeWrapper biome;
try
{
biome = this.deserializeBiomeWrapper(str, levelWrapper);
}
catch (IOException e)
{
biome = this.getPlainsBiomeWrapper(levelWrapper);
}
return biome;
}
IBlockStateWrapper deserializeBlockStateWrapper(String str, ILevelWrapper levelWrapper) throws IOException;
IBlockStateWrapper getAirBlockStateWrapper();
default IBlockStateWrapper deserializeBlockStateWrapperOrGetDefault(String str, ILevelWrapper levelWrapper)
{
IBlockStateWrapper blockState;
try
{
blockState = this.deserializeBlockStateWrapper(str, levelWrapper);
}
catch (IOException e)
{
blockState = this.getAirBlockStateWrapper();
}
return blockState;
}
/**
* Returns the set of {@link IBlockStateWrapper}'s that shouldn't be rendered. <br>
* Generally this contains blocks like: air, barriers, light blocks, etc.
@@ -28,7 +28,7 @@ import com.seibel.distanthorizons.coreapi.interfaces.dependencyInjection.IBindab
public interface ILightMapWrapper extends IBindable
{
// Returns the binded texture position
/** Returns the bound texture position */
void bind();
void unbind();
@@ -37,4 +37,8 @@ public interface IClientLevelWrapper extends ILevelWrapper
/** @return -1 if there was a problem getting the color */
int getDirtBlockColor();
/** Will return null if there was an issue finding the biome. */
@Nullable
IBiomeWrapper getPlainsBiomeWrapper();
}
@@ -321,7 +321,7 @@
"distanthorizons.config.client.advanced.worldGenerator.distantGeneratorMode":
"Distance Generator Mode",
"distanthorizons.config.client.advanced.worldGenerator.distantGeneratorMode.@tooltip":
"How complicated the generation should be when generating LODs outside the vanilla render distance.\n\n§6§6Fastest:§r Biome only\n§6Best Quality:§r Features (suggested)",
"How complicated the generation should be when generating LODs outside the vanilla render distance.",
"distanthorizons.config.client.advanced.worldGenerator.worldGenLightingEngine":
"Lighting Engine",
"distanthorizons.config.client.advanced.worldGenerator.worldGenLightingEngine.@tooltip":
@@ -573,6 +573,8 @@
"Show World Gen Queue",
"distanthorizons.config.client.advanced.debugging.debugWireframe.showRenderSectionStatus":
"Show Render Section Status",
"distanthorizons.config.client.advanced.debugging.debugWireframe.showQuadTreeRenderStatus":
"Show Quad Tree Render Status",
"distanthorizons.config.client.advanced.debugging.debugWireframe.showFullDataUpdateStatus":
"Show Full Data Update Status",
@@ -776,13 +778,11 @@
"Full",
"distanthorizons.config.enum.EDhApiDataCompressionMode.UNCOMPRESSED":
"1. Uncompressed",
"Uncompressed",
"distanthorizons.config.enum.EDhApiDataCompressionMode.LZ4":
"2. LZ4",
"distanthorizons.config.enum.EDhApiDataCompressionMode.Z_STD":
"3. Zstd",
"Fast/Big - LZ4",
"distanthorizons.config.enum.EDhApiDataCompressionMode.LZMA2":
"4. LZMA2",
"Slow/Small - LZMA2",
"distanthorizons.config.enum.EDhApiWorldCompressionMode.MERGE_SAME_BLOCKS":
"1. Merge Same Blocks",
+9 -10
View File
@@ -21,15 +21,14 @@ package tests;
import com.seibel.distanthorizons.api.enums.config.EDhApiDataCompressionMode;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV2DTO;
import com.seibel.distanthorizons.core.sql.repo.FullDataSourceV2Repo;
import it.unimi.dsi.fastutil.longs.LongArrayList;
import org.junit.Assert;
import java.io.*;
import java.text.CharacterIterator;
import java.text.StringCharacterIterator;
import java.util.ArrayList;
/**
* <strong>Note:</strong>
@@ -213,11 +212,11 @@ public class CompressionTest
}
//@Test
public void Zstd() // middle of the road
{
String compressorName = "Zstd";
this.testCompressor(compressorName, EDhApiDataCompressionMode.Z_STD);
}
//public void Zstd() // middle of the road
//{
// String compressorName = "Zstd";
// this.testCompressor(compressorName, EDhApiDataCompressionMode.Z_STD);
//}
//@Test
public void LZMA2() // very slow, very good compression though
@@ -272,7 +271,7 @@ public class CompressionTest
ArrayList<DhSectionPos> positionList = uncompressedRepo.getAllPositions();
LongArrayList positionList = uncompressedRepo.getAllPositions();
totalUncompressedFileSizeInBytes = uncompressedRepo.getTotalDataSizeInBytes();
System.out.println("Found [" + positionList.size() + "] DTOs.");
@@ -282,7 +281,7 @@ public class CompressionTest
{
try
{
DhSectionPos pos = positionList.get(i);
long pos = positionList.getLong(i);
if (i % 20 == 0)
{
System.out.println(i + "/" + maxTestPosition);
@@ -293,7 +292,7 @@ public class CompressionTest
// uncompressed input //
FullDataSourceV2DTO uncompressedDto = uncompressedRepo.getByKey(pos);
Assert.assertEquals(uncompressedDto.compressionModeEnum, EDhApiDataCompressionMode.UNCOMPRESSED);
Assert.assertEquals(uncompressedDto.compressionModeValue, EDhApiDataCompressionMode.UNCOMPRESSED.value);
FullDataSourceV2 uncompressedDataSource = uncompressedDto.createUnitTestDataSource();
long uncompressedDtoSize = uncompressedRepo.getDataSizeInBytes(pos);
+318 -181
View File
@@ -19,99 +19,143 @@
package tests;
import com.seibel.distanthorizons.core.pos.*;
import com.seibel.distanthorizons.core.enums.EDhDirection;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.pos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.DhBlockPos2D;
import org.junit.Assert;
import org.junit.Test;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicInteger;
public class DhSectionPosTest
{
@Test
public void ContainsPosTest()
public void basicEncodeDecodeTest()
{
DhSectionPos root = new DhSectionPos((byte) 10, 0, 0);
DhSectionPos child = new DhSectionPos((byte) 9, 1, 1);
long pos;
Assert.assertTrue("section pos contains fail", root.contains(child));
Assert.assertFalse("section pos contains fail", child.contains(root));
// zero pos
pos = DhSectionPos.encode((byte) 0, 0, 0);
assertSectionPosEqual(0, DhSectionPos.getDetailLevel(pos));
assertSectionPosEqual(0, DhSectionPos.getX(pos));
assertSectionPosEqual(0, DhSectionPos.getZ(pos));
// positive values
pos = DhSectionPos.encode((byte) 10, 4, 1);
assertSectionPosEqual(10, DhSectionPos.getDetailLevel(pos));
assertSectionPosEqual(4, DhSectionPos.getX(pos));
assertSectionPosEqual(1, DhSectionPos.getZ(pos));
// negative position, positive detail level
pos = DhSectionPos.encode((byte) 2, -1, -4);
assertSectionPosEqual(2, DhSectionPos.getDetailLevel(pos));
assertSectionPosEqual(-1, DhSectionPos.getX(pos));
assertSectionPosEqual(-4, DhSectionPos.getZ(pos));
}
@Test
public void containsPosTest()
{
long root = DhSectionPos.encode((byte) 10, 0, 0);
long child = DhSectionPos.encode((byte) 9, 1, 1);
Assert.assertTrue("section pos contains fail", DhSectionPos.contains(root, child));
Assert.assertFalse("section pos contains fail", DhSectionPos.contains(child, root));
root = new DhSectionPos((byte) 10, 1, 0);
root = DhSectionPos.encode((byte) 10, 1, 0);
// out of bounds
child = new DhSectionPos((byte) 9, 0, 0);
Assert.assertFalse("position should be out of bounds", root.contains(child));
child = new DhSectionPos((byte) 9, 1, 1);
Assert.assertFalse("position should be out of bounds", root.contains(child));
child = DhSectionPos.encode((byte) 9, 0, 0);
Assert.assertFalse("position should be out of bounds", DhSectionPos.contains(root, child));
child = DhSectionPos.encode((byte) 9, 1, 1);
Assert.assertFalse("position should be out of bounds", DhSectionPos.contains(root, child));
// in bounds
child = new DhSectionPos((byte) 9, 2, 0);
Assert.assertTrue("position should be in bounds", root.contains(child));
child = new DhSectionPos((byte) 9, 3, 1);
Assert.assertTrue("position should be in bounds", root.contains(child));
child = DhSectionPos.encode((byte) 9, 2, 0);
Assert.assertTrue("position should be in bounds", DhSectionPos.contains(root, child));
child = DhSectionPos.encode((byte) 9, 3, 1);
Assert.assertTrue("position should be in bounds", DhSectionPos.contains(root, child));
// out of bounds
child = new DhSectionPos((byte) 9, 2, 2);
Assert.assertFalse("position should be out of bounds", root.contains(child));
child = new DhSectionPos((byte) 9, 3, 3);
Assert.assertFalse("position should be out of bounds", root.contains(child));
child = DhSectionPos.encode((byte) 9, 2, 2);
Assert.assertFalse("position should be out of bounds", DhSectionPos.contains(root, child));
child = DhSectionPos.encode((byte) 9, 3, 3);
Assert.assertFalse("position should be out of bounds", DhSectionPos.contains(root, child));
child = new DhSectionPos((byte) 9, 4, 4);
Assert.assertFalse("position should be out of bounds", root.contains(child));
child = new DhSectionPos((byte) 9, 5, 5);
Assert.assertFalse("position should be out of bounds", root.contains(child));
child = DhSectionPos.encode((byte) 9, 4, 4);
Assert.assertFalse("position should be out of bounds", DhSectionPos.contains(root, child));
child = DhSectionPos.encode((byte) 9, 5, 5);
Assert.assertFalse("position should be out of bounds", DhSectionPos.contains(root, child));
Assert.assertTrue(DhSectionPos.contains(DhSectionPos.encode((byte) 6, 0, 0), DhSectionPos.encode((byte) 0, 0, 0)));
Assert.assertTrue(DhSectionPos.contains(DhSectionPos.encode((byte) 6, 0, 0), DhSectionPos.encode((byte) 1, 0, 0)));
Assert.assertTrue(DhSectionPos.contains(DhSectionPos.encode((byte) 6, 0, 0), DhSectionPos.encode((byte) 2, 0, 0)));
Assert.assertTrue(DhSectionPos.contains(DhSectionPos.encode((byte) 6, 0, 0), DhSectionPos.encode((byte) 3, 0, 0)));
Assert.assertTrue(DhSectionPos.contains(DhSectionPos.encode((byte) 6, 0, 0), DhSectionPos.encode((byte) 4, 0, 0)));
Assert.assertTrue(DhSectionPos.contains(DhSectionPos.encode((byte) 6, 0, 0), DhSectionPos.encode((byte) 5, 0, 0)));
Assert.assertTrue(DhSectionPos.contains(DhSectionPos.encode((byte) 6, 0, 0), DhSectionPos.encode((byte) 6, 0, 0)));
}
@Test
public void ContainsAdjacentPosTest()
public void containsAdjacentPosTest()
{
// neither should contain the other, they are single blocks that are next to each other
DhSectionPos left = new DhSectionPos((byte) 0, 4606, 0);
DhSectionPos right = new DhSectionPos((byte) 0, 4607, 0);
Assert.assertFalse(left.contains(right));
Assert.assertFalse(right.contains(left));
long left = DhSectionPos.encode((byte) 0, 4606, 0);
long right = DhSectionPos.encode((byte) 0, 4607, 0);
Assert.assertFalse(DhSectionPos.contains(left, right));
Assert.assertFalse(DhSectionPos.contains(right, left));
// 512 block wide sections that are adjacent, but not overlapping
left = new DhSectionPos((byte) 9, 0, 0);
right = new DhSectionPos((byte) 9, 1, 0);
Assert.assertFalse(left.contains(right));
Assert.assertFalse(right.contains(left));
left = DhSectionPos.encode((byte) 9, 0, 0);
right = DhSectionPos.encode((byte) 9, 1, 0);
Assert.assertFalse(DhSectionPos.contains(left, right));
Assert.assertFalse(DhSectionPos.contains(right, left));
}
@Test
public void ParentPosTest()
public void parentPosTest()
{
DhSectionPos leaf = new DhSectionPos((byte) 0, 0, 0);
DhSectionPos convert = leaf.convertNewToDetailLevel((byte) 1);
DhSectionPos parent = leaf.getParentPos();
Assert.assertEquals("get parent at 0,0 fail", convert, parent);
leaf = new DhSectionPos((byte) 0, 1, 1);
convert = leaf.convertNewToDetailLevel((byte) 1);
parent = leaf.getParentPos();
Assert.assertEquals("get parent at 1,1 fail", convert, parent);
leaf = new DhSectionPos((byte) 1, 2, 2);
convert = leaf.convertNewToDetailLevel((byte) 2);
parent = leaf.getParentPos();
Assert.assertEquals("parent upscale fail", convert, parent);
convert = leaf.convertNewToDetailLevel((byte) 0);
DhSectionPos childIndex = leaf.getChildByIndex(0);
Assert.assertEquals("child detail fail", convert, childIndex);
long leaf = DhSectionPos.encode((byte) 0, 0, 0);
long convert = DhSectionPos.convertToDetailLevel(leaf, (byte) 1);
long parent = DhSectionPos.getParentPos(leaf);
assertSectionPosEqual("get parent at 0,0 fail", convert, parent);
leaf = DhSectionPos.encode((byte) 0, 1, 1);
convert = DhSectionPos.convertToDetailLevel(leaf, (byte) 1);
parent = DhSectionPos.getParentPos(leaf);
assertSectionPosEqual("get parent at 1,1 fail", convert, parent);
leaf = DhSectionPos.encode((byte) 1, 2, 2);
convert = DhSectionPos.convertToDetailLevel(leaf, (byte) 2);
parent = DhSectionPos.getParentPos(leaf);
assertSectionPosEqual("parent upscale fail", convert, parent);
convert = DhSectionPos.convertToDetailLevel(leaf, (byte) 0);
long childIndex = DhSectionPos.getChildByIndex(leaf, 0);
assertSectionPosEqual("child detail fail", convert, childIndex);
}
@Test
public void ChildPosTest()
public void childPosTest()
{
DhSectionPos node = new DhSectionPos((byte) 1, 2302, 0);
DhSectionPos nw = node.getChildByIndex(0);
DhSectionPos sw = node.getChildByIndex(1);
DhSectionPos ne = node.getChildByIndex(2);
DhSectionPos se = node.getChildByIndex(3);
long node = DhSectionPos.encode((byte) 1, 2302, 0);
long nw = DhSectionPos.getChildByIndex(node, 0);
long sw = DhSectionPos.getChildByIndex(node, 1);
long ne = DhSectionPos.getChildByIndex(node, 2);
long se = DhSectionPos.getChildByIndex(node, 3);
// confirm no children have the same values
Assert.assertNotEquals(nw, sw);
@@ -119,244 +163,337 @@ public class DhSectionPosTest
Assert.assertNotEquals(ne, se);
// confirm each child has the correct value
Assert.assertEquals(nw, new DhSectionPos((byte) 0, 4604, 0));
Assert.assertEquals(sw, new DhSectionPos((byte) 0, 4605, 0));
Assert.assertEquals(ne, new DhSectionPos((byte) 0, 4604, 1));
Assert.assertEquals(se, new DhSectionPos((byte) 0, 4605, 1));
assertSectionPosEqual(nw, DhSectionPos.encode((byte) 0, 4604, 0));
assertSectionPosEqual(sw, DhSectionPos.encode((byte) 0, 4605, 0));
assertSectionPosEqual(ne, DhSectionPos.encode((byte) 0, 4604, 1));
assertSectionPosEqual(se, DhSectionPos.encode((byte) 0, 4605, 1));
}
@Test
public void GetCenterTest()
public void getCenterBlock2DTest()
{
DhSectionPos node = new DhSectionPos((byte) 1, 2303, 0);
DhBlockPos2D centerBlockPos = node.getCenterBlockPos();
DhBlockPos2D expectedCenterNode = new DhBlockPos2D(4606, 0);
Assert.assertEquals("", expectedCenterNode, centerBlockPos);
long parentNode = DhSectionPos.encode((byte) 2, 1151, 0); // width 4 blocks
long inputPos = DhSectionPos.encode((byte) 0, 4606, 0); // width 1 block
Assert.assertTrue(DhSectionPos.contains(parentNode, inputPos));
DhBlockPos2D parentCenter = DhSectionPos.getCenterBlockPos(parentNode);
DhBlockPos2D inputCenter = DhSectionPos.getCenterBlockPos(inputPos);
node = new DhSectionPos((byte) 10, 0, 0); // 1024 blocks wide
centerBlockPos = node.getCenterBlockPos();
expectedCenterNode = new DhBlockPos2D(1024 / 2, 1024 / 2);
Assert.assertEquals("", expectedCenterNode, centerBlockPos);
Assert.assertEquals(new DhBlockPos2D(4606, 2), parentCenter);
Assert.assertEquals(new DhBlockPos2D(4606, 0), inputCenter);
}
@Test
public void GetCenter2Test()
{
DhSectionPos parentNode = new DhSectionPos((byte) 2, 1151, 0); // width 4 blocks
DhSectionPos inputPos = new DhSectionPos((byte) 0, 4606, 0); // width 1 block
Assert.assertTrue(parentNode.contains(inputPos));
DhBlockPos2D parentCenter = parentNode.getCenterBlockPos();
DhBlockPos2D inputCenter = inputPos.getCenterBlockPos();
Assert.assertEquals(new DhBlockPos2D(4606, 2), parentCenter);
Assert.assertEquals(new DhBlockPos2D(4606, 0), inputCenter);
}
@Test
public void CreateFromBlockPos()
public void createFromBlockPos()
{
// origin pos //
DhBlockPos originBlockPos = new DhBlockPos(0, 0, 0);
DhSectionPos originSectionPos = new DhSectionPos(originBlockPos);
Assert.assertEquals(new DhSectionPos(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL, 0, 0), originSectionPos);
long originsectionPos = DhSectionPos.encode(originBlockPos);
assertSectionPosEqual(DhSectionPos.encode(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL, 0, 0), originsectionPos);
// offset pos //
long offsetSectionPos;
DhBlockPos offsetBlockPos = new DhBlockPos(1000, 0, 42000);
DhSectionPos offsetSectionPos = new DhSectionPos(offsetBlockPos);
Assert.assertEquals(new DhSectionPos(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL, 15, 656), offsetSectionPos);
offsetSectionPos = DhSectionPos.encode(offsetBlockPos);
assertSectionPosEqual(DhSectionPos.encode(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL, 15, 656), offsetSectionPos);
offsetBlockPos = new DhBlockPos(-987654, 0, 46);
offsetSectionPos = new DhSectionPos(offsetBlockPos);
Assert.assertEquals(new DhSectionPos(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL, -15433, 0), offsetSectionPos);
offsetSectionPos = DhSectionPos.encode(offsetBlockPos);
assertSectionPosEqual(DhSectionPos.encode(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL, -15433, 0), offsetSectionPos);
}
@Test
public void CreateFromBlockPos2D()
public void createFromBlockPos2D()
{
// origin pos //
DhBlockPos2D originBlockPos = new DhBlockPos2D(0, 0);
DhSectionPos originSectionPos = new DhSectionPos(originBlockPos);
Assert.assertEquals(new DhSectionPos(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL, 0, 0), originSectionPos);
long originSectionPos = DhSectionPos.encode(originBlockPos);
assertSectionPosEqual(DhSectionPos.encode(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL, 0, 0), originSectionPos);
// offset pos //
DhBlockPos2D offsetBlockPos = new DhBlockPos2D(1000, 42000);
DhSectionPos offsetSectionPos = new DhSectionPos(offsetBlockPos);
Assert.assertEquals(new DhSectionPos(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL, 15, 656), offsetSectionPos);
long offsetSectionPos = DhSectionPos.encode(offsetBlockPos);
assertSectionPosEqual(DhSectionPos.encode(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL, 15, 656), offsetSectionPos);
offsetBlockPos = new DhBlockPos2D(-987654, 46);
offsetSectionPos = new DhSectionPos(offsetBlockPos);
Assert.assertEquals(new DhSectionPos(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL, -15433, 0), offsetSectionPos);
offsetSectionPos = DhSectionPos.encode(offsetBlockPos);
assertSectionPosEqual(DhSectionPos.encode(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL, -15433, 0), offsetSectionPos);
}
@Test
public void CreateFromChunkPos()
public void createFromChunkPos()
{
// origin pos //
DhChunkPos originChunkPos = new DhChunkPos(0,0);
DhSectionPos originSectionPos = new DhSectionPos(originChunkPos);
Assert.assertEquals(new DhSectionPos(DhSectionPos.SECTION_CHUNK_DETAIL_LEVEL, 0, 0), originSectionPos);
long originSectionPos = DhSectionPos.encode(originChunkPos);
assertSectionPosEqual(DhSectionPos.encode(DhSectionPos.SECTION_CHUNK_DETAIL_LEVEL, 0, 0), originSectionPos);
// offset pos //
DhChunkPos offsetChunkPos = new DhChunkPos(1000, 42000);
DhSectionPos offsetSectionPos = new DhSectionPos(offsetChunkPos);
Assert.assertEquals(new DhSectionPos(DhSectionPos.SECTION_CHUNK_DETAIL_LEVEL, 15, 656), offsetSectionPos);
long offsetSectionPos = DhSectionPos.encode(offsetChunkPos);
assertSectionPosEqual(DhSectionPos.encode(DhSectionPos.SECTION_CHUNK_DETAIL_LEVEL, 15, 656), offsetSectionPos);
offsetChunkPos = new DhChunkPos(-987654, 46);
offsetSectionPos = new DhSectionPos(offsetChunkPos);
Assert.assertEquals(new DhSectionPos(DhSectionPos.SECTION_CHUNK_DETAIL_LEVEL, -15433, 0), offsetSectionPos);
offsetSectionPos = DhSectionPos.encode(offsetChunkPos);
assertSectionPosEqual(DhSectionPos.encode(DhSectionPos.SECTION_CHUNK_DETAIL_LEVEL, -15433, 0), offsetSectionPos);
}
@Test
public void ConvertToDetailLevel()
public void convertToDetailLevel()
{
// origin pos //
DhSectionPos originSectionPos = new DhSectionPos((byte) 0,0,0);
long originSectionPos = DhSectionPos.encode((byte) 0,0,0);
originSectionPos = originSectionPos.convertNewToDetailLevel((byte) 1);
Assert.assertEquals(new DhSectionPos((byte) 1, 0, 0), originSectionPos);
originSectionPos = DhSectionPos.convertToDetailLevel(originSectionPos, (byte) 1);
assertSectionPosEqual(DhSectionPos.encode((byte) 1, 0, 0), originSectionPos);
originSectionPos = originSectionPos.convertNewToDetailLevel(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL);
Assert.assertEquals(new DhSectionPos(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL, 0, 0), originSectionPos);
originSectionPos = DhSectionPos.convertToDetailLevel(originSectionPos, DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL);
assertSectionPosEqual(DhSectionPos.encode(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL, 0, 0), originSectionPos);
originSectionPos = originSectionPos.convertNewToDetailLevel(DhSectionPos.SECTION_REGION_DETAIL_LEVEL);
Assert.assertEquals(new DhSectionPos(DhSectionPos.SECTION_REGION_DETAIL_LEVEL, 0, 0), originSectionPos);
originSectionPos = DhSectionPos.convertToDetailLevel(originSectionPos, DhSectionPos.SECTION_REGION_DETAIL_LEVEL);
assertSectionPosEqual(DhSectionPos.encode(DhSectionPos.SECTION_REGION_DETAIL_LEVEL, 0, 0), originSectionPos);
// offset pos //
DhSectionPos sectionPos = new DhSectionPos((byte) 0,-10000,5000);
long offsetSectionPos = DhSectionPos.encode((byte) 0,-10000,5000);
sectionPos = sectionPos.convertNewToDetailLevel((byte) 1);
Assert.assertEquals(new DhSectionPos((byte) 1, -5000, 2500), sectionPos);
offsetSectionPos = DhSectionPos.convertToDetailLevel(offsetSectionPos, (byte) 1);
assertSectionPosEqual(DhSectionPos.encode((byte) 1, -5000, 2500), offsetSectionPos);
sectionPos = sectionPos.convertNewToDetailLevel(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL);
Assert.assertEquals(new DhSectionPos(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL, -157, 78), sectionPos);
offsetSectionPos = DhSectionPos.convertToDetailLevel(offsetSectionPos, DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL);
assertSectionPosEqual(DhSectionPos.encode(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL, -157, 78), offsetSectionPos);
sectionPos = sectionPos.convertNewToDetailLevel(DhSectionPos.SECTION_REGION_DETAIL_LEVEL);
Assert.assertEquals(new DhSectionPos(DhSectionPos.SECTION_REGION_DETAIL_LEVEL, -1, 0), sectionPos);
offsetSectionPos = DhSectionPos.convertToDetailLevel(offsetSectionPos, DhSectionPos.SECTION_REGION_DETAIL_LEVEL);
assertSectionPosEqual(DhSectionPos.encode(DhSectionPos.SECTION_REGION_DETAIL_LEVEL, -1, 0), offsetSectionPos);
}
@Test
public void GetOffsetWidth()
public void getOffsetWidth()
{
DhSectionPos originSectionPos = new DhSectionPos((byte) 0,0,0);
DhSectionPos sectionPos = new DhSectionPos((byte) 0,-10000,5000);
long originSectionPos = DhSectionPos.encode((byte) 0,0,0);
long sectionPos = DhSectionPos.encode((byte) 0,-10000,5000);
// 1 -> 0
byte returnDetailLevel = 0;
originSectionPos = originSectionPos.convertNewToDetailLevel((byte) 1);
Assert.assertEquals(2, originSectionPos.getWidthCountForLowerDetailedSection(returnDetailLevel));
originSectionPos = DhSectionPos.convertToDetailLevel(originSectionPos, (byte) 1);
assertSectionPosEqual(2, DhSectionPos.getWidthCountForLowerDetailedSection(originSectionPos, returnDetailLevel));
sectionPos = sectionPos.convertNewToDetailLevel((byte) 1);
Assert.assertEquals(2, sectionPos.getWidthCountForLowerDetailedSection(returnDetailLevel));
sectionPos = DhSectionPos.convertToDetailLevel(sectionPos, (byte) 1);
assertSectionPosEqual(2, DhSectionPos.getWidthCountForLowerDetailedSection(sectionPos, returnDetailLevel));
// 2 -> 1
returnDetailLevel = 1;
originSectionPos = originSectionPos.convertNewToDetailLevel((byte) 2);
Assert.assertEquals(2, originSectionPos.getWidthCountForLowerDetailedSection(returnDetailLevel));
originSectionPos = DhSectionPos.convertToDetailLevel(originSectionPos, (byte) 2);
assertSectionPosEqual(2, DhSectionPos.getWidthCountForLowerDetailedSection(originSectionPos, returnDetailLevel));
sectionPos = sectionPos.convertNewToDetailLevel((byte) 2);
Assert.assertEquals(2, sectionPos.getWidthCountForLowerDetailedSection(returnDetailLevel));
sectionPos = DhSectionPos.convertToDetailLevel(sectionPos, (byte) 2);
assertSectionPosEqual(2, DhSectionPos.getWidthCountForLowerDetailedSection(sectionPos, returnDetailLevel));
// Block -> 0
returnDetailLevel = 0;
originSectionPos = originSectionPos.convertNewToDetailLevel(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL);
Assert.assertEquals(64, originSectionPos.getWidthCountForLowerDetailedSection(returnDetailLevel));
originSectionPos = DhSectionPos.convertToDetailLevel(originSectionPos, DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL);
assertSectionPosEqual(64, DhSectionPos.getWidthCountForLowerDetailedSection(originSectionPos, returnDetailLevel));
sectionPos = sectionPos.convertNewToDetailLevel(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL);
Assert.assertEquals(64, sectionPos.getWidthCountForLowerDetailedSection(returnDetailLevel));
sectionPos = DhSectionPos.convertToDetailLevel(sectionPos, DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL);
assertSectionPosEqual(64, DhSectionPos.getWidthCountForLowerDetailedSection(sectionPos, returnDetailLevel));
// Region -> 3
returnDetailLevel = 3;
originSectionPos = originSectionPos.convertNewToDetailLevel(DhSectionPos.SECTION_REGION_DETAIL_LEVEL);
Assert.assertEquals(4096, originSectionPos.getWidthCountForLowerDetailedSection(returnDetailLevel));
originSectionPos = DhSectionPos.convertToDetailLevel(originSectionPos, DhSectionPos.SECTION_REGION_DETAIL_LEVEL);
assertSectionPosEqual(4096, DhSectionPos.getWidthCountForLowerDetailedSection(originSectionPos, returnDetailLevel));
sectionPos = sectionPos.convertNewToDetailLevel(DhSectionPos.SECTION_REGION_DETAIL_LEVEL);
Assert.assertEquals(4096, sectionPos.getWidthCountForLowerDetailedSection(returnDetailLevel));
sectionPos = DhSectionPos.convertToDetailLevel(sectionPos, DhSectionPos.SECTION_REGION_DETAIL_LEVEL);
assertSectionPosEqual(4096, DhSectionPos.getWidthCountForLowerDetailedSection(sectionPos, returnDetailLevel));
}
@Test
public void GetBlockWidth()
public void getBlockWidth()
{
DhSectionPos originSectionPos = new DhSectionPos((byte) 0,0,0);
DhSectionPos sectionPos = new DhSectionPos((byte) 0,-10000,5000);
long originSectionPos = DhSectionPos.encode((byte) 0,0,0);
long sectionPos = DhSectionPos.encode((byte) 0,-10000,5000);
Assert.assertEquals(1, originSectionPos.getBlockWidth());
Assert.assertEquals(1, sectionPos.getBlockWidth());
assertSectionPosEqual(1, DhSectionPos.getBlockWidth(originSectionPos));
assertSectionPosEqual(1, DhSectionPos.getBlockWidth(sectionPos));
originSectionPos = originSectionPos.convertNewToDetailLevel((byte) 1);
Assert.assertEquals(2, originSectionPos.getBlockWidth());
sectionPos = sectionPos.convertNewToDetailLevel((byte) 1);
Assert.assertEquals(2, sectionPos.getBlockWidth());
originSectionPos = DhSectionPos.convertToDetailLevel(originSectionPos, (byte) 1);
assertSectionPosEqual(2, DhSectionPos.getBlockWidth(originSectionPos));
sectionPos = DhSectionPos.convertToDetailLevel(sectionPos, (byte) 1);
assertSectionPosEqual(2, DhSectionPos.getBlockWidth(sectionPos));
originSectionPos = originSectionPos.convertNewToDetailLevel(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL);
Assert.assertEquals(64, originSectionPos.getBlockWidth());
sectionPos = sectionPos.convertNewToDetailLevel(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL);
Assert.assertEquals(64, sectionPos.getBlockWidth());
originSectionPos = DhSectionPos.convertToDetailLevel(originSectionPos, DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL);
assertSectionPosEqual(64, DhSectionPos.getBlockWidth(originSectionPos));
sectionPos = DhSectionPos.convertToDetailLevel(sectionPos, DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL);
assertSectionPosEqual(64, DhSectionPos.getBlockWidth(sectionPos));
originSectionPos = originSectionPos.convertNewToDetailLevel(DhSectionPos.SECTION_REGION_DETAIL_LEVEL);
Assert.assertEquals(32768, originSectionPos.getBlockWidth());
sectionPos = sectionPos.convertNewToDetailLevel(DhSectionPos.SECTION_REGION_DETAIL_LEVEL);
Assert.assertEquals(32768, sectionPos.getBlockWidth());
originSectionPos = DhSectionPos.convertToDetailLevel(originSectionPos, DhSectionPos.SECTION_REGION_DETAIL_LEVEL);
assertSectionPosEqual(32768, DhSectionPos.getBlockWidth(originSectionPos));
sectionPos = DhSectionPos.convertToDetailLevel(sectionPos, DhSectionPos.SECTION_REGION_DETAIL_LEVEL);
assertSectionPosEqual(32768, DhSectionPos.getBlockWidth(sectionPos));
}
@Test
public void GetCenterBlockPos()
public void getCenterBlockPosOrigin()
{
DhSectionPos originSectionPos = new DhSectionPos((byte) 0,0,0);
DhSectionPos sectionPos = new DhSectionPos((byte) 0,-10000,5000);
long originSectionPos = DhSectionPos.encode((byte) 0,0,0);
long sectionPos = DhSectionPos.encode((byte) 0,-10000,5000);
Assert.assertEquals(new DhBlockPos2D(0, 0), originSectionPos.getCenterBlockPos());
Assert.assertEquals(new DhBlockPos2D(-10000, 5000), sectionPos.getCenterBlockPos());
// 1x1 blocks
Assert.assertEquals(new DhBlockPos2D(0, 0), DhSectionPos.getCenterBlockPos(originSectionPos));
Assert.assertEquals(new DhBlockPos2D(-10000, 5000), DhSectionPos.getCenterBlockPos(sectionPos));
originSectionPos = originSectionPos.convertNewToDetailLevel((byte) 1);
Assert.assertEquals(new DhBlockPos2D(0, 0), originSectionPos.getCenterBlockPos());
sectionPos = sectionPos.convertNewToDetailLevel((byte) 1);
Assert.assertEquals(new DhBlockPos2D(-10000, 5000), sectionPos.getCenterBlockPos());
// 2x2 blocks
originSectionPos = DhSectionPos.convertToDetailLevel(originSectionPos, (byte) 1);
Assert.assertEquals(new DhBlockPos2D(0, 0), DhSectionPos.getCenterBlockPos(originSectionPos));
sectionPos = DhSectionPos.convertToDetailLevel(sectionPos, (byte) 1);
Assert.assertEquals(new DhBlockPos2D(-10000, 5000), DhSectionPos.getCenterBlockPos(sectionPos));
//sectionPos = DhSectionPos.encode((byte) 1, 2303, 0);
//Assert.assertEquals(new DhBlockPos2D(4606, 0), DhSectionPos.getCenterBlockPos(sectionPos));
originSectionPos = originSectionPos.convertNewToDetailLevel(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL);
Assert.assertEquals(new DhBlockPos2D(32, 32), originSectionPos.getCenterBlockPos());
sectionPos = sectionPos.convertNewToDetailLevel(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL);
Assert.assertEquals(new DhBlockPos2D(-10016, 5024), sectionPos.getCenterBlockPos());
// 4x4 blocks
originSectionPos = DhSectionPos.convertToDetailLevel(originSectionPos, (byte) 2);
Assert.assertEquals(new DhBlockPos2D(2, 2), DhSectionPos.getCenterBlockPos(originSectionPos));
sectionPos = DhSectionPos.convertToDetailLevel(sectionPos, (byte) 2);
Assert.assertEquals(new DhBlockPos2D(-9998, 5002), DhSectionPos.getCenterBlockPos(sectionPos));
originSectionPos = originSectionPos.convertNewToDetailLevel(DhSectionPos.SECTION_REGION_DETAIL_LEVEL);
Assert.assertEquals(new DhBlockPos2D(16384, 16384), originSectionPos.getCenterBlockPos());
sectionPos = sectionPos.convertNewToDetailLevel(DhSectionPos.SECTION_REGION_DETAIL_LEVEL);
Assert.assertEquals(new DhBlockPos2D(-16384, 16384), sectionPos.getCenterBlockPos());
// 64x64 blocks
originSectionPos = DhSectionPos.convertToDetailLevel(originSectionPos, DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL);
Assert.assertEquals(new DhBlockPos2D(32, 32), DhSectionPos.getCenterBlockPos(originSectionPos));
sectionPos = DhSectionPos.convertToDetailLevel(sectionPos, DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL);
Assert.assertEquals(new DhBlockPos2D(-10016, 5024), DhSectionPos.getCenterBlockPos(sectionPos));
// 32,768 x 32,768 blocks
originSectionPos = DhSectionPos.convertToDetailLevel(originSectionPos, DhSectionPos.SECTION_REGION_DETAIL_LEVEL);
Assert.assertEquals(new DhBlockPos2D(16384, 16384), DhSectionPos.getCenterBlockPos(originSectionPos));
sectionPos = DhSectionPos.convertToDetailLevel(sectionPos, DhSectionPos.SECTION_REGION_DETAIL_LEVEL);
Assert.assertEquals(new DhBlockPos2D(-16384, 16384), DhSectionPos.getCenterBlockPos(sectionPos));
}
@Test
public void getMinCornerBlockPos()
{
long pos;
// origin block detail
pos = DhSectionPos.encode((byte) 0,0,0);
Assert.assertEquals(0, DhSectionPos.getMinCornerBlockX(pos));
Assert.assertEquals(0, DhSectionPos.getMinCornerBlockZ(pos));
// offset block detail
pos = DhSectionPos.encode((byte) 0,2,3);
Assert.assertEquals(2 * DhSectionPos.getBlockWidth(pos), DhSectionPos.getMinCornerBlockX(pos));
Assert.assertEquals(3 * DhSectionPos.getBlockWidth(pos), DhSectionPos.getMinCornerBlockZ(pos));
// negative offset block detail
pos = DhSectionPos.encode((byte) 0,-1,-2);
Assert.assertEquals(-1 * DhSectionPos.getBlockWidth(pos), DhSectionPos.getMinCornerBlockX(pos));
Assert.assertEquals(-2 * DhSectionPos.getBlockWidth(pos), DhSectionPos.getMinCornerBlockZ(pos));
// origin chunk detail
pos = DhSectionPos.encode(DhSectionPos.SECTION_CHUNK_DETAIL_LEVEL,0,0);
Assert.assertEquals(0, DhSectionPos.getMinCornerBlockX(pos));
Assert.assertEquals(0, DhSectionPos.getMinCornerBlockZ(pos));
// offset chunk detail
pos = DhSectionPos.encode(DhSectionPos.SECTION_CHUNK_DETAIL_LEVEL,2,3);
Assert.assertEquals(2 * DhSectionPos.getBlockWidth(pos), DhSectionPos.getMinCornerBlockX(pos));
Assert.assertEquals(3 * DhSectionPos.getBlockWidth(pos), DhSectionPos.getMinCornerBlockZ(pos));
}
@Test
public void getAdjacentPos()
{
long pos = DhSectionPos.encode((byte) 0, 0, 0);
assertSectionPosEqual(DhSectionPos.encode((byte) 0, 0, -1), DhSectionPos.getAdjacentPos(pos, EDhDirection.NORTH));
assertSectionPosEqual(DhSectionPos.encode((byte) 0, 0, 1), DhSectionPos.getAdjacentPos(pos, EDhDirection.SOUTH));
assertSectionPosEqual(DhSectionPos.encode((byte) 0, 1, 0), DhSectionPos.getAdjacentPos(pos, EDhDirection.EAST));
assertSectionPosEqual(DhSectionPos.encode((byte) 0, -1, 0), DhSectionPos.getAdjacentPos(pos, EDhDirection.WEST));
// getting the adjacent position in the up and down position don't make sense
Assert.assertThrows(IllegalArgumentException.class, () -> { DhSectionPos.getAdjacentPos(pos, EDhDirection.UP); });
Assert.assertThrows(IllegalArgumentException.class, () -> { DhSectionPos.getAdjacentPos(pos, EDhDirection.DOWN); });
}
@Test
public void forEachChildIterator()
{
long pos = DhSectionPos.encode((byte) 1, 0, 0);
ArrayList<Long> childPosList = new ArrayList<>();
AtomicInteger childCount = new AtomicInteger(0);
DhSectionPos.forEachChild(pos, (childPos) ->
{
childCount.incrementAndGet();
childPosList.add(childPos);
});
Assert.assertTrue(childPosList.contains(DhSectionPos.encode((byte) 0, 0, 0)));
Assert.assertTrue(childPosList.contains(DhSectionPos.encode((byte) 0, 1, 0)));
Assert.assertTrue(childPosList.contains(DhSectionPos.encode((byte) 0, 0, 1)));
Assert.assertTrue(childPosList.contains(DhSectionPos.encode((byte) 0, 1, 1)));
}
//================//
// helper methods //
//================//
public static void assertSectionPosEqual(long expected, long actual) { assertSectionPosEqual("", expected, actual); }
public static void assertSectionPosEqual(String messagePrefix, long expected, long actual)
{
if (!messagePrefix.endsWith(" "))
{
messagePrefix += " ";
}
String expectedString = DhSectionPos.toString(expected);
String actualString = DhSectionPos.toString(actual);
String mismatchSuffix = "expected: ["+expectedString+"] actual: ["+actualString+"].";
Assert.assertEquals(messagePrefix+"Detail level mismatch, "+mismatchSuffix, DhSectionPos.getDetailLevel(expected), DhSectionPos.getDetailLevel(actual));
Assert.assertEquals(messagePrefix+"X Pos mismatch, "+mismatchSuffix, DhSectionPos.getX(expected), DhSectionPos.getX(actual));
Assert.assertEquals(messagePrefix+"Z Pos mismatch, "+mismatchSuffix, DhSectionPos.getZ(expected), DhSectionPos.getZ(actual));
}
}
+136 -136
View File
@@ -27,6 +27,7 @@ import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.coreapi.util.MathUtil;
import com.seibel.distanthorizons.core.util.objects.quadTree.QuadNode;
import com.seibel.distanthorizons.core.util.objects.quadTree.QuadTree;
import it.unimi.dsi.fastutil.longs.LongIterator;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.config.Configurator;
@@ -59,30 +60,30 @@ public class QuadTreeTest
// (pseudo) root node //
testSet(tree, new DhSectionPos((byte) 10, 0, 0), 0);
testSet(tree, DhSectionPos.encode((byte) 10, 0, 0), 0);
// first child (0,0) //
testSet(tree, new DhSectionPos((byte) 9, 0, 0), 1);
testSet(tree, new DhSectionPos((byte) 9, 1, 0), 2);
testSet(tree, new DhSectionPos((byte) 9, 0, 1), 3);
testSet(tree, new DhSectionPos((byte) 9, 1, 1), 4);
testSet(tree, DhSectionPos.encode((byte) 9, 0, 0), 1);
testSet(tree, DhSectionPos.encode((byte) 9, 1, 0), 2);
testSet(tree, DhSectionPos.encode((byte) 9, 0, 1), 3);
testSet(tree, DhSectionPos.encode((byte) 9, 1, 1), 4);
// second child (0,0) (0,0) //
testSet(tree, new DhSectionPos((byte) 8, 0, 0), 5);
testSet(tree, new DhSectionPos((byte) 8, 1, 0), 6);
testSet(tree, new DhSectionPos((byte) 8, 0, 1), 7);
testSet(tree, new DhSectionPos((byte) 8, 1, 1), 8);
testSet(tree, DhSectionPos.encode((byte) 8, 0, 0), 5);
testSet(tree, DhSectionPos.encode((byte) 8, 1, 0), 6);
testSet(tree, DhSectionPos.encode((byte) 8, 0, 1), 7);
testSet(tree, DhSectionPos.encode((byte) 8, 1, 1), 8);
// second child (0,0) (1,1) //
testSet(tree, new DhSectionPos((byte) 8, 2, 2), 9);
testSet(tree, new DhSectionPos((byte) 8, 3, 2), 10);
testSet(tree, new DhSectionPos((byte) 8, 2, 3), 11);
testSet(tree, new DhSectionPos((byte) 8, 3, 3), 12);
testSet(tree, DhSectionPos.encode((byte) 8, 2, 2), 9);
testSet(tree, DhSectionPos.encode((byte) 8, 3, 2), 10);
testSet(tree, DhSectionPos.encode((byte) 8, 2, 3), 11);
testSet(tree, DhSectionPos.encode((byte) 8, 3, 3), 12);
// third child (0,0) (1,0) (0,0) //
testSet(tree, new DhSectionPos((byte) 7, 5, 0), 9);
testSet(tree, new DhSectionPos((byte) 7, 6, 0), 10);
testSet(tree, new DhSectionPos((byte) 7, 5, 1), 11);
testSet(tree, new DhSectionPos((byte) 7, 6, 1), 12);
testSet(tree, DhSectionPos.encode((byte) 7, 5, 0), 9);
testSet(tree, DhSectionPos.encode((byte) 7, 6, 0), 10);
testSet(tree, DhSectionPos.encode((byte) 7, 5, 1), 11);
testSet(tree, DhSectionPos.encode((byte) 7, 6, 1), 12);
}
@@ -94,13 +95,13 @@ public class QuadTreeTest
// root node //
testSet(tree, new DhSectionPos((byte) 10, -1, -1), 0);
testSet(tree, DhSectionPos.encode((byte) 10, -1, -1), 0);
// first child (-1,-1) //
testSet(tree, new DhSectionPos((byte) 9, -2, -1), 1);
testSet(tree, new DhSectionPos((byte) 9, -1, -1), 2);
testSet(tree, new DhSectionPos((byte) 9, -2, -2), 3);
testSet(tree, new DhSectionPos((byte) 9, -1, -2), 4);
testSet(tree, DhSectionPos.encode((byte) 9, -2, -1), 1);
testSet(tree, DhSectionPos.encode((byte) 9, -1, -1), 2);
testSet(tree, DhSectionPos.encode((byte) 9, -2, -2), 3);
testSet(tree, DhSectionPos.encode((byte) 9, -1, -2), 4);
// TODO
// // second child (-1,-1) (0,0) //
@@ -131,36 +132,36 @@ public class QuadTreeTest
// wrong detail level on purpose, if the detail level was 0 (block) this should work
DhSectionPos outOfBoundsPos = new DhSectionPos(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL, (treeParams.getWidthInBlocks() / 2), 0);
long outOfBoundsPos = DhSectionPos.encode(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL, (treeParams.getWidthInBlocks() / 2), 0);
testSet(tree, outOfBoundsPos, -1, IndexOutOfBoundsException.class);
Assert.assertEquals("incorrect leaf node count", 0, tree.leafNodeCount());
// out of bounds //
outOfBoundsPos = new DhSectionPos(LodUtil.BLOCK_DETAIL_LEVEL, (treeParams.getWidthInBlocks() / 2) + 1, 0);
outOfBoundsPos = DhSectionPos.encode(LodUtil.BLOCK_DETAIL_LEVEL, (treeParams.getWidthInBlocks() / 2) + 1, 0);
testSet(tree, outOfBoundsPos, -1, IndexOutOfBoundsException.class);
Assert.assertEquals("incorrect leaf node count", 0, tree.leafNodeCount());
outOfBoundsPos = new DhSectionPos(LodUtil.BLOCK_DETAIL_LEVEL, (treeParams.getWidthInBlocks() / 2), 0);
outOfBoundsPos = DhSectionPos.encode(LodUtil.BLOCK_DETAIL_LEVEL, (treeParams.getWidthInBlocks() / 2), 0);
testSet(tree, outOfBoundsPos, -1, IndexOutOfBoundsException.class);
Assert.assertEquals("incorrect leaf node count", 0, tree.leafNodeCount());
// in bounds //
outOfBoundsPos = new DhSectionPos(LodUtil.BLOCK_DETAIL_LEVEL, (treeParams.getWidthInBlocks() / 2) - 1, 0);
outOfBoundsPos = DhSectionPos.encode(LodUtil.BLOCK_DETAIL_LEVEL, (treeParams.getWidthInBlocks() / 2) - 1, 0);
testSet(tree, outOfBoundsPos, 0);
Assert.assertEquals("incorrect leaf node count", 1, tree.leafNodeCount());
outOfBoundsPos = new DhSectionPos(LodUtil.BLOCK_DETAIL_LEVEL, (treeParams.getWidthInBlocks() / 2) - 3, 0);
outOfBoundsPos = DhSectionPos.encode(LodUtil.BLOCK_DETAIL_LEVEL, (treeParams.getWidthInBlocks() / 2) - 3, 0);
testSet(tree, outOfBoundsPos, 0);
Assert.assertEquals("incorrect leaf node count", 2, tree.leafNodeCount());
// TODO this position probably has trouble with getting the center.
outOfBoundsPos = new DhSectionPos(LodUtil.BLOCK_DETAIL_LEVEL, (treeParams.getWidthInBlocks() / 2) - 2, 0);
outOfBoundsPos = DhSectionPos.encode(LodUtil.BLOCK_DETAIL_LEVEL, (treeParams.getWidthInBlocks() / 2) - 2, 0);
testSet(tree, outOfBoundsPos, 0);
Assert.assertEquals("incorrect leaf node count", 3, tree.leafNodeCount());
outOfBoundsPos = new DhSectionPos(LodUtil.BLOCK_DETAIL_LEVEL, (treeParams.getWidthInBlocks() / 2) - 4, 0);
outOfBoundsPos = DhSectionPos.encode(LodUtil.BLOCK_DETAIL_LEVEL, (treeParams.getWidthInBlocks() / 2) - 4, 0);
testSet(tree, outOfBoundsPos, 0);
Assert.assertEquals("incorrect leaf node count", 4, tree.leafNodeCount());
@@ -173,15 +174,15 @@ public class QuadTreeTest
Assert.assertEquals("Test may need to be re-calculated for different max detail level.", 9, tree.treeMinDetailLevel);
DhSectionPos rootPos = new DhSectionPos((byte) 9, 0, -1);
long rootPos = DhSectionPos.encode((byte) 9, 0, -1);
testSet(tree, rootPos, 1);
// pos is in tree, but out of range
DhSectionPos midPos = new DhSectionPos((byte) 8, 0, -1);
long midPos = DhSectionPos.encode((byte) 8, 0, -1);
testSet(tree, midPos, 2, IndexOutOfBoundsException.class);
// pos is in tree, but out of range
DhSectionPos leafPos = new DhSectionPos((byte) 7, 0, -2);
long leafPos = DhSectionPos.encode((byte) 7, 0, -2);
testSet(tree, leafPos, 3, IndexOutOfBoundsException.class);
}
@@ -197,13 +198,13 @@ public class QuadTreeTest
// (pseudo) root nodes //
testSet(tree, new DhSectionPos((byte) 10, 0, 0), 1);
testSet(tree, DhSectionPos.encode((byte) 10, 0, 0), 1);
// first child (0,0) //
DhSectionPos nw = new DhSectionPos(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL, 0, 0);
DhSectionPos ne = new DhSectionPos(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL, 1, 0);
DhSectionPos sw = new DhSectionPos(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL, 0, 1);
DhSectionPos se = new DhSectionPos(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL, 1, 1);
long nw = DhSectionPos.encode(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL, 0, 0);
long ne = DhSectionPos.encode(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL, 1, 0);
long sw = DhSectionPos.encode(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL, 0, 1);
long se = DhSectionPos.encode(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL, 1, 1);
testSet(tree, nw, 2);
testSet(tree, ne, 3);
@@ -259,12 +260,12 @@ public class QuadTreeTest
Assert.assertEquals("Tree center incorrect", DhBlockPos2D.ZERO, tree.getCenterBlockPos());
// on the negative X edge
DhSectionPos edgePos = new DhSectionPos(LodUtil.BLOCK_DETAIL_LEVEL, -treeParams.getWidthInBlocks() / 2, 0);
long edgePos = DhSectionPos.encode(LodUtil.BLOCK_DETAIL_LEVEL, -treeParams.getWidthInBlocks() / 2, 0);
testSet(tree, edgePos, 1);
Assert.assertEquals("incorrect leaf node count", 1, tree.leafNodeCount());
// +1 root node from the negative X edge
DhSectionPos adjacentEdgePos = new DhSectionPos(LodUtil.BLOCK_DETAIL_LEVEL, (-treeParams.getWidthInBlocks() / 2) + pseudoRootNodeWidthInBlocks, 0);
long adjacentEdgePos = DhSectionPos.encode(LodUtil.BLOCK_DETAIL_LEVEL, (-treeParams.getWidthInBlocks() / 2) + pseudoRootNodeWidthInBlocks, 0);
testSet(tree, adjacentEdgePos, 2);
Assert.assertEquals("incorrect leaf node count", 2, tree.leafNodeCount());
@@ -284,24 +285,24 @@ public class QuadTreeTest
// (pseudo) root nodes //
testSet(tree, new DhSectionPos((byte) 10, 0, 0), 1);
testSet(tree, new DhSectionPos((byte) 10, 1, 0), 2);
testSet(tree, DhSectionPos.encode((byte) 10, 0, 0), 1);
testSet(tree, DhSectionPos.encode((byte) 10, 1, 0), 2);
// first child (0,0) //
testSet(tree, new DhSectionPos((byte) 9, 0, 0), 3);
testSet(tree, new DhSectionPos((byte) 9, 1, 0), 4);
testSet(tree, new DhSectionPos((byte) 9, 0, 1), 5);
testSet(tree, new DhSectionPos((byte) 9, 1, 1), 6);
testSet(tree, DhSectionPos.encode((byte) 9, 0, 0), 3);
testSet(tree, DhSectionPos.encode((byte) 9, 1, 0), 4);
testSet(tree, DhSectionPos.encode((byte) 9, 0, 1), 5);
testSet(tree, DhSectionPos.encode((byte) 9, 1, 1), 6);
// root nodes
int rootNodeCount = 0;
Iterator<DhSectionPos> rootNodePosIterator = tree.rootNodePosIterator();
LongIterator rootNodePosIterator = tree.rootNodePosIterator();
while (rootNodePosIterator.hasNext())
{
QuadNode<Integer> rootNode = tree.getNode(rootNodePosIterator.next());
QuadNode<Integer> rootNode = tree.getNode(rootNodePosIterator.nextLong());
if (rootNode != null)
{
rootNodeCount++;
@@ -331,20 +332,19 @@ public class QuadTreeTest
@Test
public void NewQuadTreeIterationTest()
{
AbstractTestTreeParams treeParams = new LargeTestTree();
QuadNode<Integer> rootNode = new QuadNode<>(new DhSectionPos((byte) 10, 0, 0), LodUtil.BLOCK_DETAIL_LEVEL);
QuadNode<Integer> rootNode = new QuadNode<>(DhSectionPos.encode((byte) 10, 0, 0), LodUtil.BLOCK_DETAIL_LEVEL);
rootNode.setValue(new DhSectionPos((byte) 10, 0, 0), 0);
rootNode.setValue(DhSectionPos.encode((byte) 10, 0, 0), 0);
rootNode.setValue(new DhSectionPos((byte) 9, 0, 0), 1);
rootNode.setValue(new DhSectionPos((byte) 9, 1, 0), 1);
rootNode.setValue(new DhSectionPos((byte) 9, 0, 1), 1);
rootNode.setValue(new DhSectionPos((byte) 9, 1, 1), null);
rootNode.setValue(DhSectionPos.encode((byte) 9, 0, 0), 1);
rootNode.setValue(DhSectionPos.encode((byte) 9, 1, 0), 1);
rootNode.setValue(DhSectionPos.encode((byte) 9, 0, 1), 1);
rootNode.setValue(DhSectionPos.encode((byte) 9, 1, 1), null);
rootNode.setValue(new DhSectionPos((byte) 8, 0, 0), 2);
rootNode.setValue(new DhSectionPos((byte) 8, 1, 0), 2);
rootNode.setValue(new DhSectionPos((byte) 8, 0, 1), 2);
rootNode.setValue(new DhSectionPos((byte) 8, 1, 1), null);
rootNode.setValue(DhSectionPos.encode((byte) 8, 0, 0), 2);
rootNode.setValue(DhSectionPos.encode((byte) 8, 1, 0), 2);
rootNode.setValue(DhSectionPos.encode((byte) 8, 0, 1), 2);
rootNode.setValue(DhSectionPos.encode((byte) 8, 1, 1), null);
@@ -411,14 +411,14 @@ public class QuadTreeTest
{
AbstractTestTreeParams treeParams = new TinyTestTree();
final QuadTree<Integer> tree = new QuadTree<>(treeParams.getWidthInBlocks(), treeParams.getPositiveEdgeCenterPos(), LodUtil.BLOCK_DETAIL_LEVEL);
testSet(tree, new DhSectionPos(tree.treeMinDetailLevel, 0, 0), 0);
testSet(tree, DhSectionPos.encode(tree.treeMinDetailLevel, 0, 0), 0);
// confirm the root node were added
int rootNodeCount = 0;
Iterator<DhSectionPos> rootNodePosIterator = tree.rootNodePosIterator();
LongIterator rootNodePosIterator = tree.rootNodePosIterator();
while (rootNodePosIterator.hasNext())
{
QuadNode<Integer> rootNode = tree.getNode(rootNodePosIterator.next());
QuadNode<Integer> rootNode = tree.getNode(rootNodePosIterator.nextLong());
if (rootNode != null)
{
rootNodeCount++;
@@ -431,7 +431,7 @@ public class QuadTreeTest
rootNodePosIterator = tree.rootNodePosIterator();
while (rootNodePosIterator.hasNext())
{
DhSectionPos rootNodePos = rootNodePosIterator.next();
long rootNodePos = rootNodePosIterator.nextLong();
QuadNode<Integer> rootNode = tree.getNode(rootNodePos);
if (rootNode != null)
{
@@ -468,10 +468,10 @@ public class QuadTreeTest
tree.setCenterBlockPos(treeMovePos);
Assert.assertEquals("tree move failed, incorrect position after move", treeMovePos, tree.getCenterBlockPos());
Iterator<DhSectionPos> rootNodePosIterator = tree.rootNodePosIterator();
LongIterator rootNodePosIterator = tree.rootNodePosIterator();
while (rootNodePosIterator.hasNext())
{
testSet(tree, rootNodePosIterator.next(), 0);
testSet(tree, rootNodePosIterator.nextLong(), 0);
}
@@ -480,7 +480,7 @@ public class QuadTreeTest
rootNodePosIterator = tree.rootNodePosIterator();
while (rootNodePosIterator.hasNext())
{
QuadNode<Integer> rootNode = tree.getNode(rootNodePosIterator.next());
QuadNode<Integer> rootNode = tree.getNode(rootNodePosIterator.nextLong());
if (rootNode != null)
{
rootNodeCount++;
@@ -499,16 +499,16 @@ public class QuadTreeTest
Assert.assertEquals("incorrect tree width", treeParams.getWidthInBlocks(), tree.diameterInBlocks());
testSet(tree, new DhSectionPos(tree.treeMinDetailLevel, 0, 0), 0);
testSet(tree, DhSectionPos.encode(tree.treeMinDetailLevel, 0, 0), 0);
testSet(tree, new DhSectionPos(tree.treeMinDetailLevel, -1, -1), -1, IndexOutOfBoundsException.class);
testSet(tree, new DhSectionPos(tree.treeMinDetailLevel, 1, 1), -1, IndexOutOfBoundsException.class);
testSet(tree, DhSectionPos.encode(tree.treeMinDetailLevel, -1, -1), -1, IndexOutOfBoundsException.class);
testSet(tree, DhSectionPos.encode(tree.treeMinDetailLevel, 1, 1), -1, IndexOutOfBoundsException.class);
int rootNodeCount = 0;
Iterator<DhSectionPos> rootNodeIterator = tree.rootNodePosIterator();
LongIterator rootNodeIterator = tree.rootNodePosIterator();
while (rootNodeIterator.hasNext())
{
QuadNode<Integer> rootNode = tree.getNode(rootNodeIterator.next());
QuadNode<Integer> rootNode = tree.getNode(rootNodeIterator.nextLong());
if (rootNode != null)
{
rootNodeCount++;
@@ -529,25 +529,25 @@ public class QuadTreeTest
// 2x2 valid positions (overlap the tree's width)
testSet(tree, new DhSectionPos(tree.treeMinDetailLevel, 0, 0), 0);
testSet(tree, new DhSectionPos(tree.treeMinDetailLevel, -1, 0), 0);
testSet(tree, new DhSectionPos(tree.treeMinDetailLevel, 0, -1), 0);
testSet(tree, new DhSectionPos(tree.treeMinDetailLevel, -1, -1), 0);
testSet(tree, DhSectionPos.encode(tree.treeMinDetailLevel, 0, 0), 0);
testSet(tree, DhSectionPos.encode(tree.treeMinDetailLevel, -1, 0), 0);
testSet(tree, DhSectionPos.encode(tree.treeMinDetailLevel, 0, -1), 0);
testSet(tree, DhSectionPos.encode(tree.treeMinDetailLevel, -1, -1), 0);
// invalid positions
testSet(tree, new DhSectionPos(tree.treeMinDetailLevel, -1, 1), -1, IndexOutOfBoundsException.class);
testSet(tree, new DhSectionPos(tree.treeMinDetailLevel, 0, 1), -1, IndexOutOfBoundsException.class);
testSet(tree, DhSectionPos.encode(tree.treeMinDetailLevel, -1, 1), -1, IndexOutOfBoundsException.class);
testSet(tree, DhSectionPos.encode(tree.treeMinDetailLevel, 0, 1), -1, IndexOutOfBoundsException.class);
testSet(tree, new DhSectionPos(tree.treeMinDetailLevel, 1, 0), -1, IndexOutOfBoundsException.class);
testSet(tree, new DhSectionPos(tree.treeMinDetailLevel, 1, 1), -1, IndexOutOfBoundsException.class);
testSet(tree, new DhSectionPos(tree.treeMinDetailLevel, 1, -1), -1, IndexOutOfBoundsException.class);
testSet(tree, DhSectionPos.encode(tree.treeMinDetailLevel, 1, 0), -1, IndexOutOfBoundsException.class);
testSet(tree, DhSectionPos.encode(tree.treeMinDetailLevel, 1, 1), -1, IndexOutOfBoundsException.class);
testSet(tree, DhSectionPos.encode(tree.treeMinDetailLevel, 1, -1), -1, IndexOutOfBoundsException.class);
int rootNodeCount = 0;
Iterator<DhSectionPos> rootNodeIterator = tree.rootNodePosIterator();
LongIterator rootNodeIterator = tree.rootNodePosIterator();
while (rootNodeIterator.hasNext())
{
QuadNode<Integer> rootNode = tree.getNode(rootNodeIterator.next());
QuadNode<Integer> rootNode = tree.getNode(rootNodeIterator.nextLong());
if (rootNode != null)
{
rootNodeCount++;
@@ -565,17 +565,17 @@ public class QuadTreeTest
Assert.assertEquals("Test detail level's need to be adjusted. This isn't necessarily a failed test.", 10, tree.treeMinDetailLevel);
// valid detail levels
testSet(tree, new DhSectionPos((byte) 10, 0, 0), 1);
testSet(tree, new DhSectionPos((byte) 9, 0, 0), 2);
testSet(tree, new DhSectionPos((byte) 8, 0, 0), 3);
testSet(tree, DhSectionPos.encode((byte) 10, 0, 0), 1);
testSet(tree, DhSectionPos.encode((byte) 9, 0, 0), 2);
testSet(tree, DhSectionPos.encode((byte) 8, 0, 0), 3);
// detail level too low
testSet(tree, new DhSectionPos((byte) 7, 0, 0), -1, IndexOutOfBoundsException.class);
testSet(tree, new DhSectionPos((byte) 6, 0, 0), -1, IndexOutOfBoundsException.class);
testSet(tree, DhSectionPos.encode((byte) 7, 0, 0), -1, IndexOutOfBoundsException.class);
testSet(tree, DhSectionPos.encode((byte) 6, 0, 0), -1, IndexOutOfBoundsException.class);
// detail level too high
testSet(tree, new DhSectionPos((byte) 11, 0, 0), -1, IndexOutOfBoundsException.class);
testSet(tree, new DhSectionPos((byte) 12, 0, 0), -1, IndexOutOfBoundsException.class);
testSet(tree, DhSectionPos.encode((byte) 11, 0, 0), -1, IndexOutOfBoundsException.class);
testSet(tree, DhSectionPos.encode((byte) 12, 0, 0), -1, IndexOutOfBoundsException.class);
}
@@ -587,25 +587,25 @@ public class QuadTreeTest
Assert.assertEquals("Test detail level's need to be adjusted. This isn't necessarily a failed test.", 10, tree.treeMinDetailLevel);
// create the root node
testSet(tree, new DhSectionPos((byte) 10, 0, 0), 1);
testSet(tree, DhSectionPos.encode((byte) 10, 0, 0), 1);
AtomicInteger minimumDetailLevelReachedRef = new AtomicInteger(tree.treeMinDetailLevel);
// recurse down the tree
Iterator<DhSectionPos> rootNodePosIterator = tree.rootNodePosIterator();
LongIterator rootNodePosIterator = tree.rootNodePosIterator();
while (rootNodePosIterator.hasNext())
{
DhSectionPos sectionPos = rootNodePosIterator.next();
long sectionPos = rootNodePosIterator.nextLong();
QuadNode<Integer> rootNode = tree.getNode(sectionPos);
if (rootNode != null)
{
// fill in the root node's direct children
Iterator<DhSectionPos> childPosIterator = rootNode.getChildPosIterator();
LongIterator childPosIterator = rootNode.getChildPosIterator();
while (childPosIterator.hasNext())
{
DhSectionPos rootChildPos = childPosIterator.next();
long rootChildPos = childPosIterator.nextLong();
rootNode.setValue(rootChildPos, 0);
}
@@ -618,7 +618,7 @@ public class QuadTreeTest
QuadNode<Integer> childNode = ChildIterator.next();
Assert.assertNotNull(childNode); // TODO is this correct?
recursivelyCreateNodeChildren(childNode, tree.treeMaxDetailLevel, minimumDetailLevelReachedRef);
this.recursivelyCreateNodeChildren(childNode, tree.treeMaxDetailLevel, minimumDetailLevelReachedRef);
}
}
}
@@ -634,10 +634,10 @@ public class QuadTreeTest
// fill in the null children
Iterator<DhSectionPos> directChildIterator = node.getChildPosIterator();
LongIterator directChildIterator = node.getChildPosIterator();
while (directChildIterator.hasNext())
{
node.setValue(directChildIterator.next(), 0);
node.setValue(directChildIterator.nextLong(), 0);
childNodesCreated = true;
}
@@ -646,10 +646,10 @@ public class QuadTreeTest
directChildIterator = node.getChildPosIterator();
while (directChildIterator.hasNext())
{
DhSectionPos sectionPos = directChildIterator.next();
long sectionPos = directChildIterator.nextLong();
QuadNode<Integer> childNode = node.getNode(sectionPos);
Assert.assertTrue("Child node recurred too low. Min detail level: " + minDetailLevel + ", node detail level: " + childNode.sectionPos.getDetailLevel(), childNode.sectionPos.getDetailLevel() >= minDetailLevel);
Assert.assertTrue("Child node recurred too low. Min detail level: " + minDetailLevel + ", node detail level: " + DhSectionPos.getDetailLevel(childNode.sectionPos), DhSectionPos.getDetailLevel(childNode.sectionPos) >= minDetailLevel);
recursivelyCreateNodeChildren(childNode, minDetailLevel, minimumDetailLevelReachedRef);
childNodesIterated = true;
@@ -657,9 +657,9 @@ public class QuadTreeTest
// keep track of how far down the tree we have gone
if (node.sectionPos.getDetailLevel() < minimumDetailLevelReachedRef.get())
if ( DhSectionPos.getDetailLevel(node.sectionPos) < minimumDetailLevelReachedRef.get())
{
minimumDetailLevelReachedRef.set(node.sectionPos.getDetailLevel());
minimumDetailLevelReachedRef.set( DhSectionPos.getDetailLevel(node.sectionPos));
}
@@ -667,22 +667,22 @@ public class QuadTreeTest
// assertions
if (childNodesCreated)
{
Assert.assertTrue("node children created below minimum detail level", node.sectionPos.getDetailLevel() >= minDetailLevel);
Assert.assertTrue("node children created below minimum detail level", DhSectionPos.getDetailLevel( node.sectionPos) >= minDetailLevel);
}
if (childNodesIterated)
{
Assert.assertTrue("node children iterated below minimum detail level", node.sectionPos.getDetailLevel() - 1 >= minDetailLevel);
Assert.assertTrue("node children iterated below minimum detail level", DhSectionPos.getDetailLevel(node.sectionPos) - 1 >= minDetailLevel);
}
}
@Test
public void quadNodeChildPositionIndexTest()
{
QuadNode<Integer> rootNode = new QuadNode<>(new DhSectionPos((byte) 10, 0, 0), (byte) 0);
Iterator<DhSectionPos> directChildPosIterator = rootNode.getChildPosIterator();
QuadNode<Integer> rootNode = new QuadNode<>(DhSectionPos.encode((byte) 10, 0, 0), (byte) 0);
LongIterator directChildPosIterator = rootNode.getChildPosIterator();
while (directChildPosIterator.hasNext())
{
DhSectionPos sectionPos = directChildPosIterator.next();
long sectionPos = directChildPosIterator.nextLong();
Assert.assertNotEquals("Root node pos shouldn't be included in direct child pos iteration", sectionPos, rootNode.sectionPos);
rootNode.setValue(sectionPos, 1);
@@ -692,9 +692,9 @@ public class QuadTreeTest
for (int i = 0; i < 4; i++)
{
DhSectionPos childPos = rootNode.sectionPos.getChildByIndex(i);
long childPos = DhSectionPos.getChildByIndex(rootNode.sectionPos, i);
QuadNode<Integer> childNode = rootNode.getChildByIndex(i);
Assert.assertEquals("child position not the same as " + DhSectionPos.class.getSimpleName() + "'s getChildByIndex()", childPos, childNode.sectionPos);
Assert.assertEquals("child position not the same as " + long.class.getSimpleName() + "'s getChildByIndex()", childPos, childNode.sectionPos);
}
}
@@ -708,7 +708,7 @@ public class QuadTreeTest
// center root node
DhSectionPos centerNodePos = new DhSectionPos((byte) 1, 0, 0);
long centerNodePos = DhSectionPos.encode((byte) 1, 0, 0);
// create node
tree.setValue(centerNodePos, 0);
@@ -716,10 +716,10 @@ public class QuadTreeTest
Assert.assertNotNull(centerRootNode);
// child pos in bounds of the tree
Iterator<DhSectionPos> childPosIterator = centerRootNode.getChildPosIterator();
LongIterator childPosIterator = centerRootNode.getChildPosIterator();
while (childPosIterator.hasNext())
{
DhSectionPos childPos = childPosIterator.next();
long childPos = childPosIterator.nextLong();
centerRootNode.setValue(childPos, 1);
}
Assert.assertEquals("center node not filled", 4, centerRootNode.getNonNullChildCount());
@@ -727,7 +727,7 @@ public class QuadTreeTest
// edge root node
DhSectionPos offsetNodePos = new DhSectionPos((byte) 1, -17, -16);
long offsetNodePos = DhSectionPos.encode((byte) 1, -17, -16);
// create node
tree.setValue(offsetNodePos, 0);
@@ -738,7 +738,7 @@ public class QuadTreeTest
childPosIterator = offsetRootNode.getChildPosIterator();
while (childPosIterator.hasNext())
{
DhSectionPos childPos = childPosIterator.next();
long childPos = childPosIterator.nextLong();
offsetRootNode.setValue(childPos, 1);
}
// TODO James thought this shouldn't work for all 4 nodes, but he must've thought wrong.
@@ -772,7 +772,7 @@ public class QuadTreeTest
//
testSet(tree, new DhSectionPos((byte) 0, 0, 0), 1);
testSet(tree, DhSectionPos.encode((byte) 0, 0, 0), 1);
Assert.assertEquals(1, tree.count());
tree.setCenterBlockPos(new DhBlockPos2D(treeWidth + (treeWidth / 2), 0));
Assert.assertEquals(0, tree.count());
@@ -783,21 +783,21 @@ public class QuadTreeTest
//@Test
public void autoDeleteNullQuadNodeChildTest()
{
QuadNode<Integer> rootNode = new QuadNode<>(new DhSectionPos((byte) 10, 0, 0), LodUtil.BLOCK_DETAIL_LEVEL);
QuadNode<Integer> rootNode = new QuadNode<>(DhSectionPos.encode((byte) 10, 0, 0), LodUtil.BLOCK_DETAIL_LEVEL);
rootNode.setValue(new DhSectionPos((byte) 10, 0, 0), 0);
rootNode.setValue(DhSectionPos.encode((byte) 10, 0, 0), 0);
DhSectionPos midNodePos = new DhSectionPos((byte) 9, 0, 0);
long midNodePos = DhSectionPos.encode((byte) 9, 0, 0);
//rootNode.setValue(midNodePos, null); // holds detail 8
rootNode.setValue(new DhSectionPos((byte) 9, 1, 0), 1);
rootNode.setValue(new DhSectionPos((byte) 9, 0, 1), 1);
rootNode.setValue(new DhSectionPos((byte) 9, 1, 1), 1);
rootNode.setValue(DhSectionPos.encode((byte) 9, 1, 0), 1);
rootNode.setValue(DhSectionPos.encode((byte) 9, 0, 1), 1);
rootNode.setValue(DhSectionPos.encode((byte) 9, 1, 1), 1);
rootNode.setValue(new DhSectionPos((byte) 8, 0, 0), 2);
rootNode.setValue(new DhSectionPos((byte) 8, 1, 0), 2);
rootNode.setValue(new DhSectionPos((byte) 8, 0, 1), 2);
rootNode.setValue(new DhSectionPos((byte) 8, 1, 1), 2);
rootNode.setValue(DhSectionPos.encode((byte) 8, 0, 0), 2);
rootNode.setValue(DhSectionPos.encode((byte) 8, 1, 0), 2);
rootNode.setValue(DhSectionPos.encode((byte) 8, 0, 1), 2);
rootNode.setValue(DhSectionPos.encode((byte) 8, 1, 1), 2);
@@ -810,12 +810,12 @@ public class QuadTreeTest
// test removing nodes //
// remove two leaf nodes from the root
DhSectionPos leafPos = new DhSectionPos((byte) 9, 1, 1);
long leafPos = DhSectionPos.encode((byte) 9, 1, 1);
rootNode.setValue(leafPos, null);
Assert.assertEquals(3, rootNode.getNonNullChildCount());
Assert.assertNull("Node wasn't deleted", rootNode.getNode(leafPos));
leafPos = new DhSectionPos((byte) 9, 0, 1);
leafPos = DhSectionPos.encode((byte) 9, 0, 1);
rootNode.setValue(leafPos, null);
Assert.assertEquals(2, rootNode.getNonNullChildCount());
Assert.assertNull("Node wasn't deleted", rootNode.getNode(leafPos));
@@ -827,13 +827,13 @@ public class QuadTreeTest
Assert.assertEquals(4, rootNode.getNode(midNodePos).getNonNullChildCount());
// remove all but one, mid-node should still be present
rootNode.setValue(new DhSectionPos((byte) 8, 0, 0), null);
rootNode.setValue(new DhSectionPos((byte) 8, 0, 1), null);
rootNode.setValue(new DhSectionPos((byte) 8, 1, 0), null);
rootNode.setValue(DhSectionPos.encode((byte) 8, 0, 0), null);
rootNode.setValue(DhSectionPos.encode((byte) 8, 0, 1), null);
rootNode.setValue(DhSectionPos.encode((byte) 8, 1, 0), null);
Assert.assertEquals(1, rootNode.getNode(midNodePos).getNonNullChildCount());
// remove last mid-node child, mid-node should now be removed
rootNode.setValue(new DhSectionPos((byte) 8, 1, 1), null);
rootNode.setValue(DhSectionPos.encode((byte) 8, 1, 1), null);
Assert.assertNull("Mid node not deleted.", rootNode.getNode(midNodePos));
Assert.assertEquals(3, rootNode.getNonNullChildCount());
@@ -847,8 +847,8 @@ public class QuadTreeTest
// helper methods //
//================//
private static void testSet(QuadTree<Integer> tree, DhSectionPos pos, Integer setValue) { testSet(tree, pos, setValue, null); }
private static <TE extends Throwable> void testSet(QuadTree<Integer> tree, DhSectionPos pos, Integer setValue, Class<TE> expectedExceptionClass)
private static void testSet(QuadTree<Integer> tree, long pos, Integer setValue) { testSet(tree, pos, setValue, null); }
private static <TE extends Throwable> void testSet(QuadTree<Integer> tree, long pos, Integer setValue, Class<TE> expectedExceptionClass)
{
// set
try
@@ -860,7 +860,7 @@ public class QuadTreeTest
if (expectedExceptionClass == null || e.getClass() != expectedExceptionClass)
{
e.printStackTrace();
Assert.fail("set failed " + pos + " with exception " + e.getClass() + ", expected exception: " + expectedExceptionClass + ". error: " + e.getMessage());
Assert.fail("set failed [" + DhSectionPos.toString(pos) + "] with exception [" + e.getClass() + "], expected exception: [" + expectedExceptionClass + "]. error: " + e.getMessage());
}
}
@@ -869,20 +869,20 @@ public class QuadTreeTest
testGet(tree, pos, setValue, expectedExceptionClass);
}
private static void testGet(QuadTree<Integer> tree, DhSectionPos pos, Integer getValue) { testSet(tree, pos, getValue, null); }
private static <TE extends Throwable> void testGet(QuadTree<Integer> tree, DhSectionPos pos, Integer getValue, Class<TE> expectedExceptionClass)
private static void testGet(QuadTree<Integer> tree, long pos, Integer getValue) { testSet(tree, pos, getValue, null); }
private static <TE extends Throwable> void testGet(QuadTree<Integer> tree, long pos, Integer getValue, Class<TE> expectedExceptionClass)
{
try
{
Integer getResult = tree.getValue(pos);
Assert.assertEquals("get failed " + pos, getValue, getResult);
Assert.assertEquals("get failed [" + DhSectionPos.toString(pos) + "]", getValue, getResult);
}
catch (Exception e)
{
if (expectedExceptionClass == null || e.getClass() != expectedExceptionClass)
{
e.printStackTrace();
Assert.fail("get failed " + pos + " with exception " + e.getClass() + ", expected exception: " + expectedExceptionClass + ". error: " + e.getMessage());
Assert.fail("get failed [" + DhSectionPos.toString(pos) + "] with exception " + e.getClass() + ", expected exception: " + expectedExceptionClass + ". error: " + e.getMessage());
}
}
}