136 lines
5.1 KiB
C#
136 lines
5.1 KiB
C#
// Assets/_Project/Scripts/Net/SessionFlow.cs
|
|
using Unity.Netcode;
|
|
using UnityEngine;
|
|
using UnityEngine.SceneManagement;
|
|
|
|
namespace TD.Net
|
|
{
|
|
/// <summary>
|
|
/// Bootstrap script — drop one on a GameObject in the MainMenu scene so it
|
|
/// runs once when the app boots. Subscribes to NGO disconnect callbacks
|
|
/// and routes the local peer back to the MainMenu scene whenever its
|
|
/// connection ends (host left, server kicked it, transport error, etc.).
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para><b>Why this exists.</b> Per Option A (see Project_Roadmap.md §1.7),
|
|
/// host disconnect tears down the session for everyone. Each client needs
|
|
/// to recover gracefully and return to its own main menu. Without this,
|
|
/// a client would be stuck in the Lobby or Match scene with no working
|
|
/// network connection after the host quits.</para>
|
|
///
|
|
/// <para><b>Lifecycle.</b> The NetworkManager itself is marked
|
|
/// DontDestroyOnLoad by NGO once it spawns. This script is also marked
|
|
/// DontDestroyOnLoad so it survives scene transitions and its subscription
|
|
/// stays alive across MainMenu → Lobby → Match → back.</para>
|
|
///
|
|
/// <para><b>Server side.</b> When the host (server) shuts down, the
|
|
/// <c>OnServerStopped</c> callback fires on the host too. For the host
|
|
/// that's fine — they've initiated the shutdown deliberately (e.g., from
|
|
/// the "Return to Main Menu" button) and being sent to MainMenu is what
|
|
/// they wanted.</para>
|
|
/// </remarks>
|
|
public class SessionFlow : MonoBehaviour
|
|
{
|
|
// ----- Singleton --------------------------------------------------
|
|
|
|
public static SessionFlow Instance { get; private set; }
|
|
|
|
private void Awake()
|
|
{
|
|
if (Instance != null && Instance != this)
|
|
{
|
|
Destroy(gameObject);
|
|
return;
|
|
}
|
|
Instance = this;
|
|
DontDestroyOnLoad(gameObject);
|
|
}
|
|
|
|
private void Start()
|
|
{
|
|
HookCallbacks();
|
|
}
|
|
|
|
private void OnDestroy()
|
|
{
|
|
UnhookCallbacks();
|
|
if (Instance == this) Instance = null;
|
|
}
|
|
|
|
// ----- NGO callback wiring ---------------------------------------
|
|
|
|
private bool callbacksHooked;
|
|
|
|
private void HookCallbacks()
|
|
{
|
|
var nm = NetworkManager.Singleton;
|
|
if (nm == null)
|
|
{
|
|
// NetworkManager may not be alive yet on the very first frame
|
|
// depending on script execution order — retry next frame.
|
|
Invoke(nameof(HookCallbacks), 0.1f);
|
|
return;
|
|
}
|
|
if (callbacksHooked) return;
|
|
|
|
nm.OnClientDisconnectCallback += HandleClientDisconnect;
|
|
nm.OnServerStopped += HandleServerStopped;
|
|
callbacksHooked = true;
|
|
}
|
|
|
|
private void UnhookCallbacks()
|
|
{
|
|
if (!callbacksHooked) return;
|
|
var nm = NetworkManager.Singleton;
|
|
if (nm != null)
|
|
{
|
|
nm.OnClientDisconnectCallback -= HandleClientDisconnect;
|
|
nm.OnServerStopped -= HandleServerStopped;
|
|
}
|
|
callbacksHooked = false;
|
|
}
|
|
|
|
// ----- Disconnect handlers ---------------------------------------
|
|
|
|
// Fires when ANY client disconnects on the server, and when the LOCAL
|
|
// client gets disconnected on a client. We only care about the latter:
|
|
// when the local peer loses its connection (host left, transport error,
|
|
// explicit Shutdown call), route back to the main menu.
|
|
private void HandleClientDisconnect(ulong clientId)
|
|
{
|
|
var nm = NetworkManager.Singleton;
|
|
if (nm == null) return;
|
|
|
|
// On the server, this fires when other clients disconnect — ignore
|
|
// those, the server keeps running. We only act when the LOCAL
|
|
// client's connection ends.
|
|
if (clientId != nm.LocalClientId) return;
|
|
|
|
ReturnToMainMenu();
|
|
}
|
|
|
|
// Fires on the server when the server shuts down (including the host).
|
|
// The host's own client also gets HandleClientDisconnect — but
|
|
// OnServerStopped is the canonical "server is gone" signal and is
|
|
// safer to act on for the host path.
|
|
private void HandleServerStopped(bool isHost)
|
|
{
|
|
ReturnToMainMenu();
|
|
}
|
|
|
|
// ----- Scene transition ------------------------------------------
|
|
|
|
// Returns the local peer to the MainMenu scene if they're not already
|
|
// there. Uses Unity's local SceneManager (NOT NGO's networked scene
|
|
// manager) because by the time this fires the network connection is
|
|
// already gone.
|
|
private static void ReturnToMainMenu()
|
|
{
|
|
if (SceneManager.GetActiveScene().name == SceneNames.MainMenu)
|
|
return;
|
|
|
|
Debug.Log("[SessionFlow] Connection ended — returning to main menu.");
|
|
SceneManager.LoadScene(SceneNames.MainMenu);
|
|
}
|
|
}
|
|
}
|