Improve initial LOD loading speed and add KeyedLockContainer
This commit is contained in:
+4
-13
@@ -7,6 +7,7 @@ import com.google.common.cache.RemovalNotification;
|
||||
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
|
||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
||||
import com.seibel.distanthorizons.core.util.KeyedLockContainer;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@@ -25,9 +26,8 @@ public class DelayedFullDataSourceSaveCache
|
||||
|
||||
private final Cache<Long, FullDataSourceV2> dataSourceByPosition;
|
||||
|
||||
protected final ReentrantLock[] saveLockArray;
|
||||
/** Based on the stack overflow post: https://stackoverflow.com/a/45909920 */
|
||||
protected ReentrantLock getSaveLockForPos(long pos) { return this.saveLockArray[Math.abs(Long.hashCode(pos)) % this.saveLockArray.length]; }
|
||||
/* don't let two threads load the same position at the same time */
|
||||
protected final KeyedLockContainer<Long> saveLockContainer = new KeyedLockContainer<>();
|
||||
|
||||
private final ISaveDataSourceFunc onSaveTimeoutAsyncFunc;
|
||||
private final int saveDelayInMs;
|
||||
@@ -51,15 +51,6 @@ public class DelayedFullDataSourceSaveCache
|
||||
.removalListener(this::handleDataSourceRemoval)
|
||||
.<Long, FullDataSourceV2>build();
|
||||
|
||||
|
||||
// the lock array's length is 2x the number of CPU cores so the number of collisions
|
||||
// should be relatively low without having too many extra locks
|
||||
int lockCount = Runtime.getRuntime().availableProcessors() * 2;
|
||||
this.saveLockArray = new ReentrantLock[lockCount];
|
||||
for (int i = 0; i < lockCount; i++)
|
||||
{
|
||||
this.saveLockArray[i] = new ReentrantLock();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -76,7 +67,7 @@ public class DelayedFullDataSourceSaveCache
|
||||
{
|
||||
long inputPos = inputDataSource.getPos();
|
||||
|
||||
ReentrantLock lock = this.getSaveLockForPos(inputPos);
|
||||
ReentrantLock lock = this.saveLockContainer.getLockForPos(inputPos);
|
||||
try
|
||||
{
|
||||
lock.lock();
|
||||
|
||||
@@ -20,7 +20,10 @@
|
||||
package com.seibel.distanthorizons.core.render;
|
||||
|
||||
import com.google.common.base.Suppliers;
|
||||
import com.google.errorprone.annotations.MustBeClosed;
|
||||
import com.google.common.cache.Cache;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.cache.RemovalCause;
|
||||
import com.google.common.cache.RemovalNotification;
|
||||
import com.seibel.distanthorizons.core.config.Config;
|
||||
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
|
||||
import com.seibel.distanthorizons.core.dataObjects.render.ColumnRenderSource;
|
||||
@@ -38,6 +41,7 @@ import com.seibel.distanthorizons.core.render.glObject.GLProxy;
|
||||
import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable;
|
||||
import com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding.ColumnRenderBuffer;
|
||||
import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
|
||||
import com.seibel.distanthorizons.core.util.KeyedLockContainer;
|
||||
import com.seibel.distanthorizons.core.util.threading.PriorityTaskPicker;
|
||||
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
|
||||
@@ -49,6 +53,7 @@ import javax.annotation.WillNotClose;
|
||||
import java.awt.*;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
@@ -60,6 +65,41 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
|
||||
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
|
||||
private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
|
||||
|
||||
/**
|
||||
* caching the loaded positions significantly improves initial loading performance
|
||||
* since the same position doesn't need to be loaded 5 times.
|
||||
*/
|
||||
private static final Cache<Long, ColumnRenderSource> CACHED_RENDER_SOURCE_BY_POS
|
||||
= CacheBuilder.newBuilder()
|
||||
// availableProcessors() : each process may need to be loading a render source
|
||||
// +1 : add 1 thread count buffer to reduce the chance of accidentally unloading a render source before it's used
|
||||
// *5 : each render source needs it's 4 adjacent sides, so a total of 5 render sources are needed per load
|
||||
.maximumSize((Runtime.getRuntime().availableProcessors() + 1) * 5L)
|
||||
.removalListener((RemovalNotification<Long, ColumnRenderSource> removalNotification) ->
|
||||
{
|
||||
RemovalCause cause = removalNotification.getCause();
|
||||
if (cause == RemovalCause.EXPIRED
|
||||
|| cause == RemovalCause.COLLECTED
|
||||
|| cause == RemovalCause.SIZE)
|
||||
{
|
||||
// close the render source after it's been
|
||||
ColumnRenderSource renderSource = removalNotification.getValue();
|
||||
if (renderSource != null)
|
||||
{
|
||||
renderSource.close();
|
||||
}
|
||||
else
|
||||
{
|
||||
LOGGER.error("Unable to close null cached render source.");
|
||||
}
|
||||
}
|
||||
})
|
||||
.<Long, ColumnRenderSource>build();
|
||||
|
||||
/** don't let two threads load the same position at the same time */
|
||||
protected static final KeyedLockContainer<Long> RENDER_LOAD_LOCK_CONTAINER = new KeyedLockContainer<>();
|
||||
|
||||
|
||||
|
||||
|
||||
public final long pos;
|
||||
@@ -182,52 +222,45 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
|
||||
{
|
||||
try
|
||||
{
|
||||
try (ColumnRenderSource renderSource = this.getRenderSourceForPos(this.pos))
|
||||
ColumnRenderSource renderSource = this.getRenderSourceForPos(this.pos);
|
||||
if (renderSource == null)
|
||||
{
|
||||
if (renderSource == null)
|
||||
{
|
||||
// nothing needs to be rendered
|
||||
// TODO how doesn't this cause infinite file handler loops?
|
||||
// to trigger an upload we check if the buffer is null, and we aren't
|
||||
// setting the render buffer here
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
boolean enableTransparency = Config.Client.Advanced.Graphics.Quality.transparency.get().transparencyEnabled;
|
||||
LodQuadBuilder lodQuadBuilder = new LodQuadBuilder(enableTransparency, this.level.getClientLevelWrapper());
|
||||
|
||||
// Getting adjacent data sources without caching
|
||||
// (IE each position must pull down it's 4 neighbors even if those neighbors
|
||||
// are used by another position)
|
||||
// roughly doubles the total time to load vs just loading the center position;
|
||||
// however, caching the ColumnRenderSource's in memory is extremely
|
||||
// difficult to do without either memory leaks or explosive memory costs.
|
||||
// So, we're just going to do it this way.
|
||||
try (ColumnRenderSource northRenderSource = this.getRenderSourceForPos(DhSectionPos.getAdjacentPos(this.pos, EDhDirection.NORTH));
|
||||
ColumnRenderSource southRenderSource = this.getRenderSourceForPos(DhSectionPos.getAdjacentPos(this.pos, EDhDirection.SOUTH));
|
||||
ColumnRenderSource eastRenderSource = this.getRenderSourceForPos(DhSectionPos.getAdjacentPos(this.pos, EDhDirection.EAST));
|
||||
ColumnRenderSource westRenderSource = this.getRenderSourceForPos(DhSectionPos.getAdjacentPos(this.pos, EDhDirection.WEST)))
|
||||
{
|
||||
ColumnRenderSource[] adjacentRenderSections = new ColumnRenderSource[EDhDirection.ADJ_DIRECTIONS.length];
|
||||
adjacentRenderSections[EDhDirection.NORTH.ordinal() - 2] = northRenderSource;
|
||||
adjacentRenderSections[EDhDirection.SOUTH.ordinal() - 2] = southRenderSource;
|
||||
adjacentRenderSections[EDhDirection.EAST.ordinal() - 2] = eastRenderSource;
|
||||
adjacentRenderSections[EDhDirection.WEST.ordinal() - 2] = westRenderSource;
|
||||
|
||||
boolean[] adjIsSameDetailLevel = new boolean[EDhDirection.ADJ_DIRECTIONS.length];
|
||||
adjIsSameDetailLevel[EDhDirection.NORTH.ordinal() - 2] = this.isAdjacentPosSameDetailLevel(EDhDirection.NORTH);
|
||||
adjIsSameDetailLevel[EDhDirection.SOUTH.ordinal() - 2] = this.isAdjacentPosSameDetailLevel(EDhDirection.SOUTH);
|
||||
adjIsSameDetailLevel[EDhDirection.EAST.ordinal() - 2] = this.isAdjacentPosSameDetailLevel(EDhDirection.EAST);
|
||||
adjIsSameDetailLevel[EDhDirection.WEST.ordinal() - 2] = this.isAdjacentPosSameDetailLevel(EDhDirection.WEST);
|
||||
|
||||
// the render sources are only needed in this synchronous method,
|
||||
// then they can be closed
|
||||
ColumnRenderBufferBuilder.makeLodRenderData(lodQuadBuilder, renderSource, this.level, adjacentRenderSections, adjIsSameDetailLevel);
|
||||
}
|
||||
|
||||
this.uploadToGpuAsync(lodQuadBuilder);
|
||||
// nothing needs to be rendered
|
||||
// TODO how doesn't this cause infinite file handler loops?
|
||||
// to trigger an upload we check if the buffer is null, and we aren't
|
||||
// setting the render buffer here
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
boolean enableTransparency = Config.Client.Advanced.Graphics.Quality.transparency.get().transparencyEnabled;
|
||||
LodQuadBuilder lodQuadBuilder = new LodQuadBuilder(enableTransparency, this.level.getClientLevelWrapper());
|
||||
|
||||
// load adjacent render sources
|
||||
{
|
||||
ColumnRenderSource northRenderSource = this.getRenderSourceForPos(DhSectionPos.getAdjacentPos(this.pos, EDhDirection.NORTH));
|
||||
ColumnRenderSource southRenderSource = this.getRenderSourceForPos(DhSectionPos.getAdjacentPos(this.pos, EDhDirection.SOUTH));
|
||||
ColumnRenderSource eastRenderSource = this.getRenderSourceForPos(DhSectionPos.getAdjacentPos(this.pos, EDhDirection.EAST));
|
||||
ColumnRenderSource westRenderSource = this.getRenderSourceForPos(DhSectionPos.getAdjacentPos(this.pos, EDhDirection.WEST));
|
||||
|
||||
ColumnRenderSource[] adjacentRenderSections = new ColumnRenderSource[EDhDirection.ADJ_DIRECTIONS.length];
|
||||
adjacentRenderSections[EDhDirection.NORTH.ordinal() - 2] = northRenderSource;
|
||||
adjacentRenderSections[EDhDirection.SOUTH.ordinal() - 2] = southRenderSource;
|
||||
adjacentRenderSections[EDhDirection.EAST.ordinal() - 2] = eastRenderSource;
|
||||
adjacentRenderSections[EDhDirection.WEST.ordinal() - 2] = westRenderSource;
|
||||
|
||||
boolean[] adjIsSameDetailLevel = new boolean[EDhDirection.ADJ_DIRECTIONS.length];
|
||||
adjIsSameDetailLevel[EDhDirection.NORTH.ordinal() - 2] = this.isAdjacentPosSameDetailLevel(EDhDirection.NORTH);
|
||||
adjIsSameDetailLevel[EDhDirection.SOUTH.ordinal() - 2] = this.isAdjacentPosSameDetailLevel(EDhDirection.SOUTH);
|
||||
adjIsSameDetailLevel[EDhDirection.EAST.ordinal() - 2] = this.isAdjacentPosSameDetailLevel(EDhDirection.EAST);
|
||||
adjIsSameDetailLevel[EDhDirection.WEST.ordinal() - 2] = this.isAdjacentPosSameDetailLevel(EDhDirection.WEST);
|
||||
|
||||
// the render sources are only needed in this synchronous method,
|
||||
// then they can be closed
|
||||
ColumnRenderBufferBuilder.makeLodRenderData(lodQuadBuilder, renderSource, this.level, adjacentRenderSections, adjIsSameDetailLevel);
|
||||
}
|
||||
|
||||
this.uploadToGpuAsync(lodQuadBuilder);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -235,12 +268,36 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
|
||||
}
|
||||
}
|
||||
@Nullable
|
||||
@MustBeClosed
|
||||
private ColumnRenderSource getRenderSourceForPos(long pos)
|
||||
{
|
||||
try (FullDataSourceV2 fullDataSource = this.fullDataSourceProvider.get(pos))
|
||||
ReentrantLock lock = RENDER_LOAD_LOCK_CONTAINER.getLockForPos(pos);
|
||||
try
|
||||
{
|
||||
return FullDataToRenderDataTransformer.transformFullDataToRenderSource(fullDataSource, this.level);
|
||||
// we don't want multiple threads attempting to load the same position at the same time
|
||||
lock.lock();
|
||||
|
||||
// use the cached data if possible
|
||||
ColumnRenderSource renderSource = CACHED_RENDER_SOURCE_BY_POS.getIfPresent(pos);
|
||||
if (renderSource != null)
|
||||
{
|
||||
return renderSource;
|
||||
}
|
||||
|
||||
try (FullDataSourceV2 fullDataSource = this.fullDataSourceProvider.get(pos))
|
||||
{
|
||||
renderSource = FullDataToRenderDataTransformer.transformFullDataToRenderSource(fullDataSource, this.level);
|
||||
// only add valid data to the cache (to prevent null pointers)
|
||||
if (renderSource != null)
|
||||
{
|
||||
CACHED_RENDER_SOURCE_BY_POS.put(pos, renderSource);
|
||||
}
|
||||
}
|
||||
|
||||
return renderSource;
|
||||
}
|
||||
finally
|
||||
{
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
private boolean isAdjacentPosSameDetailLevel(EDhDirection direction)
|
||||
|
||||
@@ -20,14 +20,13 @@
|
||||
package com.seibel.distanthorizons.core.sql.repo;
|
||||
|
||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
||||
import com.seibel.distanthorizons.core.sql.DatabaseUpdater;
|
||||
import com.seibel.distanthorizons.core.sql.DbConnectionClosedException;
|
||||
import com.seibel.distanthorizons.core.sql.dto.IBaseDTO;
|
||||
import com.seibel.distanthorizons.core.util.KeyedLockContainer;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.swing.plaf.nimbus.State;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.sql.*;
|
||||
@@ -61,9 +60,7 @@ public abstract class AbstractDhRepo<TKey, TDTO extends IBaseDTO<TKey>> implemen
|
||||
|
||||
public final Class<? extends TDTO> dtoClass;
|
||||
|
||||
protected final ReentrantLock[] saveLockArray;
|
||||
/** Based on the stack overflow post: https://stackoverflow.com/a/45909920 */
|
||||
protected ReentrantLock getSaveLockForKey(TKey key) { return this.saveLockArray[Math.abs(key.hashCode()) % this.saveLockArray.length]; }
|
||||
protected final KeyedLockContainer<TKey> saveLockContainer = new KeyedLockContainer<>();
|
||||
|
||||
|
||||
|
||||
@@ -79,16 +76,6 @@ public abstract class AbstractDhRepo<TKey, TDTO extends IBaseDTO<TKey>> implemen
|
||||
this.dtoClass = dtoClass;
|
||||
|
||||
|
||||
// the lock array's length is 2x the number of CPU cores so the number of collisions
|
||||
// should be relatively low without having too many extra locks
|
||||
int lockCount = Runtime.getRuntime().availableProcessors() * 2;
|
||||
this.saveLockArray = new ReentrantLock[lockCount];
|
||||
for (int i = 0; i < lockCount; i++)
|
||||
{
|
||||
this.saveLockArray[i] = new ReentrantLock();
|
||||
}
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
// needed by Forge to load the Java database connection
|
||||
@@ -206,7 +193,7 @@ public abstract class AbstractDhRepo<TKey, TDTO extends IBaseDTO<TKey>> implemen
|
||||
// a lock is necessary to prevent concurrent modification between
|
||||
// existsWithKey and insert/update,
|
||||
// otherwise another thread might cause the insert/update to fail.
|
||||
ReentrantLock saveLock = this.getSaveLockForKey(dto.getKey());
|
||||
ReentrantLock saveLock = this.saveLockContainer.getLockForPos(dto.getKey());
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.seibel.distanthorizons.core.util;
|
||||
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
/**
|
||||
* Can be used to allow an infinite number of keys to
|
||||
* map to a finite number of locks.
|
||||
* Useful when loading/modifying positional LOD data and wanting to
|
||||
* prevent concurrent modifications. <br>
|
||||
*
|
||||
* Based on the stack overflow post: https://stackoverflow.com/a/45909920
|
||||
*/
|
||||
public class KeyedLockContainer<TKey>
|
||||
{
|
||||
protected final ReentrantLock[] lockArray;
|
||||
|
||||
|
||||
//==============//
|
||||
// constructors //
|
||||
//==============//
|
||||
|
||||
public KeyedLockContainer()
|
||||
{
|
||||
// the lock array's length is 2x the number of CPU cores so the number of collisions
|
||||
// should be relatively low without having too many extra locks
|
||||
this(Runtime.getRuntime().availableProcessors() * 2);
|
||||
}
|
||||
public KeyedLockContainer(int lockCount)
|
||||
{
|
||||
this.lockArray = new ReentrantLock[lockCount];
|
||||
for (int i = 0; i < lockCount; i++)
|
||||
{
|
||||
this.lockArray[i] = new ReentrantLock();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//=========//
|
||||
// getters //
|
||||
//=========//
|
||||
|
||||
public ReentrantLock getLockForPos(TKey key) { return this.lockArray[Math.abs(key.hashCode()) % this.lockArray.length]; }
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user