Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
506368b
replacing polling for lobby updates with lobby events
LPLafontaineB Jan 16, 2023
817f52d
adding support for deleted lobby events
LPLafontaineB Jan 16, 2023
d3fe0bb
adding debug logs
LPLafontaineB Jan 16, 2023
877629a
updating to lobby 1.1.0-pre.1
LPLafontaineB Jan 17, 2023
5dc2c6d
fixing issue with UpdateRunner using wrong dt when updating subscribers
LPLafontaineB Jan 17, 2023
fa2b4ba
simplifying LobbyServiceFacade delete, leaveLobby and reconnect metho…
LPLafontaineB Jan 30, 2023
f9d5ad9
Simplifying reconnection
LPLafontaineB Jan 30, 2023
46323cc
Revert "Simplifying reconnection"
LPLafontaineB Jan 30, 2023
5b93167
Revert changes to connectionManagement (will be done in another PR)
LPLafontaineB Jan 30, 2023
c1e98b9
Adding early return in reconnection when lobby is deleted
LPLafontaineB Jan 30, 2023
1470e87
Revert "updating to lobby 1.1.0-pre.1"
LPLafontaineB Jan 31, 2023
ce713e7
updating to lobby 1.1.0-pre.2
LPLafontaineB Jan 31, 2023
1e4788c
Catching exception happening in editor when exiting play mode
LPLafontaineB Jan 31, 2023
3f55964
Adding changelog entry
LPLafontaineB Feb 6, 2023
dd21b31
Adding Index entry
LPLafontaineB Feb 6, 2023
210eaa1
target latest bokken images on yamato
LPLafontaineB Feb 6, 2023
9559c13
Revert "target latest bokken images on yamato"
LPLafontaineB Feb 7, 2023
021704a
Merge branch 'develop' into feature/lobby-events
LPLafontaineB Feb 17, 2023
e329b12
removing GetLobby unused method
LPLafontaineB Mar 1, 2023
3f0f934
Merge branch 'develop' into feature/lobby-events
LPLafontaineB Mar 1, 2023
1e97c05
trying a fix for the CLI issue on mobile
SamuelBellomo Mar 1, 2023
1071f0c
Merge branch 'develop' into feature/lobby-events
LPLafontaineB Mar 6, 2023
a733783
resolving conflicts
LPLafontaineB Mar 6, 2023
2b947e7
Adding short delay before first reconnect attempt to give time for lo…
LPLafontaineB Mar 6, 2023
0265f90
Added UnsubscribeToJoinedLobbyAsync method for symmetry
LPLafontaineB Mar 10, 2023
36bc9bd
Added clarifying comment about callbacks
LPLafontaineB Mar 10, 2023
a6551d3
rewording changelog entry
LPLafontaineB Mar 10, 2023
5530e7e
Merge branch 'feature/lobby-events' of https://github.com/Unity-Techn…
LPLafontaineB Mar 10, 2023
399cd0d
Added calculation example to changelog
LPLafontaineB Mar 10, 2023
a578d17
updating lobby package to latest version
LPLafontaineB Mar 10, 2023
a787389
Revert "updating lobby package to latest version"
LPLafontaineB Mar 20, 2023
153f704
Revert "Revert "updating lobby package to latest version""
LPLafontaineB Mar 20, 2023
68efccc
temp workaround for CI
LPLafontaineB Apr 11, 2023
04bb253
fixing bug with join code not being displayed
LPLafontaineB Apr 12, 2023
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
2 changes: 1 addition & 1 deletion .yamato/mobile-build-and-run.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Build_Player_With_Tests_iOS_{{ project.name }}_{{ editor }}:

