Skip to content

Latest commit

 

History

History
619 lines (472 loc) · 14.6 KB

File metadata and controls

619 lines (472 loc) · 14.6 KB

EasyLibrary Modules

EasyLibrary modules are plugin-owned runtime units discovered from module.yml or module.yaml manifests. They allow a plugin to expose independent features with:

  • ordered module loading based on dependencies.
  • isolated module lifecycle management.
  • shared services and capability providers.
  • module-scoped configuration and data.
  • automatic cleanup of runtime resources.
  • diagnostics and runtime state reporting.

Shutdown behavior

When PocketMine disables an owner plugin, EasyLibrary disables modules owned by that plugin and does not try to enable pending modules during the same disable event. PocketMine can still report the owner plugin as enabled while its disable callback is running, so skipping that pending pass avoids accidental re-enable attempts and misleading enabled lifecycle logs during server shutdown.

Requirements

  • EasyLibrary 2.0.0+
  • PocketMine-MP API 5.0.0+
  • PHP 8.2+

The owner plugin must depend on EasyLibrary:

depend:
  - EasyLibrary

Declaring module sources

Add easylibrary-modules to the owner plugin's plugin.yml:

easylibrary-modules:
  - path: modules
    namespace: vendor\plugin\modules
  • path is relative to the plugin root unless it is absolute.
  • namespace is optional when each manifest provides its own namespace or when loader is a fully qualified class name.

A single path may be declared directly:

easylibrary-modules: modules

You can declare multiple sources:

easylibrary-modules:
  - path: modules
    namespace: vendor\plugin\modules
  - path: custom-modules
    namespace: vendor\plugin\custom

Recommended layout

MyPlugin/
|-- plugin.yml
|-- modules/
|   `-- farm/
|       |-- module.yml
|       |-- FarmModule.php
|       |-- command/
|       |-- listener/
|       `-- service/
`-- src/
    `-- vendor/plugin/Main.php

Module manifest

Each module is described by a YAML manifest.

Minimal module.yml

id: vendor:farm
version: 1.0.0
loader: FarmModule
namespace: vendor\plugin\modules\farm

Full manifest example

id: vendor:farm
name: Farm System
version: 1.0.0
api-version: 1
loader: FarmModule
namespace: vendor\plugin\modules\farm
load: POSTWORLD
enabled: true

dependencies:
  vendor:economy: ">=1.0.0 <2.0.0"

soft-dependencies:
  - vendor:ranking

plugin-dependencies:
  - EconomyAPI

load-before:
  - vendor:farm-ui

provides:
  services:
    - vendor:farm-service
  capabilities:
    - vendor.farm

requires:
  services:
    - vendor:economy-service
  capabilities:
    - economy

Manifest fields

Field Purpose
id Stable module identifier
version Module version used for dependency checks
loader Module loader class name
name Human-readable module name
namespace Namespace prefix for loader class resolution
load STARTUP or POSTWORLD
enabled Initial enabled state
api-version Metadata describing the module API
dependencies Required module dependencies
soft-dependencies Optional modules that affect load order
plugin-dependencies Required PocketMine plugins
load-before Modules that should load after this one
provides Services and capabilities published by the module
requires Services and capabilities required by the module

Loader class resolution

  • If namespace is defined in plugin.yml, loader: FarmModule is resolved relative to that namespace.
  • If loader is a fully qualified class name, namespace is not required.
  • The module class must extend imperazim\module\BaseModule.

Creating a module

Most modules extend BaseModule:

use imperazim\module\BaseModule;
use imperazim\module\ModuleManager;

final class FarmModule extends BaseModule {

    protected function onEnable(ModuleManager $manager): void {
        $this->logger()->info('Farm module enabled.');
    }

    protected function onDisable(ModuleManager $manager): void {
        $this->logger()->info('Farm module disabled.');
    }
}

What BaseModule provides

  • module metadata and dependency information.
  • module-scoped logging.
  • access to the owning plugin and module manager.
  • module-specific data folder and YAML config support.
  • helpers for commands, listeners, events, tasks, async jobs, and cleanup.
  • service and capability helpers.
  • access to other module APIs.
  • health reporting.

Runtime context

While the module is enabled, the runtime context provides:

$context = $this->context();
$plugin = $context->getPlugin();
$manager = $context->getManager();
$services = $context->getServices();
$logger = $context->getLogger();
$folder = $context->getDataFolder();
$config = $context->getConfig('config.yml', [
    'enabled' => true,
]);

