-
Notifications
You must be signed in to change notification settings - Fork 0
Service Container and Capabilities
PrisonCore has two ways for a module to find things at runtime: the service container, which is type-keyed and built into the kernel, and the capability registry, which is name-keyed and meant for cross-module discovery.
If you're trying to reach something the platform itself provides (the scheduler, the menu service, the storage registry, the message service), use the service container. If you're trying to find something another module published, use the capability registry.
com.github.frosxt.prisoncore.api.service.ServiceContainer
Resolved through your ModuleContext:
final TaskOrchestrator orchestrator = context.services().resolve(TaskOrchestrator.class);Methods:
<T> T resolve(Class<T> type);
<T> Optional<T> resolveOptional(Class<T> type);
<T> Optional<T> resolveByName(String name, Class<T> type);
boolean has(Class<?> type);
<T> Provider<T> provider(Class<T> type);
<T> void register(ServiceDescriptor<T> descriptor);
Scope createModuleScope(ModuleHandle moduleHandle);resolve(Class) is the one you'll use 99% of the time. It throws if the service isn't registered.
resolveOptional(Class) returns an Optional for services that may not be present (for example, services another module publishes that yours optionally integrates with).
resolveByName(String, Class) is for the rare case where two services share an interface and need to be told apart by name.
provider(Class) returns a lazy Provider<T> for services with cyclical or deferred wiring.
register(ServiceDescriptor) adds a new service to the container. Most modules don't need this; you only register services here when you want other modules to resolve something you provide. See ServiceDescriptor below.
These are registered before any POST_INFRASTRUCTURE module loads. You can resolve them in your onPrepare.
-
TaskOrchestrator— scheduling. See Scheduler. -
CommandService— register commands. See Commands. -
MenuService— open inventory menus. See Menus. -
MessageService— send keyed multi-channel messages. See Messages and Placeholders. -
MessageCatalog— the message template store backingMessageService. -
PlaceholderService— register placeholder resolvers and process templates. -
StorageRegistry— acquire backend handles for JSON/SQLite/SQL/Mongo. See Storage. -
ConfigService— generic typed YAML loader for files outsidecore.yml. -
CoreConfig— the parsedcore.yml. Read this forstorage.backend. -
DomainEventBus— cross-module domain event publishing and subscription. -
BukkitListenerHost— register raw Bukkit listeners through the platform. See Events and Listeners. -
PlayerProfileService— player profile load/save and session management. -
PlatformInfo— platform metadata (version, server type). -
ObservabilityService— kernel-managed metrics and timings. -
CapabilityRegistry— also reachable viacontext.capabilities().
If you need to publish your own service into the container so other modules can resolve it, build one with ServiceDescriptor.builder(Class).
context.services().register(
ServiceDescriptor.builder(MyService.class)
.instance(new MyServiceImpl())
.scope(ServiceScope.KERNEL)
.build()
);Builder methods:
-
factory(Function<ServiceContainer, T>)— lazy construction. The function runs when the firstresolvelands. Use this when your service depends on another service in the container. -
instance(T)— direct instance for things that are already constructed. -
scope(ServiceScope)—KERNEL,MODULE, orSESSION. Defaults toKERNEL. -
dependsOn(Class<?>... types)— declare dependencies for the container's topological initialization. -
name(String)— give the descriptor a name so callers can reach it viaresolveByName. -
onInitialize(Consumer<T>)— a callback that fires once after construction. -
onDestroy(Consumer<T>)— a callback that fires when the container shuts the service down. Use this if your service holds resources that need closing.
For most external modules, registering a service in the kernel container is overkill. Publish a capability instead.
com.github.frosxt.prisoncore.api.service.ServiceScope
public enum ServiceScope {
KERNEL,
MODULE,
SESSION
}KERNEL lives for the whole platform lifetime and is shared across modules. The default.
MODULE lives for a single module's lifetime. Created when the module enables, disposed when it disables. Use this when each loaded module needs its own instance of a service.
SESSION lives for a single player session. Created on join, disposed on quit (or kernel shutdown).
com.github.frosxt.prisoncore.api.capability.CapabilityRegistry
Reached via context.capabilities(). This is how modules find each other without having to know each other's concrete implementation classes.
<T> void register(CapabilityKey<T> key, T implementation);
<T> T resolve(CapabilityKey<T> key);
<T> Optional<T> resolveOptional(CapabilityKey<T> key);
<T> void registerIfAbsent(CapabilityKey<T> key, T implementation);
boolean has(CapabilityKey<?> key);
Set<CapabilityKey<?>> allKeys();
int size();
void registerMarker(String qualifiedName);
boolean hasMarker(String qualifiedName);
Set<String> allMarkers();register publishes an implementation for a key. It throws on duplicate registration, which is the right behavior — two modules publishing the same capability is a configuration error.
resolve throws if the key isn't published. resolveOptional returns empty.
registerIfAbsent is the soft variant of register for capabilities multiple modules might race to publish.
The Marker family is for presence-only capabilities where there is no concrete implementation to hand out. You're saying "this feature exists, do whatever you do when it does."
com.github.frosxt.prisoncore.api.capability.CapabilityKey<T>
public static <T> CapabilityKey<T> of(String namespace, String name, Class<T> contractType);
public static <T> CapabilityKey<T> of(String name, Class<T> contractType); // namespace defaults to "core"
public String namespace();
public String name();
public Class<T> contractType();
public String qualifiedName(); // "namespace:name"The namespace is the module id by convention. The name is the capability you're advertising. The contract type is the interface consumers will cast to.
private static final CapabilityKey<EconomyService> ECONOMY =
CapabilityKey.of("economy", "economy-service", EconomyService.class);
// publishing
context.capabilities().register(ECONOMY, myEconomyImpl);
// consuming
final EconomyService economy = context.capabilities().resolve(ECONOMY);Two keys are equal when their namespace and name match. The contract type is type-safety scaffolding and does not participate in equality.
If the platform itself provides it (or your module is wiring something into the kernel for everyone), use ServiceContainer.
If your module exposes a feature for other modules to optionally integrate with, publish a CapabilityKey. Other modules can call resolveOptional to check, and your module is allowed to be absent without breaking theirs.
If you only need to know whether a feature exists, use a marker capability and skip the typed handle entirely.
This wiki documents the public API surface module authors are expected to touch. Kernel internals are intentionally omitted. Spot something wrong? Open an issue.
Start here
Architecture
Subsystems
- Commands
- Menus
- Messages and Placeholders
- Scheduler
- Storage
- Events and Listeners
- Configuration
- Player Profiles
Utilities