fix startup crash

This commit is contained in:
Lightczx
2023-12-29 10:05:03 +08:00
parent 64998453a1
commit 5d05c31af5
7 changed files with 134 additions and 69 deletions

View File

@@ -0,0 +1,23 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Core.Diagnostics.CodeAnalysis;
/// <summary>
/// 指示此特性附加的属性会在属性改变后会异步地设置的其他属性
/// </summary>
[AttributeUsage(AttributeTargets.Property, Inherited = false)]
internal sealed class AlsoAsyncSetsAttribute : Attribute
{
public AlsoAsyncSetsAttribute(string propertyName)
{
}
public AlsoAsyncSetsAttribute(string propertyName1, string propertyName2)
{
}
public AlsoAsyncSetsAttribute(params string[] propertyNames)
{
}
}

View File

@@ -0,0 +1,23 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Core.Diagnostics.CodeAnalysis;
/// <summary>
/// 指示此特性附加的属性会在属性改变后会设置的其他属性
/// </summary>
[AttributeUsage(AttributeTargets.Property, Inherited = false)]
internal sealed class AlsoSetsAttribute : Attribute
{
public AlsoSetsAttribute(string propertyName)
{
}
public AlsoSetsAttribute(string propertyName1, string propertyName2)
{
}
public AlsoSetsAttribute(params string[] propertyNames)
{
}
}

View File

@@ -0,0 +1,26 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Core.ExceptionService;
internal sealed class HutaoException : Exception
{
public HutaoException(HutaoExceptionKind kind, string message, Exception? innerException)
: this(message, innerException)
{
Kind = kind;
}
public HutaoException(string message, Exception? innerException)
: base($"{message}\n{innerException?.Message}", innerException)
{
}
public HutaoExceptionKind Kind { get; private set; }
}
internal enum HutaoExceptionKind
{
None,
}

View File

@@ -198,7 +198,7 @@
<Border Style="{StaticResource BorderCardStyle}">
<ListView
ItemTemplate="{StaticResource GameAccountListTemplate}"
ItemsSource="{Binding GameAccounts}"
ItemsSource="{Binding GameAccountsView}"
SelectedItem="{Binding SelectedGameAccount, Mode=TwoWay}"/>
</Border>

View File

