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 and FieldSet 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