Module storage and configuration

  • getDataFolder() returns plugin_data/<OwnerPlugin>/modules/<module-id>/.
  • getPath($relativePath) resolves paths inside the module storage.
  • getConfig($file, $defaults) loads YAML config for the module.

Runtime resources and cleanup

Register runtime resources with module helpers:

$this->addCommand(FarmCommand::class);
$this->addListener(FarmListener::class);
$this->addEvent(PlayerJoinEvent::class, function(PlayerJoinEvent $event): void {
    // Handle the event.
});

$this->scheduleDelayedTask(new FarmTask(), 20);
$this->scheduleRepeatingTask(new FarmTask(), 20);
$this->submitAsyncJob(new FarmAsyncJob(), function($result, $job): void {
    // Handle job completion.
});

$this->addCleanup(function(): void {
    // Release custom state.
});

EasyLibrary tracks and cleans module-owned resources automatically:

  • commands.
  • listener instances.
  • event callbacks.
  • scheduled tasks.
  • async jobs and completion callbacks.
  • plugin components.
  • manual cleanup callbacks.

Resources are removed when a module is disabled, reloaded, or forgotten.

Services

Services are shared objects published by one module and consumed by others.

Provide a service

$this->provideService(
    'vendor:farm-service',
    new FarmService(),
    FarmServiceInterface::class,
    '1.0.0'
);

Consume a typed service

/** @var FarmServiceInterface $service */
$service = $this->getTypedService(
    'vendor:farm-service',
    FarmServiceInterface::class
);

Optional service lookup

$service = $this->getOptionalService('vendor:optional-service');

Manifest keys for service dependencies

provides:
  services:
    - vendor:farm-service
requires:
  services:
    - vendor:economy-service

Services are resolved at runtime, and required services are checked before onEnable().

Capabilities

Capabilities let a module depend on a feature rather than a specific provider.

Publish capabilities

provides:
  capabilities:
    - economy

Require capabilities

requires:
  capabilities:
    - economy

Resolve the current provider

$provider = $this->getCapabilityProvider('economy');

If multiple enabled modules provide the same capability, EasyLibrary can use a preferred provider setting. Use /easymodule provider to inspect or change it.

Module APIs

A module can expose an API object by overriding getApi().

public function getApi(): object {
    return new FarmApi($this);
}

Consumers can obtain it by ID:

$api = $this->getApi('vendor:farm');
$optionalApi = $this->getOptionalApi('vendor:ranking');

Health checks

Override getHealth() to report module diagnostics:

use imperazim\module\health\ModuleHealthReport;

public function getHealth(): ModuleHealthReport {
    if (!$this->hasService('vendor:farm-service')) {
        return ModuleHealthReport::error([
            'Farm service is unavailable.'
        ]);
    }

    return ModuleHealthReport::ok();
}

Health is displayed with:

/easymodule health <id>
/easymodule doctor

Module states

State Meaning
discovered Manifest loaded, lifecycle not started
waiting_owner Owner plugin unavailable or disabled
waiting_dependency Required module dependency unavailable
waiting_service Required service or capability unavailable
enabling Module is entering its lifecycle
enabled Module is active
disabling Module is releasing resources
disabled Module is inactive
reloading Module is being recreated
failed Module lifecycle execution failed
failed_dependency Dependency or version validation failed

If a required dependency disappears, dependent modules are disabled until the dependency returns.

Management commands

EasyLibrary exposes /easymodule with aliases /emodule and /modules.

/easymodule list
/easymodule info <id>
/easymodule why <id>
/easymodule resources <id>
/easymodule health <id>
/easymodule failures
/easymodule services
/easymodule capabilities
/easymodule provider <capability> [module|clear]
/easymodule enable <id>
/easymodule disable <id>
/easymodule disable-runtime <id>
/easymodule reload <id>
/easymodule refresh
/easymodule doctor
/easymodule graph
/easymodule dependents <id>
/easymodule debug <id> <on|off>
  • disable persists the disabled state in modules.yml.
  • disable-runtime only affects the current process.
  • reload recreates a module instance and restarts its lifecycle.
  • refresh rediscovers module manifests from declared sources.
  • doctor shows missing dependencies, services, capabilities, plugins, and health.

Required permission:

easylibrary.command.modules

What modules can do

