Table of Contents
This page is due rewrites because of inaccuracies. This page may change very suddenly and should be taken with a grain of salt.
@Inject
Introduction
In the context of Mixins, when talking about injects, it is typically in reference to the specific @Inject injector(i.e. “an inject”). This is not to be confused with injectors as a whole or the general process of injection, which include other injectors such as @ModifyArg.
@Inject is arguably the most intuitively understandable injector, and a detailed example can be found here.
Effects & Use-Cases
@Inject primarily injects a call to the annotated method at the specified point in the target method. It is also able to inject early return statements by cancelling
This makes @Inject primarily useful for “plain” injections of additional instructions, and maybe optionally injecting an early return where needed. Whilst versatile, it is often overused compared to other injectors that may be better-suited for the situation. Some generic use-cases where @Inject would be fitting may be:
- Listening to a specific method's operations, optionally cancelling the whole method. This can be used to create events or an injector with a purpose similar to an event.
- Note that depending on the specific operation, some injectors able to get more context about individual operations may be more suitable.
- Adding a consistent operation at a part of a target method, without the intent of replacing any of the existing operations.
- Populating fields added by a Mixin class, via an injection at the tail of the target class's constructor.
- Adding new operations at the head or tail of a method body.
Limitations and Alternatives
Cancelling, Modifying or Diverting individual operations
It is important to not use @Inject, and in particular its cancelling, if the goal can be achieved with a more precise and less intrusive changes. Injectors such as @WrapOperation, @ModifyExpressionValue, @ModifyReturnValue or @ModifyArg are better-suited for preventing, modifying or diverting individual calls or operations, without cancelling the entire method; or applying modifications to a return value.
Context-Sensitive Injections
This section should use a more concrete example.
@Inject lacks context when injecting relative to a specific operation.
For example, say we had the following code snippet:
public void calculateSlap(int quantity, Person haykam) { /*...*/ float force = complexCalculation(quantity); haykam.slap(quantity, force / haykam.resistance); /*...*/ }
if we wished to inject right after the slap call, we could use a Shift to inject after the call with @Inject. However, if we needed to also get the force / haykam.resistance value, it would be instead preferable to use a @WrapOperation to get it without needing to use @Local on force to then recalculate it.
For the sake of showing how to appropriately add our new operations after the original method call outside of void returns, we'll say slap has a boolean return value.
@WrapOperation(method = "calculateSlap", at = @At(value = "INVOKE", target = "Lnet/fabricmc/haykam_slapper/people/Person;slap(IF)Z;")) private boolean onHaykamSlapped(Person instance, int quantity, float forceOverResistance, Operation<Boolean> original) { boolean originalValue = original.call(instance, quantity, forceOverResistance); newOperations(forceOverResistance, instance); return originalValue; }
This allows us to get the returned value of the force / haykam.resistance expression without needing to separately recalculate it via capturing the force variable, which may be especially brittle if there are other float variables earlier in the method body.
Note that
@Local capture is particularly brittle specifically in the context of obfuscated code, mainly Minecraft code. However unofbuscated code such as other mods or as Minecraft will be starting on version 26.1, where obfuscation will be removed from the game does not imply the same level of brittleness for local capture. This is because unobfuscated code will allow to target and capture locals by name rather than needing to only rely on type, ordinal or index which can often be brittle.
In summary of this subsection, @Inject should be avoided when it may lack in the context it can naturally about variables or operations it is injecting relative to, or when the intention is to modify individual operations within a method, as there are more precise and expressive injectors for those situations.
Structure
A barebones structure of @Inject would look as follows:
@Inject(method = "TARGET METHOD NAME OR SIGNATURE", at = @At(value = "INJECTION POINT"), cancellable = <false BY DEFAULT>) private void injectedHandlerMethod(<TARGET METHOD PARAMETERS>, CallbackInfo info) { }
Note that the value = is optional if @At only contains an injection point, such as when the injection point does not need to specify further via a target.
Most of the attributes of @Inject are used by other injectors, but as this page may be read by people unfamiliar with general injector syntax, this page will go into some additional detail on their contents.
Target method
The method attribute specifies which method in the target class to inject into. If the method has no overloads, the name is enough. Otherwise, a more specific descriptor is required, consisting of the name followed by (<PARAMETER DESCRIPTORS>)<RETURN TYPE DESCRIPTOR>. See the target section for JVM descriptors.
It should be noted that for both target methods and injection point targets, the MCDev IntelliJ plugin provides autocompletion to get around the need of manually entering the method descriptors.
Targeting Constructors
Constructors include the “static constructor”, which can be targeted by inputting “<clinit>” as the target method, and the target class's constructors.
The static constructor is the method which internally is in charge of declaring all static fields in a class. Targeting it may be relevant when wanting to target a specific field. It should however be noted that “static initializers” (in Java, formed with static {} code blocks) of a Mixin class are merged into the target class aswell, and they may be used rather than injecting into <clinit> at certain times.
Non-static constructors are targeted by inputting “<init>” as the method name. If there are multiple constructors, the same approach to specification via JVM descriptors applies, with a return type of V. Such that a no-args constructor would be referenced by “<init>()V”.
Fabric's Mixin fork allows to inject at any point in a constructor method, however it may be the case when developing using other forks or an upstream version that injection points are limited to
RETURN or TAIL. This will not be the case when developing on Fabric or other modding frameworks using Fabric's Mixin fork.
Injection Point
The injection point defines what instructions to inject at in the target method. The following table describes a few of the options:
| Name | Description |
|---|---|
| HEAD | Top of the method |
| RETURN | Before every return instruction |
| INVOKE | At a method call |
| TAIL | Before the final return instruction |
See the Mixin Injection Point Reference for a more thorough list of Mixin's stock injection points.
Injection Point Target
In the case of injection points that target calls, statements or members, such as INVOKE or FIELD; the injection point's complementary target can be specified via the format of: @At(value = “INJECTION POINT”, target = “TARGET VALUE”). Target values are specified using JVM bytecode descriptors.
Oracle defines the following field descriptors:
| Descriptor | Primitive | Description |
|---|---|---|
| B | byte | signed byte |
| C | char | Unicode character code point in the Basic Multilingual Plane, encoded with UTF-16 |
| D | double | double-precision floating-point value |
| F | float | single-precision floating-point value |
| I | int | signed integer |
| J | long | signed long integer |
| LClassName; | reference | an instance of ClassName |
| S | short | signed short |
| Z | boolean | true or false |
| [ | reference | one array dimension |
A method descriptor is comprised of the method name, followed by a set of parentheses containing the parameter types, followed by the return type. A method defined in Java as Object m(int i, double[] d, Thread t) would have the method descriptor m(I[DLjava/lang/Thread;)Ljava/lang/Object;.
In the case that the return type is void, you need to use V (Void Descriptor Type) as the type (for example, void foo(String bar) would become foo(Ljava/lang/String;)V).
Generics' types are left out, as generics are stripped at runtime. So Pair<Integer, ? extends Task<? super VillagerEntity>> would become Lcom/mojang/datafixers/util/Pair.
Furthermore, when using a descriptor for a target complementing an injection point, the descriptor form of the class path containing the method, followed by a ; character prefixes the method name.
Handler Method
A handler method, often abbreviated to just “handler”, is the method annotated by the injector and called by the injected instructions.
@Inject handler methods always return void. The method name does not functionally matter; using something that describes what the inject does is best. The access modifier typically does not matter either. The target method's arguments are placed first in the method's header, followed by a CallbackInfo parameter if the target method returns void. If the target method has a return type R, CallbackInfoReturnable<R> is used instead of CallbackInfo.
Cancelling the target method
@Inject is natively able to cancel the target method by injecting an early return statement. This can be done using the CallbackInfo parameter and calling cancel() on it, whilst setting the @Inject to have a cancellable = true attribute. In the case of a CallbackInfoReturnable<R> parameter, the return value must be set instead via setReturnValue(R).
This is also possible for other injectors via MixinExtras' @Cancellable annotation.
Shifting
@Inject, when targeting an element within the target method, will inject the handler's operations just before the targeted element. It is possible, however, to shift the injected instructions to be added after the targeted instruction.
The only common form of shifting comes in the form of using shift = At.Shift.AFTER within the @At. This shifts the injection point to after the target. The syntax for shifting looks as follows:
@Inject(method = "...", at = @At(value = "...", target = "...", shift = At.Shift.AFTER)