FieldGet, FieldSet, MethodInvoker - Trivaxy/Libvaxy GitHub Wiki
There are sometimes scenarios when you want to access a field, property or method that are hidden away from you. This is accomplishable through reflection, which Libvaxy provides a lot of utilities for that typically suffice.
However, you might already know that reflection is slow. Although Libvaxy caches reflection results whenever possible to speed it up, sometimes you need to access non-public things as fast as possible (for example, every frame). Libvaxy allows you to access those non-public things at native speed without suffering from any reflection penalty using three attributes:
FieldGet
FieldSet
MethodInvoker
To demonstrate, suppose we want to access the field Player._quickGrappleCooldown
. It's a private instance field of type int
:
Under normal circumstances, Reflection.GetInstanceField
and Reflection.SetInstanceField
will work just fine. However, we're going to be accessing this field very frequently, so we want to make sure it happens extremely fast. This is where FieldGet
and FieldSet
come in:
[FieldGet("_quickGrappleCooldown")]
public static int GetQuickGrappleCooldown(this Player player) => 0; // this value is ignored by Libvaxy
[FieldSet("_quickGrappleCooldown")]
public static void SetQuickGrappleCooldown(this Player player, int cooldown) { } // this method body is ignored by Libvaxy
Above, you define two methods, one annotated with FieldGet
and one with FieldSet
. Both attributes take the name of the field you wish to modify; in our case, it's _quickGrappleCooldown
.
Methods annotated with FieldGet
and FieldSet
must be static extension methods residing in a static class. They must be extension methods targetting the type containing the field you want to modify; in our case, _quickGrappleCooldown
lives in the Player
class, so both of them must be an extension method for Player
(hence why they have this Player player
as their first parameter: it specifies that _quickGrappleCooldown
is in Player
).
The return type of FieldGet
methods must match the type of the field they are targetting; in our case, _quickGrappleCooldown
is an int
, so we make the return type an int
. We return a dummy value 0
that gets ignored.
For FieldSet
methods, after the first parameter which specifies what type the field is in, you must add in a second parameter whose type matches the type of the field; in our case, _quickGrappleCooldown
is an int
, so we add an extra parameter int cooldown
. Keep in mind FieldSet
methods must return void, otherwise Libvaxy will throw an exception.
Do note a few things about methods annotated with FieldGet
and FieldSet
, though:
- They can have any name you want, and their parameters can be any name you want. The only thing that matters is the types being correct
- They can be any access modifier you want
- Any values or code you place inside them will be completely ignored when the methods are called
FieldGet
andFieldSet
also work with static fields.
Now that you have defined your methods, it's time to use them. Let's pretend we are evil today and write code that makes the grapple cooldown super long if it is 0:
public class EvilPlayer : ModPlayer
{
public override void PreUpdate()
{
// make sure you import the namespace containing the class that has your FieldGet/FieldSet methods!
if (player.GetQuickGrappleCooldown() <= 0)
player.SetQuickGrappleCooldown(99999999);
// the above can also be written like this:
if (ClassContainingYourMethods.GetQuickGrappleCooldown(player) <= 0)
ClassContainingYourMethods.SetQuickGrappleCooldown(player, 99999999);
}
}
When using FieldGet
and FieldSet
targetting static fields, the first syntax displayed above will ignore the player
instance (which is natural, since the field is static). When using the second syntax, you can pass in any parameter in place of player
, as it is also ignored:
// the first syntax works just fine with static fields. the player instance is ignored
player.GetRandomStaticField(); // player instance ignored
new Player().SetRandomStaticField(something); // player instance ignored
// the second syntax also works just fine with static fields. the player parameter can be anything you want, even null, as it is ignored
ClassContainingYourMethods.GetRandomStaticField(null);
ClassContainingYourMethods.SetRandomStaticField(null, something);
If you want to access methods fast, use MethodInvoker
. Suppose we want to call the methods Player.ExtractinatorUse
and Player.GetRequiredDD2CrystalsToUse
:
To do this, we need to create methods and annotate them with MethodInvoker
, and pass the attribute the name of the method we want to invoke:
[MethodInvoker("ExtractinatorUse")]
public static void ExtractinatorUse(this Player player, int extractType) { } // method body here ignored
[MethodInvoker("GetRequiredDD2CrystalsToUse")]
public static int GetRequiredDD2CrystalsToUse(this Player player, Item item) => 0; // value here ignored
As with FieldGet
and FieldSet
, methods annotated with MethodInvoker
must be static extension methods residing in a static class targetting the type containing the method we want to invoke. In our case, again, it is Player
, so the first parameter is this Player player
. The rest of the parameters must match the types of the targetted method, as seen above. The return type of MethodInvoker
methods must either be the same as the targetted method's return type, or a parent class to it.
Just like before, MethodInvoker
methods:
- Can have any name you want, and their parameters can have any name you want
- Can have any access modifier
- Work with overloads, just give them parameters that match the targetted overload's parameter types
- Will have code in them ignored
Using our new methods now is straightforward:
// make sure you import the namespace containing the class that has your MethodInvoker methods!
Main.LocalPlayer.ExtractinatorUse(0);
Main.LocalPlayer.GetRequiredDD2CrystalsToUse(Main.mouseItem);
// the above can also be written this way:
ClassContainingYourMethodInvokers.ExtractinatorUse(Main.LocalPlayer, 0);
ClassContainingYourMethodInvokers.GetRequiredDD2CrystalsToUse(null, Main.mouseItem); // just like before, static methods ignore the associated instance