EasyLibrary modules let you:

  • organize a plugin into independent runtime features.
  • enable, disable, and reload features at runtime.
  • declare strong and soft module dependencies.
  • publish and consume shared services.
  • provide and consume capability-based features.
  • expose typed module APIs.
  • use plugin dependencies for external PocketMine plugins.
  • track commands, listeners, events, tasks, and async jobs.
  • store per-module configuration and data.
  • surface module health and dependency diagnostics.

Example 1 — Basic module

module.yml:

id: examples:hello
name: Hello Module
version: 1.0.0
loader: HelloModule
namespace: easylibraryexamples\hello\modules\hello
load: POSTWORLD
enabled: true

HelloModule.php:

final class HelloModule extends BaseModule {
    protected function onEnable(ModuleManager $manager): void {
        $this->logger()->info('Hello module is enabled!');
        $this->addCommand(HelloCommand::class);
    }

    protected function onDisable(ModuleManager $manager): void {
        $this->logger()->info('Hello module is disabled.');
    }
}

This example demonstrates the simplest module lifecycle.

Example 2 — Service provider module

module.yml:

id: examples:economy
name: Economy Provider Example
version: 1.0.0
loader: EconomyModule
namespace: easylibraryexamples\economy\modules\economy
load: POSTWORLD
enabled: true

provides:
  services:
    - examples:economy-service
  capabilities:
    - economy
    - examples.economy

requires:
  services: []
  capabilities: []

EconomyModule.php:

final class EconomyModule extends BaseModule {
    protected function onEnable(ModuleManager $manager): void {
        $config = $this->getModuleConfig('config.yml', [
            'starting-balance' => 100.0
        ]);

        $service = new InMemoryEconomyService((float) $config->get('starting-balance', 100.0));
        $this->provideService('examples:economy-service', $service, EconomyApi::class, '1.0.0');
        $this->addCommand(EconomyCommand::class);

        $this->logger()->info('Economy service examples:economy-service is available.');
    }

    public function getApi(): EconomyApi {
        return $this->service;
    }
}

This module publishes a service and capability for others to use.

Example 3 — Consumer module with service and capability dependencies

module.yml:

id: examples:farm
name: Farm Consumer Example
version: 1.0.0
loader: FarmModule
namespace: easylibraryexamples\farm\modules\farm
load: POSTWORLD
enabled: true

provides:
  services: []
  capabilities:
    - examples.farm

requires:
  services:
    - examples:economy-service
  capabilities:
    - economy

soft-dependencies:
  - examples:hello
plugin-dependencies:
  - EasyModuleEconomyProviderExample

FarmModule.php:

final class FarmModule extends BaseModule {
    private EconomyApi $economy;
    private int $harvests = 0;

    protected function onEnable(ModuleManager $manager): void {
        $this->economy = $this->getTypedService('examples:economy-service', EconomyApi::class);
        $this->addCommand(FarmCommand::class);

        $provider = $this->getCapabilityProvider('economy');
        $this->logger()->info('Economy provider: ' . ($provider?->getId() ?? 'none'));
    }

    public function getHealth(): ModuleHealthReport {
        return ModuleHealthReport::ok([
            'harvests' => $this->harvests,
            'economyProvider' => $this->getCapabilityProvider('economy')?->getId() ?? 'none'
        ]);
    }
}

This example shows typed service consumption, capability lookup, and optional module dependencies.

Example 4 — Capability provider selection

If multiple modules provide the same capability, inspect providers with:

/easymodule provider economy

Select a preferred provider:

/easymodule provider economy examples:economy

Clear the preference:

/easymodule provider economy clear

Example 5 — Reload and refresh

  • /easymodule reload <id> reloads a module and restarts its lifecycle.
  • /easymodule refresh rediscovers module sources and newly added manifests.

Use reload after changing module code or configuration for an existing module. Use refresh after adding or updating manifests in a declared source.

Advanced notes

  • load: STARTUP modules are initialized before POSTWORLD modules.
  • soft-dependencies influence load order but do not block enabling.
  • load-before forces other modules to load later.
  • plugin-dependencies ensure required external PocketMine plugins are enabled.
  • getModuleConfig() stores files in the module's private data folder.
  • addComponent() is available when the owner plugin supports PluginComponent.
  • registerModule() allows programmatic module registration.

Example source files

Real examples are located in:

src/imperazim/module/examples/

They cover:

  • basic module lifecycle and configuration.
  • commands, listeners, and tasks.
  • service and capability usage.
  • module APIs and health checks.
  • diagnostic and dependency failure scenarios.

See the examples guide for setup instructions.