Pool FullDataPointIdMap Entries and improve hash logic

This commit is contained in:
James Seibel
2024-03-23 15:49:40 -05:00
parent 5d50775932
commit cbfd4f9de3
2 changed files with 101 additions and 8 deletions
@@ -27,12 +27,12 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrappe
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.*;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
@@ -131,7 +131,7 @@ public class FullDataPointIdMap
* If an entry with the given values already exists nothing will
* be added but the existing item's ID will still be returned.
*/
public int addIfNotPresentAndGetId(IBiomeWrapper biome, IBlockStateWrapper blockState) { return this.addIfNotPresentAndGetId(new Entry(biome, blockState), true); }
public int addIfNotPresentAndGetId(IBiomeWrapper biome, IBlockStateWrapper blockState) { return this.addIfNotPresentAndGetId(Entry.getEntry(biome, blockState), true); }
/** @param useWriteLocks should only be false if this method is already in a write lock to prevent unlocking at the wrong time */
private int addIfNotPresentAndGetId(Entry biomeBlockStateEntry, boolean useWriteLocks)
{
@@ -322,15 +322,81 @@ public class FullDataPointIdMap
{
private static final IWrapperFactory WRAPPER_FACTORY = SingletonInjector.INSTANCE.get(IWrapperFactory.class);
private static final Int2ReferenceOpenHashMap<ArrayList<Entry>> ENTRY_POOL = new Int2ReferenceOpenHashMap<>();
/** lock is necessary since {@link Int2ReferenceOpenHashMap} isn't concurrent and concurrent threads can cause infinite loops */
private static final ReentrantReadWriteLock ENTRY_POOL_LOCK = new ReentrantReadWriteLock();
public final IBiomeWrapper biome;
public final IBlockStateWrapper blockState;
private Integer hashCode = null;
// constructor //
public Entry(IBiomeWrapper biome, IBlockStateWrapper blockState)
//=============//
// constructor //
//=============//
public static Entry getEntry(IBiomeWrapper biome, IBlockStateWrapper blockState)
{
int entryHash = getHashCode(biome, blockState);
// try getting the existing entry
try
{
ENTRY_POOL_LOCK.readLock().lock();
// check if an entry already exists
ArrayList<Entry> entryList = ENTRY_POOL.get(entryHash);
if (entryList != null)
{
// at least one entry exists with this hash code
for (int i = 0; i < entryList.size(); i++)
{
Entry entry = entryList.get(i);
if (entry.biome.equals(biome) && entry.blockState.equals(blockState))
{
return entry;
}
}
// if we got here, then there was a hash collision and this entry wasn't present in the array
}
}
finally
{
ENTRY_POOL_LOCK.readLock().unlock();
}
// no entry exists,
// create a new one
try
{
ENTRY_POOL_LOCK.writeLock().lock();
ArrayList<Entry> entryList = ENTRY_POOL.get(entryHash);
if (entryList == null)
{
// no entries exist for this hash code
// we assume that hash collisions should basically never happen,
// so the array starts with an initial capacity of 1.
// However, since collisions will eventually happen, using an arrayList prevents unexpected bugs caused by collisions.
entryList = new ArrayList<>(1);
ENTRY_POOL.put(entryHash, entryList);
}
Entry newEntry = new Entry(biome, blockState);
entryList.add(newEntry);
return newEntry;
}
finally
{
ENTRY_POOL_LOCK.writeLock().unlock();
}
}
private Entry(IBiomeWrapper biome, IBlockStateWrapper blockState)
{
this.biome = biome;
this.blockState = blockState;
@@ -338,15 +404,29 @@ public class FullDataPointIdMap
// methods //
//===========//
// overrides //
//===========//
public static int getHashCode(Entry entry) { return getHashCode(entry.biome, entry.blockState); }
public static int getHashCode(IBiomeWrapper biome, IBlockStateWrapper blockState)
{
final int prime = 31;
int result = 1;
// the biome and blockstate hashcode should be already calculated by the time
// we get here, so this operation should be very fast
result = prime * result + (biome == null ? 0 : biome.hashCode());
result = prime * result + (blockState == null ? 0 : blockState.hashCode());
return result;
}
@Override
public int hashCode()
{
// cache the hash code to improve speed
if (this.hashCode == null)
{
this.hashCode = this.serialize().hashCode();
this.hashCode = getHashCode(this);
}
return this.hashCode;
@@ -371,6 +451,10 @@ public class FullDataPointIdMap
//=================//
// (de)serializing //
//=================//
public String serialize() { return this.biome.getSerialString() + BLOCK_STATE_SEPARATOR_STRING + this.blockState.getSerialString(); }
public static Entry deserialize(String str, ILevelWrapper levelWrapper) throws IOException, InterruptedException
@@ -389,7 +473,7 @@ public class FullDataPointIdMap
IBiomeWrapper biome = WRAPPER_FACTORY.deserializeBiomeWrapper(stringArray[0], levelWrapper);
IBlockStateWrapper blockState = WRAPPER_FACTORY.deserializeBlockStateWrapper(stringArray[1], levelWrapper);
return new Entry(biome, blockState);
return Entry.getEntry(biome, blockState);
}
}
@@ -40,6 +40,11 @@ public class FullDataSourceV2Repo extends AbstractDhRepo<DhSectionPos, FullDataS
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
//=============//
// constructor //
//=============//
public FullDataSourceV2Repo(String databaseType, String databaseLocation) throws SQLException
{
super(databaseType, databaseLocation, FullDataSourceV2DTO.class);
@@ -47,6 +52,10 @@ public class FullDataSourceV2Repo extends AbstractDhRepo<DhSectionPos, FullDataS
//===========//
// overrides //
//===========//
@Override
public String getTableName() { return "FullData"; }