Este README documenta o sistema de módulos da EasyLibrary. Ele foi colocado temporariamente dentro de src/imperazim/module/ para ficar perto do código enquanto a API ainda está evoluindo. Depois, ele pode ser movido ou resumido para o README raiz.
O sistema de módulos permite dividir plugins grandes em partes pequenas, carregáveis, diagnosticáveis e reutilizáveis.
Ele foi pensado para PocketMine-MP 5 e para plugins que precisam de:
- módulos internos dentro do próprio plugin;
- módulos de plugins diferentes conversando entre si;
- dependências fortes e opcionais;
- serviços compartilhados entre módulos;
- provedores por capacidade, como
economy,database,window,farm,ranking; - reload, enable e disable individual;
- diagnóstico via comando;
- limpeza automática de comandos, listeners, eventos, tasks, components, services e callbacks;
- storage separado por módulo;
- debug por módulo.
Esta pasta agora inclui exemplos completos em:
src/imperazim/module/examples/Eles sao plugins de teste prontos para copiar para a pasta plugins/ do servidor. Os exemplos cobrem modulo basico, service provider, consumer entre plugins diferentes, capabilities e diagnosticos de falha controlada.
Leia primeiro:
src/imperazim/module/examples/README.mdPlugins inclusos:
01-basic-plugin
02-economy-provider-plugin
03-farm-consumer-plugin
04-diagnostics-pluginModuleManager é o gerenciador global dos módulos. Em runtime, ele pode ser acessado por:
$manager = \Library::modules();Ele descobre módulos, calcula ordem de carregamento, ativa módulos pendentes, desativa dependentes, registra serviços, resolve capacidades e expõe diagnósticos.
Todo módulo normalmente deve estender:
use imperazim\module\BaseModule;
final class FarmModule extends BaseModule {
protected function onEnable(\imperazim\module\ModuleManager $manager): void {
// inicialização
}
protected function onDisable(\imperazim\module\ModuleManager $manager): void {
// encerramento opcional
}
}BaseModule já oferece helpers para listeners, commands, tasks, services, APIs, configs, data folder e logger.
Cada módulo recebe um contexto enquanto está ativo. Dentro de BaseModule, use:
$context = $this->context();
$plugin = $context->getPlugin();
$manager = $context->getManager();
$services = $context->getServices();
$logger = $context->getLogger();O contexto evita depender diretamente de variáveis globais ou de chamadas espalhadas para Library::modules().
Cada módulo é descoberto por um arquivo module.yml ou module.yaml.
Exemplo simples:
id: statemc:farm
name: Farm System
version: 1.0.0
loader: FarmModule
namespace: statemc\farmsystem\module
load: POSTWORLD
enabled: trueCampos obrigatórios:
id: vendor:module-id
loader: ModuleClass
version: 1.0.0Campos opcionais comuns:
name: Nome bonito
namespace: Vendor\Plugin\Modules\Farm
load: STARTUP|POSTWORLD
enabled: trueMyPlugin/
├─ plugin.yml
└─ src/
├─ Main.php
└─ modules/
├─ farm/
│ ├─ module.yml
│ ├─ FarmModule.php
│ ├─ api/
│ │ └─ FarmApi.php
│ └─ listener/
│ └─ FarmListener.php
└─ ranking/
├─ module.yml
└─ RankingModule.php
No plugin.yml, declare onde a EasyLibrary deve procurar módulos:
easylibrary-modules:
- path: src/modules
namespace: myplugin\modulesTambém é aceito formato simples:
easylibrary-modules: src/modulesSe o módulo já informa namespace no próprio module.yml, o namespace do plugin.yml pode ser omitido.
dependencies:
- statemc:database
- statemc:economyO módulo só será ativado depois dessas dependências estarem ativas.
dependencies:
statemc:economy: ">=1.2.0"
statemc:database: ">=2.0.0 <3.0.0"Se a versão não bater, o módulo entra em estado de falha de dependência.
soft-dependencies:
- statemc:rankingSe o módulo existir, ele será carregado antes. Se não existir, o módulo atual ainda pode ativar.
load-before:
- statemc:farm-uiUse quando um módulo precisa carregar antes de outro sem o outro declarar dependência direta.
Use quando o módulo depende de outro plugin PocketMine ativo:
plugin-dependencies:
- EconomyAPI
- MyExternalPluginSe o plugin não existir ou estiver desativado, o módulo fica aguardando.
Serviços são objetos expostos por um módulo para outros módulos.
interface EconomyService {
public function getMoney(string $player): float;
public function addMoney(string $player, float $amount): void;
}
final class EconomyModule extends BaseModule implements EconomyService {
protected function onEnable(ModuleManager $manager): void {
$this->provideService('statemc:economy', $this, EconomyService::class, $this->getVersion());
}
public function getMoney(string $player): float {
return 0.0;
}
public function addMoney(string $player, float $amount): void {
// ...
}
}/** @var EconomyService $economy */
$economy = $this->getTypedService('statemc:economy', EconomyService::class);
$money = $economy->getMoney($player->getName());Sem contrato tipado:
$service = $this->getService('statemc:economy');Com fallback opcional:
$service = $this->getOptionalService('statemc:economy');
if ($service !== null) {
// integração opcional
}service-dependencies:
- statemc:economyOu no formato agrupado:
requires:
services:
- statemc:economyCapacidade é um conceito mais abstrato que serviço. Ela responde: "qual módulo fornece economia?", "qual módulo fornece database?", "qual módulo fornece ranking?".
Um serviço é uma API concreta. Uma capacidade é uma categoria/função.
Formato curto:
capabilities:
- economy
- moneyFormato explícito:
provides-capabilities:
- economy
- moneyFormato agrupado:
provides:
capabilities:
- economy
services:
- statemc:economycapability-dependencies:
- economyOu:
requires:
capabilities:
- economyO módulo só ativa quando existe algum módulo ativo fornecendo essa capacidade.
$provider = $this->getCapabilityProvider('economy');
if ($provider !== null) {
$api = $provider->getApi();
}Se dois módulos fornecem a mesma capacidade, o primeiro ativo é usado por padrão. Para fixar o provider preferido:
/easymodule provider economy statemc:economy
Para voltar ao automático:
/easymodule provider economy clear
Por padrão, BaseModule::getApi() retorna o próprio módulo.
Para esconder implementação interna, retorne um objeto API dedicado:
final class FarmApi {
public function __construct(private FarmModule $module) {}
public function isInFarm(Player $player): bool {
return $this->module->isInFarm($player);
}
}
final class FarmModule extends BaseModule {
private FarmApi $api;
public function __construct(Plugin $plugin, array $config) {
parent::__construct($plugin, $config);
$this->api = new FarmApi($this);
}
public function getApi(): object {
return $this->api;
}
}Consumindo:
$api = $this->getApi('statemc:farm');Opcional:
$api = $this->getOptionalApi('statemc:farm');Use os helpers do BaseModule para que a EasyLibrary limpe tudo automaticamente no disable/reload.
$this->addListener(FarmListener::class);O listener deve aceitar o padrão:
final class FarmListener implements Listener {
public function __construct(
private Plugin $plugin,
private FarmModule $module,
private ModuleManager $manager
) {}
}$this->addEvent(PlayerJoinEvent::class, function(PlayerJoinEvent $event): void {
// ...
});$this->addCommand(FarmCommand::class);$this->scheduleDelayedTask(new MyTask(), 20);
$this->scheduleRepeatingTask(new MyTask(), 20);$this->addCleanup(function(): void {
// fechar conexão, limpar cache, salvar algo
});Cada módulo possui pasta própria em:
plugin_data/modules/<module_id_sanitizado>/
Exemplo:
$config = $this->getModuleConfig('config.yml', [
'enabled' => true,
'limit' => 10
]);
$limit = (int) $config->get('limit', 10);Caminho absoluto:
$folder = $this->context()->getDataFolder();
$file = $this->context()->getPath('cache.json');$this->logger()->info('Farm loaded.');
$this->logger()->warning('Farm spawn is not configured.');
$this->logger()->debug('Verbose debug only when debug is enabled.');Ativar debug de um módulo:
/easymodule debug statemc:farm on
Desativar:
/easymodule debug statemc:farm off
Um módulo pode sobrescrever getHealth():
use imperazim\module\health\ModuleHealthReport;
public function getHealth(): ModuleHealthReport {
if ($this->database === null) {
return ModuleHealthReport::error(['Database is not connected.']);
}
if ($this->cacheSize > 10000) {
return ModuleHealthReport::warning(['Cache is very large.'], [
'cacheSize' => $this->cacheSize
]);
}
return ModuleHealthReport::ok([
'cacheSize' => $this->cacheSize
]);
}Ver no jogo/console:
/easymodule health statemc:farm
/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 graph
/easymodule doctor
/easymodule dependents <id>
/easymodule enable <id>
/easymodule disable <id>
/easymodule disable-runtime <id>
/easymodule reload <id>
/easymodule refresh
/easymodule debug <id> <on|off>
Mostra por que um módulo não ativou:
- dependência obrigatória ausente;
- versão inválida;
- plugin dependency ausente;
- service dependency ausente;
- capability dependency ausente;
- módulo desativado no manifest;
- módulo desativado persistentemente;
- último erro de lifecycle.
Mostra recursos rastreados:
- commands;
- listeners;
- eventos;
- tasks;
- components;
- cleanups;
- services.
Ajuda a descobrir vazamento de listener/task após reload.
Mostra visão geral do sistema:
- quantidade de módulos;
- módulos ativos;
- sources;
- services;
- módulos desativados persistentemente;
- dependências faltando;
- versões inválidas;
- services faltando;
- capabilities faltando;
- plugins faltando.
Fluxo de startup:
- EasyLibrary registra sources de módulos.
- Lê todos os
module.yml. - Resolve classes loader.
- Registra módulos no registry.
- Calcula ordem por dependências fortes, soft dependencies e
load-before. - Ativa módulos em múltiplas passagens para permitir providers de capability/service que não sejam dependência direta.
- Módulos que ainda não possuem dependências ficam em estado
waiting_*.
Fluxo de disable:
- Desativa dependentes.
- Chama
onDisable(). - Remove services fornecidos pelo módulo.
- Cancela tasks registradas.
- Remove listeners/eventos.
- Remove commands.
- Executa cleanups.
- Marca como disabled.
Fluxo de reload:
- Desativa dependentes ativos.
- Desativa o módulo alvo.
- Remove recursos antigos.
- Recarrega manifest.
- Recria classe do módulo.
- Reativa pendentes.
Sim. Se dois plugins usam EasyLibrary e ambos declaram seus módulos, eles conseguem se comunicar como módulos normais.
Plugin A:
id: plugin-a:economy
version: 1.0.0
loader: EconomyModule
provides-capabilities:
- economy
provides:
- plugin-a:economy-servicePlugin B:
id: plugin-b:shop
version: 1.0.0
loader: ShopModule
requires:
capabilities:
- economy
services:
- plugin-a:economy-service
soft-dependencies:
- plugin-a:economyNo ShopModule:
$economyProvider = $this->getCapabilityProvider('economy');
$economyService = $this->getOptionalService('plugin-a:economy-service');A vantagem sobre plugin dependency comum é que você não precisa depender sempre de um plugin específico. Pode depender da capacidade economy e permitir que outro módulo forneça isso.
Use IDs com vendor:
statemc:farm
statemc:farm-ui
statemc:economy
imperazim:window
Evite IDs genéricos como:
farm
core
main
api
Use IDs de serviço também com vendor:
statemc:economy
statemc:database
imperazim:window
Capabilities podem ser mais genéricas:
economy
database
window
farm
ranking
permissions
profile
Prefira expor uma classe API pequena em vez de retornar o módulo inteiro.
Bom:
public function getApi(): object {
return $this->api;
}Evite obrigar outros módulos a chamarem métodos internos do módulo.
Use dependencies quando precisa de um módulo específico.
Use requires.capabilities quando aceita qualquer provider compatível.
Use service-dependencies quando precisa de um objeto concreto registrado.
Use soft-dependencies para integrações opcionais.
id: example:economy
name: Example Economy
version: 1.0.0
loader: EconomyModule
namespace: example\modules\economy
provides:
capabilities:
- economy
services:
- example:economy<?php
declare(strict_types=1);
namespace example\modules\economy;
interface EconomyService {
public function getMoney(string $player): float;
public function addMoney(string $player, float $amount): void;
}<?php
declare(strict_types=1);
namespace example\modules\economy;
use imperazim\module\BaseModule;
use imperazim\module\ModuleManager;
use imperazim\module\health\ModuleHealthReport;
final class EconomyModule extends BaseModule implements EconomyService {
/** @var array<string, float> */
private array $money = [];
protected function onEnable(ModuleManager $manager): void {
$this->provideService('example:economy', $this, EconomyService::class, $this->getVersion());
$this->logger()->info('Economy service registered.');
}
public function getMoney(string $player): float {
return $this->money[strtolower($player)] ?? 0.0;
}
public function addMoney(string $player, float $amount): void {
$key = strtolower($player);
$this->money[$key] = ($this->money[$key] ?? 0.0) + $amount;
}
public function getHealth(): ModuleHealthReport {
return ModuleHealthReport::ok([
'accounts' => count($this->money)
]);
}
}id: example:shop
name: Example Shop
version: 1.0.0
loader: ShopModule
namespace: example\modules\shop
requires:
capabilities:
- economy
services:
- example:economy
soft-dependencies:
- example:economy<?php
declare(strict_types=1);
namespace example\modules\shop;
use example\modules\economy\EconomyService;
use imperazim\module\BaseModule;
use imperazim\module\ModuleManager;
final class ShopModule extends BaseModule {
protected function onEnable(ModuleManager $manager): void {
$economy = $this->getTypedService('example:economy', EconomyService::class);
$this->logger()->info('Shop connected to economy service.');
}
}- Testar dois plugins diferentes fornecendo e consumindo módulos.
- Testar duas capabilities iguais e provider preferido.
- Testar reload de módulo com dependentes ativos.
- Testar disable persistente e enable persistente.
- Testar services removidos após disable/reload.
- Testar tasks/listeners/commands removidos após reload.
- Testar
doctor,why,resources,failures,capabilities. - Testar plugin disable/enable de um plugin dono de módulos.
- Testar erro em
onEnable()e confirmar que recursos parciais são limpos.
- Não registre listener/task/command manualmente se existe helper do módulo.
- Não acesse módulo de outro plugin por classe concreta sem necessidade.
- Prefira service, API ou capability.
- Dê IDs com vendor.
- Evite
reloadem produção para módulos que manipulam estado crítico sem salvar antes. - Use
getHealth()para expor problemas reais do módulo. - Use
logger()->debug()para logs verbosos e deixe desligado por padrão. - Use
requires.capabilitiespara integrações flexíveis. - Use
dependenciesquando precisa exatamente daquele módulo.
discovered
waiting_owner
waiting_dependency
waiting_service
enabling
enabled
disabling
disabled
reloading
failed
failed_dependency
waiting_service também pode ser usado quando está faltando capability, porque capability é resolvida por provider ativo.
As preferências globais ficam em:
plugin_data/EasyLibrary/modules.yml
Atualmente pode conter:
disabled:
- example:shop
debug:
example:economy: true
providers:
economy: example:economydisabled: módulos desativados persistentemente.debug: debug por módulo.providers: provider preferido por capability.