Skip to content

Commit

Permalink
Feature/home launch button (#320)
Browse files Browse the repository at this point in the history
* 添加启动过程信息弹出动画

* Squashed commit of the following:

commit eccc67d
Author: natsurainko <[email protected]>
Date:   Wed Feb 5 19:53:06 2025 +0800

    Fix/account service (#319)

    * 使用 WeakReferenceMessenger 调度 UI 线程修改 Accounts 集合

    * 删除解绑集合操作

    * 使用 DispatcherQueue.EnqueueAsync

    * 删除 Messages

commit 4e3d1b5
Author: natsurainko <[email protected]>
Date:   Tue Feb 4 20:01:47 2025 +0800

    修复部分错误

* 显示启动进度反馈

* 延长显示启动进度信息直到游戏窗口出现

* 增加设置项

* 补上缺少的 Command,同时添加本地化资源

* 细节 UI 修复

* 增加结束进程操作
  • Loading branch information
natsurainko authored Feb 6, 2025
1 parent eccc67d commit 15f1589
Show file tree
Hide file tree
Showing 18 changed files with 564 additions and 229 deletions.
2 changes: 1 addition & 1 deletion FluentLauncher.Localization
71 changes: 39 additions & 32 deletions Natsurainko.FluentLauncher/Services/Launch/LaunchService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,10 @@ internal class LaunchService
private readonly AccountService _accountService;
private readonly SettingsService _settingsService;

public ObservableCollection<LaunchTaskViewModel> LaunchTasks { get; } = [];

public event EventHandler? TaskListStateChanged;

public ObservableCollection<LaunchTaskViewModel> LaunchTasks { get; } = [];

public LaunchService(
SettingsService settingsService,
AccountService accountService,
Expand All @@ -61,8 +61,6 @@ public LaunchService(
_settingsService = settingsService;
_accountService = accountService;
_downloadService = downloadService;

LaunchTasks.CollectionChanged += (_, e) => TaskListStateChanged?.Invoke(this, e);
}

public void LaunchFromUI(MinecraftInstance instance)
Expand All @@ -74,12 +72,46 @@ public void LaunchFromUI(MinecraftInstance instance)
TaskListStateChanged?.Invoke(this, e);
};

App.DispatcherQueue.TryEnqueue(() => LaunchTasks.Insert(0, viewModel));

InsertTask(viewModel);
viewModel.Start();

WeakReferenceMessenger.Default.Send(new GlobalNavigationMessage("Tasks/Launch"));
}

public void LaunchFromUIWithTrack(MinecraftInstance instance)
{
var viewModel = new LaunchTaskViewModel(instance, this);
viewModel.PropertyChanged += (_, e) =>
{
if (e.PropertyName == "TaskState")
{
TaskListStateChanged?.Invoke(this, e);

if (viewModel.TaskState != TaskState.Prepared && viewModel.TaskState != TaskState.Running)
WeakReferenceMessenger.Default.Send(new TrackLaunchTaskChangedMessage(null)); // Cancel the tracking task
}
else if (e.PropertyName == "WaitedForInputIdle")
{
if (viewModel.WaitedForInputIdle)
WeakReferenceMessenger.Default.Send(new TrackLaunchTaskChangedMessage(null)); // Cancel the tracking task
}
};

InsertTask(viewModel);
viewModel.Start();

WeakReferenceMessenger.Default.Send(new TrackLaunchTaskChangedMessage(viewModel));
}

private void InsertTask(LaunchTaskViewModel task)
{
App.DispatcherQueue.TryEnqueue(() =>
{
LaunchTasks.Insert(0, task);
TaskListStateChanged?.Invoke(this, EventArgs.Empty);
});
}

public async Task<MinecraftProcess> LaunchAsync(
MinecraftInstance instance,
DataReceivedEventHandler? outputDataReceivedHandler = null,
Expand Down Expand Up @@ -529,29 +561,4 @@ public enum LaunchStage
BuildArguments,
LaunchProcess
}
};


/*
public record struct LaunchProgress(
LaunchSessionState State,
DependencyResolver? DependencyResolver,
MinecraftProcess? MinecraftProcess,
Exception? Exception);
public enum LaunchSessionState
{
// Launch sequence
Created = 0,
Inspecting = 1,
Authenticating = 2,
CompletingResources = 3,
BuildingArguments = 4,
LaunchingProcess = 5,
GameRunning = 6,
GameExited = 7, // Game exited normally (exit code == 0)
Faulted = 8, // Failure before game started
Killed = 9, // Game killed by user
GameCrashed = 10 // Game crashed (exit code != 0)
}*/
};
33 changes: 20 additions & 13 deletions Natsurainko.FluentLauncher/Services/Network/DownloadService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,15 @@ namespace Natsurainko.FluentLauncher.Services.Network;

