:!: //This page was recently pushed, and may still have inaccuracies or lacking in a few aspects. Feedback and improvements are greatly appreciated!// ====== Tutorial: Making your first Mixin ====== ===== Preamble ===== This is meant to be complementary to the [[tutorial:mixin_introduction|Introduction to Mixins]] page, it is not required but highly recommended to read it before this tutorial. This tutorial intends to hold a newcomer's hand through each step of creating a very elementary Mixin. This intends to create familiarity with basic Mixin notions and syntax, thus lessening the esoteric appearance Mixins have during their initial learning curve.\\ See [[tutorial:mixin_registration|Registering Mixins]] for setting up Mixins on a Fabric project if you intend to follow along with this. This tutorial will be made using a 1.21.1 Fabric Project as reference. It is recommended to develop using IntelliJ IDEa Community Edition to be able to leverage the [[https://mcdev.io/|Minecraft Development plugin]] by demonwav, AKA MCDev. Yarn mappings will be used. If you wish to follow along with any other set of mappings, you may use a tool such as [[https://mappings.dev/|mappings.dev]] or [[https://linkie.shedaniel.dev/mappings|Shedaniel.dev's mappings tool]] to translate between the Yarn mappings showcased and the ones in your development environment as needed. ---- ===== Creating the Mixin Class ===== For this tutorial, we'll add a Logger call at the head of the ''loadWorld'' method in the Vanilla ''MinecraftServer'' class. This will be based on the ''ExampleMixin'' Mixin class present in the Fabric mod template at the time of writing, with some tweaks. For this task, we'll use the injector ''@Inject'', and explain the different elements as we build it. Mixin uses annotations to parse metadata that will be used to find where and how to specifically try and apply our changes. It is important to note that we use ''@Inject'' specifically because we only need to insert a single method call at a given point. One must always consider which tool to use for the job, often it is not ''@Inject'', and it may not even be right to use a Mixin in the first place.\\ The reason ''@Inject'' is the right tool here is because it injects a method call where specified, and that's all we need to do. It is as such the smallest, least intrusive change that can be made. After following the necessary steps described in [[tutorial:mixin_registration|Registering Mixins]] to set up our ''mixins.json'' config, we'll create our class in our dedicated Mixin package. We'll call this class ''TutorialFirstMixin'': @Mixin(MinecraftServer.class) abstract class TutorialFirstMixin { } It is conventional practice to name your Mixin classes the target class with ''Mixin'' appended as a suffix. Here, it would typically be named ''MinecraftServerMixin'' instead. This makes is very easy for someone who is looking through your project to know which Mixin classes have a given target. If you, for whatever reason, opt to not follow this convention, it is recommended to still name your Mixins after what they target in some way. You'll notice we made the Mixin class ''abstract''. This is a best practice when mixing into a class, as it avoids an accidental instantiation of the class, and removes the burden of needing to implement methods that may break the Mixin when implementing and extending the target's superclasses and interfaces. Accordingly, we will also add this to our ''mixins.json'' config file: { ... "mixins": [ "TutorialFirstMixin" ], ... } And that's all there is to actually creating the Mixin Class, but it won't do anything on its own. ---- ===== Creating the injector ===== We have a Mixin class, now let's use it to do something. The first step, assuming nothing has caused a crash or gone to hell yet, is to create a "stub" method, AKA a method that's just a placeholder for our actual method. This will be the method we annotate with ''@Inject'', and putting it there before the annotation allows us to not be bothered by the ''Annotations are not allowed here'' IDE error from having a lone annotation. @Mixin(MinecraftServer.class) abstract class TutorialFirstMixin { private void logOnWorldLoad() { } } Your Mixin injectors' methods, known as the handler, should always describe what it does rather than what it is. For example, in this tutorial it will be called ''logOnWorldLoad''. We follow this convention to ease debugging as your handler name will appear in stack traces. Next, we add the annotation: abstract class TutorialFirstMixin { @Inject(method = "method_3735") private void logOnWorldLoad() { } } This now specifies to Mixin that, when this method gets merged into ''MinecraftServer'', we want to inject a call to it in the method ''loadWorld''. Since there is only one method of that name in ''MinecraftServer'', the name is enough, this would not be the case if the method had overloads. If you're using the Minecraft Development plugin, you should almost always leverage the plugin's autocomplete for a Mixin annotation's attributes to ensure accuracy. Now, to specify where in the targeted method we want our injection to happen, we add something that may be a bit counterintuitive to some, an ''@At'' annotation as one of the arguments in the ''@Inject'' annotation: abstract class TutorialFirstMixin { @Inject(method = "method_3735", at = @At(value = "HEAD")) private void logOnWorldLoad() { } } Now, let's look closer at the annotations we have thus far. Think of them as holding a series of hierarchized instructions. A very simplified explanation would be: * ''@Inject'' says that tells Mixin that it should inject a call to this method at specified point(s) * ''method = ""'' narrows down the point of injection to within that target method * ''at = @At'' is used to specify what to look for in the targeted method(s) * ''value = "HEAD"'' specifies that Mixin should try to inject at the very start of the method at the point of injection. //Sidenote: one does not need to put ''value ='' if it is the only argument passed to ''@At'', e.g. ''@At("HEAD")'' is valid.// Now, if you're following along with MCDev you'll likely notice that our handler method, ''logOnWorldLoad'', does not have the correct signature for the target. In most cases, using MCDev's context action that quickly fixes it should work. ''@Inject'' handler methods must either contain none of the target method's parameters, or all of the target method's parameters in the same order -- here there are none -- followed by a ''CallbackInfo'' parameter, conventionally called ''ci'', if injecting into a method of ''void'' return type. If we were injecting into a method that returns a value, it would instead be ''CallbackInfoReturnable cir'' where ''T'' is the return type of the target method, such as a ''Boolean''. If we fix the signature and add a call to our mod's logger, the full class becomes: @Mixin(MinecraftServer.class) abstract class TutorialFirstMixin { @Inject(method = "method_3735", at = @At(value = "HEAD")) private void logOnWorldLoad(CallbackInfo ci) { ExampleMod.LOGGER.info("MinecraftServer$method_3735 has started!") } } And that's it for making this Mixin! ---- ===== General Advice ===== ==== Extending the target class's parents ==== When the class you're targeting extends and/or implements parents, mimicking that on your Mixin class is an overall benefit, as it allows you to get all of the parent methods directly. For our example Mixin it'll look like: abstract class TutorialFirstMixin extends class_4093 implements QueryableServer, ChunkErrorHandler, class_2165, AutoCloseable { public TutorialFirstMixin(String string) { super(string) } @Inject(method = "method_3735", at = @At(value = "HEAD")) private void logOnWorldLoad(CallbackInfo ci) { ExampleMod.LOGGER.info("MinecraftServer$method_3735 has started!"); } } The constructor is functionally irrelevant here, it's only there for the sake of extending without errors. One may notice that we didn't implement any methods, this is thanks to our class being abstract. Abstract classes do not need to implement methods from parents, allowing us to not have to handle all the overrides you would normally get. ==== Debugging Mixins ==== === Exporting === Mixin provides debugging utilities, one of them is the ability to export the transformed classes once they're loaded. This allows to be able to specifically check what our Mixin has targeted and if it has applied where we wanted it to.\\ This section's information is detailed upon more specifically and quicker at the [[tutorial:mixin_export|Exporting Mixin Classes]] page. Exporting our Mixin would look like: @Debug(export = true) @Mixin(MinecraftServer.class) abstract class TutorialFirstMixin extends class_4093 implements QueryableServer, ChunkErrorHandler, class_2165, AutoCloseable { @Inject(method = "method_3735", at = @At(value = "HEAD")) private void logOnWorldLoad(CallbackInfo ci) { ExampleMod.LOGGER.info("MinecraftServer$method_3735 has started!") } } Setting a Mixin to be exported makes it so that once the targeted class has been transformed and has loaded (which could be anytime between the client starting and a world loading) Mixin will export a version of that now transformed class. It should be exported to the directory ''run/.mixin.out'', and can be read using a decompiler or IDE equipped with one. You can also configure your development runs to export //all// Mixins by adding the following argument to your VM options: ''-Dmixin.debug.export=true'' :!: If you use the ''@Debug'' annotation, remember to disable it before building into a release-intended ''jar'' file. === Why do my merged Mixins' names look so weird? === One thing you will notice in an exported class is that your Mixin handler methods' names look odd when merged into the target class. This is the result of a process known as "name mangling" or just "mangling". This process consists of procedurally modifying the merged member names such that they are unique. This is first and foremost for compatibility reasons, as two Mixins adding methods of the same name may cause a conflict even if the injectors target entirely different things. Injector methods are also given an identifier corresponding to the type of injector as part of mangling. For our case, ''@Inject'' has the prefix ''handler''. === Breakpoints and MCDev === Breakpoints don't natively work with Mixin classes as they get merged at runtime. However, MCDev adds breakpoint support in Mixin Classes directly, allowing you to debug them by directly putting a breakpoint there, rather than having to try and anticipate your injector in the target class. === Loggers === Logger calls can be very handy when trying to debug complex logical flows or just for general warning and error notifications. And whilst that may be useful for debugging, one should be particularly careful when using them anywhere that'll be called very frequently, for performance and log readability reasons. This applies just the same with Mixins, and one should in general not use loggers as their primary form of debugging. ---- ===== Closing Thoughts and where to go next ===== Assuming nothing blew up, you've completed your first functioning Mixin, and hopefully you learned a bit about how Mixin is used and broadly works along the way. //What now?// Some principles to keep in mind are that Mixin is a very case-by-case tool, so this injector example was tailored to its specific use-case, but Mixin and MixinExtras provide a multitude of tools to use for different scenarios. Knowing which one to use comes down to knowing what effect you want to have in the code, and what is the most compatible and smallest change you can make to the target for your goal. You must also remember that Mixin is not always the solution, there are times where events or other modding framework tools outside of Mixins are already available, and you should prioritize using those rather than Mixins if you can afford to, as it is typically safer for compatibility. ==== Further Reading ==== For more thorough and formal intros to the topic, you should read through the [[tutorial:mixin_introduction|Mixin Introduction]] page and especially its linked Wikis.\\ Namely, those are the [[https://github.com/SpongePowered/Mixin/wiki/|Mixin Wiki]] and the [[https://github.com/LlamaLad7/MixinExtras/wiki|MixinExtras Wiki]]. You may also find further information about specific tools in the relevant JavaDocs. Other than that, the most important step from here is trial, error, and asking for help/information in the relevant support channels when stuck. This is an entirely expected part of the learning process, and even experienced devs ask questions about how to best approach an issue with Mixin.