Auto-invoked functions
@AutoInvoke(phase) marks a function the runtime calls at the given phase, with its parameters
injected from the beans. It is Versatia's replacement for onEnable and onDisable.
import dev.versatia.Phase
import dev.versatia.lifecycle.AutoInvoke
class MyPlugin : VersatiaPlugin() {
@AutoInvoke(Phase.PLUGIN_RELOAD_OR_START)
private fun start(clock: Clock) { /* ... */ }
@AutoInvoke(Phase.PLUGIN_STOP)
fun stop() { /* ... */ }
}
Where a function may live
| Owner | Called on |
|---|---|
| the plugin's main class | the plugin instance |
a Kotlin object | the object |
| a bean | the bean instance of that plugin |
Anything else is a compile error: the runtime would have no instance to call the function on.
object Statistics {
private var starts = 0
@AutoInvoke(Phase.PLUGIN_RELOAD_OR_START)
fun countStart() { starts++ }
}
@LocalBean
class StartupBanner(private val greeter: Greeter) {
@AutoInvoke(Phase.PLUGIN_START_ONLY)
fun show(clock: Clock) { /* uses greeter and clock */ }
}
Parameters
Parameters are injected exactly like constructor dependencies of a bean: non-nullable, non-generic
class types; local beans first, then exposed ones; @Qualifier and @Primary apply. Ambiguity is a
compile error; a type no local bean provides is left to the runtime, which resolves it from the
beans other plugins exposed.
@AutoInvoke(Phase.PLUGIN_START_ONLY)
fun open(@Qualifier("grumpy") clerk: Greeter) { /* ... */ }
The plugin instance is a local bean as well, and so are its logger and the other
beans every plugin has, so a function in
an object or a bean receives them without reaching for JavaPlugin.getProvidingPlugin:
object Metrics {
@AutoInvoke(Phase.PLUGIN_START_ONLY)
fun start(plugin: JavaPlugin, logger: Logger) = logger.info("metrics on, writing to ${plugin.dataFolder}")
}
Rules
- Extension,
suspend, abstract and generic functions are rejected. Return values are ignored. Work that suspends is aBackgroundTaskor a coroutine launched from an injectedPluginScope, which a function may take as a parameter. - An override of an enhanced function carries no enhancement of its own: the call made for the base function already reaches the override, so a second annotation would run it twice. Annotate the open function in the base class and override it plainly; annotating both is a compile error.
- Visibility does not matter. A private function or class gets a reflective invoker in the descriptor, a visible one a direct call.
- Functions run after every other processor of the event, so at start all beans of the plugin already exist. Within one event they run in descriptor order: by class name, then by function name.
- A plugin's beans stay available for its whole lifetime, so functions at reload and stop receive the same instances that were created at start.
Phases
Phase | The function runs at |
|---|---|
PLUGIN_START_ONLY | plugin enable |
PLUGIN_RELOAD_ONLY | a Versatia reload |
PLUGIN_RELOAD_OR_START | both of the above |
PLUGIN_STOP | plugin disable |
The phase is per occurrence: @AutoInvoke declares enhancementPhaseParamRefName = "phase", so
the processor reads it from each annotation. See Phases and events. Prefer
PLUGIN_RELOAD_OR_START for anything that reads configuration, so that
/versatia reload picks changes up without a restart.
Failures
An exception thrown by the function is wrapped in an EnhancementException naming the function and
the event, with the original as cause:
Plugin MyPlugin: com.example.MyPlugin.start() failed at START
At start this disables the plugin, like any failure in onEnable.