User Tools

Site Tools


tutorial:mixin_your_first_mixin

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
tutorial:mixin_your_first_mixin [2025/10/16 10:08] – Extra tweaks; Likely soon exiting drafting; Remove tutorial repo links gauntreclusetutorial:mixin_your_first_mixin [2025/11/16 13:35] (current) – Minor fixes to try and simplify wordings and fix typos gauntrecluse
Line 1: Line 1:
-:!: //This page is currently a draft! It is not intended to be read by users of the wiki yet. Feedback will be appreciated but trust this page's information at your own risk!//\\ +====== Tutorial: Making your first Mixin ======
-//Contact GauntRecluse (paleintrovert) on Discord to give feedback and suggestions on this draft// +
- +
-====== Tutorial: Making your first Mixin (DRAFT) ======+
  
 ===== Preamble ===== ===== 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.\\+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, reducing how strange Mixins can seem during an 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. See [[tutorial:mixin_registration|Registering Mixins]] for setting up Mixins on a Fabric project if you intend to follow along with this.
  
Line 18: Line 15:
 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. 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, and many times it is not ''@Inject'', and it may not even be right to use a Mixin in the first place.\\+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. 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.
  
Line 24: Line 21:
 <yarncode java> <yarncode java>
 @Mixin(MinecraftServer.class) @Mixin(MinecraftServer.class)
-public abstract class TutorialFirstMixin {+abstract class TutorialFirstMixin {
  
 } }
 </yarncode> </yarncode>
  
-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.+It is conventional practice to name your Mixin classes the target class with ''Mixin'' appended as a suffix or prefix. 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 or aim to modify.
  
-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.+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. We've also not made it ''public'', this makes it package-private and is best-practice since Mixin classes shouldn't be accessed or instantiated outside of their specific packages.
  
 Accordingly, we will also add this to our ''mixins.json'' config file: Accordingly, we will also add this to our ''mixins.json'' config file:
Line 55: Line 52:
 <yarncode java> <yarncode java>
 @Mixin(MinecraftServer.class) @Mixin(MinecraftServer.class)
-public abstract class TutorialFirstMixin {+abstract class TutorialFirstMixin {
  
-    private void addLoggerAtHead() {+    private void logOnWorldLoad() {
     }     }
 } }
 </yarncode> </yarncode>
  
-Your Mixin injectors' methods should always try to best describe their intended effects on the targetOther than for injector handler methodssuch as methods annotated with ''@Inject'', and ''@Shadow'' members, one should use a ''modid$name'' naming convention as to prevent mod conflicts.+Your Mixin injectors' methods, known as the handler, should always describe what it does rather than what it isFor examplein 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: Next, we add the annotation:
 <yarncode java> <yarncode java>
-public abstract class TutorialFirstMixin {+abstract class TutorialFirstMixin {
  
     @Inject(method = "method_3735")     @Inject(method = "method_3735")
-    private void addLoggerAtHead() {+    private void logOnWorldLoad() {
          
     }     }
Line 75: Line 72:
 </yarncode> </yarncode>
  
-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'fields to ensure accuracy.+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'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: 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:
 <yarncode java> <yarncode java>
-public abstract class TutorialFirstMixin {+abstract class TutorialFirstMixin {
  
-    @Inject(method = "method_3735", +    @Inject(method = "method_3735", at = @At(value = "HEAD")) 
-            at = @At(value = "HEAD") +    private void logOnWorldLoad() {
-    +
-    private void addLoggerAtHead() {+
     }     }
 } }
 </yarncode> </yarncode>
- 
-It is not necessary to put the different fields on different lines in your code, but it may become useful when the individual arguments are very long or if you prefer it for organization purposes. 
  
 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: 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:
Line 95: Line 88:
     *  ''method = "<yarn method_3735>"'' narrows down the point of injection to within that target method     *  ''method = "<yarn method_3735>"'' 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)     * ''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.+      * ''value = "HEAD"'' specifies that Mixin should try to inject at the very start of the method as 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.// //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, ''addLoggerAtHead'', 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 contain the target method's parameters -- here there are none -- and a ''CallbackInfo'' parameter, often 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<T> cir'' where ''T'' is the return type of the target method, such as a ''Boolean''.+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<T> 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: If we fix the signature and add a call to our mod's logger, the full class becomes:
 <yarncode java> <yarncode java>
 @Mixin(MinecraftServer.class) @Mixin(MinecraftServer.class)
-public abstract class TutorialFirstMixin {+abstract class TutorialFirstMixin {
  
-    @Inject(method = "method_3735", +    @Inject(method = "method_3735", at = @At(value = "HEAD")) 
-            at = @At(value = "HEAD") +    private void logOnWorldLoad(CallbackInfo ci) {
-    +
-    private void addLoggerAtHead(CallbackInfo ci) {+
         ExampleMod.LOGGER.info("MinecraftServer$method_3735 has started!")         ExampleMod.LOGGER.info("MinecraftServer$method_3735 has started!")
     }     }
Line 124: Line 115:
 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: 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:
 <yarncode java> <yarncode java>
-public abstract class TutorialFirstMixin extends class_4093<class_3738> implements QueryableServer, ChunkErrorHandler, class_2165, AutoCloseable {+abstract class TutorialFirstMixin extends class_4093<class_3738> implements QueryableServer, ChunkErrorHandler, class_2165, AutoCloseable {
     public TutorialFirstMixin(String string) {     public TutorialFirstMixin(String string) {
         super(string)         super(string)
     }     }
  
-    @Inject(method = "method_3735", +    @Inject(method = "method_3735", at = @At(value = "HEAD")) 
-            at = @At(value = "HEAD") +    private void logOnWorldLoad(CallbackInfo ci) { 
-    +        ExampleMod.LOGGER.info("MinecraftServer$method_3735 has started!");
-    private void addLoggerAtHead(CallbackInfo ci) { +
-        GauntTutorialMod.LOGGER.info("MinecraftServer$method_3735 has started!");+
     }     }
 } }
 </yarncode> </yarncode>
  
-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.+The constructor does not impact functionality, it's only there for the sake of extending without errors. Notice that we didn't implement any methods from the supers, 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 ==== ==== Debugging Mixins ====
Line 150: Line 139:
 @Debug(export = true) @Debug(export = true)
 @Mixin(MinecraftServer.class) @Mixin(MinecraftServer.class)
-public abstract class TutorialFirstMixin extends class_4093<class_3738> implements QueryableServer, ChunkErrorHandler, class_2165, AutoCloseable {+abstract class TutorialFirstMixin extends class_4093<class_3738> implements QueryableServer, ChunkErrorHandler, class_2165, AutoCloseable {
  
-    @Inject(method = "method_3735", +    @Inject(method = "method_3735", at = @At(value = "HEAD")) 
-            at = @At(value = "HEAD") +    private void logOnWorldLoad(CallbackInfo ci) { 
-    +        ExampleMod.LOGGER.info("MinecraftServer$method_3735 has started!")
-    private void addLoggerAtHead(CallbackInfo ci) { +
-        GauntTutorialMod.LOGGER.info("MinecraftServer$method_3735 has started!")+
     }     }
 } }
 </yarncode> </yarncode>
-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''+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. :!: If you use the ''@Debug'' annotation, remember to disable it before building into a release-intended ''jar'' file.
tutorial/mixin_your_first_mixin.1760609321.txt.gz · Last modified: 2025/10/16 10:08 by gauntrecluse