Plugin Lifecycle
Plugin Lifecycle
Section titled “Plugin Lifecycle”Hytale plugins follow a well-defined lifecycle with distinct states and phases. Understanding this lifecycle is essential for properly initializing resources, registering handlers, and cleaning up when your plugin shuts down.
Plugin States
Section titled “Plugin States”A plugin transitions through the following states during its lifecycle:
flowchart LR
NONE --> SETUP --> START --> ENABLED --> SHUTDOWN --> DISABLED
SETUP -->|error| FAILED
START -->|error| FAILED
ENABLED -->|error| FAILED
SHUTDOWN -->|error| FAILED
DISABLED -->|"reload (new instance)"| NONE
State Definitions
Section titled “State Definitions”| State | Description |
|---|---|
NONE | Initial state before any lifecycle methods are called |
SETUP | Plugin is executing its setup() method |
START | Plugin is executing its start() method |
ENABLED | Plugin is fully active and operational |
SHUTDOWN | Plugin is executing its shutdown() method |
DISABLED | Plugin has been shut down cleanly |
FAILED | Plugin encountered an error and was marked as failed |
Lifecycle Methods
Section titled “Lifecycle Methods”Your plugin class should override these methods to participate in the lifecycle:
Constructor
Section titled “Constructor”public class MyPlugin extends JavaPlugin { public MyPlugin(JavaPluginInit init) { super(init); // DO NOT register anything here! // Only store references if needed }}preLoad() - Configuration Loading
Section titled “preLoad() - Configuration Loading”@Override@Nullablepublic CompletableFuture<Void> preLoad() { // Called before setup() to load configurations asynchronously return super.preLoad();}This method is called before setup() and is primarily used for loading configuration files. If you use withConfig() to register configuration codecs, their loading happens here automatically.
Timing: Called asynchronously before the plugin enters SETUP state.
setup() - Registration Phase
Section titled “setup() - Registration Phase”@Overrideprotected void setup() { // Register components, systems, commands, events, etc. getEventRegistry().register(PlayerConnectEvent.class, this::onPlayerConnect); getCommandRegistry().registerCommand(new MyCommand()); getEntityStoreRegistry().registerSystem(new MySystem());}This is the primary initialization method where you should:
- Register event listeners
- Register commands
- Register ECS components and systems
- Register block states
- Register asset types
- Set up any plugin infrastructure
State: Plugin transitions from NONE to SETUP during this method.
start() - Activation Phase
Section titled “start() - Activation Phase”@Overrideprotected void start() { // Plugin is about to become fully active // Perform any post-setup initialization getLogger().at(Level.INFO).log("MyPlugin has started successfully!");}Called after setup() completes successfully. Use this for:
- Logging startup messages
- Starting background tasks
- Connecting to external services
- Any initialization that depends on all registrations being complete
State: Plugin transitions from SETUP to START, then to ENABLED after this method returns.
shutdown() - Cleanup Phase
Section titled “shutdown() - Cleanup Phase”@Overrideprotected void shutdown() { // Clean up resources before the plugin is disabled // Note: Registered handlers are automatically unregistered}Called when the plugin is being disabled. Use this for:
- Saving data
- Closing connections
- Releasing external resources
State: Plugin transitions to SHUTDOWN during this method, then to DISABLED.
Configuration System
Section titled “Configuration System”Plugins can declare configuration files that are automatically loaded during preLoad():
public class MyPlugin extends JavaPlugin { private final Config<MyConfig> config;
public MyPlugin(JavaPluginInit init) { super(init); // Register config BEFORE setup (in constructor or field initializer) this.config = withConfig(MyConfig.CODEC); }
@Override protected void setup() { // Config is loaded and ready to use MyConfig cfg = config.get(); if (cfg.isFeatureEnabled()) { // ... } }}Configuration Methods
Section titled “Configuration Methods”| Method | Description |
|---|---|
withConfig(BuilderCodec<T> codec) | Register a config using default name “config” |
withConfig(String name, BuilderCodec<T> codec) | Register a config with a custom name |
Configuration files are stored in <data-directory>/<name>.json and are automatically loaded before setup() is called.
Available Registries
Section titled “Available Registries”The following registries are available through your plugin base class:
| Method | Description |
|---|---|
getCommandRegistry() | Register commands |
getEventRegistry() | Register event listeners |
getEntityStoreRegistry() | Register entity ECS components and systems |
getChunkStoreRegistry() | Register chunk ECS components and systems |
getBlockStateRegistry() | Register custom block states |
getEntityRegistry() | Register entity types |
getTaskRegistry() | Register scheduled tasks |
getAssetRegistry() | Register asset types |
getClientFeatureRegistry() | Register client feature flags |
getCodecRegistry(codec) | Register to codec maps |
Utility Methods
Section titled “Utility Methods”| Method | Return Type | Description |
|---|---|---|
getLogger() | HytaleLogger | Plugin-specific logger |
getDataDirectory() | Path | Plugin’s data folder |
getIdentifier() | PluginIdentifier | Plugin’s unique identifier |
getManifest() | PluginManifest | Plugin’s manifest data |
getBasePermission() | String | Root permission node (group.name) |
getFile() | Path | Path to plugin JAR (JavaPlugin only) |
isEnabled() | boolean | Returns true if state is SETUP, START, or ENABLED |
isDisabled() | boolean | Returns true if state is NONE, DISABLED, SHUTDOWN, or FAILED |
getState() | PluginState | Current lifecycle state |
Complete Example
Section titled “Complete Example”package com.example.myplugin;
import com.hypixel.hytale.server.core.plugin.JavaPlugin;import com.hypixel.hytale.server.core.plugin.JavaPluginInit;import com.hypixel.hytale.server.core.event.events.player.PlayerConnectEvent;import java.util.logging.Level;
public class MyPlugin extends JavaPlugin {
private static MyPlugin instance;
public static MyPlugin getInstance() { return instance; }
public MyPlugin(JavaPluginInit init) { super(init); }
@Override protected void setup() { instance = this;
getEventRegistry().register(PlayerConnectEvent.class, this::onPlayerConnect);
getCommandRegistry().registerCommand(new MyCommand());
getLogger().at(Level.INFO).log("MyPlugin setup complete!"); }
@Override protected void start() { getLogger().at(Level.INFO).log("MyPlugin v%s started!", getManifest().getVersion()); }
@Override protected void shutdown() { getLogger().at(Level.INFO).log("MyPlugin shutting down..."); instance = null; }
private void onPlayerConnect(PlayerConnectEvent event) { getLogger().at(Level.INFO).log("Player connected: %s", event.getPlayerRef().getUsername()); }}Error Handling
Section titled “Error Handling”If an exception occurs during setup() or start():
- The exception is logged with the plugin’s logger
- The plugin transitions to
FAILEDstate - Registrations are cleaned up when the plugin is later shut down by the plugin manager
@Overrideprotected void setup() { try { // Risky initialization } catch (Exception e) { getLogger().at(Level.SEVERE).withCause(e).log("Failed to initialize"); throw e; // Re-throw to trigger plugin disable }}Best Practices
Section titled “Best Practices”- Register in
setup(): Always perform registrations during the setup phase - Validate in
start(): Use the start phase to verify all dependencies are available - Clean up in
shutdown(): Release any external resources (files, connections, etc.) - Don’t block: Avoid long-running operations in lifecycle methods
- Use the logger: Log important events for debugging
- Check state: Use
isEnabled()before accessing plugin resources from external code
Related
Section titled “Related”- Plugin Manifest - Configure your plugin’s metadata
- First Plugin Tutorial - Build your first plugin
- Event System - Register event listeners
- Command System - Register commands