Skip to content

Platform Abstraction

Petrus Pradella edited this page Jul 23, 2026 · 1 revision

Platform Abstraction

The common module never imports Bukkit. Everything platform-specific reaches it through two mechanisms: runtime providers (interfaces looked up in a registry) and compile-time stubs (the "platoverride" pattern). This page is the reference for both, and for how to add a new seam of your own.

For the big picture and the startup order, start at Architecture Overview.


Runtime providers: ECProviders / IPlatform

The registry

EverNifeCore.getProviders() returns a single ECProviders instance. It wraps an ECBaseProvider, a thread-safe (ConcurrentHashMap) map of Class<?> -> implementation:

public <T> T register(Class<T> providerType, T something);  // register (last write wins, logs a warning if replacing)
public <T> T provide(Class<T> clazz);                        // look up (throws NoSuchElementException if absent)

ECProviders exposes typed accessors over that map:

Accessor Provider interface Registered by
getPlatform() IPlatform McPlatform / HyPlatform
getECPluginExtractor() IECPluginExtractor McECPluginExtractor / HyECPluginExtractor
getEventDispatcher() ECEventDispatcher McECEventDispatcher / HyECEventDispatcher

EverNifeCore.getPlatform() is a shortcut for getProviders().getPlatform().

provide(...) throws NoSuchElementException when a provider is missing. That only happens if core code runs before the platform entry point has registered its providers - which is why each platform registers them as early as possible (Bukkit: the plugin's instance initializer; Hytale: the plugin constructor).

What IPlatform covers

IPlatform (in common at br.com.finalcraft.evernifecore.api.common.providers.platform.IPlatform) is the main contract the core calls into. Its surface, grouped:

  • Identity - getPlatformProviderId() (a stable tag, "minecraft" / "hytale", persisted inside account rows, so it must never change).
  • Players - getOnlinePlayers(), getPlayer(String), getPlayer(UUID) (returning FPlayer).
  • Plugins - isPluginLoaded(String).
  • Commands - registerCommand, unregisterCommand, makeConsoleExecuteCommand, makePlayerExecuteCommand.
  • Listeners - registerECListener, unregisterECListener.
  • Placeholders - isPAPIPresent, parse(FPlayer, String), createPlaceholderIntegration(...).
  • Cosmetic output - sendActionBarMessage, serverSupportsActionBar.
  • Adapters - getVecAdapter(), getChatAdapter(), createLogAdapterFor(...).
  • Scheduling bridges - runOnFirstTick(Runnable) (runs after every plugin has enabled) and runOnMainThread(Runnable) (the bridge async storage callbacks use to touch game state safely).
  • Lazy bootstrap hooks - registerConfigTypes() and registerArgParsers() (see below).

The two lazy hooks

registerConfigTypes() and registerArgParsers() are called once, lazily, the first time the subsystem that needs them initializes - not eagerly at enable time. This is so that a plugin which loads before EverNifeCore still gets the types/parsers registered when it first uses them:

  • ConfigFactory's static initializer calls EverNifeCore.getPlatform().registerConfigTypes(), teaching the config engine about the platform's own types (Bukkit ItemStack/Location, Hytale vectors, ...). Implementations must be idempotent.
  • FinalCMDManager's static initializer calls EverNifeCore.getPlatform().registerArgParsers() after registering the global parsers, so the platform adds its own (see Argument Parsing).

Compile-time stubs: the "platoverride" pattern

Some things common needs are not a single method call but a class it references directly - a scheduler, a player adapter, an argument wrapper. For these, the seam is a class whose bytecode is swapped at runtime.

How it works:

  1. The api-contracts module holds a stub class - often empty, or with only the members common compiles against.
  2. common declares compileOnly project(':api-contracts'), so it compiles against the stub but never packages it.
  3. Each platform module ships the real class under the exact same fully-qualified name.
  4. At runtime, the platform's real class is the only one on the classpath. common's call sites resolve to it.

The stubs (all under api-contracts):

Stub (FQN tail) Real implementation lives in
api.platoverride.player.FPlayerAdapter each platform's player adapter
api.platoverride.argumento.ArgumentoAdapter each platform's argument adapter
minecraft.argumento.MinecraftArgumento minecraft
minecraft.scheduler.McFCScheduler minecraft
hytale.scheduler.HyFCScheduler hytale
api.platoverride.eclistener.IECBaseListener each platform's listener base
api.platoverride.math.game.adapter.GameVecPlatformAdapterConverter each platform's vec converter

Concretely. The McFCScheduler stub in api-contracts is just:

public class McFCScheduler {
    public static McFCScheduler INSTANCE;
}

The real McFCScheduler in minecraft - same package, same name - has the full Bukkit-backed implementation (runSync, scheduleSyncInTicks, the SynchronizedAction async->main bridge, ...) and assigns INSTANCE = new McFCScheduler(). FCScheduler.getMinecraftScheduler() in common compiles against the stub and finds the real one at runtime.

Because the stub is compileOnly, production JARs never contain it. The test runtime is the exception: common's tests add project(':api-contracts') back so the stubs exist while no real platform is on the classpath.


Adding a new platform seam

When common needs something a platform must provide, pick the mechanism by shape:

A single behavior (a method call) -> add it to a provider interface.

  1. Add the method to IPlatform (or introduce a new provider interface + an ECProviders accessor).
  2. Implement it in both McPlatform and HyPlatform.
  3. Call it from common through EverNifeCore.getPlatform().

A whole class common references directly -> add a platoverride stub.

  1. Add the stub class to api-contracts with just the members common compiles against.
  2. Ship a real class with the identical fully-qualified name in each platform module.
  3. Reference the stub from common; the real class replaces it at runtime.
  4. Never let a deployable JAR bundle the stub (api-contracts is compileOnly); add it to the test runtime if the core tests need it.

See also

Clone this wiki locally