commands:
- pip install unity-downloader-cli==1.2.0 --index-url https://artifactory.prd.it.unity3d.com/artifactory/api/pypi/pypi/simple --upgrade
- unity-downloader-cli -c Editor -c iOS -u {{ editor }} --fast --wait
- unity-downloader-cli -c Editor -c iOS -u 2021.3.15f1 --fast --wait
- curl -s https://artifactory.prd.it.unity3d.com/artifactory/unity-tools-local/utr-standalone/utr --output utr
- chmod +x ./utr
- ./utr --suite=playmode --platform=iOS --editor-location=.Editor --testproject={{ project.path }} --player-save-path=build/players --artifacts_path=build/logs --build-only --testfilter=Unity.BossRoom.Tests.Runtime
Expand Down
11 changes: 2 additions & 9 deletions Assets/Scripts/ConnectionManagement/ConnectionMethod.cs
Original file line number Diff line number Diff line change
Expand Up @@ -169,16 +169,9 @@ public override async Task SetupClientConnectionAsync()
// some time to attempt to reconnect (defined by the "Disconnect removal time" parameter on the dashboard),
// after which they will be removed from the lobby completely.
// See https://docs.unity.com/lobby/reconnect-to-lobby.html
var lobby = await m_LobbyServiceFacade.ReconnectToLobbyAsync(m_LocalLobby.LobbyID);
var lobby = await m_LobbyServiceFacade.ReconnectToLobbyAsync();
var success = lobby != null;
if (success)
{
Debug.Log("Successfully reconnected to Lobby.");
}
else
{
Debug.Log("Failed to reconnect to Lobby.");
}
Debug.Log(success ? "Successfully reconnected to Lobby." : "Failed to reconnect to Lobby.");
return (success, true); // return a success if reconnecting to lobby returns a lobby
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ class ClientReconnectingState : ClientConnectingState
Coroutine m_ReconnectCoroutine;
int m_NbAttempts;

const float k_TimeBeforeFirstAttempt = 1;
const float k_TimeBetweenAttempts = 5;

public override void Enter()
Expand Down Expand Up @@ -105,6 +106,15 @@ IEnumerator ReconnectCoroutine()
yield return new WaitWhile(() => m_ConnectionManager.NetworkManager.ShutdownInProgress); // wait until NetworkManager completes shutting down
Debug.Log($"Reconnecting attempt {m_NbAttempts + 1}/{m_ConnectionManager.NbReconnectAttempts}...");
m_ReconnectMessagePublisher.Publish(new ReconnectMessage(m_NbAttempts, m_ConnectionManager.NbReconnectAttempts));

// If first attempt, wait some time before attempting to reconnect to give time to services to update
// (i.e. if in a Lobby and the host shuts down unexpectedly, this will give enough time for the lobby to be
// properly deleted so that we don't reconnect to an empty lobby
if (m_NbAttempts == 0)
{
yield return new WaitForSeconds(k_TimeBeforeFirstAttempt);
}

m_NbAttempts++;
var reconnectingSetupTask = m_ConnectionMethod.SetupClientReconnectionAsync();
yield return new WaitUntil(() => reconnectingSetupTask.IsCompleted);
Expand Down
7 changes: 5 additions & 2 deletions Assets/Scripts/Gameplay/UI/RoomNameBox.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,11 @@ private void InjectDependencies(LocalLobby localLobby)
{
m_LocalLobby = localLobby;
m_LocalLobby.changed += UpdateUI;
UpdateUI(localLobby);
}

void Awake()
{
gameObject.SetActive(false);
UpdateUI(m_LocalLobby);
}

private void OnDestroy()
Expand All @@ -44,6 +43,10 @@ private void UpdateUI(LocalLobby localLobby)
gameObject.SetActive(true);
m_CopyToClipboardButton.gameObject.SetActive(true);
}
else
{
gameObject.SetActive(false);
}
}

public void CopyToClipboard()
Expand Down
6 changes: 4 additions & 2 deletions Assets/Scripts/Infrastructure/UpdateRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class SubscriberData
{
public float Period;
public float NextCallTime;
public float LastCallTime;
}

