diff --git a/src/main/java/carpetfixes/CFSettings.java b/src/main/java/carpetfixes/CFSettings.java index 14fba306..8c011672 100644 --- a/src/main/java/carpetfixes/CFSettings.java +++ b/src/main/java/carpetfixes/CFSettings.java @@ -1086,7 +1086,6 @@ public class CFSettings { public static boolean optimizedBiomeAccess = false; //by FX - PR0CESS - //I may end up converting all the other functions in recipe manager to be faster. Although I don't need them right now @Rule( desc = "Optimized the RecipeManager getFirstMatch call to be up to 3x faster", extra = {"This is a fully vanilla optimization. Improves: [Blast]Furnace/Campfire/Smoker/Stonecutter/Crafting/Sheep Color Choosing", @@ -1095,6 +1094,16 @@ public class CFSettings { ) public static boolean optimizedRecipeManager = false; + //by FX - PR0CESS + //Backported to 1.18.2 by Crec0 + @Rule( + desc = "Optimized the Furnace code drastically. Improving expensive checks, and expensive recipe lookups", + extra = {"This is a fully vanilla optimization. Improves: Furnace, Blast Furnace, Smoker, & any furnace extension", + "This is incredibly visible in modded scenarios"}, + category = {OPTIMIZATION,VANILLA,RECOMMENDED} + ) + public static boolean optimizedFurnaces = false; + /* diff --git a/src/main/java/carpetfixes/helpers/EventManager.java b/src/main/java/carpetfixes/helpers/EventManager.java new file mode 100644 index 00000000..9f888546 --- /dev/null +++ b/src/main/java/carpetfixes/helpers/EventManager.java @@ -0,0 +1,32 @@ +package carpetfixes.helpers; + +import com.google.common.collect.Maps; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class EventManager { + + /** + Using a list is better than using a set since we only loop through the eventListeners, and since + searching only happens during startup when initializing the event listeners, we don't care about + the negligible search cost of using an arraylist. This in terms saves us a bunch of memory over + a HashSet, while still only allowing one instance of Runnable per event. + */ + + private static final Map> eventListeners = Maps.newHashMap(); + + public enum CF_Event { + DATAPACK_RELOAD + } + + public static void onEvent(CF_Event event) { + eventListeners.get(event).forEach((Runnable::run)); + } + + public static void addEventListener(CF_Event event, Runnable runnable) { + List runnables = eventListeners.computeIfAbsent(event,(e) -> new ArrayList<>()); + if (!runnables.contains(runnable)) runnables.add(runnable); + } +} diff --git a/src/main/java/carpetfixes/mixins/optimizations/AbstractFurnaceBlockEntity_fastMixin.java b/src/main/java/carpetfixes/mixins/optimizations/AbstractFurnaceBlockEntity_fastMixin.java new file mode 100644 index 00000000..7fcef051 --- /dev/null +++ b/src/main/java/carpetfixes/mixins/optimizations/AbstractFurnaceBlockEntity_fastMixin.java @@ -0,0 +1,105 @@ +package carpetfixes.mixins.optimizations; + +import carpetfixes.CFSettings; +import carpetfixes.helpers.EventManager; +import net.minecraft.block.entity.AbstractFurnaceBlockEntity; +import net.minecraft.inventory.Inventory; +import net.minecraft.item.Item; +import net.minecraft.recipe.AbstractCookingRecipe; +import net.minecraft.recipe.Recipe; +import net.minecraft.recipe.RecipeManager; +import net.minecraft.recipe.RecipeType; +import net.minecraft.world.World; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.util.Map; +import java.util.Optional; + +@Mixin(AbstractFurnaceBlockEntity.class) +public class AbstractFurnaceBlockEntity_fastMixin { + + /** + * This optimization for furnaces is drastically faster than most furnace caching/optimization mods + * What we do is we cache the last recipe used, since this singular check against the last recipe is nearly + * negligible. The cache in return drastically improves the performance of large automated smelting & smelting + * more than a single item are a time in a furnace. Resulting in a performance boost basically everywhere since + * who smelts one item at a time, and if you are than it's not enough to lag a server. + * Secondly this optimization also caches the fuelTimeMap, a map that can only change on a datapack reload... + * so I cache it, and refresh it on a datapack reload. Before the game would create a LinkedHashMap every single + * time that it + */ + + private static Optional> lastRecipe = Optional.empty(); + private static Map cachedFuelTimeMap = null; + + static { + EventManager.addEventListener( + EventManager.CF_Event.DATAPACK_RELOAD, + () -> { + lastRecipe = Optional.empty(); + cachedFuelTimeMap = null; + } + ); + } + + + @Redirect( + method = "tick", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/recipe/RecipeManager;getFirstMatch(Lnet/minecraft/recipe/RecipeType;" + + "Lnet/minecraft/inventory/Inventory;Lnet/minecraft/world/World;)Ljava/util/Optional;" + ) + ) + private static Optional> getFirstMatchAndCache(RecipeManager manager, + RecipeType> type, + Inventory inventory, World world) { + if (CFSettings.optimizedFurnaces) { + if(lastRecipe.isPresent() && lastRecipe.get().matches(inventory, world)) return lastRecipe; + return lastRecipe = manager.getFirstMatch(type, inventory, world); + } + return manager.getFirstMatch(type, inventory, world); + } + + + @Inject( + method = "getCookTime", + at = @At("HEAD"), + cancellable = true + ) + private static void getCookTime(World world, RecipeType recipeType, + Inventory inventory, CallbackInfoReturnable cir) { + if (CFSettings.optimizedFurnaces + && lastRecipe.isPresent() + && lastRecipe.get() instanceof AbstractCookingRecipe cookingRecipe + && cookingRecipe.matches(inventory, world) + ) { + cir.setReturnValue(cookingRecipe.getCookTime()); + } + } + + + @Inject( + method = "createFuelTimeMap()Ljava/util/Map;", + at = @At("HEAD"), + cancellable = true + ) + private static void useCachedFuelTimeMap(CallbackInfoReturnable> cir) { + if (cachedFuelTimeMap != null) cir.setReturnValue(cachedFuelTimeMap); + } + + + @Inject( + method = "createFuelTimeMap()Ljava/util/Map;", + at = @At("RETURN") + ) + private static void cacheFuelTimeMap(CallbackInfoReturnable> cir) { + if (CFSettings.optimizedFurnaces && cachedFuelTimeMap == null) { + cachedFuelTimeMap = cir.getReturnValue(); + } + } +} diff --git a/src/main/java/carpetfixes/mixins/other/MinecraftServer_eventsMixin.java b/src/main/java/carpetfixes/mixins/other/MinecraftServer_eventsMixin.java new file mode 100644 index 00000000..eca83ea8 --- /dev/null +++ b/src/main/java/carpetfixes/mixins/other/MinecraftServer_eventsMixin.java @@ -0,0 +1,27 @@ +package carpetfixes.mixins.other; + +import carpetfixes.helpers.EventManager; +import net.minecraft.server.MinecraftServer; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.util.Collection; +import java.util.concurrent.CompletableFuture; + +@Mixin(MinecraftServer.class) +public class MinecraftServer_eventsMixin { + + + @Inject( + method = "reloadResources", + at = @At("TAIL") + ) + private void onReloadResources(Collection dataPacks, CallbackInfoReturnable> cir) { + cir.getReturnValue().handleAsync((value, throwable) -> { + EventManager.onEvent(EventManager.CF_Event.DATAPACK_RELOAD); + return value; + }, (MinecraftServer) (Object) this); + } +} diff --git a/src/main/resources/carpet-fixes.mixins.json b/src/main/resources/carpet-fixes.mixins.json index 356d292c..7163cf44 100644 --- a/src/main/resources/carpet-fixes.mixins.json +++ b/src/main/resources/carpet-fixes.mixins.json @@ -143,6 +143,7 @@ "oldSupport.old_EnchantmentScreenHandler_transparentBlocksMixin", "oldSupport.old_ServerWorld_zeroTickMixin", "oldSupport.old_World_randomMixin", + "optimizations.AbstractFurnaceBlockEntity_fastMixin", "optimizations.BiomeAccess_predictionMixin", "optimizations.ChunkTicketManager_optimizationMixin", "optimizations.MathHelper_hypotMixin", @@ -170,6 +171,7 @@ "optimizations.rounding.VoxelShapesMixin", "other.Bootstrap_developmentMixin", "other.CommandManager_devMixin", + "other.MinecraftServer_eventsMixin", "other.ServerWorld_updateSchedulerMixin", "other.StructureTestUtils_updatesMixin", "parity.ComparatorBlock_terribleMixin",