Ignore AIClient/Library and AIClient/HybridCLRData permanently; include HybridCLR/YooAsset project setup and UI tooling. Co-authored-by: Cursor <cursoragent@cursor.com>
149 lines
4.7 KiB
C#
149 lines
4.7 KiB
C#
using System;
|
|
using System.Collections;
|
|
using AOT.Boot;
|
|
using UnityEngine;
|
|
|
|
namespace HotUpdate.Net
|
|
{
|
|
/// <summary>
|
|
/// 登录 → WS 连接编排。
|
|
/// </summary>
|
|
public sealed class GameNet : MonoBehaviour
|
|
{
|
|
public string HttpBase = AuthApi.DefaultHttpBase;
|
|
public string WsUrlTemplate = "wss://game.xworld.ren/ws?pid={pid}&token={token}";
|
|
|
|
public SessionData Session { get; private set; }
|
|
public WsClient Socket { get; private set; }
|
|
public string Status { get; private set; } = "idle";
|
|
|
|
public event Action<SessionData> OnAuthed;
|
|
public event Action OnWsReady;
|
|
public event Action<string> OnStatus;
|
|
public event Action<string> OnWsMessage;
|
|
|
|
BootContext _boot;
|
|
bool _started;
|
|
|
|
public void Bind(BootContext boot)
|
|
{
|
|
_boot = boot;
|
|
if (_boot != null && !string.IsNullOrEmpty(_boot.WsUrlTemplate))
|
|
{
|
|
// 旧模板只有 pid 时补上 token
|
|
var tpl = _boot.WsUrlTemplate;
|
|
if (!tpl.Contains("{token}"))
|
|
{
|
|
tpl = tpl.Contains("?")
|
|
? tpl + (tpl.EndsWith("?") || tpl.EndsWith("&") ? "token={token}" : "&token={token}")
|
|
: tpl + "?pid={pid}&token={token}";
|
|
}
|
|
WsUrlTemplate = tpl;
|
|
}
|
|
}
|
|
|
|
public void StartGuestSession()
|
|
{
|
|
if (_started) return;
|
|
_started = true;
|
|
StartCoroutine(RunGuestFlow());
|
|
}
|
|
|
|
IEnumerator RunGuestFlow()
|
|
{
|
|
SetStatus("auth…");
|
|
var deviceId = DeviceId.GetOrCreate();
|
|
// 服务器要求 username/password;编辑器自动账号
|
|
var username = $"guest_{deviceId}";
|
|
var password = $"pw_{deviceId}";
|
|
|
|
AuthResponse auth = null;
|
|
string err = null;
|
|
yield return AuthApi.EnsureAccount(HttpBase, username, password, (r, e) => { auth = r; err = e; });
|
|
|
|
if (auth == null || string.IsNullOrEmpty(auth.token))
|
|
{
|
|
SetStatus($"auth fail: {err}");
|
|
Debug.LogError($"[GameNet] auth failed: {err}");
|
|
yield break;
|
|
}
|
|
|
|
Session = new SessionData
|
|
{
|
|
Username = username,
|
|
Token = auth.token,
|
|
Pid = auth.pid,
|
|
DeviceId = deviceId,
|
|
};
|
|
Debug.Log($"[GameNet] authed pid={Session.Pid} user={username}");
|
|
OnAuthed?.Invoke(Session);
|
|
SetStatus($"authed pid={Session.Pid}");
|
|
|
|
yield return ConnectWs();
|
|
}
|
|
|
|
IEnumerator ConnectWs()
|
|
{
|
|
if (Session == null || !Session.IsValid)
|
|
{
|
|
SetStatus("no session");
|
|
yield break;
|
|
}
|
|
|
|
SetStatus("ws connecting…");
|
|
Socket?.Dispose();
|
|
Socket = new WsClient();
|
|
Socket.AutoReconnect = true;
|
|
Socket.OnConnected += () =>
|
|
{
|
|
SetStatus("ws connected");
|
|
OnWsReady?.Invoke();
|
|
// 应用层握手(服务端当前可无响应,保留扩展)
|
|
Socket.SendText($"{{\"op\":\"hello\",\"proto\":1,\"app\":\"{_boot?.AppVersion}\",\"res\":\"{_boot?.ResVersion}\",\"pid\":{Session.Pid}}}");
|
|
};
|
|
Socket.OnDisconnected += reason => SetStatus($"ws down: {reason}");
|
|
Socket.OnMessage += msg =>
|
|
{
|
|
Debug.Log($"[GameNet] << {msg}");
|
|
OnWsMessage?.Invoke(msg);
|
|
};
|
|
|
|
var url = Session.BuildWsUrl(WsUrlTemplate);
|
|
Socket.Connect(url);
|
|
|
|
// 等待最多 15s
|
|
float t = 0f;
|
|
while (t < 15f && Socket.State != WsState.Connected && Socket.State != WsState.Disconnected)
|
|
{
|
|
t += Time.unscaledDeltaTime;
|
|
yield return null;
|
|
}
|
|
|
|
if (Socket.State == WsState.Connected)
|
|
SetStatus("ws connected");
|
|
else if (Socket.State == WsState.Disconnected && !string.IsNullOrEmpty(Socket.LastError))
|
|
SetStatus($"ws fail: {Socket.LastError}");
|
|
else if (Socket.State != WsState.Connected)
|
|
SetStatus($"ws state={Socket.State}");
|
|
}
|
|
|
|
void Update()
|
|
{
|
|
Socket?.Tick();
|
|
}
|
|
|
|
void OnDestroy()
|
|
{
|
|
Socket?.Dispose();
|
|
Socket = null;
|
|
}
|
|
|
|
void SetStatus(string s)
|
|
{
|
|
Status = s;
|
|
Debug.Log($"[GameNet] {s}");
|
|
OnStatus?.Invoke(s);
|
|
}
|
|
}
|
|
}
|