User Tools

Site Tools


tutorial:mixin_injects

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_injects [2022/03/08 21:03] – remove interface injection from this incorrect place juuztutorial:mixin_injects [2025/12/15 16:52] (current) – Added an extra bracket to show that it has to be inside @At more clearly mcgambingpro
Line 1: Line 1:
-====== Mixin Injects ======+FIXME //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 ===== ===== Introduction =====
-Injects allows you to place custom code at a specified position inside an existing method. For working example, view the [[tutorial:mixin_injects#practical_example|Practical Example]] category at the bottom of this pageThe standard form of an inject is as shown:+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 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 [[tutorial:mixin_your_first_mixin|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|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 ''[[https://github.com/LlamaLad7/MixinExtras/wiki/WrapOperation|@WrapOperation]]'', [[https://github.com/LlamaLad7/MixinExtras/wiki/ModifyExpressionValue|@ModifyExpressionValue]], [[https://github.com/LlamaLad7/MixinExtras/wiki/ModifyReturnValue|@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 === 
 +FIXME //This section should use more concrete example.// 
 + 
 +''@Inject'' lacks context when injecting relative to a specific operation.\\ 
 +For example, say we had the following code snippet: 
 +<code java> 
 +public void calculateSlap(int quantity, Person haykam) { 
 +    /*...*/ 
 +    float force = complexCalculation(quantity); 
 +    haykam.slap(quantity, force / haykam.resistance); 
 +     
 +    /*...*/ 
 +
 +</code> 
 +if we wished to inject right after the ''slap'' call, we could use a [[#shifting|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. 
 +<code java> 
 +@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; 
 +
 +</code> 
 +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:
 <code java> <code java>
-@Inject(method = "", at = @At("INJECTION POINT REFERENCE")) +@Inject(method = "TARGET METHOD NAME OR SIGNATURE", at = @At(value = "INJECTION POINT"), cancellable = <false BY DEFAULT>
-private void injectMethod(METHOD ARGS, CallbackInfo info) {+private void injectedHandlerMethod(<TARGET METHOD PARAMETERS>, CallbackInfo info) {
  
 } }
 </code> </code>
  
-The [[https://github.com/SpongePowered/Mixin/wiki/Injection-Point-Reference|Injection Point Reference]] defines where the code inside the method body is injected inside the target method. The following table describes a few of the options:+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 [[#target|the target section for JVM descriptors]]. 
 + 
 +It should be noted that for both target methods and injection point targets, the [[https://mcdev.io|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 ^ ^ Name ^ Description ^
 | HEAD | Top of the method | | HEAD | Top of the method |
-| RETURN | Before every return statement |+| RETURN | Before every return instruction |
 | INVOKE | At a method call | | INVOKE | At a method call |
-| TAIL | Before the final return statement |+| TAIL | Before the final return instruction |
  
-In the case of injection points that reference statements or members, the target value can be set inside //@At//. Target value is specified using JVM bytecode descriptors.+See the [[https://github.com/SpongePowered/Mixin/wiki/Injection-Point-Reference|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 [[https://docs.oracle.com/javase/specs/jvms/se14/html/jvms-4.html#jvms-4.3.2|field descriptors]]: Oracle defines the following [[https://docs.oracle.com/javase/specs/jvms/se14/html/jvms-4.html#jvms-4.3.2|field descriptors]]:
Line 28: Line 103:
 | D | double | double-precision floating-point value | | D | double | double-precision floating-point value |
 | F | float | single-precision floating-point value | | F | float | single-precision floating-point value |
-| I | int | integer | +| I | int | signed integer | 
-| J | long | long integer |+| J | long | signed long integer |
 | L//ClassName//; | reference | an instance of //ClassName// | | L//ClassName//; | reference | an instance of //ClassName// |
 | S | short | signed short | | S | short | signed short |
Line 35: Line 110:
 | [ | reference | one array dimension | | [ | reference | one array dimension |
  
-A method descriptor is comprised of the method name, followed by a set of parentheses containing the input types, followed by the output 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;''+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;''.
  
-Generics' types are left out, as Generics don't exist on runtime. So ''Pair<Integer, ? extends Task<? super VillagerEntity>‍>'' would become ''Lcom/mojang/datafixers/util/Pair''.+In the case that the return type is voidyou need to use V (Void Descriptor Type) as the type (for example, ''void foo(String bar)'' would become ''foo(Ljava/lang/String;)V'').
  
-//@Inject// methods always have a void return type. The method name does not matter; using something that describes what the inject does is best. The target method's arguments are placed first in the method's headerfollowed by a ''CallbackInfo'' objectIf the target method has a return type (T), ''CallbackInfoReturnable<T>'' is used instead of ''CallbackInfo''.+Genericstypes are left outas generics are stripped at runtimeSo ''Pair<Integer, ? extends Task<? super VillagerEntity>>'' would become ''Lcom/mojang/datafixers/util/Pair''.
  
-=== Returning & Cancelling from Inject === +Furthermore, when using a descriptor for target complementing an injection point, the descriptor form of the class path containing the method, followed by a '';'' character prefixes the method name.
-To cancel or return early inside a method, use ''CallbackInfo#cancel'' or ''CallbackInfoReturnable<T>#setReturnValue(T)''. Note that ''cancel'' does not have to be called after ''setReturnValue''. In both instances, ''cancellable'' will have to be set to true in the inject annotation: +
-<code java> +
-@Inject(method = "...", at = @At("..."), cancellable = true) +
-</code>+
  
 +===== Handler Method =====
 +A handler method, often abbreviated to just "handler", is the method annotated by the injector and called by the injected instructions.
  
-=== Injecting into Constructors === +''@Inject'' handler methods always return ''void''. The method name does not functionally matter; using something that describes what the inject does is bestThe 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''.
-To inject into a constructor, use ''<init>()V'' as the method target, with ''()'' containing the constructor argument descriptorsWhen injecting into constructors, ''@At'' must be set to either ''TAIL'' or ''RETURN''No other forms of injection are officially supported. Note that some classes have methods named ''init'' which are different from ''<init>''. Don't get confused!+
  
-To inject into a static constructor, use ''<clinit>'' as the method name. 
  
-===== Practical Example ===== +==== Cancelling the target method ==== 
-The following example injects a print statement at the top of ''TitleScreen#init'' (note: the method ''init'' is normal method and not constructor).+''@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 ''cancellable = true'' [[#structure|attribute]]. In the case of ''CallbackInfoReturnable<R>'' parameter, the return value must be set instead via ''setReturnValue(R)''.
  
-<code java [enable_line_numbers="false"]> +This is also possible for other injectors via MixinExtras' [[https://github.com/LlamaLad7/MixinExtras/wiki/Cancellable|@Cancellable annotation]].
-@Mixin(TitleScreen.class) +
-public class ExampleMixin { +
- @Inject(at = @At("HEAD"), method = "init()V"+
- private void init(CallbackInfo info) { +
- System.out.println("This line is printed by an example mod mixin!"); +
-+
-+
-</code>+
  
-For more information on this particular exampleview its usage in the [[https://github.com/FabricMC/fabric-example-mod/blob/master/src/main/java/net/fabricmc/example/mixin/ExampleMixin.java|Fabric Example Mod repo]].+===== 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: 
 +<code java
 +@Inject(method = "...", at = @At(value = "...", target = "...", shift = At.Shift.AFTER)) 
 +</code>
tutorial/mixin_injects.1646773416.txt.gz · Last modified: 2022/03/08 21:03 by juuz