Plugins #
Plugins let you inject custom logic into Pragma services without modifying engine code. A service declares a plugin field with an interface type, and the engine loads the implementation you specify in configuration. If no implementation is configured, the service falls back to its declared default.
All examples on this page use the GreetingPlugin from the DemoService reference service introduced in Custom services.
Declare a plugin #
A service declares a plugin using the @PragmaPlugin annotation on a lateinit var field. The annotation specifies the default implementation class and the configuration field name:
DemoService.kt
package unicorn.demo
import pragma.plugins.PragmaPlugin
//...
@PragmaPlugin("unicorn.demo.DemoGreetingPlugin", "greetingPlugin")
lateinit var greetingPlugin: GreetingPlugin
The declaration has three parts:
| Part | Example | Purpose |
|---|---|---|
| Interface type | GreetingPlugin | The Kotlin interface the plugin must implement. |
| Default implementation | "unicorn.demo.DemoGreetingPlugin" | Fully qualified class used when no override is configured. |
| Config field name | "greetingPlugin" | The key under pluginConfigs.<Service> in YAML configuration. |
The service then delegates to the plugin at the call site:
val greeting = greetingPlugin.greeting(request.name)
Define the plugin interface #
A plugin interface extends PragmaServicePlugin. It declares the methods that the service will call and that implementations must provide:
GreetingPlugin.kt
package unicorn.demo
import pragma.plugins.PragmaServicePlugin
//...
interface GreetingPlugin : PragmaServicePlugin {
fun greeting(name: String): String
fun farewell(): String
}
Implement the plugin #
Plugins that need configuration implement ConfigurablePlugin<T> alongside the plugin interface. The engine calls onConfigChanged at startup and whenever the configuration is reloaded, so the plugin always has current values.
Plugins that do not need configuration implement PragmaServicePlugin directly and skip ConfigurablePlugin.
The DemoGreetingPlugin below is a configurable plugin:
DemoGreetingPlugin.kt
package unicorn.demo
import pragma.content.ContentDataNodeService
import pragma.plugins.ConfigurablePlugin
import pragma.services.Service
//...
class DemoGreetingPlugin(
override val service: Service,
override val contentDataNodeService: ContentDataNodeService,
) : ConfigurablePlugin<DemoGreetingPluginConfig>, GreetingPlugin {
private lateinit var config: DemoGreetingPluginConfig
override suspend fun onConfigChanged(config: DemoGreetingPluginConfig) {
this.config = config
}
override fun greeting(name: String): String {
val vipGreeting = config.vips[name]
if (vipGreeting != null) {
return vipGreeting
}
return config.greeting.format(name)
}
override fun farewell(): String {
return config.farewell
}
}
The config class (DemoGreetingPluginConfig) that this plugin reads is defined in the next section.
Define the plugin config #
A plugin config extends PluginConfig. This means its YAML path lives under pluginConfigs rather than under serviceConfigs:
DemoGreetingPlugin.kt
package unicorn.demo
import pragma.config.ConfigBackendModeFactory
import pragma.config.PluginConfig
import pragma.settings.BackendType
//...
class DemoGreetingPluginConfig private constructor(type: BackendType)
: PluginConfig<DemoGreetingPluginConfig>(type)
{
override val description = "Greeting plugin configuration."
var greeting by types.string("Default greeting template.")
var farewell by types.string("Farewell template.")
var vips by types.mapOfString("Player name to custom greeting overrides.")
/**
* Only set code-level defaults when the value is safe in every environment, including
* production. Otherwise leave the value blank to force a per-environment override,
* preventing prod from accidentally running with dev defaults.
*/
init {
greeting = "Hello, %s!"
farewell = "Goodbye!"
}
companion object : ConfigBackendModeFactory<DemoGreetingPluginConfig> {
override fun getFor(type: BackendType) = DemoGreetingPluginConfig(type)
}
}
Configure the plugin #
Set the plugin implementation and its config values in YAML under pluginConfigs.<ServiceName>.<fieldName>:
game:
pluginConfigs:
DemoService.greetingPlugin:
class: unicorn.demo.DemoGreetingPlugin
config:
greeting: "Welcome, %s!"
farewell: "See you later!"
vips:
Alice: "Greetings, VIP Alice!"
If no DemoService.greetingPlugin entry exists in configuration, the engine uses the default implementation specified in the @PragmaPlugin annotation.
Related topics #
- Configuration for details about override precedence, dynamic reload, and encrypted secrets.
- Custom services for creating services that declare plugins.