Skip to content

Lifecycle

weftkit owns your plugin from onEnable to onDisable. BukkitWeft.enable starts everything up and BukkitWeft.disable shuts it down.

Startup

BukkitWeft.enable builds the loader and brings up every @Singleton in dependency order. As each singleton is created, if it implements Loader its load method runs. Returning false from load aborts startup: weftkit tears down everything it has already loaded, in reverse order, and disables the plugin, so you never run in a half initialized state. Once every singleton has loaded, weftkit registers each @Wired listener with the server.

@Override
public void onEnable() {
    loader = BukkitWeft.enable(this, WiredComponents.INSTANCE);
    if (loader == null) return;
}

Load order

Load order follows the dependency graph. A component loads after every component it depends on, so a singleton can rely on its dependencies being fully loaded inside its own load. A value exposed with @Provides is available once its owner has loaded. For ordering that is not expressed through constructor dependencies, @Initializes and @Requires place a component after the singleton that sets up the static holders it reads.

Shutdown

BukkitWeft.disable runs unload on every loaded singleton in reverse load order, so a component is torn down before the ones it depended on. Teardown keeps going even if one component's unload throws, and the failures are reported together at the end. disable is safe to call when startup aborted or never ran.

@Override
public void onDisable() {
    BukkitWeft.disable(this, loader);
}

Reloading

weftkit deliberately has no framework level reload. Recreating components at runtime cannot fix references captured outside the graph, like scheduled tasks, command executors, or other plugins holding your API service, and those stale references are exactly the bugs that made /reload infamous. Reload in place instead: give the singleton that owns the state a reload method and let dependents read through it, so nothing ever goes stale.

@Wired
@Singleton
public final class Config implements Loader {

    private volatile Settings settings;

    @Override
    public boolean load() {
        return reload();
    }

    public boolean reload() {
        Settings parsed = parse();
        if (parsed == null) return false;
        settings = parsed;
        return true;
    }

    public Settings settings() {
        return settings;
    }
}

A reload command is then one line: loader.get(Config.class).reload(). Components that injected Config call settings() when they need values and always see the current state.

Diagnostics

The loader exposes what happened during startup. loadOrder returns the singleton load sequence, and loadTimings returns how long each singleton took to construct and load, in load order.

loader.loadTimings().forEach((type, duration) ->
        getLogger().info(type.getSimpleName() + " loaded in " + duration.toMillis() + "ms"));

The processor also writes the full dependency graph as weftkit-graph.dot next to the generated registry sources (under build/generated/sources/annotationProcessor). Render it with Graphviz to see your plugin's wiring: singletons are boxes, and every edge points at a dependency.

dot -Tsvg weftkit-graph.dot -o weftkit-graph.svg

The Loader hook

Implement Loader on any singleton that needs to do work at startup or shutdown. load runs as the singleton is created and unload runs on shutdown. Only singletons may implement Loader, since a per-injection component would never have its load called.

@Wired
@Singleton
public final class Metrics implements Loader {

    @Override
    public boolean load() {
        // start up here, return false to abort the plugin
        return true;
    }

    @Override
    public void unload() {
        // release resources here, runs in reverse load order
    }
}