diff --git a/api/src/main/java/com/seibel/distanthorizons/api/interfaces/block/IDhApiBlockStateWrapper.java b/api/src/main/java/com/seibel/distanthorizons/api/interfaces/block/IDhApiBlockStateWrapper.java index 4d363005d..8a71a090f 100644 --- a/api/src/main/java/com/seibel/distanthorizons/api/interfaces/block/IDhApiBlockStateWrapper.java +++ b/api/src/main/java/com/seibel/distanthorizons/api/interfaces/block/IDhApiBlockStateWrapper.java @@ -39,7 +39,8 @@ public interface IDhApiBlockStateWrapper extends IDhApiUnsafeWrapper boolean isLiquid(); /** - * Returns the full serialized form of the given block. + * Returns the full serialized form of the given block + * as defined by DH's serialization methods. * @since API 3.0.0 */ String getSerialString(); diff --git a/api/src/main/java/com/seibel/distanthorizons/api/interfaces/data/IDhApiTerrainDataCache.java b/api/src/main/java/com/seibel/distanthorizons/api/interfaces/data/IDhApiTerrainDataCache.java new file mode 100644 index 000000000..a57a8a56e --- /dev/null +++ b/api/src/main/java/com/seibel/distanthorizons/api/interfaces/data/IDhApiTerrainDataCache.java @@ -0,0 +1,21 @@ +package com.seibel.distanthorizons.api.interfaces.data; + +/** + * Can be used to drastically speed up repeat read operations in {@link IDhApiTerrainDataRepo}. + * + * @see IDhApiTerrainDataRepo + * + * @author James Seibel + * @version 2024-7-14 + * @since API 3.0.0 + */ +public interface IDhApiTerrainDataCache +{ + /** + * Removes any data that's currently stored in this cache. + * This cane be done to free up memory or invalidate + * the cache so fresh data can be pulled in. + */ + void clear(); + +} diff --git a/api/src/main/java/com/seibel/distanthorizons/api/interfaces/data/IDhApiTerrainDataRepo.java b/api/src/main/java/com/seibel/distanthorizons/api/interfaces/data/IDhApiTerrainDataRepo.java index 2a4f81e24..45f8c1d72 100644 --- a/api/src/main/java/com/seibel/distanthorizons/api/interfaces/data/IDhApiTerrainDataRepo.java +++ b/api/src/main/java/com/seibel/distanthorizons/api/interfaces/data/IDhApiTerrainDataRepo.java @@ -29,6 +29,8 @@ import com.seibel.distanthorizons.api.objects.data.DhApiTerrainDataPoint; /** * Used to interface with Distant Horizons' terrain data. * + * @see IDhApiTerrainDataCache + * * @author James Seibel * @version 2023-6-22 * @since API 1.0.0 @@ -40,29 +42,50 @@ public interface IDhApiTerrainDataRepo // getters // //=========// - /** Returns the terrain datapoint at the given block position, at/or containing the given Y position. */ - DhApiResult getSingleDataPointAtBlockPos(IDhApiLevelWrapper levelWrapper, int blockPosX, int blockPosY, int blockPosZ); - /** Returns every datapoint in the column located at the given block X and Z position top to bottom. */ - DhApiResult getColumnDataAtBlockPos(IDhApiLevelWrapper levelWrapper, int blockPosX, int blockPosZ); + /** @see IDhApiTerrainDataRepo#getSingleDataPointAtBlockPos(IDhApiLevelWrapper, int, int, int, IDhApiTerrainDataCache) */ + default DhApiResult getSingleDataPointAtBlockPos(IDhApiLevelWrapper levelWrapper, int blockPosX, int blockPosY, int blockPosZ) { return this.getSingleDataPointAtBlockPos(levelWrapper, blockPosX, blockPosY, blockPosZ, null); } + /** + * Returns the terrain datapoint at the given block position, at/or containing the given Y position. + * @since API 3.0.0 + */ + DhApiResult getSingleDataPointAtBlockPos(IDhApiLevelWrapper levelWrapper, int blockPosX, int blockPosY, int blockPosZ, IDhApiTerrainDataCache dataCache); + /** @see IDhApiTerrainDataRepo#getColumnDataAtBlockPos(IDhApiLevelWrapper, int, int, IDhApiTerrainDataCache) */ + default DhApiResult getColumnDataAtBlockPos(IDhApiLevelWrapper levelWrapper, int blockPosX, int blockPosZ) { return this.getColumnDataAtBlockPos(levelWrapper, blockPosX, blockPosZ, null); } + /** + * Returns every datapoint in the column located at the given block X and Z position top to bottom. + * @since API 3.0.0 + */ + DhApiResult getColumnDataAtBlockPos(IDhApiLevelWrapper levelWrapper, int blockPosX, int blockPosZ, IDhApiTerrainDataCache dataCache); + + /** @see IDhApiTerrainDataRepo#getAllTerrainDataAtChunkPos(IDhApiLevelWrapper, int, int, IDhApiTerrainDataCache) */ + default DhApiResult getAllTerrainDataAtChunkPos(IDhApiLevelWrapper levelWrapper, int chunkPosX, int chunkPosZ) { return this.getAllTerrainDataAtChunkPos(levelWrapper, chunkPosX, chunkPosZ, null); } /** * Returns every datapoint in the given chunk's X and Z position.

* * The returned array is ordered: [relativeBlockX][relativeBlockZ][columnIndex]
* RelativeBlockX/Z are relative to the block position closest to negative infinity in the chunk's position.
* The column data is ordered from top to bottom. Note: each column may have a different number of values.
+ * + * @since API 3.0.0 */ - DhApiResult getAllTerrainDataAtChunkPos(IDhApiLevelWrapper levelWrapper, int chunkPosX, int chunkPosZ); + DhApiResult getAllTerrainDataAtChunkPos(IDhApiLevelWrapper levelWrapper, int chunkPosX, int chunkPosZ, IDhApiTerrainDataCache dataCache); + /** @see IDhApiTerrainDataRepo#getAllTerrainDataAtRegionPos(IDhApiLevelWrapper, int, int, IDhApiTerrainDataCache) */ + default DhApiResult getAllTerrainDataAtRegionPos(IDhApiLevelWrapper levelWrapper, int regionPosX, int regionPosZ) { return this.getAllTerrainDataAtRegionPos(levelWrapper, regionPosX, regionPosZ, null); } /** * Returns every datapoint in the given region's X and Z position.

* * The returned array is ordered: [relativeBlockX][relativeBlockZ][columnIndex]
* RelativeBlockX/Z are relative to the block position closest to negative infinity in the region's position.
* The column data is ordered from top to bottom. Note: each column may have a different number of values.
+ * + * @since API 3.0.0 */ - DhApiResult getAllTerrainDataAtRegionPos(IDhApiLevelWrapper levelWrapper, int regionPosX, int regionPosZ); + DhApiResult getAllTerrainDataAtRegionPos(IDhApiLevelWrapper levelWrapper, int regionPosX, int regionPosZ, IDhApiTerrainDataCache dataCache); + /** @see IDhApiTerrainDataRepo#getAllTerrainDataAtDetailLevelAndPos(IDhApiLevelWrapper, byte, int, int, IDhApiTerrainDataCache) */ + default DhApiResult getAllTerrainDataAtRegionPos(IDhApiLevelWrapper levelWrapper, byte detailLevel, int posX, int posZ) { return this.getAllTerrainDataAtDetailLevelAndPos(levelWrapper, detailLevel, posX, posZ, null); } /** * Returns every datapoint in the column located at the given detail level and X/Z position.
* This can be used to return terrain data for non-standard sizes (IE 2x2 blocks or 2x2 chunks). @@ -71,20 +94,42 @@ public interface IDhApiTerrainDataRepo * Every increase doubles the width of the returned area.
* Example values: 0 = block, 1 = 2x2 blocks, 2 = 4x4 blocks, ... 4 = chunk (16x16 blocks), ... 9 = region (512x512 blocks)
* See {@link EDhApiDetailLevel} for more information. + * + * @since API 3.0.0 */ - DhApiResult getAllTerrainDataAtDetailLevelAndPos(IDhApiLevelWrapper levelWrapper, byte detailLevel, int posX, int posZ); + DhApiResult getAllTerrainDataAtDetailLevelAndPos(IDhApiLevelWrapper levelWrapper, byte detailLevel, int posX, int posZ, IDhApiTerrainDataCache dataCache); + + + + /** @see IDhApiTerrainDataRepo#raycast(IDhApiLevelWrapper, double, double, double, float, float, float, int, IDhApiTerrainDataCache) */ + default DhApiResult raycast( + IDhApiLevelWrapper levelWrapper, + double rayOriginX, double rayOriginY, double rayOriginZ, + float rayDirectionX, float rayDirectionY, float rayDirectionZ, + int maxRayBlockLength) + { + return this.raycast( + levelWrapper, + rayOriginX, rayOriginY, rayOriginZ, + rayDirectionX, rayDirectionY, rayDirectionZ, + maxRayBlockLength, + null); + } /** * Returns the datapoint and position of the LOD * at the end of the given ray.

* * Will return "success" with a null datapoint if the ray reaches the max length without finding any data. + * + * @since API 3.0.0 */ DhApiResult raycast( IDhApiLevelWrapper levelWrapper, double rayOriginX, double rayOriginY, double rayOriginZ, float rayDirectionX, float rayDirectionY, float rayDirectionZ, - int maxRayBlockLength); + int maxRayBlockLength, + IDhApiTerrainDataCache dataCache); @@ -98,15 +143,27 @@ public interface IDhApiTerrainDataRepo * Notes:
* - Only works if the given {@link IDhApiLevelWrapper} points to a loaded level.
* - If the player travels to this chunk, or the chunk is updated in some other way; your data will be replaced - * by whatever the current chunk is.
- * - This method may not update the LOD data immediately. Any other chunks have - * been queued to update, they will be handled first. + * by whatever the current chunk is.
+ * - This method may not update the LOD data immediately. Any other chunks that have + * been queued to update will be handled first. * * @param levelWrapper the level wrapper that the chunk should be saved to. * @param chunkObjectArray see {@link IDhApiWorldGenerator#generateChunks} for what objects are expected. * @throws ClassCastException if chunkObjectArray doesn't contain the right objects. * The exception will contain the expected object(s). */ - public DhApiResult overwriteChunkDataAsync(IDhApiLevelWrapper levelWrapper, Object[] chunkObjectArray) throws ClassCastException; + DhApiResult overwriteChunkDataAsync(IDhApiLevelWrapper levelWrapper, Object[] chunkObjectArray) throws ClassCastException; + + + + //=========// + // helpers // + //=========// + + /** + * @return a {@link IDhApiTerrainDataCache} backed by {@link java.lang.ref.SoftReference}'s. + * @since API 3.0.0 + */ + IDhApiTerrainDataCache getSoftCache(); } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/api/external/methods/data/DhApiTerrainDataCache.java b/core/src/main/java/com/seibel/distanthorizons/core/api/external/methods/data/DhApiTerrainDataCache.java new file mode 100644 index 000000000..37cabfb97 --- /dev/null +++ b/core/src/main/java/com/seibel/distanthorizons/core/api/external/methods/data/DhApiTerrainDataCache.java @@ -0,0 +1,87 @@ +package com.seibel.distanthorizons.core.api.external.methods.data; + +import com.seibel.distanthorizons.api.interfaces.data.IDhApiTerrainDataCache; +import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2; +import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap; +import it.unimi.dsi.fastutil.longs.LongSet; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.jetbrains.annotations.Nullable; + +import java.lang.ref.SoftReference; + +public class DhApiTerrainDataCache implements IDhApiTerrainDataCache +{ + private final Object modificationLock = new Object(); + private Long2ReferenceOpenHashMap> posToFullDataRef = new Long2ReferenceOpenHashMap<>(); + + private static final Logger LOGGER = LogManager.getLogger(DhApiTerrainDataCache.class.getSimpleName()); + + + + //==================// + // internal methods // + //==================// + + public void add(long pos, FullDataSourceV2 dataSource) + { + synchronized (this.modificationLock) + { + this.posToFullDataRef.put(pos, new SoftReference<>(dataSource)); + } + } + + @Nullable + public FullDataSourceV2 get(long pos) + { + synchronized (this.modificationLock) + { + SoftReference ref = this.posToFullDataRef.get(pos); + if (ref != null) + { + return ref.get(); + } + else + { + return null; + } + } + } + + + + //=============// + // API methods // + //=============// + + @Override + public void clear() + { + synchronized (this.modificationLock) + { + LongSet keySet = this.posToFullDataRef.keySet(); + for (long pos : keySet) + { + SoftReference dataRef = this.posToFullDataRef.remove(pos); + if (dataRef != null) + { + FullDataSourceV2 dataSource = dataRef.get(); + if (dataSource != null) + { + try + { + dataSource.close(); + } + catch (Exception e) + { + LOGGER.warn("Unable to close data source, error: [" + e.getMessage() + "].", e); + } + } + } + } + } + } + + + +} diff --git a/core/src/main/java/com/seibel/distanthorizons/core/api/external/methods/data/DhApiTerrainDataRepo.java b/core/src/main/java/com/seibel/distanthorizons/core/api/external/methods/data/DhApiTerrainDataRepo.java index c04d786ab..cf7d337c0 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/api/external/methods/data/DhApiTerrainDataRepo.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/api/external/methods/data/DhApiTerrainDataRepo.java @@ -19,6 +19,7 @@ package com.seibel.distanthorizons.core.api.external.methods.data; +import com.seibel.distanthorizons.api.interfaces.data.IDhApiTerrainDataCache; import com.seibel.distanthorizons.api.interfaces.world.IDhApiLevelWrapper; import com.seibel.distanthorizons.api.objects.DhApiResult; import com.seibel.distanthorizons.api.objects.data.DhApiRaycastResult; @@ -49,6 +50,7 @@ import com.seibel.distanthorizons.core.util.math.Vec3i; import it.unimi.dsi.fastutil.longs.LongArrayList; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.concurrent.ExecutionException; @@ -56,9 +58,6 @@ import java.util.concurrent.ExecutionException; /** * Allows interfacing with the terrain data Distant Horizons has stored. - * - * @author James Seibel - * @version 2022-11-19 */ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo { @@ -68,12 +67,15 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo // debugging values private static volatile boolean debugThreadRunning = false; - private static String currentDebugBiomeName = ""; - private static int currentDebugBlockColorInt = -1; + private static DhApiTerrainDataCache debugDataCache = new DhApiTerrainDataCache(); private static DhApiVec3i currentDebugVec3i = new Vec3i(); + //=============// + // constructor // + //=============// + private DhApiTerrainDataRepo() { @@ -86,41 +88,32 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo //================// @Override - public DhApiResult getSingleDataPointAtBlockPos(IDhApiLevelWrapper levelWrapper, int blockPosX, int blockPosY, int blockPosZ) - { - return getTerrainDataAtBlockYPos(levelWrapper, new DhLodPos(LodUtil.BLOCK_DETAIL_LEVEL, blockPosX, blockPosZ), blockPosY); - } + public DhApiResult getSingleDataPointAtBlockPos(IDhApiLevelWrapper levelWrapper, int blockPosX, int blockPosY, int blockPosZ, @Nullable IDhApiTerrainDataCache dataCache) + { return getTerrainDataAtBlockYPos(levelWrapper, new DhLodPos(LodUtil.BLOCK_DETAIL_LEVEL, blockPosX, blockPosZ), blockPosY, dataCache); } @Override - public DhApiResult getColumnDataAtBlockPos(IDhApiLevelWrapper levelWrapper, int blockPosX, int blockPosZ) - { - return getTerrainDataColumnArray(levelWrapper, new DhLodPos(LodUtil.BLOCK_DETAIL_LEVEL, blockPosX, blockPosZ), null); - } + public DhApiResult getColumnDataAtBlockPos(IDhApiLevelWrapper levelWrapper, int blockPosX, int blockPosZ, @Nullable IDhApiTerrainDataCache dataCache) + { return getTerrainDataColumnArray(levelWrapper, new DhLodPos(LodUtil.BLOCK_DETAIL_LEVEL, blockPosX, blockPosZ), null, dataCache); } @Override - public DhApiResult getAllTerrainDataAtChunkPos(IDhApiLevelWrapper levelWrapper, int chunkPosX, int chunkPosZ) - { - return getTerrainDataOverAreaForPositionDetailLevel(levelWrapper, new DhLodPos(LodUtil.CHUNK_DETAIL_LEVEL, chunkPosX, chunkPosZ)); - } + public DhApiResult getAllTerrainDataAtChunkPos(IDhApiLevelWrapper levelWrapper, int chunkPosX, int chunkPosZ, @Nullable IDhApiTerrainDataCache dataCache) + { return getTerrainDataOverAreaForPositionDetailLevel(levelWrapper, new DhLodPos(LodUtil.CHUNK_DETAIL_LEVEL, chunkPosX, chunkPosZ), dataCache); } @Override - public DhApiResult getAllTerrainDataAtRegionPos(IDhApiLevelWrapper levelWrapper, int regionPosX, int regionPosZ) - { - return getTerrainDataOverAreaForPositionDetailLevel(levelWrapper, new DhLodPos(LodUtil.REGION_DETAIL_LEVEL, regionPosX, regionPosZ)); - } + public DhApiResult getAllTerrainDataAtRegionPos(IDhApiLevelWrapper levelWrapper, int regionPosX, int regionPosZ, @Nullable IDhApiTerrainDataCache dataCache) + { return getTerrainDataOverAreaForPositionDetailLevel(levelWrapper, new DhLodPos(LodUtil.REGION_DETAIL_LEVEL, regionPosX, regionPosZ), dataCache); } @Override - public DhApiResult getAllTerrainDataAtDetailLevelAndPos(IDhApiLevelWrapper levelWrapper, byte detailLevel, int posX, int posZ) - { - return getTerrainDataOverAreaForPositionDetailLevel(levelWrapper, new DhLodPos(detailLevel, posX, posZ)); - } + public DhApiResult getAllTerrainDataAtDetailLevelAndPos(IDhApiLevelWrapper levelWrapper, byte detailLevel, int posX, int posZ, @Nullable IDhApiTerrainDataCache dataCache) + { return getTerrainDataOverAreaForPositionDetailLevel(levelWrapper, new DhLodPos(detailLevel, posX, posZ), dataCache); } + // private getters // /** Returns a single API terrain datapoint that contains the given Y block position */ - private static DhApiResult getTerrainDataAtBlockYPos(IDhApiLevelWrapper levelWrapper, DhLodPos requestedColumnPos, Integer blockYPos) + private static DhApiResult getTerrainDataAtBlockYPos(IDhApiLevelWrapper levelWrapper, DhLodPos requestedColumnPos, Integer blockYPos, @Nullable IDhApiTerrainDataCache dataCache) { - DhApiResult result = getTerrainDataColumnArray(levelWrapper, requestedColumnPos, blockYPos); + DhApiResult result = getTerrainDataColumnArray(levelWrapper, requestedColumnPos, blockYPos, dataCache); if (result.success && result.payload.length > 0) { return DhApiResult.createSuccess(result.message, result.payload[0]); @@ -140,7 +133,9 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo * * will stop and return the in progress data if any errors are encountered. */ - private static DhApiResult getTerrainDataOverAreaForPositionDetailLevel(IDhApiLevelWrapper levelWrapper, DhLodPos requestedAreaPos) + private static DhApiResult getTerrainDataOverAreaForPositionDetailLevel( + IDhApiLevelWrapper levelWrapper, DhLodPos requestedAreaPos, + @Nullable IDhApiTerrainDataCache dataCache) { DhLodPos startingBlockPos = requestedAreaPos.getCornerLodPos(LodUtil.BLOCK_DETAIL_LEVEL); int widthOfAreaInBlocks = BitShiftUtil.powerOfTwo(requestedAreaPos.detailLevel); @@ -154,7 +149,7 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo for (int z = 0; z < widthOfAreaInBlocks; z++) { DhLodPos blockColumnPos = new DhLodPos(LodUtil.BLOCK_DETAIL_LEVEL, startingBlockPos.x + x, startingBlockPos.z + z); - DhApiResult result = getTerrainDataColumnArray(levelWrapper, blockColumnPos, null); + DhApiResult result = getTerrainDataColumnArray(levelWrapper, blockColumnPos, null, dataCache); if (result.success) { returnArray[x][z] = result.payload; @@ -177,8 +172,15 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo * If the ApiResult is successful there will be an array of data.
* The returned array will be empty if no data could be retrieved. */ - private static DhApiResult getTerrainDataColumnArray(IDhApiLevelWrapper levelWrapper, DhLodPos requestedColumnPos, Integer nullableBlockYPos) + private static DhApiResult getTerrainDataColumnArray( + IDhApiLevelWrapper levelWrapper, + DhLodPos requestedColumnPos, Integer nullableBlockYPos, + @Nullable IDhApiTerrainDataCache apiDataCache) { + //============// + // validation // + //============// + AbstractDhWorld currentWorld = SharedApi.getAbstractDhWorld(); if (currentWorld == null) { @@ -194,6 +196,15 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo ILevelWrapper coreLevelWrapper = (ILevelWrapper) levelWrapper; + if (!(apiDataCache instanceof DhApiTerrainDataCache)) + { + // custom level wrappers aren't supported, + // the API user must get a level wrapper from our code somewhere + return DhApiResult.createFail("Unsupported [" + IDhApiTerrainDataCache.class.getSimpleName() + "] implementation, only the core class [" + DhApiTerrainDataCache.class.getSimpleName() + "] is a valid parameter."); + } + DhApiTerrainDataCache dataCache = (DhApiTerrainDataCache) apiDataCache; + + IDhLevel level = currentWorld.getLevel(coreLevelWrapper); if (level == null) { @@ -209,70 +220,96 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo DhLodPos relativePos = requestedColumnPos.getDhSectionRelativePositionForDetailLevel(); + + //=====================// + // get the data source // + //=====================// + try { - // attempt to get/generate the data source for this section - FullDataSourceV2 dataSource = level.getFullDataProvider().getAsync(sectionPos).get(); + FullDataSourceV2 dataSource = null; + + // try using the cached data if possible + if (dataCache != null) + { + dataSource = dataCache.get(sectionPos); + } + if (dataSource == null) { - return DhApiResult.createFail("Unable to find/generate any data at the " + DhSectionPos.class.getSimpleName() + " [" + DhSectionPos.toString(sectionPos) + "]."); - } - else - { - // attempt to get the LOD data from the data source - FullDataPointIdMap mapping = dataSource.mapping; - LongArrayList dataColumn = dataSource.get(relativePos.x, relativePos.z); - if (dataColumn != null) + // attempt to get/generate the data source for this section + dataSource = level.getFullDataProvider().getAsync(sectionPos).get(); + if (dataSource == null) { - int dataColumnIndexCount = dataColumn.size(); - DhApiTerrainDataPoint[] returnArray = new DhApiTerrainDataPoint[dataColumnIndexCount]; - long dataPoint; + return DhApiResult.createFail("Unable to find/generate any data at the " + DhSectionPos.class.getSimpleName() + " [" + DhSectionPos.toString(sectionPos) + "]."); + } + dataCache.add(sectionPos, dataSource); + } + + + + //===============================// + // get LOD data from data source // + //===============================// + + FullDataPointIdMap mapping = dataSource.mapping; + LongArrayList dataColumn = dataSource.get(relativePos.x, relativePos.z); + if (dataColumn != null) + { + int dataColumnIndexCount = dataColumn.size(); + DhApiTerrainDataPoint[] returnArray = new DhApiTerrainDataPoint[dataColumnIndexCount]; + long dataPoint; + + boolean getSpecificYCoordinate = nullableBlockYPos != null; + int levelMinimumHeight = levelWrapper.getMinHeight(); + + + // search for a datapoint that contains the block y position + for (int i = 0; i < dataColumnIndexCount; i++) + { + dataPoint = dataColumn.getLong(i); - boolean getSpecificYCoordinate = nullableBlockYPos != null; - int levelMinimumHeight = levelWrapper.getMinHeight(); - - - // search for a datapoint that contains the block y position - for (int i = 0; i < dataColumnIndexCount; i++) + if (!getSpecificYCoordinate) { - dataPoint = dataColumn.getLong(i); - - if (!getSpecificYCoordinate) + // if we aren't look for a specific datapoint, add each datapoint to the return array + returnArray[i] = generateApiDatapoint(levelWrapper, mapping, requestedDetailLevel, dataPoint); + } + else + { + // we are looking for a specific datapoint, + // don't look at null ones + if (dataPoint != 0) { - // if we aren't look for a specific datapoint, add each datapoint to the return array - returnArray[i] = generateApiDatapoint(levelWrapper, mapping, requestedDetailLevel, dataPoint); - } - else - { - // we are looking for a specific datapoint, - // don't look at null ones - if (dataPoint != 0) + int requestedY = nullableBlockYPos; + int bottomY = FullDataPointUtil.getBottomY(dataPoint) + levelMinimumHeight; + int height = FullDataPointUtil.getHeight(dataPoint); + int topY = bottomY + height; + + // does this datapoint contain the requested Y position? + if (bottomY <= requestedY && requestedY < topY) // blockPositions start from the bottom of the block, thus "<=" for bottomY, just "<" for topY { - int requestedY = nullableBlockYPos; - int bottomY = FullDataPointUtil.getBottomY(dataPoint) + levelMinimumHeight; - int height = FullDataPointUtil.getHeight(dataPoint); - int topY = bottomY + height; - - // does this datapoint contain the requested Y position? - if (bottomY <= requestedY && requestedY < topY) // blockPositions start from the bottom of the block, thus "<=" for bottomY, just "<" for topY - { - // this datapoint contains the requested block position, return it - DhApiTerrainDataPoint apiTerrainData = generateApiDatapoint(levelWrapper, mapping, requestedDetailLevel, dataPoint); - return DhApiResult.createSuccess(new DhApiTerrainDataPoint[]{apiTerrainData}); - } + // this datapoint contains the requested block position, return it + DhApiTerrainDataPoint apiTerrainData = generateApiDatapoint(levelWrapper, mapping, requestedDetailLevel, dataPoint); + return DhApiResult.createSuccess(new DhApiTerrainDataPoint[]{apiTerrainData}); } } } - - // return all collected data - return DhApiResult.createSuccess(returnArray); } - // the requested data wasn't present in this column (and/or the column wasn't able to be accessed/generated) - return DhApiResult.createSuccess(new DhApiTerrainDataPoint[0]); + // return all collected data + return DhApiResult.createSuccess(returnArray); } + + // the requested data wasn't present in this column (and/or the column wasn't able to be accessed/generated) + return DhApiResult.createSuccess(new DhApiTerrainDataPoint[0]); } catch (InterruptedException | ExecutionException e) + { + // shouldn't normally happen, but just in case + LOGGER.error("getTerrainDataColumnArray operation canceled. Error: [" + e.getMessage() + "]", e); + return DhApiResult.createFail("Operation cancled before it could complete: [" + e.getMessage() + "]."); + } + catch (Exception e) { // shouldn't normally happen, but just in case LOGGER.error("Unexpected exception in getTerrainDataColumnArray. Error: [" + e.getMessage() + "]", e); @@ -306,9 +343,11 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo IDhApiLevelWrapper levelWrapper, double rayOriginX, double rayOriginY, double rayOriginZ, float rayDirectionX, float rayDirectionY, float rayDirectionZ, - int maxRayBlockLength) + int maxRayBlockLength, + @Nullable + IDhApiTerrainDataCache dataCache) { - return this.raycastLodData(levelWrapper, new Vec3d(rayOriginX, rayOriginY, rayOriginZ), new Vec3f(rayDirectionX, rayDirectionY, rayDirectionZ), maxRayBlockLength); + return this.raycastLodData(levelWrapper, new Vec3d(rayOriginX, rayOriginY, rayOriginZ), new Vec3f(rayDirectionX, rayDirectionY, rayDirectionZ), maxRayBlockLength, dataCache); } /** @@ -317,7 +356,12 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo * Works by walking through the world and attempting to get the LOD
* data present at each step. */ - private DhApiResult raycastLodData(IDhApiLevelWrapper levelWrapper, Vec3d rayOrigin, Vec3f rayDirection, int maxRayBlockLength) + private DhApiResult raycastLodData( + IDhApiLevelWrapper levelWrapper, + Vec3d rayOrigin, Vec3f rayDirection, + int maxRayBlockLength, + @Nullable + IDhApiTerrainDataCache dataCache) { rayDirection.normalize(); @@ -346,7 +390,7 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo for (Vec3i columnPos : columnPositions) { // check each column - DhApiResult result = this.getColumnDataAtBlockPos(levelWrapper, columnPos.x, columnPos.z); + DhApiResult result = this.getColumnDataAtBlockPos(levelWrapper, columnPos.x, columnPos.z, dataCache); if (!result.success) { // if there was an error, stop and return it @@ -469,6 +513,15 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo + //=============// + // API helpers // + //=============// + + @Override + public IDhApiTerrainDataCache getSoftCache() { return new DhApiTerrainDataCache(); } + + + //===============// // debug methods // //===============// @@ -485,15 +538,15 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo Thread thread = new Thread(() -> { try { - DhApiResult single = getTerrainDataAtBlockYPos(levelWrapper, new DhLodPos(LodUtil.BLOCK_DETAIL_LEVEL, blockPosX, blockPosZ), blockPosY); - DhApiResult column = getTerrainDataColumnArray(levelWrapper, new DhLodPos(LodUtil.BLOCK_DETAIL_LEVEL, blockPosX, blockPosZ), null); + DhApiResult single = getTerrainDataAtBlockYPos(levelWrapper, new DhLodPos(LodUtil.BLOCK_DETAIL_LEVEL, blockPosX, blockPosZ), blockPosY, debugDataCache); + DhApiResult column = getTerrainDataColumnArray(levelWrapper, new DhLodPos(LodUtil.BLOCK_DETAIL_LEVEL, blockPosX, blockPosZ), null, debugDataCache); DhLodPos chunkPos = new DhLodPos(LodUtil.BLOCK_DETAIL_LEVEL, blockPosX, blockPosZ).convertToDetailLevel(LodUtil.CHUNK_DETAIL_LEVEL); - DhApiResult area = getTerrainDataOverAreaForPositionDetailLevel(levelWrapper, chunkPos); + DhApiResult area = getTerrainDataOverAreaForPositionDetailLevel(levelWrapper, chunkPos, debugDataCache); IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class); - DhApiResult rayCast = INSTANCE.raycastLodData(levelWrapper, MC_RENDER.getCameraExactPosition(), MC_RENDER.getLookAtVector(), 1000); + DhApiResult rayCast = INSTANCE.raycastLodData(levelWrapper, MC_RENDER.getCameraExactPosition(), MC_RENDER.getLookAtVector(), 1000, debugDataCache); if (rayCast.payload != null && !rayCast.payload.pos.equals(currentDebugVec3i)) { currentDebugVec3i = rayCast.payload.pos;