diff --git a/core/src/main/java/com/seibel/distanthorizons/core/config/file/ConfigFileHandler.java b/core/src/main/java/com/seibel/distanthorizons/core/config/file/ConfigFileHandler.java index e4f0b2512..4b46f0a07 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/config/file/ConfigFileHandler.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/config/file/ConfigFileHandler.java @@ -240,7 +240,7 @@ public class ConfigFileHandler else if (entry.getTrueValue() == null) { // TODO when can this happen? - throw new IllegalArgumentException("Entry [" + entry.getNameAndCategory() + "] is null, this may be a problem with [" + ModInfo.NAME + "]. Please contact the authors."); + throw new IllegalArgumentException("BlockBiomeWrapperPair [" + entry.getNameAndCategory() + "] is null, this may be a problem with [" + ModInfo.NAME + "]. Please contact the authors."); } workConfig.set(entry.getNameAndCategory(), ConfigTypeConverters.attemptToConvertToString(entry.getType(), entry.getTrueValue())); @@ -287,13 +287,13 @@ public class ConfigFileHandler if (entry.getTrueValue() == null) { - LOGGER.warn("Entry [" + entry.getNameAndCategory() + "] returned as null from the config. Using default value."); + LOGGER.warn("BlockBiomeWrapperPair [" + entry.getNameAndCategory() + "] returned as null from the config. Using default value."); entry.setWithoutFiringEvents(entry.getDefaultValue()); } } catch (Exception e) { - LOGGER.warn("Entry [" + entry.getNameAndCategory() + "] had an invalid value when loading the config. Using default value."); + LOGGER.warn("BlockBiomeWrapperPair [" + entry.getNameAndCategory() + "] had an invalid value when loading the config. Using default value."); entry.setWithoutFiringEvents(entry.getDefaultValue()); } } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/BlockBiomeWrapperPair.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/BlockBiomeWrapperPair.java new file mode 100644 index 000000000..3ec642291 --- /dev/null +++ b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/BlockBiomeWrapperPair.java @@ -0,0 +1,151 @@ +package com.seibel.distanthorizons.core.dataObjects; + +import com.seibel.distanthorizons.core.dataObjects.fullData.FullDataPointIdMap; +import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; +import com.seibel.distanthorizons.core.util.objects.DataCorruptedException; +import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory; +import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper; +import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper; +import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; + +import java.util.concurrent.ConcurrentHashMap; + +/** + * A pooled compound key between the biome and blockState.
+ * These objects are pooled since we will need this compound key + * many times. + * + * @see FullDataPointIdMap + * @see IBlockStateWrapper + * @see IBiomeWrapper + */ +public class BlockBiomeWrapperPair +{ + private static final IWrapperFactory WRAPPER_FACTORY = SingletonInjector.INSTANCE.get(IWrapperFactory.class); + + /** two levels are present so we don't need to use a key object */ + private static final ConcurrentHashMap> CACHED_PAIR_BY_BIOME_BY_BLOCK = new ConcurrentHashMap<>(); + + public final IBiomeWrapper biome; + public final IBlockStateWrapper blockState; + + private int hashCode = 0; + private boolean hashGenerated = false; + private String serialString = null; + + + + //=============// + // constructor // + //=============// + + public static BlockBiomeWrapperPair get(IBlockStateWrapper blockState, IBiomeWrapper biome) + { + // check for existing entry + ConcurrentHashMap pairByBiomeWrapper = CACHED_PAIR_BY_BIOME_BY_BLOCK.get(blockState); + if (pairByBiomeWrapper != null) + { + BlockBiomeWrapperPair pair = pairByBiomeWrapper.get(biome); + if (pair != null) + { + return pair; + } + } + + // Lazily create the inner map and new BlockBiomeWrapperPair + return CACHED_PAIR_BY_BIOME_BY_BLOCK + .computeIfAbsent(blockState, newBlockState -> new ConcurrentHashMap<>()) + .computeIfAbsent(biome, newBiome -> new BlockBiomeWrapperPair(biome, blockState)); + } + private BlockBiomeWrapperPair(IBiomeWrapper biome, IBlockStateWrapper blockState) + { + this.biome = biome; + this.blockState = blockState; + } + + + + //===========// + // overrides // + //===========// + + /** + * Reminder: this hash code won't always be unique, collisions can occur; + * because of that this hash shouldn't be the only unique identifier for this object. + */ + @Override + public int hashCode() + { + // cache the hash code to improve speed + if (!this.hashGenerated) + { + this.hashCode = generateHashCode(this); + this.hashGenerated = true; + } + + return this.hashCode; + } + private static int generateHashCode(BlockBiomeWrapperPair pair) { return generateHashCode(pair.biome, pair.blockState); } + private static int generateHashCode(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 boolean equals(Object otherObj) + { + if (otherObj == this) + { + return true; + } + + if (!(otherObj instanceof BlockBiomeWrapperPair)) + { + return false; + } + + BlockBiomeWrapperPair other = (BlockBiomeWrapperPair) otherObj; + return other.biome.getSerialString().equals(this.biome.getSerialString()) + && other.blockState.getSerialString().equals(this.blockState.getSerialString()); + } + + @Override + public String toString() { return this.serialize(); } + + + + //=================// + // (de)serializing // + //=================// + + public String serialize() + { + if (this.serialString == null) + { + this.serialString = this.biome.getSerialString() + FullDataPointIdMap.BLOCK_STATE_SEPARATOR_STRING + this.blockState.getSerialString(); + } + + return this.serialString; + } + + public static BlockBiomeWrapperPair deserialize(String str, ILevelWrapper levelWrapper) throws DataCorruptedException + { + int separatorIndex = str.indexOf(FullDataPointIdMap.BLOCK_STATE_SEPARATOR_STRING); + if (separatorIndex == -1) + { + throw new DataCorruptedException("Failed to deserialize BiomeBlockStateEntry ["+str+"], unable to find separator."); + } + + IBiomeWrapper biome = WRAPPER_FACTORY.deserializeBiomeWrapperOrGetDefault(str.substring(0, separatorIndex), levelWrapper); + IBlockStateWrapper blockState = WRAPPER_FACTORY.deserializeBlockStateWrapperOrGetDefault(str.substring(separatorIndex+FullDataPointIdMap.BLOCK_STATE_SEPARATOR_STRING.length()), levelWrapper); + return BlockBiomeWrapperPair.get(blockState, biome); + } + +} \ No newline at end of file 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 b9034ded9..de96c6c97 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 @@ -19,7 +19,7 @@ package com.seibel.distanthorizons.core.dataObjects.fullData; -import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; +import com.seibel.distanthorizons.core.dataObjects.BlockBiomeWrapperPair; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.util.LodUtil; @@ -28,9 +28,7 @@ import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataInputStrea import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataOutputStream; import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper; -import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory; import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; -import org.apache.logging.log4j.LogManager; import com.seibel.distanthorizons.core.logging.DhLogger; import java.io.*; @@ -60,15 +58,15 @@ public class FullDataPointIdMap */ private static final boolean RUN_SERIALIZATION_DUPLICATE_VALIDATION = false; /** Distant Horizons - Block State Wrapper */ - private static final String BLOCK_STATE_SEPARATOR_STRING = "_DH-BSW_"; + public static final String BLOCK_STATE_SEPARATOR_STRING = "_DH-BSW_"; /** should only be used for debugging */ private long pos; - /** The index should be the same as the Entry's ID */ - private final ArrayList entryList = new ArrayList<>(); - private final ConcurrentHashMap idMap = new ConcurrentHashMap<>(); + /** The index should be the same as the BlockBiomeWrapperPair's ID */ + private final ArrayList blockBiomePairList = new ArrayList<>(); + private final ConcurrentHashMap idMap = new ConcurrentHashMap<>(); private int cachedHashCode = 0; @@ -90,28 +88,28 @@ public class FullDataPointIdMap public IBiomeWrapper getBiomeWrapper(int id) throws IndexOutOfBoundsException { return this.getEntry(id).biome; } /** @see FullDataPointIdMap#getEntry(int) */ public IBlockStateWrapper getBlockStateWrapper(int id) throws IndexOutOfBoundsException { return this.getEntry(id).blockState; } - /** @throws IndexOutOfBoundsException if the given ID isn't in the {@link FullDataPointIdMap#entryList} */ - private Entry getEntry(int id) throws IndexOutOfBoundsException + /** @throws IndexOutOfBoundsException if the given ID isn't in the {@link FullDataPointIdMap#blockBiomePairList} */ + private BlockBiomeWrapperPair getEntry(int id) throws IndexOutOfBoundsException { - Entry entry; + BlockBiomeWrapperPair pair; try { - entry = this.entryList.get(id); + pair = this.blockBiomePairList.get(id); } catch (IndexOutOfBoundsException e) { - throw new IndexOutOfBoundsException("FullData ID Map out of sync for pos: "+DhSectionPos.toString(this.pos)+". ID: ["+id+"] greater than the number of known ID's: ["+this.entryList.size()+"]."); + throw new IndexOutOfBoundsException("FullData ID Map out of sync for pos: "+DhSectionPos.toString(this.pos)+". ID: ["+id+"] greater than the number of known ID's: ["+this.blockBiomePairList.size()+"]."); } - return entry; + return pair; } /** @return -1 if the list is empty */ - public int getMaxValidId() { return this.entryList.size() - 1; } - public int size() { return this.entryList.size(); } + public int getMaxValidId() { return this.blockBiomePairList.size() - 1; } + public int size() { return this.blockBiomePairList.size(); } - public boolean isEmpty() { return this.entryList.isEmpty(); } + public boolean isEmpty() { return this.blockBiomePairList.isEmpty(); } public long getPos() { return this.pos; } @@ -125,11 +123,11 @@ 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(Entry.getEntry(biome, blockState)); } - private int addIfNotPresentAndGetId(Entry biomeBlockStateEntry) + public int addIfNotPresentAndGetId(IBiomeWrapper biome, IBlockStateWrapper blockState) { return this.addIfNotPresentAndGetId(BlockBiomeWrapperPair.get(blockState, biome)); } + private int addIfNotPresentAndGetId(BlockBiomeWrapperPair pair) { // try getting the existing ID - Integer nullableId = this.idMap.get(biomeBlockStateEntry); + Integer nullableId = this.idMap.get(pair); if (nullableId != null) { return nullableId; @@ -137,7 +135,7 @@ public class FullDataPointIdMap // create the new ID - return this.idMap.compute(biomeBlockStateEntry, (Entry newBiomeBlockStateEntry, Integer currentId) -> + return this.idMap.compute(pair, (BlockBiomeWrapperPair newPair, Integer currentId) -> { if (currentId != null) { @@ -146,8 +144,8 @@ public class FullDataPointIdMap // Add the new ID - currentId = this.entryList.size(); - this.entryList.add(biomeBlockStateEntry); + currentId = this.blockBiomePairList.size(); + this.blockBiomePairList.add(newPair); // invalidate the cached hash code this.cachedHashCode = 0; @@ -157,7 +155,7 @@ public class FullDataPointIdMap } /** - * Adds every {@link Entry} from inputMap into this map.
+ * Adds every {@link BlockBiomeWrapperPair} from inputMap into this map.
* Allows duplicate entries.

* * Allowing duplicate entries should be done if a datasource is just being read in and @@ -167,19 +165,19 @@ public class FullDataPointIdMap */ public void addAll(FullDataPointIdMap inputMap) { - ArrayList entriesToMerge = inputMap.entryList; - for (int i = 0; i < entriesToMerge.size(); i++) + ArrayList pairsToMerge = inputMap.blockBiomePairList; + for (int i = 0; i < pairsToMerge.size(); i++) { - Entry entity = entriesToMerge.get(i); - this.add(entity); + BlockBiomeWrapperPair pair = pairsToMerge.get(i); + this.add(pair); } } - /** allows for adding duplicate {@link Entry} */ - private void add(Entry biomeBlockStateEntry) + /** allows for adding duplicate {@link BlockBiomeWrapperPair} */ + private void add(BlockBiomeWrapperPair pair) { - int id = this.entryList.size(); - this.entryList.add(biomeBlockStateEntry); - this.idMap.put(biomeBlockStateEntry, id); + int id = this.blockBiomePairList.size(); + this.blockBiomePairList.add(pair); + this.idMap.put(pair, id); // invalidate the cached hash code this.cachedHashCode = 0; @@ -196,23 +194,23 @@ public class FullDataPointIdMap */ public int[] mergeAndReturnRemappedEntityIds(FullDataPointIdMap inputMap) { - ArrayList entriesToMerge = inputMap.entryList; - int[] remappedEntryIds = new int[entriesToMerge.size()]; + ArrayList entriesToMerge = inputMap.blockBiomePairList; + int[] remappedPairIds = new int[entriesToMerge.size()]; for (int i = 0; i < entriesToMerge.size(); i++) { - Entry entity = entriesToMerge.get(i); + BlockBiomeWrapperPair entity = entriesToMerge.get(i); int id = this.addIfNotPresentAndGetId(entity); - remappedEntryIds[i] = id; + remappedPairIds[i] = id; } - return remappedEntryIds; + return remappedPairIds; } /** Should only be used if this map is going to be reused, otherwise bad things will happen. */ public void clear(long pos) { this.pos = pos; - this.entryList.clear(); + this.blockBiomePairList.clear(); this.idMap.clear(); this.cachedHashCode = 0; } @@ -226,27 +224,27 @@ public class FullDataPointIdMap /** Serializes all contained entries into the given stream, formatted in UTF */ public void serialize(DhDataOutputStream outputStream) throws IOException { - outputStream.writeInt(this.entryList.size()); + outputStream.writeInt(this.blockBiomePairList.size()); // only used when debugging - HashMap dataPointEntryBySerialization = new HashMap<>(); + HashMap dataPointEntryBySerialization = new HashMap<>(); - for (Entry entry : this.entryList) + for (BlockBiomeWrapperPair pair : this.blockBiomePairList) { - String entryString = entry.serialize(); + String entryString = pair.serialize(); outputStream.writeUTF(entryString); if (RUN_SERIALIZATION_DUPLICATE_VALIDATION) { if (dataPointEntryBySerialization.containsKey(entryString)) { - LOGGER.error("Duplicate serialized entry found with serial: " + entryString); + LOGGER.error("Duplicate serialized pair found with serial: " + entryString); } - if (dataPointEntryBySerialization.containsValue(entry)) + if (dataPointEntryBySerialization.containsValue(pair)) { - LOGGER.error("Duplicate serialized entry found with value: " + entry.serialize()); + LOGGER.error("Duplicate serialized pair found with value: " + pair.serialize()); } - dataPointEntryBySerialization.put(entryString, entry); + dataPointEntryBySerialization.put(entryString, pair); } } } @@ -262,7 +260,7 @@ public class FullDataPointIdMap // only used when debugging - HashMap dataPointEntryBySerialization = new HashMap<>(); + HashMap dataPointEntryBySerialization = new HashMap<>(); FullDataPointIdMap newMap = new FullDataPointIdMap(pos); for (int i = 0; i < entityCount; i++) @@ -275,8 +273,8 @@ public class FullDataPointIdMap String entryString = inputStream.readUTF(); - Entry newEntry = Entry.deserialize(entryString, levelWrapper); - newMap.entryList.add(newEntry); + BlockBiomeWrapperPair newPair = BlockBiomeWrapperPair.deserialize(entryString, levelWrapper); + newMap.blockBiomePairList.add(newPair); if (RUN_SERIALIZATION_DUPLICATE_VALIDATION) { @@ -284,11 +282,11 @@ public class FullDataPointIdMap { LOGGER.error("Duplicate deserialized entry found with serial: " + entryString); } - if (dataPointEntryBySerialization.containsValue(newEntry)) + if (dataPointEntryBySerialization.containsValue(newPair)) { - LOGGER.error("Duplicate deserialized entry found with value: " + newEntry.serialize()); + LOGGER.error("Duplicate deserialized entry found with value: " + newPair.serialize()); } - dataPointEntryBySerialization.put(entryString, newEntry); + dataPointEntryBySerialization.put(entryString, newPair); } } @@ -334,149 +332,13 @@ public class FullDataPointIdMap private void generateHashCode() { int result = DhSectionPos.hashCode(this.pos); - for (int i = 0; i < this.entryList.size(); i++) + for (int i = 0; i < this.blockBiomePairList.size(); i++) { - result = 31 * result + this.entryList.hashCode(); + result = 31 * result + this.blockBiomePairList.hashCode(); } this.cachedHashCode = result; } - //==============// - // helper class // - //==============// - - private static final class Entry - { - private static final IWrapperFactory WRAPPER_FACTORY = SingletonInjector.INSTANCE.get(IWrapperFactory.class); - - /** two levels are present so we don't need to use a key object */ - private static final ConcurrentHashMap> ENTRY_BY_BLOCKSTATE_BY_BIOMEWRAPPER = new ConcurrentHashMap<>(); - - public final IBiomeWrapper biome; - public final IBlockStateWrapper blockState; - - private int hashCode = 0; - private boolean hashGenerated = false; - private String serialString = null; - - - - //=============// - // constructor // - //=============// - - public static Entry getEntry(IBiomeWrapper biome, IBlockStateWrapper blockState) - { - // check for existing entry - ConcurrentHashMap entryByBlockState = ENTRY_BY_BLOCKSTATE_BY_BIOMEWRAPPER.get(biome); - if (entryByBlockState != null) - { - Entry entry = entryByBlockState.get(blockState); - if (entry != null) - { - return entry; - } - } - - // Lazily create the inner map and new Entry - return ENTRY_BY_BLOCKSTATE_BY_BIOMEWRAPPER - .computeIfAbsent(biome, newBiome -> new ConcurrentHashMap<>()) - .computeIfAbsent(blockState, newBlockState -> new Entry(biome, blockState)); - } - private Entry(IBiomeWrapper biome, IBlockStateWrapper blockState) - { - this.biome = biome; - this.blockState = blockState; - } - - - - //===========// - // overrides // - //===========// - - /** - * Reminder: this hash code won't always be unique, collisions can occur; - * because of that this hash shouldn't be the only unique identifier for this object. - */ - @Override - public int hashCode() - { - // cache the hash code to improve speed - if (!this.hashGenerated) - { - this.hashCode = generateHashCode(this); - this.hashGenerated = true; - } - - return this.hashCode; - } - private static int generateHashCode(Entry entry) { return generateHashCode(entry.biome, entry.blockState); } - private static int generateHashCode(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 boolean equals(Object otherObj) - { - if (otherObj == this) - { - return true; - } - - if (!(otherObj instanceof Entry)) - { - return false; - } - - Entry other = (Entry) otherObj; - return other.biome.getSerialString().equals(this.biome.getSerialString()) - && other.blockState.getSerialString().equals(this.blockState.getSerialString()); - } - - @Override - public String toString() { return this.serialize(); } - - - - //=================// - // (de)serializing // - //=================// - - public String serialize() - { - if (this.serialString == null) - { - this.serialString = this.biome.getSerialString() + BLOCK_STATE_SEPARATOR_STRING + this.blockState.getSerialString(); - } - - return this.serialString; - } - - public static Entry deserialize(String str, ILevelWrapper levelWrapper) throws DataCorruptedException - { - int separatorIndex = str.indexOf(BLOCK_STATE_SEPARATOR_STRING); - if (separatorIndex == -1) - { - throw new DataCorruptedException("Failed to deserialize BiomeBlockStateEntry ["+str+"], unable to find separator."); - } - - IBiomeWrapper biome = WRAPPER_FACTORY.deserializeBiomeWrapperOrGetDefault(str.substring(0, separatorIndex), levelWrapper); - IBlockStateWrapper blockState = WRAPPER_FACTORY.deserializeBlockStateWrapperOrGetDefault(str.substring(separatorIndex+BLOCK_STATE_SEPARATOR_STRING.length()), levelWrapper); - return Entry.getEntry(biome, blockState); - } - - } - - }