DRAFT:
The Mixin framework enables us as modders to make a number of precise changes to Minecraft's decompiled source code, including such things as:
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!
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:
@Mixin(Parent.class) public abstract class ParentMixin { @Inject(method = "something", at = @At("SOMEWHERE")) protected void handlerMethod(CallbackInfo ci) { //Leave empty } }
@Mixin(Child.class) public abstract class ChildMixin extends ParentMixin { @Override protected void handlerMethod(CallbackInfo ci) { doStuff(); // Actual behaviour } }
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.