User Tools

Site Tools


tutorial:mixinheritance

DRAFT:

Mixin Inheritance

Motivation

The Mixin framework enables us as modders to make a number of precise changes to Minecraft's decompiled source code, including such things as:

  • Adding new methods and fields to a vanilla class
  • Implementing new interfaces onto classes
  • Overriding methods of a superclass in a subclass to create extra functionality
  • Change the behavior of vanilla methods

With such a powerful and granular tool at our disposal, it's worth considering whether any given structure you employ for your code might have implications for compatibility with other mods. Not all mods need to tout themselves as fundamentally compatible with others, but most mods unless otherwise stated are expected to try to maintain a minimum of compatibility in terms of Mixin techniques used. Example techniques for good compatibility include using the @Unique annotation for custom fields, preferring @Inject to more invasive @Redirect and @Overwrite where possible, and applying Mixins to as few methods and classes as possible to create the desired behavior.

Sometimes, the scope of a given mod necessitates sweeping changes to one or more pieces of vanilla code. Consider a mod that adds some kind of functionality to LivingEntity, the class that governs shared behavior of mobs, villagers, and even players. It could be the case that the general code added or changed in LivingEntity could create a bug due to interactions with code in one of the subclasses of LivingEntity, perhaps PlayerEntity. We could do something simple like make a new Mixin class that targets PlayerEntity and applies the necessary changes, and most of the time that would be perfectly reasonable. But let's say that our mod is ambitious and we end up needing a certain @Redirect in PlayerEntity to achieve our desired behavior. Now, what would happen if another mod ends up using a @Redirect on the same target? Well, one of the Mixins would have to fail!

A Technique For Improved Compatibility

Mixin Inheritance is one solution to the hypothetical conflict posed in our example. The basic form of Mixin Inheritance is to use a parent Mixin to “bookmark” a position in a vanilla method where we wish to apply changes, and to then use child Mixins that target individual classes which may require tailored code in order to work properly. Let's take a look at the outline:

Parent Mixin
  1. @Mixin(Parent.class)
  2. public abstract class ParentMixin {
  3. @Inject(method = "something", at = @At("SOMEWHERE"))
  4. protected void handlerMethod(CallbackInfo ci) {
  5. //Leave empty
  6. }
  7. }
Child Mixin
  1. @Mixin(Child.class)
  2. public abstract class ChildMixin extends ParentMixin {
  3. @Override
  4. protected void handlerMethod(CallbackInfo ci) {
  5. doStuff(); // Actual behaviour
  6. }
  7. }

Notably, we are “spending” our ChildMixin's extends keyword to designate ParentMixin as its superclass. This is somewhat atypical: often for ease of use, we'd have a Mixin class extend the target class' parent, rather than some other Mixin class. Our reason for doing so is to unlock for ourselves the possibility of using @Override to perform a neat sleight-of-hand in our target child class: because of how Mixins are applied to their targets, as long as the compiler is willing to accept our inheritance structure, the final result of this form is to have `Child` perform whatever code we provide in its @Override at whatever location we pinpointed in ParentMixin's method annotations. Had we tried to do things with the conventional approach, we'd either end up replacing all of the code in Child's inherited version of Parent's method and thus need to re-write a hefty amount of code, or we'd have to deal with the compatibility problems mentioned above.

tutorial/mixinheritance.txt · Last modified: 2021/03/16 17:19 by sailkite