Skip to content

WIP fix: fixing rate limit client side issues. Reenabling async/await. #501

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 19 additions & 13 deletions Assets/BossRoom/Scripts/Client/UI/Lobby/LobbyUIMediator.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
using System;
using TMPro;
using Unity.Multiplayer.Samples.BossRoom.Client;
using Unity.Multiplayer.Samples.BossRoom.Shared.Infrastructure;
using Unity.Multiplayer.Samples.BossRoom.Shared.Net.UnityServices.Infrastructure;
using Unity.Multiplayer.Samples.BossRoom.Shared.Net.UnityServices.Lobbies;
using Unity.Services.Lobbies;
using Unity.Services.Lobbies.Models;
using UnityEngine;

Expand Down Expand Up @@ -70,26 +72,30 @@ public void CreateLobbyRequest(string lobbyName, bool isPrivate, int maxPlayers,
BlockUIWhileLoadingIsInProgress();
}

public void QueryLobbiesRequest(bool blockUI)
public async void QueryLobbiesRequest(bool blockUI)
{
m_LobbyServiceFacade.RetrieveLobbyListAsync(
OnSuccess,
OnFailure
);

if (blockUI)
try
{
BlockUIWhileLoadingIsInProgress();
}
if (blockUI)
{
BlockUIWhileLoadingIsInProgress();
}

void OnSuccess(QueryResponse qr)
{
await m_LobbyServiceFacade.RetrieveAndPublishLobbyListAsync();
UnblockUIAfterLoadingIsComplete();
}

void OnFailure()
catch (LobbyServiceException e)
{
if (e.Reason != LobbyExceptionReason.RateLimited)
{
// let rate limiting handle this in above code
UnblockUIAfterLoadingIsComplete();
}
}
catch (Exception)
{
UnblockUIAfterLoadingIsComplete();
throw;
}
}

Expand Down
15 changes: 6 additions & 9 deletions Assets/BossRoom/Scripts/Shared/Infrastructure/UpdateRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public class UpdateRunner : MonoBehaviour
class SubscriberData
{
public float Period;
public float PeriodCurrent;
public float NextCallTime;
}

readonly Queue<Action> m_PendingHandlers = new Queue<Action>();
Expand All @@ -31,7 +31,7 @@ public void OnDestroy()
/// Subscribe in order to have onUpdate called approximately every period seconds (or every frame, if period <= 0).
/// Don't assume that onUpdate will be called in any particular order compared to other subscribers.
/// </summary>
public void Subscribe(Action<float> onUpdate, float period)
public void Subscribe(Action<float> onUpdate, float updatePeriod)
{
if (onUpdate == null)
{
Expand All @@ -55,7 +55,7 @@ public void Subscribe(Action<float> onUpdate, float period)
m_PendingHandlers.Enqueue(() =>
{
m_Subscribers.Add(onUpdate);
m_SubscriberData.Add(onUpdate, new SubscriberData(){Period = period, PeriodCurrent = 0});
m_SubscriberData.Add(onUpdate, new SubscriberData() {Period = updatePeriod, NextCallTime = 0});
});
}
}
Expand All @@ -82,17 +82,14 @@ void Update()
m_PendingHandlers.Dequeue()?.Invoke();
}

float dt = Time.deltaTime;

