Skip to content

Invokers

Devan-Kerman edited this page May 16, 2020 · 4 revisions

Similar to Fabric Callbacks, NanoEvents combines all the listeners into a single function that u can then invoke. NanoEvents are statically declared, and as such, all Invokers/Listeners must be static.

As an example, we will make an event that passes an integer to a function, and the listener can do some mathematical operation on it.

@Invoker("modid:event") // this is the id of the event you specified in your EVT file
public static int sumEvent(int i) {
   
}

This is an example event, where you want custom handling of return types. So, to create an invoker, we need to give NanoEvents a pattern that we can repeat for every listener for our event. NanoEvents has 2 functions for this purpose, Logic#start and Logic#end, the bytecode enclosed by start and end is copy pasted over and over again, one time for each listener, each shallow recursive call is replaced with a call to a listener. A shallow recursive call is when you directly call the invoker method, from inside the invoker method and not another function. (This does mean no lambda support as of now)

So let's write our sum event

@Invoker("modid:event")
public static int sumEvent(int i) {
   Logic.start();
   i += sumEvent(i); // Now, for every listener to sumEvent, this line of code will be copy and pasted, but the call to sumEvent is replaced with a call to the listener
   Logic.end();
   return i;
}

Good IDEs, like intellij, will warn you about recursive calls and other nonsense, just ignore them.

The transformed function will look like this

@Invoker("modid:event")
public static int sumEvent(int i) {
   i += sumListener(i);
   i += mySumListener(i);
   i += hisSumListener(i);
   return i;
}

NanoEvents will handle all the transformations from here, in your code, you can directly invoke sumEvent and at runtime, NanoEvents will transform it for you.

Further Optimization

You may have noticed that when there is only one listener for the sumEvent, that the resulting code has extra operations and could be optimized further. This is where SingleInvoker comes in.

@SingleInvoker("modid:event")
private static int singleSumEvent(int i) {
   return singleSumEvent(i) + i;
}

I would suggest you make SingleInvokers private, and it's required that SingleInvokers exist in the same class as the regular invoker. At runtime, if there is only one listener for an event, the bytecode from the single invoker is copy pasted (overriding the invoker bytecode) and all shallow recursive calls are replaced with a listener call. You're not meant to call the single invoker, you still call the invoker like normal, NanoEvents will handle the copying of bytecode for you. Unlike invokers, there is no Logic#start or Logic#end

Listener Invokers

Sometimes, you want to pass parameters to invokers, but u don't want to pass them to your listeners. This is where ListenerInvoker comes in, similarly to SingleInvoker, none of the bytecode is copied, and I suggest you make it private. The listener invoker takes the place of the normal recursive call to the invoker.

This is an example of a cancellable event, but can optionally force call all listeners even if the event has been cancelled.

@ListenerInvoker("modid:event")
public static native /*u can use native, or a dummy method body*/ boolean listenerInvoker(int x);

@Invoker("modid:event")
public static boolean invoker(int x, boolean forceInvoke /*we want to hide this parameter from listeners*/) {
   boolean cancelled = false;
   Logic.start();
   if(listenerInvoker(x)) {
      if(forceInvoke) cancelled = true;
      else return true;
   }
   Logic.end();
   return cancelled;
}

Instead of looking for recursive calls, NanoEvents will instead look for calls to listenerInvoker

Clone this wiki locally