Skip to main content

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​

OwnerCalled on
the plugin's main classthe plugin instance
a Kotlin objectthe object
a beanthe 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 a BackgroundTask or a coroutine launched from an injected PluginScope, 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​

PhaseThe function runs at
PLUGIN_START_ONLYplugin enable
PLUGIN_RELOAD_ONLYa Versatia reload
PLUGIN_RELOAD_OR_STARTboth of the above
PLUGIN_STOPplugin 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.