readonly Queue<Action> m_PendingHandlers = new Queue<Action>();
Expand Down Expand Up @@ -56,7 +57,7 @@ public void Subscribe(Action<float> onUpdate, float updatePeriod)
{
if (m_Subscribers.Add(onUpdate))
{
m_SubscriberData.Add(onUpdate, new SubscriberData() { Period = updatePeriod, NextCallTime = 0 });
m_SubscriberData.Add(onUpdate, new SubscriberData() { Period = updatePeriod, NextCallTime = 0, LastCallTime = Time.time });
}
});
}
Expand Down Expand Up @@ -90,7 +91,8 @@ void Update()

if (Time.time >= subscriberData.NextCallTime)
{
subscriber.Invoke(Time.deltaTime);
subscriber.Invoke(Time.time - subscriberData.LastCallTime);
subscriberData.LastCallTime = Time.time;
subscriberData.NextCallTime = Time.time + subscriberData.Period;
}
}
Expand Down
10 changes: 5 additions & 5 deletions Assets/Scripts/UnityServices/Lobbies/LobbyAPIInterface.cs
Original file line number Diff line number Diff line change
Expand Up @@ -107,11 +107,6 @@ public async Task<QueryResponse> QueryAllLobbies()
return await LobbyService.Instance.QueryLobbiesAsync(queryOptions);
}

public async Task<Lobby> GetLobby(string lobbyId)
{
return await LobbyService.Instance.GetLobbyAsync(lobbyId);
}

public async Task<Lobby> UpdateLobby(string lobbyId, Dictionary<string, DataObject> data, bool shouldLock)
{
UpdateLobbyOptions updateOptions = new UpdateLobbyOptions { Data = data, IsLocked = shouldLock };
Expand All @@ -133,5 +128,10 @@ public async void SendHeartbeatPing(string lobbyId)
{
await LobbyService.Instance.SendHeartbeatPingAsync(lobbyId);
}

public async Task<ILobbyEvents> SubscribeToLobby(string lobbyId, LobbyEventCallbacks eventCallbacks)
{
return await LobbyService.Instance.SubscribeToLobbyEventsAsync(lobbyId, eventCallbacks);
}
}
}
180 changes: 106 additions & 74 deletions Assets/Scripts/UnityServices/Lobbies/LobbyServiceFacade.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,12 @@ public class LobbyServiceFacade : IDisposable, IStartable

public Lobby CurrentUnityLobby { get; private set; }

ILobbyEvents m_LobbyEvents;

bool m_IsTracking = false;

LobbyEventConnectionState m_LobbyEventConnectionState = LobbyEventConnectionState.Unknown;

public void Start()
{
m_ServiceScope = m_ParentScope.CreateChild(builder =>
Expand Down Expand Up @@ -77,88 +81,29 @@ public void BeginTracking()
if (!m_IsTracking)
{
m_IsTracking = true;
// 2s update cadence is arbitrary and is here to demonstrate the fact that this update can be rather infrequent
// the actual rate limits are tracked via the RateLimitCooldown objects defined above
m_UpdateRunner.Subscribe(UpdateLobby, 2f);
SubscribeToJoinedLobbyAsync();
m_JoinedLobbyContentHeartbeat.BeginTracking();
}
}

public Task EndTracking()
public void EndTracking()
{
var task = Task.CompletedTask;
if (CurrentUnityLobby != null)
{
CurrentUnityLobby = null;

var lobbyId = m_LocalLobby?.LobbyID;

if (!string.IsNullOrEmpty(lobbyId))
if (m_LocalUser.IsHost)
{
if (m_LocalUser.IsHost)
{
task = DeleteLobbyAsync(lobbyId);
}
else
{
task = LeaveLobbyAsync(lobbyId);
}
DeleteLobbyAsync();
}
else
{
LeaveLobbyAsync();
}

m_LocalUser.ResetState();
m_LocalLobby?.Reset(m_LocalUser);
}

if (m_IsTracking)
{
m_UpdateRunner.Unsubscribe(UpdateLobby);
m_IsTracking = false;
m_HeartbeatTime = 0;
m_JoinedLobbyContentHeartbeat.EndTracking();
}

return task;
}

async void UpdateLobby(float unused)
{
if (!m_RateLimitQuery.CanCall)
{
return;
}

try
{
var lobby = await m_LobbyApiInterface.GetLobby(m_LocalLobby.LobbyID);

CurrentUnityLobby = lobby;
m_LocalLobby.ApplyRemoteData(lobby);

// as client, check if host is still in lobby
if (!m_LocalUser.IsHost)
{
foreach (var lobbyUser in m_LocalLobby.LobbyUsers)
{
if (lobbyUser.Value.IsHost)
{
return;
}
}
m_UnityServiceErrorMessagePub.Publish(new UnityServiceErrorMessage("Host left the lobby", "Disconnecting.", UnityServiceErrorMessage.Service.Lobby));
await EndTracking();
// no need to disconnect Netcode, it should already be handled by Netcode's callback to disconnect
}
}
catch (LobbyServiceException e)
{
if (e.Reason == LobbyExceptionReason.RateLimited)
{
m_RateLimitQuery.PutOnCooldown();
}
else if (e.Reason != LobbyExceptionReason.LobbyNotFound && !m_LocalUser.IsHost) // If Lobby is not found and if we are not the host, it has already been deleted. No need to publish the error here.
{
PublishError(e);
}
UnsubscribeToJoinedLobbyAsync();
}
}

Expand Down Expand Up @@ -264,6 +209,91 @@ async void UpdateLobby(float unused)
return (false, null);
}

