mirror of
https://github.com/osukey/osukey.git
synced 2025-08-04 23:24:04 +09:00
Merge branch 'master' into user-beatmap-downloading-states-2
This commit is contained in:
@ -126,20 +126,12 @@ namespace osu.Game.Online.Multiplayer
|
||||
return connection.InvokeAsync<MultiplayerRoom>(nameof(IMultiplayerServer.JoinRoom), roomId);
|
||||
}
|
||||
|
||||
public override async Task LeaveRoom()
|
||||
protected override Task LeaveRoomInternal()
|
||||
{
|
||||
if (!isConnected.Value)
|
||||
{
|
||||
// even if not connected, make sure the local room state can be cleaned up.
|
||||
await base.LeaveRoom();
|
||||
return;
|
||||
}
|
||||
return Task.FromCanceled(new CancellationToken(true));
|
||||
|
||||
if (Room == null)
|
||||
return;
|
||||
|
||||
await base.LeaveRoom();
|
||||
await connection.InvokeAsync(nameof(IMultiplayerServer.LeaveRoom));
|
||||
return connection.InvokeAsync(nameof(IMultiplayerServer.LeaveRoom));
|
||||
}
|
||||
|
||||
public override Task TransferHost(int userId)
|
||||
|
@ -7,6 +7,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
@ -109,30 +110,43 @@ namespace osu.Game.Online.Multiplayer
|
||||
});
|
||||
}
|
||||
|
||||
private readonly TaskChain joinOrLeaveTaskChain = new TaskChain();
|
||||
private CancellationTokenSource? joinCancellationSource;
|
||||
|
||||
/// <summary>
|
||||
/// Joins the <see cref="MultiplayerRoom"/> for a given API <see cref="Room"/>.
|
||||
/// </summary>
|
||||
/// <param name="room">The API <see cref="Room"/>.</param>
|
||||
public async Task JoinRoom(Room room)
|
||||
{
|
||||
if (Room != null)
|
||||
throw new InvalidOperationException("Cannot join a multiplayer room while already in one.");
|
||||
var cancellationSource = joinCancellationSource = new CancellationTokenSource();
|
||||
|
||||
Debug.Assert(room.RoomID.Value != null);
|
||||
await joinOrLeaveTaskChain.Add(async () =>
|
||||
{
|
||||
if (Room != null)
|
||||
throw new InvalidOperationException("Cannot join a multiplayer room while already in one.");
|
||||
|
||||
apiRoom = room;
|
||||
playlistItemId = room.Playlist.SingleOrDefault()?.ID ?? 0;
|
||||
Debug.Assert(room.RoomID.Value != null);
|
||||
|
||||
Room = await JoinRoom(room.RoomID.Value.Value);
|
||||
// Join the server-side room.
|
||||
var joinedRoom = await JoinRoom(room.RoomID.Value.Value);
|
||||
Debug.Assert(joinedRoom != null);
|
||||
|
||||
Debug.Assert(Room != null);
|
||||
// Populate users.
|
||||
Debug.Assert(joinedRoom.Users != null);
|
||||
await Task.WhenAll(joinedRoom.Users.Select(PopulateUser));
|
||||
|
||||
var users = await getRoomUsers();
|
||||
Debug.Assert(users != null);
|
||||
// Update the stored room (must be done on update thread for thread-safety).
|
||||
await scheduleAsync(() =>
|
||||
{
|
||||
Room = joinedRoom;
|
||||
apiRoom = room;
|
||||
playlistItemId = room.Playlist.SingleOrDefault()?.ID ?? 0;
|
||||
}, cancellationSource.Token);
|
||||
|
||||
await Task.WhenAll(users.Select(PopulateUser));
|
||||
|
||||
updateLocalRoomSettings(Room.Settings);
|
||||
// Update room settings.
|
||||
await updateLocalRoomSettings(joinedRoom.Settings, cancellationSource.Token);
|
||||
}, cancellationSource.Token);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -142,23 +156,33 @@ namespace osu.Game.Online.Multiplayer
|
||||
/// <returns>The joined <see cref="MultiplayerRoom"/>.</returns>
|
||||
protected abstract Task<MultiplayerRoom> JoinRoom(long roomId);
|
||||
|
||||
public virtual Task LeaveRoom()
|
||||
public Task LeaveRoom()
|
||||
{
|
||||
Scheduler.Add(() =>
|
||||
{
|
||||
if (Room == null)
|
||||
return;
|
||||
// The join may have not completed yet, so certain tasks that either update the room or reference the room should be cancelled.
|
||||
// This includes the setting of Room itself along with the initial update of the room settings on join.
|
||||
joinCancellationSource?.Cancel();
|
||||
|
||||
// Leaving rooms is expected to occur instantaneously whilst the operation is finalised in the background.
|
||||
// However a few members need to be reset immediately to prevent other components from entering invalid states whilst the operation hasn't yet completed.
|
||||
// For example, if a room was left and the user immediately pressed the "create room" button, then the user could be taken into the lobby if the value of Room is not reset in time.
|
||||
var scheduledReset = scheduleAsync(() =>
|
||||
{
|
||||
apiRoom = null;
|
||||
Room = null;
|
||||
CurrentMatchPlayingUserIds.Clear();
|
||||
|
||||
RoomUpdated?.Invoke();
|
||||
}, false);
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
return joinOrLeaveTaskChain.Add(async () =>
|
||||
{
|
||||
await scheduledReset;
|
||||
await LeaveRoomInternal();
|
||||
});
|
||||
}
|
||||
|
||||
protected abstract Task LeaveRoomInternal();
|
||||
|
||||
/// <summary>
|
||||
/// Change the current <see cref="MultiplayerRoom"/> settings.
|
||||
/// </summary>
|
||||
@ -462,27 +486,6 @@ namespace osu.Game.Online.Multiplayer
|
||||
/// <param name="multiplayerUser">The <see cref="MultiplayerRoomUser"/> to populate.</param>
|
||||
protected async Task PopulateUser(MultiplayerRoomUser multiplayerUser) => multiplayerUser.User ??= await userLookupCache.GetUserAsync(multiplayerUser.UserID);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve a copy of users currently in the joined <see cref="Room"/> in a thread-safe manner.
|
||||
/// This should be used whenever accessing users from outside of an Update thread context (ie. when not calling <see cref="Drawable.Schedule"/>).
|
||||
/// </summary>
|
||||
/// <returns>A copy of users in the current room, or null if unavailable.</returns>
|
||||
private Task<List<MultiplayerRoomUser>?> getRoomUsers()
|
||||
{
|
||||
var tcs = new TaskCompletionSource<List<MultiplayerRoomUser>?>();
|
||||
|
||||
// at some point we probably want to replace all these schedule calls with Room.LockForUpdate.
|
||||
// for now, as this would require quite some consideration due to the number of accesses to the room instance,
|
||||
// let's just add a manual schedule for the non-scheduled usages instead.
|
||||
Scheduler.Add(() =>
|
||||
{
|
||||
var users = Room?.Users.ToList();
|
||||
tcs.SetResult(users);
|
||||
}, false);
|
||||
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the local room settings with the given <see cref="MultiplayerRoomSettings"/>.
|
||||
/// </summary>
|
||||
@ -490,34 +493,36 @@ namespace osu.Game.Online.Multiplayer
|
||||
/// This updates both the joined <see cref="MultiplayerRoom"/> and the respective API <see cref="Room"/>.
|
||||
/// </remarks>
|
||||
/// <param name="settings">The new <see cref="MultiplayerRoomSettings"/> to update from.</param>
|
||||
private void updateLocalRoomSettings(MultiplayerRoomSettings settings)
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to cancel the update.</param>
|
||||
private Task updateLocalRoomSettings(MultiplayerRoomSettings settings, CancellationToken cancellationToken = default) => scheduleAsync(() =>
|
||||
{
|
||||
if (Room == null)
|
||||
return;
|
||||
|
||||
Scheduler.Add(() =>
|
||||
Debug.Assert(apiRoom != null);
|
||||
|
||||
// Update a few properties of the room instantaneously.
|
||||
Room.Settings = settings;
|
||||
apiRoom.Name.Value = Room.Settings.Name;
|
||||
|
||||
// The playlist update is delayed until an online beatmap lookup (below) succeeds.
|
||||
// In-order for the client to not display an outdated beatmap, the playlist is forcefully cleared here.
|
||||
apiRoom.Playlist.Clear();
|
||||
|
||||
RoomUpdated?.Invoke();
|
||||
|
||||
var req = new GetBeatmapSetRequest(settings.BeatmapID, BeatmapSetLookupType.BeatmapId);
|
||||
|
||||
req.Success += res =>
|
||||
{
|
||||
if (Room == null)
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
return;
|
||||
|
||||
Debug.Assert(apiRoom != null);
|
||||
updatePlaylist(settings, res);
|
||||
};
|
||||
|
||||
// Update a few properties of the room instantaneously.
|
||||
Room.Settings = settings;
|
||||
apiRoom.Name.Value = Room.Settings.Name;
|
||||
|
||||
// The playlist update is delayed until an online beatmap lookup (below) succeeds.
|
||||
// In-order for the client to not display an outdated beatmap, the playlist is forcefully cleared here.
|
||||
apiRoom.Playlist.Clear();
|
||||
|
||||
RoomUpdated?.Invoke();
|
||||
|
||||
var req = new GetBeatmapSetRequest(settings.BeatmapID, BeatmapSetLookupType.BeatmapId);
|
||||
req.Success += res => updatePlaylist(settings, res);
|
||||
|
||||
api.Queue(req);
|
||||
}, false);
|
||||
}
|
||||
api.Queue(req);
|
||||
}, cancellationToken);
|
||||
|
||||
private void updatePlaylist(MultiplayerRoomSettings settings, APIBeatmapSet onlineSet)
|
||||
{
|
||||
@ -566,5 +571,31 @@ namespace osu.Game.Online.Multiplayer
|
||||
else
|
||||
CurrentMatchPlayingUserIds.Remove(userId);
|
||||
}
|
||||
|
||||
private Task scheduleAsync(Action action, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var tcs = new TaskCompletionSource<bool>();
|
||||
|
||||
Scheduler.Add(() =>
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
tcs.SetCanceled();
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
action();
|
||||
tcs.SetResult(true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
tcs.SetException(ex);
|
||||
}
|
||||
});
|
||||
|
||||
return tcs.Task;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user