mirror of
https://github.com/ppy/osu
synced 2025-01-25 07:13:22 +00:00
Remove locking from SpectatorScreen
This commit is contained in:
parent
10597f7e6a
commit
f74dbb9e1f
@ -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();
|
||||||
|
Loading…
Reference in New Issue
Block a user