# 断线通知与显式重连案例

本案例只处理 Game data plane 的连接生命周期。Multiplayer 不会在断线后
自动重建 KCP/TCP，也不会自动重启 Mirror `NetworkManager`。上层收到
`ConnectionInterrupted` 后决定是否重连，调用显式重连 API；成功后收到
`ConnectionRecovered`，再由上层决定是否重建 Mirror peer 和同步游戏状态。

## 事件订阅

`ConnectionInterrupted` 只表示一个已经进入 `Ready` 的 reliable 通道被确认
中断。它的 payload 是 `IGPMultiplayerConnectionChangedEvent`，其中
`PreviousState == Ready`、`State == Failed`，`Reason` 是断开或故障原因。

`ConnectionRecovered` 只表示一次显式重连已经完成新的 descriptor 请求和
可靠握手。它不会在初次连接时触发；payload 的
`State == Ready`、`IsReady == true`，`Reason == "explicit-reconnect"`。

```csharp
using IGP.Multiplayer;
using UnityEngine;

public sealed class ConnectionLifecycle : MonoBehaviour
{
    [SerializeField] private IGPMultiplayerRuntime runtime = null!;

    private void OnEnable()
    {
        runtime.ConnectionInterrupted += OnConnectionInterrupted;
        runtime.ConnectionRecovered += OnConnectionRecovered;
    }

    private void OnDisable()
    {
        runtime.ConnectionInterrupted -= OnConnectionInterrupted;
        runtime.ConnectionRecovered -= OnConnectionRecovered;
    }

    private void OnConnectionInterrupted(IGPMultiplayerConnectionChangedEvent change)
    {
        // 停止本地输入和新的业务发送，显示“连接已断开”状态。
        Debug.Log($"Game data plane interrupted: {change.Reason}");
    }

    private void OnConnectionRecovered(IGPMultiplayerConnectionChangedEvent change)
    {
        // 这里只代表 reliable data plane Ready；不要假设 Mirror peer 已恢复。
        Debug.Log("Game data plane reconnected; application resync is required.");
    }
}
```

如果需要观察完整状态序列，同时订阅 `ConnectionChanged`。显式重连期间通常
会依次看到 `RequestingDescriptor`、`Connecting` 和 `Ready`；该事件不会替代
两个专用通知。

## 显式重连入口

### Core Host descriptor

Core 根据 `IGPConfig.hostSessionPlatform` 选择 Desktop 或 Mobile Host provider
重新取得 descriptor。Multiplayer 不判断宿主平台。该 API 不会被 Multiplayer
自己调用，调用方应当在 UI、匹配状态或自己的退避策略允许时调用：

```csharp
using System.Threading;
using System.Threading.Tasks;
using IGP.Multiplayer;

public static class HostReconnect
{
    public static async Task<bool> ReconnectAsync(
        IGPMultiplayerRuntime runtime,
        CancellationToken cancellationToken)
    {
        bool connected = await runtime.ReconnectDataPlaneAsync(cancellationToken);
        return connected && runtime.IsRealtimeReady;
    }
}
```

### 显式 descriptor

调用方也可以从自己的 Lobby/房间控制面重新申请 descriptor，并使用同一个
`RoomId` 与 `PlayerId` 构造新的 `IGPMultiplayerDescriptor`。该入口不依赖或
改变 Core Host 平台：

```csharp
using System.Threading;
using System.Threading.Tasks;
using IGP.Multiplayer;

public static class ExplicitDescriptorReconnect
{
    public static async Task<bool> ReconnectAsync(
        IGPMultiplayerRuntime runtime,
        string roomId,
        string playerId,
        string freshSdkDescriptorJson,
        CancellationToken cancellationToken)
    {
        IGPMultiplayerDescriptor descriptor =
            IGPMultiplayerDescriptor.FromSdkDescriptorJson(
                roomId,
                playerId,
                freshSdkDescriptorJson);
        bool connected = await runtime.ReconnectDataPlaneAsync(
            descriptor,
            cancellationToken);
        return connected && runtime.IsRealtimeReady;
    }
}
```

同一时刻只提交一次重连调用。成功返回后才允许上层恢复业务发送；失败返回
`false` 或抛出调用方取消异常时，仍处于非 `Ready` 状态，上层可显示失败并决定
是否再次调用。重连不会复用旧连接的发送队列，过期 descriptor 也不会被 SDK
悄悄刷新。

## Mirror 交接边界

可靠 data plane 断开时，`IGPMirrorTransport` 会清掉待派发消息并向已建立的
Mirror 逻辑连接派发断开回调。`ConnectionRecovered` 到达后，应用可以按自己
的协议重新建立 Mirror peer，例如重新调用项目的 `StartClient`/`StartHost`
流程；SDK 不替应用选择 peer、场景或同步策略。

必须由游戏自己处理：

- 重新建立 Mirror peer 和 NetworkIdentity 生命周期。
- 从服务器请求并应用权威棋盘、玩家和回合状态。
- 判断断线期间的输入、RPC 和业务消息是丢弃、重试还是幂等补偿。
- 在重同步完成前阻止画面进入“已恢复可操作”状态。

Arena 的新 reliable session 拥有全新的发送队列。旧 KCP/TCP 连接中尚未发送
或尚未完成应用交付的消息不会转移到新连接，也没有跨连接 replay/snapshot。
