-
Notifications
You must be signed in to change notification settings - Fork 2
Internals
When I mean zero overhead, I'm comparing it to directly invoking the listener methods, and if you want, even manual inlining. NanoEvents uses ASM to create this bridge for you, however there's no inlining. As you saw in my JMH test, this is no problem for the JVM, and it was easily able to inline the methods, thus, truly zero overhead.
When the game first starts up, depending on what runs first (Early riser 🍫, or the mixin plugin), NanoEvents searches all mod jsons for event/listener files. It then parses all of them in to maps.
When the early riser runs, it cuts the code in between the start and end method invocations. Then it clones it once for every listener, but replaces recursive calls in the bytecode with the names and classes of the listener. And then, last it pastes the bytecode where the start method originally was. The effect of this is the "copy & paste effect" we see when we make an invoker, this is the secret sauce of NanoEvents. By creating a bridge that directly invokes the listeners, we avoid any overhead of event managers, or iteration.
The advantage of a statically registered system is clear, however why can't NanoEvents just generate new classes every time a new listener is added? The performance of this isn't very critical, creating listeners is a relatively rare event compared to throwing events. The reason that NanoEvents doesn't do this, is because of it's ability to disable mixins ahead of time. The overhead of mixins is pretty small, but in some cases, it's non-zero, often times the jvm is able to optimize it out since the bridge is a no-op, but this is not always the case. When there are no listeners for an event, some mixins are unnecessary, and we can reduce our impact on the performance of the game by disabling them.