diff --git a/osu.Android.props b/osu.Android.props
index 90d131b117..57550cfb93 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -52,6 +52,6 @@
-
+
diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs
index 0067a55fd8..a4fc963328 100644
--- a/osu.Game/Online/Spectator/SpectatorClient.cs
+++ b/osu.Game/Online/Spectator/SpectatorClient.cs
@@ -40,10 +40,10 @@ namespace osu.Game.Online.Spectator
private readonly List watchingUsers = new List();
public IBindableList PlayingUsers => playingUsers;
-
private readonly BindableList playingUsers = new BindableList();
- private readonly Dictionary playingUserStates = new Dictionary();
+ public IBindableDictionary PlayingUserStates => playingUserStates;
+ private readonly BindableDictionary playingUserStates = new BindableDictionary();
private IBeatmap? currentBeatmap;
@@ -200,6 +200,7 @@ namespace osu.Game.Online.Spectator
Schedule(() =>
{
watchingUsers.Remove(userId);
+ playingUserStates.Remove(userId);
StopWatchingUserInternal(userId);
});
}
@@ -256,33 +257,5 @@ namespace osu.Game.Online.Spectator
lastSendTime = Time.Current;
}
-
- ///
- /// Attempts to retrieve the for a currently-playing user.
- ///
- /// The user.
- /// The current for the user, if they're playing. null if the user is not playing.
- /// true if successful (the user is playing), false otherwise.
- public bool TryGetPlayingUserState(int userId, out SpectatorState state)
- {
- return playingUserStates.TryGetValue(userId, out state);
- }
-
- ///
- /// Bind an action to with the option of running the bound action once immediately.
- ///
- /// The action to perform when a user begins playing.
- /// Whether the action provided in should be run once immediately for all users currently playing.
- public void BindUserBeganPlaying(Action callback, bool runOnceImmediately = false)
- {
- // The lock is taken before the event is subscribed to to prevent doubling of events.
- OnUserBeganPlaying += callback;
-
- if (!runOnceImmediately)
- return;
-
- foreach (var (userId, state) in playingUserStates)
- callback(userId, state);
- }
}
}
diff --git a/osu.Game/Screens/Spectate/SpectatorScreen.cs b/osu.Game/Screens/Spectate/SpectatorScreen.cs
index e6c9a0acd4..9a20bb58b8 100644
--- a/osu.Game/Screens/Spectate/SpectatorScreen.cs
+++ b/osu.Game/Screens/Spectate/SpectatorScreen.cs
@@ -9,6 +9,7 @@ using System.Threading.Tasks;
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
+using osu.Framework.Extensions.ObjectExtensions;
using osu.Game.Beatmaps;
using osu.Game.Database;
using osu.Game.Online.Spectator;
@@ -42,6 +43,8 @@ namespace osu.Game.Screens.Spectate
[Resolved]
private UserLookupCache userLookupCache { get; set; }
+ private readonly IBindableDictionary playingUserStates = new BindableDictionary();
+
private readonly Dictionary userMap = new Dictionary();
private readonly Dictionary gameplayStates = new Dictionary();
@@ -65,8 +68,9 @@ namespace osu.Game.Screens.Spectate
foreach (var u in users.Result)
userMap[u.Id] = u;
- spectatorClient.BindUserBeganPlaying(userBeganPlaying, true);
- spectatorClient.OnUserFinishedPlaying += userFinishedPlaying;
+ playingUserStates.BindTo(spectatorClient.PlayingUserStates);
+ playingUserStates.BindCollectionChanged(onPlayingUserStatesChanged, true);
+
spectatorClient.OnNewFrames += userSentFrames;
managerUpdated = beatmaps.ItemUpdated.GetBoundCopy();
@@ -102,7 +106,7 @@ namespace osu.Game.Screens.Spectate
foreach (var (userId, _) in userMap)
{
- if (!spectatorClient.TryGetPlayingUserState(userId, out var userState))
+ if (!playingUserStates.TryGetValue(userId, out var userState))
continue;
if (beatmapSet.Beatmaps.Any(b => b.OnlineBeatmapID == userState.BeatmapID))
@@ -110,7 +114,31 @@ namespace osu.Game.Screens.Spectate
}
}
- private void userBeganPlaying(int userId, SpectatorState state)
+ private void onPlayingUserStatesChanged(object sender, NotifyDictionaryChangedEventArgs e)
+ {
+ switch (e.Action)
+ {
+ case NotifyDictionaryChangedAction.Add:
+ foreach (var (userId, state) in e.NewItems.AsNonNull())
+ onUserStateAdded(userId, state);
+ break;
+
+ case NotifyDictionaryChangedAction.Remove:
+ foreach (var (userId, _) in e.OldItems.AsNonNull())
+ onUserStateRemoved(userId);
+ break;
+
+ case NotifyDictionaryChangedAction.Replace:
+ foreach (var (userId, _) in e.OldItems.AsNonNull())
+ onUserStateRemoved(userId);
+
+ foreach (var (userId, state) in e.NewItems.AsNonNull())
+ onUserStateAdded(userId, state);
+ break;
+ }
+ }
+
+ private void onUserStateAdded(int userId, SpectatorState state)
{
if (state.RulesetID == null || state.BeatmapID == null)
return;
@@ -118,24 +146,30 @@ namespace osu.Game.Screens.Spectate
if (!userMap.ContainsKey(userId))
return;
- // The user may have stopped playing.
- if (!spectatorClient.TryGetPlayingUserState(userId, out _))
+ Schedule(() => OnUserStateChanged(userId, state));
+ updateGameplayState(userId);
+ }
+
+ private void onUserStateRemoved(int userId)
+ {
+ if (!userMap.ContainsKey(userId))
return;
- Schedule(() => OnUserStateChanged(userId, state));
+ if (!gameplayStates.TryGetValue(userId, out var gameplayState))
+ return;
- updateGameplayState(userId);
+ gameplayState.Score.Replay.HasReceivedAllFrames = true;
+
+ gameplayStates.Remove(userId);
+ Schedule(() => EndGameplay(userId));
}
private void updateGameplayState(int userId)
{
Debug.Assert(userMap.ContainsKey(userId));
- // The user may have stopped playing.
- if (!spectatorClient.TryGetPlayingUserState(userId, out var spectatorState))
- return;
-
var user = userMap[userId];
+ var spectatorState = playingUserStates[userId];
var resolvedRuleset = rulesets.AvailableRulesets.FirstOrDefault(r => r.ID == spectatorState.RulesetID)?.CreateInstance();
if (resolvedRuleset == null)
@@ -186,20 +220,6 @@ namespace osu.Game.Screens.Spectate
}
}
- private void userFinishedPlaying(int userId, SpectatorState state)
- {
- if (!userMap.ContainsKey(userId))
- return;
-
- if (!gameplayStates.TryGetValue(userId, out var gameplayState))
- return;
-
- gameplayState.Score.Replay.HasReceivedAllFrames = true;
-
- gameplayStates.Remove(userId);
- Schedule(() => EndGameplay(userId));
- }
-
///
/// Invoked when a spectated user's state has changed.
///
@@ -226,7 +246,7 @@ namespace osu.Game.Screens.Spectate
/// The user to stop spectating.
protected void RemoveUser(int userId)
{
- userFinishedPlaying(userId, null);
+ onUserStateRemoved(userId);
userIds.Remove(userId);
userMap.Remove(userId);
@@ -240,8 +260,6 @@ namespace osu.Game.Screens.Spectate
if (spectatorClient != null)
{
- spectatorClient.OnUserBeganPlaying -= userBeganPlaying;
- spectatorClient.OnUserFinishedPlaying -= userFinishedPlaying;
spectatorClient.OnNewFrames -= userSentFrames;
foreach (var (userId, _) in userMap)
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index 587bdaf622..1e3b77cd70 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -29,7 +29,7 @@
-
+
diff --git a/osu.iOS.props b/osu.iOS.props
index 7ba7a554d6..a2a9ac35fc 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -70,7 +70,7 @@
-
+
@@ -93,7 +93,7 @@
-
+