1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-15 01:43:20 +08:00

Refactor APIAccess main loop to read better

This commit is contained in:
Dean Herbert 2022-08-11 15:43:39 +09:00
parent 47196b19a5
commit 865d63f768

View File

@ -104,30 +104,23 @@ namespace osu.Game.Online.API
/// </summary>
private int failureCount;
/// <summary>
/// The main API thread loop, which will continue to run until the game is shut down.
/// </summary>
private void run()
{
while (!cancellationToken.IsCancellationRequested)
{
switch (State.Value)
if (state.Value == APIState.Failing)
{
case APIState.Failing:
//todo: replace this with a ping request.
log.Add(@"In a failing state, waiting a bit before we try again...");
// To recover from a failing state, falling through and running the full reconnection process seems safest for now.
// This could probably be replaced with a ping-style request if we want to avoid the reconnection overheads.
log.Add($@"{nameof(APIAccess)} is in a failing state, waiting a bit before we try again...");
Thread.Sleep(5000);
if (!IsLoggedIn) goto case APIState.Connecting;
if (queue.Count == 0)
{
log.Add(@"Queueing a ping request");
Queue(new GetUserRequest());
}
break;
case APIState.Offline:
case APIState.Connecting:
// work to restore a connection...
// Ensure that we have valid credentials.
// If not, setting the offline state will allow the game to prompt the user to provide new credentials.
if (!HasLogin)
{
state.Value = APIState.Offline;
@ -135,6 +128,61 @@ namespace osu.Game.Online.API
continue;
}
Debug.Assert(HasLogin);
// Ensure that we are in an online state. If not, attempt a connect.
if (state.Value != APIState.Online)
{
attemptConnect();
if (state.Value != APIState.Online)
continue;
}
// hard bail if we can't get a valid access token.
if (authentication.RequestAccessToken() == null)
{
Logout();
continue;
}
processQueuedRequests();
Thread.Sleep(50);
}
}
/// <summary>
/// Dequeue from the queue and run each request synchronously until the queue is empty.
/// </summary>
private void processQueuedRequests()
{
while (true)
{
APIRequest req;
lock (queue)
{
if (queue.Count == 0) return;
req = queue.Dequeue();
}
handleRequest(req);
}
}
/// <summary>
/// From a non-connected state, perform a full connection flow, obtaining OAuth tokens and populating the local user and friends.
/// </summary>
/// <remarks>
/// This method takes control of <see cref="state"/> and transitions from <see cref="APIState.Connecting"/> to either
/// - <see cref="APIState.Online"/> (successful connection)
/// - <see cref="APIState.Failing"/> (failed connection but retrying)
/// - <see cref="APIState.Offline"/> (failed and can't retry, clear credentials and require user interaction)
/// </remarks>
/// <returns>Whether the connection attempt was successful.</returns>
private void attemptConnect()
{
state.Value = APIState.Connecting;
if (localUser.IsDefault)
@ -163,21 +211,20 @@ namespace osu.Game.Online.API
{
//todo: this fails even on network-related issues. we should probably handle those differently.
LastLoginError = e;
log.Add(@"Login failed!");
password = null;
authentication.Clear();
continue;
log.Add($@"Login failed for username {ProvidedUsername} ({LastLoginError.Message})!");
Logout();
return;
}
}
var userReq = new GetUserRequest();
userReq.Failure += ex =>
{
if (ex is APIException)
{
LastLoginError = ex;
log.Add("Login failed on local user retrieval!");
log.Add($@"Login failed for username {ProvidedUsername} on user retrieval ({LastLoginError.Message})!");
Logout();
}
else if (ex is WebException webException && webException.Message == @"Unauthorized")
@ -186,7 +233,9 @@ namespace osu.Game.Online.API
Logout();
}
else
failConnectionProcess();
{
state.Value = APIState.Failing;
}
};
userReq.Success += user =>
{
@ -195,68 +244,32 @@ namespace osu.Game.Online.API
setLocalUser(user);
//we're connected!
// we're connected!
state.Value = APIState.Online;
failureCount = 0;
};
if (!handleRequest(userReq))
{
failConnectionProcess();
continue;
state.Value = APIState.Failing;
return;
}
// getting user's friends is considered part of the connection process.
var friendsReq = new GetFriendsRequest();
friendsReq.Failure += _ => failConnectionProcess();
friendsReq.Failure += _ => state.Value = APIState.Failing;
friendsReq.Success += res => friends.AddRange(res);
if (!handleRequest(friendsReq))
{
failConnectionProcess();
continue;
state.Value = APIState.Failing;
return;
}
// The Success callback event is fired on the main thread, so we should wait for that to run before proceeding.
// Without this, we will end up circulating this Connecting loop multiple times and queueing up many web requests
// before actually going online.
while (State.Value > APIState.Offline && State.Value < APIState.Online)
while (State.Value == APIState.Connecting && !cancellationToken.IsCancellationRequested)
Thread.Sleep(500);
break;
}
// hard bail if we can't get a valid access token.
if (authentication.RequestAccessToken() == null)
{
Logout();
continue;
}
while (true)
{
APIRequest req;
lock (queue)
{
if (queue.Count == 0) break;
req = queue.Dequeue();
}
handleRequest(req);
}
Thread.Sleep(50);
}
void failConnectionProcess()
{
// if something went wrong during the connection process, we want to reset the state (but only if still connecting).
if (State.Value == APIState.Connecting)
state.Value = APIState.Failing;
}
}
public void Perform(APIRequest request)