metadata service refactor

This commit is contained in:
DismissedLight
2023-12-03 21:20:49 +08:00
parent ed556c8539
commit 21ad6be9da
28 changed files with 463 additions and 694 deletions

View File

@@ -27,8 +27,8 @@ internal sealed partial class SummaryFactory : ISummaryFactory
IdReliquaryAffixWeightMap = await metadataService.GetIdToReliquaryAffixWeightMapAsync(token).ConfigureAwait(false),
IdReliquaryMainAffixMap = await metadataService.GetIdToReliquaryMainPropertyMapAsync(token).ConfigureAwait(false),
IdReliquarySubAffixMap = await metadataService.GetIdToReliquarySubAffixMapAsync(token).ConfigureAwait(false),
ReliquaryLevels = await metadataService.GetReliquaryLevelsAsync(token).ConfigureAwait(false),
Reliquaries = await metadataService.GetReliquariesAsync(token).ConfigureAwait(false),
ReliquaryLevels = await metadataService.GetReliquaryLevelListAsync(token).ConfigureAwait(false),
Reliquaries = await metadataService.GetReliquaryListAsync(token).ConfigureAwait(false),
};
IOrderedEnumerable<AvatarView> avatars = avatarInfos

View File

@@ -31,7 +31,7 @@ internal sealed partial class GachaStatisticsFactory : IGachaStatisticsFactory
public async ValueTask<GachaStatistics> CreateAsync(List<Model.Entity.GachaItem> items, GachaLogServiceMetadataContext context)
{
await taskContext.SwitchToBackgroundAsync();
List<GachaEvent> gachaEvents = await metadataService.GetGachaEventsAsync().ConfigureAwait(false);
List<GachaEvent> gachaEvents = await metadataService.GetGachaEventListAsync().ConfigureAwait(false);
List<HistoryWishBuilder> historyWishBuilders = gachaEvents.SelectList(gachaEvent => new HistoryWishBuilder(gachaEvent, context));
return CreateCore(taskContext, homaGachaLogClient, items, historyWishBuilders, context, options.IsEmptyHistoryWishVisible);

View File

@@ -96,7 +96,7 @@ internal sealed partial class GachaLogHutaoCloudService : IGachaLogHutaoCloudSer
{
Dictionary<AvatarId, Avatar> idAvatarMap = await metadataService.GetIdToAvatarMapAsync(token).ConfigureAwait(false);
Dictionary<WeaponId, Weapon> idWeaponMap = await metadataService.GetIdToWeaponMapAsync(token).ConfigureAwait(false);
List<GachaEvent> gachaEvents = await metadataService.GetGachaEventsAsync(token).ConfigureAwait(false);
List<GachaEvent> gachaEvents = await metadataService.GetGachaEventListAsync(token).ConfigureAwait(false);
HutaoStatisticsFactoryMetadataContext context = new(idAvatarMap, idWeaponMap, gachaEvents);
GachaEventStatistics raw = response.Data;

View File

@@ -12,13 +12,10 @@ using Snap.Hutao.Web.Hutao.SpiralAbyss;
namespace Snap.Hutao.Service.Hutao;
/// <summary>
/// 胡桃 API 缓存
/// </summary>
[HighQuality]
[ConstructorGenerated]
[Injection(InjectAs.Singleton, typeof(IHutaoCache))]
internal sealed partial class HutaoCache : IHutaoCache
[Injection(InjectAs.Singleton, typeof(IHutaoSpiralAbyssStatisticsCache))]
internal sealed partial class HutaoSpiralAbyssStatisticsCache : IHutaoSpiralAbyssStatisticsCache
{
private readonly IMetadataService metadataService;
private readonly IServiceProvider serviceProvider;
@@ -51,7 +48,7 @@ internal sealed partial class HutaoCache : IHutaoCache
public Dictionary<WeaponId, WeaponCollocationView>? WeaponCollocations { get; set; }
/// <inheritdoc/>
public async ValueTask<bool> InitializeForDatabaseViewModelAsync()
public async ValueTask<bool> InitializeForSpiralAbyssViewAsync()
{
if (databaseViewModelTaskSource is not null)
{
@@ -82,7 +79,7 @@ internal sealed partial class HutaoCache : IHutaoCache
}
/// <inheritdoc/>
public async ValueTask<bool> InitializeForWikiAvatarViewModelAsync()
public async ValueTask<bool> InitializeForWikiAvatarViewAsync()
{
if (wikiAvatarViewModelTaskSource is not null)
{
@@ -106,7 +103,7 @@ internal sealed partial class HutaoCache : IHutaoCache
}
/// <inheritdoc/>
public async ValueTask<bool> InitializeForWikiWeaponViewModelAsync()
public async ValueTask<bool> InitializeForWikiWeaponViewAsync()
{
if (wikiWeaponViewModelTaskSource is not null)
{

View File

@@ -12,7 +12,7 @@ namespace Snap.Hutao.Service.Hutao;
/// 胡桃 API 缓存
/// </summary>
[HighQuality]
internal interface IHutaoCache
internal interface IHutaoSpiralAbyssStatisticsCache
{
/// <summary>
/// 角色使用率
@@ -53,17 +53,17 @@ internal interface IHutaoCache
/// 为数据库视图模型初始化
/// </summary>
/// <returns>是否初始化完成</returns>
ValueTask<bool> InitializeForDatabaseViewModelAsync();
ValueTask<bool> InitializeForSpiralAbyssViewAsync();
/// <summary>
/// 为Wiki角色视图模型初始化
/// </summary>
/// <returns>是否初始化完成</returns>
ValueTask<bool> InitializeForWikiAvatarViewModelAsync();
ValueTask<bool> InitializeForWikiAvatarViewAsync();
/// <summary>
/// 为Wiki武器视图模型初始化
/// </summary>
/// <returns>是否初始化完成</returns>
ValueTask<bool> InitializeForWikiWeaponViewModelAsync();
ValueTask<bool> InitializeForWikiWeaponViewAsync();
}

View File

@@ -1,6 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.Extensions.Caching.Memory;
using Snap.Hutao.Core.DependencyInjection.Abstraction;
namespace Snap.Hutao.Service.Metadata;
@@ -9,12 +10,13 @@ namespace Snap.Hutao.Service.Metadata;
/// 元数据服务
/// </summary>
[HighQuality]
internal interface IMetadataService : ICastService,
IMetadataServiceRawData,
IMetadataServiceIdDataMap,
IMetadataServiceNameDataMap,
IMetadataServiceNameLevelCurveMap
internal interface IMetadataService : ICastService
{
IMemoryCache MemoryCache { get; }
ValueTask<T> FromCacheOrFileAsync<T>(string fileName, CancellationToken token)
where T : class;
/// <summary>
/// 异步初始化服务,尝试更新元数据
/// </summary>

View File

@@ -1,89 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Metadata.Avatar;
using Snap.Hutao.Model.Metadata.Item;
using Snap.Hutao.Model.Metadata.Monster;
using Snap.Hutao.Model.Metadata.Reliquary;
using Snap.Hutao.Model.Metadata.Tower;
using Snap.Hutao.Model.Metadata.Weapon;
using Snap.Hutao.Model.Primitive;
namespace Snap.Hutao.Service.Metadata;
internal interface IMetadataServiceIdDataMap
{
/// <summary>
/// 异步获取装备被动Id到圣遗物套装的映射
/// </summary>
/// <param name="token">取消令牌</param>
/// <returns>装备被动Id到圣遗物套装的映射</returns>
ValueTask<Dictionary<EquipAffixId, ReliquarySet>> GetEquipAffixIdToReliquarySetMapAsync(CancellationToken token = default);
ValueTask<Dictionary<ExtendedEquipAffixId, ReliquarySet>> GetExtendedEquipAffixIdToReliquarySetMapAsync(CancellationToken token = default);
/// <summary>
/// 异步获取成就映射
/// </summary>
/// <param name="token">取消令牌</param>
/// <returns>成就映射</returns>
ValueTask<Dictionary<AchievementId, Model.Metadata.Achievement.Achievement>> GetIdToAchievementMapAsync(CancellationToken token = default);
/// <summary>
/// 异步获取Id到角色的字典
/// </summary>
/// <param name="token">取消令牌</param>
/// <returns>Id到角色的字典</returns>
ValueTask<Dictionary<AvatarId, Avatar>> GetIdToAvatarMapAsync(CancellationToken token = default);
/// <summary>
/// 异步获取显示与材料映射
/// </summary>
/// <param name="token">取消令牌</param>
/// <returns>显示与材料映射</returns>
ValueTask<Dictionary<MaterialId, DisplayItem>> GetIdToDisplayItemAndMaterialMapAsync(CancellationToken token = default);
/// <summary>
/// 异步获取Id到材料的字典
/// </summary>
/// <param name="token">取消令牌</param>
/// <returns>Id到材料的字典</returns>
ValueTask<Dictionary<MaterialId, Material>> GetIdToMaterialMapAsync(CancellationToken token = default(CancellationToken));
/// <summary>
/// 异步获取Id到圣遗物权重的映射
/// </summary>
/// <param name="token">取消令牌</param>
/// <returns>Id到圣遗物权重的字典</returns>
ValueTask<Dictionary<AvatarId, ReliquaryAffixWeight>> GetIdToReliquaryAffixWeightMapAsync(CancellationToken token = default);
/// <summary>
/// 异步获取ID到圣遗物副词条的字典
/// </summary>
/// <param name="token">取消令牌</param>
/// <returns>字典</returns>
ValueTask<Dictionary<ReliquarySubAffixId, ReliquarySubAffix>> GetIdToReliquarySubAffixMapAsync(CancellationToken token = default);
/// <summary>
/// 异步获取圣遗物主词条Id与属性的字典
/// </summary>
/// <param name="token">取消令牌</param>
/// <returns>字典</returns>
ValueTask<Dictionary<ReliquaryMainAffixId, FightProperty>> GetIdToReliquaryMainPropertyMapAsync(CancellationToken token = default);
ValueTask<Dictionary<TowerScheduleId, TowerSchedule>> GetIdToTowerScheduleMapAsync(CancellationToken token = default);
/// <summary>
/// 异步获取ID到武器的字典
/// </summary>
/// <param name="token">取消令牌</param>
/// <returns>Id到武器的字典</returns>
ValueTask<Dictionary<WeaponId, Weapon>> GetIdToWeaponMapAsync(CancellationToken token = default);
ValueTask<Dictionary<TowerLevelGroupId, List<TowerLevel>>> GetGroupIdToTowerLevelGroupMapAsync(CancellationToken token = default);
ValueTask<Dictionary<TowerFloorId, TowerFloor>> GetIdToTowerFloorMapAsync(CancellationToken token = default);
ValueTask<Dictionary<MonsterRelationshipId, Monster>> GetRelationshipIdToMonsterMapAsync(CancellationToken token = default);
}

View File

@@ -1,24 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Metadata.Avatar;
using Snap.Hutao.Model.Metadata.Weapon;
namespace Snap.Hutao.Service.Metadata;
internal interface IMetadataServiceNameDataMap
{
/// <summary>
/// 异步获取名称到角色的字典
/// </summary>
/// <param name="token">取消令牌</param>
/// <returns>名称到角色的字典</returns>
ValueTask<Dictionary<string, Avatar>> GetNameToAvatarMapAsync(CancellationToken token = default);
/// <summary>
/// 异步获取名称到武器的字典
/// </summary>
/// <param name="token">取消令牌</param>
/// <returns>名称到武器的字典</returns>
ValueTask<Dictionary<string, Weapon>> GetNameToWeaponMapAsync(CancellationToken token = default);
}

View File

@@ -1,31 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Primitive;
namespace Snap.Hutao.Service.Metadata;
internal interface IMetadataServiceNameLevelCurveMap
{
/// <summary>
/// 异步获取等级角色曲线映射
/// </summary>
/// <param name="token">取消令牌</param>
/// <returns>等级角色曲线映射</returns>
ValueTask<Dictionary<Level, Dictionary<GrowCurveType, float>>> GetLevelToAvatarCurveMapAsync(CancellationToken token = default);
/// <summary>
/// 异步获取等级怪物曲线映射
/// </summary>
/// <param name="token">取消令牌</param>
/// <returns>等级怪物曲线映射</returns>
ValueTask<Dictionary<Level, Dictionary<GrowCurveType, float>>> GetLevelToMonsterCurveMapAsync(CancellationToken token = default);
/// <summary>
/// 异步获取等级武器曲线映射
/// </summary>
/// <param name="token">取消令牌</param>
/// <returns>等级武器曲线映射</returns>
ValueTask<Dictionary<Level, Dictionary<GrowCurveType, float>>> GetLevelToWeaponCurveMapAsync(CancellationToken token = default);
}

View File

@@ -1,113 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Metadata;
using Snap.Hutao.Model.Metadata.Achievement;
using Snap.Hutao.Model.Metadata.Avatar;
using Snap.Hutao.Model.Metadata.Item;
using Snap.Hutao.Model.Metadata.Monster;
using Snap.Hutao.Model.Metadata.Reliquary;
using Snap.Hutao.Model.Metadata.Weapon;
namespace Snap.Hutao.Service.Metadata;
internal interface IMetadataServiceRawData
{
/// <summary>
/// 异步获取成就列表
/// </summary>
/// <param name="token">取消令牌</param>
/// <returns>成就列表</returns>
ValueTask<List<Model.Metadata.Achievement.Achievement>> GetAchievementsAsync(CancellationToken token = default);
/// <summary>
/// 异步获取成就分类列表
/// </summary>
/// <param name="token">取消令牌</param>
/// <returns>成就分类列表</returns>
ValueTask<List<AchievementGoal>> GetAchievementGoalsAsync(CancellationToken token = default);
/// <summary>
/// 异步获取角色突破列表
/// </summary>
/// <param name="token">取消令牌</param>
/// <returns>角色突破列表</returns>
ValueTask<List<Promote>> GetAvatarPromotesAsync(CancellationToken token = default);
/// <summary>
/// 异步获取角色列表
/// </summary>
/// <param name="token">取消令牌</param>
/// <returns>角色列表</returns>
ValueTask<List<Avatar>> GetAvatarsAsync(CancellationToken token = default);
/// <summary>
/// 异步获取卡池配置列表
/// </summary>
/// <param name="token">取消令牌</param>
/// <returns>卡池配置列表</returns>
ValueTask<List<GachaEvent>> GetGachaEventsAsync(CancellationToken token = default);
/// <summary>
/// 异步获取材料列表
/// </summary>
/// <param name="token">取消令牌</param>
/// <returns>材料列表</returns>
ValueTask<List<Material>> GetMaterialsAsync(CancellationToken token = default);
/// <summary>
/// 异步获取怪物列表
/// </summary>
/// <param name="token">取消令牌</param>
/// <returns>怪物列表</returns>
ValueTask<List<Monster>> GetMonstersAsync(CancellationToken token = default);
/// <summary>
/// 异步获取圣遗物列表
/// </summary>
/// <param name="token">取消令牌</param>
/// <returns>圣遗物列表</returns>
ValueTask<List<Reliquary>> GetReliquariesAsync(CancellationToken token = default);
/// <summary>
/// 异步获取圣遗物强化属性列表
/// </summary>
/// <param name="token">取消令牌</param>
/// <returns>圣遗物强化属性列表</returns>
ValueTask<List<ReliquarySubAffix>> GetReliquarySubAffixesAsync(CancellationToken token = default);
/// <summary>
/// 异步获取圣遗物等级数据
/// </summary>
/// <param name="token">取消令牌</param>
/// <returns>圣遗物等级数据</returns>
ValueTask<List<ReliquaryMainAffixLevel>> GetReliquaryLevelsAsync(CancellationToken token = default);
/// <summary>
/// 异步获取圣遗物主属性强化属性列表
/// </summary>
/// <param name="token">取消令牌</param>
/// <returns>圣遗物强化属性列表</returns>
ValueTask<List<ReliquaryMainAffix>> GetReliquaryMainAffixesAsync(CancellationToken token = default);
/// <summary>
/// 异步获取圣遗物套装
/// </summary>
/// <param name="token">取消令牌</param>
/// <returns>圣遗物套装列表</returns>
ValueTask<List<ReliquarySet>> GetReliquarySetsAsync(CancellationToken token = default);
/// <summary>
/// 异步获取武器突破列表
/// </summary>
/// <param name="token">取消令牌</param>
/// <returns>武器突破列表</returns>
ValueTask<List<Promote>> GetWeaponPromotesAsync(CancellationToken token = default);
/// <summary>
/// 异步获取武器列表
/// </summary>
/// <param name="token">取消令牌</param>
/// <returns>武器列表</returns>
ValueTask<List<Weapon>> GetWeaponsAsync(CancellationToken token = default);
}

View File

@@ -0,0 +1,30 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Service.Metadata;
internal static class MetadataFileNames
{
public const string FileNameAchievement = "Achievement";
public const string FileNameAchievementGoal = "AchievementGoal";
public const string FileNameAvatar = "Avatar";
public const string FileNameAvatarCurve = "AvatarCurve";
public const string FileNameAvatarPromote = "AvatarPromote";
public const string FileNameDisplayItem = "DisplayItem";
public const string FileNameGachaEvent = "GachaEvent";
public const string FileNameMaterial = "Material";
public const string FileNameMonster = "Monster";
public const string FileNameMonsterCurve = "MonsterCurve";
public const string FileNameReliquary = "Reliquary";
public const string FileNameReliquaryAffixWeight = "ReliquaryAffixWeight";
public const string FileNameReliquaryMainAffix = "ReliquaryMainAffix";
public const string FileNameReliquaryMainAffixLevel = "ReliquaryMainAffixLevel";
public const string FileNameReliquarySet = "ReliquarySet";
public const string FileNameReliquarySubAffix = "ReliquarySubAffix";
public const string FileNameTowerFloor = "TowerFloor";
public const string FileNameTowerLevel = "TowerLevel";
public const string FileNameTowerSchedule = "TowerSchedule";
public const string FileNameWeapon = "Weapon";
public const string FileNameWeaponCurve = "WeaponCurve";
public const string FileNameWeaponPromote = "WeaponPromote";
}

View File

@@ -1,33 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Service.Metadata;
/// <summary>
/// 名称常量
/// </summary>
internal partial class MetadataService
{
private const string FileNameAchievement = "Achievement";
private const string FileNameAchievementGoal = "AchievementGoal";
private const string FileNameAvatar = "Avatar";
private const string FileNameAvatarCurve = "AvatarCurve";
private const string FileNameAvatarPromote = "AvatarPromote";
private const string FileNameDisplayItem = "DisplayItem";
private const string FileNameGachaEvent = "GachaEvent";
private const string FileNameMaterial = "Material";
private const string FileNameMonster = "Monster";
private const string FileNameMonsterCurve = "MonsterCurve";
private const string FileNameReliquary = "Reliquary";
private const string FileNameReliquaryAffixWeight = "ReliquaryAffixWeight";
private const string FileNameReliquaryMainAffix = "ReliquaryMainAffix";
private const string FileNameReliquaryMainAffixLevel = "ReliquaryMainAffixLevel";
private const string FileNameReliquarySet = "ReliquarySet";
private const string FileNameReliquarySubAffix = "ReliquarySubAffix";
private const string FileNameTowerFloor = "TowerFloor";
private const string FileNameTowerLevel = "TowerLevel";
private const string FileNameTowerSchedule = "TowerSchedule";
private const string FileNameWeapon = "Weapon";
private const string FileNameWeaponCurve = "WeaponCurve";
private const string FileNameWeaponPromote = "WeaponPromote";
}

View File

@@ -1,102 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Metadata;
using Snap.Hutao.Model.Metadata.Achievement;
using Snap.Hutao.Model.Metadata.Avatar;
using Snap.Hutao.Model.Metadata.Item;
using Snap.Hutao.Model.Metadata.Monster;
using Snap.Hutao.Model.Metadata.Reliquary;
using Snap.Hutao.Model.Metadata.Weapon;
namespace Snap.Hutao.Service.Metadata;
/// <summary>
/// 接口实现部分
/// </summary>
internal sealed partial class MetadataService
{
/// <inheritdoc/>
public ValueTask<List<AchievementGoal>> GetAchievementGoalsAsync(CancellationToken token = default)
{
return FromCacheOrFileAsync<List<AchievementGoal>>(FileNameAchievementGoal, token);
}
/// <inheritdoc/>
public ValueTask<List<Model.Metadata.Achievement.Achievement>> GetAchievementsAsync(CancellationToken token = default)
{
return FromCacheOrFileAsync<List<Model.Metadata.Achievement.Achievement>>(FileNameAchievement, token);
}
/// <inheritdoc/>
public ValueTask<List<Avatar>> GetAvatarsAsync(CancellationToken token = default)
{
return FromCacheOrFileAsync<List<Avatar>>(FileNameAvatar, token);
}
/// <inheritdoc/>
public ValueTask<List<Promote>> GetAvatarPromotesAsync(CancellationToken token = default)
{
return FromCacheOrFileAsync<List<Promote>>(FileNameAvatarPromote, token);
}
/// <inheritdoc/>
public ValueTask<List<GachaEvent>> GetGachaEventsAsync(CancellationToken token = default)
{
return FromCacheOrFileAsync<List<GachaEvent>>(FileNameGachaEvent, token);
}
/// <inheritdoc/>
public ValueTask<List<Material>> GetMaterialsAsync(CancellationToken token = default)
{
return FromCacheOrFileAsync<List<Material>>(FileNameMaterial, token);
}
/// <inheritdoc/>
public ValueTask<List<Monster>> GetMonstersAsync(CancellationToken token = default)
{
return FromCacheOrFileAsync<List<Monster>>(FileNameMonster, token);
}
/// <inheritdoc/>
public ValueTask<List<Reliquary>> GetReliquariesAsync(CancellationToken token = default)
{
return FromCacheOrFileAsync<List<Reliquary>>(FileNameReliquary, token);
}
/// <inheritdoc/>
public ValueTask<List<ReliquaryMainAffixLevel>> GetReliquaryLevelsAsync(CancellationToken token = default)
{
return FromCacheOrFileAsync<List<ReliquaryMainAffixLevel>>(FileNameReliquaryMainAffixLevel, token);
}
/// <inheritdoc/>
public ValueTask<List<ReliquaryMainAffix>> GetReliquaryMainAffixesAsync(CancellationToken token = default)
{
return FromCacheOrFileAsync<List<ReliquaryMainAffix>>(FileNameReliquaryMainAffix, token);
}
/// <inheritdoc/>
public ValueTask<List<ReliquarySet>> GetReliquarySetsAsync(CancellationToken token = default)
{
return FromCacheOrFileAsync<List<ReliquarySet>>(FileNameReliquarySet, token);
}
/// <inheritdoc/>
public ValueTask<List<ReliquarySubAffix>> GetReliquarySubAffixesAsync(CancellationToken token = default)
{
return FromCacheOrFileAsync<List<ReliquarySubAffix>>(FileNameReliquarySubAffix, token);
}
/// <inheritdoc/>
public ValueTask<List<Weapon>> GetWeaponsAsync(CancellationToken token = default)
{
return FromCacheOrFileAsync<List<Weapon>>(FileNameWeapon, token);
}
/// <inheritdoc/>
public ValueTask<List<Promote>> GetWeaponPromotesAsync(CancellationToken token = default)
{
return FromCacheOrFileAsync<List<Promote>>(FileNameWeaponPromote, token);
}
}

View File

@@ -1,171 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.Extensions.Caching.Memory;
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Metadata;
using Snap.Hutao.Model.Metadata.Avatar;
using Snap.Hutao.Model.Metadata.Item;
using Snap.Hutao.Model.Metadata.Monster;
using Snap.Hutao.Model.Metadata.Reliquary;
using Snap.Hutao.Model.Metadata.Tower;
using Snap.Hutao.Model.Metadata.Weapon;
using Snap.Hutao.Model.Primitive;
namespace Snap.Hutao.Service.Metadata;
/// <summary>
/// 索引部分
/// </summary>
internal sealed partial class MetadataService
{
/// <inheritdoc/>
public ValueTask<Dictionary<EquipAffixId, ReliquarySet>> GetEquipAffixIdToReliquarySetMapAsync(CancellationToken token = default)
{
return FromCacheAsDictionaryAsync<EquipAffixId, ReliquarySet>(FileNameReliquarySet, r => r.EquipAffixId, token);
}
public async ValueTask<Dictionary<ExtendedEquipAffixId, ReliquarySet>> GetExtendedEquipAffixIdToReliquarySetMapAsync(CancellationToken token = default)
{
string cacheKey = $"{nameof(MetadataService)}.Cache.{FileNameReliquarySet}.Map.{nameof(ExtendedEquipAffixId)}";
if (memoryCache.TryGetValue(cacheKey, out object? value))
{
ArgumentNullException.ThrowIfNull(value);
return (Dictionary<ExtendedEquipAffixId, ReliquarySet>)value;
}
List<ReliquarySet> list = await FromCacheOrFileAsync<List<ReliquarySet>>(FileNameReliquarySet, token).ConfigureAwait(false);
Dictionary<ExtendedEquipAffixId, ReliquarySet> dict = list
.SelectMany(set => set.EquipAffixIds.Select(id => (id, set)))
.ToDictionary(tuple => tuple.id, tuple => tuple.set);
return memoryCache.Set(cacheKey, dict);
}
public async ValueTask<Dictionary<TowerLevelGroupId, List<TowerLevel>>> GetGroupIdToTowerLevelGroupMapAsync(CancellationToken token = default)
{
string cacheKey = $"{nameof(MetadataService)}.Cache.{FileNameTowerLevel}.Map.Group.{nameof(TowerLevelGroupId)}";
if (memoryCache.TryGetValue(cacheKey, out object? value))
{
ArgumentNullException.ThrowIfNull(value);
return (Dictionary<TowerLevelGroupId, List<TowerLevel>>)value;
}
List<TowerLevel> list = await FromCacheOrFileAsync<List<TowerLevel>>(FileNameTowerLevel, token).ConfigureAwait(false);
Dictionary<TowerLevelGroupId, List<TowerLevel>> dict = list.GroupBy(l => l.GroupId).ToDictionary(g => g.Key, g => g.ToList());
return memoryCache.Set(cacheKey, dict);
}
/// <inheritdoc/>
public ValueTask<Dictionary<AchievementId, Model.Metadata.Achievement.Achievement>> GetIdToAchievementMapAsync(CancellationToken token = default)
{
return FromCacheAsDictionaryAsync<AchievementId, Model.Metadata.Achievement.Achievement>(FileNameAchievement, a => a.Id, token);
}
/// <inheritdoc/>
public ValueTask<Dictionary<AvatarId, Avatar>> GetIdToAvatarMapAsync(CancellationToken token = default)
{
return FromCacheAsDictionaryAsync<AvatarId, Avatar>(FileNameAvatar, a => a.Id, token);
}
/// <inheritdoc/>
public async ValueTask<Dictionary<MaterialId, DisplayItem>> GetIdToDisplayItemAndMaterialMapAsync(CancellationToken token = default)
{
string cacheKey = $"{nameof(MetadataService)}.Cache.{nameof(DisplayItem)}.Map.{typeof(MaterialId).Name}";
if (memoryCache.TryGetValue(cacheKey, out object? value))
{
ArgumentNullException.ThrowIfNull(value);
return (Dictionary<MaterialId, DisplayItem>)value;
}
Dictionary<MaterialId, DisplayItem> displays = await FromCacheAsDictionaryAsync<MaterialId, DisplayItem>(FileNameDisplayItem, a => a.Id, token).ConfigureAwait(false);
Dictionary<MaterialId, Material> materials = await GetIdToMaterialMapAsync(token).ConfigureAwait(false);
Dictionary<MaterialId, DisplayItem> results = new(displays);
foreach ((MaterialId id, DisplayItem material) in materials)
{
results[id] = material;
}
return memoryCache.Set(cacheKey, results);
}
/// <inheritdoc/>
public ValueTask<Dictionary<MaterialId, Material>> GetIdToMaterialMapAsync(CancellationToken token = default)
{
return FromCacheAsDictionaryAsync<MaterialId, Material>(FileNameMaterial, a => a.Id, token);
}
/// <inheritdoc/>
public ValueTask<Dictionary<AvatarId, ReliquaryAffixWeight>> GetIdToReliquaryAffixWeightMapAsync(CancellationToken token = default)
{
return FromCacheAsDictionaryAsync<AvatarId, ReliquaryAffixWeight>(FileNameReliquaryAffixWeight, r => r.AvatarId, token);
}
/// <inheritdoc/>
public ValueTask<Dictionary<ReliquarySubAffixId, ReliquarySubAffix>> GetIdToReliquarySubAffixMapAsync(CancellationToken token = default)
{
return FromCacheAsDictionaryAsync<ReliquarySubAffixId, ReliquarySubAffix>(FileNameReliquarySubAffix, a => a.Id, token);
}
/// <inheritdoc/>
public ValueTask<Dictionary<ReliquaryMainAffixId, FightProperty>> GetIdToReliquaryMainPropertyMapAsync(CancellationToken token = default)
{
return FromCacheAsDictionaryAsync<ReliquaryMainAffixId, FightProperty, ReliquaryMainAffix>(FileNameReliquaryMainAffix, r => r.Id, r => r.Type, token);
}
public ValueTask<Dictionary<TowerFloorId, TowerFloor>> GetIdToTowerFloorMapAsync(CancellationToken token = default)
{
return FromCacheAsDictionaryAsync<TowerFloorId, TowerFloor>(FileNameTowerFloor, t => t.Id, token);
}
public ValueTask<Dictionary<TowerScheduleId, TowerSchedule>> GetIdToTowerScheduleMapAsync(CancellationToken token = default)
{
return FromCacheAsDictionaryAsync<TowerScheduleId, TowerSchedule>(FileNameTowerSchedule, t => t.Id, token);
}
/// <inheritdoc/>
public ValueTask<Dictionary<WeaponId, Weapon>> GetIdToWeaponMapAsync(CancellationToken token = default)
{
return FromCacheAsDictionaryAsync<WeaponId, Weapon>(FileNameWeapon, w => w.Id, token);
}
/// <inheritdoc/>
public ValueTask<Dictionary<Level, Dictionary<GrowCurveType, float>>> GetLevelToAvatarCurveMapAsync(CancellationToken token = default)
{
return FromCacheAsDictionaryAsync<Level, Dictionary<GrowCurveType, float>, GrowCurve>(FileNameAvatarCurve, a => a.Level, a => a.Map, token);
}
/// <inheritdoc/>
public ValueTask<Dictionary<Level, Dictionary<GrowCurveType, float>>> GetLevelToMonsterCurveMapAsync(CancellationToken token = default)
{
return FromCacheAsDictionaryAsync<Level, Dictionary<GrowCurveType, float>, GrowCurve>(FileNameMonsterCurve, m => m.Level, m => m.Map, token);
}
/// <inheritdoc/>
public ValueTask<Dictionary<Level, Dictionary<GrowCurveType, float>>> GetLevelToWeaponCurveMapAsync(CancellationToken token = default)
{
return FromCacheAsDictionaryAsync<Level, Dictionary<GrowCurveType, float>, GrowCurve>(FileNameWeaponCurve, w => w.Level, w => w.Map, token);
}
public ValueTask<Dictionary<MonsterRelationshipId, Monster>> GetRelationshipIdToMonsterMapAsync(CancellationToken token = default)
{
return FromCacheAsDictionaryAsync<MonsterRelationshipId, Monster>(FileNameMonster, m => m.RelationshipId, token);
}
/// <inheritdoc/>
public ValueTask<Dictionary<string, Avatar>> GetNameToAvatarMapAsync(CancellationToken token = default)
{
return FromCacheAsDictionaryAsync<string, Avatar>(FileNameAvatar, a => a.Name, token);
}
/// <inheritdoc/>
public ValueTask<Dictionary<string, Weapon>> GetNameToWeaponMapAsync(CancellationToken token = default)
{
return FromCacheAsDictionaryAsync<string, Weapon>(FileNameWeapon, w => w.Name, token);
}
}

View File

@@ -13,6 +13,7 @@ using System.IO;
using System.Net;
using System.Net.Http;
using System.Net.Http.Json;
using Windows.ApplicationModel.Appointments;
namespace Snap.Hutao.Service.Metadata;
@@ -38,14 +39,14 @@ internal sealed partial class MetadataService : IMetadataService, IMetadataServi
private bool isInitialized;
/// <inheritdoc/>
public IMemoryCache MemoryCache { get => memoryCache; }
public async ValueTask<bool> InitializeAsync()
{
await initializeCompletionSource.Task.ConfigureAwait(false);
return isInitialized;
}
/// <inheritdoc/>
public async ValueTask InitializeInternalAsync(CancellationToken token = default)
{
if (isInitialized)
@@ -55,38 +56,87 @@ internal sealed partial class MetadataService : IMetadataService, IMetadataServi
using (ValueStopwatch.MeasureExecution(logger))
{
isInitialized = await TryUpdateMetadataAsync(token).ConfigureAwait(false);
isInitialized = await DownloadMetadataDescriptionFileAndCheckAsync(token).ConfigureAwait(false);
initializeCompletionSource.TrySetResult();
}
}
private async ValueTask<bool> TryUpdateMetadataAsync(CancellationToken token)
public async ValueTask<T> FromCacheOrFileAsync<T>(string fileName, CancellationToken token)
where T : class
{
Verify.Operation(isInitialized, SH.ServiceMetadataNotInitialized);
string cacheKey = $"{nameof(MetadataService)}.Cache.{fileName}";
if (memoryCache.TryGetValue(cacheKey, out object? value))
{
ArgumentNullException.ThrowIfNull(value);
return (T)value;
}
string path = metadataOptions.GetLocalizedLocalFile($"{fileName}.json");
if (File.Exists(path))
{
using (Stream fileStream = File.OpenRead(path))
{
T? result = await JsonSerializer.DeserializeAsync<T>(fileStream, options, token).ConfigureAwait(false);
ArgumentNullException.ThrowIfNull(result);
return memoryCache.Set(cacheKey, result);
}
}
else
{
FileNotFoundException exception = new(SH.ServiceMetadataFileNotFound, fileName);
throw ThrowHelper.UserdataCorrupted(SH.ServiceMetadataFileNotFound, exception);
}
}
private async ValueTask<bool> DownloadMetadataDescriptionFileAndCheckAsync(CancellationToken token)
{
if (LocalSetting.Get(SettingKeys.SuppressMetadataInitialization, false))
{
return true;
}
Dictionary<string, string>? metaXXH64Map;
if (await DownloadMetadataDescriptionFileAsync(token).ConfigureAwait(false) is not { } metadataFileHashs)
{
return false;
}
await CheckMetadataSourceFilesAsync(metadataFileHashs, token).ConfigureAwait(false);
// save metadataFile
using (FileStream metaFileStream = File.Create(metadataOptions.GetLocalizedLocalFile(MetaFileName)))
{
await JsonSerializer
.SerializeAsync(metaFileStream, metadataFileHashs, options, token)
.ConfigureAwait(false);
}
return true;
}
private async ValueTask<Dictionary<string, string>?> DownloadMetadataDescriptionFileAsync(CancellationToken token)
{
Dictionary<string, string>? metadataFileHashs;
try
{
string metadataFile = metadataOptions.GetLocalizedRemoteFile(MetaFileName);
// download meta check file
metaXXH64Map = await httpClient
.GetFromJsonAsync<Dictionary<string, string>>(metadataFile, options, token)
metadataFileHashs = await httpClient
.GetFromJsonAsync<Dictionary<string, string>>(metadataOptions.GetLocalizedRemoteFile(MetaFileName), options, token)
.ConfigureAwait(false);
if (metaXXH64Map is null)
if (metadataFileHashs is null)
{
infoBarService.Error(SH.ServiceMetadataParseFailed);
return false;
return default;
}
return metadataFileHashs;
}
catch (JsonException ex)
{
infoBarService.Error(ex, SH.ServiceMetadataRequestFailed);
return false;
return default;
}
catch (HttpRequestException ex)
{
@@ -99,20 +149,8 @@ internal sealed partial class MetadataService : IMetadataService, IMetadataServi
infoBarService.Error(SH.FormatServiceMetadataHttpRequestFailed(ex.StatusCode, ex.HttpRequestError));
}
return false;
return default;
}
await CheckMetadataSourceFilesAsync(metaXXH64Map, token).ConfigureAwait(false);
// save metadataFile
using (FileStream metaFileStream = File.Create(metadataOptions.GetLocalizedLocalFile(MetaFileName)))
{
await JsonSerializer
.SerializeAsync(metaFileStream, metaXXH64Map, options, token)
.ConfigureAwait(false);
}
return true;
}
private ValueTask CheckMetadataSourceFilesAsync(Dictionary<string, string> metaMd5Map, CancellationToken token)
@@ -121,9 +159,9 @@ internal sealed partial class MetadataService : IMetadataService, IMetadataServi
{
(string fileName, string md5) = pair;
string fileFullName = $"{fileName}.json";
string fileFullPath = metadataOptions.GetLocalizedLocalFile(fileFullName);
bool skip = false;
string fileFullPath = metadataOptions.GetLocalizedLocalFile(fileFullName);
if (File.Exists(fileFullPath))
{
skip = md5 == await XXH64.HashFileAsync(fileFullPath, token).ConfigureAwait(false);
@@ -132,7 +170,6 @@ internal sealed partial class MetadataService : IMetadataService, IMetadataServi
if (!skip)
{
logger.LogInformation("{Hash} of {File} not matched, begin downloading", nameof(XXH64), fileFullName);
await DownloadMetadataSourceFilesAsync(fileFullName, token).ConfigureAwait(false);
}
}).AsValueTask();
@@ -163,65 +200,4 @@ internal sealed partial class MetadataService : IMetadataService, IMetadataServi
logger.LogInformation("Download {file} completed", fileFullName);
}
private async ValueTask<T> FromCacheOrFileAsync<T>(string fileName, CancellationToken token)
where T : class
{
Verify.Operation(isInitialized, SH.ServiceMetadataNotInitialized);
string cacheKey = $"{nameof(MetadataService)}.Cache.{fileName}";
if (memoryCache.TryGetValue(cacheKey, out object? value))
{
ArgumentNullException.ThrowIfNull(value);
return (T)value;
}
string path = metadataOptions.GetLocalizedLocalFile($"{fileName}.json");
if (File.Exists(path))
{
using (Stream fileStream = File.OpenRead(path))
{
T? result = await JsonSerializer.DeserializeAsync<T>(fileStream, options, token).ConfigureAwait(false);
ArgumentNullException.ThrowIfNull(result);
return memoryCache.Set(cacheKey, result);
}
}
else
{
FileNotFoundException exception = new(SH.ServiceMetadataFileNotFound, fileName);
throw ThrowHelper.UserdataCorrupted(SH.ServiceMetadataFileNotFound, exception);
}
}
private async ValueTask<Dictionary<TKey, TValue>> FromCacheAsDictionaryAsync<TKey, TValue>(string fileName, Func<TValue, TKey> keySelector, CancellationToken token)
where TKey : notnull
{
string cacheKey = $"{nameof(MetadataService)}.Cache.{fileName}.Map.{typeof(TKey).Name}";
if (memoryCache.TryGetValue(cacheKey, out object? value))
{
ArgumentNullException.ThrowIfNull(value);
return (Dictionary<TKey, TValue>)value;
}
List<TValue> list = await FromCacheOrFileAsync<List<TValue>>(fileName, token).ConfigureAwait(false);
Dictionary<TKey, TValue> dict = list.ToDictionaryIgnoringDuplicateKeys(keySelector); // There are duplicate name items
return memoryCache.Set(cacheKey, dict);
}
private async ValueTask<Dictionary<TKey, TValue>> FromCacheAsDictionaryAsync<TKey, TValue, TData>(string fileName, Func<TData, TKey> keySelector, Func<TData, TValue> valueSelector, CancellationToken token)
where TKey : notnull
{
string cacheKey = $"{nameof(MetadataService)}.Cache.{fileName}.Map.{typeof(TKey).Name}.{typeof(TValue).Name}";
if (memoryCache.TryGetValue(cacheKey, out object? value))
{
ArgumentNullException.ThrowIfNull(value);
return (Dictionary<TKey, TValue>)value;
}
List<TData> list = await FromCacheOrFileAsync<List<TData>>(fileName, token).ConfigureAwait(false);
Dictionary<TKey, TValue> dict = list.ToDictionaryIgnoringDuplicateKeys(keySelector, valueSelector); // There are duplicate name items
return memoryCache.Set(cacheKey, dict);
}
}

View File

@@ -0,0 +1,198 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.Extensions.Caching.Memory;
using Snap.Hutao.Core;
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Metadata;
using Snap.Hutao.Model.Metadata.Avatar;
using Snap.Hutao.Model.Metadata.Item;
using Snap.Hutao.Model.Metadata.Monster;
using Snap.Hutao.Model.Metadata.Reliquary;
using Snap.Hutao.Model.Metadata.Tower;
using Snap.Hutao.Model.Metadata.Weapon;
using Snap.Hutao.Model.Primitive;
using static Snap.Hutao.Service.Metadata.MetadataFileNames;
namespace Snap.Hutao.Service.Metadata;
internal static class MetadataServiceDictionaryExtension
{
public static ValueTask<Dictionary<EquipAffixId, ReliquarySet>> GetEquipAffixIdToReliquarySetMapAsync(this IMetadataService metadataService, CancellationToken token = default)
{
return metadataService.FromCacheAsDictionaryAsync<EquipAffixId, ReliquarySet>(FileNameReliquarySet, r => r.EquipAffixId, token);
}
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);
}
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);
}
public static ValueTask<Dictionary<AchievementId, Model.Metadata.Achievement.Achievement>> GetIdToAchievementMapAsync(this IMetadataService metadataService, CancellationToken token = default)
{
return metadataService.FromCacheAsDictionaryAsync<AchievementId, Model.Metadata.Achievement.Achievement>(FileNameAchievement, a => a.Id, token);
}
public static ValueTask<Dictionary<AvatarId, Avatar>> GetIdToAvatarMapAsync(this IMetadataService metadataService, CancellationToken token = default)
{
return metadataService.FromCacheAsDictionaryAsync<AvatarId, Avatar>(FileNameAvatar, a => a.Id, token);
}
public static async ValueTask<Dictionary<MaterialId, DisplayItem>> GetIdToDisplayItemAndMaterialMapAsync(this IMetadataService metadataService, CancellationToken token = default)
{
string cacheKey = $"{nameof(MetadataService)}.Cache.{FileNameDisplayItem}+{FileNameMaterial}.Map.{typeof(MaterialId).Name}.{nameof(DisplayItem)}+{nameof(Material)}";
if (metadataService.MemoryCache.TryGetValue(cacheKey, out object? value))
{
ArgumentNullException.ThrowIfNull(value);
return (Dictionary<MaterialId, DisplayItem>)value;
}
Dictionary<MaterialId, DisplayItem> displays = await metadataService.FromCacheAsDictionaryAsync<MaterialId, DisplayItem>(FileNameDisplayItem, a => a.Id, token).ConfigureAwait(false);
Dictionary<MaterialId, Material> materials = await metadataService.GetIdToMaterialMapAsync(token).ConfigureAwait(false);
Dictionary<MaterialId, DisplayItem> results = new(displays);
foreach ((MaterialId id, DisplayItem material) in materials)
{
results[id] = material;
}
return metadataService.MemoryCache.Set(cacheKey, results);
}
public static ValueTask<Dictionary<MaterialId, Material>> GetIdToMaterialMapAsync(this IMetadataService metadataService, CancellationToken token = default)
{
return metadataService.FromCacheAsDictionaryAsync<MaterialId, Material>(FileNameMaterial, a => a.Id, token);
}
public static ValueTask<Dictionary<AvatarId, ReliquaryAffixWeight>> GetIdToReliquaryAffixWeightMapAsync(this IMetadataService metadataService, CancellationToken token = default)
{
return metadataService.FromCacheAsDictionaryAsync<AvatarId, ReliquaryAffixWeight>(FileNameReliquaryAffixWeight, r => r.AvatarId, token);
}
public static ValueTask<Dictionary<ReliquaryMainAffixId, FightProperty>> GetIdToReliquaryMainPropertyMapAsync(this IMetadataService metadataService, CancellationToken token = default)
{
return metadataService.FromCacheAsDictionaryAsync<ReliquaryMainAffix, ReliquaryMainAffixId, FightProperty>(FileNameReliquaryMainAffix, r => r.Id, r => r.Type, token);
}
public static ValueTask<Dictionary<ReliquarySubAffixId, ReliquarySubAffix>> GetIdToReliquarySubAffixMapAsync(this IMetadataService metadataService, CancellationToken token = default)
{
return metadataService.FromCacheAsDictionaryAsync<ReliquarySubAffixId, ReliquarySubAffix>(FileNameReliquarySubAffix, a => a.Id, token);
}
public static ValueTask<Dictionary<TowerFloorId, TowerFloor>> GetIdToTowerFloorMapAsync(this IMetadataService metadataService, CancellationToken token = default)
{
return metadataService.FromCacheAsDictionaryAsync<TowerFloorId, TowerFloor>(FileNameTowerFloor, t => t.Id, token);
}
public static ValueTask<Dictionary<TowerScheduleId, TowerSchedule>> GetIdToTowerScheduleMapAsync(this IMetadataService metadataService, CancellationToken token = default)
{
return metadataService.FromCacheAsDictionaryAsync<TowerScheduleId, TowerSchedule>(FileNameTowerSchedule, t => t.Id, token);
}
public static ValueTask<Dictionary<WeaponId, Weapon>> GetIdToWeaponMapAsync(this IMetadataService metadataService, CancellationToken token = default)
{
return metadataService.FromCacheAsDictionaryAsync<WeaponId, Weapon>(FileNameWeapon, w => w.Id, token);
}
public static ValueTask<Dictionary<Level, Dictionary<GrowCurveType, float>>> GetLevelToAvatarCurveMapAsync(this IMetadataService metadataService, CancellationToken token = default)
{
return metadataService.FromCacheAsDictionaryAsync<GrowCurve, Level, Dictionary<GrowCurveType, float>>(FileNameAvatarCurve, a => a.Level, a => a.Map, token);
}
public static ValueTask<Dictionary<Level, Dictionary<GrowCurveType, float>>> GetLevelToMonsterCurveMapAsync(this IMetadataService metadataService, CancellationToken token = default)
{
return metadataService.FromCacheAsDictionaryAsync<GrowCurve, Level, Dictionary<GrowCurveType, float>>(FileNameMonsterCurve, m => m.Level, m => m.Map, token);
}
public static ValueTask<Dictionary<Level, Dictionary<GrowCurveType, float>>> GetLevelToWeaponCurveMapAsync(this IMetadataService metadataService, CancellationToken token = default)
{
return metadataService.FromCacheAsDictionaryAsync<GrowCurve, Level, Dictionary<GrowCurveType, float>>(FileNameWeaponCurve, w => w.Level, w => w.Map, token);
}
public static ValueTask<Dictionary<string, Avatar>> GetNameToAvatarMapAsync(this IMetadataService metadataService, CancellationToken token = default)
{
return metadataService.FromCacheAsDictionaryAsync<string, Avatar>(FileNameAvatar, a => a.Name, token);
}
public static ValueTask<Dictionary<string, Weapon>> GetNameToWeaponMapAsync(this IMetadataService metadataService, CancellationToken token = default)
{
return metadataService.FromCacheAsDictionaryAsync<string, Weapon>(FileNameWeapon, w => w.Name, token);
}
public static ValueTask<Dictionary<MonsterRelationshipId, Monster>> GetRelationshipIdToMonsterMapAsync(this IMetadataService metadataService, CancellationToken token = default)
{
return metadataService.FromCacheAsDictionaryAsync<MonsterRelationshipId, Monster>(FileNameMonster, m => m.RelationshipId, token);
}
private static async ValueTask<Dictionary<TKey, TValue>> FromCacheAsDictionaryAsync<TKey, TValue>(this IMetadataService metadataService, string fileName, Func<TValue, TKey> keySelector, CancellationToken token)
where TKey : notnull
{
string keyName = TypeNameHelper.GetTypeDisplayName(typeof(TKey));
string valueName = TypeNameHelper.GetTypeDisplayName(typeof(TValue));
string cacheKey = $"{nameof(MetadataService)}.Cache.{fileName}.Map.{keyName}.{valueName}";
if (metadataService.MemoryCache.TryGetValue(cacheKey, out object? value))
{
ArgumentNullException.ThrowIfNull(value);
return (Dictionary<TKey, TValue>)value;
}
List<TValue> list = await metadataService.FromCacheOrFileAsync<List<TValue>>(fileName, token).ConfigureAwait(false);
Dictionary<TKey, TValue> dict = list.ToDictionaryIgnoringDuplicateKeys(keySelector); // There are duplicate name items
return metadataService.MemoryCache.Set(cacheKey, dict);
}
private static async ValueTask<Dictionary<TKey, TValue>> FromCacheAsDictionaryAsync<TData, TKey, TValue>(this IMetadataService metadataService, string fileName, Func<TData, TKey> keySelector, Func<TData, TValue> valueSelector, CancellationToken token)
where TKey : notnull
{
string keyName = TypeNameHelper.GetTypeDisplayName(typeof(TKey));
string valueName = TypeNameHelper.GetTypeDisplayName(typeof(TValue));
string cacheKey = $"{nameof(MetadataService)}.Cache.{fileName}.Map.{keyName}.{valueName}";
if (metadataService.MemoryCache.TryGetValue(cacheKey, out object? value))
{
ArgumentNullException.ThrowIfNull(value);
return (Dictionary<TKey, TValue>)value;
}
List<TData> list = await metadataService.FromCacheOrFileAsync<List<TData>>(fileName, token).ConfigureAwait(false);
Dictionary<TKey, TValue> dict = list.ToDictionaryIgnoringDuplicateKeys(keySelector, valueSelector); // There are duplicate name items
return metadataService.MemoryCache.Set(cacheKey, dict);
}
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
{
string keyName = TypeNameHelper.GetTypeDisplayName(typeof(TKey));
string middleName = TypeNameHelper.GetTypeDisplayName(typeof(TMiddle));
string valueName = TypeNameHelper.GetTypeDisplayName(typeof(TValue));
string cacheKey = $"{nameof(MetadataService)}.Cache.{fileName}.Map.{keyName}.{middleName}.{valueName}";
if (metadataService.MemoryCache.TryGetValue(cacheKey, out object? value))
{
ArgumentNullException.ThrowIfNull(value);
return (Dictionary<TKey, TValue>)value;
}
List<TData> list = await metadataService.FromCacheOrFileAsync<List<TData>>(fileName, token).ConfigureAwait(false);
Dictionary<TKey, TValue> dict = listSelector(list).ToDictionaryIgnoringDuplicateKeys(keySelector, valueSelector); // There are duplicate name items
return metadataService.MemoryCache.Set(cacheKey, dict);
}
}

View File

@@ -0,0 +1,127 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Metadata;
using Snap.Hutao.Model.Metadata.Achievement;
using Snap.Hutao.Model.Metadata.Avatar;
using Snap.Hutao.Model.Metadata.Item;
using Snap.Hutao.Model.Metadata.Monster;
using Snap.Hutao.Model.Metadata.Reliquary;
using Snap.Hutao.Model.Metadata.Tower;
using Snap.Hutao.Model.Metadata.Weapon;
using static Snap.Hutao.Service.Metadata.MetadataFileNames;
namespace Snap.Hutao.Service.Metadata;
internal static class MetadataServiceListExtension
{
public static ValueTask<List<Model.Metadata.Achievement.Achievement>> GetAchievementListAsync(this IMetadataService metadataService, CancellationToken token = default)
{
return metadataService.FromCacheOrFileAsync<List<Model.Metadata.Achievement.Achievement>>(FileNameAchievement, token);
}
public static ValueTask<List<AchievementGoal>> GetAchievementGoalListAsync(this IMetadataService metadataService, CancellationToken token = default)
{
return metadataService.FromCacheOrFileAsync<List<AchievementGoal>>(FileNameAchievementGoal, token);
}
public static ValueTask<List<Avatar>> GetAvatarListAsync(this IMetadataService metadataService, CancellationToken token = default)
{
return metadataService.FromCacheOrFileAsync<List<Avatar>>(FileNameAvatar, token);
}
public static ValueTask<List<GrowCurve>> GetAvatarCurveListAsync(this IMetadataService metadataService, CancellationToken token = default)
{
return metadataService.FromCacheOrFileAsync<List<GrowCurve>>(FileNameAvatarCurve, token);
}
public static ValueTask<List<Promote>> GetAvatarPromoteListAsync(this IMetadataService metadataService, CancellationToken token = default)
{
return metadataService.FromCacheOrFileAsync<List<Promote>>(FileNameAvatarPromote, token);
}
public static ValueTask<List<DisplayItem>> GetDisplayItemListAsync(this IMetadataService metadataService, CancellationToken token = default)
{
return metadataService.FromCacheOrFileAsync<List<DisplayItem>>(FileNameDisplayItem, token);
}
public static ValueTask<List<GachaEvent>> GetGachaEventListAsync(this IMetadataService metadataService, CancellationToken token = default)
{
return metadataService.FromCacheOrFileAsync<List<GachaEvent>>(FileNameGachaEvent, token);
}
public static ValueTask<List<Material>> GetMaterialListAsync(this IMetadataService metadataService, CancellationToken token = default)
{
return metadataService.FromCacheOrFileAsync<List<Material>>(FileNameMaterial, token);
}
public static ValueTask<List<Monster>> GetMonsterListAsync(this IMetadataService metadataService, CancellationToken token = default)
{
return metadataService.FromCacheOrFileAsync<List<Monster>>(FileNameMonster, token);
}
public static ValueTask<List<GrowCurve>> GetMonsterCurveListAsync(this IMetadataService metadataService, CancellationToken token = default)
{
return metadataService.FromCacheOrFileAsync<List<GrowCurve>>(FileNameMonsterCurve, token);
}
public static ValueTask<List<Reliquary>> GetReliquaryListAsync(this IMetadataService metadataService, CancellationToken token = default)
{
return metadataService.FromCacheOrFileAsync<List<Reliquary>>(FileNameReliquary, token);
}
public static ValueTask<List<ReliquaryAffixWeight>> GetReliquaryAffixWeightListAsync(this IMetadataService metadataService, CancellationToken token = default)
{
return metadataService.FromCacheOrFileAsync<List<ReliquaryAffixWeight>>(FileNameReliquaryAffixWeight, token);
}
public static ValueTask<List<ReliquaryMainAffix>> GetReliquaryMainAffixListAsync(this IMetadataService metadataService, CancellationToken token = default)
{
return metadataService.FromCacheOrFileAsync<List<ReliquaryMainAffix>>(FileNameReliquaryMainAffix, token);
}
public static ValueTask<List<ReliquaryMainAffixLevel>> GetReliquaryLevelListAsync(this IMetadataService metadataService, CancellationToken token = default)
{
return metadataService.FromCacheOrFileAsync<List<ReliquaryMainAffixLevel>>(FileNameReliquaryMainAffixLevel, token);
}
public static ValueTask<List<ReliquarySet>> GetReliquarySetListAsync(this IMetadataService metadataService, CancellationToken token = default)
{
return metadataService.FromCacheOrFileAsync<List<ReliquarySet>>(FileNameReliquarySet, token);
}
public static ValueTask<List<ReliquarySubAffix>> GetReliquarySubAffixListAsync(this IMetadataService metadataService, CancellationToken token = default)
{
return metadataService.FromCacheOrFileAsync<List<ReliquarySubAffix>>(FileNameReliquarySubAffix, token);
}
public static ValueTask<List<TowerFloor>> GetTowerFloorListAsync(this IMetadataService metadataService, CancellationToken token = default)
{
return metadataService.FromCacheOrFileAsync<List<TowerFloor>>(FileNameTowerFloor, token);
}
public static ValueTask<List<TowerLevel>> GetTowerLevelListAsync(this IMetadataService metadataService, CancellationToken token = default)
{
return metadataService.FromCacheOrFileAsync<List<TowerLevel>>(FileNameTowerLevel, token);
}
public static ValueTask<List<TowerSchedule>> GetTowerScheduleListAsync(this IMetadataService metadataService, CancellationToken token = default)
{
return metadataService.FromCacheOrFileAsync<List<TowerSchedule>>(FileNameTowerSchedule, token);
}
public static ValueTask<List<Weapon>> GetWeaponListAsync(this IMetadataService metadataService, CancellationToken token = default)
{
return metadataService.FromCacheOrFileAsync<List<Weapon>>(FileNameWeapon, token);
}
public static ValueTask<List<GrowCurve>> GetWeaponCurveListAsync(this IMetadataService metadataService, CancellationToken token = default)
{
return metadataService.FromCacheOrFileAsync<List<GrowCurve>>(FileNameWeaponCurve, token);
}
public static ValueTask<List<Promote>> GetWeaponPromoteListAsync(this IMetadataService metadataService, CancellationToken token = default)
{
return metadataService.FromCacheOrFileAsync<List<Promote>>(FileNameWeaponPromote, token);
}
}

View File

@@ -321,7 +321,7 @@
<cwcont:SwitchPresenter
Grid.Row="2"
Margin="12,0"
ContentTransitions="{StaticResource ContentThemeTransitions}"
ContentTransitions="{StaticResource EntranceThemeTransitions}"
Value="{Binding ElementName=ItemsPanelSelector, Path=Current}">
<cwcont:Case Value="List">
<ListView

View File

@@ -228,6 +228,11 @@
Grid.Column="1"
Margin="16,0,0,0"
DefaultLabelPosition="Right">
<CommandBar.Resources>
<StaticResource x:Key="CommandBarBackgroundOpen" ResourceKey="ControlFillColorTransparentBrush"/>
<Thickness x:Key="CommandBarBorderThicknessOpen">0</Thickness>
</CommandBar.Resources>
<CommandBar.Content>
<AutoSuggestBox
Width="300"
@@ -294,11 +299,10 @@
</Grid>
<cwc:SwitchPresenter
Grid.Row="1"
ContentTransitions="{StaticResource ContentThemeTransitions}"
ContentTransitions="{StaticResource EntranceThemeTransitions}"
Value="{Binding ElementName=ItemsPanelSelector, Path=Current}">
<cwc:Case Value="List">
<SplitView
Grid.Row="1"
DisplayMode="Inline"
IsPaneOpen="True"
OpenPaneLength="{StaticResource CompatSplitViewOpenPaneLength2}"

View File

@@ -552,7 +552,7 @@
<cwcont:SwitchPresenter
Grid.Row="1"
ContentTransitions="{ThemeResource ContentThemeTransitions}"
ContentTransitions="{ThemeResource EntranceThemeTransitions}"
Value="{Binding ElementName=ItemsPanelSelector, Path=Current, Mode=OneWay}">
<cwcont:Case Value="Grid">
<ScrollViewer>

View File

@@ -280,14 +280,12 @@
Value="{Binding ElementName=ItemsPanelSelector, Path=Current}">
<cwc:Case Value="List">
<SplitView
Grid.Row="1"
DisplayMode="Inline"
IsPaneOpen="True"
OpenPaneLength="{StaticResource CompatSplitViewOpenPaneLength}"
PaneBackground="{ThemeResource CardBackgroundFillColorSecondaryBrush}">
<SplitView.Pane>
<ListView
Grid.Row="1"
ItemTemplate="{StaticResource AvatarListTemplate}"
ItemsSource="{Binding Avatars}"
SelectedItem="{Binding Selected, Mode=TwoWay}"

View File

@@ -127,7 +127,7 @@
</CommandBar>
<cwc:SwitchPresenter
Grid.Row="1"
ContentTransitions="{ThemeResource ContentThemeTransitions}"
ContentTransitions="{ThemeResource EntranceThemeTransitions}"
Value="{Binding ElementName=ItemsPanelSelector, Path=Current}">
<cwc:Case Value="List">
<SplitView

View File

@@ -168,7 +168,7 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
using (await EnterCriticalExecutionAsync().ConfigureAwait(false))
{
List<MetadataAchievementGoal> goals = await metadataService
.GetAchievementGoalsAsync(CancellationToken)
.GetAchievementGoalListAsync(CancellationToken)
.ConfigureAwait(false);
sortedGoals = goals.SortBy(goal => goal.Order).SelectList(AchievementGoalView.From);
@@ -309,7 +309,7 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
return;
}
List<MetadataAchievement> achievements = await metadataService.GetAchievementsAsync(CancellationToken).ConfigureAwait(false);
List<MetadataAchievement> achievements = await metadataService.GetAchievementListAsync(CancellationToken).ConfigureAwait(false);
if (TryGetAchievements(archive, achievements, out List<AchievementView>? combined))
{

View File

@@ -15,7 +15,7 @@ namespace Snap.Hutao.ViewModel.Complex;
[Injection(InjectAs.Scoped)]
internal sealed partial class HutaoDatabaseViewModel : Abstraction.ViewModel
{
private readonly IHutaoCache hutaoCache;
private readonly IHutaoSpiralAbyssStatisticsCache hutaoCache;
private readonly ITaskContext taskContext;
private List<AvatarRankView>? avatarUsageRanks;
@@ -61,7 +61,7 @@ internal sealed partial class HutaoDatabaseViewModel : Abstraction.ViewModel
/// <inheritdoc/>
protected override async Task OpenUIAsync()
{
if (await hutaoCache.InitializeForDatabaseViewModelAsync().ConfigureAwait(false))
if (await hutaoCache.InitializeForSpiralAbyssViewAsync().ConfigureAwait(false))
{
await taskContext.SwitchToMainThreadAsync();
AvatarAppearanceRanks = hutaoCache.AvatarAppearanceRanks;

View File

@@ -135,7 +135,7 @@ internal sealed partial class CultivationViewModel : Abstraction.ViewModel
{
if (project is not null)
{
List<Material> materials = await metadataService.GetMaterialsAsync().ConfigureAwait(false);
List<Material> materials = await metadataService.GetMaterialListAsync().ConfigureAwait(false);
Dictionary<AvatarId, Model.Metadata.Avatar.Avatar> idAvatarMap = await metadataService.GetIdToAvatarMapAsync().ConfigureAwait(false);
Dictionary<WeaponId, Model.Metadata.Weapon.Weapon> idWeaponMap = await metadataService.GetIdToWeaponMapAsync().ConfigureAwait(false);
@@ -194,7 +194,7 @@ internal sealed partial class CultivationViewModel : Abstraction.ViewModel
ObservableCollection<StatisticsCultivateItem> statistics;
try
{
List<Material> materials = await metadataService.GetMaterialsAsync(token).ConfigureAwait(false);
List<Material> materials = await metadataService.GetMaterialListAsync(token).ConfigureAwait(false);
statistics = await cultivationService.GetStatisticsCultivateItemCollectionAsync(SelectedProject, materials, token).ConfigureAwait(false);
}
catch (OperationCanceledException)

View File

@@ -38,7 +38,7 @@ internal sealed partial class WikiAvatarViewModel : Abstraction.ViewModel
private readonly ICultivationService cultivationService;
private readonly IMetadataService metadataService;
private readonly ITaskContext taskContext;
private readonly IHutaoCache hutaoCache;
private readonly IHutaoSpiralAbyssStatisticsCache hutaoCache;
private readonly IInfoBarService infoBarService;
private readonly CalculateClient calculateClient;
private readonly IUserService userService;
@@ -87,10 +87,10 @@ internal sealed partial class WikiAvatarViewModel : Abstraction.ViewModel
}
levelAvatarCurveMap = await metadataService.GetLevelToAvatarCurveMapAsync().ConfigureAwait(false);
promotes = await metadataService.GetAvatarPromotesAsync().ConfigureAwait(false);
promotes = await metadataService.GetAvatarPromoteListAsync().ConfigureAwait(false);
Dictionary<MaterialId, Material> idMaterialMap = await metadataService.GetIdToMaterialMapAsync().ConfigureAwait(false);
List<Avatar> avatars = await metadataService.GetAvatarsAsync().ConfigureAwait(false);
List<Avatar> avatars = await metadataService.GetAvatarListAsync().ConfigureAwait(false);
IOrderedEnumerable<Avatar> sorted = avatars
.OrderByDescending(avatar => avatar.BeginTime)
.ThenByDescending(avatar => avatar.Sort);
@@ -106,7 +106,7 @@ internal sealed partial class WikiAvatarViewModel : Abstraction.ViewModel
private async ValueTask CombineComplexDataAsync(List<Avatar> avatars, Dictionary<MaterialId, Material> idMaterialMap)
{
if (!await hutaoCache.InitializeForWikiAvatarViewModelAsync().ConfigureAwait(false))
if (!await hutaoCache.InitializeForWikiAvatarViewAsync().ConfigureAwait(false))
{
return;
}

View File

@@ -55,7 +55,7 @@ internal sealed partial class WikiMonsterViewModel : Abstraction.ViewModel
{
levelMonsterCurveMap = await metadataService.GetLevelToMonsterCurveMapAsync().ConfigureAwait(false);
List<Monster> monsters = await metadataService.GetMonstersAsync().ConfigureAwait(false);
List<Monster> monsters = await metadataService.GetMonsterListAsync().ConfigureAwait(false);
Dictionary<MaterialId, DisplayItem> idDisplayMap = await metadataService.GetIdToDisplayItemAndMaterialMapAsync().ConfigureAwait(false);
foreach (Monster monster in monsters)
{

View File

@@ -36,7 +36,7 @@ internal sealed partial class WikiWeaponViewModel : Abstraction.ViewModel
private readonly ICultivationService cultivationService;
private readonly ITaskContext taskContext;
private readonly IMetadataService metadataService;
private readonly IHutaoCache hutaoCache;
private readonly IHutaoSpiralAbyssStatisticsCache hutaoCache;
private readonly IInfoBarService infoBarService;
private readonly IUserService userService;
@@ -82,10 +82,10 @@ internal sealed partial class WikiWeaponViewModel : Abstraction.ViewModel
if (await metadataService.InitializeAsync().ConfigureAwait(false))
{
levelWeaponCurveMap = await metadataService.GetLevelToWeaponCurveMapAsync().ConfigureAwait(false);
promotes = await metadataService.GetWeaponPromotesAsync().ConfigureAwait(false);
promotes = await metadataService.GetWeaponPromoteListAsync().ConfigureAwait(false);
Dictionary<MaterialId, Material> idMaterialMap = await metadataService.GetIdToMaterialMapAsync().ConfigureAwait(false);
List<Weapon> weapons = await metadataService.GetWeaponsAsync().ConfigureAwait(false);
List<Weapon> weapons = await metadataService.GetWeaponListAsync().ConfigureAwait(false);
IEnumerable<Weapon> sorted = weapons
.OrderByDescending(weapon => weapon.RankLevel)
.ThenBy(weapon => weapon.WeaponType)
@@ -103,7 +103,7 @@ internal sealed partial class WikiWeaponViewModel : Abstraction.ViewModel
private async ValueTask CombineComplexDataAsync(List<Weapon> weapons, Dictionary<MaterialId, Material> idMaterialMap)
{
if (await hutaoCache.InitializeForWikiWeaponViewModelAsync().ConfigureAwait(false))
if (await hutaoCache.InitializeForWikiWeaponViewAsync().ConfigureAwait(false))
{
ArgumentNullException.ThrowIfNull(hutaoCache.WeaponCollocations);