Skip to content
UCIK Docs
Esc
↑↓navigate↵open⌘Jpreview
On this page

Recover from disconnects

Reconnect players or continue a match after its host leaves.

Separate a dropped connection from an intentional exit. UCIK remembers the last lobby, session, and validated address across restarts; a saved destination may no longer exist.

Rejoin the last match

Rejoin the last match
Rejoin the last match
Preparing your graph…

Loading the interactive viewer.

Clear a match after leaving
Clear a match after leaving
Preparing your graph…

Loading the interactive viewer.

Game module dependencies: UCIKCore, BetideCore.

#include "Engine/GameInstance.h"
#include "Travel/UCIKTravelSubsystem.h"
#include "UCIKCore.h"

void ForgetMatchAfterLeaving(UGameInstance &GameInstance)
{
    if (auto *Service = GameInstance.GetSubsystem<UUCIKTravelSubsystem>())
        Service->ClearLastMatch();
}

void ReconnectToLastMatch(UGameInstance &GameInstance)
{
    auto *Service = GameInstance.GetSubsystem<UUCIKTravelSubsystem>();
    if (!Service)
        return;

    if (!Service->HasLastMatch())
        return;
    Service->RejoinLastMatch(
        true, FBetideCallback<FUCIK_ConnectInfo>::CreateWeakLambda(
                  &GameInstance, [](const TBetideResult<FUCIK_ConnectInfo> &Result) {
                      if (!Result.bOk)
                      {
                          UE_LOG(LogUCIK, Warning, TEXT("%s"), *Result.Error.GetLogString());
                          return;
                      }
                      UE_LOG(LogUCIK, Log, TEXT("Rejoin destination resolved"));
                  }));
}

Rejoin tries the last lobby, then session, then address. If it fails, return to a usable menu. Call Clear Last Crossplay Match after the player deliberately leaves; it clears memory and disk.

Host migration

Choose Host Migration Mode in Betide → Crossplay:

Mode Behavior
Off No recovery; Unreal returns to the default map.
Manual Your game reacts to host-loss/election events and controls hosting/reconnection.
Seamless UCIK elects a host, reconnects members, and restores marked actor state.

Manual host handover

Use a GameInstance Blueprint and set migration mode to Manual. Choose either the explicit election flow or Run Crossplay Host Migration; bind before host loss and stop watching when leaving the match. Reconnect To Migrated Crossplay Host succeeds when travel is issued; On Migration Completed confirms recovery.

Hand over ownership
Hand over ownership
Preparing your graph…

Loading the interactive viewer.

Handle host election explicitly
Handle host election explicitly
Preparing your graph…

Loading the interactive viewer.

Use the default migration flow
Use the default migration flow
Preparing your graph…

Loading the interactive viewer.

Game module dependencies: UCIKCore, BetideCore.

Hand over ownership
#include "Engine/GameInstance.h"
#include "HostMigration/UCIKHostMigrationSubsystem.h"
#include "UCIKCore.h"

void HandOverToMember(UGameInstance &GameInstance, const FUCIK_PlayerHandle &PreferredMember)
{
    auto *Migration = GameInstance.GetSubsystem<UUCIKHostMigrationSubsystem>();
    if (!Migration)
        return;
    Migration->HandOverHost(
        PreferredMember,
        FBetideCallback<FUCIK_PlayerHandle>::CreateWeakLambda(
            &GameInstance, [](const TBetideResult<FUCIK_PlayerHandle> &Result) {
                if (!Result.bOk)
                {
                    UE_LOG(LogUCIK, Warning, TEXT("%s"), *Result.Error.GetLogString());
                    return;
                }
                UE_LOG(LogUCIK, Log, TEXT("New owner: %s"), *Result.GetValue().ToString());
            }));
}
Handle host election

Store FMigrationBindings on your GameInstance. Choose the default flow or the explicit host/wait/reconnect sequence with bUseDefaultFlow.

#include "Engine/GameInstance.h"
#include "HostMigration/UCIKHostMigrationSubsystem.h"
#include "UCIKCore.h"

struct FMigrationBindings
{
    FDelegateHandle Elected;
    FDelegateHandle Completed;
    FDelegateHandle Failed;
};

