Refactor rcheevo http requests (again). This should better protect against the UI thread deadlocking due to Task semantics with WinForms
This commit is contained in:
parent
e065263ff2
commit
b517228475
|
@ -2,7 +2,6 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BizHawk.Client.EmuHawk
|
||||
{
|
||||
|
@ -10,37 +9,32 @@ namespace BizHawk.Client.EmuHawk
|
|||
{
|
||||
private readonly RCheevosAchievementListForm _cheevoListForm = new();
|
||||
|
||||
private class CheevoUnlockTask
|
||||
private sealed class CheevoUnlockRequest : RCheevoHttpRequest
|
||||
{
|
||||
private LibRCheevos.rc_api_award_achievement_request_t _apiParams;
|
||||
public Task Task { get; private set; }
|
||||
public bool Success { get; private set; }
|
||||
|
||||
private void CheevoUnlockTaskCallback(byte[] serv_resp)
|
||||
protected override void ResponseCallback(byte[] serv_resp)
|
||||
{
|
||||
var res = _lib.rc_api_process_award_achievement_response(out var resp, serv_resp);
|
||||
_lib.rc_api_destroy_award_achievement_response(ref resp);
|
||||
Success = res == LibRCheevos.rc_error_t.RC_OK;
|
||||
if (res != LibRCheevos.rc_error_t.RC_OK)
|
||||
{
|
||||
Console.WriteLine($"CheevoUnlockRequest failed in ResponseCallback with {res}");
|
||||
}
|
||||
}
|
||||
|
||||
public void DoRequest()
|
||||
public override void DoRequest()
|
||||
{
|
||||
var res = _lib.rc_api_init_award_achievement_request(out var api_req, ref _apiParams);
|
||||
Task = SendAPIRequestIfOK(res, ref api_req, CheevoUnlockTaskCallback);
|
||||
var apiParamsResult = _lib.rc_api_init_award_achievement_request(out var api_req, ref _apiParams);
|
||||
InternalDoRequest(apiParamsResult, ref api_req);
|
||||
}
|
||||
|
||||
public CheevoUnlockTask(string username, string api_token, int achievement_id, bool hardcore, string game_hash)
|
||||
public CheevoUnlockRequest(string username, string api_token, int achievement_id, bool hardcore, string game_hash)
|
||||
{
|
||||
_apiParams = new(username, api_token, achievement_id, hardcore, game_hash);
|
||||
DoRequest();
|
||||
}
|
||||
}
|
||||
|
||||
// keep a list of all cheevo unlock trigger tasks that have been queued
|
||||
// on Dispose(), we wait for all these to complete
|
||||
// on Update(), we clear out successfully completed tasks, any not completed will be resent
|
||||
private readonly List<CheevoUnlockTask> _queuedCheevoUnlockTasks = new();
|
||||
|
||||
private bool CheevosActive { get; set; }
|
||||
private bool AllowUnofficialCheevos { get; set; }
|
||||
|
||||
|
@ -54,8 +48,11 @@ namespace BizHawk.Client.EmuHawk
|
|||
public string Definition { get; }
|
||||
public string Author { get; }
|
||||
private string BadgeName { get; }
|
||||
public Bitmap BadgeUnlocked { get; private set; }
|
||||
public Bitmap BadgeLocked { get; private set; }
|
||||
public Bitmap BadgeUnlocked => _badgeUnlockedRequest?.Image;
|
||||
public Bitmap BadgeLocked => _badgeLockedRequest?.Image;
|
||||
|
||||
private ImageRequest _badgeUnlockedRequest, _badgeLockedRequest;
|
||||
|
||||
public DateTime Created { get; }
|
||||
public DateTime Updated { get; }
|
||||
|
||||
|
@ -83,10 +80,12 @@ namespace BizHawk.Client.EmuHawk
|
|||
public bool IsEnabled => !Invalid && (IsOfficial || AllowUnofficialCheevos());
|
||||
public bool IsOfficial => Category is LibRCheevos.rc_runtime_achievement_category_t.RC_ACHIEVEMENT_CATEGORY_CORE;
|
||||
|
||||
public void LoadImages()
|
||||
public void LoadImages(IList<RCheevoHttpRequest> requests)
|
||||
{
|
||||
GetImage(BadgeName, LibRCheevos.rc_api_image_type_t.RC_IMAGE_TYPE_ACHIEVEMENT, badge => BadgeUnlocked = badge);
|
||||
GetImage(BadgeName, LibRCheevos.rc_api_image_type_t.RC_IMAGE_TYPE_ACHIEVEMENT_LOCKED, badge => BadgeLocked = badge);
|
||||
_badgeUnlockedRequest = new(BadgeName, LibRCheevos.rc_api_image_type_t.RC_IMAGE_TYPE_ACHIEVEMENT);
|
||||
_badgeLockedRequest = new(BadgeName, LibRCheevos.rc_api_image_type_t.RC_IMAGE_TYPE_ACHIEVEMENT_LOCKED);
|
||||
requests.Add(_badgeUnlockedRequest);
|
||||
requests.Add(_badgeLockedRequest);
|
||||
}
|
||||
|
||||
public Cheevo(in LibRCheevos.rc_api_achievement_definition_t cheevo, Func<bool> allowUnofficialCheevos)
|
||||
|
@ -99,8 +98,6 @@ namespace BizHawk.Client.EmuHawk
|
|||
Definition = cheevo.Definition;
|
||||
Author = cheevo.Author;
|
||||
BadgeName = cheevo.BadgeName;
|
||||
BadgeUnlocked = null;
|
||||
BadgeLocked = null;
|
||||
Created = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc).AddSeconds(cheevo.created).ToLocalTime();
|
||||
Updated = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc).AddSeconds(cheevo.updated).ToLocalTime();
|
||||
IsSoftcoreUnlocked = false;
|
||||
|
@ -120,8 +117,6 @@ namespace BizHawk.Client.EmuHawk
|
|||
Definition = cheevo.Definition;
|
||||
Author = cheevo.Author;
|
||||
BadgeName = cheevo.BadgeName;
|
||||
BadgeUnlocked = null;
|
||||
BadgeLocked = null;
|
||||
Created = cheevo.Created;
|
||||
Updated = cheevo.Updated;
|
||||
IsSoftcoreUnlocked = false;
|
||||
|
@ -148,7 +143,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
return;
|
||||
}
|
||||
|
||||
_activeModeUnlocksTask.Wait();
|
||||
_activeModeUnlocksRequest.Wait();
|
||||
|
||||
foreach (var cheevo in _gameData.CheevoEnumerable)
|
||||
{
|
||||
|
@ -173,7 +168,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
{
|
||||
if (_gameData == null || _gameData.GameID == 0) return;
|
||||
|
||||
_inactiveModeUnlocksTask.Wait();
|
||||
_inactiveModeUnlocksRequest.Wait();
|
||||
|
||||
foreach (var cheevo in _gameData.CheevoEnumerable)
|
||||
{
|
||||
|
@ -183,7 +178,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
}
|
||||
}
|
||||
|
||||
_activeModeUnlocksTask.Wait();
|
||||
_activeModeUnlocksRequest.Wait();
|
||||
|
||||
foreach (var cheevo in _gameData.CheevoEnumerable)
|
||||
{
|
||||
|
|
|
@ -4,7 +4,6 @@ using System.Drawing;
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using BizHawk.Common.CollectionExtensions;
|
||||
|
||||
|
@ -22,7 +21,131 @@ namespace BizHawk.Client.EmuHawk
|
|||
private GameData _gameData;
|
||||
private readonly Dictionary<int, GameData> _cachedGameDatas = new(); // keep game data around to avoid unneeded API calls for a simple RebootCore
|
||||
|
||||
private Task _activeModeUnlocksTask, _inactiveModeUnlocksTask;
|
||||
public sealed class UserUnlocksRequest : RCheevoHttpRequest
|
||||
{
|
||||
private LibRCheevos.rc_api_fetch_user_unlocks_request_t _apiParams;
|
||||
private readonly IReadOnlyDictionary<int, Cheevo> _cheevos;
|
||||
private readonly Action _activeModeCallback;
|
||||
|
||||
protected override void ResponseCallback(byte[] serv_resp)
|
||||
{
|
||||
var res = _lib.rc_api_process_fetch_user_unlocks_response(out var resp, serv_resp);
|
||||
if (res == LibRCheevos.rc_error_t.RC_OK)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
var unlocks = (int*)resp.achievement_ids;
|
||||
for (var i = 0; i < resp.num_achievement_ids; i++)
|
||||
{
|
||||
if (_cheevos.TryGetValue(unlocks![i], out var cheevo))
|
||||
{
|
||||
cheevo.SetUnlocked(_apiParams.hardcore, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_activeModeCallback?.Invoke();
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"UserUnlocksRequest failed in ResponseCallback with {res}");
|
||||
}
|
||||
|
||||
_lib.rc_api_destroy_fetch_user_unlocks_response(ref resp);
|
||||
}
|
||||
|
||||
public override void DoRequest()
|
||||
{
|
||||
var apiParamsResult = _lib.rc_api_init_fetch_user_unlocks_request(out var api_req, ref _apiParams);
|
||||
InternalDoRequest(apiParamsResult, ref api_req);
|
||||
}
|
||||
|
||||
public UserUnlocksRequest(string username, string api_token, int game_id, bool hardcore,
|
||||
IReadOnlyDictionary<int, Cheevo> cheevos, Action activeModeCallback = null)
|
||||
{
|
||||
_apiParams = new(username, api_token, game_id, hardcore);
|
||||
_cheevos = cheevos;
|
||||
_activeModeCallback = activeModeCallback;
|
||||
}
|
||||
}
|
||||
|
||||
private RCheevoHttpRequest _activeModeUnlocksRequest, _inactiveModeUnlocksRequest;
|
||||
|
||||
private sealed class GameDataRequest : RCheevoHttpRequest
|
||||
{
|
||||
private LibRCheevos.rc_api_fetch_game_data_request_t _apiParams;
|
||||
private readonly Func<bool> _allowUnofficialCheevos;
|
||||
|
||||
public GameData GameData { get; private set; }
|
||||
|
||||
protected override void ResponseCallback(byte[] serv_resp)
|
||||
{
|
||||
var res = _lib.rc_api_process_fetch_game_data_response(out var resp, serv_resp);
|
||||
if (res == LibRCheevos.rc_error_t.RC_OK)
|
||||
{
|
||||
GameData = new(in resp, _allowUnofficialCheevos);
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"GameDataRequest failed in ResponseCallback with {res}");
|
||||
}
|
||||
|
||||
_lib.rc_api_destroy_fetch_game_data_response(ref resp);
|
||||
}
|
||||
|
||||
public override void DoRequest()
|
||||
{
|
||||
GameData = new();
|
||||
var apiParamsResult = _lib.rc_api_init_fetch_game_data_request(out var api_req, ref _apiParams);
|
||||
InternalDoRequest(apiParamsResult, ref api_req);
|
||||
}
|
||||
|
||||
public GameDataRequest(string username, string api_token, int game_id, Func<bool> allowUnofficialCheevos)
|
||||
{
|
||||
_apiParams = new(username, api_token, game_id);
|
||||
_allowUnofficialCheevos = allowUnofficialCheevos;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class ImageRequest : RCheevoHttpRequest
|
||||
{
|
||||
private LibRCheevos.rc_api_fetch_image_request_t _apiParams;
|
||||
|
||||
public Bitmap Image { get; private set; }
|
||||
|
||||
public override bool ShouldRetry => false;
|
||||
|
||||
protected override void ResponseCallback(byte[] serv_resp)
|
||||
{
|
||||
try
|
||||
{
|
||||
var image = new Bitmap(new MemoryStream(serv_resp));
|
||||
Image = image;
|
||||
}
|
||||
catch
|
||||
{
|
||||
Image = null;
|
||||
}
|
||||
}
|
||||
|
||||
public override void DoRequest()
|
||||
{
|
||||
Image = null;
|
||||
|
||||
if (_apiParams.image_name is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var apiParamsResult = _lib.rc_api_init_fetch_image_request(out var api_req, ref _apiParams);
|
||||
InternalDoRequest(apiParamsResult, ref api_req);
|
||||
}
|
||||
|
||||
public ImageRequest(string image_name, LibRCheevos.rc_api_image_type_t image_type)
|
||||
{
|
||||
_apiParams = new(image_name, image_type);
|
||||
}
|
||||
}
|
||||
|
||||
public class GameData
|
||||
{
|
||||
|
@ -30,9 +153,11 @@ namespace BizHawk.Client.EmuHawk
|
|||
public ConsoleID ConsoleID { get; }
|
||||
public string Title { get; }
|
||||
private string ImageName { get; }
|
||||
public Bitmap GameBadge { get; private set; }
|
||||
public Bitmap GameBadge => _gameBadgeImageRequest?.Image;
|
||||
public string RichPresenseScript { get; }
|
||||
|
||||
private ImageRequest _gameBadgeImageRequest;
|
||||
|
||||
private readonly IReadOnlyDictionary<int, Cheevo> _cheevos;
|
||||
private readonly IReadOnlyDictionary<int, LBoard> _lboards;
|
||||
|
||||
|
@ -42,43 +167,26 @@ namespace BizHawk.Client.EmuHawk
|
|||
public Cheevo GetCheevoById(int i) => _cheevos[i];
|
||||
public LBoard GetLboardById(int i) => _lboards[i];
|
||||
|
||||
public Task InitUnlocks(string username, string api_token, bool hardcore, Action callback = null)
|
||||
public UserUnlocksRequest InitUnlocks(string username, string api_token, bool hardcore, Action callback = null)
|
||||
{
|
||||
var api_params = new LibRCheevos.rc_api_fetch_user_unlocks_request_t(username, api_token, GameID, hardcore);
|
||||
var res = _lib.rc_api_init_fetch_user_unlocks_request(out var api_req, ref api_params);
|
||||
return SendAPIRequestIfOK(res, ref api_req, serv_resp =>
|
||||
{
|
||||
if (_lib.rc_api_process_fetch_user_unlocks_response(out var resp, serv_resp) == LibRCheevos.rc_error_t.RC_OK)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
var unlocks = (int*)resp.achievement_ids;
|
||||
for (var i = 0; i < resp.num_achievement_ids; i++)
|
||||
{
|
||||
if (_cheevos.TryGetValue(unlocks![i], out var cheevo))
|
||||
{
|
||||
cheevo.SetUnlocked(hardcore, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_lib.rc_api_destroy_fetch_user_unlocks_response(ref resp);
|
||||
}
|
||||
|
||||
callback?.Invoke();
|
||||
});
|
||||
return new(username, api_token, GameID, hardcore, _cheevos, callback);
|
||||
}
|
||||
|
||||
public void LoadImages()
|
||||
public IEnumerable<RCheevoHttpRequest> LoadImages()
|
||||
{
|
||||
GetImage(ImageName, LibRCheevos.rc_api_image_type_t.RC_IMAGE_TYPE_GAME, badge => GameBadge = badge);
|
||||
var requests = new List<RCheevoHttpRequest>(1 + (_cheevos?.Count ?? 0) * 2);
|
||||
|
||||
if (_cheevos is null) return;
|
||||
_gameBadgeImageRequest = new(ImageName, LibRCheevos.rc_api_image_type_t.RC_IMAGE_TYPE_GAME);
|
||||
requests.Add(_gameBadgeImageRequest);
|
||||
|
||||
if (_cheevos is null) return requests;
|
||||
|
||||
foreach (var cheevo in _cheevos.Values)
|
||||
{
|
||||
cheevo.LoadImages();
|
||||
cheevo.LoadImages(requests);
|
||||
}
|
||||
|
||||
return requests;
|
||||
}
|
||||
|
||||
public int TotalCheevoPoints(bool hardcore)
|
||||
|
@ -90,7 +198,6 @@ namespace BizHawk.Client.EmuHawk
|
|||
ConsoleID = resp.console_id;
|
||||
Title = resp.Title;
|
||||
ImageName = resp.ImageName;
|
||||
GameBadge = null;
|
||||
RichPresenseScript = resp.RichPresenceScript;
|
||||
|
||||
var cheevos = new Dictionary<int, Cheevo>();
|
||||
|
@ -118,7 +225,6 @@ namespace BizHawk.Client.EmuHawk
|
|||
ConsoleID = gameData.ConsoleID;
|
||||
Title = gameData.Title;
|
||||
ImageName = gameData.ImageName;
|
||||
GameBadge = null;
|
||||
RichPresenseScript = gameData.RichPresenseScript;
|
||||
|
||||
_cheevos = gameData.CheevoEnumerable.ToDictionary<Cheevo, int, Cheevo>(cheevo => cheevo.ID, cheevo => new(in cheevo, allowUnofficialCheevos));
|
||||
|
@ -131,22 +237,48 @@ namespace BizHawk.Client.EmuHawk
|
|||
}
|
||||
}
|
||||
|
||||
private static int SendHash(string hash)
|
||||
private sealed class ResolveHashRequest : RCheevoHttpRequest
|
||||
{
|
||||
var api_params = new LibRCheevos.rc_api_resolve_hash_request_t(null, null, hash);
|
||||
var ret = 0;
|
||||
var res = _lib.rc_api_init_resolve_hash_request(out var api_req, ref api_params);
|
||||
SendAPIRequestIfOK(res, ref api_req, serv_resp =>
|
||||
private LibRCheevos.rc_api_resolve_hash_request_t _apiParams;
|
||||
public int GameID { get; private set; }
|
||||
|
||||
// eh? not sure I want this retried, giving the blocking behavior
|
||||
public override bool ShouldRetry => false;
|
||||
|
||||
protected override void ResponseCallback(byte[] serv_resp)
|
||||
{
|
||||
if (_lib.rc_api_process_resolve_hash_response(out var resp, serv_resp) == LibRCheevos.rc_error_t.RC_OK)
|
||||
var res = _lib.rc_api_process_resolve_hash_response(out var resp, serv_resp);
|
||||
if (res == LibRCheevos.rc_error_t.RC_OK)
|
||||
{
|
||||
ret = resp.game_id;
|
||||
GameID = resp.game_id;
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"ResolveHashRequest failed in ResponseCallback with {res}");
|
||||
}
|
||||
|
||||
_lib.rc_api_destroy_resolve_hash_response(ref resp);
|
||||
}).Wait(); // currently, this is done synchronously
|
||||
}
|
||||
|
||||
return ret;
|
||||
public override void DoRequest()
|
||||
{
|
||||
GameID = 0;
|
||||
var apiParamsResult = _lib.rc_api_init_resolve_hash_request(out var api_req, ref _apiParams);
|
||||
InternalDoRequest(apiParamsResult, ref api_req);
|
||||
}
|
||||
|
||||
public ResolveHashRequest(string hash)
|
||||
{
|
||||
_apiParams = new(null, null, hash);
|
||||
}
|
||||
}
|
||||
|
||||
private int SendHash(string hash)
|
||||
{
|
||||
var resolveHashRequest = new ResolveHashRequest(hash);
|
||||
_inactiveHttpRequests.Push(resolveHashRequest);
|
||||
resolveHashRequest.Wait(); // currently, this is done synchronously
|
||||
return resolveHashRequest.GameID;
|
||||
}
|
||||
|
||||
protected override int IdentifyHash(string hash)
|
||||
|
@ -169,7 +301,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
|
||||
private void InitGameData()
|
||||
{
|
||||
_activeModeUnlocksTask = _gameData.InitUnlocks(Username, ApiToken, HardcoreMode, () =>
|
||||
_activeModeUnlocksRequest = _gameData.InitUnlocks(Username, ApiToken, HardcoreMode, () =>
|
||||
{
|
||||
foreach (var cheevo in _gameData.CheevoEnumerable)
|
||||
{
|
||||
|
@ -179,9 +311,13 @@ namespace BizHawk.Client.EmuHawk
|
|||
}
|
||||
}
|
||||
});
|
||||
_inactiveHttpRequests.Push(_activeModeUnlocksRequest);
|
||||
|
||||
_inactiveModeUnlocksTask = _gameData.InitUnlocks(Username, ApiToken, !HardcoreMode);
|
||||
_gameData.LoadImages();
|
||||
_inactiveModeUnlocksRequest = _gameData.InitUnlocks(Username, ApiToken, !HardcoreMode);
|
||||
_inactiveHttpRequests.Push(_inactiveModeUnlocksRequest);
|
||||
|
||||
var loadImageRequests = _gameData.LoadImages();
|
||||
_inactiveHttpRequests.PushRange(loadImageRequests.ToArray());
|
||||
|
||||
foreach (var lboard in _gameData.LBoardEnumerable)
|
||||
{
|
||||
|
@ -194,48 +330,12 @@ namespace BizHawk.Client.EmuHawk
|
|||
}
|
||||
}
|
||||
|
||||
private static GameData GetGameData(string username, string api_token, int id, Func<bool> allowUnofficialCheevos)
|
||||
private GameData GetGameData(int id)
|
||||
{
|
||||
var api_params = new LibRCheevos.rc_api_fetch_game_data_request_t(username, api_token, id);
|
||||
var ret = new GameData();
|
||||
var res = _lib.rc_api_init_fetch_game_data_request(out var api_req, ref api_params);
|
||||
SendAPIRequestIfOK(res, ref api_req, serv_resp =>
|
||||
{
|
||||
if (_lib.rc_api_process_fetch_game_data_response(out var resp, serv_resp) == LibRCheevos.rc_error_t.RC_OK)
|
||||
{
|
||||
ret = new(in resp, allowUnofficialCheevos);
|
||||
}
|
||||
|
||||
_lib.rc_api_destroy_fetch_game_data_response(ref resp);
|
||||
}).Wait();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static void GetImage(string image_name, LibRCheevos.rc_api_image_type_t image_type, Action<Bitmap> callback)
|
||||
{
|
||||
if (image_name is null)
|
||||
{
|
||||
callback(null);
|
||||
return;
|
||||
}
|
||||
|
||||
var api_params = new LibRCheevos.rc_api_fetch_image_request_t(image_name, image_type);
|
||||
var res = _lib.rc_api_init_fetch_image_request(out var api_req, ref api_params);
|
||||
SendAPIRequestIfOK(res, ref api_req, serv_resp =>
|
||||
{
|
||||
Bitmap image;
|
||||
try
|
||||
{
|
||||
image = new(new MemoryStream(serv_resp));
|
||||
}
|
||||
catch
|
||||
{
|
||||
image = null;
|
||||
}
|
||||
|
||||
callback(image);
|
||||
});
|
||||
var gameDataRequest = new GameDataRequest(Username, ApiToken, id, () => AllowUnofficialCheevos);
|
||||
_inactiveHttpRequests.Push(gameDataRequest);
|
||||
gameDataRequest.Wait();
|
||||
return gameDataRequest.GameData;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
@ -11,14 +13,188 @@ namespace BizHawk.Client.EmuHawk
|
|||
{
|
||||
private static readonly HttpClient _http = new() { DefaultRequestHeaders = { ConnectionClose = true } };
|
||||
|
||||
/// <summary>
|
||||
/// A concurrent stack containing all pending HTTP requests
|
||||
/// The main thread will push new requests onto this stack
|
||||
/// The HTTP thread will pop requests and start them
|
||||
/// </summary>
|
||||
private readonly ConcurrentStack<RCheevoHttpRequest> _inactiveHttpRequests = new();
|
||||
|
||||
/// <summary>
|
||||
/// A list containing all currently active HTTP requests
|
||||
/// Completed requests might be restarted if ShouldRetry is true
|
||||
/// Otherwise, the completed request is disposed and removed
|
||||
/// Only the HTTP thread is allowed to use this list, no other thread may use it
|
||||
/// </summary>
|
||||
private readonly List<RCheevoHttpRequest> _activeHttpRequests = new();
|
||||
|
||||
private volatile bool _isActive;
|
||||
private readonly Thread _httpThread;
|
||||
|
||||
/// <summary>
|
||||
/// Base class for all HTTP requests to rcheevos servers
|
||||
/// </summary>
|
||||
public abstract class RCheevoHttpRequest : IDisposable
|
||||
{
|
||||
private readonly object _syncObject = new();
|
||||
private readonly ManualResetEventSlim _completionEvent = new();
|
||||
private bool _isDisposed;
|
||||
|
||||
public virtual bool ShouldRetry { get; protected set; }
|
||||
|
||||
public bool IsCompleted
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_syncObject)
|
||||
{
|
||||
return _isDisposed || _completionEvent.IsSet;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public abstract void DoRequest();
|
||||
protected abstract void ResponseCallback(byte[] serv_resp);
|
||||
|
||||
public void Wait()
|
||||
{
|
||||
lock (_syncObject)
|
||||
{
|
||||
if (_isDisposed) return;
|
||||
_completionEvent.Wait();
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void Dispose()
|
||||
{
|
||||
if (_isDisposed) return;
|
||||
|
||||
lock (_syncObject)
|
||||
{
|
||||
_completionEvent.Wait();
|
||||
_completionEvent.Dispose();
|
||||
_isDisposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Don't use, for FailedRCheevosRequest use only
|
||||
/// </summary>
|
||||
protected void DisposeWithoutWait()
|
||||
{
|
||||
#pragma warning disable BHI1101 // yeah, complain I guess, but this is a hack so meh
|
||||
if (GetType() != typeof(FailedRCheevosRequest)) throw new InvalidOperationException();
|
||||
#pragma warning restore BHI1101
|
||||
_completionEvent.Dispose();
|
||||
_isDisposed = true;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
ShouldRetry = false;
|
||||
_completionEvent.Reset();
|
||||
}
|
||||
|
||||
protected void InternalDoRequest(LibRCheevos.rc_error_t apiParamsResult, ref LibRCheevos.rc_api_request_t request)
|
||||
{
|
||||
if (apiParamsResult != LibRCheevos.rc_error_t.RC_OK)
|
||||
{
|
||||
// api params were bad, so we can't send a request
|
||||
// therefore any retry will fail
|
||||
ShouldRetry = false;
|
||||
_completionEvent.Set();
|
||||
_lib.rc_api_destroy_request(ref request);
|
||||
return;
|
||||
}
|
||||
|
||||
var apiTask = request.post_data != IntPtr.Zero
|
||||
? HttpPost(request.URL, request.PostData)
|
||||
: HttpGet(request.URL);
|
||||
apiTask.ConfigureAwait(false);
|
||||
|
||||
_lib.rc_api_destroy_request(ref request);
|
||||
var result = apiTask.Result; // FIXME: THIS IS BAD (but kind of needed?)
|
||||
|
||||
if (result is null) // likely a timeout
|
||||
{
|
||||
ShouldRetry = true;
|
||||
_completionEvent.Set();
|
||||
return;
|
||||
}
|
||||
|
||||
ResponseCallback(result);
|
||||
|
||||
ShouldRetry = false; // this is a bit naive, but if the response callback "fails," retrying will just result in the same thing
|
||||
_completionEvent.Set();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a generic failed rcheevos request
|
||||
/// </summary>
|
||||
public sealed class FailedRCheevosRequest : RCheevoHttpRequest
|
||||
{
|
||||
public static readonly FailedRCheevosRequest Singleton = new();
|
||||
|
||||
public override bool ShouldRetry => false;
|
||||
|
||||
protected override void ResponseCallback(byte[] serv_resp)
|
||||
{
|
||||
}
|
||||
|
||||
public override void DoRequest()
|
||||
{
|
||||
}
|
||||
|
||||
private FailedRCheevosRequest()
|
||||
{
|
||||
DisposeWithoutWait();
|
||||
}
|
||||
}
|
||||
|
||||
private void HttpRequestThreadProc()
|
||||
{
|
||||
while (_isActive)
|
||||
{
|
||||
if (_inactiveHttpRequests.TryPop(out var request))
|
||||
{
|
||||
Task.Run(request.DoRequest);
|
||||
_activeHttpRequests.Add(request);
|
||||
}
|
||||
|
||||
foreach (var activeRequest in _activeHttpRequests.Where(activeRequest => activeRequest.IsCompleted && activeRequest.ShouldRetry).ToArray())
|
||||
{
|
||||
activeRequest.Reset();
|
||||
Task.Run(activeRequest.DoRequest);
|
||||
}
|
||||
|
||||
_activeHttpRequests.RemoveAll(activeRequest =>
|
||||
{
|
||||
var shouldRemove = activeRequest.IsCompleted && !activeRequest.ShouldRetry;
|
||||
if (shouldRemove)
|
||||
{
|
||||
activeRequest.Dispose();
|
||||
}
|
||||
|
||||
return shouldRemove;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<byte[]> HttpGet(string url)
|
||||
{
|
||||
var response = await _http.GetAsync(url).ConfigureAwait(false);
|
||||
if (response.IsSuccessStatusCode)
|
||||
try
|
||||
{
|
||||
return await response.Content.ReadAsByteArrayAsync().ConfigureAwait(false);
|
||||
var response = await _http.GetAsync(url).ConfigureAwait(false);
|
||||
return response.IsSuccessStatusCode
|
||||
? await response.Content.ReadAsByteArrayAsync().ConfigureAwait(false)
|
||||
: null;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e);
|
||||
return null;
|
||||
}
|
||||
return new byte[1];
|
||||
}
|
||||
|
||||
private static async Task<byte[]> HttpPost(string url, string post)
|
||||
|
@ -27,39 +203,15 @@ namespace BizHawk.Client.EmuHawk
|
|||
{
|
||||
using var content = new StringContent(post, Encoding.UTF8, "application/x-www-form-urlencoded");
|
||||
using var response = await _http.PostAsync(url, content).ConfigureAwait(false);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
return new byte[1];
|
||||
}
|
||||
return await response.Content.ReadAsByteArrayAsync().ConfigureAwait(false);
|
||||
return response.IsSuccessStatusCode
|
||||
? await response.Content.ReadAsByteArrayAsync().ConfigureAwait(false)
|
||||
: null;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e);
|
||||
return new byte[1];
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static Task SendAPIRequest(in LibRCheevos.rc_api_request_t api_req, Action<byte[]> callback)
|
||||
{
|
||||
var isPost = api_req.post_data != IntPtr.Zero;
|
||||
var url = api_req.URL;
|
||||
var postData = isPost ? api_req.PostData : null;
|
||||
return Task.Factory.StartNew(() =>
|
||||
{
|
||||
var apiRequestTask = isPost ? HttpPost(url, postData) : HttpGet(url);
|
||||
callback(apiRequestTask.Result);
|
||||
}, TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
}
|
||||
|
||||
private static Task SendAPIRequestIfOK(LibRCheevos.rc_error_t res, ref LibRCheevos.rc_api_request_t api_req, Action<byte[]> callback)
|
||||
{
|
||||
var ret = res == LibRCheevos.rc_error_t.RC_OK
|
||||
? SendAPIRequest(in api_req, callback)
|
||||
: Task.CompletedTask;
|
||||
_lib.rc_api_destroy_request(ref api_req);
|
||||
// TODO: report failures when res is not RC_OK (can be done in this function, as it's the main thread)
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BizHawk.Client.EmuHawk
|
||||
{
|
||||
|
@ -10,39 +9,33 @@ namespace BizHawk.Client.EmuHawk
|
|||
private readonly RCheevosLeaderboardListForm _lboardListForm = new();
|
||||
#endif
|
||||
|
||||
private class LboardTriggerTask
|
||||
private sealed class LboardTriggerRequest : RCheevoHttpRequest
|
||||
{
|
||||
private LibRCheevos.rc_api_submit_lboard_entry_request_t _apiParams;
|
||||
public Task Task { get; private set; }
|
||||
public bool Success { get; private set; }
|
||||
|
||||
private void LboardTriggerTaskCallback(byte[] serv_resp)
|
||||
protected override void ResponseCallback(byte[] serv_resp)
|
||||
{
|
||||
var res = _lib.rc_api_process_submit_lboard_entry_response(out var resp, serv_resp);
|
||||
_lib.rc_api_destroy_submit_lboard_entry_response(ref resp);
|
||||
Success = res == LibRCheevos.rc_error_t.RC_OK;
|
||||
if (res != LibRCheevos.rc_error_t.RC_OK)
|
||||
{
|
||||
Console.WriteLine($"LboardTriggerRequest failed in ResponseCallback with {res}");
|
||||
}
|
||||
}
|
||||
|
||||
public void DoRequest()
|
||||
public override void DoRequest()
|
||||
{
|
||||
var res = _lib.rc_api_init_submit_lboard_entry_request(out var api_req, ref _apiParams);
|
||||
Task = SendAPIRequestIfOK(res, ref api_req, LboardTriggerTaskCallback);
|
||||
var apiParamsResult = _lib.rc_api_init_submit_lboard_entry_request(out var api_req, ref _apiParams);
|
||||
InternalDoRequest(apiParamsResult, ref api_req);
|
||||
}
|
||||
|
||||
public LboardTriggerTask(string username, string api_token, int id, int value, string hash)
|
||||
public LboardTriggerRequest(string username, string api_token, int id, int value, string hash)
|
||||
{
|
||||
_apiParams = new(username, api_token, id, value, hash);
|
||||
DoRequest();
|
||||
}
|
||||
}
|
||||
|
||||
// keep a list of all cheevo unlock trigger tasks that have been queued
|
||||
// on Dispose(), we wait for all these to complete
|
||||
// on Update(), we clear out successfully completed tasks, any not completed will be resent
|
||||
private readonly List<LboardTriggerTask> _queuedLboardTriggerTasks = new();
|
||||
|
||||
private bool LBoardsActive { get; set; }
|
||||
|
||||
private LBoard CurrentLboard { get; set; }
|
||||
|
||||
public class LBoard
|
||||
|
|
|
@ -9,15 +9,30 @@ namespace BizHawk.Client.EmuHawk
|
|||
|
||||
private event Action LoginStatusChanged;
|
||||
|
||||
private bool DoLogin(string username, string apiToken = null, string password = null)
|
||||
private sealed class LoginRequest : RCheevoHttpRequest
|
||||
{
|
||||
Username = null;
|
||||
ApiToken = null;
|
||||
private LibRCheevos.rc_api_login_request_t _apiParams;
|
||||
public string Username { get; private set; }
|
||||
public string ApiToken { get; private set; }
|
||||
|
||||
var api_params = new LibRCheevos.rc_api_login_request_t(username, apiToken, password);
|
||||
var res = _lib.rc_api_init_login_request(out var api_req, ref api_params);
|
||||
SendAPIRequestIfOK(res, ref api_req, serv_resp =>
|
||||
public override bool ShouldRetry => false;
|
||||
|
||||
public LoginRequest(string username, string apiToken = null, string password = null)
|
||||
{
|
||||
_apiParams = new(username, apiToken, password);
|
||||
}
|
||||
|
||||
public override void DoRequest()
|
||||
{
|
||||
var apiParamsResult = _lib.rc_api_init_login_request(out var api_req, ref _apiParams);
|
||||
InternalDoRequest(apiParamsResult, ref api_req);
|
||||
}
|
||||
|
||||
protected override void ResponseCallback(byte[] serv_resp)
|
||||
{
|
||||
Username = null;
|
||||
ApiToken = null;
|
||||
|
||||
if (_lib.rc_api_process_login_response(out var resp, serv_resp) == LibRCheevos.rc_error_t.RC_OK)
|
||||
{
|
||||
Username = resp.Username;
|
||||
|
@ -25,7 +40,17 @@ namespace BizHawk.Client.EmuHawk
|
|||
}
|
||||
|
||||
_lib.rc_api_destroy_login_response(ref resp);
|
||||
}).Wait(); // currently, this is done synchronously
|
||||
}
|
||||
}
|
||||
|
||||
private bool DoLogin(string username, string apiToken = null, string password = null)
|
||||
{
|
||||
var loginRequest = new LoginRequest(username, apiToken, password);
|
||||
_inactiveHttpRequests.Push(loginRequest);
|
||||
loginRequest.Wait();
|
||||
|
||||
Username = loginRequest.Username;
|
||||
ApiToken = loginRequest.ApiToken;
|
||||
|
||||
return LoggedIn;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
using System;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BizHawk.Client.EmuHawk
|
||||
{
|
||||
|
@ -8,31 +7,67 @@ namespace BizHawk.Client.EmuHawk
|
|||
{
|
||||
private bool RichPresenceActive { get; set; }
|
||||
private string CurrentRichPresence { get; set; }
|
||||
private bool GameSessionStartSuccessful { get; set; }
|
||||
|
||||
private Task _startGameSessionTask;
|
||||
private sealed class StartGameSessionRequest : RCheevoHttpRequest
|
||||
{
|
||||
private LibRCheevos.rc_api_start_session_request_t _apiParams;
|
||||
|
||||
public StartGameSessionRequest(string username, string apiToken, int gameId)
|
||||
{
|
||||
_apiParams = new(username, apiToken, gameId);
|
||||
}
|
||||
|
||||
public override void DoRequest()
|
||||
{
|
||||
var apiParamsResult = _lib.rc_api_init_start_session_request(out var api_req, ref _apiParams);
|
||||
InternalDoRequest(apiParamsResult, ref api_req);
|
||||
}
|
||||
|
||||
protected override void ResponseCallback(byte[] serv_resp)
|
||||
{
|
||||
var res = _lib.rc_api_process_start_session_response(out var resp, serv_resp);
|
||||
_lib.rc_api_destroy_start_session_response(ref resp);
|
||||
if (res != LibRCheevos.rc_error_t.RC_OK)
|
||||
{
|
||||
Console.WriteLine($"StartGameSessionRequest failed in ResponseCallback with {res}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void StartGameSession()
|
||||
{
|
||||
GameSessionStartSuccessful = false;
|
||||
var api_params = new LibRCheevos.rc_api_start_session_request_t(Username, ApiToken, _gameData.GameID);
|
||||
var res = _lib.rc_api_init_start_session_request(out var api_req, ref api_params);
|
||||
_startGameSessionTask = SendAPIRequestIfOK(res, ref api_req, serv_resp =>
|
||||
{
|
||||
GameSessionStartSuccessful = _lib.rc_api_process_start_session_response(out var resp, serv_resp) == LibRCheevos.rc_error_t.RC_OK;
|
||||
_lib.rc_api_destroy_start_session_response(ref resp);
|
||||
});
|
||||
_inactiveHttpRequests.Push(new StartGameSessionRequest(Username, ApiToken, _gameData.GameID));
|
||||
}
|
||||
|
||||
private static void SendPing(string username, string api_token, int id, string rich_presence)
|
||||
private sealed class PingRequest : RCheevoHttpRequest
|
||||
{
|
||||
var api_params = new LibRCheevos.rc_api_ping_request_t(username, api_token, id, rich_presence);
|
||||
var res = _lib.rc_api_init_ping_request(out var api_req, ref api_params);
|
||||
SendAPIRequestIfOK(res, ref api_req, static serv_resp =>
|
||||
private LibRCheevos.rc_api_ping_request_t _apiParams;
|
||||
|
||||
public PingRequest(string username, string apiToken, int gameId, string richPresence)
|
||||
{
|
||||
_lib.rc_api_process_ping_response(out var resp, serv_resp);
|
||||
_apiParams = new(username, apiToken, gameId, richPresence);
|
||||
}
|
||||
|
||||
public override void DoRequest()
|
||||
{
|
||||
var apiParamsResult = _lib.rc_api_init_ping_request(out var api_req, ref _apiParams);
|
||||
InternalDoRequest(apiParamsResult, ref api_req);
|
||||
}
|
||||
|
||||
protected override void ResponseCallback(byte[] serv_resp)
|
||||
{
|
||||
var res = _lib.rc_api_process_ping_response(out var resp, serv_resp);
|
||||
_lib.rc_api_destroy_ping_response(ref resp);
|
||||
});
|
||||
if (res != LibRCheevos.rc_error_t.RC_OK)
|
||||
{
|
||||
Console.WriteLine($"PingRequest failed in ResponseCallback with {res}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SendPing()
|
||||
{
|
||||
_inactiveHttpRequests.Push(new PingRequest(Username, ApiToken, _gameData.GameID, CurrentRichPresence));
|
||||
}
|
||||
|
||||
private readonly byte[] _richPresenceBuffer = new byte[1024];
|
||||
|
@ -54,7 +89,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
|
||||
var now = DateTime.Now;
|
||||
if (now - _lastPingTime < _pingCooldown) return;
|
||||
SendPing(Username, ApiToken, _gameData.GameID, CurrentRichPresence);
|
||||
SendPing();
|
||||
_lastPingTime = now;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
|
||||
using BizHawk.BizInvoke;
|
||||
|
@ -195,6 +194,10 @@ namespace BizHawk.Client.EmuHawk
|
|||
Func<Config> getConfig, ToolStripItemCollection raDropDownItems, Action shutdownRACallback)
|
||||
: base(mainForm, inputManager, tools, getConfig, raDropDownItems, shutdownRACallback)
|
||||
{
|
||||
_isActive = true;
|
||||
_httpThread = new(HttpRequestThreadProc) { IsBackground = true };
|
||||
_httpThread.Start();
|
||||
|
||||
_runtime = default;
|
||||
_lib.rc_runtime_init(ref _runtime);
|
||||
Login();
|
||||
|
@ -215,8 +218,21 @@ namespace BizHawk.Client.EmuHawk
|
|||
|
||||
public override void Dispose()
|
||||
{
|
||||
Task.WaitAll(_queuedCheevoUnlockTasks.Select(t => t.Task).ToArray());
|
||||
Task.WaitAll(_queuedLboardTriggerTasks.Select(t => t.Task).ToArray());
|
||||
while (!_inactiveHttpRequests.IsEmpty)
|
||||
{
|
||||
// wait until all pending http requests are enqueued
|
||||
}
|
||||
|
||||
_isActive = false;
|
||||
_httpThread.Join();
|
||||
|
||||
// the http thread is dead, so we can safely use _activeHttpRequests
|
||||
foreach (var request in _activeHttpRequests)
|
||||
{
|
||||
if (request is ImageRequest) continue; // THIS IS BAD, I KNOW
|
||||
request.Dispose(); // implicitly waits for the request to finish or timeout
|
||||
}
|
||||
|
||||
_lib.rc_runtime_destroy(ref _runtime);
|
||||
Stop();
|
||||
_gameInfoForm.Dispose();
|
||||
|
@ -234,7 +250,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
return;
|
||||
}
|
||||
|
||||
_activeModeUnlocksTask.Wait();
|
||||
_activeModeUnlocksRequest.Wait();
|
||||
var size = _lib.rc_runtime_progress_size(ref _runtime, IntPtr.Zero);
|
||||
if (size > 0)
|
||||
{
|
||||
|
@ -257,7 +273,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
HandleHardcoreModeDisable("Loading savestates is not allowed in hardcore mode.");
|
||||
}
|
||||
|
||||
_activeModeUnlocksTask.Wait();
|
||||
_activeModeUnlocksRequest.Wait();
|
||||
_lib.rc_runtime_reset(ref _runtime);
|
||||
|
||||
if (!File.Exists(path + ".rap")) return;
|
||||
|
@ -351,13 +367,18 @@ namespace BizHawk.Client.EmuHawk
|
|||
AllGamesVerified = !ids.Contains(0);
|
||||
|
||||
var gameId = ids.Count > 0 ? ids[0] : 0;
|
||||
_gameData = new();
|
||||
|
||||
if (gameId != 0)
|
||||
{
|
||||
_gameData = _cachedGameDatas.TryGetValue(gameId, out var cachedGameData)
|
||||
? new(cachedGameData, () => AllowUnofficialCheevos)
|
||||
: GetGameData(Username, ApiToken, gameId, () => AllowUnofficialCheevos);
|
||||
: GetGameData(gameId);
|
||||
}
|
||||
|
||||
// this check seems redundant, but it covers the case where GetGameData failed somehow
|
||||
if (_gameData.GameID != 0)
|
||||
{
|
||||
StartGameSession();
|
||||
|
||||
_cachedGameDatas.Remove(gameId);
|
||||
|
@ -367,14 +388,13 @@ namespace BizHawk.Client.EmuHawk
|
|||
}
|
||||
else
|
||||
{
|
||||
_gameData = new();
|
||||
_activeModeUnlocksTask = _inactiveModeUnlocksTask = Task.CompletedTask;
|
||||
_activeModeUnlocksRequest = _inactiveModeUnlocksRequest = FailedRCheevosRequest.Singleton;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_gameData = new();
|
||||
_activeModeUnlocksTask = _inactiveModeUnlocksTask = Task.CompletedTask;
|
||||
_activeModeUnlocksRequest = _inactiveModeUnlocksRequest = FailedRCheevosRequest.Singleton;
|
||||
}
|
||||
|
||||
// validate addresses now that we have cheevos init
|
||||
|
@ -401,33 +421,13 @@ namespace BizHawk.Client.EmuHawk
|
|||
return;
|
||||
}
|
||||
|
||||
if (_startGameSessionTask is not null && _startGameSessionTask.IsCompleted && !GameSessionStartSuccessful)
|
||||
{
|
||||
// retry if this failed
|
||||
StartGameSession();
|
||||
}
|
||||
|
||||
_queuedCheevoUnlockTasks.RemoveAll(t => t.Task.IsCompleted && t.Success);
|
||||
|
||||
foreach (var task in _queuedCheevoUnlockTasks.Where(task => task.Task.IsCompleted && !task.Success))
|
||||
{
|
||||
task.DoRequest();
|
||||
}
|
||||
|
||||
_queuedLboardTriggerTasks.RemoveAll(t => t.Task.IsCompleted && t.Success);
|
||||
|
||||
foreach (var task in _queuedLboardTriggerTasks.Where(task => task.Task.IsCompleted && !task.Success))
|
||||
{
|
||||
task.DoRequest();
|
||||
}
|
||||
|
||||
if (HardcoreMode)
|
||||
{
|
||||
CheckHardcoreModeConditions();
|
||||
}
|
||||
|
||||
if (_gameData.GameID != 0)
|
||||
{
|
||||
if (HardcoreMode)
|
||||
{
|
||||
CheckHardcoreModeConditions();
|
||||
}
|
||||
|
||||
CheckPing();
|
||||
}
|
||||
}
|
||||
|
@ -454,7 +454,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
|
||||
if (cheevo.IsOfficial)
|
||||
{
|
||||
_queuedCheevoUnlockTasks.Add(new(Username, ApiToken, evt->id, HardcoreMode, _gameHash));
|
||||
_inactiveHttpRequests.Push(new CheevoUnlockRequest(Username, ApiToken, evt->id, HardcoreMode, _gameHash));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -539,7 +539,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
var lboard = _gameData.GetLboardById(evt->id);
|
||||
if (!lboard.Invalid)
|
||||
{
|
||||
_queuedLboardTriggerTasks.Add(new(Username, ApiToken, evt->id, evt->value, _gameHash));
|
||||
_inactiveHttpRequests.Push(new LboardTriggerRequest(Username, ApiToken, evt->id, evt->value, _gameHash));
|
||||
|
||||
if (!lboard.Hidden)
|
||||
{
|
||||
|
@ -601,7 +601,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
|
||||
public override void OnFrameAdvance()
|
||||
{
|
||||
if (!LoggedIn || !_activeModeUnlocksTask.IsCompleted)
|
||||
if (!LoggedIn || !_activeModeUnlocksRequest.IsCompleted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue