Skip to content

WrapOperation

LlamaLad7 edited this page Oct 26, 2023 · 8 revisions

Note: wrapping object instantiations is currently in beta. It may be changed in a future release and may cause unforeseen issues in its current state.

Allows you to wrap a method call, field get/set, instanceof check, or object instantiation.

Your handler method receives the targeted instruction's arguments and an Operation representing the operation being wrapped (optionally followed by the enclosing method's parameters). You should return the same type as the wrapped operation does.

Should be used in favour of Redirects when you are wrapping the original operation and not replacing it outright, as unlike Redirects, this chains when used by multiple people.

Handler Methods

Targeted operation Handler signature
Non-static method call private ReturnType yourHandlerMethod(OwnerType instance, <arguments of the original call>, Operation<ReturnType> original)
super. method call private ReturnType yourHandlerMethod(ThisClass instance, <arguments of the original call>, Operation<ReturnType> original)
Static method call private ReturnType yourHandlerMethod(<arguments of the original call>, Operation<ReturnType> original)
Non-static field get private FieldType yourHandlerMethod(OwnerType instance, Operation<FieldType> original)
Static field get private FieldType yourHandlerMethod(Operation<FieldType> original)
Non-static field write private void yourHandlerMethod(OwnerType instance, FieldType newValue, Operation<Void> original)
Static field write private void yourHandlerMethod(FieldType newValue, Operation<Void> original)
instanceof check private boolean yourHandlerMethod(Object obj, Operation<Boolean> original)
Object instantiation private ObjectType yourHandlerMethod(<arguments of the relevant constructor>, Operation<ObjectType> original)

In all of these cases, you can optionally add the enclosing method's parameters to the end, should you require them for additional context.

Example

When targeting code such as the following:

int number = this.expensiveCalculation(flag);

you may wish to wrap the call such that it is bypassed if a setting is enabled.

This could be done like so:

@WrapOperation(
	method = "targetMethod", 
	at = @At(value = "INVOKE", target = "Lsome/package/TargetClass;expensiveCalculation(Z)I")
)
private int bypassExpensiveCalculationIfNecessary(TargetClass instance, boolean flag, Operation<Integer> original) {
	if (YourMod.bypassExpensiveCalculation) {
                return 10;
        } else {
                return original.call(instance, flag);
        }
}

expensiveCalculation would then only be called if you called it yourself via the original.call(...) invocation.

Multiple mods can do this at the same time, and the wrapping will chain. The first to be applied receives an Operation representing the vanilla call, if another is applied it receives an Operation representing the first one's wrapping, etc.

Code Diff

- int number = this.expensiveCalculation(flag);
+ int number = this.bypassExpensiveCalculationIfNecessary(this, flag, args -> {
+         return ((TargetClass) args[0]).expensiveCalculation((Boolean) args[1]);
+ });
Clone this wiki locally