avatar info optimization

This commit is contained in:
Lightczx
2024-04-24 17:12:34 +08:00
parent f12cd63c92
commit 7cf106ec50
21 changed files with 282 additions and 118 deletions

View File

@@ -1,5 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Core.Logging;
namespace Snap.Hutao.Core.Diagnostics;
internal readonly struct MeasureExecutionToken : IDisposable
@@ -17,6 +19,6 @@ internal readonly struct MeasureExecutionToken : IDisposable
public void Dispose()
{
logger.LogDebug("{Caller} toke {Time} ms", callerName, stopwatch.GetElapsedTime().TotalMilliseconds);
logger.LogColorizedDebug(("{Caller} toke {Time} ms", ConsoleColor.Gray), (callerName, ConsoleColor.Yellow), (stopwatch.GetElapsedTime().TotalMilliseconds, ConsoleColor.DarkGreen));
}
}

View File

@@ -0,0 +1,40 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.IO;
namespace Snap.Hutao.Core.IO;
internal sealed class StreamReaderWriter : IDisposable
{
private readonly StreamReader reader;
private readonly StreamWriter writer;
public StreamReaderWriter(StreamReader reader, StreamWriter writer)
{
this.reader = reader;
this.writer = writer;
}
public StreamReader Reader { get => reader; }
public StreamWriter Writer { get => writer; }
/// <inheritdoc cref="StreamReader.ReadLineAsync(CancellationToken)"/>
public ValueTask<string?> ReadLineAsync(CancellationToken token)
{
return reader.ReadLineAsync(token);
}
/// <inheritdoc cref="StreamWriter.WriteAsync(string?)"/>
public Task WriteAsync(string value)
{
return writer.WriteAsync(value);
}
public void Dispose()
{
writer.Dispose();
reader.Dispose();
}
}

View File

