mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
refactor launch game view model
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
|
||||
namespace Snap.Hutao.Test.PlatformExtensions;
|
||||
@@ -11,6 +12,7 @@ public sealed class DependencyInjectionTest
|
||||
.AddSingleton<IService, ServiceB>()
|
||||
.AddScoped<IScopedService, ServiceA>()
|
||||
.AddTransient(typeof(IGenericService<>), typeof(GenericService<>))
|
||||
.AddLogging(builder => builder.AddConsole())
|
||||
.BuildServiceProvider();
|
||||
|
||||
[TestMethod]
|
||||
@@ -41,6 +43,13 @@ public sealed class DependencyInjectionTest
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void LoggerWithInterfaceTypeCanBeResolved()
|
||||
{
|
||||
Assert.IsNotNull(services.GetService<ILogger<IScopedService>>());
|
||||
Assert.IsNotNull(services.GetRequiredService<ILoggerFactory>().CreateLogger(nameof(IScopedService)));
|
||||
}
|
||||
|
||||
private interface IService
|
||||
{
|
||||
Guid Id { get; }
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="3.2.2" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="3.2.2" />
|
||||
|
||||
@@ -38,7 +38,7 @@ internal sealed partial class ScopedDbCurrent<TEntity, TMessage>
|
||||
return;
|
||||
}
|
||||
|
||||
if (serviceProvider.IsDisposedSlow())
|
||||
if (serviceProvider.IsDisposed())
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -96,7 +96,7 @@ internal sealed partial class ScopedDbCurrent<TEntityOnly, TEntity, TMessage>
|
||||
return;
|
||||
}
|
||||
|
||||
if (serviceProvider.IsDisposedSlow())
|
||||
if (serviceProvider.IsDisposed())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -18,13 +18,22 @@ internal static class ServiceProviderExtension
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsDisposedSlow(this IServiceProvider? serviceProvider)
|
||||
public static bool IsDisposed(this IServiceProvider? serviceProvider)
|
||||
{
|
||||
if (serviceProvider is null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (serviceProvider is ServiceProvider serviceProviderImpl)
|
||||
{
|
||||
return GetPrivateDisposed(serviceProviderImpl);
|
||||
}
|
||||
|
||||
return serviceProvider.GetType().GetField("_disposed")?.GetValue(serviceProvider) is true;
|
||||
}
|
||||
|
||||
// private bool _disposed;
|
||||
[UnsafeAccessor(UnsafeAccessorKind.Field, Name = "_disposed")]
|
||||
private static extern ref bool GetPrivateDisposed(ServiceProvider serviceProvider);
|
||||
}
|
||||
@@ -17,4 +17,5 @@ internal enum HutaoExceptionKind
|
||||
// Service
|
||||
GachaStatisticsInvalidItemId,
|
||||
GameFpsUnlockingFailed,
|
||||
GameConfigInvalidChannelOptions,
|
||||
}
|
||||
@@ -27,4 +27,27 @@ internal sealed partial class IdentifyMonitorWindow : Window
|
||||
}
|
||||
|
||||
public string Monitor { get; private set; }
|
||||
|
||||
public static async ValueTask IdentifyAllMonitorsAsync(int secondsDelay)
|
||||
{
|
||||
List<IdentifyMonitorWindow> windows = [];
|
||||
|
||||
IReadOnlyList<DisplayArea> displayAreas = DisplayArea.FindAll();
|
||||
for (int i = 0; i < displayAreas.Count; i++)
|
||||
{
|
||||
windows.Add(new IdentifyMonitorWindow(displayAreas[i], i + 1));
|
||||
}
|
||||
|
||||
foreach (IdentifyMonitorWindow window in windows)
|
||||
{
|
||||
window.Activate();
|
||||
}
|
||||
|
||||
await Delay.FromSeconds(secondsDelay).ConfigureAwait(true);
|
||||
|
||||
foreach (IdentifyMonitorWindow window in windows)
|
||||
{
|
||||
window.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,4 +36,6 @@ internal sealed class GameFileSystem
|
||||
public string GameConfigFilePath { get => gameConfigFilePath ??= Path.Combine(GameDirectory, GameConstants.ConfigFileName); }
|
||||
|
||||
public string PCGameSDKFilePath { get => pcGameSDKFilePath ??= Path.Combine(GameDirectory, GameConstants.PCGameSDKFilePath); }
|
||||
|
||||
public string ScreenShotDirectory { get => Path.Combine(GameDirectory, "ScreenShot"); }
|
||||
}
|
||||
@@ -1,12 +1,21 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Model.Entity;
|
||||
using Snap.Hutao.Service.Game.PathAbstraction;
|
||||
using Snap.Hutao.Service.Notification;
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace Snap.Hutao.ViewModel.Game;
|
||||
|
||||
internal interface IViewModelSupportLaunchExecution
|
||||
{
|
||||
void SetGamePathEntriesAndSelectedGamePathEntry(ImmutableList<GamePathEntry> gamePathEntries, GamePathEntry? selectedEntry);
|
||||
LaunchGameShared Shared { get; }
|
||||
|
||||
GameAccount? SelectedGameAccount { get; }
|
||||
|
||||
void SetGamePathEntriesAndSelectedGamePathEntry(ImmutableList<GamePathEntry> gamePathEntries, GamePathEntry? selectedEntry)
|
||||
{
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Service.Game.Launching;
|
||||
using Snap.Hutao.Service.Game.Scheme;
|
||||
using Snap.Hutao.Service.Notification;
|
||||
|
||||
namespace Snap.Hutao.ViewModel.Game;
|
||||
|
||||
internal static class LaunchGameLaunchExecution
|
||||
{
|
||||
public static async ValueTask LaunchExecutionAsync(this IViewModelSupportLaunchExecution launchExecution)
|
||||
{
|
||||
IServiceProvider root = Ioc.Default;
|
||||
IInfoBarService infoBarService = root.GetRequiredService<IInfoBarService>();
|
||||
ILogger<IViewModelSupportLaunchExecution> logger = root.GetRequiredService<ILogger<IViewModelSupportLaunchExecution>>();
|
||||
|
||||
LaunchScheme? scheme = launchExecution.Shared.GetCurrentLaunchSchemeFromConfigFile();
|
||||
try
|
||||
{
|
||||
// Root service provider is required.
|
||||
LaunchExecutionContext context = new(root, launchExecution, scheme, launchExecution.SelectedGameAccount);
|
||||
LaunchExecutionResult result = await new LaunchExecutionInvoker().InvokeAsync(context).ConfigureAwait(false);
|
||||
|
||||
if (result.Kind is not LaunchExecutionResultKind.Ok)
|
||||
{
|
||||
infoBarService.Warning(result.ErrorMessage);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogCritical(ex, "Launch failed");
|
||||
infoBarService.Error(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41,7 +41,7 @@ internal sealed partial class LaunchGameShared
|
||||
if (!IgnoredInvalidChannelOptions.Contains(options))
|
||||
{
|
||||
// 后台收集
|
||||
throw ThrowHelper.NotSupported($"不支持的 MultiChannel: {options}");
|
||||
HutaoException.Throw(HutaoExceptionKind.GameConfigInvalidChannelOptions, $"不支持的 ChannelOptions: {options}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +70,9 @@ internal sealed partial class LaunchGameShared
|
||||
string persistentScriptVersionFile = Path.Combine(gameFileSystem.GameDirectory, dataFolder, "Persistent", "ScriptVersion");
|
||||
string version = await File.ReadAllTextAsync(persistentScriptVersionFile).ConfigureAwait(false);
|
||||
|
||||
LaunchGameConfigurationFixDialog dialog = await contentDialogFactory.CreateInstanceAsync<LaunchGameConfigurationFixDialog>().ConfigureAwait(false);
|
||||
LaunchGameConfigurationFixDialog dialog = await contentDialogFactory
|
||||
.CreateInstanceAsync<LaunchGameConfigurationFixDialog>()
|
||||
.ConfigureAwait(false);
|
||||
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
dialog.KnownSchemes = KnownLaunchSchemes.Get().Where(scheme => scheme.IsOversea == isOversea);
|
||||
|
||||
@@ -60,6 +60,8 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel, IView
|
||||
private GamePathEntry? selectedGamePathEntry;
|
||||
private GameAccountFilter? gameAccountFilter;
|
||||
|
||||
LaunchGameShared IViewModelSupportLaunchExecution.Shared { get => launchGameShared; }
|
||||
|
||||
public LaunchOptions LaunchOptions { get => launchOptions; }
|
||||
|
||||
public LaunchStatusOptions LaunchStatusOptions { get => launchStatusOptions; }
|
||||
@@ -83,7 +85,6 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel, IView
|
||||
|
||||
public GameResource? GameResource { get => gameResource; set => SetProperty(ref gameResource, value); }
|
||||
|
||||
[AlsoAsyncSets(nameof(SelectedScheme), nameof(GameAccountsView))]
|
||||
public bool GamePathSelectedAndValid
|
||||
{
|
||||
get => gamePathSelectedAndValid;
|
||||
@@ -149,7 +150,6 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel, IView
|
||||
|
||||
public ImmutableList<GamePathEntry> GamePathEntries { get => gamePathEntries; set => SetProperty(ref gamePathEntries, value); }
|
||||
|
||||
[AlsoSets(nameof(GamePathSelectedAndValid))]
|
||||
public GamePathEntry? SelectedGamePathEntry
|
||||
{
|
||||
get => selectedGamePathEntry;
|
||||
@@ -184,25 +184,7 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel, IView
|
||||
[Command("IdentifyMonitorsCommand")]
|
||||
private static async Task IdentifyMonitorsAsync()
|
||||
{
|
||||
List<IdentifyMonitorWindow> windows = [];
|
||||
|
||||
IReadOnlyList<DisplayArea> displayAreas = DisplayArea.FindAll();
|
||||
for (int i = 0; i < displayAreas.Count; i++)
|
||||
{
|
||||
windows.Add(new IdentifyMonitorWindow(displayAreas[i], i + 1));
|
||||
}
|
||||
|
||||
foreach (IdentifyMonitorWindow window in windows)
|
||||
{
|
||||
window.Activate();
|
||||
}
|
||||
|
||||
await Delay.FromSeconds(3).ConfigureAwait(true);
|
||||
|
||||
foreach (IdentifyMonitorWindow window in windows)
|
||||
{
|
||||
window.Close();
|
||||
}
|
||||
await IdentifyMonitorWindow.IdentifyAllMonitorsAsync(3);
|
||||
}
|
||||
|
||||
[Command("SetGamePathCommand")]
|
||||
@@ -234,21 +216,7 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel, IView
|
||||
[Command("LaunchCommand")]
|
||||
private async Task LaunchAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
LaunchExecutionContext context = new(Ioc.Default, this, SelectedScheme, SelectedGameAccount);
|
||||
LaunchExecutionResult result = await new LaunchExecutionInvoker().InvokeAsync(context).ConfigureAwait(false);
|
||||
|
||||
if (result.Kind is not LaunchExecutionResultKind.Ok)
|
||||
{
|
||||
infoBarService.Warning(result.ErrorMessage);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogCritical(ex, "Launch failed");
|
||||
infoBarService.Error(ex);
|
||||
}
|
||||
await this.LaunchExecutionAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[Command("DetectGameAccountCommand")]
|
||||
@@ -269,7 +237,7 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel, IView
|
||||
SelectedGameAccount = account;
|
||||
}
|
||||
}
|
||||
catch (UserdataCorruptedException ex)
|
||||
catch (Exception ex)
|
||||
{
|
||||
infoBarService.Error(ex);
|
||||
}
|
||||
@@ -278,47 +246,54 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel, IView
|
||||
[Command("AttachGameAccountCommand")]
|
||||
private void AttachGameAccountToCurrentUserGameRole(GameAccount? gameAccount)
|
||||
{
|
||||
if (gameAccount is not null)
|
||||
if (gameAccount is null)
|
||||
{
|
||||
if (userService.Current?.SelectedUserGameRole is { } role)
|
||||
{
|
||||
gameService.AttachGameAccountToUid(gameAccount, role.GameUid);
|
||||
}
|
||||
else
|
||||
{
|
||||
infoBarService.Warning(SH.MustSelectUserAndUid);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (userService.Current?.SelectedUserGameRole is { } role)
|
||||
{
|
||||
gameService.AttachGameAccountToUid(gameAccount, role.GameUid);
|
||||
}
|
||||
else
|
||||
{
|
||||
infoBarService.Warning(SH.MustSelectUserAndUid);
|
||||
}
|
||||
}
|
||||
|
||||
[Command("ModifyGameAccountCommand")]
|
||||
private async Task ModifyGameAccountAsync(GameAccount? gameAccount)
|
||||
{
|
||||
if (gameAccount is not null)
|
||||
if (gameAccount is null)
|
||||
{
|
||||
await gameService.ModifyGameAccountAsync(gameAccount).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
await gameService.ModifyGameAccountAsync(gameAccount).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[Command("RemoveGameAccountCommand")]
|
||||
private async Task RemoveGameAccountAsync(GameAccount? gameAccount)
|
||||
{
|
||||
if (gameAccount is not null)
|
||||
if (gameAccount is null)
|
||||
{
|
||||
await gameService.RemoveGameAccountAsync(gameAccount).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
await gameService.RemoveGameAccountAsync(gameAccount).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[Command("OpenScreenshotFolderCommand")]
|
||||
private async Task OpenScreenshotFolderAsync()
|
||||
{
|
||||
string game = LaunchOptions.GamePath;
|
||||
string? directory = Path.GetDirectoryName(game);
|
||||
ArgumentException.ThrowIfNullOrEmpty(directory);
|
||||
string screenshot = Path.Combine(directory, "ScreenShot");
|
||||
if (Directory.Exists(screenshot))
|
||||
if (!launchOptions.TryGetGameFileSystem(out GameFileSystem? gameFileSystem))
|
||||
{
|
||||
await Windows.System.Launcher.LaunchFolderPathAsync(screenshot);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Directory.Exists(gameFileSystem.ScreenShotDirectory))
|
||||
{
|
||||
await Windows.System.Launcher.LaunchFolderPathAsync(gameFileSystem.ScreenShotDirectory);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -326,12 +301,12 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel, IView
|
||||
{
|
||||
if (SetProperty(ref selectedScheme, value, nameof(SelectedScheme)))
|
||||
{
|
||||
UpdateGameResourceAsync(value).SafeForget();
|
||||
await UpdateGameAccountsViewAsync().ConfigureAwait(false);
|
||||
|
||||
// Clear the selected game account to prevent setting
|
||||
// incorrect CN/OS account when scheme not match
|
||||
SelectedGameAccount = default;
|
||||
|
||||
await UpdateGameAccountsViewAsync().ConfigureAwait(false);
|
||||
UpdateGameResourceAsync(value).SafeForget();
|
||||
}
|
||||
|
||||
async ValueTask UpdateGameResourceAsync(LaunchScheme? scheme)
|
||||
|
||||
@@ -22,7 +22,6 @@ namespace Snap.Hutao.ViewModel.Game;
|
||||
internal sealed partial class LaunchGameViewModelSlim : Abstraction.ViewModelSlim<View.Page.LaunchGamePage>, IViewModelSupportLaunchExecution
|
||||
{
|
||||
private readonly LaunchStatusOptions launchStatusOptions;
|
||||
private readonly ILogger<LaunchGameViewModelSlim> logger;
|
||||
private readonly LaunchGameShared launchGameShared;
|
||||
private readonly IInfoBarService infoBarService;
|
||||
private readonly IGameServiceFacade gameService;
|
||||
@@ -32,6 +31,8 @@ internal sealed partial class LaunchGameViewModelSlim : Abstraction.ViewModelSli
|
||||
private GameAccount? selectedGameAccount;
|
||||
private GameAccountFilter? gameAccountFilter;
|
||||
|
||||
LaunchGameShared IViewModelSupportLaunchExecution.Shared { get => launchGameShared; }
|
||||
|
||||
public LaunchStatusOptions LaunchStatusOptions { get => launchStatusOptions; }
|
||||
|
||||
public AdvancedCollectionView<GameAccount>? GameAccountsView { get => gameAccountsView; set => SetProperty(ref gameAccountsView, value); }
|
||||
@@ -41,10 +42,6 @@ internal sealed partial class LaunchGameViewModelSlim : Abstraction.ViewModelSli
|
||||
/// </summary>
|
||||
public GameAccount? SelectedGameAccount { get => selectedGameAccount; set => SetProperty(ref selectedGameAccount, value); }
|
||||
|
||||
public void SetGamePathEntriesAndSelectedGamePathEntry(ImmutableList<GamePathEntry> gamePathEntries, GamePathEntry? selectedEntry)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task OpenUIAsync()
|
||||
{
|
||||
@@ -59,7 +56,7 @@ internal sealed partial class LaunchGameViewModelSlim : Abstraction.ViewModelSli
|
||||
SelectedGameAccount ??= gameService.DetectCurrentGameAccount(scheme);
|
||||
}
|
||||
}
|
||||
catch (UserdataCorruptedException ex)
|
||||
catch (Exception ex)
|
||||
{
|
||||
infoBarService.Error(ex);
|
||||
}
|
||||
@@ -76,23 +73,6 @@ internal sealed partial class LaunchGameViewModelSlim : Abstraction.ViewModelSli
|
||||
[Command("LaunchCommand")]
|
||||
private async Task LaunchAsync()
|
||||
{
|
||||
IInfoBarService infoBarService = ServiceProvider.GetRequiredService<IInfoBarService>();
|
||||
LaunchScheme? scheme = launchGameShared.GetCurrentLaunchSchemeFromConfigFile();
|
||||
|
||||
try
|
||||
{
|
||||
LaunchExecutionContext context = new(Ioc.Default, this, scheme, SelectedGameAccount);
|
||||
LaunchExecutionResult result = await new LaunchExecutionInvoker().InvokeAsync(context).ConfigureAwait(false);
|
||||
|
||||
if (result.Kind is not LaunchExecutionResultKind.Ok)
|
||||
{
|
||||
infoBarService.Warning(result.ErrorMessage);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogCritical(ex, "Launch failed");
|
||||
infoBarService.Error(ex);
|
||||
}
|
||||
await this.LaunchExecutionAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user