internal partial class DownloadService
{
private readonly SettingsService _settingsService;
private MultipartDownloader _downloader;
private readonly SettingsService _settingsService;

public event EventHandler? TaskListStateChanged;

public IDownloader Downloader { get => _downloader; }

public ObservableCollection<TaskViewModel> DownloadTasks { get; } = [];

public IDownloader Downloader { get => _downloader; }

public DownloadService(SettingsService settingsService)
{
_settingsService = settingsService;
Expand All @@ -37,12 +37,6 @@ public DownloadService(SettingsService settingsService)
_settingsService.CurrentDownloadSourceChanged += (_, _) => UpdateDownloader();
}

void UpdateDownloader()
{
_downloader = new(HttpUtils.HttpClient, 1024 * 1024, 8, _settingsService.MaxDownloadThreads,
_settingsService.CurrentDownloadSource == "Bmclapi" ? DownloadMirrors.BmclApi : null);
}

public void DownloadResourceFile(GameResourceFile file, string filePath)
{
var taskViewModel = new DownloadGameResourceTaskViewModel(file, filePath);
Expand All @@ -52,8 +46,7 @@ public void DownloadResourceFile(GameResourceFile file, string filePath)
TaskListStateChanged?.Invoke(this, e);
};

App.DispatcherQueue.TryEnqueue(() => DownloadTasks.Insert(0, taskViewModel));

InsertTask(taskViewModel);
taskViewModel.Start();
}

Expand All @@ -69,11 +62,25 @@ public void InstallInstance(InstanceInstallConfig config)
TaskListStateChanged?.Invoke(this, e);
};

App.DispatcherQueue.TryEnqueue(() => DownloadTasks.Insert(0, taskViewModel));

InsertTask(taskViewModel);
taskViewModel.Start();
}

private void InsertTask(TaskViewModel taskViewModel)
{
App.DispatcherQueue.TryEnqueue(() =>
{
DownloadTasks.Insert(0, taskViewModel);
TaskListStateChanged?.Invoke(this, EventArgs.Empty);
});
}

private void UpdateDownloader()
{
_downloader = new(HttpUtils.HttpClient, 1024 * 1024, 8, _settingsService.MaxDownloadThreads,
_settingsService.CurrentDownloadSource == "Bmclapi" ? DownloadMirrors.BmclApi : null);
}

IInstanceInstaller GetInstanceInstaller(
InstanceInstallConfig instanceInstallConfig,
out IReadOnlyList<InstallationStageViewModel> installationStageViews)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,9 @@ public partial class SettingsService : SettingsContainer
[SettingItem(Default = 0, Converter = typeof(JsonStringConverter<int>))]
public partial int HomeLaunchButtonSize { get; set; }

[SettingItem(Default = false, Converter = typeof(JsonStringConverter<bool>))]
public partial bool EnableHomeLaunchTaskTrack { get; set; }

#endregion

#region Application Window
Expand Down
6 changes: 6 additions & 0 deletions Natsurainko.FluentLauncher/Services/UI/Messaging/Messages.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
using CommunityToolkit.Mvvm.Messaging.Messages;
using Natsurainko.FluentLauncher.ViewModels.Common;
using Nrk.FluentCore.Authentication;

namespace Natsurainko.FluentLauncher.Services.UI.Messaging;

class TrackLaunchTaskChangedMessage : ValueChangedMessage<LaunchTaskViewModel?>
{
public TrackLaunchTaskChangedMessage(LaunchTaskViewModel? value) : base(value) { }
}

class ActiveAccountChangedMessage : ValueChangedMessage<Account>
{
public ActiveAccountChangedMessage(Account value) : base(value) { }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ private void AccountService_ActiveAccountChanged(object? sender, Nrk.FluentCore.
if (e == null)
return;

WeakReferenceMessenger.Default.Send(new ActiveAccountChangedMessage(e));
App.DispatcherQueue.TryEnqueue(() => WeakReferenceMessenger.Default.Send(new ActiveAccountChangedMessage(e)));
}

private void SettingsService_SettingsStringValueChanged(SettingsContainer sender, SettingChangedEventArgs e)
Expand Down
55 changes: 50 additions & 5 deletions Natsurainko.FluentLauncher/ViewModels/Common/TaskViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -126,12 +126,11 @@ public virtual void Start()
protected abstract void Run();

[RelayCommand(CanExecute = nameof(CanCancel))]
void Cancel()
public void Cancel()
{
_tokenSource.Cancel();
TaskState = TaskState.Canceling;
}

}

