diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/FullDataPointIdMap.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/FullDataPointIdMap.java index 468d57bee..56d682a42 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/FullDataPointIdMap.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/FullDataPointIdMap.java @@ -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> 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 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 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); } } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/sql/repo/FullDataSourceV2Repo.java b/core/src/main/java/com/seibel/distanthorizons/core/sql/repo/FullDataSourceV2Repo.java index cea06e2bd..b325aacd7 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/sql/repo/FullDataSourceV2Repo.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/sql/repo/FullDataSourceV2Repo.java @@ -40,6 +40,11 @@ public class FullDataSourceV2Repo extends AbstractDhRepo