Skip to content

Latest commit

 

History

History
373 lines (283 loc) · 11.8 KB

File metadata and controls

373 lines (283 loc) · 11.8 KB

AnarchyRuleEngine Module API

Version: 1.0.0 (Beta) Status: Built-in lifecycle enabled (registration, enable, disable)

Overview

The AnarchyRuleEngine Module API provides a stable, extensible abstraction for building optional modules that integrate with the core rule engine. Modules can:

  • Register custom conditions and actions
  • Provide placeholder resolvers
  • Implement specialized features (Combat, Economy, Items, World, etc.)
  • Operate independently without modifying core plugin behavior
  • Be loaded/unloaded at runtime

Architecture

Core Classes

RuleModule (Interface)

Defines the contract that all modules must implement.

public interface RuleModule {
    ModuleDescriptor getDescriptor();
    void onEnable(ModuleContext context) throws Exception;
    void onDisable();
}

Lifecycle:

  • getDescriptor(): Provides module metadata (called during registration)
  • onEnable(): Module initialization; happens after engine is fully initialized
  • onDisable(): Module cleanup; happens during plugin shutdown or reload

ModuleDescriptor (Immutable)

Immutable metadata about a module. Contains:

  • id: Unique identifier (String, required, non-empty)
  • name: Human-readable name (String, required, non-empty)
  • version: Module version (String, required, non-empty)
  • description: What the module does (String, required, non-empty)
ModuleDescriptor descriptor = new ModuleDescriptor(
    "combat",
    "Combat Module",
    "1.0.0",
    "Provides combat-related conditions and actions"
);

ModuleContext (Immutable)

Immutable context passed to modules during lifecycle methods. Exposes only stable, vetted APIs:

Method Purpose Type
getPlugin() Access plugin for tasks, config, etc. JavaPlugin
getRuleEngine() Read-only access to rule engine RuleEngine
getApi() Public integration API for registering extensions AnarchyRuleEngineApi
getLogger() Logger for module logging Logger
registerCondition(String id, Collection<String> keys, ConditionFactory.ConditionCreator creator) NEW Register custom conditions (convenience method) void
registerAction(String type, ActionFactory.ActionCreator creator) NEW Register custom actions (convenience method) void

Design Note: ModuleContext intentionally exposes only stable public APIs to allow the engine to evolve without breaking module contracts.

Convenience Methods: The registerCondition() and registerAction() methods are convenience wrappers that delegate to the underlying AnarchyRuleEngineApi. They provide a cleaner API surface for modules registering extensions during initialization.

ModuleRegistry

Manages module registration, enabled state, and lifecycle order.

Key Methods:

  • register(RuleModule module): Register a module (rejects nulls, duplicates)
  • getModule(String id): Look up a module by id (returns Optional)
  • getModules(): Get all registered modules as unmodifiable list
  • getModuleCount(): Get count of registered modules
  • isRegistered(String id): Check if a module is registered
  • enableAll(ModuleContext context): Enable modules in registration order
  • disableAll(Logger logger): Disable enabled modules in reverse registration order
  • isEnabled(String id): Check module runtime state (ENABLED/DISABLED)

Validation:

  • Rejects null modules
  • Rejects modules with null/empty id
  • Rejects duplicate module ids
  • Throws descriptive IllegalArgumentException on violation
  • Preserves registration order

Usage Examples

1. Creating a Custom Module

public class CombatModule implements RuleModule {

    private ModuleContext context;

    @Override
    public ModuleDescriptor getDescriptor() {
        return new ModuleDescriptor(
            "combat",
            "Combat Module",
            "1.0.0",
            "Combat-related conditions and actions"
        );
    }

    @Override
    public void onEnable(ModuleContext context) throws Exception {
        this.context = context;
        
        context.getLogger().info("[CombatModule] Enabling...");
        
        // Register custom conditions using convenience method
        context.registerCondition(
            "player-health",
            Arrays.asList("player-health"),
            section -> new PlayerHealthCondition(...)
        );
        
        // Register custom actions using convenience method
        context.registerAction(
            "damage-player",
            actionData -> new DamagePlayerAction(...)
        );
        
        context.getLogger().info("[CombatModule] Enabled successfully");
    }

    @Override
    public void onDisable() {
        context.getLogger().info("[CombatModule] Disabling...");
        // Cleanup resources
        context.getLogger().info("[CombatModule] Disabled");
    }
}

2. Registering Modules

ModuleRegistry registry = new ModuleRegistry();

// Register multiple modules
registry.register(new CombatModule());
registry.register(new ItemsModule());
registry.register(new EconomyModule());

// Look up a module
Optional<RuleModule> combat = registry.getModule("combat");
if (combat.isPresent()) {
    RuleModule module = combat.get();
    // Module operations
}

// Iterate all modules
for (RuleModule module : registry.getModules()) {
    System.out.println(module.getDescriptor().getName());
}

3. Module Initialization Pattern

ModuleRegistry registry = new ModuleRegistry();

// Register modules
registry.register(new CombatModule());
registry.register(new ItemsModule());