#region Download Task
Expand Down Expand Up @@ -489,6 +488,22 @@ class LaunchProgressViewModel : IProgress<LaunchProgress>
{
public Dictionary<LaunchStage, LaunchStageViewModel> Stages { get; } = [];

private LaunchStage _currentStage;
public LaunchStage CurrentStage
{
get => _currentStage;
set
{
if (_currentStage != value)
{
_currentStage = value;
CurrentStageChanged?.Invoke(this, Stages[value]);
}
}
}

public event EventHandler<LaunchStageViewModel> CurrentStageChanged;

public LaunchProgressViewModel()
{
foreach (var name in Enum.GetNames(typeof(LaunchStage)))
Expand All @@ -499,7 +514,12 @@ public LaunchProgressViewModel()
public virtual void Report(LaunchProgress value)
{
var vm = Stages[value.Stage];
App.DispatcherQueue.TryEnqueue(() => vm.UpdateProgress(value.StageProgress));

App.DispatcherQueue.TryEnqueue(() =>
{
CurrentStage = value.Stage;
vm.UpdateProgress(value.StageProgress);
});
}
}

Expand Down Expand Up @@ -585,15 +605,19 @@ partial class LaunchStageViewModel : ObservableObject
public partial TaskState State { get; set; } = TaskState.Prepared;

[ObservableProperty]
[NotifyPropertyChangedFor(nameof(ProgressPercentage))]
public partial int TotalTasks { get; set; } = 1;

public string ProgressPercentage => (FinishedTasks / (double)TotalTasks).ToString("P1");

public int FinishedTasks
{
get => _finishedTasks;
set
{
_finishedTasks = value;
OnPropertyChanged(nameof(FinishedTasks));
OnPropertyChanged(nameof(ProgressPercentage));
}
}
private int _finishedTasks = 0;
Expand Down Expand Up @@ -625,6 +649,7 @@ public void UpdateProgress(LaunchStageProgress payload)
case LaunchStageProgressType.IncrementFinishedTasks:
Interlocked.Increment(ref _finishedTasks);
OnPropertyChanged(nameof(FinishedTasks));
OnPropertyChanged(nameof(ProgressPercentage));
break;

case LaunchStageProgressType.Finished:
Expand Down Expand Up @@ -699,6 +724,12 @@ public LaunchTaskViewModel(MinecraftInstance instance, LaunchService launchServi
IsExpanded = true;

_stopwatchElapsedFormat = "mm\\:ss\\.fffff";

launchProgressViewModel.CurrentStageChanged += (sender, vm) =>
{
CurrentStage = vm;
CurrentStageNumber++;
};
}

[ObservableProperty]
Expand All @@ -716,9 +747,18 @@ public LaunchTaskViewModel(MinecraftInstance instance, LaunchService launchServi
[NotifyCanExecuteChangedFor(nameof(KillProcessCommand))]
public partial bool ProcessExited { get; set; }

[ObservableProperty]
public partial bool WaitedForInputIdle { get; set; }

[ObservableProperty]
public partial bool Crashed { get; set; } = false;

[ObservableProperty]
public partial LaunchStageViewModel CurrentStage { get; set; }

[ObservableProperty]
public partial int CurrentStageNumber { get; set; } = 1;

public override bool CanCancel => IsLaunching && TaskState != TaskState.Canceling;

public bool IsGameRunning => ProcessLaunched && !ProcessExited;
Expand Down Expand Up @@ -796,8 +836,13 @@ protected override async void Run()
void AfterLaunchedProcess()
{
App.DispatcherQueue.TryEnqueue(() => ProcessLaunched = true);

McProcess.Process.Exited += Process_Exited;

Task.Run(() =>
{
McProcess.Process.WaitForInputIdle(15 * 1000);
App.DispatcherQueue.TryEnqueue(() => WaitedForInputIdle = true);
});
}

void Process_Exited(object sender, EventArgs e)
Expand Down Expand Up @@ -866,7 +911,7 @@ void ShowLogger()
}

[RelayCommand(CanExecute = nameof(IsGameRunning))]
void KillProcess()
public void KillProcess()
{
_isMcProcessKilled = true;
McProcess!.Process.KillProcessTree(); // not null when game is running
Expand Down
Loading

0 comments on commit 15f1589

Please sign in to comment.