Skip to content

05. Actor relocation

Reference index

This category covers the external entry points ZLinkActorManager/ZLinkActorClient provide, the entry point for joining a Spot from inside Actor code via ZLinkActorContext, and relocation policy selection. The exact signatures are owned by the Java Actor exact interface (Korean-only).


ZLinkActorManager.create

Always creates a new Actor.

ZLinkActorCreateResult created = actorManager.create("player-1", "player")
    .inMesh("play")
    .request(new SpawnPlayer("player-1"))
    .submit()
    .toCompletableFuture().get();

Options. This call carries the following modifiers.

Modifier Default Meaning
.inMesh(meshName) Optional if exactly one Mesh has Object Client/Server role The Mesh to create the Actor in. Omitting it with two or more candidates completes with INVALID_OPERATION; none completes with NOT_CONFIGURED; a nonexistent specified Mesh completes with NOT_FOUND
.request(Object) / .request(ZLinkMessage) None (empty request) The request passed at Actor factory creation time
.timeout(Duration) 5 seconds The deadline covering resolve/reservation/factory/Ready barrier altogether
.submit() terminal (pick one) Waits until creation completes
.yield() terminal (pick one) Only valid inside a SpotWide handler

Completion result. ZLinkActorCreateResult (a sealed interface) completes as one of Created (newly created) or Rejected (the factory rejected it). If a Ready incarnation of the same ActorId already exists, it completes with an ALREADY_EXISTS error rather than either alternative — Existing only exists for getOrCreate. If a Ready incarnation exists but its stable type differs, it is TYPE_MISMATCH.

When to use. Use this when a new Actor is always needed. Use getOrCreate to reuse an existing one and only create when there is none.


ZLinkActorManager.getOrCreate

Returns the Ready Actor with the same ActorId if it exists, and creates a new one otherwise.

ZLinkActorCreateResult existingOrCreated = actorManager.getOrCreate("player-1", "player")
    .inMesh("play")
    .request(new SpawnPlayer("player-1"))
    .submit()
    .toCompletableFuture().get();

Options. The same as create.inMesh(...), .request(...), .timeout(...), terminal .submit() or .yield().

Completion result. Existing returns the already-existing Actor and ignores request. Contending with a creating attempt waits for that result and joins it; a distinct operation receives Existing after Ready and does not share the earlier reply.

When to use. Use this when an idempotent "use if it exists, create if it doesn't" by ActorId is needed.


find / findSpot / destroy (manager)

Queries an existing Actor, queries the Spot it currently participates in, or terminates the exact incarnation.

Optional<ActorRef> actor = actorManager.find("player-1").toCompletableFuture().get();
Optional<SpotRef> spot = actorManager.findSpot("player-1").toCompletableFuture().get();

if (actor.isPresent()) {
    boolean destroyed = actorManager.destroy(actor.get()).toCompletableFuture().get();
}

Options. None of the three calls has modifiers — all only take the target identifier.

Completion result. find returns Optional.empty() if there is no Ready Actor. findSpot returns Optional.empty() if there is no current User Spot membership. destroy returns false if the incarnation does not exist, completes with INVALID_OPERATION if the generation differs, and UNAVAILABLE while a pre-commit seal is in progress.

When to use. Use this when you need to check current existence/membership, or explicitly terminate an Actor.


sendToActor / requestToActor (ZLinkActorClient)

Sends a one-way message, or exchanges a typed request/reply, to a single global ActorId. Used from an external client.

actorClient.sendToActor("player-1", new GrantItem("sword")).submit();

CompletionStage<Inventory> reply = actorClient
    .requestToActor("player-1", new GetInventory())
    .timeout(Duration.ofSeconds(3))
    .submit(Inventory.class);

Options. sendToActor only has .metadata(...) and terminal .submit(). requestToActor additionally has the following.

Modifier Default Meaning
.timeout(Duration) The MeshNode's request default timeout The upper bound for waiting on the reply
.submit(TReply.class) terminal (pick one) Waits until the reply arrives
.yield(TReply.class) terminal (pick one) Only valid inside a SpotWide handler

Completion result. No ActorId completes with NOT_FOUND. The remaining completion kinds follow the same common rules as the messaging-execution category.

When to use. Use sendToActor if no reply is needed, and requestToActor if one is.


joinSpot / joinEntrySpot (inside Actor code)

Joins the current Actor to a User Spot or an Entry Spot. Called via ZLinkActorContext.joinSpot(...)/joinEntrySpot(...) — unlike other entries, the only terminal here is defer(), not submit/yield.

context.joinSpot("room-42", new JoinRoomRequest("player-1"))
    .timeout(Duration.ofSeconds(5))
    .defer();

Options. This call carries the following modifiers.

Modifier Default Meaning
.timeout(Duration) 5 seconds A monotonic absolute deadline
.defer() Required terminal A synchronous call with no result. Only registers the join intent and an inactive barrier — it does not start the target lookup immediately

Completion result. defer() itself has no return value. If the current handler ends normally, the barrier activates and executes the Join; if the handler fails, the barrier is discarded. The actual result (accepted/rejected/failed) is delivered asynchronously via the ZLinkActor.onJoinCompleted(...) callback carrying the same 128-bit ZLinkActorJoinOperationId — one of Accepted/Rejected/Failed (the sealed interface ZLinkActorJoinCompletion).

When to use. Use this to move an Actor to a different Spot, or return it to an Entry Spot. Calling it from an Actor in an Entry Spot or a PER_ACTOR User Spot completes with INVALID_OPERATION.


Relocation policy selection (at Actor factory registration time)

Choose exactly one, in the configure callback of addActorFactory(actorType, actorClass, factoryClass, configure) (topology-discovery category).

Policy Behavior on cross-node move When to use
disableRelocation() Rejects the move itself before Capture When this Actor must never be moved to another node
recreateOnRelocation() Recreates the same logical identity via the target factory. Does not restore application state When an Actor may be recreated without state
preserveStateWith(adapterClass) Moves an opaque byte[] via ZLinkActorRelocationAdapter<TActor>.capture/restore When state must be preserved across the move

Completion result. preserveStateWith's capture(...) result is capped at 64 MiB. Capture/Restore can each be called multiple times within the same relocation, so both callbacks must be retry-safe — they must not depend on an external side effect executing exactly once. If adapterClass does not implement ZLinkActorRelocationAdapter<TActor> for that Actor type, it is a startup configuration error.

When to use. Which of the three policies you choose determines this Actor type's entire relocation behavior — it is decided once, at factory registration time, and cannot be changed per call afterward.


See the Java Actor exact interface (Korean-only) for the full rationale.