> For the complete documentation index, see [llms.txt](https://medieval-kingdoms.gitbook.io/docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://medieval-kingdoms.gitbook.io/docs/implementation/images-and-media.md).

# Registrable

## Introduction

The `Registrable` interface defines a common lifecycle contract for components within the Medieval Plugin system. It is intended for objects that need to be registered with and managed by the plugin lifecycle, such as registries, managers, or subsystems.

By implementing `Registrable`, a class can hook into key stages of the plugin’s execution cycle (`start`, `post-start`, and `shutdown`) without requiring boilerplate lifecycle management code.

## Example

### Interface

```java
public interface PlayerManager extends Registrable { // We extend registrable

    void updatePlayerData(@NotNull Player player);

    PlayerData getPlayerData(@NotNull Player player);

    void savePlayerData(UUID uuid, PlayerData playerData);
}
```

### Implementation

```java
public class PlayerManagerImpl implements PlayerManager {

    private MedievalPlugin plugin;

    private final RemovalListener<@NotNull UUID, @NotNull PlayerData> removalListener = (uuid, playerData, cause) -> {
        if (uuid != null && playerData != null) {
            if (Bukkit.isPrimaryThread()) {
                savePlayerData(uuid, playerData);
            } else {
                Bukkit.getScheduler().runTask(plugin, () -> savePlayerData(uuid, playerData));
            }
        }
    };

    private final Cache<@NotNull UUID, @NotNull PlayerData> cache = Caffeine.newBuilder()
            .expireAfterAccess(5, TimeUnit.MINUTES)
            .removalListener(removalListener)
            .build();

    @Override
    public void onStart(MedievalPlugin plugin) {
        this.plugin = plugin;
    }

    @Override
    public void onShutdown(MedievalPlugin plugin) {
        if (Bukkit.isPrimaryThread()) {
            cache.asMap().forEach(this::savePlayerData);
        } else {
            var future = new CompletableFuture<Void>();
            Bukkit.getScheduler().runTask(plugin, () -> {
                try {
                    cache.asMap().forEach(this::savePlayerData);
                    future.complete(null);
                } catch (Exception e) {
                    future.completeExceptionally(e);
                }
            });

            try {
                future.get(5, TimeUnit.SECONDS);
            } catch (Exception e) {
                plugin.getLogger().severe("Failed to save all player data on shutdown: " + e.getMessage());
            }
        }

        cache.invalidateAll();
    }

    @Override
    public void updatePlayerData(@NotNull Player player) {
        var uuid = player.getUniqueId();
        var playerData = new PlayerData();
        playerData.setName(player.getName());

        cache.put(uuid, playerData);
    }

    @Override
    public PlayerData getPlayerData(@NotNull Player player) {
        var uuid = player.getUniqueId();
        return cache.get(uuid, key -> {
            if (!Bukkit.isPrimaryThread()) {
                plugin.getLogger().warning("Loading player data from non-main thread for UUID: " + uuid);
            }

            try {
                var yaml = new Yaml(uuid.toString());
                yaml.load(plugin, false);

                var configuration = yaml.getConfiguration();

                var playerData = new PlayerData();
                var playerName = configuration.getString("name");
                if (playerName != null) playerData.setName(playerName);

                return playerData;
            } catch (Exception e) {
                plugin.getLogger().severe("Failed to load player data for " + uuid + ": " + e.getMessage());
                return new PlayerData();
            }
        });
    }

    @Override
    public void savePlayerData(UUID uuid, PlayerData playerData) {
        try {
            var yaml = new Yaml(uuid.toString());
            yaml.load(plugin, false);

            var configuration = yaml.getConfiguration();
            configuration.set("name", playerData.getName());

            yaml.save();
        } catch (Exception e) {
            plugin.getLogger().severe("Failed to save player data for " + uuid + ": " + e.getMessage());
        }
    }
}
```

### Usage

Here is an example of how we can retrieve the player manager when we want to use it in an event listener.

```java
public class TestListener implements Listener {

    private final MedievalPlugin plugin;
    private final PlayerManager playerManager;

    public TestListener(MedievalPlugin plugin) {
        this.plugin = plugin;
        this.playerManager = plugin.provide(PlayerManager.class); 
        // instead of passing PlayerManager into the contructutor parameters,
        // our plugin provides the registered PlayerManager class
    }

    @EventHandler
    public void onJoin(PlayerJoinEvent event) {
        var player = event.getPlayer();
        playerManager.updatePlayerData(player);

        var playerData = playerManager.getPlayerData(player);
        plugin.getLogger().info("Successfully loaded" + playerData.getName());
    }
}
```

### Registering

```
public class TestPlugin extends MedievalPlugin {

    @Override
    public void onInitialize(MedievalPlugin plugin) {
        register(PlayerManager.class, new PlayerManagerImpl());
    }

    @Override
    public void onPostInitialize(MedievalPlugin plugin) {
        registerListeners(new TestListener(this));
    }

    @Override
    public void onShutdown(MedievalPlugin plugin) {
        // because player manager is registered, it automatically handles invalidating
        // and storing player manager data. Therefore, we do not need to do anything here
    }
}
```