void ResetLobby()
{
CurrentUnityLobby = null;
m_LocalUser.ResetState();
m_LocalLobby?.Reset(m_LocalUser);

// no need to disconnect Netcode, it should already be handled by Netcode's callback to disconnect
}

void OnLobbyChanges(ILobbyChanges changes)
{
if (changes.LobbyDeleted)
{
Debug.Log("Lobby deleted");
ResetLobby();
}
else
{
Debug.Log("Lobby updated");
changes.ApplyToLobby(CurrentUnityLobby);
m_LocalLobby.ApplyRemoteData(CurrentUnityLobby);

// as client, check if host is still in lobby
if (!m_LocalUser.IsHost)
{
foreach (var lobbyUser in m_LocalLobby.LobbyUsers)
{
if (lobbyUser.Value.IsHost)
{
return;
}
}

m_UnityServiceErrorMessagePub.Publish(new UnityServiceErrorMessage("Host left the lobby", "Disconnecting.", UnityServiceErrorMessage.Service.Lobby));
EndTracking();
// no need to disconnect Netcode, it should already be handled by Netcode's callback to disconnect
}
}
}

void OnKickedFromLobby()
{
Debug.Log("Kicked from Lobby");
ResetLobby();
}

void OnLobbyEventConnectionStateChanged(LobbyEventConnectionState lobbyEventConnectionState)
{
m_LobbyEventConnectionState = lobbyEventConnectionState;
Debug.Log($"LobbyEventConnectionState changed to {lobbyEventConnectionState}");
}

async void SubscribeToJoinedLobbyAsync()
{
var lobbyEventCallbacks = new LobbyEventCallbacks();
lobbyEventCallbacks.LobbyChanged += OnLobbyChanges;
lobbyEventCallbacks.KickedFromLobby += OnKickedFromLobby;
lobbyEventCallbacks.LobbyEventConnectionStateChanged += OnLobbyEventConnectionStateChanged;
// The LobbyEventCallbacks object created here will now be managed by the Lobby SDK. The callbacks will be
// unsubscribed from when we call UnsubscribeAsync on the ILobbyEvents object we receive and store here.
m_LobbyEvents = await m_LobbyApiInterface.SubscribeToLobby(m_LocalLobby.LobbyID, lobbyEventCallbacks);
m_JoinedLobbyContentHeartbeat.BeginTracking();
}

