Class CloudFeatures

java.lang.Object
com.ultikits.ultitools.utils.CloudFeatures

@Internal public final class CloudFeatures extends Object
The inbound UltiCloud features of one enabled plugin (plan 22-21, D-26, S-59, S-60); internal, not for module use.

Two lifetimes. The objects a module or a redelivery depends on live as long as the plugin is enabled: the CloudPlayerLinks API and the PlayerLinkChannel behind it (so a handler a module set at startup survives every reconnect), and the one SessionDedupeLedger every handler shares (so the ledger monitor is the same across connections). Each upgraded connection gets its own AgentProtocol; CloudSession calls register(AgentMessageRouter, CloudChannel, AgentProtocol, CredentialRotationHandler.CredentialPersister, ScheduledExecutorService) once for it, which registers the request handlers on that connection's router and points the lifetime objects at that connection. A credential.rotate is persisted through the session that owns the connection, never through the store directly, because that session is the one owner of the current credential.

Threads. The player-link handler of a module runs on one daemon thread, UltiTools-CloudLinks; the create and handler deadlines run on a second, UltiTools-CloudLinks-Timer. Both stop in shutdown(). The file and server.properties handlers do blocking disk I/O, so each connection gets one SerialRequestWorker (UltiTools-CloudFiles, daemon, bounded queue, overflow answered rate_limited) that runs them in arrival order; heartbeats, acks and every other request never wait behind a slow listing. The worker closes with its connection (on close, on the next connection, and in shutdown()). The router dispatches every other request on the agent thread, which is never the server thread; the handlers that call the Bukkit API hand that work to the server thread through CloudFeatures.Bindings.mainThread.

Registered request types (each only when its port is bound; CloudFeatures.Bindings.forPlugin(com.ultikits.ultitools.UltiTools) binds every one): command.execute (22-09), server.power (22-10), player.action (22-11), files.* and properties.* (22-14), config.* (22-15), module.request (22-16), and player.link.created/completed/expired and credential.rotate (22-17).

Capabilities. AgentCapabilities decides the hello list and the router's per-request switch check (S-65, S-66). A module.request is also filtered by the module capabilities the gateway accepted: a request for a module whose modules:<id>@<X.Y.Z> name was not accepted is answered capability_not_accepted (contract SEMANTICS 8) and never reaches the module.

Outbound (plan 22-22). One AgentOutboundFeeds lives as long as the plugin is enabled. On every established connection it gets the live channel; the connection's agent scheduler then runs its once-a-second send (log lines) until the close, and a main-thread task every 20 ticks, started at the first connection, samples TPS, CPU and the server state and advances the telemetry cadence. snapshot.request is registered on every connection. A /ul reload that changes an operator setting sends settings.changed once (onReload()).

Shutdown (22-23 shutdown-window round). Every connection's router and file worker share one ShutdownGate built from shutdown(): from the first step of UltiTools#onDisable every state-changing request is answered with its shutting-down failure and runs nothing, and a change already running holds the gate's permit until it is answered, so onDisable closes the action log only after it.

