From 91d7f23b4f2e628054cac37822dd934762a8a0d9 Mon Sep 17 00:00:00 2001 From: Nicholas Ignoffo Date: Wed, 28 Feb 2018 17:53:23 -0800 Subject: [PATCH] Add API method for registering recipes at proper time The old access to IBloodMagicRecipeRegistrar still exists, but should not be used so mods like CraftTweaker can do their thing. This commit also introduces @BloodMagicPlugin.Inject which will inject the active API instance into an annotated field with the API interface as it's type. These fields are populated during pre init. --- .../bloodmagic/api/BloodMagicPlugin.java | 13 +++ .../bloodmagic/api/IBloodMagicAPI.java | 4 + .../bloodmagic/api/IBloodMagicPlugin.java | 15 ++- .../java/WayofTime/bloodmagic/BloodMagic.java | 3 +- .../api/impl/BloodMagicCorePlugin.java | 12 ++- .../core/RegistrarBloodMagicRecipes.java | 3 + .../WayofTime/bloodmagic/util/PluginUtil.java | 93 ++++++++++++++++--- 7 files changed, 125 insertions(+), 18 deletions(-) diff --git a/src/api/java/WayofTime/bloodmagic/api/BloodMagicPlugin.java b/src/api/java/WayofTime/bloodmagic/api/BloodMagicPlugin.java index 0d2bf397..3cbeb4b9 100644 --- a/src/api/java/WayofTime/bloodmagic/api/BloodMagicPlugin.java +++ b/src/api/java/WayofTime/bloodmagic/api/BloodMagicPlugin.java @@ -13,4 +13,17 @@ import java.lang.annotation.Target; @Target(ElementType.TYPE) public @interface BloodMagicPlugin { + /** + * This annotation will inject the active {@link IBloodMagicAPI} into a {@code static} field of the same + * type. Fields with invalid types will be ignored and an error will be logged. + * + * These fields are populated during {@link net.minecraftforge.fml.common.event.FMLPreInitializationEvent}. + * + * {@code public static @BloodMagicPlugin.Inject IBloodMagicAPI API_INSTANCE = null;} + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.FIELD) + @interface Inject { + + } } diff --git a/src/api/java/WayofTime/bloodmagic/api/IBloodMagicAPI.java b/src/api/java/WayofTime/bloodmagic/api/IBloodMagicAPI.java index 776bee6d..11ce4bce 100644 --- a/src/api/java/WayofTime/bloodmagic/api/IBloodMagicAPI.java +++ b/src/api/java/WayofTime/bloodmagic/api/IBloodMagicAPI.java @@ -6,6 +6,10 @@ import javax.annotation.Nonnull; /** * The main interface between a plugin and Blood Magic's internals. + * + * This API is intended for compatibility between other mods and Blood Magic. More advanced integration is out of the scope of this API and are considered "addons". + * + * To get an instance of this without actually creating an {@link IBloodMagicPlugin}, use {@link BloodMagicPlugin.Inject}. */ public interface IBloodMagicAPI { diff --git a/src/api/java/WayofTime/bloodmagic/api/IBloodMagicPlugin.java b/src/api/java/WayofTime/bloodmagic/api/IBloodMagicPlugin.java index 78ac7036..c18d3c1d 100644 --- a/src/api/java/WayofTime/bloodmagic/api/IBloodMagicPlugin.java +++ b/src/api/java/WayofTime/bloodmagic/api/IBloodMagicPlugin.java @@ -7,9 +7,20 @@ package WayofTime.bloodmagic.api; public interface IBloodMagicPlugin { /** - * Register mod content with the API + * Register mod content with the API. Called during {@link net.minecraftforge.fml.common.event.FMLInitializationEvent}. * * @param api The active instance of the {@link IBloodMagicAPI} */ - void register(IBloodMagicAPI api); + default void register(IBloodMagicAPI api) { + // No-op + } + + /** + * Register recipes with the API. Called during {@link net.minecraftforge.event.RegistryEvent.Register}. + * + * @param recipeRegistrar The active instance of the {@link IBloodMagicRecipeRegistrar} + */ + default void registerRecipes(IBloodMagicRecipeRegistrar recipeRegistrar) { + // No-op + } } diff --git a/src/main/java/WayofTime/bloodmagic/BloodMagic.java b/src/main/java/WayofTime/bloodmagic/BloodMagic.java index 0b1122f4..ce2f8153 100644 --- a/src/main/java/WayofTime/bloodmagic/BloodMagic.java +++ b/src/main/java/WayofTime/bloodmagic/BloodMagic.java @@ -73,6 +73,7 @@ public class BloodMagic { configDir = new File(event.getModConfigurationDirectory(), "bloodmagic"); PLUGINS.addAll(PluginUtil.gatherPlugins(event.getAsmData())); + PluginUtil.injectAPIInstances(PluginUtil.gatherInjections(event.getAsmData())); ModTranquilityHandlers.init(); ModDungeons.init(); @@ -84,7 +85,7 @@ public class BloodMagic { public void init(FMLInitializationEvent event) { BloodMagicPacketHandler.init(); - PluginUtil.registerPlugins(PLUGINS); + PluginUtil.handlePluginStep(PluginUtil.RegistrationStep.PLUGIN_REGISTER); ModRecipes.init(); ModRituals.initRituals(); diff --git a/src/main/java/WayofTime/bloodmagic/api/impl/BloodMagicCorePlugin.java b/src/main/java/WayofTime/bloodmagic/api/impl/BloodMagicCorePlugin.java index 9f98f857..453d5174 100644 --- a/src/main/java/WayofTime/bloodmagic/api/impl/BloodMagicCorePlugin.java +++ b/src/main/java/WayofTime/bloodmagic/api/impl/BloodMagicCorePlugin.java @@ -6,6 +6,7 @@ import WayofTime.bloodmagic.api.BloodMagicPlugin; import WayofTime.bloodmagic.api.IBloodMagicAPI; import WayofTime.bloodmagic.api.IBloodMagicPlugin; import WayofTime.bloodmagic.altar.EnumAltarComponent; +import WayofTime.bloodmagic.api.IBloodMagicRecipeRegistrar; import WayofTime.bloodmagic.block.BlockBloodRune; import WayofTime.bloodmagic.block.BlockDecorative; import WayofTime.bloodmagic.block.enums.BloodRuneType; @@ -78,11 +79,14 @@ public class BloodMagicCorePlugin implements IBloodMagicPlugin { BlockBloodRune bloodRune = (BlockBloodRune) RegistrarBloodMagicBlocks.BLOOD_RUNE; for (BloodRuneType runeType : BloodRuneType.values()) api.registerAltarComponent(bloodRune.getDefaultState().withProperty(bloodRune.getProperty(), runeType), EnumAltarComponent.BLOODRUNE.name()); + } - RegistrarBloodMagicRecipes.registerAltarRecipes(api.getRecipeRegistrar()); - RegistrarBloodMagicRecipes.registerAlchemyTableRecipes(api.getRecipeRegistrar()); - RegistrarBloodMagicRecipes.registerTartaricForgeRecipes(api.getRecipeRegistrar()); - RegistrarBloodMagicRecipes.registerAlchemyArrayRecipes(api.getRecipeRegistrar()); + @Override + public void registerRecipes(IBloodMagicRecipeRegistrar recipeRegistrar) { + RegistrarBloodMagicRecipes.registerAltarRecipes((BloodMagicRecipeRegistrar) recipeRegistrar); + RegistrarBloodMagicRecipes.registerAlchemyTableRecipes((BloodMagicRecipeRegistrar) recipeRegistrar); + RegistrarBloodMagicRecipes.registerTartaricForgeRecipes((BloodMagicRecipeRegistrar) recipeRegistrar); + RegistrarBloodMagicRecipes.registerAlchemyArrayRecipes((BloodMagicRecipeRegistrar) recipeRegistrar); } private static void handleConfigValues(BloodMagicAPI api) { diff --git a/src/main/java/WayofTime/bloodmagic/core/RegistrarBloodMagicRecipes.java b/src/main/java/WayofTime/bloodmagic/core/RegistrarBloodMagicRecipes.java index 298b7480..85ceebe4 100644 --- a/src/main/java/WayofTime/bloodmagic/core/RegistrarBloodMagicRecipes.java +++ b/src/main/java/WayofTime/bloodmagic/core/RegistrarBloodMagicRecipes.java @@ -12,6 +12,7 @@ import WayofTime.bloodmagic.item.alchemy.ItemCuttingFluid; import WayofTime.bloodmagic.item.alchemy.ItemLivingArmourPointsUpgrade; import WayofTime.bloodmagic.item.soul.ItemSoulGem; import WayofTime.bloodmagic.item.types.ComponentTypes; +import WayofTime.bloodmagic.util.PluginUtil; import com.google.common.collect.Sets; import net.minecraft.init.Blocks; import net.minecraft.init.Items; @@ -51,6 +52,8 @@ public class RegistrarBloodMagicRecipes { OreDictionary.registerOre("dustIron", ComponentTypes.SAND_IRON.getStack()); OreDictionary.registerOre("dustGold", ComponentTypes.SAND_GOLD.getStack()); OreDictionary.registerOre("dustCoal", ComponentTypes.SAND_COAL.getStack()); + + PluginUtil.handlePluginStep(PluginUtil.RegistrationStep.RECIPE_REGISTER); } public static void registerAltarRecipes(BloodMagicRecipeRegistrar registrar) { diff --git a/src/main/java/WayofTime/bloodmagic/util/PluginUtil.java b/src/main/java/WayofTime/bloodmagic/util/PluginUtil.java index 2faf5d38..aeed6037 100644 --- a/src/main/java/WayofTime/bloodmagic/util/PluginUtil.java +++ b/src/main/java/WayofTime/bloodmagic/util/PluginUtil.java @@ -1,17 +1,23 @@ package WayofTime.bloodmagic.util; +import WayofTime.bloodmagic.BloodMagic; import WayofTime.bloodmagic.api.BloodMagicPlugin; +import WayofTime.bloodmagic.api.IBloodMagicAPI; import WayofTime.bloodmagic.api.IBloodMagicPlugin; import WayofTime.bloodmagic.api.impl.BloodMagicAPI; import WayofTime.bloodmagic.api.impl.BloodMagicCorePlugin; import com.google.common.base.Stopwatch; import com.google.common.collect.Lists; +import net.minecraftforge.common.util.EnumHelper; import net.minecraftforge.fml.common.discovery.ASMDataTable; import org.apache.commons.lang3.tuple.Pair; import javax.annotation.Nonnull; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; import java.util.List; import java.util.Set; +import java.util.function.Consumer; public class PluginUtil { @@ -20,7 +26,7 @@ public class PluginUtil { public static List> gatherPlugins(ASMDataTable dataTable) { Stopwatch stopwatch = Stopwatch.createStarted(); List> discoveredAnnotations = Lists.newArrayList(); - Set discoveredPlugins = dataTable.getAll(BloodMagicPlugin.class.getCanonicalName()); + Set discoveredPlugins = dataTable.getAll(BloodMagicPlugin.class.getName()); for (ASMDataTable.ASMData data : discoveredPlugins) { try { @@ -47,20 +53,85 @@ public class PluginUtil { return discoveredAnnotations; } - public static void registerPlugins(List> plugins) { - Stopwatch total = Stopwatch.createStarted(); - int errors = 0; - for (Pair plugin : plugins) { - Stopwatch per = Stopwatch.createStarted(); + @Nonnull + public static List gatherInjections(ASMDataTable dataTable) { + Stopwatch stopwatch = Stopwatch.createStarted(); + List injectees = Lists.newArrayList(); + Set discoveredInjectees = dataTable.getAll(BloodMagicPlugin.Inject.class.getName()); + + for (ASMDataTable.ASMData data : discoveredInjectees) { try { - plugin.getLeft().register(BloodMagicAPI.INSTANCE); + Class asmClass = Class.forName(data.getClassName()); + Field toInject = asmClass.getDeclaredField(data.getObjectName()); + if (toInject.getType() != IBloodMagicAPI.class) { + BMLog.API.error("Mod requested API injection on field {}.{} which is an invalid type.", data.getClassName(), data.getObjectName()); + continue; + } + + BMLog.API.info("Discovered injection request at {}.{}", data.getClassName(), data.getObjectName()); + injectees.add(toInject); } catch (Exception e) { - errors++; - BMLog.DEFAULT.error("Error loading plugin at {}: {}: {}", plugin.getLeft().getClass(), e.getClass().getSimpleName(), e.getMessage()); + e.printStackTrace(); } - BMLog.API.info("Registered plugin at {} in {}", plugin.getLeft().getClass(), per.stop()); } - BMLog.API.info("Registered {} plugins with {} errors in {}", plugins.size() - errors, errors, total.stop()); + BMLog.API.info("Discovered {} potential API injection(s) in {}", injectees.size(), stopwatch.stop()); + return injectees; + } + + public static void handlePluginStep(RegistrationStep step) { + Stopwatch total = Stopwatch.createStarted(); + int errors = 0; + for (Pair plugin : BloodMagic.PLUGINS) { + Stopwatch per = Stopwatch.createStarted(); + try { + step.getConsumer().accept(plugin); + } catch (Exception e) { + errors++; + BMLog.DEFAULT.error("Error handling plugin step {} at {}: {}: {}", step, plugin.getLeft().getClass(), e.getClass().getSimpleName(), e.getMessage()); + } + BMLog.API.info("Handled plugin step {} at {} in {}", step, plugin.getLeft().getClass(), per.stop()); + } + + BMLog.API.info("Handled {} plugin(s) at step {} with {} errors in {}", BloodMagic.PLUGINS.size() - errors, step, errors, total.stop()); + } + + public static void injectAPIInstances(List injectees) { + Stopwatch total = Stopwatch.createStarted(); + int errors = 0; + + for (Field injectee : injectees) { + Stopwatch per = Stopwatch.createStarted(); + if (!Modifier.isStatic(injectee.getModifiers())) + continue; + + try { + EnumHelper.setFailsafeFieldValue(injectee, null, BloodMagicAPI.INSTANCE); + } catch (Exception e) { + errors++; + BMLog.DEFAULT.error("Error injecting API instance at {}.{}", injectee.getDeclaringClass().getCanonicalName(), injectee.getName()); + } + BMLog.API.info("Injected API instance at {}.{} in {}", injectee.getDeclaringClass().getCanonicalName(), injectee.getName(), per.stop()); + } + + BMLog.API.info("Injected API {} times with {} errors in {}", injectees.size() - errors, errors, total.stop()); + } + + public enum RegistrationStep { + + PLUGIN_REGISTER(p -> p.getLeft().register(BloodMagicAPI.INSTANCE)), + RECIPE_REGISTER(p -> p.getLeft().registerRecipes(BloodMagicAPI.INSTANCE.getRecipeRegistrar())) + ; + + private final Consumer> consumer; + + RegistrationStep(Consumer> consumer) { + this.consumer = consumer; + } + + @Nonnull + public Consumer> getConsumer() { + return consumer; + } } }