async void UnsubscribeToJoinedLobbyAsync()
{
if (m_LobbyEvents != null && m_LobbyEventConnectionState != LobbyEventConnectionState.Unsubscribed)
{
try
{
await m_LobbyEvents.UnsubscribeAsync();
}
catch (ObjectDisposedException e)
{
// This exception occurs in the editor when exiting play mode without first leaving the lobby.
// This is because Wire disposes of subscriptions internally when exiting play mode in the editor.
Debug.Log("Subscription is already disposed of, cannot unsubscribe.");
Debug.Log(e.Message);
}

}
m_HeartbeatTime = 0;
m_JoinedLobbyContentHeartbeat.EndTracking();
}

/// <summary>
/// Used for getting the list of all active lobbies, without needing full info for each.
/// </summary>
Expand Down Expand Up @@ -293,11 +323,11 @@ public async Task RetrieveAndPublishLobbyListAsync()
}
}

public async Task<Lobby> ReconnectToLobbyAsync(string lobbyId)
public async Task<Lobby> ReconnectToLobbyAsync()
{
try
{
return await m_LobbyApiInterface.ReconnectToLobby(lobbyId);
return await m_LobbyApiInterface.ReconnectToLobby(m_LocalLobby.LobbyID);
}
catch (LobbyServiceException e)
{
Expand All @@ -314,12 +344,13 @@ public async Task<Lobby> ReconnectToLobbyAsync(string lobbyId)
/// <summary>
/// Attempt to leave a lobby
/// </summary>
public async Task LeaveLobbyAsync(string lobbyId)
public async void LeaveLobbyAsync()
{
string uasId = AuthenticationService.Instance.PlayerId;
try
{
await m_LobbyApiInterface.RemovePlayerFromLobby(uasId, lobbyId);
await m_LobbyApiInterface.RemovePlayerFromLobby(uasId, m_LocalLobby.LobbyID);
ResetLobby();
}
catch (LobbyServiceException e)
{
Expand Down Expand Up @@ -351,13 +382,14 @@ public async void RemovePlayerFromLobbyAsync(string uasId, string lobbyId)
}
}

public async Task DeleteLobbyAsync(string lobbyId)
public async void DeleteLobbyAsync()
{
if (m_LocalUser.IsHost)
{
try
{
await m_LobbyApiInterface.DeleteLobby(lobbyId);
await m_LobbyApiInterface.DeleteLobby(m_LocalLobby.LobbyID);
ResetLobby();
}
catch (LobbyServiceException e)
{
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com).

## [unreleased] - yyyy-mm-dd

### Changed
* Replaced our polling for lobby updates with a subscription to the new Websocket based LobbyEvents (#805). This saves up a significant amount of bandwidth usage to and from the service, since updates are infrequent in this game. Now clients and hosts only use up bandwidth on the Lobby service when it is needed. With polling, we used to send a GET request per client once every 2s. The responses were between ~550 bytes and 900 bytes, so if we suppose an average of 725 bytes and 100 000 concurrent users (CCU), this amounted to around 725B * 30 calls per minute * 100 000 CCU = 2.175 GB per minute. Scaling this to a month would get us 93.96 TB per month. In our case, since the only changes to the lobbies happen when a user connects or disconnects, most of that data was not necessary and can be saved to reduce bandwidth usage. Since the cost of using the Lobby service depends on bandwidth usage, this would also save money on an actual game.
*
### Cleanup
* Clarified a TODO comment inside ClientCharacter, detailing how anticipation should only be executed on owning client players (#786)
* Removed now unnecessary cached NetworkBehaviour status on some components, since they now do not allocate memory (#799)
Expand All @@ -18,6 +22,7 @@ Additional documentation and release notes are available at [Multiplayer Documen
* EnemyPortals' VFX get disabled and re-enabled once the breakable crystals are broken (#784)
* Elements inside the Tank's and Rogue's AnimatorTriggeredSpecialFX list have been revised to not loop AudioSource clips, ending the logging of multiple warnings to the console (#785)
* ClientConnectedState now inherits from OnlineState instead of the base ConnectionState (#801)
* UpdateRunner now sends the right value for deltaTime when updating its subscribers (#805)

## [2.0.4] - 2022-12-13
### Changed
Expand Down
Loading