void StopWatchingHostElection(UGameInstance &GameInstance, FMigrationBindings &Bindings)
{
    if (auto *Migration = GameInstance.GetSubsystem<UUCIKHostMigrationSubsystem>())
    {
        Migration->OnHostElectedNative.Remove(Bindings.Elected);
        Migration->OnMigrationCompletedNative.Remove(Bindings.Completed);
        Migration->OnMigrationFailedNative.Remove(Bindings.Failed);
    }
    Bindings = {};
}

void WatchHostElection(UGameInstance &GameInstance, FMigrationBindings &Bindings,
                       bool bUseDefaultFlow)
{
    StopWatchingHostElection(GameInstance, Bindings);
    auto *Migration = GameInstance.GetSubsystem<UUCIKHostMigrationSubsystem>();
    if (!Migration)
        return;
    const TWeakObjectPtr<UUCIKHostMigrationSubsystem> WeakMigration(Migration);
    Bindings.Completed = Migration->OnMigrationCompletedNative.AddWeakLambda(
        &GameInstance, [WeakMigration](const FUCIK_MigrationContext &) {
            if (WeakMigration.IsValid())
                UE_LOG(LogUCIK, Log, TEXT("Migration phase: %d"),
                       static_cast<int32>(WeakMigration->GetPhase()));
        });
    Bindings.Failed = Migration->OnMigrationFailedNative.AddWeakLambda(
        &GameInstance, [](const FUCIK_MigrationContext &, const FBetideError &Error) {
            UE_LOG(LogUCIK, Warning, TEXT("%s"), *Error.GetLogString());
        });
    Bindings.Elected = Migration->OnHostElectedNative.AddWeakLambda(
        &GameInstance, [WeakMigration, bUseDefaultFlow](const FUCIK_MigrationContext &Context) {
            if (!WeakMigration.IsValid())
                return;
            if (bUseDefaultFlow)
            {
                WeakMigration->RunMigration();
                return;
            }
            if (Context.bLocalIsElected)
            {
                WeakMigration->BecomeHost(
                    Context.MapName,
                    FBetideCallback<FUCIK_ConnectInfo>::CreateWeakLambda(
                        WeakMigration.Get(), [](const TBetideResult<FUCIK_ConnectInfo> &Result) {
                            if (!Result.bOk)
                                UE_LOG(LogUCIK, Warning, TEXT("%s"), *Result.Error.GetLogString());
                            else
                                UE_LOG(LogUCIK, Log, TEXT("New host ready"));
                        }));
                return;
            }
            WeakMigration->WaitForHost(
                30.0f,
                FBetideCallback<FString>::CreateWeakLambda(
                    WeakMigration.Get(), [WeakMigration](const TBetideResult<FString> &Result) {
                        if (!WeakMigration.IsValid())
                            return;
                        if (!Result.bOk)
                        {
                            UE_LOG(LogUCIK, Warning, TEXT("%s"), *Result.Error.GetLogString());
                            return;
                        }
                        WeakMigration->ReconnectToHost(FBetideCallback<bool>::CreateWeakLambda(
                            WeakMigration.Get(), [](const TBetideResult<bool> &Reconnect) {
                                if (!Reconnect.bOk)
                                    UE_LOG(LogUCIK, Warning, TEXT("%s"),
                                           *Reconnect.Error.GetLogString());
                                else
                                    UE_LOG(LogUCIK, Log, TEXT("Reconnect travel requested"));
                            }));
                    }));
        });
}

For Seamless, add UCIK Migration State Component to actors that must survive. Mark custom state as SaveGame. The component restores enabled transforms, velocity, SaveGame properties, and references between marked actors; it is not a copy of every runtime object.

Migration limits and testing

Migration uses a lobby/session this process actually joined, not just a record loaded from disk. Provider-native owner election is used where available; otherwise UCIK ranks peers. Direct-IP peers need reachable addresses registered through Register Crossplay Migration Peer.

Only the elected host publishes the new ready state and migration epoch. Do not rewrite UCIK_ host/connection attributes from members. Test both a graceful handover and a killed host process, including restored pawns, timers, inventory, and your failure screen.

Was this page helpful?