@@ -2,10 +2,10 @@
// Licensed under the MIT license.
using CommunityToolkit.WinUI.Collections;
using Microsoft.Data.Sqlite;
using Microsoft.Extensions.Caching.Memory;
using Snap.Hutao.Control.Extension;
using Snap.Hutao.Core;
using Snap.Hutao.Core.Diagnostics.CodeAnalysis;
using Snap.Hutao.Core.ExceptionService;
using Snap.Hutao.Factory.ContentDialog;
using Snap.Hutao.Factory.Progress;
@@ -64,8 +64,17 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel
private GamePathEntry? selectedGamePathEntry;
private GameAccountFilter? gameAccountFilter;
public LaunchOptions LaunchOptions { get => launchOptions; }
public LaunchStatusOptions LaunchStatusOptions { get => launchStatusOptions; }
public RuntimeOptions RuntimeOptions { get => runtimeOptions; }
public AppOptions AppOptions { get => appOptions; }
public List<LaunchScheme> KnownSchemes { get; } = KnownLaunchSchemes.Get();
[AlsoAsyncSets(nameof(GameResource), nameof(GameAccountsView))]
public LaunchScheme? SelectedScheme
{
get => selectedScheme;
@@ -76,16 +85,9 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel
public GameAccount? SelectedGameAccount { get => selectedGameAccount; set => SetProperty(ref selectedGameAccount, value); }
public LaunchOptions LaunchOptions { get => launchOptions; }
public LaunchStatusOptions LaunchStatusOptions { get => launchStatusOptions; }
public RuntimeOptions RuntimeOptions { get => runtimeOptions; }
public AppOptions AppOptions { get => appOptions; }
public GameResource? GameResource { get => gameResource; set => SetProperty(ref gameResource, value); }
[AlsoAsyncSets(nameof(SelectedScheme), nameof(GameAccountsView))]
public bool GamePathSelectedAndValid
{
get => gamePathSelectedAndValid;
@@ -106,24 +108,7 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel
await taskContext.SwitchToMainThreadAsync();
await SetSelectedSchemeAsync(scheme).ConfigureAwait(true);
// Sync uid, almost never hit, so we are not so care about performance
if (memoryCache.TryRemove(DesiredUid, out object? value) && value is string uid)
{
ArgumentNullException.ThrowIfNull(GameAccountsView);
// Exists in the source collection
if (GameAccountsView.SourceCollection.Cast<GameAccount>().FirstOrDefault(g => g.AttachUid == uid) is { } sourceAccount)
{
SelectedGameAccount = GameAccountsView.Cast<GameAccount>().FirstOrDefault(g => g.AttachUid == uid);
// But not exists in the view for current scheme
if (SelectedGameAccount is null)
{
infoBarService.Warning(SH.FormatViewModelLaunchGameUnableToSwitchUidAttachedGameAccount(uid, sourceAccount.Name));
}
}
}
TrySetGameAccountByDesiredUid();
// Try set to the current account.
if (SelectedScheme is not null)
@@ -142,59 +127,68 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel
infoBarService.Error(ex);
}
}
void TrySetGameAccountByDesiredUid()
{
// Sync uid, almost never hit, so we are not so care about performance
if (memoryCache.TryRemove(DesiredUid, out object? value) && value is string uid)
{
ArgumentNullException.ThrowIfNull(GameAccountsView);
// Exists in the source collection
if (GameAccountsView.SourceCollection.Cast<GameAccount>().FirstOrDefault(g => g.AttachUid == uid) is { } sourceAccount)
{
SelectedGameAccount = GameAccountsView.Cast<GameAccount>().FirstOrDefault(g => g.AttachUid == uid);
// But not exists in the view for current scheme
if (SelectedGameAccount is null)
{
infoBarService.Warning(SH.FormatViewModelLaunchGameUnableToSwitchUidAttachedGameAccount(uid, sourceAccount.Name));
}
}
}
}
}
}
public ImmutableList<GamePathEntry> GamePathEntries { get => gamePathEntries; set => SetProperty(ref gamePathEntries, value); }
[AlsoSets(nameof(GamePathSelectedAndValid))]
public GamePathEntry? SelectedGamePathEntry
{
get => selectedGamePathEntry;
set => UpdateSelectedGamePathEntry(value, true);
set
{
if (SetProperty(ref selectedGamePathEntry, value, nameof(SelectedGamePathEntry)))
{
if (IsViewDisposed)
{
return;
}
launchOptions.GamePath = value?.Path ?? string.Empty;
GamePathSelectedAndValid = File.Exists(launchOptions.GamePath);
}
}
}
protected override ValueTask<bool> InitializeUIAsync()
{
GamePathEntries = launchOptions.GetGamePathEntries(out GamePathEntry? entry);
SelectedGamePathEntry = entry;
SyncGamePathEntriesAndSelectedGamePathEntryFromLaunchOptions();
return ValueTask.FromResult(true);
}
private void UpdateSelectedGamePathEntry(GamePathEntry? value, bool setBack)
{
if (SetProperty(ref selectedGamePathEntry, value, nameof(SelectedGamePathEntry)) && setBack)
{
if (IsViewDisposed)
{
return;
}
launchOptions.GamePath = value?.Path ?? string.Empty;
GamePathSelectedAndValid = File.Exists(launchOptions.GamePath);
}
}
[Command("SetGamePathCommand")]
private async Task SetGamePathAsync()
{
IGameLocator locator = gameLocatorFactory.Create(GameLocationSource.Manual);
(bool isOk, string path) = await locator.LocateGamePathAsync().ConfigureAwait(false);
(bool isOk, string path) = await gameLocatorFactory.LocateAsync(GameLocationSource.Manual).ConfigureAwait(false);
if (!isOk)
{
return;
}
await taskContext.SwitchToMainThreadAsync();
try
{
GamePathEntries = launchOptions.UpdateGamePathAndRefreshEntries(path);
}
catch (SqliteException ex)
{
// 文件夹权限不足,无法写入数据库
infoBarService.Error(ex, SH.ViewModelSettingSetGamePathDatabaseFailedTitle);
}
GamePathEntries = launchOptions.UpdateGamePathAndRefreshEntries(path);
}
[Command("ResetGamePathCommand")]
@@ -237,18 +231,14 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel
else
{
await taskContext.SwitchToMainThreadAsync();
GamePathEntries = launchOptions.GetGamePathEntries(out GamePathEntry? entry);
UpdateSelectedGamePathEntry(entry, true);
SyncGamePathEntriesAndSelectedGamePathEntryFromLaunchOptions();
}
}
if (SelectedGameAccount is not null)
if (SelectedGameAccount is not null && !gameService.SetGameAccount(SelectedGameAccount))
{
if (!gameService.SetGameAccount(SelectedGameAccount))
{
infoBarService.Warning(SH.ViewModelLaunchGameSwitchGameAccountFail);
return;
}
infoBarService.Warning(SH.ViewModelLaunchGameSwitchGameAccountFail);
return;
}
IProgress<LaunchStatus> launchProgress = progressFactory.CreateForMainThread<LaunchStatus>(status => launchStatusOptions.LaunchStatus = status);
@@ -377,4 +367,10 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel
};
}
}
private void SyncGamePathEntriesAndSelectedGamePathEntryFromLaunchOptions()
{
GamePathEntries = launchOptions.GetGamePathEntries(out GamePathEntry? entry);
SelectedGamePathEntry = entry;
}
}

View File

@@ -5,9 +5,7 @@ using CommunityToolkit.WinUI.Collections;
using Snap.Hutao.Core.ExceptionService;
using Snap.Hutao.Factory.Progress;
using Snap.Hutao.Model.Entity;
using Snap.Hutao.Model.Entity.Primitive;
using Snap.Hutao.Service.Game;
using Snap.Hutao.Service.Game.Configuration;
using Snap.Hutao.Service.Game.Scheme;
using Snap.Hutao.Service.Notification;
using System.Collections.ObjectModel;
@@ -43,6 +41,7 @@ internal sealed partial class LaunchGameViewModelSlim : Abstraction.ViewModelSli
protected override async Task OpenUIAsync()
{
LaunchScheme? scheme = LaunchGameShared.GetCurrentLaunchSchemeFromConfigFile(gameService, infoBarService);
ObservableCollection<GameAccount> accounts = gameService.GameAccountCollection;
try
{
@@ -58,7 +57,6 @@ internal sealed partial class LaunchGameViewModelSlim : Abstraction.ViewModelSli
}
gameAccountFilter = new(scheme?.GetSchemeType());
ObservableCollection<GameAccount> accounts = gameService.GameAccountCollection;
await taskContext.SwitchToMainThreadAsync();
GameAccountsView = new(accounts, true)

View File

@@ -15,7 +15,6 @@ using Snap.Hutao.Model;
using Snap.Hutao.Service;
using Snap.Hutao.Service.GachaLog.QueryProvider;
using Snap.Hutao.Service.Game;
using Snap.Hutao.Service.Game.Locator;
using Snap.Hutao.Service.Hutao;
using Snap.Hutao.Service.Navigation;
using Snap.Hutao.Service.Notification;