콘텐츠로 이동

05. Actor relocation

레퍼런스 목차

이 category는 actor_manager_t·actor_client_t가 제공하는 외부 진입점과, Actor 코드 안에서 actor_context_t로 Spot에 참여하는 진입점, 그리고 relocation 정책 선택을 다룬다. 정확한 signature는 Actor exact interface가 소유한다.


actor_manager_t::create

새 Actor를 항상 새로 만든다.

zlink::framework::actor_create_result_t created = co_await actor_manager
  .create(zlink::framework::actor_id_t{"player-1"}, "player")
  .in_mesh("play")
  .creation_request(spawn_player_t{"player-1"})
  .submit();

옵션. 이 호출에는 다음 modifier가 붙는다.

Modifier 기본값 의미
.in_mesh(mesh_name) Object Client·Server role의 Mesh가 하나면 생략 가능 Actor를 생성할 Mesh. 후보가 둘 이상인데 생략하면 invalid_operation, 없으면 not_configured, 지정한 Mesh가 없으면 not_found
.creation_request(message_t) / .creation_request<TCreation>(TCreation) 없음(빈 요청) Actor factory 생성 시점에 전달할 요청
.timeout(milliseconds) 5초 resolve·reservation·factory·Ready barrier 전체의 deadline
.submit() terminal(택 1) 생성 완료까지 기다린다
.yield() terminal(택 1) spot_wide handler 안에서만 유효

완료 결과. actor_create_result_t(std::variant)는 actor_create_created_t(새로 생성) 또는 actor_create_rejected_t(factory가 거부) 중 하나로 완료한다. 같은 ActorId의 Ready incarnation이 이미 있으면 두 대안이 아니라 already_exists 오류로 완료한다 — actor_create_existing_tget_or_create에만 있다. Ready incarnation이 있는데 stable type이 다르면 type_mismatch다.

선택 기준. 항상 새 Actor가 필요할 때 쓴다. 있으면 재사용하고 없을 때만 만들려면 get_or_create를 쓴다.


actor_manager_t::get_or_create

같은 ActorId의 Ready Actor가 있으면 그것을 반환하고, 없으면 새로 만든다.

zlink::framework::actor_create_result_t existing_or_created = co_await actor_manager
  .get_or_create(zlink::framework::actor_id_t{"player-1"}, "player")
  .in_mesh("play")
  .creation_request(spawn_player_t{"player-1"})
  .submit();

옵션. create와 동일하다 — .in_mesh(...), .creation_request(...), .timeout(...), terminal .submit() 또는 .yield().

완료 결과. actor_create_existing_t이면 이미 있던 Actor를 반환하고 creation_request는 무시한다. Creating attempt와 경합하면 그 결과를 기다렸다가 합류하며, 서로 다른 operation은 Ready 뒤 actor_create_existing_t를 받고 이전 reply를 공유하지 않는다.

선택 기준. ActorId로 멱등하게 "있으면 쓰고 없으면 만들기"가 필요할 때 쓴다.


find / find_spot / destroy (manager)

기존 Actor를 조회하거나, 참여 중인 Spot을 조회하거나, 정확한 incarnation을 종료한다.

std::optional<zlink::framework::actor_ref_t> actor =
  co_await actor_manager.find(zlink::framework::actor_id_t{"player-1"});
std::optional<zlink::framework::spot_ref_t> spot =
  co_await actor_manager.find_spot(zlink::framework::actor_id_t{"player-1"});

if (actor) {
    bool destroyed = co_await actor_manager.destroy(*actor);
}

옵션. 세 호출 모두 modifier가 없다 — 대상 식별자만 받는다.

완료 결과. find는 Ready Actor가 없으면 std::nullopt을 반환한다. find_spot은 current User Spot membership이 없으면 std::nullopt을 반환한다. destroy는 해당 incarnation이 없으면 false, generation이 다르면 invalid_operation, pre-commit seal 중이면 unavailable이다.

선택 기준. 지금 시점의 존재·소속 확인이나 명시적 종료가 필요할 때 쓴다.