Since:
6.3.1
  • Field Details

  • Constructor Details

  • Method Details

    • start

      public static CloudFeatures start(UltiTools plugin)
      Creates the features of the enabling plugin and makes them the ones every new connection registers. Called once per enable, before any module loads, so a module can set its player-link handler while it enables.
      Parameters:
      plugin - the enabling framework plugin
      Returns:
      the features, until shutdown()
    • start

      static CloudFeatures start(CloudFeatures.Bindings bindings)
      Parameters:
      bindings - the ports the handlers are built from
      Returns:
      new features, installed for every later connection
    • installed

      static CloudFeatures installed()
      Returns:
      the features new connections register, or null before the plugin enabled or after it stopped
    • playerLinks

      public CloudPlayerLinks playerLinks()
      Returns:
      the player-link API; the same object for the whole enabled lifetime, never null
    • shutdown

      public void shutdown()
      Stops the player-link threads and answers every pending create as not connected. New connections no longer register these features. Never waits for a thread.
    • register

      Registers the inbound handlers on one connection's router and points the lifetime objects at that connection. Called by CloudSession once for each upgraded socket, before its hello.
      Parameters:
      router - the connection's router
      channel - the connection's live channel (open between welcome and close)
      protocol - the connection's protocol layer
      rotation - persists a credential.rotate through the session that owns this connection
      agent - the connection's agent scheduler: runs the once-a-second send while it is established
    • registerSnapshots

      private void registerSnapshots(AgentMessageRouter router)
      Registers snapshot.request on every connection; the telemetry behind it is looked up per request. While it is unavailable the hello does not declare telemetry and the router refuses the request before it gets here (available(String)); the warning says why, once per connection.
    • available

      private boolean available(String contractCapability)
      Returns:
      whether the feature behind a contract capability can run now (only telemetry can be missing)
    • startMainTimer

      private void startMainTimer()
      Starts the main-thread sampling task once; it runs until shutdown(), so TPS history has no gaps.
    • sendPlayerCommand

      void sendPlayerCommand(String commandLine)
      Sends one player.command carrying only the command's normalised name (D-20), while a connection is established and the events:commands switch is on; never buffered (D-14).
      Parameters:
      commandLine - the command line as the player typed it
    • capabilities

      AgentCapabilities capabilities()
      Returns:
      the hello list and the per-request switch check of every connection
    • helloSettings

      com.google.gson.JsonObject helloSettings()
      The hello's operator settings (powerActionsEnabled), remembered as reported so a later reload reports only a change (settings.changed, plan 22-22).
      Returns:
      the settings the next hello carries
    • onReload

      public void onReload()
      Reports a changed operator setting on the open connection (plan 22-22, S-53): called by /ul reload after the framework configuration was read again. Exactly one settings.changed { powerActionsEnabled } is sent when the value differs from the last one the panel was told (by a hello or an earlier settings.changed); nothing when it is unchanged or no connection is established, in which case the next hello carries the value.

      A reload that adds or removes a declared capability (an operator switch) closes the connection normally (1000) instead, and the connection reconnects after its normal short backoff: capabilities are negotiated only at the handshake (backend decision k), so the new hello declares the new set and carries the settings; no settings.changed is sent then. A change made while the hello waits for its welcome is caught when the welcome arrives. Reads the configuration only; never writes an operator file (D-31).

    • reconnectForNewCapabilities

      private static void reconnectForNewCapabilities(AgentProtocol connection)
    • reportSettingsIfChanged

      private void reportSettingsIfChanged(CloudChannel channel)
      The comparison and the mark happen under one monitor, with the hello's own mark, so a reload racing a hello can neither repeat a value nor lose one. Also run when a connection is established, which reports a change made between its hello and its welcome.
    • settings

      AgentSettings settings()
      Returns:
      the operator settings reporter shared by every connection
    • registerCommands

      private void registerCommands(AgentMessageRouter router)
    • registerPower

      private void registerPower(AgentMessageRouter router)
    • registerPlayerActions

      private void registerPlayerActions(AgentMessageRouter router)
    • registerAll

      private static void registerAll(AgentMessageRouter router, List<String> types, Supplier<CloudRequestHandler> factory, SerialRequestWorker worker)
    • registerConfig

      private void registerConfig(AgentMessageRouter router)
    • registerModules

      private void registerModules(AgentMessageRouter router, AgentProtocol connection)
    • moduleOf

      private static String moduleOf(com.google.gson.JsonObject payload)
    • accepted

      private static boolean accepted(Set<String> acceptedCapabilities, String module)
    • moduleNames

      private List<String> moduleNames()
    • persistBeforeShutdown

      private void persistBeforeShutdown()
      The power handler's pre-shutdown hook: the outbox and the ids reach the disk before the server stops.
    • recordSwitchRefusal

      private void recordSwitchRefusal(CloudRequest request, String capability, String message)
      Records a request the capability gate refused as exactly one DENIED entry (FEATURES.md ultitools.remote.file-refusal-audit, as 6.3.0 did): the switch that decides the capability, the request type as action, its command or path as target when it carries one (the type otherwise), the panel as actor and the refusal sentence naming the configuration key as reason. A capability no Capability switch decides records nothing. Writes only the action log, never an operator file.
    • refusalTarget

      private static String refusalTarget(CloudRequest request)
    • recordAction

      private void recordAction(RemoteActionLog.Entry entry)
      One action-log entry; a failing log never reaches the handler, so the request still gets its reply.
    • ledger

      DedupeLedger ledger()
      Returns:
      the ledger every handler of every connection shares
    • bindings

      Returns:
      the bindings these features were built from
    • liveChannel

      private CloudChannel liveChannel()
    • gatewayClockOffsetMillis

      private long gatewayClockOffsetMillis()
    • daemon

      private static ThreadFactory daemon(String name)