您最多选择25个主题
主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
611 行
24 KiB
611 行
24 KiB
using System;
|
|
using System.Collections.Generic;
|
|
using System.Threading.Tasks;
|
|
using Unity.Services.Authentication;
|
|
using Unity.Services.Lobbies;
|
|
using Unity.Services.Lobbies.Models;
|
|
using UnityEngine;
|
|
|
|
namespace LobbyRelaySample
|
|
{
|
|
/// <summary>
|
|
/// An abstraction layer between the direct calls into the Lobby API and the outcomes you actually want. E.g. you can request to get a readable list of
|
|
/// current lobbies and not need to make the query call directly.
|
|
/// </summary>
|
|
///
|
|
/// Manages one Lobby at a time, Only entry points to a lobby with ID is via JoinAsync, CreateAsync, and QuickJoinAsync
|
|
public class LobbyManager : IDisposable
|
|
{
|
|
const string key_RelayCode = nameof(LocalLobby.RelayCode);
|
|
const string key_LobbyState = nameof(LocalLobby.LocalLobbyState);
|
|
const string key_LobbyColor = nameof(LocalLobby.LocalLobbyColor);
|
|
|
|
const string key_Displayname = nameof(LocalPlayer.DisplayName);
|
|
const string key_Userstatus = nameof(LocalPlayer.UserStatus);
|
|
const string key_Emote = nameof(LocalPlayer.Emote);
|
|
|
|
//Once connected to a lobby, cache the local lobby object so we don't query for it for every lobby operation.
|
|
// (This assumes that the game will be actively in just one lobby at a time, though they could be in more on the service side.)
|
|
|
|
public Lobby CurrentLobby => m_CurrentLobby;
|
|
Lobby m_CurrentLobby;
|
|
LobbyEventCallbacks m_LobbyEventCallbacks = new LobbyEventCallbacks();
|
|
const int
|
|
k_maxLobbiesToShow = 16; // If more are necessary, consider retrieving paginated results or using filters.
|
|
|
|
Task m_HeartBeatTask;
|
|
|
|
#region Rate Limiting
|
|
|
|
public enum RequestType
|
|
{
|
|
Query = 0,
|
|
Join,
|
|
QuickJoin,
|
|
Host
|
|
}
|
|
|
|
public bool InLobby()
|
|
{
|
|
if (m_CurrentLobby == null)
|
|
{
|
|
Debug.LogWarning("LobbyManager not currently in a lobby. Did you CreateLobbyAsync or JoinLobbyAsync?");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public ServiceRateLimiter GetRateLimit(RequestType type)
|
|
{
|
|
if (type == RequestType.Join)
|
|
return m_JoinCooldown;
|
|
else if (type == RequestType.QuickJoin)
|
|
return m_QuickJoinCooldown;
|
|
else if (type == RequestType.Host)
|
|
return m_CreateCooldown;
|
|
return m_QueryCooldown;
|
|
}
|
|
|
|
// Rate Limits are posted here: https://docs.unity.com/lobby/rate-limits.html
|
|
|
|
ServiceRateLimiter m_QueryCooldown = new ServiceRateLimiter(1, 1f);
|
|
ServiceRateLimiter m_CreateCooldown = new ServiceRateLimiter(2, 6f);
|
|
ServiceRateLimiter m_JoinCooldown = new ServiceRateLimiter(2, 6f);
|
|
ServiceRateLimiter m_QuickJoinCooldown = new ServiceRateLimiter(1, 10f);
|
|
ServiceRateLimiter m_GetLobbyCooldown = new ServiceRateLimiter(1, 1f);
|
|
ServiceRateLimiter m_DeleteLobbyCooldown = new ServiceRateLimiter(2, 1f);
|
|
ServiceRateLimiter m_UpdateLobbyCooldown = new ServiceRateLimiter(5, 5f);
|
|
ServiceRateLimiter m_UpdatePlayerCooldown = new ServiceRateLimiter(5, 5f);
|
|
ServiceRateLimiter m_LeaveLobbyOrRemovePlayer = new ServiceRateLimiter(5, 1);
|
|
ServiceRateLimiter m_HeartBeatCooldown = new ServiceRateLimiter(5, 30);
|
|
|
|
#endregion
|
|
|
|
Dictionary<string, PlayerDataObject> CreateInitialPlayerData(LocalPlayer user)
|
|
{
|
|
Dictionary<string, PlayerDataObject> data = new Dictionary<string, PlayerDataObject>();
|
|
|
|
var displayNameObject =
|
|
new PlayerDataObject(PlayerDataObject.VisibilityOptions.Member, user.DisplayName.Value);
|
|
data.Add("DisplayName", displayNameObject);
|
|
return data;
|
|
}
|
|
|
|
public async Task<Lobby> CreateLobbyAsync(string lobbyName, int maxPlayers, bool isPrivate,
|
|
LocalPlayer localUser)
|
|
{
|
|
if (m_CreateCooldown.IsCoolingDown)
|
|
{
|
|
Debug.LogWarning("Create Lobby hit the rate limit.");
|
|
return null;
|
|
}
|
|
|
|
await m_CreateCooldown.QueueUntilCooldown();
|
|
|
|
try
|
|
{
|
|
string uasId = AuthenticationService.Instance.PlayerId;
|
|
|
|
CreateLobbyOptions createOptions = new CreateLobbyOptions
|
|
{
|
|
IsPrivate = isPrivate,
|
|
Player = new Player(id: uasId, data: CreateInitialPlayerData(localUser))
|
|
};
|
|
m_CurrentLobby = await LobbyService.Instance.CreateLobbyAsync(lobbyName, maxPlayers, createOptions);
|
|
StartHeartBeat();
|
|
|
|
return m_CurrentLobby;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.LogError($"Lobby Create failed:\n{ex}");
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public async Task<Lobby> JoinLobbyAsync(string lobbyId, string lobbyCode, LocalPlayer localUser)
|
|
{
|
|
if (m_JoinCooldown.IsCoolingDown ||
|
|
(lobbyId == null && lobbyCode == null))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
await m_JoinCooldown.QueueUntilCooldown();
|
|
|
|
string uasId = AuthenticationService.Instance.PlayerId;
|
|
var playerData = CreateInitialPlayerData(localUser);
|
|
|
|
if (!string.IsNullOrEmpty(lobbyId))
|
|
{
|
|
JoinLobbyByIdOptions joinOptions = new JoinLobbyByIdOptions
|
|
{ Player = new Player(id: uasId, data: playerData) };
|
|
m_CurrentLobby = await LobbyService.Instance.JoinLobbyByIdAsync(lobbyId, joinOptions);
|
|
}
|
|
else
|
|
{
|
|
JoinLobbyByCodeOptions joinOptions = new JoinLobbyByCodeOptions
|
|
{ Player = new Player(id: uasId, data: playerData) };
|
|
m_CurrentLobby = await LobbyService.Instance.JoinLobbyByCodeAsync(lobbyCode, joinOptions);
|
|
}
|
|
|
|
return m_CurrentLobby;
|
|
}
|
|
|
|
public async Task<Lobby> QuickJoinLobbyAsync(LocalPlayer localUser, LobbyColor limitToColor = LobbyColor.None)
|
|
{
|
|
//We dont want to queue a quickjoin
|
|
if (m_QuickJoinCooldown.IsCoolingDown)
|
|
{
|
|
UnityEngine.Debug.LogWarning("Quick Join Lobby hit the rate limit.");
|
|
return null;
|
|
}
|
|
|
|
await m_QuickJoinCooldown.QueueUntilCooldown();
|
|
var filters = LobbyColorToFilters(limitToColor);
|
|
string uasId = AuthenticationService.Instance.PlayerId;
|
|
|
|
var joinRequest = new QuickJoinLobbyOptions
|
|
{
|
|
Filter = filters,
|
|
Player = new Player(id: uasId, data: CreateInitialPlayerData(localUser))
|
|
};
|
|
|
|
return m_CurrentLobby = await LobbyService.Instance.QuickJoinLobbyAsync(joinRequest);
|
|
}
|
|
|
|
public async Task<QueryResponse> RetrieveLobbyListAsync(LobbyColor limitToColor = LobbyColor.None)
|
|
{
|
|
var filters = LobbyColorToFilters(limitToColor);
|
|
|
|
if (m_QueryCooldown.TaskQueued)
|
|
return null;
|
|
await m_QueryCooldown.QueueUntilCooldown();
|
|
|
|
QueryLobbiesOptions queryOptions = new QueryLobbiesOptions
|
|
{
|
|
Count = k_maxLobbiesToShow,
|
|
Filters = filters
|
|
};
|
|
|
|
return await LobbyService.Instance.QueryLobbiesAsync(queryOptions);
|
|
}
|
|
|
|
public async Task BindLocalLobbyToRemote(string lobbyID, LocalLobby localLobby)
|
|
{
|
|
m_LobbyEventCallbacks.LobbyChanged += async changes =>
|
|
{
|
|
if (changes.LobbyDeleted)
|
|
{
|
|
await LeaveLobbyAsync();
|
|
return;
|
|
}
|
|
|
|
//Lobby Fields
|
|
if (changes.Name.Changed)
|
|
localLobby.LobbyName.Value = changes.Name.Value;
|
|
if (changes.HostId.Changed)
|
|
localLobby.HostID.Value = changes.HostId.Value;
|
|
if (changes.IsPrivate.Changed)
|
|
localLobby.Private.Value = changes.IsPrivate.Value;
|
|
if (changes.IsLocked.Changed)
|
|
localLobby.Locked.Value = changes.IsLocked.Value;
|
|
if (changes.AvailableSlots.Changed)
|
|
localLobby.AvailableSlots.Value = changes.AvailableSlots.Value;
|
|
if (changes.MaxPlayers.Changed)
|
|
localLobby.MaxPlayerCount.Value = changes.MaxPlayers.Value;
|
|
|
|
if (changes.LastUpdated.Changed)
|
|
localLobby.LastUpdated.Value = changes.LastUpdated.Value.ToFileTimeUtc();
|
|
|
|
//Custom Lobby Fields
|
|
if (changes.Data.Changed)
|
|
LobbyChanged();
|
|
|
|
if (changes.PlayerJoined.Changed)
|
|
PlayersJoined();
|
|
|
|
if (changes.PlayerLeft.Changed)
|
|
PlayersLeft();
|
|
|
|
if (changes.PlayerData.Changed)
|
|
PlayerDataChanged();
|
|
|
|
void LobbyChanged()
|
|
{
|
|
foreach (var change in changes.Data.Value)
|
|
{
|
|
var changedValue = change.Value;
|
|
var changedKey = change.Key;
|
|
|
|
if (changedValue.Removed)
|
|
{
|
|
RemoveCustomLobbyData(changedKey);
|
|
}
|
|
|
|
if (changedValue.Changed)
|
|
{
|
|
ParseCustomLobbyData(changedKey, changedValue.Value);
|
|
}
|
|
}
|
|
|
|
void RemoveCustomLobbyData(string changedKey)
|
|
{
|
|
if (changedKey == key_RelayCode)
|
|
localLobby.RelayCode.Value = "";
|
|
}
|
|
|
|
void ParseCustomLobbyData(string changedKey, DataObject playerDataObject)
|
|
{
|
|
if (changedKey == key_RelayCode)
|
|
localLobby.RelayCode.Value = playerDataObject.Value;
|
|
|
|
if (changedKey == key_LobbyState)
|
|
localLobby.LocalLobbyState.Value = (LobbyState)int.Parse(playerDataObject.Value);
|
|
|
|
if (changedKey == key_LobbyColor)
|
|
localLobby.LocalLobbyColor.Value = (LobbyColor)int.Parse(playerDataObject.Value);
|
|
}
|
|
}
|
|
|
|
void PlayersJoined()
|
|
{
|
|
foreach (var playerChanges in changes.PlayerJoined.Value)
|
|
{
|
|
Player joinedPlayer = playerChanges.Player;
|
|
|
|
var id = joinedPlayer.Id;
|
|
var index = playerChanges.PlayerIndex;
|
|
var isHost = localLobby.HostID.Value == id;
|
|
|
|
var newPlayer = new LocalPlayer(id, index, isHost);
|
|
|
|
foreach (var dataEntry in joinedPlayer.Data)
|
|
{
|
|
var dataObject = dataEntry.Value;
|
|
ParseCustomPlayerData(newPlayer, dataEntry.Key, dataObject.Value);
|
|
}
|
|
|
|
localLobby.AddPlayer(index, newPlayer);
|
|
}
|
|
}
|
|
|
|
void PlayersLeft()
|
|
{
|
|
foreach (var leftPlayerIndex in changes.PlayerLeft.Value)
|
|
{
|
|
localLobby.RemovePlayer(leftPlayerIndex);
|
|
}
|
|
}
|
|
|
|
void PlayerDataChanged()
|
|
{
|
|
foreach (var lobbyPlayerChanges in changes.PlayerData.Value)
|
|
{
|
|
var playerIndex = lobbyPlayerChanges.Key;
|
|
var localPlayer = localLobby.GetLocalPlayer(playerIndex);
|
|
if (localPlayer == null)
|
|
continue;
|
|
var playerChanges = lobbyPlayerChanges.Value;
|
|
if (playerChanges.ConnectionInfoChanged.Changed)
|
|
{
|
|
var connectionInfo = playerChanges.ConnectionInfoChanged.Value;
|
|
Debug.Log(
|
|
$"ConnectionInfo for player {playerIndex} changed to {connectionInfo}");
|
|
}
|
|
|
|
if (playerChanges.LastUpdatedChanged.Changed) { }
|
|
|
|
//There are changes on the Player
|
|
if (playerChanges.ChangedData.Changed)
|
|
{
|
|
foreach (var playerChange in playerChanges.ChangedData.Value)
|
|
{
|
|
var changedValue = playerChange.Value;
|
|
|
|
//There are changes on some of the changes in the player list of changes
|
|
|
|
if (changedValue.Changed)
|
|
{
|
|
if (changedValue.Removed)
|
|
{
|
|
Debug.LogWarning("This Sample does not remove Player Values currently.");
|
|
continue;
|
|
}
|
|
|
|
var playerDataObject = changedValue.Value;
|
|
ParseCustomPlayerData(localPlayer, playerChange.Key, playerDataObject.Value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
m_LobbyEventCallbacks.LobbyEventConnectionStateChanged += lobbyEventConnectionState =>
|
|
{
|
|
Debug.Log($"Lobby ConnectionState Changed to {lobbyEventConnectionState}");
|
|
};
|
|
|
|
m_LobbyEventCallbacks.KickedFromLobby += () =>
|
|
{
|
|
Debug.Log("Left Lobby");
|
|
Dispose();
|
|
};
|
|
await LobbyService.Instance.SubscribeToLobbyEventsAsync(lobbyID, m_LobbyEventCallbacks);
|
|
}
|
|
|
|
void ParseCustomPlayerData(LocalPlayer player, string dataKey, string playerDataValue)
|
|
{
|
|
if (dataKey == key_Emote)
|
|
player.Emote.Value = (EmoteType)int.Parse(playerDataValue);
|
|
else if (dataKey == key_Userstatus)
|
|
player.UserStatus.Value = (PlayerStatus)int.Parse(playerDataValue);
|
|
else if (dataKey == key_Displayname)
|
|
player.DisplayName.Value = playerDataValue;
|
|
}
|
|
|
|
public async Task<Lobby> GetLobbyAsync(string lobbyId = null)
|
|
{
|
|
if (!InLobby())
|
|
return null;
|
|
await m_GetLobbyCooldown.QueueUntilCooldown();
|
|
lobbyId ??= m_CurrentLobby.Id;
|
|
return m_CurrentLobby = await LobbyService.Instance.GetLobbyAsync(lobbyId);
|
|
}
|
|
|
|
public async Task LeaveLobbyAsync()
|
|
{
|
|
await m_LeaveLobbyOrRemovePlayer.QueueUntilCooldown();
|
|
if (!InLobby())
|
|
return;
|
|
string playerId = AuthenticationService.Instance.PlayerId;
|
|
|
|
await LobbyService.Instance.RemovePlayerAsync(m_CurrentLobby.Id, playerId);
|
|
Dispose();
|
|
}
|
|
|
|
public async Task UpdatePlayerDataAsync(Dictionary<string, string> data)
|
|
{
|
|
if (!InLobby())
|
|
return;
|
|
|
|
string playerId = AuthenticationService.Instance.PlayerId;
|
|
Dictionary<string, PlayerDataObject> dataCurr = new Dictionary<string, PlayerDataObject>();
|
|
foreach (var dataNew in data)
|
|
{
|
|
PlayerDataObject dataObj = new PlayerDataObject(visibility: PlayerDataObject.VisibilityOptions.Member,
|
|
value: dataNew.Value);
|
|
if (dataCurr.ContainsKey(dataNew.Key))
|
|
dataCurr[dataNew.Key] = dataObj;
|
|
else
|
|
dataCurr.Add(dataNew.Key, dataObj);
|
|
}
|
|
|
|
if (m_UpdatePlayerCooldown.TaskQueued)
|
|
return;
|
|
await m_UpdatePlayerCooldown.QueueUntilCooldown();
|
|
|
|
UpdatePlayerOptions updateOptions = new UpdatePlayerOptions
|
|
{
|
|
Data = dataCurr,
|
|
AllocationId = null,
|
|
ConnectionInfo = null
|
|
};
|
|
m_CurrentLobby = await LobbyService.Instance.UpdatePlayerAsync(m_CurrentLobby.Id, playerId, updateOptions);
|
|
}
|
|
|
|
public async Task UpdatePlayerRelayInfoAsync(string lobbyID, string allocationId, string connectionInfo)
|
|
{
|
|
if (!InLobby())
|
|
return;
|
|
|
|
string playerId = AuthenticationService.Instance.PlayerId;
|
|
|
|
if (m_UpdatePlayerCooldown.TaskQueued)
|
|
return;
|
|
await m_UpdatePlayerCooldown.QueueUntilCooldown();
|
|
|
|
UpdatePlayerOptions updateOptions = new UpdatePlayerOptions
|
|
{
|
|
Data = new Dictionary<string, PlayerDataObject>(),
|
|
AllocationId = allocationId,
|
|
ConnectionInfo = connectionInfo
|
|
};
|
|
m_CurrentLobby = await LobbyService.Instance.UpdatePlayerAsync(lobbyID, playerId, updateOptions);
|
|
}
|
|
|
|
public async Task UpdateLobbyDataAsync(Dictionary<string, string> data)
|
|
{
|
|
if (!InLobby())
|
|
return;
|
|
|
|
Dictionary<string, DataObject> dataCurr = m_CurrentLobby.Data ?? new Dictionary<string, DataObject>();
|
|
|
|
var shouldLock = false;
|
|
foreach (var dataNew in data)
|
|
{
|
|
// Special case: We want to be able to filter on our color data, so we need to supply an arbitrary index to retrieve later. Uses N# for numerics, instead of S# for strings.
|
|
DataObject.IndexOptions index = dataNew.Key == "LocalLobbyColor" ? DataObject.IndexOptions.N1 : 0;
|
|
DataObject dataObj = new DataObject(DataObject.VisibilityOptions.Public, dataNew.Value,
|
|
index); // Public so that when we request the list of lobbies, we can get info about them for filtering.
|
|
if (dataCurr.ContainsKey(dataNew.Key))
|
|
dataCurr[dataNew.Key] = dataObj;
|
|
else
|
|
dataCurr.Add(dataNew.Key, dataObj);
|
|
|
|
//Special Use: Get the state of the Local lobby so we can lock it from appearing in queries if it's not in the "Lobby" LocalLobbyState
|
|
if (dataNew.Key == "LocalLobbyState")
|
|
{
|
|
Enum.TryParse(dataNew.Value, out LobbyState lobbyState);
|
|
shouldLock = lobbyState != LobbyState.Lobby;
|
|
}
|
|
}
|
|
|
|
//We can still update the latest data to send to the service, but we will not send multiple UpdateLobbySyncCalls
|
|
if (m_UpdateLobbyCooldown.TaskQueued)
|
|
return;
|
|
await m_UpdateLobbyCooldown.QueueUntilCooldown();
|
|
|
|
UpdateLobbyOptions updateOptions = new UpdateLobbyOptions { Data = dataCurr, IsLocked = shouldLock };
|
|
m_CurrentLobby = await LobbyService.Instance.UpdateLobbyAsync(m_CurrentLobby.Id, updateOptions);
|
|
}
|
|
|
|
public async Task DeleteLobbyAsync()
|
|
{
|
|
if (!InLobby())
|
|
return;
|
|
await m_DeleteLobbyCooldown.QueueUntilCooldown();
|
|
|
|
await LobbyService.Instance.DeleteLobbyAsync(m_CurrentLobby.Id);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
m_CurrentLobby = null;
|
|
m_LobbyEventCallbacks = new LobbyEventCallbacks();
|
|
}
|
|
|
|
#region HeartBeat
|
|
|
|
List<QueryFilter> LobbyColorToFilters(LobbyColor limitToColor)
|
|
{
|
|
List<QueryFilter> filters = new List<QueryFilter>();
|
|
if (limitToColor == LobbyColor.Orange)
|
|
filters.Add(new QueryFilter(QueryFilter.FieldOptions.N1, ((int)LobbyColor.Orange).ToString(),
|
|
QueryFilter.OpOptions.EQ));
|
|
else if (limitToColor == LobbyColor.Green)
|
|
filters.Add(new QueryFilter(QueryFilter.FieldOptions.N1, ((int)LobbyColor.Green).ToString(),
|
|
QueryFilter.OpOptions.EQ));
|
|
else if (limitToColor == LobbyColor.Blue)
|
|
filters.Add(new QueryFilter(QueryFilter.FieldOptions.N1, ((int)LobbyColor.Blue).ToString(),
|
|
QueryFilter.OpOptions.EQ));
|
|
return filters;
|
|
}
|
|
|
|
//Since the LobbyManager maintains the "connection" to the lobby, we will continue to heartbeat until host leaves.
|
|
async Task SendHeartbeatPingAsync()
|
|
{
|
|
if (!InLobby())
|
|
return;
|
|
if (m_HeartBeatCooldown.IsCoolingDown)
|
|
return;
|
|
await m_HeartBeatCooldown.QueueUntilCooldown();
|
|
|
|
await LobbyService.Instance.SendHeartbeatPingAsync(m_CurrentLobby.Id);
|
|
}
|
|
|
|
void StartHeartBeat()
|
|
{
|
|
#pragma warning disable 4014
|
|
m_HeartBeatTask = HeartBeatLoop();
|
|
#pragma warning restore 4014
|
|
}
|
|
|
|
async Task HeartBeatLoop()
|
|
{
|
|
while (m_CurrentLobby != null)
|
|
{
|
|
await SendHeartbeatPingAsync();
|
|
await Task.Delay(8000);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
|
|
//Manages the Amount of times you can hit a service call.
|
|
//Adds a buffer to account for ping times.
|
|
//Will Queue the latest overflow task for when the cooldown ends.
|
|
//Created to mimic the way rate limits are implemented Here: https://docs.unity.com/lobby/rate-limits.html
|
|
public class ServiceRateLimiter
|
|
{
|
|
public Action<bool> onCooldownChange;
|
|
public readonly int coolDownMS;
|
|
public bool TaskQueued { get; private set; } = false;
|
|
|
|
readonly int m_ServiceCallTimes;
|
|
bool m_CoolingDown = false;
|
|
int m_TaskCounter;
|
|
|
|
//(If you're still getting rate limit errors, try increasing the pingBuffer)
|
|
public ServiceRateLimiter(int callTimes, float coolDown, int pingBuffer = 100)
|
|
{
|
|
m_ServiceCallTimes = callTimes;
|
|
m_TaskCounter = m_ServiceCallTimes;
|
|
coolDownMS =
|
|
Mathf.CeilToInt(coolDown * 1000) +
|
|
pingBuffer;
|
|
}
|
|
|
|
public async Task QueueUntilCooldown()
|
|
{
|
|
if (!m_CoolingDown)
|
|
{
|
|
#pragma warning disable 4014
|
|
ParallelCooldownAsync();
|
|
#pragma warning restore 4014
|
|
}
|
|
|
|
m_TaskCounter--;
|
|
|
|
if (m_TaskCounter > 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!TaskQueued)
|
|
TaskQueued = true;
|
|
else
|
|
return;
|
|
|
|
while (m_CoolingDown)
|
|
{
|
|
await Task.Delay(10);
|
|
}
|
|
}
|
|
|
|
async Task ParallelCooldownAsync()
|
|
{
|
|
IsCoolingDown = true;
|
|
await Task.Delay(coolDownMS);
|
|
IsCoolingDown = false;
|
|
TaskQueued = false;
|
|
m_TaskCounter = m_ServiceCallTimes;
|
|
}
|
|
|
|
public bool IsCoolingDown
|
|
{
|
|
get => m_CoolingDown;
|
|
private set
|
|
{
|
|
if (m_CoolingDown != value)
|
|
{
|
|
m_CoolingDown = value;
|
|
onCooldownChange?.Invoke(m_CoolingDown);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|