Remove locking from SpectatorScreen

This commit is contained in:
smoogipoo 2021-05-20 18:52:20 +09:00
parent 10597f7e6a
commit f74dbb9e1f

View File

@ -42,9 +42,6 @@ namespace osu.Game.Screens.Spectate
[Resolved] [Resolved]
private UserLookupCache userLookupCache { get; set; } private UserLookupCache userLookupCache { get; set; }
// A lock is used to synchronise access to spectator/gameplay states, since this class is a screen which may become non-current and stop receiving updates at any point.
private readonly object stateLock = new object();
private readonly Dictionary<int, User> userMap = new Dictionary<int, User>(); private readonly Dictionary<int, User> userMap = new Dictionary<int, User>();
private readonly Dictionary<int, GameplayState> gameplayStates = new Dictionary<int, GameplayState>(); private readonly Dictionary<int, GameplayState> gameplayStates = new Dictionary<int, GameplayState>();
@ -63,8 +60,11 @@ namespace osu.Game.Screens.Spectate
{ {
base.LoadComplete(); base.LoadComplete();
populateAllUsers().ContinueWith(_ => Schedule(() => getAllUsers().ContinueWith(users => Schedule(() =>
{ {
foreach (var u in users.Result)
userMap[u.Id] = u;
spectatorClient.BindUserBeganPlaying(userBeganPlaying, true); spectatorClient.BindUserBeganPlaying(userBeganPlaying, true);
spectatorClient.OnUserFinishedPlaying += userFinishedPlaying; spectatorClient.OnUserFinishedPlaying += userFinishedPlaying;
spectatorClient.OnNewFrames += userSentFrames; spectatorClient.OnNewFrames += userSentFrames;
@ -72,27 +72,23 @@ namespace osu.Game.Screens.Spectate
managerUpdated = beatmaps.ItemUpdated.GetBoundCopy(); managerUpdated = beatmaps.ItemUpdated.GetBoundCopy();
managerUpdated.BindValueChanged(beatmapUpdated); managerUpdated.BindValueChanged(beatmapUpdated);
lock (stateLock) foreach (var (id, _) in userMap)
{ spectatorClient.WatchUser(id);
foreach (var (id, _) in userMap)
spectatorClient.WatchUser(id);
}
})); }));
} }
private Task populateAllUsers() private Task<User[]> getAllUsers()
{ {
var userLookupTasks = new List<Task>(); var userLookupTasks = new List<Task<User>>();
foreach (var u in userIds) foreach (var u in userIds)
{ {
userLookupTasks.Add(userLookupCache.GetUserAsync(u).ContinueWith(task => userLookupTasks.Add(userLookupCache.GetUserAsync(u).ContinueWith(task =>
{ {
if (!task.IsCompletedSuccessfully) if (!task.IsCompletedSuccessfully)
return; return null;
lock (stateLock) return task.Result;
userMap[u] = task.Result;
})); }));
} }
@ -104,16 +100,13 @@ namespace osu.Game.Screens.Spectate
if (!e.NewValue.TryGetTarget(out var beatmapSet)) if (!e.NewValue.TryGetTarget(out var beatmapSet))
return; return;
lock (stateLock) foreach (var (userId, _) in userMap)
{ {
foreach (var (userId, _) in userMap) if (!spectatorClient.TryGetPlayingUserState(userId, out var userState))
{ continue;
if (!spectatorClient.TryGetPlayingUserState(userId, out var userState))
continue;
if (beatmapSet.Beatmaps.Any(b => b.OnlineBeatmapID == userState.BeatmapID)) if (beatmapSet.Beatmaps.Any(b => b.OnlineBeatmapID == userState.BeatmapID))
updateGameplayState(userId); updateGameplayState(userId);
}
} }
} }
@ -122,101 +115,89 @@ namespace osu.Game.Screens.Spectate
if (state.RulesetID == null || state.BeatmapID == null) if (state.RulesetID == null || state.BeatmapID == null)
return; return;
lock (stateLock) if (!userMap.ContainsKey(userId))
{ return;
if (!userMap.ContainsKey(userId))
return;
// The user may have stopped playing. // The user may have stopped playing.
if (!spectatorClient.TryGetPlayingUserState(userId, out _)) if (!spectatorClient.TryGetPlayingUserState(userId, out _))
return; return;
Schedule(() => OnUserStateChanged(userId, state)); Schedule(() => OnUserStateChanged(userId, state));
updateGameplayState(userId); updateGameplayState(userId);
}
} }
private void updateGameplayState(int userId) private void updateGameplayState(int userId)
{ {
lock (stateLock) Debug.Assert(userMap.ContainsKey(userId));
// The user may have stopped playing.
if (!spectatorClient.TryGetPlayingUserState(userId, out var spectatorState))
return;
var user = userMap[userId];
var resolvedRuleset = rulesets.AvailableRulesets.FirstOrDefault(r => r.ID == spectatorState.RulesetID)?.CreateInstance();
if (resolvedRuleset == null)
return;
var resolvedBeatmap = beatmaps.QueryBeatmap(b => b.OnlineBeatmapID == spectatorState.BeatmapID);
if (resolvedBeatmap == null)
return;
var score = new Score
{ {
Debug.Assert(userMap.ContainsKey(userId)); ScoreInfo = new ScoreInfo
// The user may have stopped playing.
if (!spectatorClient.TryGetPlayingUserState(userId, out var spectatorState))
return;
var user = userMap[userId];
var resolvedRuleset = rulesets.AvailableRulesets.FirstOrDefault(r => r.ID == spectatorState.RulesetID)?.CreateInstance();
if (resolvedRuleset == null)
return;
var resolvedBeatmap = beatmaps.QueryBeatmap(b => b.OnlineBeatmapID == spectatorState.BeatmapID);
if (resolvedBeatmap == null)
return;
var score = new Score
{ {
ScoreInfo = new ScoreInfo Beatmap = resolvedBeatmap,
{ User = user,
Beatmap = resolvedBeatmap, Mods = spectatorState.Mods.Select(m => m.ToMod(resolvedRuleset)).ToArray(),
User = user, Ruleset = resolvedRuleset.RulesetInfo,
Mods = spectatorState.Mods.Select(m => m.ToMod(resolvedRuleset)).ToArray(), },
Ruleset = resolvedRuleset.RulesetInfo, Replay = new Replay { HasReceivedAllFrames = false },
}, };
Replay = new Replay { HasReceivedAllFrames = false },
};
var gameplayState = new GameplayState(score, resolvedRuleset, beatmaps.GetWorkingBeatmap(resolvedBeatmap)); var gameplayState = new GameplayState(score, resolvedRuleset, beatmaps.GetWorkingBeatmap(resolvedBeatmap));
gameplayStates[userId] = gameplayState; gameplayStates[userId] = gameplayState;
Schedule(() => StartGameplay(userId, gameplayState)); Schedule(() => StartGameplay(userId, gameplayState));
}
} }
private void userSentFrames(int userId, FrameDataBundle bundle) private void userSentFrames(int userId, FrameDataBundle bundle)
{ {
lock (stateLock) if (!userMap.ContainsKey(userId))
return;
if (!gameplayStates.TryGetValue(userId, out var gameplayState))
return;
// The ruleset instance should be guaranteed to be in sync with the score via ScoreLock.
Debug.Assert(gameplayState.Ruleset != null && gameplayState.Ruleset.RulesetInfo.Equals(gameplayState.Score.ScoreInfo.Ruleset));
foreach (var frame in bundle.Frames)
{ {
if (!userMap.ContainsKey(userId)) IConvertibleReplayFrame convertibleFrame = gameplayState.Ruleset.CreateConvertibleReplayFrame();
return; convertibleFrame.FromLegacy(frame, gameplayState.Beatmap.Beatmap);
if (!gameplayStates.TryGetValue(userId, out var gameplayState)) var convertedFrame = (ReplayFrame)convertibleFrame;
return; convertedFrame.Time = frame.Time;
// The ruleset instance should be guaranteed to be in sync with the score via ScoreLock. gameplayState.Score.Replay.Frames.Add(convertedFrame);
Debug.Assert(gameplayState.Ruleset != null && gameplayState.Ruleset.RulesetInfo.Equals(gameplayState.Score.ScoreInfo.Ruleset));
foreach (var frame in bundle.Frames)
{
IConvertibleReplayFrame convertibleFrame = gameplayState.Ruleset.CreateConvertibleReplayFrame();
convertibleFrame.FromLegacy(frame, gameplayState.Beatmap.Beatmap);
var convertedFrame = (ReplayFrame)convertibleFrame;
convertedFrame.Time = frame.Time;
gameplayState.Score.Replay.Frames.Add(convertedFrame);
}
} }
} }
private void userFinishedPlaying(int userId, SpectatorState state) private void userFinishedPlaying(int userId, SpectatorState state)
{ {
lock (stateLock) if (!userMap.ContainsKey(userId))
{ return;
if (!userMap.ContainsKey(userId))
return;
if (!gameplayStates.TryGetValue(userId, out var gameplayState)) if (!gameplayStates.TryGetValue(userId, out var gameplayState))
return; return;
gameplayState.Score.Replay.HasReceivedAllFrames = true; gameplayState.Score.Replay.HasReceivedAllFrames = true;
gameplayStates.Remove(userId); gameplayStates.Remove(userId);
Schedule(() => EndGameplay(userId)); Schedule(() => EndGameplay(userId));
}
} }
/// <summary> /// <summary>
@ -245,15 +226,12 @@ namespace osu.Game.Screens.Spectate
/// <param name="userId">The user to stop spectating.</param> /// <param name="userId">The user to stop spectating.</param>
protected void RemoveUser(int userId) protected void RemoveUser(int userId)
{ {
lock (stateLock) userFinishedPlaying(userId, null);
{
userFinishedPlaying(userId, null);
userIds.Remove(userId); userIds.Remove(userId);
userMap.Remove(userId); userMap.Remove(userId);
spectatorClient.StopWatchingUser(userId); spectatorClient.StopWatchingUser(userId);
}
} }
protected override void Dispose(bool isDisposing) protected override void Dispose(bool isDisposing)
@ -266,11 +244,8 @@ namespace osu.Game.Screens.Spectate
spectatorClient.OnUserFinishedPlaying -= userFinishedPlaying; spectatorClient.OnUserFinishedPlaying -= userFinishedPlaying;
spectatorClient.OnNewFrames -= userSentFrames; spectatorClient.OnNewFrames -= userSentFrames;
lock (stateLock) foreach (var (userId, _) in userMap)
{ spectatorClient.StopWatchingUser(userId);
foreach (var (userId, _) in userMap)
spectatorClient.StopWatchingUser(userId);
}
} }
managerUpdated?.UnbindAll(); managerUpdated?.UnbindAll();