Files
ft/Client/Assets/Scripts/Services/RTService.cs

233 lines
8.6 KiB
C#

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<T>(Action<T> handler);
public Task<string> Publish(string methodName, string arg1);
public Task<string> 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<string, Action<string>> handlers = new Dictionary<string, Action<string>>();
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<EvtAPISvrLoginSuccess>().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<string, string, string>("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<string> handler)
{
hub.On<string>(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<T>(Action<T> handler)
{
if (released) throw new ObjectDisposedException(nameof(SignalRService));
var name = typeof(T).Name;
if (!handlers.ContainsKey(name))
{
Action<string> dispatch = data => _eventAgg.Publish(JsonConvert.DeserializeObject<T>(data));
handlers.Add(name, dispatch);
if (_hubConnection != null)
Bind(_hubConnection, connectionVersion, _serverMgr.SessionVersion, name, dispatch);
}
return _eventAgg.GetEvent<T>().Subscribe(handler);
}
public async Task<string> 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<string>(methodName, arg1);
return IsCurrent(hub, version, session) ? result : null;
}
catch(Exception e)
{
Debug.LogException(e);
return null;
}
}
public async Task<string> 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<string>(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<NoticeConfirmPopupPanel>();
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<IStageService>().SwitchStageAsync("QuitStage");
}
public async void Test()
{
Debug.Log("[RTService] Test invoking");
var returnValue = await _hubConnection.InvokeAsync<string>("Test","arg1", "arg2");
Debug.Log($"[RTService] Test invoked Value {returnValue}");
}
}