Add beacon nearby culling

This commit is contained in:
James Seibel
2024-08-31 20:20:24 -05:00
parent b7d94c2ed1
commit ba59daf747
3 changed files with 186 additions and 27 deletions
@@ -21,6 +21,7 @@ package com.seibel.distanthorizons.core.render.renderer.generic;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiBlockMaterial;
import com.seibel.distanthorizons.api.interfaces.render.IDhApiRenderableBoxGroup;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam;
import com.seibel.distanthorizons.api.objects.math.DhApiVec3d;
import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBox;
import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBoxGroupShading;
@@ -32,6 +33,8 @@ import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.sql.dto.BeaconBeamDTO;
import com.seibel.distanthorizons.core.sql.repo.BeaconBeamRepo;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.math.Vec3d;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import com.seibel.distanthorizons.coreapi.ModInfo;
import org.apache.logging.log4j.Logger;
@@ -41,6 +44,11 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Predicate;
public class BeaconRenderHandler
{
@@ -49,13 +57,23 @@ public class BeaconRenderHandler
private static final int BEAM_TOP_Y = 6_000;
/** how often should we check if a beacon should be culled? */
private static final int MAX_CULLING_FREQUENCY_IN_MS = 1_000;
/** if this is null then the other handler is probably null too, but just in case */
private final BeaconBeamRepo beaconBeamRepo;
private final ReentrantLock updateLock = new ReentrantLock();
private final IDhApiRenderableBoxGroup beaconBoxGroup;
private final ArrayList<DhApiRenderableBox> fullBeaconBoxList = new ArrayList<>();
private final HashSet<DhBlockPos> beaconBlockPosSet = new HashSet<>();
private boolean cullingThreadRunning = false;
private boolean updateRenderDataNextFrame = false;
//=============//
@@ -71,7 +89,7 @@ public class BeaconRenderHandler
this.beaconBoxGroup.setSkyLight(LodUtil.MAX_MC_LIGHT);
this.beaconBoxGroup.setSsaoEnabled(false);
this.beaconBoxGroup.setShading(DhApiRenderableBoxGroupShading.getUnshaded());
this.beaconBoxGroup.setPreRenderFunc((renderEventParam) -> this.beaconBoxGroup.setActive(Config.Client.Advanced.Graphics.GenericRendering.enableBeaconRendering.get()));
this.beaconBoxGroup.setPreRenderFunc(this::beforeRender);
renderer.add(this.beaconBoxGroup);
}
@@ -178,49 +196,162 @@ public class BeaconRenderHandler
private void startRenderingBeacon(BeaconBeamDTO beacon)
{
if (this.beaconBlockPosSet.add(beacon.blockPos))
try
{
DhApiRenderableBox beaconBox = new DhApiRenderableBox(
new DhApiVec3d(beacon.blockPos.getX(), beacon.blockPos.getY() +1, beacon.blockPos.getZ()),
new DhApiVec3d(beacon.blockPos.getX() +1, BEAM_TOP_Y, beacon.blockPos.getZ() +1),
beacon.color,
EDhApiBlockMaterial.ILLUMINATED
);
this.updateLock.lock();
this.beaconBoxGroup.add(beaconBox);
this.beaconBoxGroup.triggerBoxChange();
if (this.beaconBlockPosSet.add(beacon.blockPos))
{
DhApiRenderableBox beaconBox = new DhApiRenderableBox(
new DhApiVec3d(beacon.blockPos.getX(), beacon.blockPos.getY() + 1, beacon.blockPos.getZ()),
new DhApiVec3d(beacon.blockPos.getX() + 1, BEAM_TOP_Y, beacon.blockPos.getZ() + 1),
beacon.color,
EDhApiBlockMaterial.ILLUMINATED
);
this.beaconBoxGroup.add(beaconBox);
this.fullBeaconBoxList.add(beaconBox);
this.beaconBoxGroup.triggerBoxChange();
}
}
finally
{
this.updateLock.unlock();
}
}
private void stopRenderingBeaconAtPos(DhBlockPos beaconPos)
{
if (this.beaconBlockPosSet.remove(beaconPos))
try
{
this.beaconBoxGroup.removeIf((box) ->
this.updateLock.lock();
if (this.beaconBlockPosSet.remove(beaconPos))
{
return box.minPos.x == beaconPos.getX()
&& box.minPos.y == beaconPos.getY() +1 // plus 1 because the beam starts above the beacon
&& box.minPos.z == beaconPos.getZ();
});
this.beaconBoxGroup.triggerBoxChange();
Predicate<DhApiRenderableBox> removePredicate = (DhApiRenderableBox box) ->
{
return box.minPos.x == beaconPos.getX()
&& box.minPos.y == beaconPos.getY() + 1 // plus 1 because the beam starts above the beacon
&& box.minPos.z == beaconPos.getZ();
};
this.beaconBoxGroup.removeIf(removePredicate);
this.fullBeaconBoxList.removeIf(removePredicate);
this.beaconBoxGroup.triggerBoxChange();
}
}
finally
{
this.updateLock.unlock();
}
}
private void updateBeaconColor(BeaconBeamDTO newBeam)
{
DhBlockPos pos = newBeam.blockPos;
for (int i = 0; i < this.beaconBoxGroup.size(); i++)
try
{
DhApiRenderableBox box = this.beaconBoxGroup.get(i);
if (box.minPos.x == pos.getX()
&& box.minPos.y == pos.getY() +1 // plus 1 because the beam starts above the beacon
&& box.minPos.z == pos.getZ())
this.updateLock.lock();
DhBlockPos pos = newBeam.blockPos;
for (int i = 0; i < this.fullBeaconBoxList.size(); i++)
{
box.color = newBeam.color;
this.beaconBoxGroup.triggerBoxChange();
break;
DhApiRenderableBox box = this.fullBeaconBoxList.get(i);
if (box.minPos.x == pos.getX()
&& box.minPos.y == pos.getY() + 1 // plus 1 because the beam starts above the beacon
&& box.minPos.z == pos.getZ())
{
box.color = newBeam.color;
this.beaconBoxGroup.triggerBoxChange();
break;
}
}
}
finally
{
this.updateLock.unlock();
}
}
private void beforeRender(DhApiRenderParam renderEventParam)
{
// this could be called only when the player moves, but it's an extremely cheap check,
// so there isn't much of a reason to bother
this.tryUpdateBeaconCullingAsync();
// this must be called on the render thread to prevent concurrency issues
if (this.updateRenderDataNextFrame)
{
this.beaconBoxGroup.triggerBoxChange();
this.updateRenderDataNextFrame = false;
}
this.beaconBoxGroup.setActive(Config.Client.Advanced.Graphics.GenericRendering.enableBeaconRendering.get());
}
/** does nothing if the culling thread is already running */
private void tryUpdateBeaconCullingAsync()
{
ThreadPoolExecutor executor = ThreadPoolUtil.getBeaconCullingExecutor();
if (executor != null
&& !this.cullingThreadRunning)
{
this.cullingThreadRunning = true;
try
{
executor.execute(() ->
{
try
{
Thread.sleep(MAX_CULLING_FREQUENCY_IN_MS);
}
catch (InterruptedException ignore)
{
}
try
{
// lock to make sure we don't try adding beacons to the arrays while processing them
this.updateLock.lock();
Vec3d cameraPos = MC_RENDER.getCameraExactPosition();
double mcRenderDistance = MC_RENDER.getRenderDistance() * LodUtil.CHUNK_WIDTH;
// Clear the existing box group so we can re-populate it.
// Since the box group is only used when we trigger an update, clearing it here
// and repopulating it is fine.
this.beaconBoxGroup.clear();
// While iterating over every beacon isn't a great way of doing this,
// when 940 beacons were tested this only took ~0.9 Milliseconds, so as long as
// we aren't freezing the render thread this method of culling works just fine.
for (DhApiRenderableBox box : this.fullBeaconBoxList)
{
// if a beacon is outside the vanilla render distance render it
double distance = Vec3d.getDistance(cameraPos, box.minPos);
if (distance > mcRenderDistance)
{
this.beaconBoxGroup.add(box);
}
}
this.updateRenderDataNextFrame = true;
}
catch (Exception e)
{
LOGGER.error("Unexpected issue while updating beacon culling. Error: " + e.getMessage(), e);
}
finally
{
this.updateLock.unlock();
this.cullingThreadRunning = false;
}
});
}
catch (RejectedExecutionException ignore)
{ /* If this happens that means everything is already shut down and no culling is necessary */ }
}
}
}
@@ -20,6 +20,7 @@
package com.seibel.distanthorizons.core.util.math;
import com.seibel.distanthorizons.api.objects.math.DhApiVec3d;
import com.seibel.distanthorizons.api.objects.math.DhApiVec3f;
import com.seibel.distanthorizons.coreapi.util.MathUtil;
/**
@@ -134,7 +135,6 @@ public class Vec3d extends DhApiVec3d
public Vec3d copy() { return new Vec3d(this.x, this.y, this.z); }
// Forge start
public Vec3d(double[] values) { this.set(values); }
public void set(double[] values)
@@ -144,4 +144,25 @@ public class Vec3d extends DhApiVec3d
this.z = values[2];
}
public static double getManhattanDistance(DhApiVec3d a, DhApiVec3d b)
{
return Math.abs(a.x - b.x)
+ Math.abs(a.y - b.y)
+ Math.abs(a.z - b.z);
}
public static double getDistance(DhApiVec3d a, DhApiVec3d b)
{
return Math.sqrt(Math.pow(a.x - b.x, 2)
+ Math.pow(a.y - b.y, 2)
+ Math.pow(a.z - b.z, 2));
}
/** slightly faster version of {@link Vec3d#getDistance} */
public static double getSquaredDistance(DhApiVec3d a, DhApiVec3d b)
{
return Math.pow(a.x - b.x, 2)
+ Math.pow(a.y - b.y, 2)
+ Math.pow(a.z - b.z, 2);
}
}
@@ -66,6 +66,11 @@ public class ThreadPoolUtil
@Nullable
public static ThreadPoolExecutor getCleanupExecutor() { return cleanupThreadPool; }
public static final String BEACON_CULLING_THREAD_NAME = "Beacon Culling";
private static ThreadPoolExecutor beaconCullingThreadPool;
@Nullable
public static ThreadPoolExecutor getBeaconCullingExecutor() { return beaconCullingThreadPool; }
//======================//
@@ -109,6 +114,7 @@ public class ThreadPoolUtil
worldGenThreadPool = new ConfigThreadPool(WORLD_GEN_THREAD_FACTORY, Config.Client.Advanced.MultiThreading.numberOfWorldGenerationThreads, Config.Client.Advanced.MultiThreading.runTimeRatioForWorldGenerationThreads, null);
bufferUploaderThreadPool = ThreadUtil.makeSingleThreadPool(BUFFER_UPLOADER_THREAD_NAME);
cleanupThreadPool = ThreadUtil.makeSingleThreadPool(CLEANUP_THREAD_NAME);
beaconCullingThreadPool = ThreadUtil.makeSingleThreadPool(BEACON_CULLING_THREAD_NAME);
@@ -149,6 +155,7 @@ public class ThreadPoolUtil
worldGenThreadPool.shutdownExecutorService();
bufferUploaderThreadPool.shutdown();
cleanupThreadPool.shutdown();
beaconCullingThreadPool.shutdown();
// worker threads