Interface SpawneryApi
The methods that return a CompletionStage ask the operator, and
can fail; every other method is answered locally. The operator keeps a
mirror current in each agent, so a read -- self() through
player(java.util.UUID) -- never crosses a network, blocks, or fails: there is no
timeout and no exception to handle. What it returns is the last thing the
operator said, which during a reconnect may be a few seconds old and is
never wrong about a moment that happened. A proxy is told of a server's
lifecycle (created, placed, unschedulable, gone) usually within a second;
counts and announcements arrive with the operator's 30 second resync, and a
backend's mirror is refreshed by that resync alone. Each stage-returning method says
how it can fail; holdReadiness(java.lang.String) is local and throws on a proxy.
Consume this interface; do not implement it. New methods break
implementors but no caller. The agent supplies the implementation; a plugin
obtains it from Spawnery.api().
Everything is scoped to this pod's own namespace. No method reaches another network: the agent's credentials are a pod-bound ServiceAccount token, so there is nothing to widen.
-
Method Summary
Modifier and TypeMethodDescriptionacceptJoins(boolean accept) Opens or closes this server's own door.Publishes what this server is doing, for every other server to read.booleanarrivedByTransfer(UUID player) Whether this player's connection to this proxy is a transfer from another proxy of the network, carrying a cookie this agent verified.Deprecated.Asks the operator to move a player.deleteServer(String group, String key) Deletes one private server for good: stops it if it is running, asstopServer(java.lang.String)does, and deletes its world.endRound()Says this server's round is over.events()Where to hear about things happening in the cloud.One group by name, empty if this network has none.groups()Every group in this network, in no particular order.holdReadiness(String reason) Holds this server back from readiness until the returned hold is closed.booleanleavingByTransfer(UUID player) Whether this proxy has sent this player to another proxy of the network, because this proxy is leaving.default CompletionStage<List<RestorePoint>> listRestorePoints(String group, String key) The points ofrestorePoints(java.lang.String, java.lang.String), without whether the world is settling.voidplayableSlots(int slots) Sets how many of this server's seats count as capacity, from now until changed: what its group's spare slots, free slots and a connect to the group measure.One player by UUID, empty if they are not on this network.players()Every player on this network, whichever backend they are on.proxies()Every proxy in this network, in no particular order.One proxy by name, empty if this network has none.resetScale(String group) Ends every pin and every boost on a group and reports how many there were.restorePoints(String group, String key) Lists the generations of a private server's world thatrestoreWorld(java.lang.String, java.lang.String, long)can go back to, newest first.restoreWorld(String group, String key, long generation) Makes an older generation of a private server's world its current world, as a new generation.Asks that one server stop taking joins and empty out.Holds an ephemeral group at exactly this many servers for a while.self()What this process is.One server by name, empty if this network has none.servers()Every server in this network, in no particular order.startServer(String group, String key) Asks for the private server that carries this key.startServer(String group, String key, StartOptions options) Asks for the private server that carries this key, with resources of your choosing for it.status()How this network is doing: CPU and memory of its pods against what they asked for, per group, and each server's tick rate.status()for one server group, proxy group, server or proxy, looked up in that order.stopBoosts(String group) Deprecated.useresetScale(java.lang.String); this keeps working.stopServer(String server) Stops one private server and leaves its world where it is.Takes a server's retirement back.
-
Method Details
-
self
Self self()What this process is. Useinstanceofto learn which side. -
groups
Every group in this network, in no particular order. -
group
One group by name, empty if this network has none. -
servers
List<ServerInfo> servers()Every server in this network, in no particular order. -
server
One server by name, empty if this network has none. -
proxies
Every proxy in this network, in no particular order. -
proxy
One proxy by name, empty if this network has none. -
players
List<CloudPlayer> players()Every player on this network, whichever backend they are on. -
player
One player by UUID, empty if they are not on this network. -
arrivedByTransfer
Whether this player's connection to this proxy is a transfer from another proxy of the network, carrying a cookie this agent verified.For a plugin that announces joins: a player who arrived this way was already online a moment ago, and to their friends nothing happened. A transfer intent alone is not enough for that, because any client can claim one; this is true only when the signed cookie checked out.
Known from
LoginEventon, for a listener that runs after the agent's own (the agent asks for the cookie there and holds the login until it has the answer). Stays true until the player disconnects from this proxy. Always false on a server, and on a proxy whose group does not havespec.update.transfer.Agents older than 0.19.0 do not have this method.
-
leavingByTransfer
Whether this proxy has sent this player to another proxy of the network, because this proxy is leaving.For a plugin that cleans up after a player: the disconnect that follows is not a quit. True from the moment the transfer is sent until every listener of the player's
DisconnectEventon this proxy has run. It says the player was sent, not that they arrived; a client that fails to reconnect is gone like any other, so a plugin that keeps state for them should still let it lapse if they do not turn up elsewhere. Always false on a server.Agents older than 0.19.0 do not have this method.
-
connect
Asks the operator to move a player.Asynchronous on both platforms, although a proxy could answer locally, so that the same code compiles on either side.
The operator does not know permissions. A player sent to a group whose join permission refuses them is still answered
ordered, and is then refused by the proxy or the server.The stage fails rather than returning a result when the operator refuses or cannot answer. A request in flight while the stream is renewed is answered on the new stream; one lost on the wire fails at its deadline and is not retried, because only you know whether moving that player twice is safe.
A player who logged out between your call and the operator reading it is an ordinary failure and not a bug. So is a target that is not routable yet.
-
retire
Asks that one server stop taking joins and empty out.Retiring is not stopping. The server takes no further joins, the players on it finish in their own time, and nobody is moved or disconnected. Emptied, it is taken down by the same rules that take down any server its group no longer needs.
The stage completes with no value. It fails when the operator refuses, and asking for a server that is already retiring is a failure, so a caller can tell "you retired it" from "somebody had already asked".
-
unretire
Takes a server's retirement back. It takes joins again, and nothing automatic removes it any more; it stays until it ends by itself.Fails for a server that is already stopping or is not retiring, with the operator's reason.
-
status
CompletionStage<NetworkStatus> status()How this network is doing: CPU and memory of its pods against what they asked for, per group, and each server's tick rate. Asks the operator; never anything outside this network's namespace. -
status
status()for one server group, proxy group, server or proxy, looked up in that order. Fails when nothing on this network has the name. -
boost
Deprecated.usescale(java.lang.String, int, java.time.Duration), which sets the size rather than adding to it; this keeps working.Asks for extra capacity on a group, for a while.It adds to what the group tries for and never to what it may reach. The group's own
maxReplicasstill binds. A request for more than the ceiling leaves is refused rather than trimmed.It expires. Pass
nullfor the operator's default, which is an hour. The operator also bounds how long you may ask for; a lasting need belongs in the group's own definition.Boosts add rather than replace: two calls make two boosts.
The stage fails when the operator refuses: a group it does not have, a group sized by a fixed replica count rather than by scaling, more than the ceiling leaves, a negative duration, or longer than it allows. Each says which.
- Parameters:
forHowLong- how long the boost should run, ornullfor the operator's default.
-
stopBoosts
Deprecated.useresetScale(java.lang.String); this keeps working.Ends every boost on a group and reports how many there were.Every one, not the newest. Zero is an ordinary answer -- the group had no boosts -- and not a failure.
-
scale
Holds an ephemeral group at exactly this many servers for a while.Unlike
boost(java.lang.String, int, java.time.Duration), which adds to the floor, a pin is both the floor and the ceiling. Empty servers above the number are deleted and occupied ones are retired: no new joins, and the players stay until they leave. 0 is allowed, but a pin below the group'sminReplicasis refused unless the caller is a proxy, and a pin above itsmaxReplicasis always refused. Of several pins on one group the newest wins, and boosts do not count while one holds.The stage fails when the operator refuses: a group it does not have, a group that is not ephemeral, more than
maxReplicas, fewer thanminReplicasfrom a caller that is not a proxy, a negative duration, or longer than seven days. Each says which.- Parameters:
forHowLong- how long the pin holds, ornullfor the operator's default of an hour.
-
resetScale
Ends every pin and every boost on a group and reports how many there were. Zero is an ordinary answer. -
startServer
Asks for the private server that carries this key.The key names a world and not a server: the operator composes the server's name from the group and the key, and the same key later finds the same world. Only a group of type
OnDemandhas members to ask for; anything else fails.The stage completing means the server was asked for, not that it can take players. It starts out as any server does; watch
ServerInfo.phase()or the events forServerPhase.READYbefore sending anybody there.Asking for one that is already running succeeds with
StartedServer.alreadyRunning()set, so a player pressing a button twice needs no lock on your side. That flag says the server exists and nothing about its being ready.How it fails. When the operator answers no, the stage fails with an
IllegalStateExceptionwhose message is<REASON>: <message>-- the operator's reason, then its own sentence.handle,exceptionallyandwhenCompletecalled directly on this stage receive that exception itself. A stage derived from it withthenApplyorthenComposedelivers it wrapped in aCompletionException, andget()wraps it in anExecutionException; unwrap withgetCause()only when what you caught is one of those wrappers. The reasons:REFUSEDfor a key that cannot be part of a name, for a name longer than 63 characters once the group and the key are composed, for a group that is not on-demand, for a name that another group's server already has, and for a group that is genuinely at itsspec.maxInstances.NOT_FOUNDfor a group this network does not have.UNAVAILABLEfor a member of that key that is still stopping, and for a group that is at itsmaxInstancesonly because one of its members is stopping. Neither is a refusal: the same request succeeds once that member is gone, so ask again.
One failure is not the operator's answer and carries no reason: the stage fails with a
TimeoutExceptionwhen no answer arrives within ten seconds. That includes a request lost while the stream was renewed; it is not retried, because only you know whether asking twice is safe. For a start it is: one that was in fact carried out is answeredalreadyRunningthe second time. -
startServer
Asks for the private server that carries this key, with resources of your choosing for it.Like
startServer(String, String), which uses the group's values. Whatoptionssets replaces the group's value for this member. The operator answersREFUSEDfor values the group does not allow, and for any value at all when the group has nomemberResources.A member that is already running keeps the values it started with: the stage succeeds with
StartedServer.alreadyRunning()set andStartedServer.resources()says what it runs with; compare only the fields you set.An operator that predates per-start resources would start the member with the group's values and say nothing. The stage therefore fails with an
IllegalStateExceptionwhen options were set and the operator did not acknowledge them; the member keeps running with the group's values, started that way or already running.Agents older than 0.33.0 do not have this method.
-
stopServer
Stops one private server and leaves its world where it is.Not
retire(java.lang.String): retiring closes a server's door and lets it empty in its own time, while this says its owner is done with it — the players on it are moved through the proxies and the pod goes. The world is on a claim nothing deletes, so the nextstartServer(java.lang.String, java.lang.String)of the same key finds it.The stage completes with no value. Stopping a server that is already on its way out succeeds too, so a second press of the same button is not an error.
It fails with
REFUSEDfor a server that is not a member of an on-demand group, which is what keeps a wrong name from taking down a lobby, and withNOT_FOUNDfor a name this network does not have. The failure has the shapestartServer(java.lang.String, java.lang.String)describes, the timeout and the renewed stream included. Asking again after either is safe, but a stop that was in fact carried out may answerNOT_FOUNDthe second time, once the server is gone. -
deleteServer
Deletes one private server for good: stops it if it is running, asstopServer(java.lang.String)does, and deletes its world. There is no undo.Group and key as
startServer(java.lang.String, java.lang.String)takes them, because a stopped server's world is all that is left of it. The stage completes when the deletion is under way; astartServer(java.lang.String, java.lang.String)of the same key answersUNAVAILABLEuntil the world is gone, and then starts an empty one.It fails with
NOT_FOUNDfor a group this network does not have or a key with neither a server nor a world, and withREFUSEDfor a group that is not on-demand, a key no name can be built from, or a world this operator did not make. The failure has the shapestartServer(java.lang.String, java.lang.String)describes, the timeout and the renewed stream included; asking again is safe, and answersNOT_FOUNDonce the world is gone. -
restorePoints
Lists the generations of a private server's world thatrestoreWorld(java.lang.String, java.lang.String, long)can go back to, newest first. The first is the world as it stands and saysRestorePoint.current().RestorePoints.settling()says whether the world may still change. It is true while the member runs, while it is being stopped, and after a stop until its node has uploaded the final state of the world, which can take some seconds more; also while a restore is being written. Until it turns false, the list can miss the newest state and arestoreWorld(java.lang.String, java.lang.String, long)fails withUNAVAILABLEorREFUSED.Only for a group whose worlds live in the object store (
storage.backend: ObjectStore). Which older generations stay is the group'sstorage.retention; without one the list holds the current generation alone. A member that runs adds a generation about every five minutes, and one more when it stops.It answers while the member runs and while its world is being deleted; it only reads. The list is what the bucket held at that moment: an upload right after it can drop a listed generation, and a restore of that one then fails with
NOT_FOUND.It fails with
NOT_FOUNDfor a group this network does not have or a key without a world; withREFUSEDfor a group that is not on-demand or keeps its worlds on claims, a key no name can be built from, and when the operator runs without world sync; and withUNAVAILABLEwhen the operator could not read the bucket just now. The failure has the shapestartServer(java.lang.String, java.lang.String)describes, except that the operator may take up to a minute to answer, so the stage times out after 75 seconds rather than ten. Asking again after a timeout is safe. -
listRestorePoints
The points ofrestorePoints(java.lang.String, java.lang.String), without whether the world is settling. -
restoreWorld
Makes an older generation of a private server's world its current world, as a new generation. The generation that was current stays a restore point, so a restore can itself be undone before the member runs again; after its next upload, only while the group's retention keeps it. The nextstartServer(java.lang.String, java.lang.String)of the key starts on the restored world.Who may restore, and what a restore means for the players, is your plugin's decision; the operator asks nobody.
Only while the member is stopped. It fails with:
NOT_FOUNDfor a group this network does not have, a key without a world, or a generation the world does not keep.REFUSEDfor a group that is not on-demand or keeps its worlds on claims, an operator that runs without world sync, a member that is running, a world that is being deleted, and the current generation.UNAVAILABLEwhile the member is still stopping, while its world is being written, and when the operator could not reach the bucket just now. Right after a stop it is the final upload of the world: ask again a few seconds later.
startServer(java.lang.String, java.lang.String)describes, the renewed stream included. The operator answers once the restore is written, which can take up to a minute. The stage therefore times out after 75 seconds instead of ten, and after a timeout you cannot tell whether the restore happened. Asking again after either is safe: if the restore was carried out and the member has not run since, the answer is the generation it made, even once the world no longer keeps the generation it restored. -
acceptJoins
Opens or closes this server's own door.Closing is not
retire(java.lang.String). Retiring says the server is finished: it stops taking joins, empties out, and is taken down once it is empty. Closing only stops the joins, for as long as you like, and can be taken back.Nobody is moved. The players already here go on playing until they leave on their own. What changes is only whether the proxies send anybody new.
The phase does not change, and neither does the routing table. A closed server is still
ServerPhase.READY— the phase is the operator's account of a server's lifecycle, and shutting a door is not a lifecycle event. The server also stays in the proxies' table, so a spectator sent there by name still arrives. What changes is who is sent there unasked: the closed server's empty seats stop counting as capacity, so the next player looking for a game is placed somewhere else. To leave the table as well, say the round is over —endRound().Your group notices. A closed server's empty seats stop counting as the group's free capacity, so a group sized by spare slots builds a replacement rather than sitting at its floor while every server in it has shut its door.
The stage fails when the operator refuses — most plainly on a proxy, which is not in anybody's routing table but is the routing table.
Like
announce(java.lang.String, java.util.Map<java.lang.String, java.lang.String>), it survives a reconnection without being called again: the agent restates the last door state on every new session, because the operator's default for a session it has never seen is open.- Parameters:
accept-falsecloses the door,trueopens it.
-
endRound
CompletionStage<Void> endRound()Says this server's round is over.Two things follow, and only the operator can do either: the server leaves the proxies' routing table, so nobody new arrives at a round that has finished, and the pod stopping after this reads as an ending rather than a fault — its group replaces it without counting a failure.
It does not stop the server. Call it when the round is over and let the process end as it always did.
The stage fails when the operator refuses — most plainly on a proxy, which is not in anybody's routing table but is the routing table.
Like
acceptJoins(boolean), it survives a reconnection without being called again: the agent restates it on every new session, because the operator's default for a session it has never seen is that the round has not ended. It is restated together with the door, never apart — a round that ended closed the door too, and the two are never told apart on the wire. -
playableSlots
void playableSlots(int slots) Sets how many of this server's seats count as capacity, from now until changed: what its group's spare slots, free slots and a connect to the group measure. Players beyond it are still admitted up to the server's limit, and make the server full rather than overfull -- unless the group sets enforcePlayableSlots, in which case it is also the door.It asks the operator nothing. The next periodic report carries it, and every report after that, so a new session restates it without a second call.
Servers only; a proxy throws
UnsupportedOperationException.- Parameters:
slots- the playable seats; 0 hands the decision back to the group'sspec.playableSlots. A figure above the server's slots counts as its slots.- Throws:
IllegalArgumentException- ifslotsis negative
-
holdReadiness
Holds this server back from readiness until the returned hold is closed.For a plugin whose initialisation continues after the server has finished enabling -- a mapping table loaded on its own executor, a database opened in the background. The agent reports readiness when the last hold is released and the server has finished enabling, whichever comes second, so a server that is not finished stays
Startingrather than becomingReadywith nobody able to play on it.It cannot lower a readiness already reported. Readiness is a one-way latch; a hold taken after the agent has reported does nothing but log. To stop new players reaching a server that is already ready, use
acceptJoins(boolean), which is what that method is for.A hold that is never released pins the server in
Startinguntil the operator's startup deadline fails it. That is the intended outcome -- a plugin that never finishes starting is a broken server -- andreasonis what names it in the log.Servers only. A proxy has no readiness of this kind and this throws
UnsupportedOperationExceptionthere.- Parameters:
reason- what is being waited for, for the log. Required.
-
announce
Publishes what this server is doing, for every other server to read.The cloud carries this and never reads it. No operator decision looks at it. It reaches the other agents in this network as
ServerInfo.state()andServerInfo.attributes()and goes no further.It is not
ServerInfo.phase(). The phase is the operator's account of a server's lifecycle and no plugin can write it. This is the server's own account of what happens while it isREADY.Each call replaces the last one whole. Attributes are not merged: a call with one attribute leaves this server with one, whatever the call before it said. Publish the whole description each time, which is also the only way an attribute can ever be taken back.
The stage fails when the operator refuses -- a state or an attribute longer than it carries, more attributes than it carries, or a call from a proxy, which has no per-instance record in the network's picture for an announcement to appear in. Each refusal says which.
It survives a reconnection without being called again: the agent holds the last description it published and re-publishes it on every new session.
Repeating the description the operator already holds, or is about to, sends nothing; the stage follows the request already sent. The operator limits how many requests a pod may make, and a repeat that sends nothing leaves that budget to starts, stops and connects.
- Parameters:
state- what this server is doing, in a word or a short phrase. Empty clears it.attributes- anything else worth publishing. Empty clears them.
-
events
EventBus events()Where to hear about things happening in the cloud.The same object every time, so a plugin may hold it. See
EventBusfor what it does and does not promise — most of all that it is a feed and not a ledger.
-
scale(java.lang.String, int, java.time.Duration), which sets the size rather than adding to it; this keeps working.