Merge in multi-dim support

This commit is contained in:
James Seibel
2022-03-19 12:38:42 -05:00
27 changed files with 550 additions and 1051 deletions
@@ -58,7 +58,7 @@ import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
public class ClientApi
{
public static boolean prefLoggerEnabled = false;
public static List<WeakReference<SpamReducedLogger>> spamReducedLoggers
public static final List<WeakReference<SpamReducedLogger>> spamReducedLoggers
= Collections.synchronizedList(new LinkedList<WeakReference<SpamReducedLogger>>());
public static final ClientApi INSTANCE = new ClientApi();
@@ -19,14 +19,10 @@
package com.seibel.lod.core.api;
import org.lwjgl.glfw.GLFW;
import com.seibel.lod.core.api.ClientApi.LagSpikeCatcher;
import com.seibel.lod.core.builders.lodBuilding.LodBuilder;
import com.seibel.lod.core.builders.worldGeneration.BatchGenerator;
import com.seibel.lod.core.builders.worldGeneration.LodWorldGenerator;
import com.seibel.lod.core.enums.worldGeneration.BatchGenerator;
import com.seibel.lod.core.enums.WorldType;
import com.seibel.lod.core.enums.config.VerticalQuality;
import com.seibel.lod.core.handlers.dependencyInjection.SingletonHandler;
import com.seibel.lod.core.objects.lod.LodDimension;
import com.seibel.lod.core.objects.lod.RegionPos;
@@ -84,13 +80,9 @@ public class EventApi {
if (CONFIG.client().worldGenerator().getEnableDistantGeneration()) {
try {
if (VERSION_CONSTANTS.hasBatchGenerationImplementation()) {
if (batchGenerator == null)
batchGenerator = new BatchGenerator(ApiShared.lodBuilder, lodDim);
batchGenerator.queueGenerationRequests(lodDim, ApiShared.lodBuilder);
} else {
LodWorldGenerator.INSTANCE.queueGenerationRequests(lodDim, ApiShared.lodBuilder);
}
if (batchGenerator == null)
batchGenerator = new BatchGenerator(ApiShared.lodBuilder, lodDim);
batchGenerator.queueGenerationRequests(lodDim, ApiShared.lodBuilder);
} catch (Exception e) {
// Exception may happen if world got unloaded unorderly
e.printStackTrace();
@@ -160,13 +152,9 @@ public class EventApi {
ApiShared.isShuttingDown = true;
// TODO Better report on when world gen is stuck and timeout
if (VERSION_CONSTANTS.hasBatchGenerationImplementation()) {
if (batchGenerator != null)
batchGenerator.stop(true);
batchGenerator = null;
} else {
LodWorldGenerator.INSTANCE.restartExecutorService();
}
if (batchGenerator != null)
batchGenerator.stop(true);
batchGenerator = null;
ApiShared.lodWorld.deselectWorld(); // This force a save and shutdown lodDim properly
@@ -25,16 +25,13 @@ import java.util.concurrent.locks.ReentrantLock;
import com.seibel.lod.core.api.ApiShared;
import com.seibel.lod.core.handlers.dependencyInjection.SingletonHandler;
import com.seibel.lod.core.objects.Pos2D;
import com.seibel.lod.core.objects.lod.LodDimension;
import com.seibel.lod.core.objects.lod.RegionPos;
import com.seibel.lod.core.objects.opengl.RenderRegion;
import com.seibel.lod.core.render.LodRenderer;
import com.seibel.lod.core.util.LevelPosUtil;
import com.seibel.lod.core.util.LodThreadFactory;
import com.seibel.lod.core.util.LodUtil;
import com.seibel.lod.core.util.MovableGridRingList;
import com.seibel.lod.core.util.SpamReducedLogger;
import com.seibel.lod.core.util.StatsMap;
import com.seibel.lod.core.util.*;
import com.seibel.lod.core.util.gridList.MovableGridRingList;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
@@ -209,8 +206,8 @@ public class LodBufferBuilderFactory {
// int cullingRangeZ = Math.max((int)(1.5 * Math.abs(lastZ - playerZ)),
// minCullingRange);
MovableGridRingList.Pos minPos = renderRegions.getMinInRange();
MovableGridRingList.Pos maxPos = renderRegions.getMaxInRange();
Pos2D minPos = renderRegions.getMinInRange();
Pos2D maxPos = renderRegions.getMaxInRange();
CompletableFuture<Void> future = CompletableFuture.completedFuture(null);
try {
@@ -17,7 +17,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.builders.worldGeneration;
package com.seibel.lod.core.enums.worldGeneration;
import com.seibel.lod.core.api.ApiShared;
import com.seibel.lod.core.builders.lodBuilding.LodBuilder;
@@ -1,364 +0,0 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.builders.worldGeneration;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.seibel.lod.core.api.ApiShared;
import com.seibel.lod.core.builders.lodBuilding.LodBuilder;
import com.seibel.lod.core.enums.config.DistanceGenerationMode;
import com.seibel.lod.core.enums.config.GenerationPriority;
import com.seibel.lod.core.handlers.dependencyInjection.SingletonHandler;
import com.seibel.lod.core.objects.PosToGenerateContainer;
import com.seibel.lod.core.objects.lod.LodDimension;
import com.seibel.lod.core.util.LevelPosUtil;
import com.seibel.lod.core.util.LodThreadFactory;
import com.seibel.lod.core.util.LodUtil;
import com.seibel.lod.core.wrapperInterfaces.IVersionConstants;
import com.seibel.lod.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
import com.seibel.lod.core.wrapperInterfaces.worldGeneration.AbstractWorldGeneratorWrapper;
/**
* A singleton that handles all long distance LOD world generation.
*
* @author Leonardo Amato
* @author James Seibel
* @version 12-11-2021
*/
public class LodWorldGenerator {
private static final IMinecraftClientWrapper MC = SingletonHandler.get(IMinecraftClientWrapper.class);
private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class);
private static final IWrapperFactory WRAPPER_FACTORY = SingletonHandler.get(IWrapperFactory.class);
private static final IVersionConstants VERSION_CONSTANTS = SingletonHandler.get(IVersionConstants.class);
/**
* This holds the thread used to create LOD generation requests off the main
* thread.
*/
private final ExecutorService mainGenThread = Executors
.newSingleThreadExecutor(new LodThreadFactory(this.getClass().getSimpleName() + " world generator", 1));
private ExecutorService genSubThreads = Executors.newFixedThreadPool(
CONFIG.client().advanced().threading().getNumberOfWorldGenerationThreads(),
new LodThreadFactory("Gen-Worker-Thread", 1));
/** we only want to queue up one generator thread at a time */
private boolean generatorThreadRunning = false;
/**
* This keeps track of how many chunk generation requests are on going. This is
* to limit how many chunks are queued at once. To prevent chunks from being
* generated for a long time in an area the player is no longer in.
*/
public AtomicInteger numberOfChunksWaitingToGenerate = new AtomicInteger(0);
public final Set<AbstractChunkPosWrapper> positionsWaitingToBeGenerated = new HashSet<>();
/**
* Singleton copy of this object
*/
public static final LodWorldGenerator INSTANCE = new LodWorldGenerator();
private LodWorldGenerator() {
}
/**
* Queues up LodNodeGenWorkers for the given lodDimension. renderer needed so
* the LodNodeGenWorkers can flag that the buffers need to be rebuilt.
*/
public void queueGenerationRequests(LodDimension lodDim, LodBuilder lodBuilder) {
if (!CONFIG.client().worldGenerator().getEnableDistantGeneration())
return;
// TODO: This currently doesn't use the
// DetailDistanceUtil.getDistanceGenerationMode(int detail) to get the mode.
// This is fine currently since DistanceGenerationMode doesn't care about the
// detail level for now.
// However, If that was to be changed, This will need to be fixed.
DistanceGenerationMode mode = CONFIG.client().worldGenerator().getDistanceGenerationMode();
final GenerationPriority priority;
if (CONFIG.client().worldGenerator().getGenerationPriority() == GenerationPriority.AUTO)
priority = MC.hasSinglePlayerServer() ? GenerationPriority.FAR_FIRST : GenerationPriority.NEAR_FIRST;
else
priority = CONFIG.client().worldGenerator().getGenerationPriority();
if (mode != DistanceGenerationMode.NONE && !generatorThreadRunning && MC.hasSinglePlayerServer()) {
// the thread is now running, don't queue up another thread
generatorThreadRunning = true;
/**
* How many chunks to generate outside the player's view distance at one time.
* (or more specifically how many requests to make at one time). I multiply by 8
* to make sure there is always a buffer of chunk requests, to make sure the CPU
* is always busy, and we can generate LODs as quickly as possible.
*/
int genRequestPerThread = VERSION_CONSTANTS.getWorldGenerationCountPerThread();
int maxChunkGenRequests;
if (VERSION_CONSTANTS.isWorldGeneratorSingleThreaded(mode))
maxChunkGenRequests = CONFIG.client().advanced().threading().getNumberOfWorldGenerationThreads()
* genRequestPerThread;
else
maxChunkGenRequests = genRequestPerThread;
Runnable generatorFunc = (() -> {
try {
// round the player's block position down to the nearest chunk BlockPos
int playerPosX = MC.getPlayerBlockPos().getX();
int playerPosZ = MC.getPlayerBlockPos().getZ();
// =======================================//
// fill in positionsWaitingToBeGenerated //
// =======================================//
IWorldWrapper serverWorld = LodUtil.getServerWorldFromDimension(lodDim.dimension);
PosToGenerateContainer posToGenerate = lodDim.getPosToGenerate(maxChunkGenRequests, playerPosX,
playerPosZ, priority, mode);
byte detailLevel;
int posX;
int posZ;
int nearIndex = 0;
int farIndex = 0;
for (int i = 0; i < posToGenerate.getNumberOfPos(); i++) {
// I wish there was a way to compress this code, but I'm not aware of
// an easy way to do so.
// add the near positions
if (nearIndex < posToGenerate.getNumberOfNearPos()
&& posToGenerate.getNthDetail(nearIndex, true) != 0) {
detailLevel = (byte) (posToGenerate.getNthDetail(nearIndex, true) - 1);
posX = posToGenerate.getNthPosX(nearIndex, true);
posZ = posToGenerate.getNthPosZ(nearIndex, true);
nearIndex++;
AbstractChunkPosWrapper chunkPos = WRAPPER_FACTORY.createChunkPos(
LevelPosUtil.getChunkPos(detailLevel, posX),
LevelPosUtil.getChunkPos(detailLevel, posZ));
// prevent generating the same chunk multiple times
if (positionsWaitingToBeGenerated.contains(chunkPos))
continue;
// don't add more to the generation queue then allowed
if (numberOfChunksWaitingToGenerate.get() >= maxChunkGenRequests)
break;
positionsWaitingToBeGenerated.add(chunkPos);
numberOfChunksWaitingToGenerate.addAndGet(1);
queueWork(chunkPos, mode, lodBuilder, lodDim, serverWorld);
}
// add the far positions
// But if priority is NEAR_FIRST, we only do that if near pos has ran out.
if ((nearIndex >= posToGenerate.getNumberOfNearPos()
|| priority != GenerationPriority.NEAR_FIRST)
&& farIndex < posToGenerate.getNumberOfFarPos()
&& posToGenerate.getNthDetail(farIndex, false) != 0) {
detailLevel = (byte) (posToGenerate.getNthDetail(farIndex, false) - 1);
posX = posToGenerate.getNthPosX(farIndex, false);
posZ = posToGenerate.getNthPosZ(farIndex, false);
farIndex++;
AbstractChunkPosWrapper chunkPos = WRAPPER_FACTORY.createChunkPos(
LevelPosUtil.getChunkPos(detailLevel, posX),
LevelPosUtil.getChunkPos(detailLevel, posZ));
// don't add more to the generation queue then allowed
if (numberOfChunksWaitingToGenerate.get() >= maxChunkGenRequests)
continue;
// break;
// prevent generating the same chunk multiple times
if (positionsWaitingToBeGenerated.contains(chunkPos))
continue;
positionsWaitingToBeGenerated.add(chunkPos);
numberOfChunksWaitingToGenerate.addAndGet(1);
queueWork(chunkPos, mode, lodBuilder, lodDim, serverWorld);
}
}
} catch (RuntimeException e) {
// this shouldn't ever happen, but just in case
e.printStackTrace();
} finally {
generatorThreadRunning = false;
}
});
if (VERSION_CONSTANTS.isWorldGeneratorSingleThreaded(mode)) {
generatorFunc.run();
} else {
mainGenThread.execute(generatorFunc);
}
} // if distanceGenerationMode != DistanceGenerationMode.NONE &&
// !generatorThreadRunning
} // queueGenerationRequests
private void queueWork(AbstractChunkPosWrapper newPos, DistanceGenerationMode newGenerationMode,
LodBuilder newLodBuilder, LodDimension newLodDimension, IWorldWrapper serverWorld) {
// just a few sanity checks
if (newPos == null)
throw new IllegalArgumentException("LodChunkGenWorker must have a non-null ChunkPos");
if (newLodBuilder == null)
throw new IllegalArgumentException("LodChunkGenThread requires a non-null LodChunkBuilder");
if (newLodDimension == null)
throw new IllegalArgumentException("LodChunkGenThread requires a non-null LodDimension");
if (serverWorld == null)
throw new IllegalArgumentException("LodChunkGenThread requires a non-null ServerWorld");
Runnable method = (() -> {
generateChunk(newPos, newGenerationMode, newLodBuilder, newLodDimension, serverWorld);
});
if (VERSION_CONSTANTS.isWorldGeneratorSingleThreaded(newGenerationMode)) {
// --Note: This is now using version constants--
// if we are using FULL generation there is no reason
// to queue up a bunch of generation requests,
// because MC's internal server (as of 1.16.5) only
// responds with a single thread. And we don't
// want to cause more lag than necessary or queue up
// requests that may end up being unneeded.
// In 1.17+, world generation becomes completely single
// threaded. So to allow that, we check the boolean for
// whether the wrapper requires single thread
method.run();
} else {
// Every other method can
// be done asynchronously
genSubThreads.execute(method);
}
// useful for debugging
// ClientProxy.LOGGER.info(thread.lodDim.getNumberOfLods());
// ClientProxy.LOGGER.info(genThreads.toString());
}
private void generateChunk(AbstractChunkPosWrapper pos, DistanceGenerationMode generationMode,
LodBuilder newLodBuilder, LodDimension lodDim, IWorldWrapper worldWrapper) {
// try
{
AbstractWorldGeneratorWrapper worldGenWrapper = WRAPPER_FACTORY.createWorldGenerator(newLodBuilder, lodDim,
worldWrapper);
// only generate LodChunks if they can
// be added to the current LodDimension
if (lodDim.regionIsInRange(pos.getX() / LodUtil.REGION_WIDTH_IN_CHUNKS,
pos.getZ() / LodUtil.REGION_WIDTH_IN_CHUNKS)) {
switch (generationMode) {
case NONE:
// don't generate
break;
case BIOME_ONLY:
case BIOME_ONLY_SIMULATE_HEIGHT:
// fastest
worldGenWrapper.generateBiomesOnly(pos, generationMode);
break;
case SURFACE:
// faster
worldGenWrapper.generateSurface(pos);
break;
case FEATURES:
// fast
worldGenWrapper.generateFeatures(pos);
break;
case FULL:
// very slow
worldGenWrapper.generateFull(pos);
break;
}
// boolean dataExistence = lodDim.doesDataExist(new LevelPos((byte) 3, pos.x, pos.z));
// if (dataExistence)
// ClientProxy.LOGGER.info(pos.x + " " + pos.z + " Success!");
// else
// ClientProxy.LOGGER.info(pos.x + " " + pos.z);
// shows the pool size, active threads, queued tasks and completed tasks
// ClientProxy.LOGGER.info(genThreads.toString());
} // if in range
}
// catch (Exception e)
// {
// ApiShared.LOGGER.error(LodWorldGenerator.class.getSimpleName() + ": ran into
// an error: " + e.getMessage());
// e.printStackTrace();
// }
// finally
{
// decrement how many threads are running
LodWorldGenerator.INSTANCE.numberOfChunksWaitingToGenerate.addAndGet(-1);
// this position is no longer being generated
LodWorldGenerator.INSTANCE.positionsWaitingToBeGenerated.remove(pos);
}
}// run
/**
* Stops the current genThreads if they are running and then recreates the
* Executor service. <br>
* <br>
* <p>
* This is done to clear any outstanding tasks that may exist after the player
* leaves their current world. If this isn't done unfinished tasks may be left
* in the queue preventing new LodChunks form being generated.
*/
public void restartExecutorService() {
if (genSubThreads != null && !genSubThreads.isShutdown()) {
ApiShared.LOGGER.info("Blocking until generator sub threads terminated!!");
try {
mainGenThread.shutdownNow();
genSubThreads.shutdownNow();
boolean worked = genSubThreads.awaitTermination(30, TimeUnit.SECONDS);
if (!worked)
ApiShared.LOGGER.error(
"Generator sub threads timed out! May cause crash on game exit due to cleanup failure.");
} catch (InterruptedException e) {
ApiShared.LOGGER.error(
"Generator sub threads shutdown is interrupted! May cause crash on game exit due to cleanup failure.");
e.printStackTrace();
} finally {
genSubThreads = Executors.newFixedThreadPool(
CONFIG.client().advanced().threading().getNumberOfWorldGenerationThreads(),
new ThreadFactoryBuilder().setNameFormat("Gen-Worker-Thread-%d").build());
}
}
// Doing this instead of setting it to 0 because even if shutdown fail, it won't
// cause the int to underflow below 0 afterwards
numberOfChunksWaitingToGenerate = new AtomicInteger(0);
}
}
@@ -1,5 +1,6 @@
package com.seibel.lod.core.dataFormat;
@Deprecated //Unused
public class DataMergeUtil
{
public static void shrinkArray(short[] array, int packetSize, int start, int length, int arraySize)
@@ -0,0 +1,7 @@
package com.seibel.lod.core.objects;
public final class BoolType {
public static final BoolType TRUE = new BoolType();
public static final BoolType FALSE = null;
private BoolType() {}
}
@@ -0,0 +1,10 @@
package com.seibel.lod.core.objects;
public final class Pos2D {
public final int x;
public final int y;
public Pos2D(int x, int y) {
this.x = x;
this.y = y;
}
}
@@ -28,10 +28,10 @@ import com.seibel.lod.core.enums.config.VerticalQuality;
import com.seibel.lod.core.handlers.LodDimensionFileHandler;
import com.seibel.lod.core.handlers.LodDimensionFileHelper;
import com.seibel.lod.core.handlers.dependencyInjection.SingletonHandler;
import com.seibel.lod.core.objects.Pos2D;
import com.seibel.lod.core.objects.PosToGenerateContainer;
import com.seibel.lod.core.util.*;
import com.seibel.lod.core.util.MovableGridRingList.Pos;
import com.seibel.lod.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.lod.core.util.gridList.MovableGridRingList;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.lod.core.wrapperInterfaces.world.IDimensionTypeWrapper;
@@ -62,7 +62,6 @@ public class LodDimension
{
private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class);
private static final IMinecraftClientWrapper MC = SingletonHandler.get(IMinecraftClientWrapper.class);
private static final IWrapperFactory FACTORY = SingletonHandler.get(IWrapperFactory.class);
public final IDimensionTypeWrapper dimension;
@@ -164,21 +163,18 @@ public class LodDimension
private void generateIteratorList()
{
iteratorList = null;
RegionPos[] list = new RegionPos[width * width];
RegionPos[] list = new RegionPos[width*width];
int i = 0;
for (int ix = -halfWidth; ix <= halfWidth; ix++)
{
for (int iz = -halfWidth; iz <= halfWidth; iz++)
{
for (int ix=-halfWidth; ix<=halfWidth; ix++) {
for (int iz=-halfWidth; iz<=halfWidth; iz++) {
list[i] = new RegionPos(ix, iz);
i++;
}
}
Arrays.sort(list, (a, b) ->
{
double disSqrA = a.x * a.x + a.z * a.z;
double disSqrB = b.x * b.x + b.z * b.z;
Arrays.sort(list, (a, b) -> {
double disSqrA = a.x* a.x+ a.z* a.z;
double disSqrB = b.x* b.x+ b.z* b.z;
return Double.compare(disSqrA, disSqrB);
});
iteratorList = list;
@@ -195,11 +191,11 @@ public class LodDimension
*/
public synchronized void move(RegionPos regionOffset)
{
ApiShared.LOGGER.info("LodDim MOVE. Offset: " + regionOffset);
ApiShared.LOGGER.info("LodDim MOVE. Offset: "+regionOffset);
saveDirtyRegionsToFile(false); //async add dirty regions to be saved.
Pos p = regions.getCenter();
regions.move(p.x + regionOffset.x, p.y + regionOffset.z);
ApiShared.LOGGER.info("LodDim MOVE complete. Offset: " + regionOffset);
Pos2D p = regions.getCenter();
regions.move(p.x+regionOffset.x, p.y+regionOffset.z);
ApiShared.LOGGER.info("LodDim MOVE complete. Offset: "+regionOffset);
}
@@ -237,8 +233,8 @@ public class LodDimension
@Deprecated
public LodRegion getRegionByArrayIndex(int xIndex, int zIndex)
{
Pos p = regions.getMinInRange();
return regions.get(p.x + xIndex, p.y + zIndex);
Pos2D p = regions.getMinInRange();
return regions.get(p.x+xIndex, p.y+zIndex);
}
/**
@@ -253,45 +249,36 @@ public class LodDimension
regions[xIndex][zIndex] = newRegion;
}*/
public interface PosComsumer
{
public interface PosComsumer {
void run(int x, int z);
}
public void iterateWithSpiral(PosComsumer r)
{
int ox, oy, dx, dy;
ox = oy = dx = 0;
dy = -1;
int len = regions.getSize();
int maxI = len * len;
int halfLen = len / 2;
for (int i = 0; i < maxI; i++)
{
if ((-halfLen <= ox) && (ox <= halfLen) && (-halfLen <= oy) && (oy <= halfLen))
{
int x = ox + halfLen;
int z = oy + halfLen;
r.run(x, z);
}
if ((ox == oy) || ((ox < 0) && (ox == -oy)) || ((ox > 0) && (ox == 1 - oy)))
{
int temp = dx;
dx = -dy;
dy = temp;
}
ox += dx;
oy += dy;
}
public void iterateWithSpiral(PosComsumer r) {
int ox,oy,dx,dy;
ox = oy = dx = 0;
dy = -1;
int len = regions.getSize();
int maxI = len*len;
int halfLen = len/2;
for(int i =0; i < maxI; i++){
if ((-halfLen <= ox) && (ox <= halfLen) && (-halfLen <= oy) && (oy <= halfLen)){
int x = ox+halfLen;
int z = oy+halfLen;
r.run(x, z);
}
if( (ox == oy) || ((ox < 0) && (ox == -oy)) || ((ox > 0) && (ox == 1-oy))){
int temp = dx;
dx = -dy;
dy = temp;
}
ox += dx;
oy += dy;
}
}
public void iterateByDistance(PosComsumer r)
{
if (iteratorList == null)
return;
for (RegionPos relativePos : iteratorList)
{
r.run(relativePos.x + halfWidth, relativePos.z + halfWidth);
public void iterateByDistance(PosComsumer r) {
if (iteratorList==null) return;
for (RegionPos relativePos : iteratorList) {
r.run(relativePos.x+halfWidth, relativePos.z+halfWidth);
}
}
@@ -305,49 +292,40 @@ public class LodDimension
public void cutRegionNodesAsync(int playerPosX, int playerPosZ)
{
if (isCutting)
return;
if (isCutting) return;
isCutting = true;
// don't run the tree cutter multiple times
// for the same location
Runnable thread = () ->
{
Runnable thread = () -> {
//ApiShared.LOGGER.info("LodDim cut Region: " + playerPosX + "," + playerPosZ);
totalDirtiedRegions = 0;
Pos minPos = regions.getMinInRange();
Pos2D minPos = regions.getMinInRange();
// go over every region in the dimension
iterateWithSpiral((int x, int z) ->
{
iterateWithSpiral((int x, int z) -> {
double minDistance;
byte detail;
LodRegion region = regions.get(x + minPos.x, z + minPos.y);
if (region != null && region.needSaving)
totalDirtiedRegions++;
if (region != null && !region.needSaving && region.isWriting.get() == 0)
{
LodRegion region = regions.get(x+minPos.x, z+minPos.y);
if (region != null && region.needSaving) totalDirtiedRegions++;
if (region != null && !region.needSaving && region.isWriting.get()==0) {
// check what detail level this region should be
// and cut it if it is higher then that
minDistance = LevelPosUtil.minDistance(LodUtil.REGION_DETAIL_LEVEL, x + minPos.x, z + minPos.y,
minDistance = LevelPosUtil.minDistance(LodUtil.REGION_DETAIL_LEVEL, x+minPos.x, z+minPos.y,
playerPosX, playerPosZ);
detail = DetailDistanceUtil.getDetailLevelFromDistance(minDistance);
if (region.getMinDetailLevel() < detail)
{
if (region.needSaving)
return; // FIXME: A crude attempt at lowering chance of race condition!
if (region.getMinDetailLevel() < detail) {
if (region.needSaving) return; // FIXME: A crude attempt at lowering chance of race condition!
region.cutTree(detail);
region.needSignalToRegenBuffer = true;
}
}
if (region != null && region.needSignalToRegenBuffer)
{
if (region != null && region.needSignalToRegenBuffer) {
region.needSignalToRegenBuffer = false;
ClientApi.lodBufferBuilderFactory.setRegionNeedRegen(x + minPos.x, z + minPos.y);
ClientApi.lodBufferBuilderFactory.setRegionNeedRegen(x+minPos.x, z+minPos.y);
}
});
if (totalDirtiedRegions > 8)
this.saveDirtyRegionsToFile(false);
if (totalDirtiedRegions > 8) this.saveDirtyRegionsToFile(false);
dirtiedRegionsRoughCount = totalDirtiedRegions;
//ApiShared.LOGGER.info("LodDim cut Region complete: " + playerPosX + "," + playerPosZ);
isCutting = false;
@@ -356,20 +334,15 @@ public class LodDimension
};
cutAndExpandThread.execute(thread);
}
private boolean expandOrLoadPaused = false;
/** Either expands or loads all regions in the rendered LOD area */
public void expandOrLoadRegionsAsync(int playerPosX, int playerPosZ)
{
if (isExpanding)
return;
public void expandOrLoadRegionsAsync(int playerPosX, int playerPosZ) {
if (isExpanding) return;
// If we have less than 20% or 128MB ram left. Don't expend.
if (expandOrLoadPaused)
{
if (LodUtil.checkRamUsage(0.2, 128))
{
if (expandOrLoadPaused) {
if (LodUtil.checkRamUsage(0.2, 128)) {
ApiShared.LOGGER.info("Enough ram for expandOrLoadThread. Restarting...");
expandOrLoadPaused = false;
}
@@ -379,21 +352,18 @@ public class LodDimension
VerticalQuality verticalQuality = CONFIG.client().graphics().quality().getVerticalQuality();
DropoffQuality dropoffQuality = CONFIG.client().graphics().quality().getDropoffQuality();
if (dropoffQuality == DropoffQuality.AUTO)
dropoffQuality = CONFIG.client().graphics().quality().getLodChunkRenderDistance() < 128 ? DropoffQuality.SMOOTH_DROPOFF : DropoffQuality.PERFORMANCE_FOCUSED;
dropoffQuality = CONFIG.client().graphics().quality().getLodChunkRenderDistance() < 128 ?
DropoffQuality.SMOOTH_DROPOFF : DropoffQuality.PERFORMANCE_FOCUSED;
int dropoffSwitch = dropoffQuality.fastModeSwitch;
// don't run the expander multiple times
// for the same location
Runnable thread = () ->
{
Runnable thread = () -> {
//ApiShared.LOGGER.info("LodDim expend Region: " + playerPosX + "," + playerPosZ);
Pos minPos = regions.getMinInRange();
iterateWithSpiral((int x, int z) ->
{
if (!expandOrLoadPaused && !LodUtil.checkRamUsage(0.02, 64))
{
Pos2D minPos = regions.getMinInRange();
iterateWithSpiral((int x, int z) -> {
if (!expandOrLoadPaused && !LodUtil.checkRamUsage(0.02, 64)) {
Runtime.getRuntime().gc();
if (!LodUtil.checkRamUsage(0.2, 128))
{
if (!LodUtil.checkRamUsage(0.2, 128)) {
ApiShared.LOGGER.warn("Not enough ram for expandOrLoadThread. Pausing until Ram is freed...");
// We have less than 10% or 64MB ram left. Don't expend.
expandOrLoadPaused = true;
@@ -412,21 +382,19 @@ public class LodDimension
regionZ = z + minPos.y;
final RegionPos regionPos = new RegionPos(regionX, regionZ);
region = regions.get(regionX, regionZ);
if (region != null && region.isWriting.get() != 0)
return; // FIXME: A crude attempt at lowering chance of race condition!
if (region != null && region.isWriting.get()!=0) return; // FIXME: A crude attempt at lowering chance of race condition!
minDistance = LevelPosUtil.minDistance(LodUtil.REGION_DETAIL_LEVEL, regionX, regionZ, playerPosX,
playerPosZ);
maxDistance = LevelPosUtil.maxDistance(LodUtil.REGION_DETAIL_LEVEL, regionX, regionZ, playerPosX,
playerPosZ);
{
double debugRPosX = LevelPosUtil.convert(LodUtil.REGION_DETAIL_LEVEL, regionX, (byte) 0) + LodUtil.REGION_WIDTH / 2;
double debugRPosZ = LevelPosUtil.convert(LodUtil.REGION_DETAIL_LEVEL, regionZ, (byte) 0) + LodUtil.REGION_WIDTH / 2;
double debugRPosX = LevelPosUtil.convert(LodUtil.REGION_DETAIL_LEVEL, regionX, (byte) 0) + LodUtil.REGION_WIDTH/2;
double debugRPosZ = LevelPosUtil.convert(LodUtil.REGION_DETAIL_LEVEL, regionZ, (byte) 0) + LodUtil.REGION_WIDTH/2;
double deltaRPosX = debugRPosX - playerPosX;
double deltaRPosZ = debugRPosZ - playerPosZ;
double debugDistance = Math.sqrt(deltaRPosX * deltaRPosX + deltaRPosZ * deltaRPosZ);
if (minDistance > debugDistance || maxDistance < debugDistance || minDistance > maxDistance)
{
double debugDistance = Math.sqrt(deltaRPosX*deltaRPosX + deltaRPosZ*deltaRPosZ);
if (minDistance > debugDistance || maxDistance < debugDistance || minDistance > maxDistance) {
ApiShared.LOGGER.error("MinDistance/MaxDistance is WRONG!!! minDist: [{}], maxDist: [{}], centerDist: [{}]\n"
+ "At center block pos: {} {}, region pos: {}",
minDistance, maxDistance, debugDistance, debugRPosX, debugRPosZ, regionPos);
@@ -436,50 +404,39 @@ public class LodDimension
minDetail = DetailDistanceUtil.getDetailLevelFromDistance(minDistance);
maxDetail = DetailDistanceUtil.getDetailLevelFromDistance(maxDistance);
boolean updated = false;
if (region == null)
{
if ((!expandOrLoadPaused))
{
if (region == null) {
if ((!expandOrLoadPaused)) {
region = getRegionFromFile(regionPos, minDetail, verticalQuality);
regions.set(regionX, regionZ, region);
updated = true;
}
}
else if (region.getVerticalQuality() != verticalQuality ||
region.getMinDetailLevel() > minDetail)
{
} else if (region.getVerticalQuality() != verticalQuality ||
region.getMinDetailLevel() > minDetail) {
// The 'getRegionFromFile' will flush and save the region if it returns a new one
if ((!expandOrLoadPaused))
{
if ((!expandOrLoadPaused)) {
region = getRegionFromFile(region, minDetail, verticalQuality);
regions.set(regionX, regionZ, region);
updated = true;
}
}
else if (minDetail <= dropoffSwitch && region.lastMaxDetailLevel != maxDetail)
{
} else if (minDetail <= dropoffSwitch && region.lastMaxDetailLevel != maxDetail) {
region.lastMaxDetailLevel = maxDetail;
updated = true;
}
else if (minDetail <= dropoffSwitch && region.lastMaxDetailLevel != region.getMinDetailLevel())
{
} else if (minDetail <= dropoffSwitch && region.lastMaxDetailLevel != region.getMinDetailLevel()) {
updated = true;
}
if (updated)
{
if (updated) {
region.needSignalToRegenBuffer = true;
region.needRecheckGenPoint = true;
}
if (region != null && region.needSignalToRegenBuffer)
{
if (region != null && region.needSignalToRegenBuffer) {
region.needSignalToRegenBuffer = false;
ClientApi.lodBufferBuilderFactory.setRegionNeedRegen(x + minPos.x, z + minPos.y);
ClientApi.lodBufferBuilderFactory.setRegionNeedRegen(x+minPos.x, z+minPos.y);
}
});
//ApiShared.LOGGER.info("LodDim expend Region complete: " + playerPosX + "," + playerPosZ);
isExpanding = false;
};
cutAndExpandThread.execute(thread);
}
@@ -514,39 +471,34 @@ public class LodDimension
// This ensures that we don't spawn way too much regions without finish flushing them first.
//if (dirtiedRegionsRoughCount > 16) return posToGenerate;
GenerationPriority allowedPriority = dirtiedRegionsRoughCount > 12 ? GenerationPriority.NEAR_FIRST : priority;
Pos minPos = regions.getMinInRange();
iterateByDistance((int x, int z) ->
{
boolean isCloseRange = (Math.abs(x - halfWidth) + Math.abs(z - halfWidth) <= 2);
GenerationPriority allowedPriority = dirtiedRegionsRoughCount>12 ? GenerationPriority.NEAR_FIRST : priority;
Pos2D minPos = regions.getMinInRange();
iterateByDistance((int x, int z) -> {
boolean isCloseRange = (Math.abs(x-halfWidth)+Math.abs(z-halfWidth)<=2);
//boolean isCloseRange = true;
//All of this is handled directly by the region, which scan every pos from top to bottom of the quad tree
LodRegion lodRegion = regions.get(minPos.x + x, minPos.y + z);
LodRegion lodRegion = regions.get(minPos.x+x, minPos.y+z);
if (lodRegion != null && lodRegion.needRecheckGenPoint)
{
if (lodRegion != null && lodRegion.needRecheckGenPoint) {
int nearCount = posToGenerate.getNumberOfNearPos();
int farCount = posToGenerate.getNumberOfFarPos();
boolean checkForFlag = (nearCount < posToGenerate.getMaxNumberOfNearPos() && farCount < posToGenerate.getMaxNumberOfFarPos());
if (checkForFlag)
{
if (checkForFlag) {
lodRegion.needRecheckGenPoint = false;
}
lodRegion.getPosToGenerate(posToGenerate, playerBlockPosX, playerBlockPosZ, allowedPriority, genMode,
isCloseRange);
if (checkForFlag)
{
if (nearCount != posToGenerate.getNumberOfNearPos() || farCount != posToGenerate.getNumberOfFarPos())
{
if (checkForFlag) {
if (nearCount != posToGenerate.getNumberOfNearPos() || farCount != posToGenerate.getNumberOfFarPos()) {
lodRegion.needRecheckGenPoint = true;
}
}
}
});
return posToGenerate;
return posToGenerate;
}
/**
* Determines how many vertical LODs could be used
* for the given region at the given detail level
@@ -632,12 +584,11 @@ public class LodDimension
{
if (detailLevel > LodUtil.REGION_DETAIL_LEVEL)
throw new IllegalArgumentException("getLodFromCoordinates given a level of \"" + detailLevel + "\" when \"" + LodUtil.REGION_DETAIL_LEVEL + "\" is the max.");
int xRegion = LevelPosUtil.getRegion(detailLevel, posX);
int zRegion = LevelPosUtil.getRegion(detailLevel, posZ);
LodRegion region = getRegion(xRegion, zRegion);
if (region == null)
return;
if (region == null) return;
region.updateArea(detailLevel, posX, posZ);
}
@@ -654,23 +605,23 @@ public class LodDimension
*/
public LodRegion getRegionFromFile(RegionPos regionPos, byte detailLevel, VerticalQuality verticalQuality)
{
return fileHandler != null ? fileHandler.loadRegionFromFile(detailLevel, regionPos, verticalQuality) : new LodRegion(detailLevel, regionPos, verticalQuality);
return fileHandler != null ? fileHandler.loadRegionFromFile(detailLevel, regionPos, verticalQuality) :
new LodRegion(detailLevel, regionPos, verticalQuality);
}
/**
* Loads the region at the given region from file,
* if a file exists for that region.
*/
public LodRegion getRegionFromFile(LodRegion existingRegion, byte detailLevel, VerticalQuality verticalQuality)
{
return fileHandler != null ? fileHandler.loadRegionFromFile(detailLevel, existingRegion, verticalQuality) : new LodRegion(detailLevel, existingRegion.getRegionPos(), verticalQuality);
return fileHandler != null ? fileHandler.loadRegionFromFile(detailLevel, existingRegion, verticalQuality) :
new LodRegion(detailLevel, existingRegion.getRegionPos(), verticalQuality);
}
/** Save all dirty regions in this LodDimension to file. */
public void saveDirtyRegionsToFile(boolean blockUntilFinished)
{
if (fileHandler == null)
return;
if (fileHandler == null) return;
fileHandler.saveDirtyRegionsToFile(blockUntilFinished);
}
@@ -709,9 +660,8 @@ public class LodDimension
return regions.getCenter().y;
}
public RegionPos getCenterRegionPos()
{
Pos p = regions.getCenter();
public RegionPos getCenterRegionPos() {
Pos2D p = regions.getCenter();
return new RegionPos(p.x, p.y);
}
@@ -728,19 +678,17 @@ public class LodDimension
public void setRegionWidth(int newWidth)
{
width = newWidth;
halfWidth = width / 2;
Pos p = regions.getCenter();
halfWidth = width/ 2;
Pos2D p = regions.getCenter();
regions = new MovableGridRingList<LodRegion>(halfWidth, p.x, p.y);
generateIteratorList();
}
private final SpamReducedLogger ramLogger = new SpamReducedLogger(1);
public void dumpRamUsage()
{
if (!ramLogger.canMaybeLog())
return;
int regionCount = width * width;
if (!ramLogger.canMaybeLog()) return;
int regionCount = width*width;
ramLogger.info("Dumping Ram Usage for LodDim in {} with {} regions...", dimension.getDimensionName(), regionCount);
int nonNullRegionCount = 0;
int dirtiedRegionCount = 0;
@@ -748,25 +696,18 @@ public class LodDimension
long totalUsage = 0;
int[] detailCount = new int[LodUtil.DETAIL_OPTIONS];
long[] detailUsage = new long[LodUtil.DETAIL_OPTIONS];
for (LodRegion r : regions)
{
if (r == null)
continue;
for (LodRegion r : regions) {
if (r==null) continue;
nonNullRegionCount++;
if (r.needSaving)
dirtiedRegionCount++;
if (r.isWriting.get() != 0)
writingRegionCount++;
if (r.needSaving) dirtiedRegionCount++;
if (r.isWriting.get() != 0) writingRegionCount++;
LevelContainer[] container = r.debugGetDataContainers().clone();
if (container == null || container.length != LodUtil.DETAIL_OPTIONS)
{
if (container == null || container.length != LodUtil.DETAIL_OPTIONS) {
ApiShared.LOGGER.warn("DumpRamUsage encountered an invalid region!");
continue;
}
for (int i = 0; i < LodUtil.DETAIL_OPTIONS; i++)
{
if (container[i] == null)
continue;
for (int i = 0; i < LodUtil.DETAIL_OPTIONS; i++) {
if (container[i] == null) continue;
detailCount[i]++;
long byteUsage = container[i].getRoughRamUsage();
detailUsage[i] += byteUsage;
@@ -777,8 +718,7 @@ public class LodDimension
ramLogger.info("Non Null Regions: [{}], Dirtied Regions: [{}], Writing Regions: [{}], Bytes: [{}]",
nonNullRegionCount, dirtiedRegionCount, writingRegionCount, new UnitBytes(totalUsage));
ramLogger.info("------------------------------------------------");
for (int i = 0; i < LodUtil.DETAIL_OPTIONS; i++)
{
for (int i = 0; i < LodUtil.DETAIL_OPTIONS; i++) {
ramLogger.info("DETAIL {}: Containers: [{}], Bytes: [{}]", i, detailCount[i], new UnitBytes(detailUsage[i]));
}
ramLogger.info("================================================");
@@ -789,7 +729,7 @@ public class LodDimension
@Override
public String toString()
{
return "[Dim = " + dimension.getDimensionName() + ", Region = " + regions + "]";
return "[Dim = "+dimension.getDimensionName()+", Region = "+regions+"]";
}
public String toDetailString()
@@ -799,18 +739,14 @@ public class LodDimension
stringBuilder.append(regions.toDetailString());
return stringBuilder.toString();
}
public void shutdown()
{
public void shutdown() {
cutAndExpandThread.shutdown();
try
{
try {
boolean worked = cutAndExpandThread.awaitTermination(5, TimeUnit.SECONDS);
if (!worked)
ApiShared.LOGGER.error("Cut And Expend threads timed out! May cause crash on game exit due to cleanup failure.");
}
catch (InterruptedException e)
{
} catch (InterruptedException e) {
ApiShared.LOGGER.error("Cut And Expend threads shutdown is interrupted! May cause crash on game exit due to cleanup failure: ", e);
}
@@ -91,8 +91,7 @@ public class VerticalLevelContainer implements LevelContainer
private void forceWriteVerticalData(long[] data, int posX, int posZ)
{
int index = posX * size * verticalSize + posZ * verticalSize;
for (int verticalIndex = 0; verticalIndex < verticalSize; verticalIndex++)
dataContainer[index + verticalIndex] = data[verticalIndex];
if (verticalSize >= 0) System.arraycopy(data, 0, dataContainer, index + 0, verticalSize);
}
@Override
@@ -21,7 +21,6 @@ import com.seibel.lod.core.objects.PosToRenderContainer;
import com.seibel.lod.core.objects.lod.LodDimension;
import com.seibel.lod.core.objects.lod.LodRegion;
import com.seibel.lod.core.objects.lod.RegionPos;
import com.seibel.lod.core.objects.math.Mat4f;
import com.seibel.lod.core.objects.math.Vec3d;
import com.seibel.lod.core.objects.math.Vec3f;
import com.seibel.lod.core.render.GLProxy;
@@ -31,7 +30,7 @@ import com.seibel.lod.core.util.DataPointUtil;
import com.seibel.lod.core.util.DetailDistanceUtil;
import com.seibel.lod.core.util.LevelPosUtil;
import com.seibel.lod.core.util.LodUtil;
import com.seibel.lod.core.util.MovableGridList;
import com.seibel.lod.core.util.gridList.MovableCenteredGridList;
import com.seibel.lod.core.util.StatsMap;
import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
@@ -243,8 +242,8 @@ public class RenderRegion implements AutoCloseable
{ 1, 1}
};
private static MovableGridList<Boolean> shinkGridEdge(MovableGridList<Boolean> target) {
MovableGridList<Boolean> result = new MovableGridList<Boolean>(
private static MovableCenteredGridList<Boolean> shinkGridEdge(MovableCenteredGridList<Boolean> target) {
MovableCenteredGridList<Boolean> result = new MovableCenteredGridList<Boolean>(
target.gridCentreToEdge-1, target.getCenterX(), target.getCenterY());
int chunkGridMinX = target.getCenterX() - target.gridCentreToEdge;
int chunkGridMinZ = target.getCenterY() - target.gridCentreToEdge;
@@ -274,7 +273,7 @@ public class RenderRegion implements AutoCloseable
// position
PosToRenderContainer posToRender = new PosToRenderContainer(minDetail, region.regionPosX, region.regionPosZ);
region.getPosToRender(posToRender, playerX, playerZ);
MovableGridList<Boolean> chunkGrid = ClientApi.renderer.vanillaRenderedChunks;
MovableCenteredGridList<Boolean> chunkGrid = ClientApi.renderer.vanillaRenderedChunks;
if (CONFIG.client().graphics().advancedGraphics().getVanillaOverdraw() == VanillaOverdraw.BORDER) {
chunkGrid = shinkGridEdge(chunkGrid);
}
@@ -24,7 +24,10 @@ import java.time.Duration;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import com.seibel.lod.core.objects.Pos2D;
import com.seibel.lod.core.util.*;
import com.seibel.lod.core.util.gridList.MovableCenteredGridList;
import com.seibel.lod.core.util.gridList.MovableGridRingList;
import org.lwjgl.opengl.GL32;
import com.seibel.lod.core.api.ApiShared;
@@ -32,7 +35,6 @@ import com.seibel.lod.core.builders.bufferBuilding.LodBufferBuilderFactory;
import com.seibel.lod.core.enums.rendering.DebugMode;
import com.seibel.lod.core.enums.rendering.FogColorMode;
import com.seibel.lod.core.enums.rendering.FogDistance;
import com.seibel.lod.core.handlers.IReflectionHandler;
import com.seibel.lod.core.handlers.dependencyInjection.SingletonHandler;
import com.seibel.lod.core.objects.lod.LodDimension;
import com.seibel.lod.core.objects.math.Mat4f;
@@ -119,7 +121,7 @@ public class LodRenderer
* This HashSet contains every chunk that Vanilla Minecraft
* is going to render
*/
public MovableGridList<Boolean> vanillaRenderedChunks;
public MovableCenteredGridList<Boolean> vanillaRenderedChunks;
public int vanillaRenderedChunksCenterX;
public int vanillaRenderedChunksCenterZ;
public int vanillaRenderedChunksRefreshTimer;
@@ -354,7 +356,7 @@ public class LodRenderer
int halfLen = len/2;
for(int i =0; i < maxI; i++){
if ((-halfLen <= ox) && (ox <= halfLen) && (-halfLen <= oy) && (oy <= halfLen)){
MovableGridRingList.Pos pos = regions.getCenter();
Pos2D pos = regions.getCenter();
int regionX = ox+pos.x;
int regionZ = oy+pos.y;
{
@@ -527,16 +529,16 @@ public class LodRenderer
// if the player is high enough, draw all LODs
IWorldWrapper world = MC.getWrappedClientWorld();
if (lastUpdatedPos.getY() > world.getHeight()-world.getMinHeight()) {
vanillaRenderedChunks = new MovableGridList<Boolean>(
vanillaRenderedChunks = new MovableCenteredGridList<Boolean>(
chunkRenderDistance, chunkX, chunkZ);
return true;
}
MovableGridList<Boolean> chunkList;
MovableCenteredGridList<Boolean> chunkList;
boolean anyChanged = false;
if (vanillaRenderedChunks == null || vanillaRenderedChunks.gridCentreToEdge != chunkRenderDistance ||
vanillaRenderedChunks.getCenterX()!=chunkX || vanillaRenderedChunks.getCenterY()!=chunkZ) {
chunkList = new MovableGridList<Boolean>(chunkRenderDistance, chunkX, chunkZ);
chunkList = new MovableCenteredGridList<Boolean>(chunkRenderDistance, chunkX, chunkZ);
anyChanged = true;
} else {
chunkList = vanillaRenderedChunks;
@@ -1,245 +0,0 @@
package com.seibel.lod.core.util;
/*Layout:
* 0,1,2,
* 3,4,5,
* 6,7,8
*/
public class BooleanMovableGridList {
private int centerX;
private int centerY;
public final int gridCentreToEdge;
public final int gridSize;
private boolean[] b;
public BooleanMovableGridList(int gridCentreToEdge, int centerX, int centerY) {
gridSize = gridCentreToEdge * 2 + 1;
this.gridCentreToEdge = gridCentreToEdge;
this.centerX = centerX;
this.centerY = centerY;
clear();
}
public void clear() {
b = new boolean[gridSize*gridSize];
}
public int getCenterX() {return centerX;}
public int getCenterY() {return centerY;}
private void assertIndex(int ix, int iy) {
if (ix<0 || ix>=gridSize || iy<0 || iy>=gridSize)
throw new IndexOutOfBoundsException("BooleanMovableGridList index position out of bound");
}
public boolean isInBound(int x, int y) {
x = x-centerX+gridCentreToEdge;
y = y-centerY+gridCentreToEdge;
return !(x<0 || x>=gridSize || y<0 || y>=gridSize);
}
// return onFail if x,y is outside of the grid
public boolean get(int x, int y) {
x = x-centerX+gridCentreToEdge;
y = y-centerY+gridCentreToEdge;
return _getDirect(x,y);
}
// return false if x,y is outside of the grid
public void set(int x, int y, boolean t) {
x = x-centerX+gridCentreToEdge;
y = y-centerY+gridCentreToEdge;
_setDirect(x,y,t);
}
// return onFail if x,y is outside of the grid
// Otherwise, return the new value (for chaining)
public boolean setAndGet(int x, int y, boolean t) {
x = x-centerX+gridCentreToEdge;
y = y-centerY+gridCentreToEdge;
_setDirect(x,y,t);
return t;
}
// return null if x,y is outside of the grid
// Otherwise, return the old value
public boolean swap(int x, int y, boolean t, boolean onFail) {
x = x-centerX+gridCentreToEdge;
y = y-centerY+gridCentreToEdge;
return _swapDirect(x,y, t);
}
private boolean _getDirect(int x, int y) {
assertIndex(x,y);
return b[x + y * gridSize];
}
private void _setDirect(int x, int y, boolean t) {
assertIndex(x,y);
b[x + y * gridSize] = t;
}
private boolean _swapDirect(int x, int y, boolean t) {
assertIndex(x,y);
boolean r = b[x + y * gridSize];
b[x + y * gridSize] = t;
return r;
}
interface BoolTransformer {
boolean transform(boolean oldValue, int x, int y);
}
// Transform the list via the function. The data can still be accessed
// inside the function, and the returned value will not be applied
// until all elements have done the transform.
public void twoStageTransform(BoolTransformer transformer) {
boolean[] nb = new boolean[b.length];
int i=0;
for (int y=0; y<gridSize; y++) {
for (int x=0; x<gridSize; x++) {
nb[i] = transformer.transform(b[i],
x+centerX-gridCentreToEdge, y+centerY-gridCentreToEdge);
i++;
}
}
b = nb;
}
public void flipBorder(boolean valueToBeFlipped) {
BoolTransformer tran = (v, x, y) -> {
if (v!= valueToBeFlipped) return v;
boolean r = false;
r |= (isInBound(x - 1, y) && get(x - 1, y) == !valueToBeFlipped);
r |= (isInBound(x, y - 1) && get(x, y - 1) == !valueToBeFlipped);
r |= (isInBound(x + 1, y) && get(x + 1, y) == !valueToBeFlipped);
r |= (isInBound(x, y + 1) && get(x, y + 1) == !valueToBeFlipped);
return r != valueToBeFlipped;
};
twoStageTransform(tran);
}
public void flipBorderCorner(boolean valueToBeFlipped) {
BoolTransformer tran = (v, x, y) -> {
if (v!= valueToBeFlipped) return v;
boolean r = false;
r |= (isInBound(x - 1, y) && get(x - 1, y) == !valueToBeFlipped);
r |= (isInBound(x, y - 1) && get(x, y - 1) == !valueToBeFlipped);
r |= (isInBound(x + 1, y) && get(x + 1, y) == !valueToBeFlipped);
r |= (isInBound(x, y + 1) && get(x, y + 1) == !valueToBeFlipped);
r |= (isInBound(x - 1, y - 1) && get(x - 1, y - 1) == !valueToBeFlipped);
r |= (isInBound(x + 1, y - 1) && get(x + 1, y - 1) == !valueToBeFlipped);
r |= (isInBound(x + 1, y + 1) && get(x + 1, y + 1) == !valueToBeFlipped);
r |= (isInBound(x - 1, y + 1) && get(x - 1, y + 1) == !valueToBeFlipped);
return r != valueToBeFlipped;
};
twoStageTransform(tran);
}
public void flipBorderCorner(boolean valueToBeFlipped, int range) {
BoolTransformer tran = (v, x, y) -> {
if (v!= valueToBeFlipped) return v;
boolean r = false;
for (int dx=-range;dx<=range;dx++)
for (int dy=-range;dy<=range;dy++)
r |= (isInBound(x + dx, y + dy) && get(x + dx, y + dy) == !valueToBeFlipped);
return r != valueToBeFlipped;
};
twoStageTransform(tran);
}
// Return false if haven't changed. Return true if it did
public boolean move(int newCenterX, int newCenterY) {
return move(newCenterX, newCenterY, false);
}
// Return false if haven't changed. Return true if it did
public boolean move(int newCenterX, int newCenterY, boolean value) {
if (centerX == newCenterX && centerY == newCenterY) return false;
int deltaX = newCenterX - centerX;
int deltaY = newCenterY - centerY;
// if the x or z offset is equal to or greater than
// the total width, just delete the current data
// and update the centerX and/or centerZ
if (Math.abs(deltaX) >= gridSize || Math.abs(deltaY) >= gridSize)
{
clear();
// update the new center
centerX = newCenterX;
centerY = newCenterY;
return true;
}
centerX = newCenterX;
centerY = newCenterY;
// X
if (deltaX >= 0 && deltaY >= 0)
{
// move everything over to the left-up (as the center moves to the right-down)
for (int x = 0; x < gridSize; x++)
{
for (int y = 0; y < gridSize; y++)
{
_setDirect(x, y, _getDirect(x+deltaX, y+deltaY));
}
}
}
else if (deltaX < 0 && deltaY >= 0)
{
// move everything over to the right-up (as the center moves to the left-down)
for (int x = gridSize - 1; x >= 0; x--)
{
for (int y = 0; y < gridSize; y++)
{
_setDirect(x, y, _getDirect(x+deltaX, y+deltaY));
}
}
}
else if (deltaX >= 0)// && deltaY < 0)
{
// move everything over to the left-down (as the center moves to the right-up)
for (int x = 0; x < gridSize; x++)
{
for (int y = gridSize - 1; y >= 0; y--)
{
_setDirect(x, y, _getDirect(x+deltaX, y+deltaY));
}
}
}
else //if (deltaX < 0 && deltaY < 0)
{
// move everything over to the right-down (as the center moves to the left-up)
for (int x = gridSize - 1; x >= 0; x--)
{
for (int y = gridSize - 1; y >= 0; y--)
{
_setDirect(x, y, _getDirect(x+deltaX, y+deltaY));
}
}
}
return true;
}
@Override
public String toString() {
return "MovableGridList[" + centerX + "," + centerY + "] " + gridSize + "*" + gridSize + "[" + b.length + "]";
}
public String toDetailString() {
StringBuilder str = new StringBuilder("\n");
int i = 0;
str.append(this);
str.append("\n");
for (boolean t : b) {
str.append(t ? "#" : ".");
i++;
if (i % gridSize == 0) {
str.append("\n");
} //else {
//str.append(", ");
//}
}
return str.toString();
}
}
@@ -239,18 +239,15 @@ public class DataPointUtil
isVoid(dataPoint) + " " +
doesItExist(dataPoint) + '\n';
}
public static void shrinkArray(short[] array, int packetSize, int start, int length, int arraySize)
{
start *= packetSize;
length *= packetSize;
arraySize *= packetSize;
for (int i = 0; i < arraySize - start; i++)
{
array[start + i] = array[start + length + i];
//remove comment to not leave garbage at the end
//array[start + packetSize + i] = 0;
}
//remove comment to not leave garbage at the end
//array[start + packetSize + i] = 0;
if (arraySize - start >= 0) System.arraycopy(array, start + length + 0, array, start + 0, arraySize - start);
}
public static void extendArray(short[] array, int packetSize, int start, int length, int arraySize)
@@ -1,76 +0,0 @@
package com.seibel.lod.core.util;
import java.util.ArrayList;
import java.util.List;
public class GridList<T> extends ArrayList<T> implements List<T> {
public static class Pos {
public int x;
public int y;
public Pos(int xx, int yy) {
x = xx;
y = yy;
}
}
private static final long serialVersionUID = 1585978374811888116L;
public final int gridCentreToEdge;
public final int gridSize;
public GridList(int gridCentreToEdge) {
super((gridCentreToEdge * 2 + 1) * (gridCentreToEdge * 2 + 1));
gridSize = gridCentreToEdge * 2 + 1;
this.gridCentreToEdge = gridCentreToEdge;
}
public final T getOffsetOf(int index, int x, int y) {
return get(index + x + y * gridSize);
}
public final int offsetOf(int index, int x, int y) {
return index + x + y * gridSize;
}
public final Pos posOf(int index) {
return new Pos(index % gridSize, index / gridSize);
}
public final int calculateOffset(int x, int y) {
return x + y * gridSize;
}
public final GridList<T> subGrid(int gridCentreToEdge) {
int centreIndex = size() / 2;
GridList<T> subGrid = new GridList<T>(gridCentreToEdge);
for (int oy = -gridCentreToEdge; oy <= gridCentreToEdge; oy++) {
int begin = offsetOf(centreIndex, -gridCentreToEdge, oy);
int end = offsetOf(centreIndex, gridCentreToEdge, oy);
subGrid.addAll(this.subList(begin, end + 1));
}
// System.out.println("========================================\n"+
// this.toDetailString() + "\nTOOOOOOOOOOOOO\n"+subGrid.toDetailString()+
// "==========================================\n");
return subGrid;
}
@Override
public String toString() {
return "GridList " + gridSize + "*" + gridSize + "[" + size() + "]";
}
public String toDetailString() {
StringBuilder str = new StringBuilder("\n");
int i = 0;
for (T t : this) {
str.append(t.toString());
str.append(", ");
i++;
if (i % gridSize == 0) {
str.append("\n");
}
}
return str.toString();
}
}
@@ -34,6 +34,7 @@ import com.seibel.lod.core.objects.lod.LodDimension;
import com.seibel.lod.core.objects.lod.RegionPos;
import com.seibel.lod.core.objects.opengl.DefaultLodVertexFormats;
import com.seibel.lod.core.objects.opengl.LodVertexFormat;
import com.seibel.lod.core.util.gridList.MovableCenteredGridList;
import com.seibel.lod.core.wrapperInterfaces.IVersionConstants;
import com.seibel.lod.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
@@ -478,7 +479,7 @@ public class LodUtil
}
return false;
}
public static boolean isBorderChunk(MovableGridList<Boolean> vanillaRenderedChunks, int chunkX, int chunkZ)
public static boolean isBorderChunk(MovableCenteredGridList<Boolean> vanillaRenderedChunks, int chunkX, int chunkZ)
{
for (LodDirection lodDirection : LodDirection.ADJ_DIRECTIONS)
{
@@ -32,15 +32,15 @@ public class UnitBytes
long v = value;
StringBuilder str = new StringBuilder();
long GB = byteToGB(v);
if (GB != 0) str.append(GB+ "GB ");
if (GB != 0) str.append(GB).append("GB ");
v -= GBToByte(GB);
long MB = byteToMB(v);
if (MB != 0) str.append(MB+ "MB ");
if (MB != 0) str.append(MB).append("MB ");
v -= MBToByte(MB);
long KB = byteToKB(v);
if (KB != 0) str.append(KB+ "KB ");
if (KB != 0) str.append(KB).append("KB ");
v -= KBToByte(KB);
str.append(v+"B");
str.append(v).append("B");
return str.toString();
}
}
@@ -0,0 +1,114 @@
package com.seibel.lod.core.util.gridList;
import com.seibel.lod.core.objects.Pos2D;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer;
public class ArrayGridList<T> extends ArrayList<T> implements List<T> {
public final int gridSize;
public ArrayGridList(int gridSize, BiFunction<Integer, Integer, T> filler) {
super((gridSize) * (gridSize));
this.gridSize = gridSize;
this.forEachPos((x, y) -> super.add(filler.apply(x,y)));
}
public ArrayGridList(int gridSize) {
this(gridSize, (x,y) -> null);
}
public ArrayGridList(ArrayGridList<T> copy) {
super(copy);
gridSize = copy.gridSize;
}
public ArrayGridList(ArrayGridList<T> from, int minR, int maxR) {
super(maxR-minR);
if (minR > maxR) throw new IndexOutOfBoundsException("minR greater than maxR");
if (minR < 0) throw new IndexOutOfBoundsException("minR less than 0");
if (maxR > from.gridSize) throw new IndexOutOfBoundsException("maxR greater than gridSize");
gridSize = maxR-minR;
for (int oy = minR; oy < maxR; oy++) {
int begin = minR + oy * from.gridSize;
int end = maxR + oy * from.gridSize;
super.addAll(from.subList(begin, end));
}
if (super.size() != gridSize*gridSize) throw new IllegalStateException("subgrid clone failure");
// System.out.println("========================================\n"+
// this.toDetailString() + "\nTOOOOOOOOOOOOO\n"+subGrid.toDetailString()+
// "==========================================\n");
}
protected int _indexOf(int x, int y) {
return x + y * gridSize;
}
public final T get(Pos2D pos) {
return get(pos.x, pos.y);
}
public final T set(Pos2D pos, T e) {
return set(pos.x, pos.y, e);
}
public T get(int x, int y) {
return get(_indexOf(x,y));
}
public T set(int x, int y, T e) {
return set(_indexOf(x, y), e);
}
public final void clear() {
this.clear(null);
}
public final void fill(BiFunction<Integer, Integer, T> filler) {
this.fill(null, filler);
}
public final void clear(Consumer<? super T> dealloc) {
this.fill(dealloc, (x,y) -> null);
}
public final void fill(Consumer<? super T> dealloc,
BiFunction<Integer, Integer, T> filler) {
this.forEachPos((x, y) -> {
T t = this.set(x, y, filler.apply(x, y));
if (t!=null) dealloc.accept(t);
});
}
public void forEachPos(BiConsumer<Integer, Integer> consumer) {
for (int y=0; y<gridSize; y++) {
for (int x=0; x<gridSize; x++) {
consumer.accept(x,y);
}
}
}
@Override
public final void forEach(Consumer<? super T> consumer) {
super.forEach(consumer);
}
@Override
public String toString() {
return getClass().toString() + " " + gridSize + "*" + gridSize + "[" + size() + "]";
}
public String toDetailString() {
StringBuilder str = new StringBuilder("\n");
int i = 0;
str.append(this);
str.append("\n");
for (T t : this) {
str.append(t!=null ? t.toString() : "NULL");
str.append(", ");
i++;
if (i % gridSize == 0) {
str.append("\n");
}
}
return str.toString();
}
}
@@ -0,0 +1,70 @@
package com.seibel.lod.core.util.gridList;
import com.seibel.lod.core.objects.BoolType;
import com.seibel.lod.core.objects.Pos2D;
import java.util.Iterator;
import java.util.function.IntPredicate;
public class EdgeDistanceBooleanGrid extends PosArrayGridList<BoolType> {
ArrayGridList<Integer> edgeCache = null;
public EdgeDistanceBooleanGrid(Iterator<Pos2D> posIter, int offsetX, int offsetY, int gridSize) {
super(gridSize, offsetX, offsetY);
while (posIter.hasNext()) {
Pos2D p = posIter.next();
this.set(p, BoolType.TRUE);
}
}
// Return false if it is indeed updated
private static boolean updatePos(ArrayGridList<Integer> grid, int ox, int oy) {
if (grid.get(ox,oy) < 0) return true;
if (ox==0 || oy==0 || ox==grid.gridSize-1 || oy==grid.gridSize-1) {
return true;
}
int v = grid.get(ox,oy);
if (
grid.get(ox,oy+1)<v ||
grid.get(ox,oy-1)<v ||
grid.get(ox+1, oy)<v ||
grid.get(ox-1, oy)<v
) {
return true;
} else {
grid.set(ox,oy, v+1);
return false;
}
}
//FIXME: This is slow and expensive. Use queue to make this skip recheck done pos
private void computeEdgeCache() {
if (edgeCache != null) return;
edgeCache = new ArrayGridList<Integer>(gridSize, (ox, oy) -> {
BoolType b = get(ox+getOffsetX(), oy+getOffsetY());
return b==null ? -1 : 0;
});
final boolean[] isDone = {false};
while (!isDone[0]) {
isDone[0] = true;
edgeCache.forEachPos( (ox, oy) -> {
isDone[0] &= updatePos(edgeCache, ox, oy);
});
}
}
// 0 means right on the edge, while 1 means 1 ceil away. Uses Manhattan Distance
public <T extends ArrayGridList<BoolType>> void flagAllWithDistance(T list, IntPredicate predicate) {
computeEdgeCache();
edgeCache.forEachPos((ox, oy) -> {
int v = edgeCache.get(ox, oy);
if (v<0 || !predicate.test(v)) return;
list.set(ox + getOffsetX(), oy + getOffsetY(), BoolType.TRUE);
});
}
}
@@ -1,4 +1,4 @@
package com.seibel.lod.core.util;
package com.seibel.lod.core.util.gridList;
import java.util.ArrayList;
import java.util.List;
@@ -10,9 +10,8 @@ import java.util.function.Consumer;
* 6,7,8
*/
public class MovableGridList<T> extends ArrayList<T> implements List<T> {
private static final long serialVersionUID = 5366261085254591277L;
@Deprecated // Replace with PosArrayGridList<T>
public class MovableCenteredGridList<T> extends ArrayList<T> implements List<T> {
private int centerX;
private int centerY;
@@ -23,7 +22,7 @@ public class MovableGridList<T> extends ArrayList<T> implements List<T> {
/*
* WARNING: Not yet tested if its atomic. (non Thread safe)
*/
public MovableGridList(MovableGridList<T> other) {
public MovableCenteredGridList(MovableCenteredGridList<T> other) {
super(other);
centerX = other.centerX;
centerY = other.centerY;
@@ -31,7 +30,7 @@ public class MovableGridList<T> extends ArrayList<T> implements List<T> {
gridSize = other.gridSize;
}
public MovableGridList(int gridCentreToEdge, int centerX, int centerY) {
public MovableCenteredGridList(int gridCentreToEdge, int centerX, int centerY) {
super((gridCentreToEdge * 2 + 1) * (gridCentreToEdge * 2 + 1));
gridSize = gridCentreToEdge * 2 + 1;
this.gridCentreToEdge = gridCentreToEdge;
@@ -1,4 +1,6 @@
package com.seibel.lod.core.util;
package com.seibel.lod.core.util.gridList;
import com.seibel.lod.core.objects.Pos2D;
import java.util.ArrayList;
import java.util.List;
@@ -8,15 +10,7 @@ import java.util.function.Consumer;
public class MovableGridRingList<T> extends ArrayList<T> implements List<T> {
private static final long serialVersionUID = -7743190533384530134L;
public static class Pos {
public final int x;
public final int y;
Pos(int x, int y) {this.x=x; this.y=y;}
}
private AtomicReference<Pos> pos = new AtomicReference<Pos>();
private AtomicReference<Pos2D> pos = new AtomicReference<Pos2D>();
private final int halfSize;
private final int size;
@@ -26,7 +20,7 @@ public class MovableGridRingList<T> extends ArrayList<T> implements List<T> {
super((halfSize * 2 + 1) * (halfSize * 2 + 1));
size = halfSize * 2 + 1;
this.halfSize = halfSize;
pos.set(new Pos(centerX-halfSize, centerY-halfSize));
pos.set(new Pos2D(centerX-halfSize, centerY-halfSize));
clear();
}
@@ -53,16 +47,16 @@ public class MovableGridRingList<T> extends ArrayList<T> implements List<T> {
}
}
public Pos getCenter() {
Pos bottom = pos.get();
return new Pos(bottom.x+halfSize, bottom.y+halfSize);
public Pos2D getCenter() {
Pos2D bottom = pos.get();
return new Pos2D(bottom.x+halfSize, bottom.y+halfSize);
}
public Pos getMinInRange() {
public Pos2D getMinInRange() {
return pos.get();
}
public Pos getMaxInRange() {
Pos bottom = pos.get();
return new Pos(bottom.x+size-1, bottom.y+size-1);
public Pos2D getMaxInRange() {
Pos2D bottom = pos.get();
return new Pos2D(bottom.x+size-1, bottom.y+size-1);
}
public int getSize() {return size;}
public int getHalfSize() {return halfSize;}
@@ -70,10 +64,10 @@ public class MovableGridRingList<T> extends ArrayList<T> implements List<T> {
// WARNNING! Be careful with race condition!
// The grid may get moved after this query!
public boolean inRange(int x, int y) {
Pos min = pos.get();
Pos2D min = pos.get();
return (x>=min.x && x<min.x+size && y>=min.y && y<min.y+size);
}
private boolean _inRangeAquired(int x, int y, Pos min) {
private boolean _inRangeAquired(int x, int y, Pos2D min) {
return (x>=min.x && x<min.x+size && y>=min.y && y<min.y+size);
}
private T _getUnsafe(int x, int y) {
@@ -88,11 +82,11 @@ public class MovableGridRingList<T> extends ArrayList<T> implements List<T> {
// return null if x,y is outside of the grid
public T get(int x, int y) {
Pos min = pos.get();
Pos2D min = pos.get();
if (!_inRangeAquired(x, y, min)) return null;
moveLock.readLock().lock();
try {
Pos newMin = pos.get();
Pos2D newMin = pos.get();
// Use EXECT compare here
if (min!=newMin)
if (!_inRangeAquired(x, y, newMin)) return null;
@@ -104,11 +98,11 @@ public class MovableGridRingList<T> extends ArrayList<T> implements List<T> {
// return false if x,y is outside of the grid
public boolean set(int x, int y, T t) {
Pos min = pos.get();
Pos2D min = pos.get();
if (!_inRangeAquired(x, y, min)) return false;
moveLock.readLock().lock();
try {
Pos newMin = pos.get();
Pos2D newMin = pos.get();
// Use EXECT compare here
if (min!=newMin)
if (!_inRangeAquired(x, y, newMin)) return false;
@@ -121,11 +115,11 @@ public class MovableGridRingList<T> extends ArrayList<T> implements List<T> {
// return input t if x,y is outside of the grid
public T swap(int x, int y, T t) {
Pos min = pos.get();
Pos2D min = pos.get();
if (!_inRangeAquired(x, y, min)) return t;
moveLock.readLock().lock();
try {
Pos newMin = pos.get();
Pos2D newMin = pos.get();
// Use EXECT compare here
if (min!=newMin)
if (!_inRangeAquired(x, y, newMin)) return t;
@@ -165,7 +159,7 @@ public class MovableGridRingList<T> extends ArrayList<T> implements List<T> {
}
public boolean move(int newCenterX, int newCenterY, Consumer<? super T> d) {
Pos cPos = pos.get();
Pos2D cPos = pos.get();
int newMinX = newCenterX - halfSize;
int newMinY = newCenterY - halfSize;
if (cPos.x == newMinX && cPos.y == newMinY)
@@ -193,7 +187,7 @@ public class MovableGridRingList<T> extends ArrayList<T> implements List<T> {
}
}
}
pos.set(new Pos(newMinX, newMinY));
pos.set(new Pos2D(newMinX, newMinY));
return true;
} finally {
moveLock.writeLock().unlock();
@@ -202,7 +196,7 @@ public class MovableGridRingList<T> extends ArrayList<T> implements List<T> {
@Override
public String toString() {
Pos p = pos.get();
Pos2D p = pos.get();
return "MovabeGridRingList[" + p.x+halfSize + "," + p.y+halfSize + "] " + size + "*" + size + "[" + size() + "]";
}
@@ -0,0 +1,158 @@
package com.seibel.lod.core.util.gridList;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer;
/*Layout:
* 0,1,2,
* 3,4,5,
* 6,7,8
*/
public class PosArrayGridList<T> extends ArrayGridList<T> {
private int offsetX;
private int offsetY;
/*
* WARNING: Non Thread safe!
*/
public PosArrayGridList(int gridSize, int offsetX, int offsetY, BiFunction<Integer, Integer, T> filler) {
super(gridSize, filler);
this.offsetX = offsetX;
this.offsetY = offsetY;
}
public PosArrayGridList(int gridSize, int offsetX, int offsetY) {
this(gridSize, offsetX, offsetY, (x,y) -> null);
}
public PosArrayGridList(PosArrayGridList<T> copy) {
super(copy);
offsetX = copy.offsetX;
offsetY = copy.offsetY;
}
public PosArrayGridList(PosArrayGridList<T> source, int minR, int maxR) {
super(source, minR, maxR);
}
@Override
protected int _indexOf(int x, int y) {
return (x-offsetX) + (y-offsetY) * gridSize;
}
@Override
public void forEachPos(BiConsumer<Integer, Integer> consumer) {
for (int y=offsetY; y<offsetY+gridSize; y++) {
for (int x=offsetX; x<offsetX+gridSize; x++) {
consumer.accept(x,y);
}
}
}
public int getOffsetX() {
return offsetX;
}
public int getOffsetY() {
return offsetY;
}
public boolean inRange(int x, int y) {
return (x>=offsetX && x<offsetX+gridSize &&
y>=offsetY && y<offsetY+gridSize);
}
private T _directGet(int x, int y) {
if (!inRange(x,y)) return null;
return get(x,y);
}
// Return false if haven't changed. Return true if it did
public boolean move(int deltaX, int deltaY, Consumer<? super T> dealloc) {
if (deltaX==0 && deltaY==0) return false;
// if the x or z offset is equal to or greater than
// the total width, just delete the current data
// and update the centerX and/or centerZ
if (Math.abs(deltaX) >= gridSize || Math.abs(deltaY) >= gridSize)
{
clear(dealloc);
// update the new center
offsetX += deltaX;
offsetY += deltaY;
return true;
}
int newMinX = offsetX + deltaX;
int newMinY = offsetY + deltaY;
int newMaxX = newMinX + gridSize;
int newMaxY = newMinY + gridSize;
// Dealloc stuff
if (dealloc != null)
forEachPos((x,y) -> {
if (x<newMinX || y<newMinY ||
x>=newMaxX || y>=newMaxY) {
T t = get(x,y);
if (t!=null) dealloc.accept(t);
}
});
offsetX = newMinX;
offsetY = newMinY;
// X
if (deltaX >= 0 && deltaY >= 0)
{
// move everything over to the left-up (as the center moves to the right-down)
for (int x = newMinX; x < newMaxX; x++)
{
for (int y = newMinY; y < newMaxY; y++)
{
set(x, y, _directGet(x+deltaX, y+deltaY));
}
}
}
else if (deltaX < 0 && deltaY >= 0)
{
// move everything over to the right-up (as the center moves to the left-down)
for (int x = newMaxX - 1; x >= newMinX; x--)
{
for (int y = newMinY; y < newMaxY; y++)
{
set(x, y, _directGet(x+deltaX, y+deltaY));
}
}
}
else if (deltaX >= 0) // && deltaY < 0)
{
// move everything over to the left-down (as the center moves to the right-up)
for (int x = newMinX; x < newMaxX; x++)
{
for (int y = newMaxY - 1; y >= newMinY; y--)
{
set(x, y, _directGet(x+deltaX, y+deltaY));
}
}
}
else //if (deltaX < 0 && deltaY < 0)
{
// move everything over to the right-down (as the center moves to the left-up)
for (int x = newMaxX - 1; x >= newMinX; x--)
{
for (int y = newMaxY - 1; y >= newMinY; y--)
{
set(x, y, _directGet(x+deltaX, y+deltaY));
}
}
}
return true;
}
@Override
public String toString() {
return getClass().toString() + "[" + offsetX + "," + offsetY + "] " + gridSize + "*" + gridSize + "[" + size() + "]";
}
}
@@ -15,19 +15,6 @@ public interface IVersionConstants extends IBindable {
/** @returns the minimum height blocks can be generated */
int getMinimumWorldHeight();
/**
* @Returns True if the given DistanceGenerationMode can be run on our own
* thread. <br>
* False if the generation must be run on Minecraft's server thread.
*/
boolean isWorldGeneratorSingleThreaded(DistanceGenerationMode distanceGenerationMode);
/**
* @Returns True if BatchGeneration is implemented <br>
* False if it is not supported
*/
boolean hasBatchGenerationImplementation();
/**
* @Returns the number of generations call per thread.
*/
@@ -26,7 +26,6 @@ import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper;
import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
import com.seibel.lod.core.wrapperInterfaces.worldGeneration.AbstractBatchGenerationEnvionmentWrapper;
import com.seibel.lod.core.wrapperInterfaces.worldGeneration.AbstractWorldGeneratorWrapper;
/**
* This handles creating abstract wrapper objects.
@@ -45,9 +44,6 @@ public interface IWrapperFactory extends IBindable
AbstractChunkPosWrapper createChunkPos(AbstractChunkPosWrapper newChunkPos);
AbstractChunkPosWrapper createChunkPos(AbstractBlockPosWrapper blockPos);
AbstractWorldGeneratorWrapper createWorldGenerator(LodBuilder newLodBuilder, LodDimension newLodDimension,
IWorldWrapper worldWrapper);
AbstractBatchGenerationEnvionmentWrapper createBatchGenerator(LodBuilder newLodBuilder,
LodDimension newLodDimension, IWorldWrapper worldWrapper);
}
@@ -604,32 +604,32 @@ public interface ILodConfigWrapperSingleton extends IBindable
+ " Only generate the biomes and use the biome's \n"
+ " grass color, water color, or snow color. \n"
+ " Doesn't generate height, everything is shown at sea level. \n"
+ " " + multiOrSingleThreadText(versionConstants, DistanceGenerationMode.BIOME_ONLY) + " - Fastest (2-5 ms) \n"
+ " - Fastest (2-5 ms) \n"
+ "\n"
+ " " + DistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT + " \n"
+ " Same as " + DistanceGenerationMode.BIOME_ONLY + ", except instead \n"
+ " of always using sea level as the LOD height \n"
+ " different biome types (mountain, ocean, forest, etc.) \n"
+ " use predetermined heights to simulate having height data. \n"
+ " " + multiOrSingleThreadText(versionConstants, DistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT) + " - Fastest (2-5 ms) \n"
+ " - Fastest (2-5 ms) \n"
+ "\n"
+ " " + DistanceGenerationMode.SURFACE + " \n"
+ " Generate the world surface, \n"
+ " this does NOT include trees, \n"
+ " or structures. \n"
+ " " + multiOrSingleThreadText(versionConstants, DistanceGenerationMode.SURFACE) + " - Faster (10-20 ms) \n"
+ " - Faster (10-20 ms) \n"
+ "\n"
+ " " + DistanceGenerationMode.FEATURES + " \n"
+ " Generate everything except structures. \n"
+ " WARNING: This may cause world generation bugs or instability! \n"
+ " " + multiOrSingleThreadText(versionConstants, DistanceGenerationMode.FEATURES) + " - Fast (15-20 ms) \n"
+ " - Fast (15-20 ms) \n"
+ "\n"
+ " " + DistanceGenerationMode.FULL + " \n"
+ " Ask the local server to generate/load each chunk. \n"
+ " This will show player made structures, which can \n"
+ " be useful if you are adding the mod to a pre-existing world. \n"
+ " This is the most compatible, but causes server/simulation lag. \n"
+ " " + multiOrSingleThreadText(versionConstants, DistanceGenerationMode.FULL) + " - Slow (15-50 ms, with spikes up to 200 ms) \n"
+ " - Slow (15-50 ms, with spikes up to 200 ms) \n"
+ "\n"
+ " The multithreaded options may increase CPU load significantly (while generating) \n"
+ " depending on how many world generation threads you have allocated. \n";
@@ -704,13 +704,6 @@ public interface ILodConfigWrapperSingleton extends IBindable
+ " This wont't affect performance.";
BlocksToAvoid getBlocksToAvoid();
void setBlockToAvoid(BlocksToAvoid newBlockToAvoid);
/** description helper method */
static String multiOrSingleThreadText(IVersionConstants versionConstants, DistanceGenerationMode distanceGenerationMode)
{
return versionConstants.isWorldGeneratorSingleThreaded(distanceGenerationMode) ? "Singlethreaded" : "Multithreaded";
}
}
@@ -1,15 +0,0 @@
package com.seibel.lod.core.wrapperInterfaces.worldGeneration;
import com.seibel.lod.core.builders.lodBuilding.LodBuilder;
import com.seibel.lod.core.objects.lod.LodDimension;
import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
public abstract class AbstractExperimentalWorldGeneratorWrapper {
public AbstractExperimentalWorldGeneratorWrapper(LodBuilder newLodBuilder, LodDimension newLodDimension,
IWorldWrapper worldWrapper) {
}
public abstract void queueGenerationRequests(LodDimension lodDim, LodBuilder lodBuilder);
public abstract void stop();
}
@@ -1,49 +0,0 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.wrapperInterfaces.worldGeneration;
import com.seibel.lod.core.builders.lodBuilding.LodBuilder;
import com.seibel.lod.core.enums.config.DistanceGenerationMode;
import com.seibel.lod.core.objects.lod.LodDimension;
import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper;
import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
/**
* This is used for generating chunks in a variety of detail and threading
* levels.
* <p>
* Abstract instead of an interface, so we can define its constructors.
*
* @author James Seibel
* @version 11-20-2021
*/
public abstract class AbstractWorldGeneratorWrapper {
public AbstractWorldGeneratorWrapper(LodBuilder newLodBuilder, LodDimension newLodDimension,
IWorldWrapper worldWrapper) {
}
public abstract void generateBiomesOnly(AbstractChunkPosWrapper pos, DistanceGenerationMode generationMode);
public abstract void generateSurface(AbstractChunkPosWrapper pos);
public abstract void generateFeatures(AbstractChunkPosWrapper pos);
public abstract void generateFull(AbstractChunkPosWrapper pos);
}