1
0
mirror of https://github.com/ppy/osu.git synced 2025-02-20 05:36:05 +08:00

Rework spectator components to use new user state

This commit is contained in:
Dan Balasescu 2022-01-26 22:21:07 +09:00
parent 41007169f7
commit f4210f7a30
3 changed files with 45 additions and 38 deletions

View File

@ -93,12 +93,8 @@ namespace osu.Game.Online.Spectator
{
Schedule(() =>
{
// UserBeganPlaying() is called by the server regardless of whether the local user is watching the remote user, and is called a further time when the remote user is watched.
// This may be a temporary thing (see: https://github.com/ppy/osu-server-spectator/blob/2273778e02cfdb4a9c6a934f2a46a8459cb5d29c/osu.Server.Spectator/Hubs/SpectatorHub.cs#L28-L29).
// We don't want the user states to update unless the player is being watched, otherwise calling BindUserBeganPlaying() can lead to double invocations.
if (watchingUsers.Contains(userId))
playingUserStates[userId] = state;
OnUserBeganPlaying?.Invoke(userId, state);
});
@ -109,8 +105,8 @@ namespace osu.Game.Online.Spectator
{
Schedule(() =>
{
playingUserStates.Remove(userId);
if (watchingUsers.Contains(userId))
playingUserStates[userId] = state;
OnUserFinishedPlaying?.Invoke(userId, state);
});

View File

@ -22,7 +22,7 @@ namespace osu.Game.Overlays.Dashboard
{
internal class CurrentlyPlayingDisplay : CompositeDrawable
{
private readonly IBindableDictionary<int, SpectatorState> playingUserStates = new BindableDictionary<int, SpectatorState>();
private readonly IBindableDictionary<int, SpectatorState> userStates = new BindableDictionary<int, SpectatorState>();
private FillFlowContainer<PlayingUserPanel> userFlow;
@ -51,33 +51,32 @@ namespace osu.Game.Overlays.Dashboard
{
base.LoadComplete();
playingUserStates.BindTo(spectatorClient.PlayingUserStates);
playingUserStates.BindCollectionChanged(onUsersChanged, true);
userStates.BindTo(spectatorClient.PlayingUserStates);
userStates.BindCollectionChanged(onUserStatesChanged, true);
}
private void onUsersChanged(object sender, NotifyDictionaryChangedEventArgs<int, SpectatorState> e) => Schedule(() =>
private void onUserStatesChanged(object sender, NotifyDictionaryChangedEventArgs<int, SpectatorState> e) => Schedule(() =>
{
switch (e.Action)
{
case NotifyDictionaryChangedAction.Add:
case NotifyDictionaryChangedAction.Replace:
Debug.Assert(e.NewItems != null);
foreach ((int userId, _) in e.NewItems)
foreach ((int userId, SpectatorState state) in e.NewItems)
{
if (state.State != SpectatingUserState.Playing)
{
removePlayingUser(userId);
continue;
}
users.GetUserAsync(userId).ContinueWith(task =>
{
var user = task.GetResultSafely();
if (user == null) return;
Schedule(() =>
{
// user may no longer be playing.
if (!playingUserStates.ContainsKey(user.Id))
return;
userFlow.Add(createUserPanel(user));
});
if (user != null)
Schedule(() => addPlayingUser(user));
});
}
@ -87,9 +86,20 @@ namespace osu.Game.Overlays.Dashboard
Debug.Assert(e.OldItems != null);
foreach ((int userId, _) in e.OldItems)
userFlow.FirstOrDefault(card => card.User.Id == userId)?.Expire();
removePlayingUser(userId);
break;
}
void addPlayingUser(APIUser user)
{
// user may no longer be playing.
if (!userStates.TryGetValue(user.Id, out var state2) || state2.State != SpectatingUserState.Playing)
return;
userFlow.Add(createUserPanel(user));
}
void removePlayingUser(int userId) => userFlow.FirstOrDefault(card => card.User.Id == userId)?.Expire();
});
private PlayingUserPanel createUserPanel(APIUser user) =>

View File

@ -42,7 +42,7 @@ namespace osu.Game.Screens.Spectate
[Resolved]
private UserLookupCache userLookupCache { get; set; }
private readonly IBindableDictionary<int, SpectatorState> playingUserStates = new BindableDictionary<int, SpectatorState>();
private readonly IBindableDictionary<int, SpectatorState> userStates = new BindableDictionary<int, SpectatorState>();
private readonly Dictionary<int, APIUser> userMap = new Dictionary<int, APIUser>();
private readonly Dictionary<int, SpectatorGameplayState> gameplayStates = new Dictionary<int, SpectatorGameplayState>();
@ -77,8 +77,8 @@ namespace osu.Game.Screens.Spectate
userMap[u.Id] = u;
}
playingUserStates.BindTo(spectatorClient.PlayingUserStates);
playingUserStates.BindCollectionChanged(onPlayingUserStatesChanged, true);
userStates.BindTo(spectatorClient.PlayingUserStates);
userStates.BindCollectionChanged(onUserStatesChanged, true);
realmSubscription = realm.RegisterForNotifications(
realm => realm.All<BeatmapSetInfo>().Where(s => !s.DeletePending), beatmapsChanged);
@ -99,7 +99,7 @@ namespace osu.Game.Screens.Spectate
{
foreach ((int userId, _) in userMap)
{
if (!playingUserStates.TryGetValue(userId, out var userState))
if (!userStates.TryGetValue(userId, out var userState))
continue;
if (beatmapSet.Beatmaps.Any(b => b.OnlineID == userState.BeatmapID))
@ -107,40 +107,41 @@ namespace osu.Game.Screens.Spectate
}
}
private void onPlayingUserStatesChanged(object sender, NotifyDictionaryChangedEventArgs<int, SpectatorState> e)
private void onUserStatesChanged(object sender, NotifyDictionaryChangedEventArgs<int, SpectatorState> e)
{
switch (e.Action)
{
case NotifyDictionaryChangedAction.Add:
foreach ((int userId, var state) in e.NewItems.AsNonNull())
onUserStateAdded(userId, state);
onUserStateChanged(userId, state);
break;
case NotifyDictionaryChangedAction.Remove:
foreach ((int userId, var _) in e.OldItems.AsNonNull())
foreach ((int userId, _) in e.OldItems.AsNonNull())
onUserStateRemoved(userId);
break;
case NotifyDictionaryChangedAction.Replace:
foreach ((int userId, var _) in e.OldItems.AsNonNull())
onUserStateRemoved(userId);
foreach ((int userId, var state) in e.NewItems.AsNonNull())
onUserStateAdded(userId, state);
onUserStateChanged(userId, state);
break;
}
}
private void onUserStateAdded(int userId, SpectatorState state)
private void onUserStateChanged(int userId, SpectatorState newState)
{
if (state.RulesetID == null || state.BeatmapID == null)
if (newState.RulesetID == null || newState.BeatmapID == null)
return;
if (!userMap.ContainsKey(userId))
return;
Schedule(() => OnUserStateChanged(userId, state));
updateGameplayState(userId);
// Do nothing for failed/completed states.
if (newState.State == SpectatingUserState.Playing)
{
Schedule(() => OnUserStateChanged(userId, newState));
updateGameplayState(userId);
}
}
private void onUserStateRemoved(int userId)
@ -162,7 +163,7 @@ namespace osu.Game.Screens.Spectate
Debug.Assert(userMap.ContainsKey(userId));
var user = userMap[userId];
var spectatorState = playingUserStates[userId];
var spectatorState = userStates[userId];
var resolvedRuleset = rulesets.AvailableRulesets.FirstOrDefault(r => r.OnlineID == spectatorState.RulesetID)?.CreateInstance();
if (resolvedRuleset == null)