Skip to content
Open
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
7 changes: 7 additions & 0 deletions client/Packages/com.beamable/Common/Runtime/Api/IBeamState.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Beamable.Common.Api
{
public interface IBeamState
{
Promise<Unit> OnPlayerReady { get; }
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -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<EmptyResponse> SendHeartbeat()
Expand Down Expand Up @@ -59,25 +61,30 @@ public Promise<MultiplePlayersStatus> GetManyStatuses(params string[] playerIds)
}

public bool ConnectivityCheckingEnabled { get; set; }
public Promise<bool> ForceCheck()
public async Promise<bool> 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,
* then we should include the pre-check.
*/
var useConnectivityPreCheck = !ConnectivityCheckingEnabled;

return _requester.BeamableRequest(new SDKRequesterOptions<EmptyResponse>
{
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<EmptyResponse>
{
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;
}
}

Expand Down
3 changes: 2 additions & 1 deletion client/Packages/com.beamable/Runtime/Beam.cs
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,8 @@ static Beam()
DependencyBuilder.AddScoped<IPresenceApi>(provider => new PresenceService(
// the presence service needs a special instance of the beamable api requester
provider.GetService<IBeamableApiRequester>(),
provider.GetService<IUserContext>()));
provider.GetService<IUserContext>(),
provider.GetService<IBeamState>()));
DependencyBuilder.AddSingleton<AnalyticsTracker>(provider =>
new AnalyticsTracker(
provider.GetService<IPlatformService>(),
Expand Down
15 changes: 12 additions & 3 deletions client/Packages/com.beamable/Runtime/BeamContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ public interface IObservedPlayer : IUserContext
/// </para>
/// </summary>
[Serializable]
public class BeamContext : IPlatformService, IGameObjectContext, IObservedPlayer, IBeamableDisposableOrder, IDependencyNameProvider, IDependencyScopeNameProvider
public class BeamContext : IPlatformService, IGameObjectContext, IObservedPlayer, IBeamableDisposableOrder, IDependencyNameProvider, IDependencyScopeNameProvider, IBeamState
{
#region Internal State
/// <summary>
Expand Down Expand Up @@ -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<BeamContext> _children = new HashSet<BeamContext>();

Expand Down Expand Up @@ -330,6 +331,10 @@ public static BeamContext CreateAuthorizedContext(string playerCode, TokenRespon
/// <returns>The same instance, but now mutated for the new authorized user</returns>
public async Promise<BeamContext> ChangeAuthorizedPlayer(TokenResponse token)
{
var promiseRef = _playerReadyPromise;
_playerReadyPromise = new Promise();
var _ =_playerReadyPromise.Merge(promiseRef);

if (AuthorizedUser != null)
{
OnUserLoggingOut?.Invoke(AuthorizedUser);
Expand All @@ -346,7 +351,7 @@ public async Promise<BeamContext> ChangeAuthorizedPlayer(TokenResponse token)
await Accounts.Refresh();
}
OnReloadUser?.Invoke();

return ctx;
}

Expand Down Expand Up @@ -510,6 +515,7 @@ protected virtual void RegisterServices(IDependencyBuilder builder)
builder.AddSingleton<IBeamableAPI>(provider => Api);
builder.AddSingleton<BeamContext>(this);
builder.AddSingleton<IPlatformService>(this);
builder.AddSingleton<IBeamState>(this);
builder.AddSingleton<IGameObjectContext>(this);
builder.AddScoped<IDependencyScopeNameProvider>(this);
builder.AddSingleton<IDependencyNameProvider>(this);
Expand Down Expand Up @@ -644,6 +650,7 @@ async Promise SetupGetUser()
{
var user = await _authService.GetUser();
AuthorizedUser.Value = user;
_playerReadyPromise.CompleteSuccess();
}

async Promise SetupNewSession()
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -881,6 +888,8 @@ public void ChangeTime()
User IPlatformService.User => AuthorizedUser.Value;
public Promise<Unit> OnReady => _initPromise;

Promise<Unit> IBeamState.OnPlayerReady => _playerReadyPromise;

/// <summary>
/// A <see cref="Promise{T}"/> that is completed when the <see cref="BeamContext"/> is ready to be used.
/// This promise happens immediately after the <see cref="OnReady"/> promise, but returns the current instance
Expand Down
20 changes: 20 additions & 0 deletions wiki/features/BEAM-3815.md
Original file line number Diff line number Diff line change
@@ -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)