Skip to content

Lifecycle and discovery

Plugin-owned facade

For the new API, create one Renderer.forPlugin(this) in onEnable() and close it from onDisable(), on the server thread. The quickstart is a complete compiled example. The facade owns capture/render work, listeners, temporary streams and a bounded conversion worker. Owner disable also triggers cleanup automatically. Saved provider photos survive this cleanup.

Service discovery is lazy: when no provider is ready, a new operation fails with PROVIDER_DISABLED. Provider replacement invalidates existing work; later requests can discover the new provider. Do not carry captured scenes or assets across generations. Affected world unload also invalidates pending work. Use the facade's callback overloads for Bukkit delivery and keep callbacks short.

renderer.session() and renderer.client() retain the advanced API. If you manage a separate low-level client, you own its lifecycle as shown below.

Direct clients and optional providers

For a hard dependency, direct API imports are safe: load RendererService, create one client for your plugin, retain it in the lifecycle owner, and call close() during disable. After a provider reload, discover a new service and client; do not retain old clients, assets, scenes, or tasks. See the complete sample plugin.

package docs.examples;

import ke.ric.renderer.api.RendererClient;
import ke.ric.renderer.api.RendererService;
import org.bukkit.Bukkit;
import org.bukkit.plugin.Plugin;

public final class LifecycleExample {
    public static RendererClient discover(Plugin owner) {
        RendererService service = Bukkit.getServicesManager().load(RendererService.class);
        return service == null ? null : service.createClient(owner);
    }
}

For softdepend, the Bukkit entry point must contain no API imports or symbolic API references, because the provided API can be absent before discovery. Use an API-free entry point that reflectively loads an isolated API runtime only after checking availability. Retry discovery when the asynchronously starting provider registers RendererService; do not busy-loop or enable twice.

package docs.examples;

import org.bukkit.plugin.java.JavaPlugin;

/** API-free entry point for a soft-dependent consumer. */
public final class OptionalProviderEntrypoint extends JavaPlugin {
    @Override public void onEnable() {
        try {
            Class.forName("ke.ric.renderer.api.RendererService", false, getClassLoader());
            Class.forName("docs.examples.OptionalRendererRuntime", true, getClassLoader())
                    .getMethod("enable", JavaPlugin.class).invoke(null, this);
        } catch (ReflectiveOperationException | LinkageError unavailable) {
            getLogger().warning("Renderer API/provider is unavailable; continuing without rendering");
        }
    }
    @Override public void onDisable() {
        try { Class.forName("docs.examples.OptionalRendererRuntime", false, getClassLoader())
                .getMethod("disable").invoke(null); }
        catch (ReflectiveOperationException | LinkageError ignored) { }
    }
}
package docs.examples;

import ke.ric.renderer.api.RendererClient;
import ke.ric.renderer.api.RendererService;
import org.bukkit.Bukkit;
import org.bukkit.event.EventHandler;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import org.bukkit.event.server.ServiceRegisterEvent;
import org.bukkit.plugin.java.JavaPlugin;

/** Isolated API-linked runtime loaded only after the API-free entry point checks availability. */
public final class OptionalRendererRuntime {
    private static JavaPlugin owner;
    private static Listener registrationListener;
    private static RendererClient client;

    public static synchronized void enable(JavaPlugin plugin) {
        if (owner != null) return;
        owner = java.util.Objects.requireNonNull(plugin, "plugin");
        registrationListener = new Listener() {
            @EventHandler
            public void onServiceRegister(ServiceRegisterEvent event) {
                if (event.getProvider().getService() == RendererService.class) attach();
            }
        };
        Bukkit.getPluginManager().registerEvents(registrationListener, plugin);
        attach();
    }

    private static synchronized void attach() {
        if (owner == null || client != null) return;
        RendererService service = Bukkit.getServicesManager().load(RendererService.class);
        if (service == null) return;
        client = service.createClient(owner);
    }

    public static void disable() {
        RendererClient closingClient;
        Listener closingListener;
        synchronized (OptionalRendererRuntime.class) {
            closingClient = client;
            closingListener = registrationListener;
            client = null;
            registrationListener = null;
            owner = null;
        }
        if (closingListener != null) HandlerList.unregisterAll(closingListener);
        if (closingClient != null) closingClient.close();
    }
}