send / request (actor_client_t)

Global ActorId 하나로 one-way message를 보내거나 typed request/reply를 주고받는다. 외부 client에서 쓴다.

co_await actor_client
  .send(zlink::framework::actor_id_t{"player-1"}, grant_item_t{"sword"})
  .submit();

inventory_t reply = co_await actor_client
  .request(zlink::framework::actor_id_t{"player-1"}, get_inventory_t{})
  .timeout(std::chrono::seconds{3})
  .submit<inventory_t>();

옵션. send.metadata(...)와 terminal .submit()만 있다. request는 다음이 더 있다.

Modifier 기본값 의미
.timeout(milliseconds) MeshNode의 request 기본 timeout reply를 기다리는 상한
.submit<TReply>() terminal(택 1) reply 수신까지 기다린다
.yield<TReply>() terminal(택 1) spot_wide handler 안에서만 유효
.submit_message() / .yield_message() terminal(택 1) typed reply 대신 raw message_t로 받는다

완료 결과. ActorId가 없으면 not_found. 나머지 완료 kind는 messaging-execution category의 공통 규칙과 같다.

선택 기준. Reply가 필요 없으면 send, 필요하면 request를 쓴다.


join_spot / join_entry_spot (Actor 코드 안)

현재 Actor를 User Spot 또는 Entry Spot에 참여시킨다. actor_context_t::join_spot(...)/ join_entry_spot(...)로 호출하며, 다른 항목과 달리 terminal이 submit/yield가 아니라 defer() 하나뿐이다.

context_
  .join_spot("room-42", join_room_request_t{"player-1"})
  .timeout(std::chrono::seconds{5})
  .defer();

옵션. 이 호출에는 다음 modifier가 붙는다.

Modifier 기본값 의미
.timeout(milliseconds) 5초 monotonic absolute deadline
.defer() 필수 terminal 결과 없는 동기 호출. Join intent와 비활성 barrier만 등록하고 target 조회를 바로 시작하지 않는다

완료 결과. defer() 자체는 반환값이 없다. 현재 handler가 정상적으로 끝나면 barrier가 활성화되어 Join을 실행하고, handler가 실패하면 barrier를 폐기한다. 실제 결과(수락·거부·실패)는 같은 128-bit operation ID를 담은 actor_t::on_join_completed(...) callback으로 비동기 전달된다 — actor_join_accepted_t/actor_join_rejected_t/actor_join_failed_t std::variant 중 하나다.

선택 기준. Actor를 다른 Spot으로 옮기거나 Entry Spot으로 되돌릴 때 쓴다. Entry Spot과 per_actor User Spot의 Actor에서 호출하면 invalid_operation으로 완료한다.


Relocation 정책 선택 (Actor factory 등록 시점)

add_actor_factory<TActor, TFactory>(...)(topology-discovery category)의 configure callback에서 정확히 하나를 선택한다.

정책 cross-node 이동 시 동작 선택 기준
disable_relocation() Capture 전에 이동 자체를 거부한다 이 Actor가 다른 node로 옮겨지면 안 될 때
recreate_on_relocation() Target factory로 같은 logical identity를 다시 만든다. Application state는 복구하지 않는다 State 없이 다시 만들어도 되는 Actor일 때
preserve_state_with<TAdapter>() actor_relocation_adapter_t<TActor>::capture/restore로 opaque byte vector를 옮긴다 State를 유지한 채 옮겨야 할 때

완료 결과. preserve_state_withcapture(...) 결과는 최대 64 MiB다. Capture·Restore는 같은 relocation에서 여러 번 호출될 수 있으므로 두 callback 모두 retry-safe해야 한다 — 외부 side effect의 exactly-once 실행에 의존하면 안 된다.

선택 기준. 세 정책 중 무엇을 고르느냐가 이 Actor 타입의 relocation 동작 전체를 결정한다 — factory 등록 시점에 한 번만 정하고 나중에 호출별로 바꿀 수 없다.


전체 근거는 Actor exact interface를 참고한다.