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 [2025/12/01 18:49] gauntreclusetutorial: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 13: Line 13:
  
 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:  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. This can be used to create events or an injector with a purpose similar to an event.+  * 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.     * 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.   * 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 the Mixin class used, via an injection at the tail of the target class's constructor.+  * Populating fields added by 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.
  
-It is important to not use ''@Inject'' if the goal can be achieved with a more precise and less intrusive change. 
  
 +==== 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 a 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 ===== ===== Structure =====
 A barebones structure of ''@Inject'' would look as follows: A barebones structure of ''@Inject'' would look as follows:
Line 90: Line 124:
  
  
-==== Cancelling ====+==== 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'' [[#structure|attribute]]. In the case of a ''CallbackInfoReturnable<R>'' parameter, the return value must be set instead via ''setReturnValue(R)''. ''@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'' [[#structure|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' [[https://github.com/LlamaLad7/MixinExtras/wiki/Cancellable|@Cancellable annotation]]. This is also possible for other injectors via MixinExtras' [[https://github.com/LlamaLad7/MixinExtras/wiki/Cancellable|@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:
 +<code java>
 +@Inject(method = "...", at = @At(value = "...", target = "...", shift = At.Shift.AFTER))
 +</code>
tutorial/mixin_injects.1764614993.txt.gz · Last modified: 2025/12/01 18:49 by gauntrecluse