diff --git a/src/main/java/gregtech/GregTechMod.java b/src/main/java/gregtech/GregTechMod.java index 3d9cbeabd6c..791e8944281 100644 --- a/src/main/java/gregtech/GregTechMod.java +++ b/src/main/java/gregtech/GregTechMod.java @@ -3,6 +3,7 @@ import gregtech.api.GTValues; import gregtech.api.GregTechAPI; import gregtech.api.modules.ModuleContainerRegistryEvent; +import gregtech.api.persistence.PersistentData; import gregtech.client.utils.BloomEffectUtil; import gregtech.modules.GregTechModules; import gregtech.modules.ModuleManager; @@ -49,6 +50,7 @@ public GregTechMod() { @EventHandler public void onConstruction(FMLConstructionEvent event) { + PersistentData.instance().init(); moduleManager = ModuleManager.getInstance(); GregTechAPI.moduleManager = moduleManager; moduleManager.registerContainer(new GregTechModules()); diff --git a/src/main/java/gregtech/api/persistence/PersistentData.java b/src/main/java/gregtech/api/persistence/PersistentData.java new file mode 100644 index 00000000000..dc88872d9a3 --- /dev/null +++ b/src/main/java/gregtech/api/persistence/PersistentData.java @@ -0,0 +1,108 @@ +package gregtech.api.persistence; + +import gregtech.api.GTValues; +import gregtech.api.util.GTLog; + +import net.minecraft.nbt.CompressedStreamTools; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraftforge.fml.common.Loader; + +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; + +public final class PersistentData { + + private static final PersistentData INSTANCE = new PersistentData(); + + private @Nullable Path path; + private @Nullable NBTTagCompound tag; + + public static @NotNull PersistentData instance() { + return INSTANCE; + } + + private PersistentData() {} + + @ApiStatus.Internal + public void init() { + this.path = Loader.instance().getConfigDir().toPath() + .resolve(GTValues.MODID) + .resolve("persistent_data.dat"); + } + + /** + * @return the stored persistent data + */ + public synchronized @NotNull NBTTagCompound getTag() { + if (this.tag == null) { + this.tag = read(); + } + return this.tag; + } + + /** + * @return the read NBTTagCompound from disk + */ + private @NotNull NBTTagCompound read() { + GTLog.logger.debug("Reading persistent data from path {}", path); + if (this.path == null) { + throw new IllegalStateException("Persistent data path cannot be null"); + } + + if (!Files.exists(path)) { + return new NBTTagCompound(); + } + + try (InputStream inputStream = Files.newInputStream(this.path)) { + return CompressedStreamTools.readCompressed(inputStream); + } catch (IOException e) { + GTLog.logger.error("Failed to read persistent data", e); + return new NBTTagCompound(); + } + } + + /** + * Save the GT Persistent data to disk + */ + public synchronized void save() { + if (this.tag != null) { + write(this.tag); + } + } + + /** + * @param tagCompound the tag compound to save to disk + */ + private void write(@NotNull NBTTagCompound tagCompound) { + GTLog.logger.debug("Writing persistent data to path {}", path); + if (tagCompound.isEmpty()) { + return; + } + + if (this.path == null) { + throw new IllegalStateException("Persistent data path cannot be null"); + } + + if (!Files.exists(path)) { + try { + Files.createDirectories(path.getParent()); + } catch (IOException e) { + GTLog.logger.error("Could not create persistent data dir", e); + return; + } + } + + try (OutputStream outputStream = Files.newOutputStream(path)) { + CompressedStreamTools.writeCompressed(tagCompound, outputStream); + } catch (IOException e) { + GTLog.logger.error("Failed to write persistent data", e); + } + } +} diff --git a/src/main/java/gregtech/api/recipes/GTRecipeInputCache.java b/src/main/java/gregtech/api/recipes/GTRecipeInputCache.java index f72fb572f6e..4e9a68fdf03 100644 --- a/src/main/java/gregtech/api/recipes/GTRecipeInputCache.java +++ b/src/main/java/gregtech/api/recipes/GTRecipeInputCache.java @@ -1,11 +1,18 @@ package gregtech.api.recipes; import gregtech.api.GTValues; +import gregtech.api.persistence.PersistentData; import gregtech.api.recipes.ingredients.GTRecipeInput; import gregtech.api.util.GTLog; import gregtech.common.ConfigHolder; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.math.MathHelper; + +import it.unimi.dsi.fastutil.Hash; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; import java.util.ArrayList; import java.util.Collections; @@ -14,54 +21,77 @@ /** * Cache of GTRecipeInput instances for deduplication. *
- * Each GTRecipeInput is cached by an internal hashtable, and any duplicative - * instances will be replaced by identical object previously created. + * Each GTRecipeInput is cached by an internal hashtable, and any duplicative instances will be replaced by identical + * object previously created. *
- * Caching and duplication is only available during recipe registration; once
- * recipe registration is over, the cache will be discarded and no further entries
- * will be put into cache.
+ * Caching and duplication is only available during recipe registration; once recipe registration is over, the cache
+ * will be discarded and no further entries will be put into cache.
*/
-public class GTRecipeInputCache {
+public final class GTRecipeInputCache {
+
+ private static final int MINIMUM_CACHE_SIZE = 1 << 13;
+ private static final int MAXIMUM_CACHE_SIZE = 1 << 30;
+
+ private static ObjectOpenHashSet
* This operation returns {@code recipeInput} without doing anything if cache is disabled.
*
* @param recipeInput ingredient instance to be deduplicated
- * @return Either previously cached instance, or {@code recipeInput} marked cached;
- * or unmodified {@code recipeInput} instance if the cache is disabled
+ * @return Either previously cached instance, or {@code recipeInput} marked cached; or unmodified
+ * {@code recipeInput} instance if the cache is disabled
*/
public static GTRecipeInput deduplicate(GTRecipeInput recipeInput) {
if (!isCacheEnabled() || recipeInput.isCached()) {
return recipeInput;
}
- GTRecipeInput cached = INSTANCES.addOrGet(recipeInput);
+ GTRecipeInput cached = instances.addOrGet(recipeInput);
if (cached == recipeInput) { // If recipeInput is cached just now...
cached.setCached();
}
@@ -69,9 +99,9 @@ public static GTRecipeInput deduplicate(GTRecipeInput recipeInput) {
}
/**
- * Tries to deduplicate each instance in the list with previously cached instances.
- * If there is no identical GTRecipeInput present in cache, the
- * {@code recipeInput} will be put into cache, marked as cached, and returned subsequently.
+ * Tries to deduplicate each instance in the list with previously cached instances. If there is no identical
+ * GTRecipeInput present in cache, the {@code recipeInput} will be put into cache, marked as cached, and returned
+ * subsequently.
*
* This operation returns {@code inputs} without doing anything if cache is disabled.
*
@@ -91,4 +121,50 @@ public static List
+ *
+ *
+ * @return the optimal expected input cache size
+ */
+ private static int calculateOptimalExpectedSize() {
+ int min = Math.max(getExpectedInstanceAmount(PersistentData.instance().getTag()), MINIMUM_CACHE_SIZE);
+ for (int i = 13; i < 31; i++) {
+ int sizeToTest = 1 << i;
+ int arraySize = nextHighestPowerOf2((int) (sizeToTest / Hash.DEFAULT_LOAD_FACTOR));
+ int maxStoredBeforeRehash = (int) (arraySize * Hash.DEFAULT_LOAD_FACTOR);
+
+ if (maxStoredBeforeRehash >= min) {
+ return sizeToTest;
+ }
+ }
+ return MINIMUM_CACHE_SIZE;
+ }
+
+ /**
+ * Algorithm source.
+ *
+ * @param x the number to use
+ * @return the next highest power of 2 relative to the number
+ */
+ private static int nextHighestPowerOf2(int x) {
+ x--;
+ x |= x >> 1;
+ x |= x >> 2;
+ x |= x >> 4;
+ x |= x >> 8;
+ x |= x >> 16;
+ x++;
+ return x;
+ }
}