Skip to content

Commit

Permalink
Merge branch 'main' into better-limiter
Browse files Browse the repository at this point in the history
  • Loading branch information
Eric-Joker committed Aug 7, 2024
2 parents fa6f26a + bf071c4 commit ed5821c
Show file tree
Hide file tree
Showing 7 changed files with 71 additions and 64 deletions.
4 changes: 2 additions & 2 deletions src/Starward.Core/Gacha/GachaLogClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ public abstract class GachaLogClient

protected const string WEB_CACHE_SR_PATH = @"StarRail_Data\webCaches\Cache\Cache_Data\data_2";

protected const string API_PREFIX_SR_CN = "https://api-takumi.mihoyo.com/common/gacha_record/api/getGachaLog";
protected const string API_PREFIX_SR_OS = "https://api-os-takumi.mihoyo.com/common/gacha_record/api/getGachaLog";
protected const string API_PREFIX_SR_CN = "https://public-operation-hkrpg.mihoyo.com/common/gacha_record/api/getGachaLog";
protected const string API_PREFIX_SR_OS = "https://public-operation-hkrpg-sg.hoyoverse.com/common/gacha_record/api/getGachaLog";

protected static ReadOnlySpan<byte> SPAN_WEB_PREFIX_SR_CN => "https://webstatic.mihoyo.com/hkrpg/event/e20211215gacha-v2/index.html"u8;
protected static ReadOnlySpan<byte> SPAN_WEB_PREFIX_SR_OS => "https://gs.hoyoverse.com/hkrpg/event/e20211215gacha-v2/index.html"u8;
Expand Down
2 changes: 1 addition & 1 deletion src/Starward.Core/Gacha/Genshin/GenshinGachaClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ protected override string GetGachaUrlPrefix(string gachaUrl, string? lang = null
}
return gachaUrl;
}
match = Regex.Match(gachaUrl, @"(https://hk4e-api[!-z]+)");
match = Regex.Match(gachaUrl, @"(https://public-operation-hk4e[!-z]+)");
if (match.Success)
{
gachaUrl = match.Groups[1].Value;
Expand Down
2 changes: 1 addition & 1 deletion src/Starward.Core/Gacha/StarRail/StarRailGachaClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ protected override string GetGachaUrlPrefix(string gachaUrl, string? lang = null
}
return gachaUrl;
}
match = Regex.Match(gachaUrl, @"(https://api[!-z]+)");
match = Regex.Match(gachaUrl, @"(https://public-operation-hkrpg[!-z]+)");
if (match.Success)
{
gachaUrl = match.Groups[1].Value;
Expand Down
2 changes: 1 addition & 1 deletion src/Starward/Pages/Setting/DownloadSettingPage.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@ partial void OnSpeedLimitChanged(int value)
{
int speed = value <= 0 ? int.MaxValue : value * 1024;
InstallGameManager.SpeedLimitBytesPerSecond = speed;
InstallGameManager.GlobalRateLimiter = InstallGameManager.GetRateLimiter(speed);
AppConfig.SpeedLimitKBPerSecond = value;
InstallGameManager.SetRateLimit();
}


Expand Down
39 changes: 18 additions & 21 deletions src/Starward/Services/Download/InstallGameManager.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.Mvvm.Messaging;
using Starward.Core;
using Starward.Helpers;
using Starward.Messages;
Expand All @@ -23,7 +23,7 @@ private InstallGameManager()
_services = new();
int speed = AppConfig.SpeedLimitKBPerSecond * 1024;
SpeedLimitBytesPerSecond = speed == 0 ? int.MaxValue : speed;
GlobalRateLimiter = GetRateLimiter(SpeedLimitBytesPerSecond);
SetRateLimit();
}


Expand All @@ -35,14 +35,14 @@ private InstallGameManager()
public static int SpeedLimitBytesPerSecond { get; set; }


public static TokenBucketRateLimiter GlobalRateLimiter;
public static TokenBucketRateLimiter RateLimiter { get; private set; }


public static bool IsEnableSpeedLimit => SpeedLimitBytesPerSecond != int.MaxValue;


// BUFFER_SIZE越大限速时保留速度也会越大,可以用来抵消迷之原因造成的超速¿
// speedLimit<=2MB/s → 4Bytes else 16KB
// speedLimit<=2MB/s → 16Bytes else 1KB
public static int BUFFER_SIZE => (SpeedLimitBytesPerSecond <= (1 << 21)) ? (1 << 4) : (1 << 10);


Expand All @@ -54,34 +54,27 @@ private InstallGameManager()