foreach (var subscriber in m_Subscribers)
{
var subscriberData = m_SubscriberData[subscriber];
subscriberData.PeriodCurrent += dt;

if (subscriberData.PeriodCurrent > subscriberData.Period)
if (Time.time >= subscriberData.NextCallTime)
{
subscriber.Invoke(subscriberData.PeriodCurrent);
subscriberData.PeriodCurrent = 0;
subscriber.Invoke(Time.deltaTime);
subscriberData.NextCallTime = Time.time + subscriberData.Period;
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ void InjectDependencies(IPublisher<UnityServiceErrorMessage> unityServiceErrorMe

void OnServiceException(AuthenticationException e)
{
Debug.LogWarning(e.Message);
Debug.LogException(e);

var reason = $"{e.Message} ({e.InnerException?.Message})"; // Lobby error type, then HTTP error type.

Expand All @@ -30,7 +30,7 @@ void OnServiceException(AuthenticationException e)
public void DoSignInAsync(Action onSigninComplete, Action onFailed, InitializationOptions initializationOptions)
{
var task = TrySignIn(initializationOptions);
UnityServiceCallsTaskWrapper.RunTask<AuthenticationException>(task, onSigninComplete, onFailed, OnServiceException);
UnityServiceCallsTaskWrapper.RunTaskAsync<AuthenticationException>(task, onSigninComplete, onFailed, OnServiceException);
}

async Task TrySignIn(InitializationOptions initializationOptions)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,39 +1,42 @@
using System;
using System.Collections.Generic;
using Unity.Multiplayer.Samples.BossRoom.Shared.Infrastructure;
using UnityEngine;

namespace Unity.Multiplayer.Samples.BossRoom.Shared.Net.UnityServices.Infrastructure
{
public class RateLimitCooldown
{
float m_TimeSinceLastCall = float.MaxValue;
readonly float m_CooldownTime;
readonly float m_CooldownTimeLength;
private float m_CooldownFinishedTime;
readonly UpdateRunner m_UpdateRunner;
Queue<Action> m_PendingOperations = new Queue<Action>();

public void EnqueuePendingOperation(Action action)
public RateLimitCooldown(float cooldownTimeLength, UpdateRunner updateRunner)
{
m_PendingOperations.Enqueue(action);
m_CooldownTimeLength = cooldownTimeLength;
m_CooldownFinishedTime = 0f;
m_UpdateRunner = updateRunner;
}

public RateLimitCooldown(float cooldownTime, UpdateRunner updateRunner)
public void EnqueuePendingOperation(Action action)
{
m_CooldownTime = cooldownTime;
m_UpdateRunner = updateRunner;
m_PendingOperations.Enqueue(action);
}

public bool CanCall => m_TimeSinceLastCall >= m_CooldownTime;
public bool CanCall => Time.unscaledTime > m_CooldownFinishedTime;

public bool CanCallSam { get; set; } = true; // todo

public void PutOnCooldown()
{
m_UpdateRunner.Subscribe(OnUpdate, m_CooldownTime);
m_TimeSinceLastCall = 0;
m_CooldownFinishedTime = Time.unscaledTime + m_CooldownTimeLength;
m_UpdateRunner.Subscribe(OnUpdate, 1 / 10f);
}

// the below should disappear once the SDK handles automatic retries
void OnUpdate(float dt)
{
m_TimeSinceLastCall += dt;

if (CanCall)
{
m_UpdateRunner.Unsubscribe(OnUpdate);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace Unity.Multiplayer.Samples.BossRoom.Shared.Net.UnityServices.Infrastruc
/// </summary>
public static class UnityServiceCallsTaskWrapper
{
public static async void RunTask<TException>(Task task, Action onComplete, Action onFailed, Action<TException> onException) where TException : Exception
public static async void RunTaskAsync<TException>(Task task, Action onComplete, Action onFailed, Action<TException> onException) where TException : Exception
{
string currentTrace = Environment.StackTrace; // For debugging. If we don't get the calling context here, it's lost once the async operation begins.
try
Expand All @@ -20,7 +20,8 @@ public static async void RunTask<TException>(Task task, Action onComplete, Actio
catch (TException e)
{
onException?.Invoke(e);
Debug.LogWarning($"AsyncRequest threw an exception. Call stack before async call:\n{currentTrace}\n"); // Note that we log here instead of creating a new Exception in case of a change in calling context during the async call. E.g. Relay has its own exception handling that would intercept this call stack.
Debug.LogError($"AsyncRequest threw an exception. Call stack before async call:\n{currentTrace}\n"); // Note that we log here instead of creating a new Exception in case of a change in calling context during the async call. E.g. Relay has its own exception handling that would intercept this call stack.
throw e;
}
finally
{
Expand All @@ -35,7 +36,7 @@ public static async void RunTask<TException>(Task task, Action onComplete, Actio
}
}

public static async void RunTask<TResult, TException>(Task<TResult> task, Action<TResult> onComplete, Action onFailed, Action<TException> onException) where TException : Exception
public static async void RunTaskAsync<TResult, TException>(Task<TResult> task, Action<TResult> onComplete, Action onFailed, Action<TException> onException) where TException : Exception
{
string currentTrace = Environment.StackTrace; // For debugging. If we don't get the calling context here, it's lost once the async operation begins.
try
Expand All @@ -45,7 +46,8 @@ public static async void RunTask<TResult, TException>(Task<TResult> task, Action
catch (TException e)
{
onException?.Invoke(e);
Debug.LogWarning($"AsyncRequest threw an exception. Call stack before async call:\n{currentTrace}\n"); // Note that we log here instead of creating a new Exception in case of a change in calling context during the async call. E.g. Relay has its own exception handling that would intercept this call stack.
Debug.LogError($"AsyncRequest threw an exception. Call stack before async call:\n{currentTrace}\n"); // Note that we log here instead of creating a new Exception in case of a change in calling context during the async call. E.g. Relay has its own exception handling that would intercept this call stack.
throw e;
}
finally
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,20 +47,21 @@ public LobbyAPIInterface(IPublisher<UnityServiceErrorMessage> unityServiceErrorM

void RunTask(Task task, Action onComplete, Action onFailed)
{
UnityServiceCallsTaskWrapper.RunTask<LobbyServiceException>(task, onComplete, onFailed, OnServiceException);
UnityServiceCallsTaskWrapper.RunTaskAsync<LobbyServiceException>(task, onComplete, onFailed, OnServiceException);
}

void RunTask<T>(Task<T> task, Action<T> onComplete, Action onFailed)
{
UnityServiceCallsTaskWrapper.RunTask<T,LobbyServiceException>(task, onComplete, onFailed, OnServiceException);
UnityServiceCallsTaskWrapper.RunTaskAsync<T,LobbyServiceException>(task, onComplete, onFailed, OnServiceException);
}

void OnServiceException(LobbyServiceException e)
{
Debug.LogWarning(e.Message);
Debug.LogException(e);

if (e.Reason == LobbyExceptionReason.RateLimited) // We have other ways of preventing players from hitting the rate limit, so the developer-facing 429 error is sufficient here.
{
// todo trigger client side rate limit here
return;
}

Expand Down Expand Up @@ -119,7 +120,7 @@ public void LeaveLobbyAsync(string requesterUasId, string lobbyId, Action onComp
RunTask(task, onComplete, onFailed);
}

public void QueryAllLobbiesAsync(Action<QueryResponse> onComplete, Action onFailed)
public async Task<QueryResponse> QueryAllLobbiesAsync()
{
QueryLobbiesOptions queryOptions = new QueryLobbiesOptions
{
Expand All @@ -128,8 +129,15 @@ public void QueryAllLobbiesAsync(Action<QueryResponse> onComplete, Action onFail
Order = m_Order
};

var task = Lobbies.Instance.QueryLobbiesAsync(queryOptions);
RunTask(task, onComplete, onFailed);
try
{
return await Lobbies.Instance.QueryLobbiesAsync(queryOptions);
}
catch (LobbyServiceException serviceE)
{
OnServiceException(serviceE);
throw;
}
}

public void GetLobbyAsync(string lobbyId, Action<Lobby> onComplete, Action onFailed)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Unity.Multiplayer.Samples.BossRoom.Shared.Infrastructure;
using Unity.Multiplayer.Samples.BossRoom.Shared.Net.UnityServices.Infrastructure;
using Unity.Services.Authentication;
using Unity.Services.Lobbies;
using Unity.Services.Lobbies.Models;

namespace Unity.Multiplayer.Samples.BossRoom.Shared.Net.UnityServices.Lobbies
Expand Down Expand Up @@ -61,9 +63,9 @@ public LobbyServiceFacade(
m_JoinedLobbyContentHeartbeat = m_ServiceScope.Resolve<JoinedLobbyContentHeartbeat>();

m_RateLimitQuery = new RateLimitCooldown(1.5f, updateRunner);
m_RateLimitJoin = new RateLimitCooldown(3f, updateRunner);
m_RateLimitQuickJoin = new RateLimitCooldown(10f, updateRunner);
m_RateLimitHost = new RateLimitCooldown(3f, updateRunner);
m_RateLimitJoin = new RateLimitCooldown(0, updateRunner);
m_RateLimitQuickJoin = new RateLimitCooldown(0, updateRunner);
m_RateLimitHost = new RateLimitCooldown(0, updateRunner);
}

public void Dispose()
Expand Down Expand Up @@ -143,16 +145,24 @@ public void CreateLobbyAsync(string lobbyName, int maxPlayers, bool isPrivate, O
return;
}

m_RateLimitHost.PutOnCooldown();

var initialLobbyData = new Dictionary<string, DataObject>()
{
{"OnlineMode", new DataObject(DataObject.VisibilityOptions.Public, ((int)onlineMode).ToString())},
{"OnlineMode", new DataObject(DataObject.VisibilityOptions.Public, ((int) onlineMode).ToString())},
{"IP", new DataObject(DataObject.VisibilityOptions.Public, ip)},
{"Port", new DataObject(DataObject.VisibilityOptions.Public, port.ToString())},
{"Port", new DataObject(DataObject.VisibilityOptions.Public, port.ToString())},
};

m_LobbyApiInterface.CreateLobbyAsync(AuthenticationService.Instance.PlayerId, lobbyName, maxPlayers, isPrivate, m_LocalUser.GetDataForUnityServices(), initialLobbyData, onSuccess, onFailure);
try
{
m_LobbyApiInterface.CreateLobbyAsync(AuthenticationService.Instance.PlayerId, lobbyName, maxPlayers, isPrivate, m_LocalUser.GetDataForUnityServices(), initialLobbyData, onSuccess, onFailure);
}
catch (LobbyServiceException e)
{
if (e.Reason == LobbyExceptionReason.RateLimited)
{
m_RateLimitHost.PutOnCooldown();
}
}
}

/// <summary>
Expand Down Expand Up @@ -198,22 +208,28 @@ public void QuickJoinLobbyAsync(Action<Lobby> onSuccess, Action onFailure)
/// <summary>
/// Used for getting the list of all active lobbies, without needing full info for each.
/// </summary>
public void RetrieveLobbyListAsync(Action<QueryResponse> onSuccess, Action onFailure)
public async Task RetrieveAndPublishLobbyListAsync()
{
if (!m_RateLimitQuery.CanCall)
if (!m_RateLimitQuery.CanCallSam)
{
onFailure?.Invoke();
UnityEngine.Debug.LogWarning("Retrieve Lobby list hit the rate limit. Will try again soon...");
return;
}

m_RateLimitQuery.PutOnCooldown();
m_LobbyApiInterface.QueryAllLobbiesAsync(OnSuccess, onFailure);

void OnSuccess(QueryResponse qr)
try
{
onSuccess?.Invoke(qr);
m_LobbyListFetchedPub.Publish(new LobbyListFetchedMessage(LocalLobby.CreateLocalLobbies(qr)));
var response = await m_LobbyApiInterface.QueryAllLobbiesAsync();
m_LobbyListFetchedPub.Publish(new LobbyListFetchedMessage(LocalLobby.CreateLocalLobbies(response)));
}
catch (LobbyServiceException e)
{
if (e.Reason == LobbyExceptionReason.RateLimited)
{
// todo put in RateLimitQuery
m_RateLimitQuery.CanCallSam = false;
await Task.Delay(1500);
m_RateLimitQuery.CanCallSam = true;
}
}
}

Expand Down