tutorial:mixin_your_first_mixin
Differences
This shows you the differences between two versions of the page.
Both sides previous revisionPrevious revisionNext revision | Previous revision | ||
tutorial:mixin_your_first_mixin [2025/10/10 15:10] – removed - external edit (Unknown date) 127.0.0.1 | tutorial:mixin_your_first_mixin [2025/10/10 16:00] (current) – Misc. changes, elaborate closing thoughts 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!//\\ | ||
+ | //Contact GauntRecluse (paleintrovert) on Discord to give feedback and suggestions on this draft// | ||
+ | ====== Tutorial: Making your first Mixin (DRAFT) ====== | ||
+ | |||
+ | ===== Preamble ===== | ||
+ | |||
+ | This is meant to be complementary to the [[tutorial: | ||
+ | |||
+ | 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:// | ||
+ | |||
+ | The repository containing this tutorial' | ||
+ | |||
+ | ===== Creating the Mixin Class ===== | ||
+ | For this tutorial, we'll add a Logger call at the head of the '' | ||
+ | |||
+ | For this task, we'll use the Mixin injector annotation '' | ||
+ | |||
+ | It is important to note that we use '' | ||
+ | The reason '' | ||
+ | |||
+ | After following the necessary steps described in [[tutorial: | ||
+ | < | ||
+ | @Mixin(MinecraftServer.class) | ||
+ | public abstract class TutorialFirstMixin { | ||
+ | |||
+ | } | ||
+ | </ | ||
+ | |||
+ | It is conventional practice to name your Mixin classes the target class with '' | ||
+ | |||
+ | You'll notice we made the Mixin class '' | ||
+ | |||
+ | Accordingly, | ||
+ | Adding to our config file: | ||
+ | <code json> | ||
+ | { | ||
+ | ... | ||
+ | " | ||
+ | " | ||
+ | ], | ||
+ | ... | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | 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 " | ||
+ | < | ||
+ | @Mixin(MinecraftServer.class) | ||
+ | public abstract class TutorialFirstMixin { | ||
+ | |||
+ | private void addLoggerAtHead() { | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | We've named it '' | ||
+ | |||
+ | We now add our '' | ||
+ | < | ||
+ | public abstract class TutorialFirstMixin { | ||
+ | |||
+ | @Inject(method = " | ||
+ | private void addLoggerAtHead() { | ||
+ | | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | This now specifies to Mixin that, when that method gets merged into '' | ||
+ | |||
+ | 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 '' | ||
+ | < | ||
+ | public abstract class TutorialFirstMixin { | ||
+ | |||
+ | @Inject(method = " | ||
+ | at = @At(value = " | ||
+ | ) | ||
+ | private void addLoggerAtHead() { | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | 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: | ||
+ | * '' | ||
+ | * '' | ||
+ | * '' | ||
+ | * '' | ||
+ | |||
+ | //Sidenote: one does not need to put '' | ||
+ | |||
+ | Now, if you're following along with MCDev you'll likely notice that our handler method, '' | ||
+ | |||
+ | If we fix the signature and add a logger call, the full class becomes: | ||
+ | < | ||
+ | @Mixin(MinecraftServer.class) | ||
+ | public abstract class TutorialFirstMixin { | ||
+ | |||
+ | @Inject(method = " | ||
+ | at = @At(value = " | ||
+ | ) | ||
+ | private void addLoggerAtHead(CallbackInfo ci) { | ||
+ | GauntTutorialMod.LOGGER.info(" | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | And that's it for making this Mixin work! But let's go over some general advice that will come in handy as you learn Mixins: | ||
+ | |||
+ | ---- | ||
+ | |||
+ | ===== General Advice ===== | ||
+ | |||
+ | ==== Extending the target class' | ||
+ | |||
+ | 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: | ||
+ | < | ||
+ | public abstract class TutorialFirstMixin extends class_4093< | ||
+ | public TutorialFirstMixin(String string) { | ||
+ | super(string) | ||
+ | } | ||
+ | |||
+ | @Inject(method = " | ||
+ | at = @At(value = " | ||
+ | ) | ||
+ | private void addLoggerAtHead(CallbackInfo ci) { | ||
+ | GauntTutorialMod.LOGGER.info(" | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | The constructor is irrelevant here, it's only there for the sake of extending without errors. One may notice that we didn't implement any methods. That is another benefit of making our class abstract. Abstract classes, because they are never instantiated, | ||
+ | |||
+ | ---- | ||
+ | |||
+ | ==== Debugging Mixins ==== | ||
+ | |||
+ | === The export feature === | ||
+ | Mixin provides debugging utilities, one of them is the ability to export the transformed classes once they' | ||
+ | This section' | ||
+ | |||
+ | Exporting our Mixin would look like: | ||
+ | < | ||
+ | @Debug(export = true) | ||
+ | @Mixin(MinecraftServer.class) | ||
+ | public abstract class TutorialFirstMixin extends class_4093< | ||
+ | |||
+ | @Inject(method = " | ||
+ | at = @At(value = " | ||
+ | ) | ||
+ | private void addLoggerAtHead(CallbackInfo ci) { | ||
+ | GauntTutorialMod.LOGGER.info(" | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | 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 decompiled version of that now transformed class. It should be exported to the directory '' | ||
+ | |||
+ | === Why do my merged Mixins' | ||
+ | |||
+ | One thing you will notice in an exported class is that your Mixin handler methods' | ||
+ | |||
+ | :!: //The '' | ||
+ | |||
+ | === 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' | ||
+ | |||
+ | ---- | ||
+ | |||
+ | ===== 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: | ||
+ | Namely, those are the [[https:// | ||
+ | |||
+ | Other than that, the most important step from here is trial, error, and asking for help/ |