Ship the WeChat hot-update catalogs and the camp, TideWhale T2, UI atlas, and animation-lab work.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
udx
2026-10-24 18:04:41 +08:00
co-authored by Cursor
parent 6ebc9401ce
commit 45e62132c8
1432 changed files with 2971856 additions and 2798 deletions
@@ -11,7 +11,8 @@ namespace HotUpdate.Net
/// </summary>
public static class AuthApi
{
public const string DefaultHttpBase = "https://game.xworld.ren";
public const string DefaultHttpBase = AOT.Boot.BootSettings.ProductionHttpBase;
public const string DefaultWsTemplate = AOT.Boot.BootSettings.ProductionWsTemplate;
[Serializable]
class AuthBody
@@ -11,7 +11,7 @@ namespace HotUpdate.Net
public sealed class GameNet : MonoBehaviour
{
public string HttpBase = AuthApi.DefaultHttpBase;
public string WsUrlTemplate = "wss://game.xworld.ren/ws?pid={pid}&token={token}";
public string WsUrlTemplate = AuthApi.DefaultWsTemplate;
public SessionData Session { get; private set; }
public WsClient Socket { get; private set; }
@@ -60,6 +60,27 @@ namespace HotUpdate.Net
}
WsUrlTemplate = tpl;
}
#if UNITY_WEBGL && !UNITY_EDITOR
// Serialized Boot scenes used for local Editor testing must not send players to loopback.
HttpBase = ResolveRemoteEndpoint(HttpBase, "https", AuthApi.DefaultHttpBase);
WsUrlTemplate = ResolveRemoteEndpoint(WsUrlTemplate, "wss", AuthApi.DefaultWsTemplate);
_boot.HttpBase = HttpBase;
_boot.WsUrlTemplate = WsUrlTemplate;
#endif
}
public static string ResolveRemoteEndpoint(string value, string scheme, string fallback)
{
if (Uri.TryCreate(value, UriKind.Absolute, out var uri) &&
uri.Scheme == scheme && !uri.IsLoopback && !uri.Host.EndsWith(".localhost", StringComparison.OrdinalIgnoreCase))
{
// Migrate the former production defaults stored in already-shipped Boot scenes.
if (uri.IsDefaultPort && uri.Host.Equals("game.xworld.ren", StringComparison.OrdinalIgnoreCase) &&
((scheme == "https" && uri.AbsolutePath == "/") || (scheme == "wss" && uri.AbsolutePath == "/ws")))
return fallback;
return value;
}
return fallback;
}
public void StartGuestSession()
@@ -27,7 +27,7 @@ namespace HotUpdate.Net
public string BuildWsUrl(string template = null)
{
var tpl = string.IsNullOrEmpty(template)
? "wss://game.xworld.ren/ws?pid={pid}&token={token}"
? AuthApi.DefaultWsTemplate
: template;
return tpl
.Replace("{pid}", Pid.ToString())
@@ -1,9 +1,12 @@
using System;
using System.Collections.Concurrent;
#if !UNITY_WEBGL || UNITY_EDITOR
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
#endif
using AOT.Boot;
using UnityEngine;
namespace HotUpdate.Net
@@ -17,18 +20,23 @@ namespace HotUpdate.Net
}
/// <summary>
/// 基于 ClientWebSocket 的传输(Editor / Standalone)。
/// WebGL/微信需另接平台桥,本类在 WebGL 下不可用。
/// Editor/Standalone 使用 ClientWebSocket;WebGL 使用微信 SocketTask/浏览器桥。
/// </summary>
public sealed class WsClient : IDisposable
{
readonly ConcurrentQueue<string> _inbox = new ConcurrentQueue<string>();
readonly ConcurrentQueue<string> _outbox = new ConcurrentQueue<string>();
#if UNITY_WEBGL && !UNITY_EDITOR
int _browserSocket;
float _connectDeadline;
float _reconnectAt = -1;
#else
ClientWebSocket _ws;
CancellationTokenSource _cts;
Task _recvTask;
Task _sendTask;
#endif
string _url;
int _reconnectAttempt;
bool _manualClose;
@@ -48,7 +56,11 @@ namespace HotUpdate.Net
_url = url;
_manualClose = false;
_reconnectAttempt = 0;
#if UNITY_WEBGL && !UNITY_EDITOR
StartBrowserConnection();
#else
_ = ConnectInternalAsync();
#endif
}
/// <summary>立刻断开并重新连接(重置退避计数)。</summary>
@@ -58,11 +70,15 @@ namespace HotUpdate.Net
_manualClose = false;
AutoReconnect = true;
_reconnectAttempt = 0;
#if UNITY_WEBGL && !UNITY_EDITOR
StartBrowserConnection();
#else
SafeCancel();
try { _ws?.Dispose(); } catch { /* ignore */ }
_ws = null;
State = WsState.Disconnected;
_ = ConnectInternalAsync();
#endif
}
public void SendText(string text)
@@ -75,26 +91,91 @@ namespace HotUpdate.Net
{
_manualClose = true;
AutoReconnect = false;
#if UNITY_WEBGL && !UNITY_EDITOR
ReleaseBrowserSocket();
_reconnectAt = -1;
#else
SafeCancel();
try { _ws?.Dispose(); } catch { /* ignore */ }
_ws = null;
#endif
State = WsState.Disconnected;
}
public void Tick()
{
#if UNITY_WEBGL && !UNITY_EDITOR
TickBrowser();
#endif
while (_inbox.TryDequeue(out var msg))
OnMessage?.Invoke(msg);
}
#if UNITY_WEBGL && !UNITY_EDITOR
void ReleaseBrowserSocket()
{
if (_browserSocket != 0) WebSocketBridge.AIGame_WsDispose(_browserSocket);
_browserSocket = 0;
}
void StartBrowserConnection()
{
ReleaseBrowserSocket();
_reconnectAt = -1;
LastError = null;
State = _reconnectAttempt > 0 ? WsState.Reconnecting : WsState.Connecting;
_connectDeadline = Time.realtimeSinceStartup + 15f;
_browserSocket = WebSocketBridge.AIGame_WsCreate(_url);
if (_browserSocket == 0) BrowserDisconnected("WebSocket bridge unavailable");
}
void BrowserDisconnected(string reason)
{
ReleaseBrowserSocket();
LastError = reason;
State = WsState.Disconnected;
while (_outbox.TryDequeue(out _)) { }
_reconnectAt = -1;
if (!_manualClose && AutoReconnect)
{
_reconnectAttempt++;
_reconnectAt = Time.realtimeSinceStartup + Math.Min(30, 1 << Math.Min(5, _reconnectAttempt));
}
OnDisconnected?.Invoke(reason);
if (_reconnectAt >= 0) OnReconnecting?.Invoke(_reconnectAttempt);
}
void TickBrowser()
{
if (_reconnectAt >= 0 && Time.realtimeSinceStartup >= _reconnectAt)
{
_reconnectAt = -1;
if (!_manualClose && AutoReconnect) StartBrowserConnection();
}
int current = _browserSocket;
// Bound work per frame; callbacks may close or replace this connection.
for (int i = 0; current != 0 && current == _browserSocket && i < 100; i++)
{
string item = WebSocketBridge.AIGame_WsPoll(current);
if (string.IsNullOrEmpty(item)) break;
if (item[0] == 'O')
{
State = WsState.Connected;
_reconnectAttempt = 0;
LastError = null;
OnConnected?.Invoke();
}
else if (item[0] == 'M') OnMessage?.Invoke(item.Substring(1));
else if (item[0] == 'E' || item[0] == 'C') BrowserDisconnected(item.Substring(1));
}
if (_browserSocket != 0 && State != WsState.Connected && Time.realtimeSinceStartup >= _connectDeadline)
BrowserDisconnected("WebSocket connection timed out");
for (int i = 0; _browserSocket != 0 && State == WsState.Connected && i < 100 && _outbox.TryDequeue(out var text); i++)
WebSocketBridge.AIGame_WsSend(_browserSocket, text);
}
#else
async Task ConnectInternalAsync()
{
#if UNITY_WEBGL && !UNITY_EDITOR
LastError = "ClientWebSocket not supported on WebGL; need WeChat bridge";
State = WsState.Disconnected;
OnDisconnected?.Invoke(LastError);
return;
#else
SafeCancel();
_cts = new CancellationTokenSource();
State = _reconnectAttempt > 0 ? WsState.Reconnecting : WsState.Connecting;
@@ -144,7 +225,6 @@ namespace HotUpdate.Net
catch { /* ignore */ }
}
}
#endif
}
async Task RecvLoopAsync(CancellationToken ct)
@@ -194,6 +274,7 @@ namespace HotUpdate.Net
try { _cts?.Dispose(); } catch { /* ignore */ }
_cts = null;
}
#endif
public void Dispose() => Close();
}