365 lines
10 KiB
C#
365 lines
10 KiB
C#
using System;
|
|
using System.Collections;
|
|
using GameServer.Client;
|
|
using UnityEngine;
|
|
using UnityEngine.Networking;
|
|
using UnityEngine.UI;
|
|
|
|
public class serverSelector : MonoBehaviour
|
|
{
|
|
private const string DefaultServerUrl = "https://game.bansonic.top";
|
|
private const float RefreshIntervalSeconds = 5f;
|
|
private const float StatusCacheDurationSeconds = 60f;
|
|
|
|
[Header("button")]
|
|
[SerializeField] private Button serverSelectorButton;
|
|
|
|
[Header("text")]
|
|
[SerializeField] private Text currentServerName;
|
|
[SerializeField] private Image currentServerStatus;
|
|
[SerializeField] private uServerStatus currentServerStatusDisplay;
|
|
|
|
[Header("Server Status Sprites")]
|
|
[SerializeField] private Sprite serverOkaySprite;
|
|
[SerializeField] private Sprite serverBusySprite;
|
|
[SerializeField] private Sprite serverErrorSprite;
|
|
[SerializeField] private Sprite serverSupportingSprite;
|
|
[SerializeField] private Sprite userOfflineSprite;
|
|
|
|
[Header("objs")]
|
|
[SerializeField] private GameObject serverSelectorPanel;
|
|
[SerializeField] private Button shutSSPanel;
|
|
|
|
[Header("network")]
|
|
[SerializeField] private string fallbackServerUrl = DefaultServerUrl;
|
|
[SerializeField] private float requestTimeoutSeconds = 5f;
|
|
|
|
private Coroutine _pollCoroutine;
|
|
private bool _started;
|
|
private static bool s_hasCachedStatus;
|
|
private static uServerStatus.ServerStatus s_cachedStatus;
|
|
private static DateTime s_lastValidationUtc;
|
|
|
|
[Serializable]
|
|
private class ServerStatusResponse
|
|
{
|
|
public bool success;
|
|
public string server_state;
|
|
public bool maintenance_enabled;
|
|
public string maintenance_message;
|
|
public float requests_per_second;
|
|
public float busy_qps_threshold;
|
|
}
|
|
|
|
private void Awake()
|
|
{
|
|
if (serverSelectorButton != null)
|
|
{
|
|
serverSelectorButton.onClick.RemoveListener(OpenServerSelectorPanel);
|
|
serverSelectorButton.onClick.AddListener(OpenServerSelectorPanel);
|
|
}
|
|
|
|
if (shutSSPanel != null)
|
|
{
|
|
shutSSPanel.onClick.RemoveListener(CloseServerSelectorPanel);
|
|
shutSSPanel.onClick.AddListener(CloseServerSelectorPanel);
|
|
}
|
|
}
|
|
|
|
private void Start()
|
|
{
|
|
_started = true;
|
|
|
|
if (serverSelectorPanel != null)
|
|
{
|
|
serverSelectorPanel.SetActive(false);
|
|
}
|
|
|
|
if (OnlineModeSettings.IsLocalOnlyMode)
|
|
{
|
|
ApplyStatus(uServerStatus.ServerStatus.Offline);
|
|
return;
|
|
}
|
|
|
|
StartOrRestartPolling(refreshImmediately: true);
|
|
}
|
|
|
|
private void OnEnable()
|
|
{
|
|
if (!_started || !Application.isPlaying)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (OnlineModeSettings.IsLocalOnlyMode)
|
|
{
|
|
ApplyStatus(uServerStatus.ServerStatus.Offline);
|
|
return;
|
|
}
|
|
|
|
StartOrRestartPolling(refreshImmediately: true);
|
|
}
|
|
|
|
private void StartOrRestartPolling(bool refreshImmediately)
|
|
{
|
|
if (_pollCoroutine != null)
|
|
{
|
|
StopCoroutine(_pollCoroutine);
|
|
_pollCoroutine = null;
|
|
}
|
|
|
|
_pollCoroutine = StartCoroutine(StatusPollLoop(refreshImmediately));
|
|
}
|
|
|
|
private void OnDisable()
|
|
{
|
|
if (_pollCoroutine != null)
|
|
{
|
|
StopCoroutine(_pollCoroutine);
|
|
_pollCoroutine = null;
|
|
}
|
|
}
|
|
|
|
private IEnumerator StatusPollLoop(bool refreshImmediately)
|
|
{
|
|
if (refreshImmediately)
|
|
{
|
|
if (TryApplyFreshCachedStatus())
|
|
{
|
|
float remainingLockDuration = GetRemainingCacheLockSeconds();
|
|
if (remainingLockDuration > 0f)
|
|
{
|
|
yield return new WaitForSecondsRealtime(remainingLockDuration);
|
|
}
|
|
}
|
|
|
|
yield return RefreshServerStatus();
|
|
}
|
|
|
|
while (true)
|
|
{
|
|
yield return new WaitForSecondsRealtime(RefreshIntervalSeconds);
|
|
yield return RefreshServerStatus();
|
|
}
|
|
}
|
|
|
|
private IEnumerator RefreshServerStatus()
|
|
{
|
|
if (OnlineModeSettings.IsLocalOnlyMode)
|
|
{
|
|
ApplyStatus(uServerStatus.ServerStatus.Offline);
|
|
yield break;
|
|
}
|
|
|
|
if (Application.internetReachability == NetworkReachability.NotReachable)
|
|
{
|
|
ApplyStatus(uServerStatus.ServerStatus.Offline);
|
|
yield break;
|
|
}
|
|
|
|
string statusUrl = BuildServerStatusUrl();
|
|
using (UnityWebRequest request = UnityWebRequest.Get(statusUrl))
|
|
{
|
|
request.timeout = Mathf.Max(1, Mathf.RoundToInt(requestTimeoutSeconds));
|
|
yield return request.SendWebRequest();
|
|
|
|
if (request.result != UnityWebRequest.Result.Success)
|
|
{
|
|
CacheAndApplyStatus(uServerStatus.ServerStatus.Unreachable);
|
|
yield break;
|
|
}
|
|
|
|
string responseText = request.downloadHandler != null ? request.downloadHandler.text : string.Empty;
|
|
if (string.IsNullOrWhiteSpace(responseText))
|
|
{
|
|
CacheAndApplyStatus(uServerStatus.ServerStatus.Unreachable);
|
|
yield break;
|
|
}
|
|
|
|
ServerStatusResponse response;
|
|
try
|
|
{
|
|
response = JsonUtility.FromJson<ServerStatusResponse>(responseText);
|
|
}
|
|
catch
|
|
{
|
|
CacheAndApplyStatus(uServerStatus.ServerStatus.Unreachable);
|
|
yield break;
|
|
}
|
|
|
|
if (response == null || !response.success)
|
|
{
|
|
CacheAndApplyStatus(uServerStatus.ServerStatus.Unreachable);
|
|
yield break;
|
|
}
|
|
|
|
CacheAndApplyStatus(ResolveStatus(response));
|
|
}
|
|
}
|
|
|
|
private void OpenServerSelectorPanel()
|
|
{
|
|
if (serverSelectorPanel != null)
|
|
{
|
|
serverSelectorPanel.SetActive(true);
|
|
}
|
|
|
|
if (!OnlineModeSettings.IsLocalOnlyMode)
|
|
{
|
|
StartCoroutine(RefreshServerStatusIfNeeded());
|
|
}
|
|
else
|
|
{
|
|
ApplyStatus(uServerStatus.ServerStatus.Offline);
|
|
}
|
|
}
|
|
|
|
private void CloseServerSelectorPanel()
|
|
{
|
|
if (serverSelectorPanel != null)
|
|
{
|
|
serverSelectorPanel.SetActive(false);
|
|
}
|
|
}
|
|
|
|
private string ResolveServerBaseUrl()
|
|
{
|
|
NetworkManager manager = NetworkManager.Instance;
|
|
if (manager != null && !string.IsNullOrWhiteSpace(manager.ServerUrl))
|
|
{
|
|
return manager.ServerUrl.TrimEnd('/');
|
|
}
|
|
|
|
return string.IsNullOrWhiteSpace(fallbackServerUrl) ? DefaultServerUrl : fallbackServerUrl.TrimEnd('/');
|
|
}
|
|
|
|
private string BuildServerStatusUrl()
|
|
{
|
|
return $"{ResolveServerBaseUrl()}/api/server-status";
|
|
}
|
|
|
|
private IEnumerator RefreshServerStatusIfNeeded()
|
|
{
|
|
if (TryApplyFreshCachedStatus())
|
|
{
|
|
yield break;
|
|
}
|
|
|
|
yield return RefreshServerStatus();
|
|
}
|
|
|
|
private uServerStatus.ServerStatus ResolveStatus(ServerStatusResponse response)
|
|
{
|
|
if (response == null)
|
|
{
|
|
return uServerStatus.ServerStatus.Unreachable;
|
|
}
|
|
|
|
if (response.maintenance_enabled)
|
|
{
|
|
return uServerStatus.ServerStatus.Maintenance;
|
|
}
|
|
|
|
string normalizedState = (response.server_state ?? string.Empty).Trim().ToUpperInvariant();
|
|
switch (normalizedState)
|
|
{
|
|
case "MAINTENANCE":
|
|
return uServerStatus.ServerStatus.Maintenance;
|
|
case "BUSY":
|
|
return uServerStatus.ServerStatus.Fair;
|
|
case "OK":
|
|
if (response.busy_qps_threshold > 0f &&
|
|
response.requests_per_second >= response.busy_qps_threshold)
|
|
{
|
|
return uServerStatus.ServerStatus.Fair;
|
|
}
|
|
|
|
return uServerStatus.ServerStatus.Good;
|
|
default:
|
|
return uServerStatus.ServerStatus.Unknown;
|
|
}
|
|
}
|
|
|
|
private bool TryApplyFreshCachedStatus()
|
|
{
|
|
if (!HasFreshCachedStatus())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
ApplyStatus(s_cachedStatus);
|
|
return true;
|
|
}
|
|
|
|
private bool HasFreshCachedStatus()
|
|
{
|
|
if (!s_hasCachedStatus)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return (DateTime.UtcNow - s_lastValidationUtc).TotalSeconds < StatusCacheDurationSeconds;
|
|
}
|
|
|
|
private float GetRemainingCacheLockSeconds()
|
|
{
|
|
if (!s_hasCachedStatus)
|
|
{
|
|
return 0f;
|
|
}
|
|
|
|
double elapsedSeconds = (DateTime.UtcNow - s_lastValidationUtc).TotalSeconds;
|
|
return Mathf.Max(0f, StatusCacheDurationSeconds - (float)elapsedSeconds);
|
|
}
|
|
|
|
private void CacheAndApplyStatus(uServerStatus.ServerStatus status)
|
|
{
|
|
s_cachedStatus = status;
|
|
s_lastValidationUtc = DateTime.UtcNow;
|
|
s_hasCachedStatus = true;
|
|
ApplyStatus(status);
|
|
}
|
|
|
|
private Sprite ResolveSpriteForStatus(uServerStatus.ServerStatus status)
|
|
{
|
|
switch (status)
|
|
{
|
|
case uServerStatus.ServerStatus.Good:
|
|
return serverOkaySprite;
|
|
case uServerStatus.ServerStatus.Fair:
|
|
return serverBusySprite;
|
|
case uServerStatus.ServerStatus.Maintenance:
|
|
return serverSupportingSprite;
|
|
case uServerStatus.ServerStatus.Offline:
|
|
return userOfflineSprite != null ? userOfflineSprite : serverErrorSprite;
|
|
case uServerStatus.ServerStatus.Unreachable:
|
|
case uServerStatus.ServerStatus.Unknown:
|
|
case uServerStatus.ServerStatus.Test:
|
|
default:
|
|
return serverErrorSprite;
|
|
}
|
|
}
|
|
|
|
private void ApplyStatus(uServerStatus.ServerStatus status)
|
|
{
|
|
Sprite sprite = ResolveSpriteForStatus(status);
|
|
|
|
if (currentServerStatus == null || sprite == null)
|
|
{
|
|
if (currentServerStatusDisplay != null)
|
|
{
|
|
currentServerStatusDisplay.SetStatus(status);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
currentServerStatus.sprite = sprite;
|
|
|
|
if (currentServerStatusDisplay != null)
|
|
{
|
|
currentServerStatusDisplay.SetStatus(status);
|
|
}
|
|
}
|
|
}
|