mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
refactor gacha service 2
This commit is contained in:
@@ -12,7 +12,7 @@ namespace Snap.Hutao.Extension;
|
|||||||
internal static partial class EnumerableExtension
|
internal static partial class EnumerableExtension
|
||||||
{
|
{
|
||||||
/// <inheritdoc cref="Enumerable.Average(IEnumerable{int})"/>
|
/// <inheritdoc cref="Enumerable.Average(IEnumerable{int})"/>
|
||||||
public static double SpanAverage(this List<int> source)
|
public static double Average(this List<int> source)
|
||||||
{
|
{
|
||||||
Span<int> span = CollectionsMarshal.AsSpan(source);
|
Span<int> span = CollectionsMarshal.AsSpan(source);
|
||||||
if (span.IsEmpty)
|
if (span.IsEmpty)
|
||||||
@@ -21,7 +21,7 @@ internal static partial class EnumerableExtension
|
|||||||
}
|
}
|
||||||
|
|
||||||
long sum = 0;
|
long sum = 0;
|
||||||
foreach (int item in span)
|
foreach (ref readonly int item in span)
|
||||||
{
|
{
|
||||||
sum += item;
|
sum += item;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
// Licensed under the MIT license.
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Snap.Hutao.Extension;
|
namespace Snap.Hutao.Extension;
|
||||||
|
|
||||||
@@ -59,7 +60,7 @@ internal static class SpanExtension
|
|||||||
/// <returns>平均值</returns>
|
/// <returns>平均值</returns>
|
||||||
public static byte Average(this in ReadOnlySpan<byte> span)
|
public static byte Average(this in ReadOnlySpan<byte> span)
|
||||||
{
|
{
|
||||||
if (span.Length == 0)
|
if (span.IsEmpty)
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -74,4 +75,9 @@ internal static class SpanExtension
|
|||||||
|
|
||||||
return unchecked((byte)(sum / count));
|
return unchecked((byte)(sum / count));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Span<T> AsSpan<T>(this List<T> list)
|
||||||
|
{
|
||||||
|
return CollectionsMarshal.AsSpan(list);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,20 +1,10 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
// Licensed under the MIT license.
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
using CommunityToolkit.Mvvm.Messaging;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Snap.Hutao.Core.Database;
|
using Snap.Hutao.Core.Database;
|
||||||
using Snap.Hutao.Core.DependencyInjection.Abstraction;
|
|
||||||
using Snap.Hutao.Message;
|
|
||||||
using Snap.Hutao.Model.Entity;
|
using Snap.Hutao.Model.Entity;
|
||||||
using Snap.Hutao.Model.Entity.Database;
|
using Snap.Hutao.Model.Entity.Database;
|
||||||
using Snap.Hutao.Service.Notification;
|
|
||||||
using Snap.Hutao.Service.User;
|
|
||||||
using Snap.Hutao.ViewModel.User;
|
|
||||||
using Snap.Hutao.Web.Hoyolab;
|
|
||||||
using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord;
|
|
||||||
using System.Collections.ObjectModel;
|
|
||||||
using WebDailyNote = Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.DailyNote.DailyNote;
|
|
||||||
|
|
||||||
namespace Snap.Hutao.Service.DailyNote;
|
namespace Snap.Hutao.Service.DailyNote;
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ using Snap.Hutao.Model.Metadata.Avatar;
|
|||||||
using Snap.Hutao.Model.Metadata.Weapon;
|
using Snap.Hutao.Model.Metadata.Weapon;
|
||||||
using Snap.Hutao.Service.Metadata;
|
using Snap.Hutao.Service.Metadata;
|
||||||
using Snap.Hutao.ViewModel.GachaLog;
|
using Snap.Hutao.ViewModel.GachaLog;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Snap.Hutao.Service.GachaLog.Factory;
|
namespace Snap.Hutao.Service.GachaLog.Factory;
|
||||||
|
|
||||||
@@ -26,7 +27,7 @@ internal sealed partial class GachaStatisticsFactory : IGachaStatisticsFactory
|
|||||||
private readonly AppOptions options;
|
private readonly AppOptions options;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public async ValueTask<GachaStatistics> CreateAsync(IOrderedQueryable<GachaItem> items, GachaLogServiceContext context)
|
public async ValueTask<GachaStatistics> CreateAsync(List<GachaItem> items, GachaLogServiceMetadataContext context)
|
||||||
{
|
{
|
||||||
await taskContext.SwitchToBackgroundAsync();
|
await taskContext.SwitchToBackgroundAsync();
|
||||||
List<GachaEvent> gachaEvents = await metadataService.GetGachaEventsAsync().ConfigureAwait(false);
|
List<GachaEvent> gachaEvents = await metadataService.GetGachaEventsAsync().ConfigureAwait(false);
|
||||||
@@ -37,9 +38,9 @@ internal sealed partial class GachaStatisticsFactory : IGachaStatisticsFactory
|
|||||||
|
|
||||||
private static GachaStatistics CreateCore(
|
private static GachaStatistics CreateCore(
|
||||||
IServiceProvider serviceProvider,
|
IServiceProvider serviceProvider,
|
||||||
IOrderedQueryable<GachaItem> items,
|
List<GachaItem> items,
|
||||||
List<HistoryWishBuilder> historyWishBuilders,
|
List<HistoryWishBuilder> historyWishBuilders,
|
||||||
in GachaLogServiceContext context,
|
in GachaLogServiceMetadataContext context,
|
||||||
bool isEmptyHistoryWishVisible)
|
bool isEmptyHistoryWishVisible)
|
||||||
{
|
{
|
||||||
TypedWishSummaryBuilder standardWishBuilder = new(
|
TypedWishSummaryBuilder standardWishBuilder = new(
|
||||||
@@ -65,9 +66,9 @@ internal sealed partial class GachaStatisticsFactory : IGachaStatisticsFactory
|
|||||||
Dictionary<Weapon, int> purpleWeaponCounter = new();
|
Dictionary<Weapon, int> purpleWeaponCounter = new();
|
||||||
Dictionary<Weapon, int> blueWeaponCounter = new();
|
Dictionary<Weapon, int> blueWeaponCounter = new();
|
||||||
|
|
||||||
// Items are ordered by precise time
|
// Items are ordered by precise time, first is oldest
|
||||||
// first is oldest
|
// 'ref' is not allowed here because we have lambda below
|
||||||
foreach (GachaItem item in items)
|
foreach (GachaItem item in CollectionsMarshal.AsSpan(items))
|
||||||
{
|
{
|
||||||
// Find target history wish to operate.
|
// Find target history wish to operate.
|
||||||
HistoryWishBuilder? targetHistoryWishBuilder = historyWishBuilders
|
HistoryWishBuilder? targetHistoryWishBuilder = historyWishBuilders
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ using Snap.Hutao.Model.Intrinsic;
|
|||||||
using Snap.Hutao.Model.Metadata.Abstraction;
|
using Snap.Hutao.Model.Metadata.Abstraction;
|
||||||
using Snap.Hutao.ViewModel.GachaLog;
|
using Snap.Hutao.ViewModel.GachaLog;
|
||||||
using Snap.Hutao.Web.Hoyolab.Hk4e.Event.GachaInfo;
|
using Snap.Hutao.Web.Hoyolab.Hk4e.Event.GachaInfo;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Snap.Hutao.Service.GachaLog.Factory;
|
namespace Snap.Hutao.Service.GachaLog.Factory;
|
||||||
|
|
||||||
@@ -19,10 +20,36 @@ internal sealed partial class GachaStatisticsSlimFactory : IGachaStatisticsSlimF
|
|||||||
private readonly ITaskContext taskContext;
|
private readonly ITaskContext taskContext;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public async ValueTask<GachaStatisticsSlim> CreateAsync(IOrderedQueryable<GachaItem> items, GachaLogServiceContext context)
|
public async ValueTask<GachaStatisticsSlim> CreateAsync(List<GachaItem> items, GachaLogServiceMetadataContext context)
|
||||||
{
|
{
|
||||||
await taskContext.SwitchToBackgroundAsync();
|
await taskContext.SwitchToBackgroundAsync();
|
||||||
|
|
||||||
|
return CreateCore(items, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void Track(INameQuality nameQuality, ref int orangeTracker, ref int purpleTracker)
|
||||||
|
{
|
||||||
|
switch (nameQuality.Quality)
|
||||||
|
{
|
||||||
|
case QualityType.QUALITY_ORANGE:
|
||||||
|
orangeTracker = 0;
|
||||||
|
++purpleTracker;
|
||||||
|
break;
|
||||||
|
case QualityType.QUALITY_PURPLE:
|
||||||
|
++orangeTracker;
|
||||||
|
purpleTracker = 0;
|
||||||
|
break;
|
||||||
|
case QualityType.QUALITY_BLUE:
|
||||||
|
++orangeTracker;
|
||||||
|
++purpleTracker;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private GachaStatisticsSlim CreateCore(List<GachaItem> items, GachaLogServiceMetadataContext context)
|
||||||
|
{
|
||||||
int standardOrangeTracker = 0;
|
int standardOrangeTracker = 0;
|
||||||
int standardPurpleTracker = 0;
|
int standardPurpleTracker = 0;
|
||||||
TypedWishSummarySlim standardWish = new(SH.ServiceGachaLogFactoryPermanentWishName, 90, 10);
|
TypedWishSummarySlim standardWish = new(SH.ServiceGachaLogFactoryPermanentWishName, 90, 10);
|
||||||
@@ -36,7 +63,7 @@ internal sealed partial class GachaStatisticsSlimFactory : IGachaStatisticsSlimF
|
|||||||
TypedWishSummarySlim weaponWish = new(SH.ServiceGachaLogFactoryWeaponWishName, 80, 10);
|
TypedWishSummarySlim weaponWish = new(SH.ServiceGachaLogFactoryWeaponWishName, 80, 10);
|
||||||
|
|
||||||
// O(n) operation
|
// O(n) operation
|
||||||
foreach (GachaItem item in items)
|
foreach (ref readonly GachaItem item in CollectionsMarshal.AsSpan(items))
|
||||||
{
|
{
|
||||||
INameQuality nameQuality = context.GetNameQualityByItemId(item.ItemId);
|
INameQuality nameQuality = context.GetNameQualityByItemId(item.ItemId);
|
||||||
switch (item.QueryType)
|
switch (item.QueryType)
|
||||||
@@ -70,25 +97,4 @@ internal sealed partial class GachaStatisticsSlimFactory : IGachaStatisticsSlimF
|
|||||||
StandardWish = standardWish,
|
StandardWish = standardWish,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void Track(INameQuality nameQuality, ref int orangeTracker, ref int purpleTracker)
|
|
||||||
{
|
|
||||||
switch (nameQuality.Quality)
|
|
||||||
{
|
|
||||||
case QualityType.QUALITY_ORANGE:
|
|
||||||
orangeTracker = 0;
|
|
||||||
++purpleTracker;
|
|
||||||
break;
|
|
||||||
case QualityType.QUALITY_PURPLE:
|
|
||||||
++orangeTracker;
|
|
||||||
purpleTracker = 0;
|
|
||||||
break;
|
|
||||||
case QualityType.QUALITY_BLUE:
|
|
||||||
++orangeTracker;
|
|
||||||
++purpleTracker;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -30,7 +30,7 @@ internal sealed class HistoryWishBuilder
|
|||||||
/// <param name="gachaEvent">卡池配置</param>
|
/// <param name="gachaEvent">卡池配置</param>
|
||||||
/// <param name="context">祈愿记录上下文</param>
|
/// <param name="context">祈愿记录上下文</param>
|
||||||
[SuppressMessage("", "SH002")]
|
[SuppressMessage("", "SH002")]
|
||||||
public HistoryWishBuilder(GachaEvent gachaEvent, GachaLogServiceContext context)
|
public HistoryWishBuilder(GachaEvent gachaEvent, GachaLogServiceMetadataContext context)
|
||||||
{
|
{
|
||||||
this.gachaEvent = gachaEvent;
|
this.gachaEvent = gachaEvent;
|
||||||
ConfigType = gachaEvent.Type;
|
ConfigType = gachaEvent.Type;
|
||||||
|
|||||||
@@ -18,5 +18,5 @@ internal interface IGachaStatisticsFactory
|
|||||||
/// <param name="items">物品列表</param>
|
/// <param name="items">物品列表</param>
|
||||||
/// <param name="context">祈愿记录上下文</param>
|
/// <param name="context">祈愿记录上下文</param>
|
||||||
/// <returns>祈愿统计对象</returns>
|
/// <returns>祈愿统计对象</returns>
|
||||||
ValueTask<GachaStatistics> CreateAsync(IOrderedQueryable<GachaItem> items, GachaLogServiceContext context);
|
ValueTask<GachaStatistics> CreateAsync(List<GachaItem> items, GachaLogServiceMetadataContext context);
|
||||||
}
|
}
|
||||||
@@ -17,5 +17,5 @@ internal interface IGachaStatisticsSlimFactory
|
|||||||
/// <param name="items">排序的物品</param>
|
/// <param name="items">排序的物品</param>
|
||||||
/// <param name="context">祈愿记录服务上下文</param>
|
/// <param name="context">祈愿记录服务上下文</param>
|
||||||
/// <returns>简化的祈愿统计</returns>
|
/// <returns>简化的祈愿统计</returns>
|
||||||
ValueTask<GachaStatisticsSlim> CreateAsync(IOrderedQueryable<GachaItem> items, GachaLogServiceContext context);
|
ValueTask<GachaStatisticsSlim> CreateAsync(List<GachaItem> items, GachaLogServiceMetadataContext context);
|
||||||
}
|
}
|
||||||
@@ -127,10 +127,6 @@ internal sealed class TypedWishSummaryBuilder
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 转换到类型化祈愿统计信息
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>类型化祈愿统计信息</returns>
|
|
||||||
public TypedWishSummary ToTypedWishSummary(AsyncBarrier barrier)
|
public TypedWishSummary ToTypedWishSummary(AsyncBarrier barrier)
|
||||||
{
|
{
|
||||||
summaryItems.CompleteAdding(guaranteeOrangeThreshold);
|
summaryItems.CompleteAdding(guaranteeOrangeThreshold);
|
||||||
@@ -157,12 +153,11 @@ internal sealed class TypedWishSummaryBuilder
|
|||||||
TotalOrangePercent = totalOrangePullTracker / totalCount,
|
TotalOrangePercent = totalOrangePullTracker / totalCount,
|
||||||
TotalPurplePercent = totalPurplePullTracker / totalCount,
|
TotalPurplePercent = totalPurplePullTracker / totalCount,
|
||||||
TotalBluePercent = totalBluePullTracker / totalCount,
|
TotalBluePercent = totalBluePullTracker / totalCount,
|
||||||
AverageOrangePull = averageOrangePullTracker.SpanAverage(),
|
AverageOrangePull = averageOrangePullTracker.Average(),
|
||||||
AverageUpOrangePull = averageUpOrangePullTracker.SpanAverage(),
|
AverageUpOrangePull = averageUpOrangePullTracker.Average(),
|
||||||
OrangeList = summaryItems,
|
OrangeList = summaryItems,
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: barrier all predictions.
|
|
||||||
new PullPrediction(serviceProvider, summary, distributionType).PredictAsync(barrier).SafeForget();
|
new PullPrediction(serviceProvider, summary, distributionType).PredictAsync(barrier).SafeForget();
|
||||||
|
|
||||||
return summary;
|
return summary;
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ internal readonly struct GachaArchiveInitializationContext
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 任务上下文
|
/// 任务上下文
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Obsolete]
|
||||||
public readonly ITaskContext TaskContext;
|
public readonly ITaskContext TaskContext;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
|
||||||
// Licensed under the MIT license.
|
|
||||||
|
|
||||||
using Microsoft.Data.Sqlite;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Snap.Hutao.Core.ExceptionService;
|
|
||||||
using Snap.Hutao.Model.Entity;
|
|
||||||
using Snap.Hutao.Model.Entity.Database;
|
|
||||||
using System.Collections.ObjectModel;
|
|
||||||
|
|
||||||
namespace Snap.Hutao.Service.GachaLog;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 存档操作
|
|
||||||
/// </summary>
|
|
||||||
internal static class GachaArchives
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 初始化存档集合
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="serviceProvider">服务提供器</param>
|
|
||||||
/// <param name="collection">集合</param>
|
|
||||||
public static void Initialize(IServiceProvider serviceProvider, out ObservableCollection<GachaArchive> collection)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
|
||||||
{
|
|
||||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
|
||||||
collection = appDbContext.GachaArchives.AsNoTracking().ToObservableCollection();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (SqliteException ex)
|
|
||||||
{
|
|
||||||
string message = string.Format(SH.ServiceGachaLogArchiveCollectionUserdataCorruptedMessage, ex.Message);
|
|
||||||
throw ThrowHelper.UserdataCorrupted(message, ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -20,5 +20,5 @@ internal static class GachaLog
|
|||||||
GachaConfigType.StandardWish,
|
GachaConfigType.StandardWish,
|
||||||
GachaConfigType.AvatarEventWish,
|
GachaConfigType.AvatarEventWish,
|
||||||
GachaConfigType.WeaponEventWish,
|
GachaConfigType.WeaponEventWish,
|
||||||
}.ToImmutableList();
|
}.ToImmutableList(); // TODO: FrozenSet
|
||||||
}
|
}
|
||||||
@@ -49,7 +49,7 @@ internal struct GachaLogFetchContext
|
|||||||
public GachaConfigType CurrentType;
|
public GachaConfigType CurrentType;
|
||||||
|
|
||||||
private readonly IServiceProvider serviceProvider;
|
private readonly IServiceProvider serviceProvider;
|
||||||
private readonly GachaLogServiceContext serviceContext;
|
private readonly GachaLogServiceMetadataContext serviceContext;
|
||||||
private readonly bool isLazy;
|
private readonly bool isLazy;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -58,7 +58,7 @@ internal struct GachaLogFetchContext
|
|||||||
/// <param name="serviceProvider">服务提供器</param>
|
/// <param name="serviceProvider">服务提供器</param>
|
||||||
/// <param name="serviceContext">祈愿服务上下文</param>
|
/// <param name="serviceContext">祈愿服务上下文</param>
|
||||||
/// <param name="isLazy">是否为懒惰模式</param>
|
/// <param name="isLazy">是否为懒惰模式</param>
|
||||||
public GachaLogFetchContext(IServiceProvider serviceProvider, in GachaLogServiceContext serviceContext, bool isLazy)
|
public GachaLogFetchContext(IServiceProvider serviceProvider, in GachaLogServiceMetadataContext serviceContext, bool isLazy)
|
||||||
{
|
{
|
||||||
this.serviceProvider = serviceProvider;
|
this.serviceProvider = serviceProvider;
|
||||||
this.serviceContext = serviceContext;
|
this.serviceContext = serviceContext;
|
||||||
@@ -99,8 +99,8 @@ internal struct GachaLogFetchContext
|
|||||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||||
ITaskContext taskContext = scope.ServiceProvider.GetRequiredService<ITaskContext>();
|
ITaskContext taskContext = scope.ServiceProvider.GetRequiredService<ITaskContext>();
|
||||||
|
|
||||||
GachaArchiveInitializationContext initContext = new(taskContext, item.Uid, appDbContext.GachaArchives, serviceContext.ArchiveCollection);
|
GachaArchiveInitializationContext context = new(taskContext, item.Uid, appDbContext.GachaArchives, serviceContext.ArchiveCollection);
|
||||||
GachaArchive.SkipOrInit(initContext, ref TargetArchive);
|
GachaArchive.SkipOrInit(context, ref TargetArchive);
|
||||||
DbEndId ??= TargetArchive.GetEndId(CurrentType, appDbContext.GachaItems);
|
DbEndId ??= TargetArchive.GetEndId(CurrentType, appDbContext.GachaItems);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
// Licensed under the MIT license.
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using Microsoft.Data.Sqlite;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Snap.Hutao.Core.Database;
|
using Snap.Hutao.Core.Database;
|
||||||
using Snap.Hutao.Core.Diagnostics;
|
using Snap.Hutao.Core.Diagnostics;
|
||||||
|
using Snap.Hutao.Core.ExceptionService;
|
||||||
using Snap.Hutao.Model.Entity;
|
using Snap.Hutao.Model.Entity;
|
||||||
using Snap.Hutao.Model.Entity.Database;
|
using Snap.Hutao.Model.Entity.Database;
|
||||||
using Snap.Hutao.Model.InterChange.GachaLog;
|
using Snap.Hutao.Model.InterChange.GachaLog;
|
||||||
@@ -34,9 +37,11 @@ internal sealed partial class GachaLogService : IGachaLogService
|
|||||||
private readonly IMetadataService metadataService;
|
private readonly IMetadataService metadataService;
|
||||||
private readonly ILogger<GachaLogService> logger;
|
private readonly ILogger<GachaLogService> logger;
|
||||||
private readonly GachaInfoClient gachaInfoClient;
|
private readonly GachaInfoClient gachaInfoClient;
|
||||||
|
private readonly IGachaLogDbService gachaLogDbService;
|
||||||
private readonly ITaskContext taskContext;
|
private readonly ITaskContext taskContext;
|
||||||
|
|
||||||
private GachaLogServiceContext context;
|
private GachaLogServiceMetadataContext context;
|
||||||
|
private ObservableCollection<GachaArchive>? archiveCollection;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public GachaArchive? CurrentArchive
|
public GachaArchive? CurrentArchive
|
||||||
@@ -48,11 +53,11 @@ internal sealed partial class GachaLogService : IGachaLogService
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public ObservableCollection<GachaArchive>? ArchiveCollection
|
public ObservableCollection<GachaArchive>? ArchiveCollection
|
||||||
{
|
{
|
||||||
get => context.ArchiveCollection;
|
get => archiveCollection;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public async ValueTask<bool> InitializeAsync(CancellationToken token)
|
public async ValueTask<bool> InitializeAsync(CancellationToken token = default)
|
||||||
{
|
{
|
||||||
if (context.IsInitialized)
|
if (context.IsInitialized)
|
||||||
{
|
{
|
||||||
@@ -67,9 +72,9 @@ internal sealed partial class GachaLogService : IGachaLogService
|
|||||||
Dictionary<string, Model.Metadata.Avatar.Avatar> nameAvatarMap = await metadataService.GetNameToAvatarMapAsync(token).ConfigureAwait(false);
|
Dictionary<string, Model.Metadata.Avatar.Avatar> nameAvatarMap = await metadataService.GetNameToAvatarMapAsync(token).ConfigureAwait(false);
|
||||||
Dictionary<string, Model.Metadata.Weapon.Weapon> nameWeaponMap = await metadataService.GetNameToWeaponMapAsync(token).ConfigureAwait(false);
|
Dictionary<string, Model.Metadata.Weapon.Weapon> nameWeaponMap = await metadataService.GetNameToWeaponMapAsync(token).ConfigureAwait(false);
|
||||||
|
|
||||||
GachaArchives.Initialize(serviceProvider, out ObservableCollection<GachaArchive> collection);
|
ObservableCollection<GachaArchive> collection = gachaLogDbService.GetGachaArchiveCollection();
|
||||||
|
|
||||||
context = new(idAvatarMap, idWeaponMap, nameAvatarMap, nameWeaponMap, collection);
|
context = new(idAvatarMap, idWeaponMap, nameAvatarMap, nameWeaponMap);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -87,48 +92,37 @@ internal sealed partial class GachaLogService : IGachaLogService
|
|||||||
// Return statistics
|
// Return statistics
|
||||||
using (ValueStopwatch.MeasureExecution(logger))
|
using (ValueStopwatch.MeasureExecution(logger))
|
||||||
{
|
{
|
||||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
List<GachaItem> items = gachaLogDbService.GetGachaItemListByArchiveId(archive.InnerId);
|
||||||
{
|
return await gachaStatisticsFactory.CreateAsync(items, context).ConfigureAwait(false);
|
||||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
|
||||||
IOrderedQueryable<GachaItem> items = appDbContext.GachaItems
|
|
||||||
.Where(i => i.ArchiveId == archive.InnerId)
|
|
||||||
.OrderBy(i => i.Id);
|
|
||||||
|
|
||||||
return await gachaStatisticsFactory.CreateAsync(items, context).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public async Task<List<GachaStatisticsSlim>> GetStatisticsSlimsAsync()
|
public async ValueTask<List<GachaStatisticsSlim>> GetStatisticsSlimListAsync(CancellationToken token = default)
|
||||||
{
|
{
|
||||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
await InitializeAsync(token).ConfigureAwait(false);
|
||||||
|
ArgumentNullException.ThrowIfNull(ArchiveCollection);
|
||||||
|
|
||||||
|
List<GachaStatisticsSlim> statistics = new();
|
||||||
|
foreach (GachaArchive archive in ArchiveCollection)
|
||||||
{
|
{
|
||||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
List<GachaItem> items = gachaLogDbService.GetGachaItemListByArchiveId(archive.InnerId);
|
||||||
List<GachaStatisticsSlim> statistics = new();
|
GachaStatisticsSlim slim = await gachaStatisticsSlimFactory.CreateAsync(items, context).ConfigureAwait(false);
|
||||||
foreach (GachaArchive archive in appDbContext.GachaArchives)
|
slim.Uid = archive.Uid;
|
||||||
{
|
statistics.Add(slim);
|
||||||
IOrderedQueryable<GachaItem> items = appDbContext.GachaItems
|
|
||||||
.Where(i => i.ArchiveId == archive.InnerId)
|
|
||||||
.OrderBy(i => i.Id);
|
|
||||||
|
|
||||||
GachaStatisticsSlim slim = await gachaStatisticsSlimFactory.CreateAsync(items, context).ConfigureAwait(false);
|
|
||||||
slim.Uid = archive.Uid;
|
|
||||||
statistics.Add(slim);
|
|
||||||
}
|
|
||||||
|
|
||||||
return statistics;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return statistics;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public Task<UIGF> ExportToUIGFAsync(GachaArchive archive)
|
public ValueTask<UIGF> ExportToUIGFAsync(GachaArchive archive)
|
||||||
{
|
{
|
||||||
return gachaLogExportService.ExportAsync(context, archive);
|
return gachaLogExportService.ExportAsync(context, archive);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public async Task ImportFromUIGFAsync(UIGF uigf)
|
public async ValueTask ImportFromUIGFAsync(UIGF uigf)
|
||||||
{
|
{
|
||||||
CurrentArchive = await gachaLogImportService.ImportAsync(context, uigf).ConfigureAwait(false);
|
CurrentArchive = await gachaLogImportService.ImportAsync(context, uigf).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
@@ -156,19 +150,15 @@ internal sealed partial class GachaLogService : IGachaLogService
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public async Task RemoveArchiveAsync(GachaArchive archive)
|
public async Task RemoveArchiveAsync(GachaArchive archive)
|
||||||
{
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(archiveCollection);
|
||||||
|
|
||||||
// Sync cache
|
// Sync cache
|
||||||
await taskContext.SwitchToMainThreadAsync();
|
await taskContext.SwitchToMainThreadAsync();
|
||||||
context.ArchiveCollection.Remove(archive);
|
archiveCollection.Remove(archive);
|
||||||
|
|
||||||
// Sync database
|
// Sync database
|
||||||
await taskContext.SwitchToBackgroundAsync();
|
await taskContext.SwitchToBackgroundAsync();
|
||||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
await gachaLogDbService.DeleteGachaArchiveByIdAsync(archive.InnerId).ConfigureAwait(false);
|
||||||
{
|
|
||||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
|
||||||
await appDbContext.GachaArchives
|
|
||||||
.ExecuteDeleteWhereAsync(a => a.InnerId == archive.InnerId)
|
|
||||||
.ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Task RandomDelayAsync(CancellationToken token)
|
private static Task RandomDelayAsync(CancellationToken token)
|
||||||
@@ -246,9 +236,54 @@ internal sealed partial class GachaLogService : IGachaLogService
|
|||||||
[Injection(InjectAs.Scoped, typeof(IGachaLogDbService))]
|
[Injection(InjectAs.Scoped, typeof(IGachaLogDbService))]
|
||||||
internal sealed partial class GachaLogDbService : IGachaLogDbService
|
internal sealed partial class GachaLogDbService : IGachaLogDbService
|
||||||
{
|
{
|
||||||
|
private readonly IServiceProvider serviceProvider;
|
||||||
|
|
||||||
|
public ObservableCollection<GachaArchive> GetGachaArchiveCollection()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||||
|
{
|
||||||
|
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||||
|
return appDbContext.GachaArchives.AsNoTracking().ToObservableCollection();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (SqliteException ex)
|
||||||
|
{
|
||||||
|
string message = string.Format(SH.ServiceGachaLogArchiveCollectionUserdataCorruptedMessage, ex.Message);
|
||||||
|
throw ThrowHelper.UserdataCorrupted(message, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<GachaItem> GetGachaItemListByArchiveId(Guid archiveId)
|
||||||
|
{
|
||||||
|
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||||
|
{
|
||||||
|
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||||
|
return appDbContext.GachaItems
|
||||||
|
.AsNoTracking()
|
||||||
|
.Where(i => i.ArchiveId == archiveId)
|
||||||
|
.OrderBy(i => i.Id)
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask DeleteGachaArchiveByIdAsync(Guid archiveId)
|
||||||
|
{
|
||||||
|
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||||
|
{
|
||||||
|
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||||
|
await appDbContext.GachaArchives
|
||||||
|
.ExecuteDeleteWhereAsync(a => a.InnerId == archiveId)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal interface IGachaLogDbService
|
internal interface IGachaLogDbService
|
||||||
{
|
{
|
||||||
|
ValueTask DeleteGachaArchiveByIdAsync(Guid archiveId);
|
||||||
|
ObservableCollection<GachaArchive> GetGachaArchiveCollection();
|
||||||
|
|
||||||
|
List<GachaItem> GetGachaItemListByArchiveId(Guid archiveId);
|
||||||
}
|
}
|
||||||
@@ -15,7 +15,7 @@ namespace Snap.Hutao.Service.GachaLog;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 祈愿记录服务上下文
|
/// 祈愿记录服务上下文
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal readonly struct GachaLogServiceContext
|
internal readonly struct GachaLogServiceMetadataContext
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 物品缓存
|
/// 物品缓存
|
||||||
@@ -45,6 +45,7 @@ internal readonly struct GachaLogServiceContext
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 存档集合
|
/// 存档集合
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Obsolete]
|
||||||
public readonly ObservableCollection<GachaArchive> ArchiveCollection;
|
public readonly ObservableCollection<GachaArchive> ArchiveCollection;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -59,19 +60,16 @@ internal readonly struct GachaLogServiceContext
|
|||||||
/// <param name="idWeaponMap">Id 武器 映射</param>
|
/// <param name="idWeaponMap">Id 武器 映射</param>
|
||||||
/// <param name="nameAvatarMap">名称 角色 映射</param>
|
/// <param name="nameAvatarMap">名称 角色 映射</param>
|
||||||
/// <param name="nameWeaponMap">名称 武器 映射</param>
|
/// <param name="nameWeaponMap">名称 武器 映射</param>
|
||||||
/// <param name="archiveCollection">存档集合</param>
|
public GachaLogServiceMetadataContext(
|
||||||
public GachaLogServiceContext(
|
|
||||||
Dictionary<AvatarId, Avatar> idAvatarMap,
|
Dictionary<AvatarId, Avatar> idAvatarMap,
|
||||||
Dictionary<WeaponId, Weapon> idWeaponMap,
|
Dictionary<WeaponId, Weapon> idWeaponMap,
|
||||||
Dictionary<string, Avatar> nameAvatarMap,
|
Dictionary<string, Avatar> nameAvatarMap,
|
||||||
Dictionary<string, Weapon> nameWeaponMap,
|
Dictionary<string, Weapon> nameWeaponMap)
|
||||||
ObservableCollection<GachaArchive> archiveCollection)
|
|
||||||
{
|
{
|
||||||
IdAvatarMap = idAvatarMap;
|
IdAvatarMap = idAvatarMap;
|
||||||
IdWeaponMap = idWeaponMap;
|
IdWeaponMap = idWeaponMap;
|
||||||
NameAvatarMap = nameAvatarMap;
|
NameAvatarMap = nameAvatarMap;
|
||||||
NameWeaponMap = nameWeaponMap;
|
NameWeaponMap = nameWeaponMap;
|
||||||
ArchiveCollection = archiveCollection;
|
|
||||||
|
|
||||||
IsInitialized = true;
|
IsInitialized = true;
|
||||||
}
|
}
|
||||||
@@ -30,7 +30,7 @@ internal interface IGachaLogService
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="archive">存档</param>
|
/// <param name="archive">存档</param>
|
||||||
/// <returns>UIGF对象</returns>
|
/// <returns>UIGF对象</returns>
|
||||||
Task<UIGF> ExportToUIGFAsync(GachaArchive archive);
|
ValueTask<UIGF> ExportToUIGFAsync(GachaArchive archive);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获得对应的祈愿统计
|
/// 获得对应的祈愿统计
|
||||||
@@ -42,22 +42,23 @@ internal interface IGachaLogService
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 异步获取简化的祈愿统计列表
|
/// 异步获取简化的祈愿统计列表
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="token">取消令牌</param>
|
||||||
/// <returns>简化的祈愿统计列表</returns>
|
/// <returns>简化的祈愿统计列表</returns>
|
||||||
Task<List<GachaStatisticsSlim>> GetStatisticsSlimsAsync();
|
ValueTask<List<GachaStatisticsSlim>> GetStatisticsSlimListAsync(CancellationToken token = default);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 异步从UIGF导入数据
|
/// 异步从UIGF导入数据
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="uigf">信息</param>
|
/// <param name="uigf">信息</param>
|
||||||
/// <returns>任务</returns>
|
/// <returns>任务</returns>
|
||||||
Task ImportFromUIGFAsync(UIGF uigf);
|
ValueTask ImportFromUIGFAsync(UIGF uigf);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 异步初始化
|
/// 异步初始化
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="token">取消令牌</param>
|
/// <param name="token">取消令牌</param>
|
||||||
/// <returns>是否初始化成功</returns>
|
/// <returns>是否初始化成功</returns>
|
||||||
ValueTask<bool> InitializeAsync(CancellationToken token);
|
ValueTask<bool> InitializeAsync(CancellationToken token = default);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 刷新祈愿记录
|
/// 刷新祈愿记录
|
||||||
|
|||||||
@@ -17,5 +17,5 @@ internal interface IUIGFExportService
|
|||||||
/// <param name="context">元数据上下文</param>
|
/// <param name="context">元数据上下文</param>
|
||||||
/// <param name="archive">存档</param>
|
/// <param name="archive">存档</param>
|
||||||
/// <returns>UIGF</returns>
|
/// <returns>UIGF</returns>
|
||||||
Task<UIGF> ExportAsync(GachaLogServiceContext context, GachaArchive archive);
|
ValueTask<UIGF> ExportAsync(GachaLogServiceMetadataContext context, GachaArchive archive);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,5 +17,5 @@ internal interface IUIGFImportService
|
|||||||
/// <param name="context">祈愿记录服务上下文</param>
|
/// <param name="context">祈愿记录服务上下文</param>
|
||||||
/// <param name="uigf">数据</param>
|
/// <param name="uigf">数据</param>
|
||||||
/// <returns>存档</returns>
|
/// <returns>存档</returns>
|
||||||
Task<GachaArchive> ImportAsync(GachaLogServiceContext context, UIGF uigf);
|
Task<GachaArchive> ImportAsync(GachaLogServiceMetadataContext context, UIGF uigf);
|
||||||
}
|
}
|
||||||
@@ -18,7 +18,7 @@ internal sealed partial class UIGFExportService : IUIGFExportService
|
|||||||
private readonly ITaskContext taskContext;
|
private readonly ITaskContext taskContext;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public async Task<UIGF> ExportAsync(GachaLogServiceContext context, GachaArchive archive)
|
public async ValueTask<UIGF> ExportAsync(GachaLogServiceMetadataContext context, GachaArchive archive)
|
||||||
{
|
{
|
||||||
await taskContext.SwitchToBackgroundAsync();
|
await taskContext.SwitchToBackgroundAsync();
|
||||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ internal sealed partial class UIGFImportService : IUIGFImportService
|
|||||||
private readonly ITaskContext taskContext;
|
private readonly ITaskContext taskContext;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public async Task<GachaArchive> ImportAsync(GachaLogServiceContext context, UIGF uigf)
|
public async Task<GachaArchive> ImportAsync(GachaLogServiceMetadataContext context, UIGF uigf)
|
||||||
{
|
{
|
||||||
await taskContext.SwitchToBackgroundAsync();
|
await taskContext.SwitchToBackgroundAsync();
|
||||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ internal sealed class GachaLogViewModelSlim : Abstraction.ViewModelSlim<View.Pag
|
|||||||
|
|
||||||
if (await gachaLogService.InitializeAsync(default).ConfigureAwait(false))
|
if (await gachaLogService.InitializeAsync(default).ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
List<GachaStatisticsSlim> list = await gachaLogService.GetStatisticsSlimsAsync().ConfigureAwait(false);
|
List<GachaStatisticsSlim> list = await gachaLogService.GetStatisticsSlimListAsync().ConfigureAwait(false);
|
||||||
await taskContext.SwitchToMainThreadAsync();
|
await taskContext.SwitchToMainThreadAsync();
|
||||||
StatisticsList = list;
|
StatisticsList = list;
|
||||||
IsInitialized = true;
|
IsInitialized = true;
|
||||||
|
|||||||
Reference in New Issue
Block a user