@@ -21,6 +21,11 @@ internal readonly struct LogArgument
return new(argument);
}
public static implicit operator LogArgument(double argument)
{
return new(argument);
}
public static implicit operator LogArgument((object? Argument, ConsoleColor Foreground) tuple)
{
return new(tuple.Argument, tuple.Foreground);

View File

@@ -2,6 +2,7 @@
// Licensed under the MIT license.
using Microsoft.UI.Xaml;
using Snap.Hutao.Core;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using WinRT;

View File

@@ -12,7 +12,7 @@ namespace Snap.Hutao.Service.AvatarInfo.Factory.Builder;
internal static class AvatarViewBuilderExtension
{
public static TBuilder ApplyCostumeIconOrDefault<TBuilder>(this TBuilder builder, Web.Enka.Model.AvatarInfo avatarInfo, Avatar avatar)
public static TBuilder SetCostumeIconOrDefault<TBuilder>(this TBuilder builder, Web.Enka.Model.AvatarInfo avatarInfo, Avatar avatar)
where TBuilder : IAvatarViewBuilder
{
if (avatarInfo.CostumeId.TryGetValue(out CostumeId id))

View File

@@ -0,0 +1,11 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Core.Abstraction;
namespace Snap.Hutao.Service.AvatarInfo.Factory.Builder;
internal interface IWeaponViewBuilder : IBuilder
{
ViewModel.AvatarProperty.WeaponView WeaponView { get; }
}

View File

@@ -0,0 +1,9 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Service.AvatarInfo.Factory.Builder;
internal sealed class WeaponViewBuilder : IWeaponViewBuilder
{
public ViewModel.AvatarProperty.WeaponView WeaponView { get; } = new();
}

View File

@@ -0,0 +1,98 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Core.Abstraction.Extension;
using Snap.Hutao.Model;
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Metadata.Converter;
using Snap.Hutao.Model.Primitive;
using Snap.Hutao.Web.Enka.Model;
namespace Snap.Hutao.Service.AvatarInfo.Factory.Builder;
internal static class WeaponViewBuilderExtension
{
public static TBuilder SetAffixLevelNumber<TBuilder>(this TBuilder builder, uint affixLevelNumber)
where TBuilder : IWeaponViewBuilder
{
return builder.Configure(b => b.WeaponView.AffixLevelNumber = affixLevelNumber);
}
public static TBuilder SetAffixDescription<TBuilder>(this TBuilder builder, string? affixDescription)
where TBuilder : IWeaponViewBuilder
{
return builder.Configure(b => b.WeaponView.AffixDescription = affixDescription ?? string.Empty);
}
public static TBuilder SetAffixName<TBuilder>(this TBuilder builder, string affixName)
where TBuilder : IWeaponViewBuilder
{
return builder.Configure(b => b.WeaponView.AffixName = affixName);
}
public static TBuilder SetDescription<TBuilder>(this TBuilder builder, string description)
where TBuilder : IWeaponViewBuilder
{
return builder.Configure(b => b.WeaponView.Description = description);
}
public static TBuilder SetIcon<TBuilder>(this TBuilder builder, Uri icon)
where TBuilder : IWeaponViewBuilder
{
return builder.Configure(b => b.WeaponView.Icon = icon);
}
public static TBuilder SetId<TBuilder>(this TBuilder builder, WeaponId id)
where TBuilder : IWeaponViewBuilder
{
return builder.Configure(b => b.WeaponView.Id = id);
}
public static TBuilder SetLevel<TBuilder>(this TBuilder builder, string level)
where TBuilder : IWeaponViewBuilder
{
return builder.Configure(b => b.WeaponView.Level = level);
}
public static TBuilder SetLevelNumber<TBuilder>(this TBuilder builder, uint levelNumber)
where TBuilder : IWeaponViewBuilder
{
return builder.Configure(b => b.WeaponView.LevelNumber = levelNumber);
}
public static TBuilder SetMainProperty<TBuilder>(this TBuilder builder, WeaponStat? mainStat)
where TBuilder : IWeaponViewBuilder
{
return builder.SetMainProperty(mainStat is not null ? FightPropertyFormat.ToNameValue(mainStat.AppendPropId, mainStat.StatValue) : NameValueDefaults.String);
}
public static TBuilder SetMainProperty<TBuilder>(this TBuilder builder, NameValue<string> mainProperty)
where TBuilder : IWeaponViewBuilder
{
return builder.Configure(b => b.WeaponView.MainProperty = mainProperty);
}
public static TBuilder SetName<TBuilder>(this TBuilder builder, string name)
where TBuilder : IWeaponViewBuilder
{
return builder.Configure(b => b.WeaponView.Name = name);
}
public static TBuilder SetQuality<TBuilder>(this TBuilder builder, QualityType quality)
where TBuilder : IWeaponViewBuilder
{
return builder.Configure(b => b.WeaponView.Quality = quality);
}
public static TBuilder SetSubProperty<TBuilder>(this TBuilder builder, NameDescription subProperty)
where TBuilder : IWeaponViewBuilder
{
return builder.Configure(b => b.WeaponView.SubProperty = subProperty);
}
public static TBuilder SetWeaponType<TBuilder>(this TBuilder builder, WeaponType weaponType)
where TBuilder : IWeaponViewBuilder
{
return builder.Configure(b => b.WeaponView.WeaponType = weaponType);
}
}

View File

@@ -6,15 +6,13 @@ using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Intrinsic.Format;
using Snap.Hutao.Model.Metadata.Converter;
using Snap.Hutao.Service.AvatarInfo.Factory.Builder;
using Snap.Hutao.ViewModel.AvatarProperty;
using Snap.Hutao.Web.Enka.Model;
using System.Runtime.InteropServices;
using EntityAvatarInfo = Snap.Hutao.Model.Entity.AvatarInfo;
using MetadataAvatar = Snap.Hutao.Model.Metadata.Avatar.Avatar;
using MetadataWeapon = Snap.Hutao.Model.Metadata.Weapon.Weapon;
using ModelAvatarInfo = Snap.Hutao.Web.Enka.Model.AvatarInfo;
using PropertyAvatar = Snap.Hutao.ViewModel.AvatarProperty.AvatarView;
using PropertyReliquary = Snap.Hutao.ViewModel.AvatarProperty.ReliquaryView;
using PropertyWeapon = Snap.Hutao.ViewModel.AvatarProperty.WeaponView;
namespace Snap.Hutao.Service.AvatarInfo.Factory;
@@ -37,17 +35,17 @@ internal sealed class SummaryAvatarFactory
calculatorRefreshTime = avatarInfo.CalculatorRefreshTime;
}
public static PropertyAvatar Create(SummaryFactoryMetadataContext context, EntityAvatarInfo avatarInfo)
public static AvatarView Create(SummaryFactoryMetadataContext context, EntityAvatarInfo avatarInfo)
{
return new SummaryAvatarFactory(context, avatarInfo).Create();
}
public PropertyAvatar Create()
public AvatarView Create()
{
ReliquaryAndWeapon reliquaryAndWeapon = ProcessEquip(avatarInfo.EquipList.EmptyIfNull());
MetadataAvatar avatar = context.IdAvatarMap[avatarInfo.AvatarId];
PropertyAvatar propertyAvatar = new AvatarViewBuilder()
AvatarView propertyAvatar = new AvatarViewBuilder()
.SetId(avatar.Id)
.SetName(avatar.Name)
.SetQuality(avatar.Quality)
@@ -65,7 +63,7 @@ internal sealed class SummaryAvatarFactory
.SetShowcaseRefreshTimeFormat(showcaseRefreshTime, SH.FormatServiceAvatarInfoSummaryShowcaseRefreshTimeFormat, SH.ServiceAvatarInfoSummaryShowcaseNotRefreshed)
.SetGameRecordRefreshTimeFormat(gameRecordRefreshTime, SH.FormatServiceAvatarInfoSummaryGameRecordRefreshTimeFormat, SH.ServiceAvatarInfoSummaryGameRecordNotRefreshed)
.SetCalculatorRefreshTimeFormat(calculatorRefreshTime, SH.FormatServiceAvatarInfoSummaryCalculatorRefreshTimeFormat, SH.ServiceAvatarInfoSummaryCalculatorNotRefreshed)
.ApplyCostumeIconOrDefault(avatarInfo, avatar)
.SetCostumeIconOrDefault(avatarInfo, avatar)
.AvatarView;
return propertyAvatar;
@@ -73,8 +71,8 @@ internal sealed class SummaryAvatarFactory
private ReliquaryAndWeapon ProcessEquip(List<Equip> equipments)
{
List<PropertyReliquary> reliquaryList = [];
PropertyWeapon? weapon = null;
List<ReliquaryView> reliquaryList = [];
WeaponView? weapon = null;
foreach (ref readonly Equip equip in CollectionsMarshal.AsSpan(equipments))
{
@@ -92,7 +90,7 @@ internal sealed class SummaryAvatarFactory
return new(reliquaryList, weapon);
}
private PropertyWeapon CreateWeapon(Equip equip)
private WeaponView CreateWeapon(Equip equip)
{
MetadataWeapon weapon = context.IdWeaponMap[equip.ItemId];
@@ -118,36 +116,29 @@ internal sealed class SummaryAvatarFactory
ArgumentNullException.ThrowIfNull(equip.Weapon);
return new()
{
// NameIconDescription
Name = weapon.Name,
Icon = EquipIconConverter.IconNameToUri(weapon.Icon),
Description = weapon.Description,
// EquipBase
Level = $"Lv.{equip.Weapon.Level.Value}",
Quality = weapon.Quality,
MainProperty = mainStat is not null
? FightPropertyFormat.ToNameValue(mainStat.AppendPropId, mainStat.StatValue)
: NameValueDefaults.String,
// Weapon
Id = weapon.Id,
LevelNumber = equip.Weapon.Level,
SubProperty = subProperty,
AffixLevelNumber = affixLevel + 1,
AffixName = weapon.Affix?.Name ?? string.Empty,
AffixDescription = weapon.Affix?.Descriptions.Single(a => a.Level == affixLevel).Description ?? string.Empty,
};
return new WeaponViewBuilder()
.SetName(weapon.Name)
.SetIcon(EquipIconConverter.IconNameToUri(weapon.Icon))
.SetDescription(weapon.Description)
.SetLevel($"Lv.{equip.Weapon.Level.Value}")
.SetQuality(weapon.Quality)
.SetMainProperty(mainStat)
.SetId(weapon.Id)
.SetLevelNumber(equip.Weapon.Level)
.SetSubProperty(subProperty)
.SetAffixLevelNumber(affixLevel + 1)
.SetAffixName(weapon.Affix?.Name ?? string.Empty)
.SetAffixDescription(weapon.Affix?.Descriptions.Single(a => a.Level == affixLevel).Description)
.SetWeaponType(weapon.WeaponType)
.WeaponView;
}
private readonly struct ReliquaryAndWeapon
{
public readonly List<PropertyReliquary> Reliquaries;
public readonly PropertyWeapon? Weapon;
public readonly List<ReliquaryView> Reliquaries;
public readonly WeaponView? Weapon;
public ReliquaryAndWeapon(List<PropertyReliquary> reliquaries, PropertyWeapon? weapon)
public ReliquaryAndWeapon(List<ReliquaryView> reliquaries, WeaponView? weapon)
{
Reliquaries = reliquaries;
Weapon = weapon;

View File

@@ -13,11 +13,6 @@ namespace Snap.Hutao.Service.AvatarInfo.Factory;
[HighQuality]
internal static class SummaryAvatarProperties
{
/// <summary>
/// 创建角色属性
/// </summary>
/// <param name="fightPropMap">属性映射</param>
/// <returns>列表</returns>
public static List<AvatarProperty> Create(Dictionary<FightProperty, float>? fightPropMap)
{
if (fightPropMap is null)
@@ -25,32 +20,27 @@ internal static class SummaryAvatarProperties
return [];
}
AvatarProperty hpProp = ToAvatarProperty(FightProperty.FIGHT_PROP_BASE_HP, fightPropMap);
AvatarProperty atkProp = ToAvatarProperty(FightProperty.FIGHT_PROP_BASE_ATTACK, fightPropMap);
AvatarProperty defProp = ToAvatarProperty(FightProperty.FIGHT_PROP_BASE_DEFENSE, fightPropMap);
AvatarProperty emProp = FightPropertyFormat.ToAvatarProperty(FightProperty.FIGHT_PROP_ELEMENT_MASTERY, fightPropMap);
AvatarProperty critRateProp = FightPropertyFormat.ToAvatarProperty(FightProperty.FIGHT_PROP_CRITICAL, fightPropMap);
AvatarProperty critDMGProp = FightPropertyFormat.ToAvatarProperty(FightProperty.FIGHT_PROP_CRITICAL_HURT, fightPropMap);
AvatarProperty chargeEffProp = FightPropertyFormat.ToAvatarProperty(FightProperty.FIGHT_PROP_CHARGE_EFFICIENCY, fightPropMap);
List<AvatarProperty> properties = new(9) { hpProp, atkProp, defProp, emProp, critRateProp, critDMGProp, chargeEffProp };
List<AvatarProperty> properties =
[
ToAvatarProperty(FightProperty.FIGHT_PROP_BASE_HP, fightPropMap),
ToAvatarProperty(FightProperty.FIGHT_PROP_BASE_ATTACK, fightPropMap),
ToAvatarProperty(FightProperty.FIGHT_PROP_BASE_DEFENSE, fightPropMap),
FightPropertyFormat.ToAvatarProperty(FightProperty.FIGHT_PROP_ELEMENT_MASTERY, fightPropMap),
FightPropertyFormat.ToAvatarProperty(FightProperty.FIGHT_PROP_CRITICAL, fightPropMap),
FightPropertyFormat.ToAvatarProperty(FightProperty.FIGHT_PROP_CRITICAL_HURT, fightPropMap),
FightPropertyFormat.ToAvatarProperty(FightProperty.FIGHT_PROP_CHARGE_EFFICIENCY, fightPropMap)
];
// 元素伤害
if (TryGetBonusFightProperty(fightPropMap, out FightProperty bonusProperty, out float value))
if (TryGetBonusFightProperty(fightPropMap, out FightProperty bonusProperty, out float value) && value > 0)
{
if (value > 0)
{
properties.Add(FightPropertyFormat.ToAvatarProperty(bonusProperty, value));
}
properties.Add(FightPropertyFormat.ToAvatarProperty(bonusProperty, value));
}
// 物伤 可以和其他元素伤害并存,所以分别判断
if (fightPropMap.TryGetValue(FightProperty.FIGHT_PROP_PHYSICAL_ADD_HURT, out float addValue))
if (fightPropMap.TryGetValue(FightProperty.FIGHT_PROP_PHYSICAL_ADD_HURT, out float addValue) && addValue > 0)
{
if (addValue > 0)
{
properties.Add(FightPropertyFormat.ToAvatarProperty(FightProperty.FIGHT_PROP_PHYSICAL_ADD_HURT, addValue));
}
properties.Add(FightPropertyFormat.ToAvatarProperty(FightProperty.FIGHT_PROP_PHYSICAL_ADD_HURT, addValue));
}
return properties;

View File

@@ -28,11 +28,12 @@ internal sealed partial class SummaryFactory : ISummaryFactory
IOrderedEnumerable<AvatarView> avatars = avatarInfos
.Where(a => !AvatarIds.IsPlayer(a.Info.AvatarId))
.Select(a => SummaryAvatarFactory.Create(context, a))
.OrderByDescending(a => a.LevelNumber)
.ThenByDescending(a => a.FetterLevel)
.ThenBy(a => a.Element);
.OrderByDescending(a => a.Quality)
.ThenByDescending(a => a.LevelNumber)
.ThenBy(a => a.Element)
.ThenBy(a => a.Weapon?.WeaponType)
.ThenByDescending(a => a.FetterLevel);
// TODO: thenby weapon type
return new()
{
Avatars = [.. avatars],

View File

@@ -18,6 +18,7 @@ internal sealed class SummaryFactoryMetadataContext : IMetadataContext,
IMetadataDictionaryIdReliquaryAffixWeightSource,
IMetadataDictionaryIdReliquaryMainPropertySource,
IMetadataDictionaryIdReliquarySubAffixSource,
IMetadataDictionaryIdReliquarySource,
IMetadataListReliquaryMainAffixLevelSource
{
public Dictionary<AvatarId, MetadataAvatar> IdAvatarMap { get; set; } = default!;
@@ -32,5 +33,5 @@ internal sealed class SummaryFactoryMetadataContext : IMetadataContext,
public List<ReliquaryMainAffixLevel> ReliquaryMainAffixLevels { get; set; } = default!;
public List<MetadataReliquary> Reliquaries { get; set; } = default!;
public Dictionary<ReliquaryId, MetadataReliquary> IdReliquaryMap { get; set; } = default!;
}

View File

@@ -1,6 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Core.ExceptionService;
using Snap.Hutao.Model.Primitive;
namespace Snap.Hutao.Service.AvatarInfo.Factory;
@@ -11,11 +12,6 @@ namespace Snap.Hutao.Service.AvatarInfo.Factory;
[HighQuality]
internal static class SummaryHelper
{
/// <summary>
/// 获取副属性对应的最大属性的Id
/// </summary>
/// <param name="appendId">属性Id</param>
/// <returns>最大属性Id</returns>
public static ReliquarySubAffixId GetAffixMaxId(in ReliquarySubAffixId appendId)
{
// axxxxx -> a
@@ -27,18 +23,13 @@ internal static class SummaryHelper
1 => 2,
2 => 3,
3 or 4 or 5 => 4,
_ => throw Must.NeverHappen(),
_ => throw HutaoException.Throw($"不支持的 ReliquarySubAffixId: {appendId}"),
};
// axxxxb -> axxxx -> axxxx0 -> axxxxm
return ((appendId / 10) * 10) + max;
}
/// <summary>
/// 获取百分比属性副词条分数
/// </summary>
/// <param name="appendId">id</param>
/// <returns>分数</returns>
public static float GetPercentSubAffixScore(in ReliquarySubAffixId appendId)
{
// 圣遗物相同类型副词条强化档位一共为 4/3/2 档
@@ -65,7 +56,7 @@ internal static class SummaryHelper
(1, 0) => 100F,
(1, 1) => 80F,
_ => throw Must.NeverHappen($"Unexpected AppendId: {appendId.Value} Delta: {delta}"),
_ => throw HutaoException.Throw($"Unexpected AppendId: {appendId} Delta: {delta}"),
};
}
}

View File

@@ -13,9 +13,6 @@ using ModelAvatarInfo = Snap.Hutao.Web.Enka.Model.AvatarInfo;
namespace Snap.Hutao.Service.AvatarInfo.Factory;
/// <summary>
/// 圣遗物工厂
/// </summary>
[HighQuality]
internal sealed class SummaryReliquaryFactory
{
@@ -37,7 +34,7 @@ internal sealed class SummaryReliquaryFactory
public ReliquaryView Create()
{
MetadataReliquary reliquary = metadataContext.Reliquaries.Single(r => r.Ids.Contains(equip.ItemId));
MetadataReliquary reliquary = metadataContext.IdReliquaryMap[equip.ItemId];
ArgumentNullException.ThrowIfNull(equip.Reliquary);
List<ReliquarySubProperty> subProperty = equip.Reliquary.AppendPropIdList.EmptyIfNull().SelectList(CreateSubProperty);
@@ -74,10 +71,10 @@ internal sealed class SummaryReliquaryFactory
return result;
}
private static int GetSecondaryAffixCount(MetadataReliquary reliquary, Web.Enka.Model.Reliquary enkaReliquary)
private static int GetSecondaryAffixCount(MetadataReliquary metaReliquary, Web.Enka.Model.Reliquary enkaReliquary)
{
// 强化词条个数
return (reliquary.RankLevel, enkaReliquary.Level.Value) switch
return (metaReliquary.RankLevel, enkaReliquary.Level.Value) switch
{
(QualityType.QUALITY_ORANGE, > 20U) => 5,
(QualityType.QUALITY_ORANGE, > 16U) => 4,

View File

@@ -0,0 +1,12 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Metadata.Reliquary;
using Snap.Hutao.Model.Primitive;
namespace Snap.Hutao.Service.Metadata.ContextAbstraction;
internal interface IMetadataDictionaryIdReliquarySource
{
public Dictionary<ReliquaryId, Reliquary> IdReliquaryMap { get; set; }
}

View File

@@ -62,6 +62,11 @@ internal static class MetadataServiceContextExtension
dictionaryIdMaterialSource.IdMaterialMap = await metadataService.GetIdToMaterialMapAsync(token).ConfigureAwait(false);
}
if (context is IMetadataDictionaryIdReliquarySource dictionaryIdReliquarySource)
{
dictionaryIdReliquarySource.IdReliquaryMap = await metadataService.GetIdToReliquaryMapAsync(token).ConfigureAwait(false);
}
if (context is IMetadataDictionaryIdReliquaryAffixWeightSource dictionaryIdReliquaryAffixWeightSource)
{
dictionaryIdReliquaryAffixWeightSource.IdReliquaryAffixWeightMap = await metadataService.GetIdToReliquaryAffixWeightMapAsync(token).ConfigureAwait(false);

View File

@@ -6,6 +6,7 @@ using Snap.Hutao.Core;
using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
using Snap.Hutao.Core.Diagnostics;
using Snap.Hutao.Core.ExceptionService;
using Snap.Hutao.Core.IO;
using Snap.Hutao.Core.IO.Hashing;
using Snap.Hutao.Core.Setting;
using Snap.Hutao.Service.Notification;
@@ -29,7 +30,7 @@ internal sealed partial class MetadataService : IMetadataService, IMetadataServi
private readonly TaskCompletionSource initializeCompletionSource = new();
private readonly IHttpClientFactory httpClientFactory;
private readonly IServiceScopeFactory serviceScopeFactory;
private readonly ILogger<MetadataService> logger;
private readonly MetadataOptions metadataOptions;
private readonly IInfoBarService infoBarService;
@@ -119,12 +120,16 @@ internal sealed partial class MetadataService : IMetadataService, IMetadataServi
Dictionary<string, string>? metadataFileHashs;
try
{
// Download meta check file
using (HttpClient httpClient = httpClientFactory.CreateClient(nameof(MetadataService)))
using (IServiceScope scope = serviceScopeFactory.CreateScope())
{
metadataFileHashs = await httpClient
.GetFromJsonAsync<Dictionary<string, string>>(metadataOptions.GetLocalizedRemoteFile(MetaFileName), options, token)
.ConfigureAwait(false);
IHttpClientFactory httpClientFactory = scope.ServiceProvider.GetRequiredService<IHttpClientFactory>();
using (HttpClient httpClient = httpClientFactory.CreateClient(nameof(MetadataService)))
{
// Download meta check file
metadataFileHashs = await httpClient
.GetFromJsonAsync<Dictionary<string, string>>(metadataOptions.GetLocalizedRemoteFile(MetaFileName), options, token)
.ConfigureAwait(false);
}
}
if (metadataFileHashs is null)
@@ -180,26 +185,27 @@ internal sealed partial class MetadataService : IMetadataService, IMetadataServi
private async ValueTask DownloadMetadataSourceFilesAsync(string fileFullName, CancellationToken token)
{
Stream sourceStream;
using (HttpClient httpClient = httpClientFactory.CreateClient(nameof(MetadataService)))
using (IServiceScope scope = serviceScopeFactory.CreateScope())
{
sourceStream = await httpClient
.GetStreamAsync(metadataOptions.GetLocalizedRemoteFile(fileFullName), token)
.ConfigureAwait(false);
IHttpClientFactory httpClientFactory = scope.ServiceProvider.GetRequiredService<IHttpClientFactory>();
using (HttpClient httpClient = httpClientFactory.CreateClient(nameof(MetadataService)))
{
sourceStream = await httpClient
.GetStreamAsync(metadataOptions.GetLocalizedRemoteFile(fileFullName), token)
.ConfigureAwait(false);
}
}
// Write stream while convert LF to CRLF
using (StreamReader streamReader = new(sourceStream))
using (StreamReaderWriter readerWriter = new(new(sourceStream), File.CreateText(metadataOptions.GetLocalizedLocalFile(fileFullName))))
{
using (StreamWriter streamWriter = File.CreateText(metadataOptions.GetLocalizedLocalFile(fileFullName)))
while (await readerWriter.ReadLineAsync(token).ConfigureAwait(false) is { } line)
{
while (await streamReader.ReadLineAsync(token).ConfigureAwait(false) is { } line)
{
await streamWriter.WriteAsync(line).ConfigureAwait(false);
await readerWriter.WriteAsync(line).ConfigureAwait(false);
if (!streamReader.EndOfStream)
{
await streamWriter.WriteAsync(StringLiterals.CRLF).ConfigureAwait(false);
}
if (!readerWriter.Reader.EndOfStream)
{
await readerWriter.WriteAsync(StringLiterals.CRLF).ConfigureAwait(false);
}
}
}

View File

@@ -25,22 +25,12 @@ internal static class MetadataServiceDictionaryExtension
public static ValueTask<Dictionary<ExtendedEquipAffixId, ReliquarySet>> GetExtendedEquipAffixIdToReliquarySetMapAsync(this IMetadataService metadataService, CancellationToken token = default)
{
return metadataService.FromCacheAsDictionaryAsync<ReliquarySet, (ExtendedEquipAffixId Id, ReliquarySet Set), ExtendedEquipAffixId, ReliquarySet>(
FileNameReliquarySet,
list => list.SelectMany(set => set.EquipAffixIds.Select(id => (Id: id, Set: set))),
tuple => tuple.Id,
tuple => tuple.Set,
token);
return metadataService.FromCacheAsDictionaryAsync(FileNameReliquarySet, (List<ReliquarySet> list) => list.SelectMany(set => set.EquipAffixIds, (set, id) => (Id: id, Set: set)), token);
}
public static ValueTask<Dictionary<TowerLevelGroupId, List<TowerLevel>>> GetGroupIdToTowerLevelGroupMapAsync(this IMetadataService metadataService, CancellationToken token = default)
{
return metadataService.FromCacheAsDictionaryAsync<TowerLevel, IGrouping<TowerLevelGroupId, TowerLevel>, TowerLevelGroupId, List<TowerLevel>>(
FileNameTowerLevel,
list => list.GroupBy(l => l.GroupId),
g => g.Key,
g => g.ToList(),
token);
return metadataService.FromCacheAsDictionaryAsync(FileNameTowerLevel, (List<TowerLevel> list) => list.GroupBy(l => l.GroupId), g => g.Key, g => g.ToList(), token);
}
public static ValueTask<Dictionary<AchievementId, Model.Metadata.Achievement.Achievement>> GetIdToAchievementMapAsync(this IMetadataService metadataService, CancellationToken token = default)
@@ -81,6 +71,11 @@ internal static class MetadataServiceDictionaryExtension
return metadataService.FromCacheAsDictionaryAsync<MaterialId, Material>(FileNameMaterial, a => a.Id, token);
}
public static ValueTask<Dictionary<ReliquaryId, Reliquary>> GetIdToReliquaryMapAsync(this IMetadataService metadataService, CancellationToken token = default)
{
return metadataService.FromCacheAsDictionaryAsync(FileNameReliquary, (List<Reliquary> list) => list.SelectMany(r => r.Ids, (r, i) => (Index: i, Reliquary: r)), token);
}
public static ValueTask<Dictionary<AvatarId, ReliquaryAffixWeight>> GetIdToReliquaryAffixWeightMapAsync(this IMetadataService metadataService, CancellationToken token = default)
{
return metadataService.FromCacheAsDictionaryAsync<AvatarId, ReliquaryAffixWeight>(FileNameReliquaryAffixWeight, r => r.AvatarId, token);
@@ -177,6 +172,12 @@ internal static class MetadataServiceDictionaryExtension
return metadataService.MemoryCache.Set(cacheKey, dict);
}
private static ValueTask<Dictionary<TKey, TValue>> FromCacheAsDictionaryAsync<TData, TKey, TValue>(this IMetadataService metadataService, string fileName, Func<List<TData>, IEnumerable<(TKey Key, TValue Value)>> listSelector, CancellationToken token)
where TKey : notnull
{
return FromCacheAsDictionaryAsync(metadataService, fileName, listSelector, kvp => kvp.Key, kvp => kvp.Value, token);
}
private static async ValueTask<Dictionary<TKey, TValue>> FromCacheAsDictionaryAsync<TData, TMiddle, TKey, TValue>(this IMetadataService metadataService, string fileName, Func<List<TData>, IEnumerable<TMiddle>> listSelector, Func<TMiddle, TKey> keySelector, Func<TMiddle, TValue> valueSelector, CancellationToken token)
where TKey : notnull
{

View File

@@ -10,7 +10,7 @@ namespace Snap.Hutao.ViewModel.AvatarProperty;
/// 装备基类
/// </summary>
[HighQuality]
internal abstract class Equip : NameIconDescription
internal abstract class EquipView : NameIconDescription
{
/// <summary>
/// 等级

View File

@@ -7,7 +7,7 @@ namespace Snap.Hutao.ViewModel.AvatarProperty;
/// 圣遗物
/// </summary>
[HighQuality]
internal sealed class ReliquaryView : Equip
internal sealed class ReliquaryView : EquipView
{
/// <summary>
/// 初始词条

View File

@@ -3,6 +3,7 @@
using Snap.Hutao.Model;
using Snap.Hutao.Model.Calculable;
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Primitive;
namespace Snap.Hutao.ViewModel.AvatarProperty;
@@ -11,7 +12,7 @@ namespace Snap.Hutao.ViewModel.AvatarProperty;
/// 武器
/// </summary>
[HighQuality]
internal sealed class WeaponView : Equip, ICalculableSource<ICalculableWeapon>
internal sealed class WeaponView : EquipView, ICalculableSource<ICalculableWeapon>
{
/// <summary>
/// 副属性
@@ -53,6 +54,8 @@ internal sealed class WeaponView : Equip, ICalculableSource<ICalculableWeapon>
/// </summary>
internal uint MaxLevel { get => Model.Metadata.Weapon.Weapon.GetMaxLevelByQuality(Quality); }
internal WeaponType WeaponType { get; set; }
/// <inheritdoc/>
public ICalculableWeapon ToCalculable()
{