// Enable all modules (in order)
for (RuleModule module : registry.getModules()) {
    try {
        // Create context from plugin state
        ModuleContext context = new ModuleContext(
            plugin,
            ruleEngine,
            api,
            plugin.getLogger()
        );
        
        module.onEnable(context);
        
        plugin.getLogger().info(
            "Enabled module: " + module.getDescriptor().getId()
        );
    } catch (Exception e) {
        plugin.getLogger().severe(
            "Failed to enable module " + module.getDescriptor().getId()
            + ": " + e.getMessage()
        );
        // Handle error appropriately
    }
}

4. Shutdown Handling

// Disable all modules (in reverse order)
List<RuleModule> modules = registry.getModules();
for (int i = modules.size() - 1; i >= 0; i--) {
    try {
        modules.get(i).onDisable();
    } catch (Exception e) {
        plugin.getLogger().warning(
            "Error disabling module: " + e.getMessage()
        );
    }
}

Design Principles

1. Small and Focused

The API intentionally exposes only what's needed for modules to integrate cleanly. Stability is prioritized over convenience.

2. Immutable Where Practical

ModuleDescriptor and ModuleContext are immutable, preventing accidental state corruption and simplifying reasoning about module behavior.

3. Explicit Errors

All validation failures produce clear, descriptive error messages to help module developers understand what went wrong.

4. No Reflection

The API uses interfaces and plain Java types. No reflection, no magic, no runtime class loading.

5. Java 8 Compatible

Uses only Java 8 language features (no records, sealed classes, pattern matching, etc.).

6. Non-Invasive

The Module API is completely separate from core plugin initialization. No changes to existing startup/shutdown behavior.

Future Modules (Not Yet Implemented)

The Module API is designed to support these future modules:

Module Purpose
CombatModule Player damage, health, combat conditions/actions
ItemsModule Custom item effects, item-based conditions
WorldModule World manipulation, region-based features
EconomyModule Economy conditions, money-based actions (with Vault integration)
GUIModule Admin GUI for rule management

Each would:

  1. Implement RuleModule interface
  2. Define unique id in ModuleDescriptor
  3. Register extensions via ModuleContext.getApi()
  4. Clean up resources in onDisable()

Current Status

API Complete and Documented

  • ✅ Module contract defined (RuleModule)
  • ✅ Metadata support (ModuleDescriptor)
  • ✅ Context and lifecycle management (ModuleContext)
  • ✅ Registration convenience methods (registerCondition(), registerAction())
  • ✅ Registration and discovery (ModuleRegistry)
  • ✅ Lifecycle execution integrated into plugin startup/shutdown
  • ✅ Built-in CoreModule and CustomItemsModule registered by default

Integration Points

Modules can integrate with AnarchyRuleEngine via ModuleContext:

// Convenience methods (recommended for registration)
context.registerCondition("my-condition", Arrays.asList("my-condition"),
    section -> new MyCondition(...));

context.registerAction("my-action",
    data -> new MyAction(...));

// Alternative: Direct API access (also supported)
AnarchyRuleEngineApi api = context.getApi();

api.registerCondition("my-condition", Arrays.asList("my-condition"),
    section -> new MyCondition(...));

api.registerAction("my-action",
    data -> new MyAction(...));

api.registerPlaceholder(
    new MyPlaceholderResolver());

// Query and control rules
List<Rule> rules = api.getRules();
RuleExecutionResult result = api.evaluate(context);
api.enableRule("my-rule");
api.disableRule("my-rule");
api.resetRule("my-rule");

Performance Notes

  • Registration: O(1) per module
  • Lookup: O(1) via HashMap
  • Iteration: O(n) unmodifiable copy
  • Memory: Minimal; stores only module references and metadata
  • Thread Safety: Not thread-safe; use on main Bukkit thread only

Compatibility

  • Java: 8+
  • Paper: 1.16.5+
  • Bukkit: 1.16.5+
  • Dependencies: None (uses only core AnarchyRuleEngine APIs)

Example: ItemsModule (Conceptual)

public class ItemsModule implements RuleModule {
    
    @Override
    public ModuleDescriptor getDescriptor() {
        return new ModuleDescriptor(
            "items",
            "Items Module",
            "1.0.0",
            "Custom item properties and conditions"
        );
    }

    @Override
    public void onEnable(ModuleContext context) throws Exception {
        context.getLogger().info("[ItemsModule] Loading custom items...");
        
        // Register custom conditions for item checks using convenience method
        context.registerCondition("item-name", 
            Arrays.asList("item-name"),
            section -> new ItemNameCondition(...));
        
        // Register actions that modify items using convenience method
        context.registerAction("modify-item",
            data -> new ModifyItemAction(...));
        
        context.getLogger().info("[ItemsModule] Items module loaded");
    }

    @Override
    public void onDisable() {
        // Items module has no persistent state to clean up
    }
}

FAQ

Q: Can modules be loaded dynamically at runtime? A: Modules are currently registered during plugin startup. Runtime hot-loading from external module jars is not implemented.

Q: Can modules depend on each other? A: Not directly. Modules are independent. If needed, modules can coordinate through shared conditions/actions registered in the API.

Q: What if a module throws an exception during enable? A: The exception is propagated to the caller. It's up to the plugin to decide: skip the module, disable plugin, or handle gracefully.

Q: Can modules access internal services? A: No, intentionally. Only stable public APIs are exposed via ModuleContext to prevent tight coupling and allow future refactoring.

Q: Is the registry thread-safe? A: No. All operations must occur on the main Bukkit thread during startup/shutdown.

Related Documentation