From 67a1e66c954fa2abd52d7cee4c15e09a347a3657 Mon Sep 17 00:00:00 2001 From: Gabriel Ramos Date: Mon, 23 Oct 2023 10:32:41 -0300 Subject: [PATCH] Attempt to fix issue with heartbeat erroring while users are being changed --- .../Common/Runtime/Api/IBeamState.cs | 7 +++++ .../Common/Runtime/Api/IBeamState.cs.meta | 3 ++ .../Runtime/Api/Presence/PresenceService.cs | 31 ++++++++++++------- client/Packages/com.beamable/Runtime/Beam.cs | 3 +- .../com.beamable/Runtime/BeamContext.cs | 15 +++++++-- wiki/features/BEAM-3815.md | 20 ++++++++++++ 6 files changed, 63 insertions(+), 16 deletions(-) create mode 100644 client/Packages/com.beamable/Common/Runtime/Api/IBeamState.cs create mode 100644 client/Packages/com.beamable/Common/Runtime/Api/IBeamState.cs.meta create mode 100644 wiki/features/BEAM-3815.md diff --git a/client/Packages/com.beamable/Common/Runtime/Api/IBeamState.cs b/client/Packages/com.beamable/Common/Runtime/Api/IBeamState.cs new file mode 100644 index 0000000000..9146381246 --- /dev/null +++ b/client/Packages/com.beamable/Common/Runtime/Api/IBeamState.cs @@ -0,0 +1,7 @@ +namespace Beamable.Common.Api +{ + public interface IBeamState + { + Promise OnPlayerReady { get; } + } +} diff --git a/client/Packages/com.beamable/Common/Runtime/Api/IBeamState.cs.meta b/client/Packages/com.beamable/Common/Runtime/Api/IBeamState.cs.meta new file mode 100644 index 0000000000..c59df25128 --- /dev/null +++ b/client/Packages/com.beamable/Common/Runtime/Api/IBeamState.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 6f9f77a39b564fd2a7e2fb41ad9fe16b +timeCreated: 1697826728 \ No newline at end of file diff --git a/client/Packages/com.beamable/Common/Runtime/Api/Presence/PresenceService.cs b/client/Packages/com.beamable/Common/Runtime/Api/Presence/PresenceService.cs index 6a3a990348..9e6d0b0bb7 100644 --- a/client/Packages/com.beamable/Common/Runtime/Api/Presence/PresenceService.cs +++ b/client/Packages/com.beamable/Common/Runtime/Api/Presence/PresenceService.cs @@ -9,11 +9,13 @@ public class PresenceService : IPresenceApi { private readonly IRequester _requester; private readonly IUserContext _userContext; + private readonly IBeamState _beamState; - public PresenceService(IRequester requester, IUserContext userContext) + public PresenceService(IRequester requester, IUserContext userContext, IBeamState beamState) { _requester = requester; _userContext = userContext; + _beamState = beamState; } public Promise SendHeartbeat() @@ -59,8 +61,10 @@ public Promise GetManyStatuses(params string[] playerIds) } public bool ConnectivityCheckingEnabled { get; set; } - public Promise ForceCheck() + public async Promise ForceCheck() { + await _beamState.OnPlayerReady; + /* * if the ConnectivityCheckingEnabled is enabled, then we DON'T want the request * to include the pre-check. But if the ConnectivityCheckingEnabled is disabled, @@ -68,16 +72,19 @@ public Promise ForceCheck() */ var useConnectivityPreCheck = !ConnectivityCheckingEnabled; - return _requester.BeamableRequest(new SDKRequesterOptions - { - method = Method.PUT, - uri = $"/players/{_userContext.UserId}/presence", - includeAuthHeader = true, - useConnectivityPreCheck = - useConnectivityPreCheck // the magic sauce to allow this to ignore the connectivity - }) - .Map(_ => true) - .RecoverFromNoConnectivity(() => false); // if no connection happens, that is fine, just carry on. + var response = await _requester.BeamableRequest(new SDKRequesterOptions + { + method = Method.PUT, + uri = $"/players/{_userContext.UserId}/presence", + includeAuthHeader = true, + useConnectivityPreCheck = + useConnectivityPreCheck // the magic sauce to allow this to ignore the connectivity + }) + .Map(_ => true) + .RecoverFromNoConnectivity( + () => + false); // if no connection happens, that is fine, just carry on. + return response; } } diff --git a/client/Packages/com.beamable/Runtime/Beam.cs b/client/Packages/com.beamable/Runtime/Beam.cs index fa6e06a5d0..71ddd3c1c4 100644 --- a/client/Packages/com.beamable/Runtime/Beam.cs +++ b/client/Packages/com.beamable/Runtime/Beam.cs @@ -209,7 +209,8 @@ static Beam() DependencyBuilder.AddScoped(provider => new PresenceService( // the presence service needs a special instance of the beamable api requester provider.GetService(), - provider.GetService())); + provider.GetService(), + provider.GetService())); DependencyBuilder.AddSingleton(provider => new AnalyticsTracker( provider.GetService(), diff --git a/client/Packages/com.beamable/Runtime/BeamContext.cs b/client/Packages/com.beamable/Runtime/BeamContext.cs index ef8ae1fec6..ef95b48ce3 100644 --- a/client/Packages/com.beamable/Runtime/BeamContext.cs +++ b/client/Packages/com.beamable/Runtime/BeamContext.cs @@ -72,7 +72,7 @@ public interface IObservedPlayer : IUserContext /// /// [Serializable] - public class BeamContext : IPlatformService, IGameObjectContext, IObservedPlayer, IBeamableDisposableOrder, IDependencyNameProvider, IDependencyScopeNameProvider + public class BeamContext : IPlatformService, IGameObjectContext, IObservedPlayer, IBeamableDisposableOrder, IDependencyNameProvider, IDependencyScopeNameProvider, IBeamState { #region Internal State /// @@ -120,6 +120,7 @@ public class BeamContext : IPlatformService, IGameObjectContext, IObservedPlayer private bool _isStopped; private GameObject _gob; private Promise _initPromise; + private Promise _playerReadyPromise = new Promise(); private BeamContext _parent; private HashSet _children = new HashSet(); @@ -330,6 +331,10 @@ public static BeamContext CreateAuthorizedContext(string playerCode, TokenRespon /// The same instance, but now mutated for the new authorized user public async Promise ChangeAuthorizedPlayer(TokenResponse token) { + var promiseRef = _playerReadyPromise; + _playerReadyPromise = new Promise(); + var _ =_playerReadyPromise.Merge(promiseRef); + if (AuthorizedUser != null) { OnUserLoggingOut?.Invoke(AuthorizedUser); @@ -346,7 +351,7 @@ public async Promise ChangeAuthorizedPlayer(TokenResponse token) await Accounts.Refresh(); } OnReloadUser?.Invoke(); - + return ctx; } @@ -510,6 +515,7 @@ protected virtual void RegisterServices(IDependencyBuilder builder) builder.AddSingleton(provider => Api); builder.AddSingleton(this); builder.AddSingleton(this); + builder.AddSingleton(this); builder.AddSingleton(this); builder.AddScoped(this); builder.AddSingleton(this); @@ -644,6 +650,7 @@ async Promise SetupGetUser() { var user = await _authService.GetUser(); AuthorizedUser.Value = user; + _playerReadyPromise.CompleteSuccess(); } async Promise SetupNewSession() @@ -727,7 +734,7 @@ async Promise SetupWithConnection() var rsp = await _authService.LoginRefreshToken(AccessToken.RefreshToken); await SaveToken(rsp); } - + await SetupGetUser(); var connection = SetupBeamableNotificationChannel(); var session = SetupNewSession(); @@ -881,6 +888,8 @@ public void ChangeTime() User IPlatformService.User => AuthorizedUser.Value; public Promise OnReady => _initPromise; + Promise IBeamState.OnPlayerReady => _playerReadyPromise; + /// /// A that is completed when the is ready to be used. /// This promise happens immediately after the promise, but returns the current instance diff --git a/wiki/features/BEAM-3815.md b/wiki/features/BEAM-3815.md new file mode 100644 index 0000000000..855f60c1c0 --- /dev/null +++ b/wiki/features/BEAM-3815.md @@ -0,0 +1,20 @@ +### Why +If you change users fast enough and while the SDK is sending a HeartBeat to the server, it's possible to get +an error where it complains that the user being sent by the HeartBeat is not the actual user. + +### Configuration +none + +### How +Now we are waiting for an user change to happen before sending the heartbeat. The error still might occur in +the case that, for some weird reason not found yet, the heartbeat was send before the user start changing, and +somehow the change happens before the heartbeat finishs. + +### Prefab +none + +### Editor +none + +### Notes +(Insert anything else that is important) \ No newline at end of file