public static event EventHandler LimitStateChanged;

public static TokenBucketRateLimiter GetRateLimiter(int speedLimitBytesPerSecond)



public static void SetRateLimit()
{
// 小于speedLimitBytesPerSecond的最大能被BUFFER_SIZE整除的值
var speedLimitBytesPerPeriod = Math.Max(speedLimitBytesPerSecond / 25 / BUFFER_SIZE * BUFFER_SIZE, BUFFER_SIZE);
return new TokenBucketRateLimiter(new TokenBucketRateLimiterOptions
var speedLimitBytesPerPeriod = Math.Max(SpeedLimitBytesPerSecond / 25 / BUFFER_SIZE * BUFFER_SIZE, BUFFER_SIZE);
RateLimiter = new TokenBucketRateLimiter(new TokenBucketRateLimiterOptions
{
TokenLimit = speedLimitBytesPerPeriod,
// 0.04: 将每秒切割为上面的25份,间隔越小速度越精准。
// 因补充令牌逻辑运行耗时远大于期望,若间隔极小,将无法达到最高限速。
ReplenishmentPeriod = TimeSpan.FromSeconds(Math.Max(BUFFER_SIZE / (double)speedLimitBytesPerSecond, 0.04)),
ReplenishmentPeriod = TimeSpan.FromSeconds(Math.Max(BUFFER_SIZE / (double)SpeedLimitBytesPerSecond, 0.04)),
TokensPerPeriod = speedLimitBytesPerPeriod,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
AutoReplenishment = true
});
}



public static async Task GetLeaseAsync(TokenBucketRateLimiter rateLimiter, int length, CancellationToken cancellationToken)
{
RateLimitLease lease;
do
{
lease = await rateLimiter.AcquireAsync(length, cancellationToken).ConfigureAwait(false);
if (!lease.IsAcquired && lease.TryGetMetadata(MetadataName.RetryAfter, out TimeSpan retryAfter))
await Task.Delay((int)Math.Max(Math.Sqrt(retryAfter.TotalMilliseconds), 1), cancellationToken).ConfigureAwait(false);
} while (!lease.IsAcquired);
if (LimitStateChanged != null && LimitStateChanged.GetInvocationList().Length > 0)
Task.Run(() => LimitStateChanged.Invoke(null, EventArgs.Empty));
}


Expand Down Expand Up @@ -112,6 +105,8 @@ public void AddInstallService(InstallGameService service)
model.InstallFailed += Model_InstallFailed;
model.InstallCanceled -= Model_InstallCanceled;
model.InstallCanceled += Model_InstallCanceled;
LimitStateChanged -= model._manager_LimitStateChanged;
LimitStateChanged += model._manager_LimitStateChanged;
InstallTaskAdded?.Invoke(this, model);
}

Expand All @@ -126,6 +121,7 @@ private void Model_InstallFinished(object? sender, EventArgs e)
model.InstallFinished -= Model_InstallFinished;
model.InstallFailed -= Model_InstallFailed;
model.InstallCanceled -= Model_InstallCanceled;
LimitStateChanged -= model._manager_LimitStateChanged;
InstallTaskRemoved?.Invoke(this, model);
WeakReferenceMessenger.Default.Send(new InstallGameFinishedMessage(model.GameBiz));
NotificationBehavior.Instance.Success(Lang.InstallGameManager_DownloadTaskCompleted, $"{InstallTaskToString(model.Service.InstallTask)} - {model.GameBiz.ToGameName()} - {model.GameBiz.ToGameServer()}", 0);
Expand Down Expand Up @@ -154,6 +150,7 @@ private void Model_InstallCanceled(object? sender, EventArgs e)
model.InstallFinished -= Model_InstallFinished;
model.InstallFailed -= Model_InstallFailed;
model.InstallCanceled -= Model_InstallCanceled;
LimitStateChanged -= model._manager_LimitStateChanged;
InstallTaskRemoved?.Invoke(this, model);
}
}
Expand Down
60 changes: 34 additions & 26 deletions src/Starward/Services/Download/InstallGameService.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging;
using Starward.Core;
using Starward.Core.HoYoPlay;
using Starward.Services.InstallGame;
Expand All @@ -21,6 +21,7 @@
using System.Text;
using System.Text.Json.Nodes;
using System.Threading;
using System.Threading.RateLimiting;
using System.Threading.Tasks;
using Vanara.PInvoke;

