using System; using System.Collections; using System.Collections.Generic; using System.Threading.Tasks; using UnityEngine; using System.Net; using asap.core; using Microsoft.AspNetCore.Http.Connections; using Microsoft.AspNetCore.SignalR.Client; using Newtonsoft.Json; using UniRx; using GameCore; using game; public interface IRTService { public void WaitForAPISvrLogin(); public Task Connect(); public Task Disconnect(); public bool IsConnected { get; } public IDisposable OnEvent(Action handler); public Task Publish(string methodName, string arg1); public Task Publish(string methodName, string arg1, string arg2); //public void Reset(); public void Release(); public Task ReleaseAsync(); } public class SignalRService : IRTService { private HubConnection _hubConnection; private readonly ICustomServerMgr _serverMgr; private readonly IEventAggregator _eventAgg = new EventAggregator(); private readonly string _url; private readonly Dictionary> handlers = new Dictionary>(); private readonly System.Threading.SemaphoreSlim lifecycle = new System.Threading.SemaphoreSlim(1, 1); private IDisposable loginSubscription; private bool released; private int connectionVersion; private System.Threading.CancellationTokenSource connecting; public SignalRService(ICustomServerMgr serverMgr, IConfig config) { _serverMgr = serverMgr; _url = new Uri(serverMgr.ApiBaseUri, "gamehub").AbsoluteUri; _serverMgr.SessionChanged += OnSessionChanged; } public void WaitForAPISvrLogin() { if (released || loginSubscription != null) return; loginSubscription = GContext.OnEvent().Subscribe(OnApiSvrLoginSuccess); } private async void OnApiSvrLoginSuccess(EvtAPISvrLoginSuccess evt) { try { await Connect(); } catch (Exception ex) { Debug.LogException(ex); } } private async void OnSessionChanged() { connectionVersion++; connecting?.Cancel(); try { await Disconnect(); } catch (Exception ex) { Debug.LogException(ex); } } private async Task CloseConnection() { var previous = _hubConnection; _hubConnection = null; if (previous == null) return; try { await previous.StopAsync(); } finally { await previous.DisposeAsync(); } } public async void Release() { try { await ReleaseAsync(); } catch (Exception ex) { Debug.LogException(ex); } } public async Task ReleaseAsync() { released = true; connectionVersion++; connecting?.Cancel(); loginSubscription?.Dispose(); loginSubscription = null; _serverMgr.SessionChanged -= OnSessionChanged; await lifecycle.WaitAsync(); try { await CloseConnection(); handlers.Clear(); } finally { lifecycle.Release(); } } private bool IsCurrent(HubConnection hub, int version, int session) => !released && version == connectionVersion && session == _serverMgr.SessionVersion && ReferenceEquals(hub, _hubConnection); public bool IsConnected => !released && _hubConnection != null && _hubConnection.State == HubConnectionState.Connected; public async Task Connect() { var session = _serverMgr.SessionVersion; var version = connectionVersion; var cookies = _serverMgr.Cookie; await lifecycle.WaitAsync(); try { if (released || version != connectionVersion || session != _serverMgr.SessionVersion) return; if (_hubConnection != null && _hubConnection.State != HubConnectionState.Disconnected) return; await CloseConnection(); if (released || version != connectionVersion || session != _serverMgr.SessionVersion) return; var hub = new HubConnectionBuilder() .WithUrl(_url, option => { option.Cookies = cookies; option.SkipNegotiation = true; option.Transports = HttpTransportType.WebSockets; }) .WithAutomaticReconnect() .Build(); _hubConnection = hub; hub.On("ForceQuit", (title, info, button) => { if (IsCurrent(hub, version, session)) ForceQuit(title, info, button, version, session); }); foreach (var handler in handlers) Bind(hub, version, session, handler.Key, handler.Value); connecting?.Dispose(); connecting = new System.Threading.CancellationTokenSource(); try { await hub.StartAsync(connecting.Token); } catch { await CloseConnection(); throw; } if (!IsCurrent(hub, version, session)) { await CloseConnection(); return; } Debug.Log("[RTService] Connected to SignalR server True"); } finally { lifecycle.Release(); } } private void Bind(HubConnection hub, int version, int session, string name, Action handler) { hub.On(name, data => { if (IsCurrent(hub, version, session)) handler(data); }); } public async Task Disconnect() { await lifecycle.WaitAsync(); try { await CloseConnection(); } finally { lifecycle.Release(); } } public IDisposable OnEvent(Action handler) { if (released) throw new ObjectDisposedException(nameof(SignalRService)); var name = typeof(T).Name; if (!handlers.ContainsKey(name)) { Action dispatch = data => _eventAgg.Publish(JsonConvert.DeserializeObject(data)); handlers.Add(name, dispatch); if (_hubConnection != null) Bind(_hubConnection, connectionVersion, _serverMgr.SessionVersion, name, dispatch); } return _eventAgg.GetEvent().Subscribe(handler); } public async Task Publish(string methodName, string arg1) { var hub = _hubConnection; var version = connectionVersion; var session = _serverMgr.SessionVersion; if (hub == null || !IsCurrent(hub, version, session)) return null; try { var result = await hub.InvokeAsync(methodName, arg1); return IsCurrent(hub, version, session) ? result : null; } catch(Exception e) { Debug.LogException(e); return null; } } public async Task Publish(string methodName, string arg1, string arg2) { var hub = _hubConnection; var version = connectionVersion; var session = _serverMgr.SessionVersion; if (hub == null || !IsCurrent(hub, version, session)) return null; try { var result = await hub.InvokeAsync(methodName, arg1, arg2); return IsCurrent(hub, version, session) ? result : null; } catch(Exception e) { Debug.LogException(e); return null; } } private async void ForceQuit(string titleKey, string infoKey, string btnKey, int version, int session) { await Awaiters.NextFrame; if (released || version != connectionVersion || session != _serverMgr.SessionVersion) return; GameObject go = await UIManager.Instance.ShowUI(UITypes.NoticeConfirmPopupPanel); if (released || version != connectionVersion || session != _serverMgr.SessionVersion) return; var noticeConfirmPopupPanel = go.GetComponent(); string title = LocalizationMgr.GetText(titleKey ?? "UI_NoticePopupPanel_3"); string info = LocalizationMgr.GetText(infoKey ?? "UI_NoticePopupPanel_4"); string label = LocalizationMgr.GetText(btnKey ?? "UI_COMMON_confirm"); noticeConfirmPopupPanel.Init(1, title, info, onClickLeft: () => { if (!released && version == connectionVersion && session == _serverMgr.SessionVersion) Restart(); }, onClose: () => { if (!released && version == connectionVersion && session == _serverMgr.SessionVersion) Restart(); }, btn_left_text: label); } private async void Restart() { QuitStage.Logout = true; await LoadingScreen.Show(); await GContext.container.Resolve().SwitchStageAsync("QuitStage"); } public async void Test() { Debug.Log("[RTService] Test invoking"); var returnValue = await _hubConnection.InvokeAsync("Test","arg1", "arg2"); Debug.Log($"[RTService] Test invoked Value {returnValue}"); } }