From 6525fbb0d97d8734ad8707b4e59e2305a650438c Mon Sep 17 00:00:00 2001 From: Observer KRypt0n_ Date: Wed, 22 Feb 2023 20:46:47 +0200 Subject: [PATCH] core: done dynamic heavy tasks initialization - now launcher is able to load some heavy stuff in background so they will not freeze it during start - patch status and latest game data is loaded in background from now - renamed some components and their messages types for consistency --- src/main.rs | 23 ----- src/ui/about.rs | 10 +-- src/ui/main.rs | 61 +++++++++++-- src/ui/preferences/enhancements.rs | 6 +- src/ui/preferences/general.rs | 132 +++++++++++++++++------------ src/ui/preferences/main.rs | 49 ++++++++--- 6 files changed, 179 insertions(+), 102 deletions(-) diff --git a/src/main.rs b/src/main.rs index 84a13cd..a1ca792 100644 --- a/src/main.rs +++ b/src/main.rs @@ -37,29 +37,6 @@ lazy_static::lazy_static! { pub static ref CONFIG: config::Config = config::get().expect("Failed to load config"); pub static ref GAME: Game = Game::new(&CONFIG.game.path); - - // TODO: add loading screen for heavy tasks like this - // UPD: tried once. The problem is that I use this variable, as well as ones above, - // in the view! macro, which makes it times harder to make the main window load - // faster than this variable calculates its value to show StatusPage with loader. - // As for now I have no idea how to fix this - pub static ref GAME_DIFF: Option = match GAME.try_get_diff() { - Ok(diff) => Some(diff), - Err(err) => { - tracing::error!("Failed to get game diff {err}"); - - None - } - }; - - pub static ref PATCH: Option = match Patch::try_fetch(&CONFIG.patch.servers, None) { - Ok(patch) => Some(patch), - Err(err) => { - tracing::error!("Failed to fetch patch info {err}"); - - None - } - }; } fn main() { diff --git a/src/ui/about.rs b/src/ui/about.rs index 420a45c..c83bf54 100644 --- a/src/ui/about.rs +++ b/src/ui/about.rs @@ -13,7 +13,7 @@ pub struct AboutDialog { } #[derive(Debug)] -pub enum AppMsg { +pub enum AboutDialogMsg { Show, Hide } @@ -21,7 +21,7 @@ pub enum AppMsg { #[relm4::component(pub)] impl SimpleComponent for AboutDialog { type Init = (); - type Input = AppMsg; + type Input = AboutDialogMsg; type Output = (); view! { @@ -77,7 +77,7 @@ impl SimpleComponent for AboutDialog { set_visible: model.visible, connect_close_request[sender] => move |_| { - sender.input(AppMsg::Hide); + sender.input(AboutDialogMsg::Hide); gtk::Inhibit(false) } @@ -104,11 +104,11 @@ impl SimpleComponent for AboutDialog { tracing::debug!("Called about dialog event: {:?}", msg); match msg { - AppMsg::Show => { + AboutDialogMsg::Show => { self.visible = true; } - AppMsg::Hide => { + AboutDialogMsg::Hide => { self.visible = false; } } diff --git a/src/ui/main.rs b/src/ui/main.rs index e2ccb03..8c4114f 100644 --- a/src/ui/main.rs +++ b/src/ui/main.rs @@ -12,8 +12,8 @@ use anime_launcher_sdk::config::launcher::LauncherStyle; use crate::*; use crate::i18n::tr; -use super::preferences::main::App as PreferencesApp; -use super::about::{AboutDialog, AppMsg as AboutDialogMsg}; +use super::preferences::main::*; +use super::about::*; relm4::new_action_group!(WindowActionGroup, "win"); @@ -33,6 +33,14 @@ pub struct App { #[derive(Debug)] pub enum AppMsg { + /// Supposed to be called automatically on app's run when the latest game version + /// was retrieved from the API + UpdateGameDiff(Option), + + /// Supposed to be called automatically on app's run when the latest patch version + /// was retrieved from remote repos + UpdatePatch(Option), + PerformAction, OpenPreferences, ClosePreferences, @@ -261,11 +269,42 @@ impl SimpleComponent for App { widgets.main_window.insert_action_group("win", Some(&group.into_action_group())); - unsafe { - crate::READY = true; - } + tracing::info!("Main window initialized"); - tracing::info!("Main window initialized. App is ready"); + // Initialize some heavy tasks + std::thread::spawn(move || { + tracing::info!("Initializing heavy tasks"); + + // Update initial game version status + sender.input(AppMsg::UpdateGameDiff(match GAME.try_get_diff() { + Ok(diff) => Some(diff), + Err(err) => { + tracing::error!("Failed to get game diff {err}"); + + None + } + })); + + tracing::info!("Updated game version status"); + + // Update initial patch status + sender.input(AppMsg::UpdatePatch(match Patch::try_fetch(&CONFIG.patch.servers, None) { + Ok(patch) => Some(patch), + Err(err) => { + tracing::error!("Failed to fetch patch info {err}"); + + None + } + })); + + tracing::info!("Updated patch status"); + + unsafe { + crate::READY = true; + } + + tracing::info!("App is ready"); + }); ComponentParts { model, widgets } } @@ -274,6 +313,16 @@ impl SimpleComponent for App { tracing::debug!("Called main window event: {:?}", msg); match msg { + #[allow(unused_must_use)] + AppMsg::UpdateGameDiff(diff) => unsafe { + PREFERENCES_WINDOW.as_ref().unwrap_unchecked().sender().send(PreferencesAppMsg::UpdateGameDiff(diff)); + }, + + #[allow(unused_must_use)] + AppMsg::UpdatePatch(patch) => unsafe { + PREFERENCES_WINDOW.as_ref().unwrap_unchecked().sender().send(PreferencesAppMsg::UpdatePatch(patch)); + }, + AppMsg::PerformAction => { anime_launcher_sdk::game::run().expect("Failed to run the game"); } diff --git a/src/ui/preferences/enhancements.rs b/src/ui/preferences/enhancements.rs index 35b589a..f4b4436 100644 --- a/src/ui/preferences/enhancements.rs +++ b/src/ui/preferences/enhancements.rs @@ -8,10 +8,10 @@ use anime_launcher_sdk::config::prelude::*; use crate::i18n::tr; use crate::*; -pub struct Enhancements; +pub struct EnhancementsApp; #[relm4::component(pub)] -impl SimpleComponent for Enhancements { +impl SimpleComponent for EnhancementsApp { type Init = (); type Input = (); type Output = (); @@ -403,7 +403,7 @@ impl SimpleComponent for Enhancements { root: &Self::Root, _sender: ComponentSender, ) -> ComponentParts { - tracing::info!("Initializing about dialog"); + tracing::info!("Initializing enhancements settings"); let model = Self; let widgets = view_output!(); diff --git a/src/ui/preferences/general.rs b/src/ui/preferences/general.rs index b68bdc6..eb91760 100644 --- a/src/ui/preferences/general.rs +++ b/src/ui/preferences/general.rs @@ -15,9 +15,12 @@ use crate::ui::components::*; use crate::i18n::*; use crate::*; -pub struct General { - wine_components: AsyncController>, - dxvk_components: AsyncController>, +pub struct GeneralApp { + wine_components: AsyncController>, + dxvk_components: AsyncController>, + + game_diff: Option, + patch: Option, style: LauncherStyle, @@ -32,7 +35,15 @@ pub struct General { } #[derive(Debug, Clone)] -pub enum AppMsg { +pub enum GeneralAppMsg { + /// Supposed to be called automatically on app's run when the latest game version + /// was retrieved from the API + UpdateGameDiff(Option), + + /// Supposed to be called automatically on app's run when the latest patch version + /// was retrieved from remote repos + UpdatePatch(Option), + Toast { title: String, description: Option @@ -49,10 +60,10 @@ pub enum AppMsg { } #[relm4::component(pub)] -impl SimpleComponent for General { +impl SimpleComponent for GeneralApp { type Init = (); - type Input = AppMsg; - type Output = super::main::AppMsg; + type Input = GeneralAppMsg; + type Output = super::main::PreferencesAppMsg; view! { adw::PreferencesPage { @@ -84,7 +95,7 @@ impl SimpleComponent for General { set_from_resource: Some("/org/app/images/modern.svg") }, - connect_clicked => AppMsg::UpdateLauncherStyle(LauncherStyle::Modern) + connect_clicked => GeneralAppMsg::UpdateLauncherStyle(LauncherStyle::Modern) }, gtk::Label { @@ -110,7 +121,7 @@ impl SimpleComponent for General { set_from_resource: Some("/org/app/images/classic.svg") }, - connect_clicked => AppMsg::UpdateLauncherStyle(LauncherStyle::Classic) + connect_clicked => GeneralAppMsg::UpdateLauncherStyle(LauncherStyle::Classic) }, gtk::Label { @@ -233,7 +244,8 @@ impl SimpleComponent for General { set_title: &tr("game-version"), add_suffix = >k::Label { - set_text: &match GAME_DIFF.as_ref() { + #[watch] + set_text: &match model.game_diff.as_ref() { Some(diff) => match diff { VersionDiff::Latest(current) | VersionDiff::Predownload { current, .. } | @@ -246,19 +258,21 @@ impl SimpleComponent for General { None => String::from("?") }, - add_css_class: match GAME_DIFF.as_ref() { + #[watch] + set_css_classes: match model.game_diff.as_ref() { Some(diff) => match diff { - VersionDiff::Latest(_) => "success", - VersionDiff::Predownload { .. } => "accent", - VersionDiff::Diff { .. } => "warning", - VersionDiff::Outdated { .. } => "error", - VersionDiff::NotInstalled { .. } => "" + VersionDiff::Latest(_) => &["success"], + VersionDiff::Predownload { .. } => &["accent"], + VersionDiff::Diff { .. } => &["warning"], + VersionDiff::Outdated { .. } => &["error"], + VersionDiff::NotInstalled { .. } => &[] } - None => "success" + None => &["success"] }, - set_tooltip_text: Some(&match GAME_DIFF.as_ref() { + #[watch] + set_tooltip_text: Some(&match model.game_diff.as_ref() { Some(diff) => match diff { VersionDiff::Latest(_) => String::new(), VersionDiff::Predownload { current, latest, .. } => tr_args("game-predownload-available", [ @@ -284,7 +298,8 @@ impl SimpleComponent for General { set_title: &tr("patch-version"), add_suffix = >k::Label { - set_text: &match PATCH.as_ref() { + #[watch] + set_text: &match model.patch.as_ref() { Some(patch) => match patch { Patch::NotAvailable => tr("patch-not-available"), Patch::Outdated { current, .. } => tr_args("patch-outdated", [("current", current.to_string().into())]), @@ -296,25 +311,27 @@ impl SimpleComponent for General { None => String::from("?") }, - add_css_class: match PATCH.as_ref() { + #[watch] + set_css_classes: match model.patch.as_ref() { Some(patch) => match patch { - Patch::NotAvailable => "error", + Patch::NotAvailable => &["error"], Patch::Outdated { .. } | Patch::Preparation { .. } | - Patch::Testing { .. } => "warning", + Patch::Testing { .. } => &["warning"], Patch::Available { .. } => unsafe { - if let Ok(true) = PATCH.as_ref().unwrap_unchecked().is_applied(&CONFIG.game.path) { - "success" + if let Ok(true) = model.patch.as_ref().unwrap_unchecked().is_applied(&CONFIG.game.path) { + &["success"] } else { - "warning" + &["warning"] } } } - None => "" + None => &[] }, - set_tooltip_text: Some(&match PATCH.as_ref() { + #[watch] + set_tooltip_text: Some(&match model.patch.as_ref() { Some(patch) => match patch { Patch::NotAvailable => tr("patch-not-available-tooltip"), Patch::Outdated { current, latest, .. } => tr_args("patch-outdated-tooltip", [ @@ -324,7 +341,7 @@ impl SimpleComponent for General { Patch::Preparation { .. } => tr("patch-preparation-tooltip"), Patch::Testing { .. } => tr("patch-testing-tooltip"), Patch::Available { .. } => unsafe { - if let Ok(true) = PATCH.as_ref().unwrap_unchecked().is_applied(&CONFIG.game.path) { + if let Ok(true) = model.patch.as_ref().unwrap_unchecked().is_applied(&CONFIG.game.path) { String::new() } else { tr("patch-testing-tooltip") @@ -364,7 +381,7 @@ impl SimpleComponent for General { connect_selected_notify[sender] => move |row| { if is_ready() { - sender.input(AppMsg::SelectWine(row.selected() as usize)); + sender.input(GeneralAppMsg::SelectWine(row.selected() as usize)); } } @wine_selected_notify }, @@ -378,7 +395,7 @@ impl SimpleComponent for General { set_state: true, connect_state_notify[sender] => move |switch| { - sender.input(AppMsg::WineRecommendedOnly(switch.state())); + sender.input(GeneralAppMsg::WineRecommendedOnly(switch.state())); } } } @@ -414,7 +431,7 @@ impl SimpleComponent for General { connect_selected_notify[sender] => move |row| { if is_ready() { - sender.input(AppMsg::SelectDxvk(row.selected() as usize)); + sender.input(GeneralAppMsg::SelectDxvk(row.selected() as usize)); } } @dxvk_selected_notify }, @@ -428,7 +445,7 @@ impl SimpleComponent for General { set_state: true, connect_state_notify[sender] => move |switch| { - sender.input(AppMsg::DxvkRecommendedOnly(switch.state())); + sender.input(GeneralAppMsg::DxvkRecommendedOnly(switch.state())); } } } @@ -445,7 +462,7 @@ impl SimpleComponent for General { root: &Self::Root, sender: ComponentSender, ) -> ComponentParts { - tracing::info!("Initializing about dialog"); + tracing::info!("Initializing general settings"); let model = Self { wine_components: ComponentsList::builder() @@ -454,8 +471,8 @@ impl SimpleComponent for General { download_folder: CONFIG.game.wine.builds.clone(), groups: wine::get_groups().into_iter().map(|group| group.into()).collect() }, - on_downloaded: Some(AppMsg::UpdateDownloadedWine), - on_deleted: Some(AppMsg::UpdateDownloadedWine) + on_downloaded: Some(GeneralAppMsg::UpdateDownloadedWine), + on_deleted: Some(GeneralAppMsg::UpdateDownloadedWine) }) .forward(sender.input_sender(), std::convert::identity), @@ -465,11 +482,14 @@ impl SimpleComponent for General { download_folder: CONFIG.game.dxvk.builds.clone(), groups: dxvk::get_groups().into_iter().map(|group| group.into()).collect() }, - on_downloaded: Some(AppMsg::UpdateDownloadedDxvk), - on_deleted: Some(AppMsg::UpdateDownloadedDxvk) + on_downloaded: Some(GeneralAppMsg::UpdateDownloadedDxvk), + on_deleted: Some(GeneralAppMsg::UpdateDownloadedDxvk) }) .forward(sender.input_sender(), std::convert::identity), + game_diff: None, + patch: None, + style: CONFIG.launcher.style, downloaded_wine_versions: vec![], @@ -488,11 +508,19 @@ impl SimpleComponent for General { } fn update(&mut self, msg: Self::Input, sender: ComponentSender) { - tracing::debug!("Called enhancements settings event: {:?}", msg); + tracing::debug!("Called general settings event: {:?}", msg); match msg { + GeneralAppMsg::UpdateGameDiff(diff) => { + self.game_diff = diff; + }, + + GeneralAppMsg::UpdatePatch(patch) => { + self.patch = patch; + }, + #[allow(unused_must_use)] - AppMsg::UpdateLauncherStyle(style) => { + GeneralAppMsg::UpdateLauncherStyle(style) => { if let Ok(mut config) = config::get() { config.launcher.style = style; @@ -505,21 +533,21 @@ impl SimpleComponent for General { } #[allow(unused_must_use)] - AppMsg::Toast { title, description } => { + GeneralAppMsg::Toast { title, description } => { sender.output(Self::Output::Toast { title, description }); } - AppMsg::WineRecommendedOnly(state) => { + GeneralAppMsg::WineRecommendedOnly(state) => { // todo self.wine_components.sender().send(components::list::AppMsg::ShowRecommendedOnly(state)).unwrap(); } - AppMsg::DxvkRecommendedOnly(state) => { + GeneralAppMsg::DxvkRecommendedOnly(state) => { // todo self.dxvk_components.sender().send(components::list::AppMsg::ShowRecommendedOnly(state)).unwrap(); } - AppMsg::UpdateDownloadedWine => { + GeneralAppMsg::UpdateDownloadedWine => { self.downloaded_wine_versions = wine::get_downloaded(&CONFIG.game.wine.builds).unwrap_or_default(); self.selected_wine_version = if let Some(selected) = &CONFIG.game.wine.selected { @@ -541,7 +569,7 @@ impl SimpleComponent for General { }; } - AppMsg::UpdateDownloadedDxvk => { + GeneralAppMsg::UpdateDownloadedDxvk => { self.downloaded_dxvk_versions = dxvk::get_downloaded(&CONFIG.game.dxvk.builds).unwrap_or_default(); self.selected_dxvk_version = if let Ok(Some(selected)) = CONFIG.try_get_selected_dxvk_info() { @@ -563,7 +591,7 @@ impl SimpleComponent for General { }; } - AppMsg::SelectWine(index) => { + GeneralAppMsg::SelectWine(index) => { if let Ok(mut config) = config::get() { if let Some(version) = self.downloaded_wine_versions.get(index) { if config.game.wine.selected.as_ref().unwrap_or(&String::new()) != &version.title { @@ -581,26 +609,26 @@ impl SimpleComponent for General { } Err(err) => { - sender.input(AppMsg::Toast { + sender.input(GeneralAppMsg::Toast { title: tr("wine-prefix-update-failed"), description: Some(err.to_string()) }); } } - sender.input(AppMsg::ResetWineSelection(index)); + sender.input(GeneralAppMsg::ResetWineSelection(index)); }); } } } } - AppMsg::ResetWineSelection(index) => { + GeneralAppMsg::ResetWineSelection(index) => { self.selecting_wine_version = false; self.selected_wine_version = index as u32; } - AppMsg::SelectDxvk(index) => { + GeneralAppMsg::SelectDxvk(index) => { if let Ok(config) = config::get() { if let Some(version) = self.downloaded_dxvk_versions.get(index) { if let Ok(selected) = config.try_get_selected_dxvk_info() { @@ -618,13 +646,13 @@ impl SimpleComponent for General { std::thread::spawn(move || { if let Err(err) = Dxvk::install(&wine, dxvk_folder, InstallParams::default()) { - sender.input(AppMsg::Toast { + sender.input(GeneralAppMsg::Toast { title: tr("dxvk-install-failed"), description: Some(err.to_string()) }); } - sender.input(AppMsg::ResetDxvkSelection(index)); + sender.input(GeneralAppMsg::ResetDxvkSelection(index)); }); } } @@ -632,7 +660,7 @@ impl SimpleComponent for General { } } - AppMsg::ResetDxvkSelection(index) => { + GeneralAppMsg::ResetDxvkSelection(index) => { self.selecting_dxvk_version = false; self.selected_dxvk_version = index as u32; } diff --git a/src/ui/preferences/main.rs b/src/ui/preferences/main.rs index 90ee285..1df97d3 100644 --- a/src/ui/preferences/main.rs +++ b/src/ui/preferences/main.rs @@ -3,19 +3,32 @@ use relm4::prelude::*; use gtk::prelude::*; use adw::prelude::*; +use anime_launcher_sdk::anime_game_core::prelude::*; +use anime_launcher_sdk::anime_game_core::genshin::prelude::*; use anime_launcher_sdk::config::launcher::LauncherStyle; use crate::i18n::tr; +use super::general::*; +use super::enhancements::*; + pub static mut PREFERENCES_WINDOW: Option = None; -pub struct App { - general: Controller, - enhancements: Controller +pub struct PreferencesApp { + general: Controller, + enhancements: Controller } #[derive(Debug, Clone)] -pub enum AppMsg { +pub enum PreferencesAppMsg { + /// Supposed to be called automatically on app's run when the latest game version + /// was retrieved from the API + UpdateGameDiff(Option), + + /// Supposed to be called automatically on app's run when the latest patch version + /// was retrieved from remote repos + UpdatePatch(Option), + Toast { title: String, description: Option @@ -24,9 +37,9 @@ pub enum AppMsg { } #[relm4::component(pub)] -impl SimpleComponent for App { +impl SimpleComponent for PreferencesApp { type Init = gtk::Window; - type Input = AppMsg; + type Input = PreferencesAppMsg; type Output = crate::ui::main::AppMsg; view! { @@ -41,7 +54,7 @@ impl SimpleComponent for App { connect_close_request[sender] => move |_| { if let Err(err) = anime_launcher_sdk::config::flush() { - sender.input(AppMsg::Toast { + sender.input(PreferencesAppMsg::Toast { title: tr("config-flush-error"), description: Some(err.to_string()) }); @@ -60,11 +73,11 @@ impl SimpleComponent for App { tracing::info!("Initializing preferences window"); let model = Self { - general: super::general::General::builder() + general: GeneralApp::builder() .launch(()) .forward(sender.input_sender(), std::convert::identity), - enhancements: super::enhancements::Enhancements::builder() + enhancements: EnhancementsApp::builder() .launch(()) .detach() }; @@ -78,8 +91,8 @@ impl SimpleComponent for App { } #[allow(unused_must_use)] { - model.general.sender().send(super::general::AppMsg::UpdateDownloadedWine); - model.general.sender().send(super::general::AppMsg::UpdateDownloadedDxvk); + model.general.sender().send(GeneralAppMsg::UpdateDownloadedWine); + model.general.sender().send(GeneralAppMsg::UpdateDownloadedDxvk); } ComponentParts { model, widgets } @@ -89,7 +102,17 @@ impl SimpleComponent for App { tracing::debug!("Called preferences window event: {:?}", msg); match msg { - AppMsg::Toast { title, description } => unsafe { + #[allow(unused_must_use)] + PreferencesAppMsg::UpdateGameDiff(diff) => { + self.general.sender().send(GeneralAppMsg::UpdateGameDiff(diff)); + }, + + #[allow(unused_must_use)] + PreferencesAppMsg::UpdatePatch(patch) => { + self.general.sender().send(GeneralAppMsg::UpdatePatch(patch)); + }, + + PreferencesAppMsg::Toast { title, description } => unsafe { let toast = adw::Toast::new(&title); toast.set_timeout(5); @@ -124,7 +147,7 @@ impl SimpleComponent for App { } #[allow(unused_must_use)] - AppMsg::UpdateLauncherStyle(style) => { + PreferencesAppMsg::UpdateLauncherStyle(style) => { sender.output(Self::Output::UpdateLauncherStyle(style)); } }