Expand Down Expand Up @@ -245,7 +246,7 @@ await Task.Run(() =>
{
File.SetAttributes(file, FileAttributes.Normal);
}
});
}).ConfigureAwait(false);
CurrentGameBiz = gameBiz;
_installPath = installPath;
_initialized = true;
Expand Down Expand Up @@ -622,6 +623,8 @@ public void ClearState()





protected List<Task> _taskItems;
public List<Task> TaskItems => _taskItems;

Expand Down Expand Up @@ -984,15 +987,15 @@ protected async Task CleanGameDeprecatedFilesAsync()
{
File.Delete(file);
}
foreach (var file in await _hoYoPlayService.GetGameDeprecatedFilesAsync(CurrentGameBiz))
foreach (var file in await _hoYoPlayService.GetGameDeprecatedFilesAsync(CurrentGameBiz).ConfigureAwait(false))
{
var path = Path.Combine(_installPath, file.Name);
if (File.Exists(path))
{
File.Delete(path);
}
}
await WriteConfigFileAsync();
await WriteConfigFileAsync().ConfigureAwait(false);
CurrentTaskFinished();
}

Expand Down Expand Up @@ -1129,19 +1132,19 @@ protected async Task ExecuteTaskItemAsync(CancellationToken cancellationToken =
case InstallGameItemType.None:
break;
case InstallGameItemType.Download:
await DownloadItemAsync(item, cancellationToken);
await DownloadItemAsync(item, cancellationToken).ConfigureAwait(false);
Interlocked.Increment(ref _finishCount);
break;
case InstallGameItemType.Verify:
await VerifyItemAsync(item, cancellationToken);
await VerifyItemAsync(item, cancellationToken).ConfigureAwait(false);
Interlocked.Increment(ref _finishCount);
break;
case InstallGameItemType.Decompress:
await DecompressItemAsync(item, cancellationToken);
await DecompressItemAsync(item, cancellationToken).ConfigureAwait(false);
Interlocked.Increment(ref _finishCount);
break;
case InstallGameItemType.HardLink:
await HardLinkItemAsync(item, cancellationToken);
await HardLinkItemAsync(item, cancellationToken).ConfigureAwait(false);
Interlocked.Increment(ref _finishCount);
break;
default:
Expand All @@ -1152,21 +1155,22 @@ protected async Task ExecuteTaskItemAsync(CancellationToken cancellationToken =
{
// network error
_installItemQueue.Enqueue(item);
await Task.Delay(1000, CancellationToken.None);
await Task.Delay(1000, CancellationToken.None).ConfigureAwait(false);
}
catch (Exception ex) when (ex is OperationCanceledException or TaskCanceledException)
{
// cacel
_installItemQueue.Enqueue(item);
return;
}
catch (Exception ex)
{
_logger.LogError(ex, nameof(ExecuteTaskItemAsync));
_installItemQueue.Enqueue(item);
OnInstallFailed(ex);
}
}
}
catch (Exception ex)
{
_logger.LogError(ex, nameof(ExecuteTaskItemAsync));
OnInstallFailed(ex);
}
finally
{
Interlocked.Decrement(ref _concurrentExecuteThreadCount);
Expand Down Expand Up @@ -1218,16 +1222,20 @@ protected async Task DownloadItemAsync(InstallGameItem item, CancellationToken c
{
if (IsEnableSpeedLimit)
{
await InstallGameManager.GetLeaseAsync(InstallGameManager.GlobalRateLimiter, length, cancellationToken).ConfigureAwait(false);
RateLimitLease lease;
do
{
lease = await InstallGameManager.RateLimiter.AcquireAsync(length, cancellationToken).ConfigureAwait(false);
if (!lease.IsAcquired && lease.TryGetMetadata(MetadataName.RetryAfter, out TimeSpan retryAfter))
await Task.Delay((int)Math.Max(Math.Sqrt(retryAfter.TotalMilliseconds), 1), cancellationToken).ConfigureAwait(false);
} while (!lease.IsAcquired);
int remainingSpace = buffer.Length - bufferOffset;
if (length > remainingSpace)
{
Buffer.BlockCopy(httpBuffer, 0, buffer, bufferOffset, remainingSpace);
bufferOffset += remainingSpace;
await fs.WriteAsync(buffer.AsMemory(0, buffer.Length), cancellationToken).ConfigureAwait(false);
bufferOffset = 0;
Buffer.BlockCopy(httpBuffer, remainingSpace, buffer, bufferOffset, length - remainingSpace);
bufferOffset += length - remainingSpace;
Buffer.BlockCopy(httpBuffer, remainingSpace, buffer, 0, length - remainingSpace);
bufferOffset = length - remainingSpace;
}
else
{
Expand All @@ -1236,7 +1244,7 @@ protected async Task DownloadItemAsync(InstallGameItem item, CancellationToken c
}
}
else
await fs.WriteAsync(buffer.AsMemory(0, buffer.Length), cancellationToken).ConfigureAwait(false);
await fs.WriteAsync(buffer.AsMemory(0, length), cancellationToken).ConfigureAwait(false);
Interlocked.Add(ref _finishBytes, length);
}
// Write any remaining data in buffer
Expand Down Expand Up @@ -1277,7 +1285,7 @@ protected async Task VerifyItemAsync(InstallGameItem item, CancellationToken can
var buffer = ArrayPool<byte>.Shared.Rent(BUFFER_SIZE);
try
{
await _verifyGlobalSemaphore.WaitAsync(cancellationToken);
await _verifyGlobalSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
using var fs = File.OpenRead(file_target);
if (fs.Length != item.Size)
{
Expand Down Expand Up @@ -1324,7 +1332,7 @@ protected async Task DecompressItemAsync(InstallGameItem item, CancellationToken
{
try
{
await _decompressSemaphore.WaitAsync();
await _decompressSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
using var fs = new FileSliceStream(item.DecompressPackageFiles);
if (item.DecompressPackageFiles[0].Contains(".7z", StringComparison.CurrentCultureIgnoreCase))
{
Expand All @@ -1341,7 +1349,7 @@ await Task.Run(() =>
};
extra.Extract(item.DecompressPath, true);
_finishBytes += fs.Length - sum;
}).ConfigureAwait(false);
}, cancellationToken).ConfigureAwait(false);
}
else
{
Expand All @@ -1366,8 +1374,8 @@ await Task.Run(async () =>
}
}
_finishBytes += fs.Length - sum;
await ApplyDiffFilesAsync(item.DecompressPath);
}).ConfigureAwait(false);
await ApplyDiffFilesAsync(item.DecompressPath).ConfigureAwait(false);
}, cancellationToken).ConfigureAwait(false);
}
fs.Dispose();
foreach (var file in item.DecompressPackageFiles)
Expand Down Expand Up @@ -1471,7 +1479,7 @@ protected async Task HardLinkItemAsync(InstallGameItem item, CancellationToken c
var buffer = ArrayPool<byte>.Shared.Rent(BUFFER_SIZE);
try
{
await _verifyGlobalSemaphore.WaitAsync(cancellationToken);
await _verifyGlobalSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
using var fs = File.OpenRead(file_source);
if (fs.Length != item.Size)
{
Expand Down
26 changes: 14 additions & 12 deletions src/Starward/Services/Download/InstallGameStateModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -135,18 +135,6 @@ private void Cancel()

public void UpdateState()
{
if (Service.HTTP_BUFFER_SIZE != InstallGameManager.BUFFER_SIZE || Service.IsEnableSpeedLimit != InstallGameManager.IsEnableSpeedLimit)
{
Interlocked.Exchange(ref Service.HTTP_BUFFER_SIZE, InstallGameManager.BUFFER_SIZE);
Service.IsEnableSpeedLimit = InstallGameManager.IsEnableSpeedLimit;
Service.Pause();
Task.Run(() =>
{
Task.WhenAll(Service.TaskItems).Wait();
Service.Continue();
InstallStarted?.Invoke(this, EventArgs.Empty);
});
}
try
{
IsContinueOrPauseButtonEnabled = true;
Expand Down Expand Up @@ -282,6 +270,7 @@ private void ComputeSpeed(InstallGameState state)
private void _service_StateChanged(object? sender, InstallGameState e)
{
uiContext.Post(_ => UpdateState(), null);

}


Expand All @@ -292,4 +281,17 @@ private void Service_InstallFailed(object? sender, Exception e)
}



public void _manager_LimitStateChanged(object? sender, EventArgs e)
{
if (Service.State is InstallGameState.Download && Service.HTTP_BUFFER_SIZE != InstallGameManager.BUFFER_SIZE || Service.IsEnableSpeedLimit != InstallGameManager.IsEnableSpeedLimit)
{
Service.Pause();
Task.WhenAll(Service.TaskItems).Wait();
Service.HTTP_BUFFER_SIZE = InstallGameManager.BUFFER_SIZE;
Service.IsEnableSpeedLimit = InstallGameManager.IsEnableSpeedLimit;
Service.Continue();
InstallStarted?.Invoke(this, EventArgs.Empty);
}
}
}

0 comments on commit ed5821c

Please sign in to comment.