From 411c2cab1c9250e06e9947f0d21d762f68f00fa2 Mon Sep 17 00:00:00 2001 From: Umatriz Date: Fri, 9 Aug 2024 11:53:31 +0300 Subject: [PATCH 01/15] refactor: move imps of `LaunchInstanceBuilderExt` into loader's files --- crates/nomi-core/src/instance/builder_ext.rs | 20 -------------------- crates/nomi-core/src/loaders/fabric.rs | 12 +++++++++++- crates/nomi-core/src/loaders/forge.rs | 12 +++++++++++- crates/nomi-core/src/loaders/vanilla.rs | 10 ++++++++++ 4 files changed, 32 insertions(+), 22 deletions(-) diff --git a/crates/nomi-core/src/instance/builder_ext.rs b/crates/nomi-core/src/instance/builder_ext.rs index a83e434..b4db364 100644 --- a/crates/nomi-core/src/instance/builder_ext.rs +++ b/crates/nomi-core/src/instance/builder_ext.rs @@ -1,5 +1,3 @@ -use crate::loaders::{fabric::Fabric, forge::Forge, vanilla::Vanilla}; - use super::launch::{LaunchInstanceBuilder, LaunchSettings}; pub trait LaunchInstanceBuilderExt { @@ -7,21 +5,3 @@ pub trait LaunchInstanceBuilderExt { } const _: Option> = None; - -impl LaunchInstanceBuilderExt for Vanilla { - fn insert(&self, builder: LaunchInstanceBuilder) -> LaunchInstanceBuilder { - builder - } -} - -impl LaunchInstanceBuilderExt for Fabric { - fn insert(&self, builder: LaunchInstanceBuilder) -> LaunchInstanceBuilder { - builder.profile(self.to_profile()) - } -} - -impl LaunchInstanceBuilderExt for Forge { - fn insert(&self, builder: LaunchInstanceBuilder) -> LaunchInstanceBuilder { - builder.profile(self.to_profile()) - } -} diff --git a/crates/nomi-core/src/loaders/fabric.rs b/crates/nomi-core/src/loaders/fabric.rs index f016a3d..37e76b4 100644 --- a/crates/nomi-core/src/loaders/fabric.rs +++ b/crates/nomi-core/src/loaders/fabric.rs @@ -15,7 +15,11 @@ use crate::{ }, fs::write_to_file, game_paths::GamePaths, - instance::profile::LoaderProfile, + instance::{ + builder_ext::LaunchInstanceBuilderExt, + launch::{LaunchInstanceBuilder, LaunchSettings}, + profile::LoaderProfile, + }, maven_data::{MavenArtifact, MavenData}, repository::{ fabric_meta::FabricVersions, @@ -147,3 +151,9 @@ impl Downloader for Fabric { Box::pin(fut) } } + +impl LaunchInstanceBuilderExt for Fabric { + fn insert(&self, builder: LaunchInstanceBuilder) -> LaunchInstanceBuilder { + builder.profile(self.to_profile()) + } +} diff --git a/crates/nomi-core/src/loaders/forge.rs b/crates/nomi-core/src/loaders/forge.rs index f0a29a9..a6f5622 100644 --- a/crates/nomi-core/src/loaders/forge.rs +++ b/crates/nomi-core/src/loaders/forge.rs @@ -22,7 +22,11 @@ use crate::{ DownloadQueue, FileDownloader, LibrariesDownloader, LibrariesMapper, }, game_paths::GamePaths, - instance::{launch::CLASSPATH_SEPARATOR, profile::LoaderProfile}, + instance::{ + builder_ext::LaunchInstanceBuilderExt, + launch::{LaunchInstanceBuilder, LaunchSettings, CLASSPATH_SEPARATOR}, + profile::LoaderProfile, + }, loaders::vanilla::VanillaLibrariesMapper, maven_data::{MavenArtifact, MavenData}, repository::{ @@ -385,6 +389,12 @@ impl Downloader for Forge { } } +impl LaunchInstanceBuilderExt for Forge { + fn insert(&self, builder: LaunchInstanceBuilder) -> LaunchInstanceBuilder { + builder.profile(self.to_profile()) + } +} + #[derive(Debug, Clone)] struct ProcessorsData { processors: Vec, diff --git a/crates/nomi-core/src/loaders/vanilla.rs b/crates/nomi-core/src/loaders/vanilla.rs index 9cb36ae..f80de13 100644 --- a/crates/nomi-core/src/loaders/vanilla.rs +++ b/crates/nomi-core/src/loaders/vanilla.rs @@ -16,6 +16,10 @@ use crate::{ }, fs::write_to_file, game_paths::GamePaths, + instance::{ + builder_ext::LaunchInstanceBuilderExt, + launch::{LaunchInstanceBuilder, LaunchSettings}, + }, repository::manifest::{Classifiers, DownloadFile, Library, Manifest}, state::get_launcher_manifest, PinnedFutureWithBounds, @@ -137,3 +141,9 @@ impl Downloader for Vanilla { Box::pin(fut) } } + +impl LaunchInstanceBuilderExt for Vanilla { + fn insert(&self, builder: LaunchInstanceBuilder) -> LaunchInstanceBuilder { + builder + } +} From 41f614d3a403ca99d819c43e40ffb9d11cd0a1c4 Mon Sep 17 00:00:00 2001 From: Umatriz Date: Sat, 10 Aug 2024 21:17:28 +0300 Subject: [PATCH 02/15] 50 errors help --- crates/client/src/collections.rs | 14 +- crates/client/src/context.rs | 8 +- crates/client/src/download.rs | 25 +--- crates/client/src/main.rs | 6 +- crates/client/src/states.rs | 6 +- crates/client/src/views/add_profile_menu.rs | 6 +- crates/client/src/views/mods_manager.rs | 6 +- crates/client/src/views/profile_info.rs | 12 +- crates/client/src/views/profiles.rs | 81 +++++----- crates/nomi-core/src/configs/profile.rs | 14 +- crates/nomi-core/src/consts.rs | 14 +- crates/nomi-core/src/game_paths.rs | 51 ++++++- crates/nomi-core/src/instance/launch.rs | 81 +++++----- .../src/instance/launch/arguments.rs | 36 +++-- crates/nomi-core/src/instance/loader.rs | 14 ++ .../instance/{version_marker.rs => marker.rs} | 4 +- crates/nomi-core/src/instance/mod.rs | 140 +++++++++++++----- crates/nomi-core/src/instance/profile.rs | 52 +++++-- crates/nomi-core/src/loaders/fabric.rs | 4 +- crates/nomi-core/src/loaders/forge.rs | 4 +- crates/nomi-core/src/loaders/vanilla.rs | 4 +- crates/nomi-core/src/maven_data.rs | 12 +- crates/nomi-core/tests/download_test.rs | 10 +- crates/nomi-core/tests/fabric_test.rs | 22 +-- crates/nomi-core/tests/forge_new_test.rs | 30 ++-- crates/nomi-core/tests/forge_old_test.rs | 27 ++-- crates/nomi-core/tests/full_fabric_test.rs | 26 +--- crates/nomi-core/tests/instance_test.rs | 66 +++++++++ crates/nomi-core/tests/vanilla_test.rs | 10 +- 29 files changed, 500 insertions(+), 285 deletions(-) create mode 100644 crates/nomi-core/src/instance/loader.rs rename crates/nomi-core/src/instance/{version_marker.rs => marker.rs} (75%) create mode 100644 crates/nomi-core/tests/instance_test.rs diff --git a/crates/client/src/collections.rs b/crates/client/src/collections.rs index f3b0bb5..b984e3a 100644 --- a/crates/client/src/collections.rs +++ b/crates/client/src/collections.rs @@ -9,7 +9,7 @@ use nomi_modding::modrinth::{ use crate::{ errors_pool::ErrorPoolExt, - views::{ProfilesConfig, SimpleDependency}, + views::{InstancesConfig, SimpleDependency}, }; pub struct FabricDataCollection; @@ -73,7 +73,7 @@ impl<'c> TasksCollection<'c> for JavaCollection { pub struct GameDownloadingCollection; impl<'c> TasksCollection<'c> for GameDownloadingCollection { - type Context = &'c ProfilesConfig; + type Context = &'c InstancesConfig; type Target = Option<()>; @@ -85,7 +85,7 @@ impl<'c> TasksCollection<'c> for GameDownloadingCollection { fn handle(context: Self::Context) -> Handler<'c, Self::Target> { Handler::new(|_| { - context.update_config().report_error(); + context.update_config_sync().report_error(); }) } } @@ -182,7 +182,7 @@ impl<'c> TasksCollection<'c> for DependenciesCollection { pub struct ModsDownloadingCollection; impl<'c> TasksCollection<'c> for ModsDownloadingCollection { - type Context = &'c ProfilesConfig; + type Context = &'c InstancesConfig; type Target = Option<()>; @@ -194,7 +194,7 @@ impl<'c> TasksCollection<'c> for ModsDownloadingCollection { fn handle(context: Self::Context) -> Handler<'c, Self::Target> { Handler::new(|_| { - context.update_config().report_error(); + context.update_config_sync().report_error(); }) } } @@ -220,7 +220,7 @@ impl<'c> TasksCollection<'c> for GameRunnerCollection { pub struct DownloadAddedModsCollection; impl<'c> TasksCollection<'c> for DownloadAddedModsCollection { - type Context = (&'c mut HashSet, &'c ProfilesConfig); + type Context = (&'c mut HashSet, &'c InstancesConfig); type Target = ProjectId; @@ -233,7 +233,7 @@ impl<'c> TasksCollection<'c> for DownloadAddedModsCollection { fn handle(context: Self::Context) -> Handler<'c, Self::Target> { Handler::new(|id| { context.0.remove(&id); - context.1.update_config().report_error(); + context.1.update_config_sync().report_error(); }) } } diff --git a/crates/client/src/context.rs b/crates/client/src/context.rs index 3b452dd..6e064e8 100644 --- a/crates/client/src/context.rs +++ b/crates/client/src/context.rs @@ -69,11 +69,11 @@ impl TabViewer for MyContext { match &tab.kind { TabKind::Mods { profile } => { let profile = profile.read(); - self.states.profiles.profiles.find_profile(profile.profile.id).is_none() + self.states.profiles.instances.find_instance(profile.profile.id).is_none() } TabKind::ProfileInfo { profile } => { let profile = profile.read(); - self.states.profiles.profiles.find_profile(profile.profile.id).is_none() + self.states.profiles.instances.find_instance(profile.profile.id).is_none() } _ => false, } @@ -117,13 +117,13 @@ impl TabViewer for MyContext { } TabKind::Mods { profile } => ModManager { task_manager: &mut self.manager, - profiles_config: &mut self.states.profiles.profiles, + profiles_config: &mut self.states.profiles.instances, mod_manager_state: &mut self.states.mod_manager, profile: profile.clone(), } .ui(ui), TabKind::ProfileInfo { profile } => ProfileInfo { - profiles: &self.states.profiles.profiles, + profiles: &self.states.profiles.instances, task_manager: &mut self.manager, profile: profile.clone(), tabs_state: &mut self.states.tabs, diff --git a/crates/client/src/download.rs b/crates/client/src/download.rs index 96f76d3..f48d6e5 100644 --- a/crates/client/src/download.rs +++ b/crates/client/src/download.rs @@ -10,7 +10,7 @@ use nomi_core::{ AssetsDownloader, DownloadQueue, }, game_paths::GamePaths, - instance::{launch::LaunchSettings, Instance}, + instance::{launch::LaunchSettings, Profile}, loaders::{ fabric::Fabric, forge::{Forge, ForgeVersion}, @@ -48,46 +48,37 @@ async fn try_download_version(profile: Arc>, progress_shar let game_paths = GamePaths { game: mc_dir.clone(), assets: mc_dir.join("assets"), - version: mc_dir.join("versions").join(version), + profile: mc_dir.join("versions").join(version), libraries: mc_dir.join("libraries"), }; - let builder = Instance::builder() + let builder = Profile::builder() .name(version_profile.name.clone()) .version(version_profile.version().to_string()) .game_paths(game_paths.clone()); let instance = match loader { - Loader::Vanilla => builder.instance(Box::new(Vanilla::new(version_profile.version(), game_paths.clone()).await?)), - Loader::Fabric { version } => builder.instance(Box::new( + Loader::Vanilla => builder.downloader(Box::new(Vanilla::new(version_profile.version(), game_paths.clone()).await?)), + Loader::Fabric { version } => builder.downloader(Box::new( Fabric::new(version_profile.version(), version.as_ref(), game_paths.clone()).await?, )), - Loader::Forge => builder.instance(Box::new( + Loader::Forge => builder.downloader(Box::new( Forge::new(version_profile.version(), ForgeVersion::Recommended, game_paths.clone()).await?, )), } .build(); let settings = LaunchSettings { - assets: instance.game_paths.assets.clone(), - game_dir: instance.game_paths.game.clone(), - java_bin: JavaRunner::default(), - libraries_dir: instance.game_paths.libraries.clone(), - manifest_file: instance.game_paths.version.join(format!("{}.json", &version)), - natives_dir: instance.game_paths.version.join("natives"), - version_jar_file: instance.game_paths.version.join(format!("{}.jar", &version)), + java_runner: None, version: version.to_string(), version_type: version_type.clone(), }; let launch_instance = instance.launch_instance(settings, Some(vec!["-Xms2G".to_string(), "-Xmx4G".to_string()])); - let instance = instance.instance(); - + let instance = instance.downloader(); let io = instance.io(); - let downloader: Box> = instance.into_downloader(); - io.await?; let downloader = DownloadQueue::new().with_downloader_dyn(downloader); diff --git a/crates/client/src/main.rs b/crates/client/src/main.rs index 28c1b60..8761791 100644 --- a/crates/client/src/main.rs +++ b/crates/client/src/main.rs @@ -140,7 +140,7 @@ impl eframe::App for MyTabs { .add_collection::(()) .add_collection::(&mut self.context.states.add_profile_menu_state.fabric_versions) .add_collection::(()) - .add_collection::(&self.context.states.profiles.profiles) + .add_collection::(&self.context.states.profiles.instances) .add_collection::(()) .add_collection::(&mut self.context.states.mod_manager.current_project) .add_collection::(&mut self.context.states.mod_manager.current_versions) @@ -148,11 +148,11 @@ impl eframe::App for MyTabs { &mut self.context.states.mod_manager.current_dependencies, self.context.states.mod_manager.current_project.as_ref().map(|p| &p.id), )) - .add_collection::(&self.context.states.profiles.profiles) + .add_collection::(&self.context.states.profiles.instances) .add_collection::(()) .add_collection::(( &mut self.context.states.profile_info.currently_downloading_mods, - &self.context.states.profiles.profiles, + &self.context.states.profiles.instances, )); ctx.set_pixels_per_point(self.context.states.client_settings.pixels_per_point); diff --git a/crates/client/src/states.rs b/crates/client/src/states.rs index 57547a7..73b5208 100644 --- a/crates/client/src/states.rs +++ b/crates/client/src/states.rs @@ -4,7 +4,7 @@ use egui_task_manager::{Caller, Task, TaskManager}; use nomi_core::{ downloads::{java::JavaDownloader, progress::MappedSender, traits::Downloader}, fs::read_toml_config_sync, - DOT_NOMI_JAVA_DIR, DOT_NOMI_JAVA_EXECUTABLE, DOT_NOMI_PROFILES_CONFIG, DOT_NOMI_SETTINGS_CONFIG, + DOT_NOMI_JAVA_DIR, DOT_NOMI_JAVA_EXECUTABLE, DOT_NOMI_SETTINGS_CONFIG, }; use tracing::info; @@ -15,7 +15,7 @@ use crate::{ add_tab_menu::TabsState, profiles::ProfilesState, settings::{ClientSettingsState, SettingsState}, - AddProfileMenuState, LogsState, ModManagerState, ProfileInfoState, ProfilesConfig, + AddProfileMenuState, InstancesConfig, LogsState, ModManagerState, ProfileInfoState, }, }; @@ -44,7 +44,7 @@ impl Default for States { java: JavaState::new(), profiles: ProfilesState { currently_downloading_profiles: HashSet::new(), - profiles: read_toml_config_sync::(DOT_NOMI_PROFILES_CONFIG).unwrap_or_default(), + instances: read_toml_config_sync::(DOT_NOMI_PROFILES_CONFIG).unwrap_or_default(), }, client_settings: settings.client_settings.clone(), settings, diff --git a/crates/client/src/views/add_profile_menu.rs b/crates/client/src/views/add_profile_menu.rs index a660646..c9c738a 100644 --- a/crates/client/src/views/add_profile_menu.rs +++ b/crates/client/src/views/add_profile_menu.rs @@ -202,8 +202,8 @@ impl View for AddProfileMenu<'_> { ) .clicked() { - self.profiles_state.profiles.add_profile(ModdedProfile::new(VersionProfile { - id: self.profiles_state.profiles.create_id(), + self.profiles_state.instances.add_instance(ModdedProfile::new(VersionProfile { + id: self.profiles_state.instances.next_id(), name: self.menu_state.profile_name_buf.trim_end().to_owned(), state: ProfileState::NotDownloaded { // PANICS: It will never panic because it's @@ -213,7 +213,7 @@ impl View for AddProfileMenu<'_> { version_type: self.menu_state.selected_version_type.clone(), }, })); - self.profiles_state.profiles.update_config().report_error(); + self.profiles_state.instances.update_config_sync().report_error(); } } } diff --git a/crates/client/src/views/mods_manager.rs b/crates/client/src/views/mods_manager.rs index cde4b30..95ac04e 100644 --- a/crates/client/src/views/mods_manager.rs +++ b/crates/client/src/views/mods_manager.rs @@ -28,13 +28,13 @@ use crate::{ DOT_NOMI_MODS_STASH_DIR, }; -use super::{ModdedProfile, ProfilesConfig, View}; +use super::{InstancesConfig, ModdedProfile, View}; pub use crate::mods::*; pub struct ModManager<'a> { pub task_manager: &'a mut TaskManager, - pub profiles_config: &'a mut ProfilesConfig, + pub profiles_config: &'a mut InstancesConfig, pub profile: Arc>, pub mod_manager_state: &'a mut ModManagerState, } @@ -586,7 +586,7 @@ impl View for ModManager<'_> { let project_type = project.project_type; - let _ = self.profiles_config.update_config().report_error(); + let _ = self.profiles_config.update_config_sync().report_error(); let is_data_pack = self.mod_manager_state.is_datapack; let profile_id = { let lock = profile.read(); diff --git a/crates/client/src/views/profile_info.rs b/crates/client/src/views/profile_info.rs index 07dcca6..6467730 100644 --- a/crates/client/src/views/profile_info.rs +++ b/crates/client/src/views/profile_info.rs @@ -7,14 +7,14 @@ use nomi_modding::modrinth::project::ProjectId; use parking_lot::RwLock; use crate::{ - collections::DownloadAddedModsCollection, errors_pool::ErrorPoolExt, open_directory::open_directory_native, ui_ext::UiExt, views::ProfilesConfig, - TabKind, DOT_NOMI_MODS_STASH_DIR, + collections::DownloadAddedModsCollection, errors_pool::ErrorPoolExt, open_directory::open_directory_native, ui_ext::UiExt, + views::InstancesConfig, TabKind, DOT_NOMI_MODS_STASH_DIR, }; use super::{download_added_mod, Mod, ModdedProfile, TabsState, View}; pub struct ProfileInfo<'a> { - pub profiles: &'a ProfilesConfig, + pub profiles: &'a InstancesConfig, pub task_manager: &'a mut TaskManager, pub profile: Arc>, pub tabs_state: &'a mut TabsState, @@ -273,7 +273,7 @@ impl View for ProfileInfo<'_> { } { - self.profiles.update_config().report_error(); + self.profiles.update_config_sync().report_error(); } self.profile_info_state.mods_to_import.clear(); @@ -377,7 +377,7 @@ impl View for ProfileInfo<'_> { } } - self.profiles.update_config().report_error(); + self.profiles.update_config_sync().report_error(); } if ui.button("Reset").clicked() { @@ -476,7 +476,7 @@ impl View for ProfileInfo<'_> { vec.retain(|m| !mods_to_remove.contains(&m.project_id)); if !mods_to_remove.is_empty() { - self.profiles.update_config().report_error(); + self.profiles.update_config_sync().report_error(); } let _ = std::mem::replace(&mut self.profile.write().mods.mods, vec); diff --git a/crates/client/src/views/profiles.rs b/crates/client/src/views/profiles.rs index e8988b2..62ff3e0 100644 --- a/crates/client/src/views/profiles.rs +++ b/crates/client/src/views/profiles.rs @@ -4,15 +4,16 @@ use std::{ sync::Arc, }; +use anyhow::bail; use eframe::egui::{self, popup_below_widget, Align2, Button, Id, PopupCloseBehavior, TextWrapMode, Ui}; use egui_extras::{Column, TableBuilder}; use egui_task_manager::{Caller, Task, TaskManager}; +use itertools::Itertools; use nomi_core::{ configs::profile::{Loader, ProfileState, VersionProfile}, fs::{read_toml_config, read_toml_config_sync, write_toml_config, write_toml_config_sync}, - instance::launch::arguments::UserData, + instance::{launch::arguments::UserData, load_instances, Instance, InstanceProfileId}, repository::{launcher_manifest::LauncherManifest, username::Username}, - DOT_NOMI_PROFILES_CONFIG, }; use parking_lot::RwLock; use serde::{Deserialize, Serialize}; @@ -49,20 +50,13 @@ pub struct ProfilesPage<'a> { } pub struct ProfilesState { - pub currently_downloading_profiles: HashSet, - pub profiles: ProfilesConfig, -} - -#[derive(Default, PartialEq, Eq, Hash, Clone)] -pub struct SimpleProfile { - pub name: String, - pub version: String, - pub loader: Loader, + pub currently_downloading_profiles: HashSet, + pub instances: InstancesConfig, } #[derive(Serialize, Deserialize, Default)] -pub struct ProfilesConfig { - pub profiles: Vec>>, +pub struct InstancesConfig { + pub instances: Vec>>, } #[derive(Serialize, Deserialize, PartialEq, Eq, Hash)] @@ -80,40 +74,57 @@ impl ModdedProfile { } } -impl ProfilesConfig { - pub fn find_profile(&self, target_id: usize) -> Option<&Arc>> { - self.profiles.iter().find(|p| p.read().profile.id == target_id) +impl InstancesConfig { + pub fn find_instance(&self, id: usize) -> Option>> { + self.instances.iter().find(|p| p.read().id() == id).cloned() } - pub fn read() -> Self { - read_toml_config_sync::(DOT_NOMI_PROFILES_CONFIG).unwrap_or_default() + pub fn load() -> Self { + Self { + instances: load_instances() + .unwrap_or_default() + .into_iter() + .map(RwLock::new) + .map(Arc::new) + .collect_vec(), + } } - pub async fn read_async() -> Self { - read_toml_config::(DOT_NOMI_PROFILES_CONFIG).await.unwrap_or_default() + pub async fn load_async() -> anyhow::Result { + tokio::task::spawn_blocking(Self::load).await.map_err(Into::into) } - pub fn try_read() -> anyhow::Result { - read_toml_config_sync::(DOT_NOMI_PROFILES_CONFIG) + pub fn add_instance(&mut self, instance: Instance) { + self.instances.push(RwLock::new(instance).into()) } - pub fn add_profile(&mut self, profile: ModdedProfile) { - self.profiles.push(RwLock::new(profile).into()) + pub fn next_id(&self) -> usize { + match &self.instances.iter().map(|instance| instance.read().id()).max() { + Some(id) => id + 1, + None => 0, + } } - pub fn create_id(&self) -> usize { - match &self.profiles.iter().max_by_key(|profile| profile.read().profile.id) { - Some(v) => v.read().profile.id + 1, - None => 0, + pub async fn update_all_configs(&self) { + for instance in self.instances.iter() { + let instance = instance.read(); + instance.write().await.report_error(); } } - pub fn update_config(&self) -> anyhow::Result<()> { - write_toml_config_sync(&self, DOT_NOMI_PROFILES_CONFIG) + pub fn update_config_sync(&self, id: usize) -> anyhow::Result<()> { + let runtime = tokio::runtime::Builder::new_current_thread().build()?; + runtime.block_on(self.update_config(id)) } - pub async fn update_config_async(&self) -> anyhow::Result<()> { - write_toml_config(&self, DOT_NOMI_PROFILES_CONFIG).await + pub async fn update_config(&self, id: usize) -> anyhow::Result<()> { + let Some(instance) = self.find_instance(id) else { + bail!("No such instance") + }; + + let instance = instance.read(); + + instance.write().await } } @@ -159,7 +170,7 @@ impl View for ProfilesPage<'_> { .body(|mut body| { let mut is_deleting = vec![]; - for (index, profile_lock) in self.profiles_state.profiles.profiles.iter().enumerate() { + for (index, profile_lock) in self.profiles_state.instances.instances.iter().enumerate() { body.row(30.0, |mut row| { let profile = profile_lock.read(); row.col(|ui| { @@ -319,8 +330,8 @@ impl View for ProfilesPage<'_> { } is_deleting.drain(..).for_each(|index| { - self.profiles_state.profiles.profiles.remove(index); - self.profiles_state.profiles.update_config().report_error(); + self.profiles_state.instances.instances.remove(index); + self.profiles_state.instances.update_config_sync().report_error(); }); }); } diff --git a/crates/nomi-core/src/configs/profile.rs b/crates/nomi-core/src/configs/profile.rs index 8ea9727..08959fb 100644 --- a/crates/nomi-core/src/configs/profile.rs +++ b/crates/nomi-core/src/configs/profile.rs @@ -5,9 +5,11 @@ use serde::{Deserialize, Serialize}; use typed_builder::TypedBuilder; use crate::{ + game_paths::GamePaths, instance::{ launch::{arguments::UserData, LaunchInstance}, logs::GameLogsWriter, + InstanceProfileId, }, repository::{java_runner::JavaRunner, manifest::VersionType}, }; @@ -73,16 +75,22 @@ impl ProfileState { #[derive(Serialize, Deserialize, Debug, TypedBuilder, Clone, PartialEq, Eq, Hash)] pub struct VersionProfile { - pub id: usize, + pub id: InstanceProfileId, pub name: String, pub state: ProfileState, } impl VersionProfile { - pub async fn launch(&self, user_data: UserData, java_runner: &JavaRunner, logs_writer: &dyn GameLogsWriter) -> anyhow::Result<()> { + pub async fn launch( + &self, + paths: GamePaths, + user_data: UserData, + java_runner: &JavaRunner, + logs_writer: &dyn GameLogsWriter, + ) -> anyhow::Result<()> { match &self.state { - ProfileState::Downloaded(instance) => instance.launch(user_data, java_runner, logs_writer).await, + ProfileState::Downloaded(instance) => instance.launch(paths, user_data, java_runner, logs_writer).await, ProfileState::NotDownloaded { .. } => Err(anyhow!("This profile is not downloaded!")), } } diff --git a/crates/nomi-core/src/consts.rs b/crates/nomi-core/src/consts.rs index f67a141..ced8a96 100644 --- a/crates/nomi-core/src/consts.rs +++ b/crates/nomi-core/src/consts.rs @@ -1,7 +1,6 @@ pub const DOT_NOMI_DIR: &str = "./.nomi"; pub const DOT_NOMI_TEMP_DIR: &str = "./.nomi/temp"; pub const DOT_NOMI_CONFIGS_DIR: &str = "./.nomi/configs"; -pub const DOT_NOMI_PROFILES_CONFIG: &str = "./.nomi/configs/Profiles.toml"; pub const DOT_NOMI_SETTINGS_CONFIG: &str = "./.nomi/configs/Settings.toml"; pub const DOT_NOMI_LOGS_DIR: &str = "./.nomi/logs"; pub const DOT_NOMI_JAVA_DIR: &str = "./.nomi/java"; @@ -9,6 +8,19 @@ pub const DOT_NOMI_JAVA_EXECUTABLE: &str = "./.nomi/java/jdk-22.0.1/bin/java"; pub const DOT_NOMI_DATA_PACKS_DIR: &str = "./.nomi/datapacks"; pub const MINECRAFT_DIR: &str = "./minecraft"; +pub const LIBRARIES_DIR: &str = "./libraries"; +pub const ASSETS_DIR: &str = "./assets"; + +pub const INSTANCES_DIR: &str = "./instances"; +/// Path to instance's config file with respect to instance's directory. +/// +/// # Example +/// +/// ```rust +/// # use std::path::Path; +/// Path::new("./instances/example").join(INSTANCE_CONFIG) +/// ``` +pub const INSTANCE_CONFIG: &str = ".nomi/Instance.toml"; pub const NOMI_VERSION: &str = "0.2.0"; pub const NOMI_NAME: &str = "Nomi"; diff --git a/crates/nomi-core/src/game_paths.rs b/crates/nomi-core/src/game_paths.rs index c0a0283..eb2fe3a 100644 --- a/crates/nomi-core/src/game_paths.rs +++ b/crates/nomi-core/src/game_paths.rs @@ -1,21 +1,64 @@ -use std::path::PathBuf; +use std::path::{Path, PathBuf}; -use crate::MINECRAFT_DIR; +use crate::{ASSETS_DIR, LIBRARIES_DIR, MINECRAFT_DIR}; #[derive(Debug, Clone)] pub struct GamePaths { pub game: PathBuf, pub assets: PathBuf, - pub version: PathBuf, + pub profile: PathBuf, pub libraries: PathBuf, } +impl GamePaths { + pub fn from_instance_path(instance: impl AsRef, game_version: &str) -> Self { + let path = instance.as_ref(); + + Self { + game: path.to_path_buf(), + assets: ASSETS_DIR.into(), + // Is this a good approach? + profile: path.join("profiles").join(game_version), + libraries: LIBRARIES_DIR.into(), + } + } + + pub fn make_absolute(self) -> anyhow::Result { + let current_dir = std::env::current_dir()?; + + let make_path_absolute = |path: PathBuf| if path.is_absolute() { path } else { current_dir.join(path) }; + + Ok(Self { + game: make_path_absolute(self.game), + assets: make_path_absolute(self.assets), + profile: make_path_absolute(self.profile), + libraries: make_path_absolute(self.libraries), + }) + } + + pub fn profile(&self) -> PathBuf { + self.profile.join("Profile.toml") + } + + pub fn manifest_file(&self, game_version: &str) -> PathBuf { + self.profile.join(format!("{game_version}.json")) + } + + pub fn natives_dir(&self) -> PathBuf { + self.profile.join("natives") + } + + pub fn version_jar_file(&self, game_version: &str) -> PathBuf { + self.profile.join(format!("{game_version}.jar")) + } +} + impl Default for GamePaths { fn default() -> Self { Self { game: MINECRAFT_DIR.into(), assets: PathBuf::from(MINECRAFT_DIR).join("assets"), - version: PathBuf::from(MINECRAFT_DIR).join("versions").join("NOMI_DEFAULT"), + profile: PathBuf::from(MINECRAFT_DIR).join("versions").join("NOMI_DEFAULT"), libraries: PathBuf::from(MINECRAFT_DIR).join("libraries"), } } diff --git a/crates/nomi-core/src/instance/launch.rs b/crates/nomi-core/src/instance/launch.rs index ba73dd1..98a2d26 100644 --- a/crates/nomi-core/src/instance/launch.rs +++ b/crates/nomi-core/src/instance/launch.rs @@ -15,6 +15,7 @@ use tracing::{debug, error, info, trace, warn}; use crate::{ downloads::Assets, fs::read_json_config, + game_paths::GamePaths, markers::Undefined, repository::{ java_runner::JavaRunner, @@ -25,8 +26,8 @@ use crate::{ use self::arguments::ArgumentsBuilder; use super::{ + loader::LoaderProfile, logs::{GameLogsEvent, GameLogsWriter}, - profile::LoaderProfile, }; pub mod arguments; @@ -38,16 +39,9 @@ pub const CLASSPATH_SEPARATOR: &str = ";"; #[cfg(not(windows))] pub const CLASSPATH_SEPARATOR: &str = ":"; -#[derive(Serialize, Deserialize, Default, PartialEq, Eq, Debug, Clone, Hash)] +#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq, Hash)] pub struct LaunchSettings { - pub assets: PathBuf, - pub java_bin: JavaRunner, - pub game_dir: PathBuf, - pub libraries_dir: PathBuf, - pub manifest_file: PathBuf, - pub natives_dir: PathBuf, - pub version_jar_file: PathBuf, - + pub java_runner: Option, pub version: String, pub version_type: VersionType, } @@ -61,18 +55,19 @@ pub struct LaunchInstance { impl LaunchInstance { #[tracing::instrument(skip(self), err)] - pub async fn delete(&self, delete_client: bool, delete_libraries: bool, delete_assets: bool) -> anyhow::Result<()> { - let manifest = read_json_config::(&self.settings.manifest_file).await?; - let arguments_builder = ArgumentsBuilder::new(self, &manifest).with_classpath(); + pub async fn delete(&self, paths: GamePaths, delete_client: bool, delete_libraries: bool, delete_assets: bool) -> anyhow::Result<()> { + let manifest = read_json_config::(paths.manifest_file(&self.settings.version)).await?; + let arguments_builder = ArgumentsBuilder::new(&paths, self, &manifest).build_classpath(); if delete_client { - let _ = tokio::fs::remove_file(&self.settings.version_jar_file) + let path = paths.version_jar_file(&self.settings.version); + let _ = tokio::fs::remove_file(&path) .await .inspect(|()| { - debug!("Removed client successfully: {}", &self.settings.version_jar_file.display()); + debug!("Removed client successfully: {}", &path.display()); }) .inspect_err(|_| { - warn!("Cannot remove client: {}", &self.settings.version_jar_file.display()); + warn!("Cannot remove client: {}", &path.display()); }); } @@ -86,16 +81,11 @@ impl LaunchInstance { } if delete_assets { - let assets = read_json_config::(dbg!(&self - .settings - .assets - .join("indexes") - .join(format!("{}.json", manifest.asset_index.id)))) - .await?; + let assets = read_json_config::(dbg!(paths.assets.join("indexes").join(format!("{}.json", manifest.asset_index.id)))).await?; for asset in assets.objects.values() { - let path = &self.settings.assets.join("objects").join(&asset.hash[0..2]).join(&asset.hash); + let path = paths.assets.join("objects").join(&asset.hash[0..2]).join(&asset.hash); - let _ = tokio::fs::remove_file(path) + let _ = tokio::fs::remove_file(&path) .await .inspect(|()| trace!("Removed asset successfully: {}", path.display())) .inspect_err(|e| warn!("Cannot remove asset: {}. Error: {e}", path.display())); @@ -117,10 +107,10 @@ impl LaunchInstance { &mut self.jvm_args } - fn process_natives(&self, natives: &[PathBuf]) -> anyhow::Result<()> { + fn process_natives(natives_dir: &Path, natives: &[PathBuf]) -> anyhow::Result<()> { for lib in natives { let reader = OpenOptions::new().read(true).open(lib)?; - std::fs::create_dir_all(&self.settings.natives_dir)?; + std::fs::create_dir_all(natives_dir)?; let mut archive = zip::ZipArchive::new(reader)?; @@ -138,7 +128,7 @@ impl LaunchInstance { }) .try_for_each(|lib| { let mut file = archive.by_name(&lib)?; - let mut out = File::create(self.settings.natives_dir.join(lib))?; + let mut out = File::create(natives_dir.join(lib))?; io::copy(&mut file, &mut out)?; Ok::<_, anyhow::Error>(()) @@ -149,12 +139,22 @@ impl LaunchInstance { } #[tracing::instrument(skip(self, logs_writer), err)] - pub async fn launch(&self, user_data: UserData, java_runner: &JavaRunner, logs_writer: &dyn GameLogsWriter) -> anyhow::Result<()> { - let manifest = read_json_config::(&self.settings.manifest_file).await?; + pub async fn launch( + &self, + paths: GamePaths, + user_data: UserData, + java_runner: &JavaRunner, + logs_writer: &dyn GameLogsWriter, + ) -> anyhow::Result<()> { + let paths = paths.make_absolute()?; + + let manifest = read_json_config::(paths.manifest_file(&self.settings.version)).await?; - let arguments_builder = ArgumentsBuilder::new(self, &manifest).with_classpath().with_userdata(user_data); + let arguments_builder = ArgumentsBuilder::new(&paths, self, &manifest).build_classpath().with_userdata(user_data); - self.process_natives(arguments_builder.get_native_libs())?; + let natives_dir = paths.natives_dir(); + let native_libs = arguments_builder.get_native_libs().to_vec(); + tokio::task::spawn_blocking(move || Self::process_natives(&natives_dir, &native_libs)).await??; let custom_jvm_arguments = arguments_builder.custom_jvm_arguments(); let manifest_jvm_arguments = arguments_builder.manifest_jvm_arguments(); @@ -170,30 +170,21 @@ impl LaunchInstance { let loader_game_arguments = loader_arguments.game_arguments(); let mut command = Command::new(java_runner.get()); - let command = command + let command = dbg!(command .args(custom_jvm_arguments) .args(loader_jvm_arguments) .args(manifest_jvm_arguments) .arg(main_class) .args(manifest_game_arguments) .args(loader_game_arguments) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()); - // Works incorrectly so let's ignore it for now. - // It will work when the instances are implemented. - // .current_dir(std::fs::canonicalize(MINECRAFT_DIR)?) - - // if matches!(manifest.arguments, Arguments::Old(_)) { - // let mut cp = arguments_builder.classpath_as_str().to_string(); - // cp.push_str(CLASSPATH_SEPARATOR); - // cp.push_str("./.nomi/launchwrapper-1.12.jar"); - // command.env("CLASSPATH", cp); - // } + .current_dir(&paths.game)) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()); let mut child = command.spawn()?; let stdout = child.stdout.take().expect("child did not have a handle to stdout"); - let stderr = child.stderr.take().expect("child did not have a handle to stdout"); + let stderr = child.stderr.take().expect("child did not have a handle to stderr"); // let mut stdout_reader = BufReader::new(stdout).lines(); // let mut stderr_reader = BufReader::new(stderr).lines(); diff --git a/crates/nomi-core/src/instance/launch/arguments.rs b/crates/nomi-core/src/instance/launch/arguments.rs index ccfa2f0..9f845de 100644 --- a/crates/nomi-core/src/instance/launch/arguments.rs +++ b/crates/nomi-core/src/instance/launch/arguments.rs @@ -4,9 +4,10 @@ use itertools::Itertools; use tracing::info; use crate::{ + game_paths::GamePaths, instance::{ launch::{macros::replace, rules::is_library_passes}, - profile::LoaderProfile, + loader::LoaderProfile, }, markers::Undefined, maven_data::MavenArtifact, @@ -26,6 +27,7 @@ pub enum WithClasspath {} pub struct ArgumentsBuilder<'a, S = Undefined, U = Undefined> { instance: &'a LaunchInstance, manifest: &'a Manifest, + paths: &'a GamePaths, classpath: Vec, classpath_string: String, native_libs: Vec, @@ -65,10 +67,11 @@ impl<'a, 'b> LoaderArguments<'a, 'b> { } impl<'a> ArgumentsBuilder<'a, Undefined, Undefined> { - pub fn new(instance: &'a LaunchInstance, manifest: &'a Manifest) -> ArgumentsBuilder<'a, Undefined, Undefined> { + pub fn new(paths: &'a GamePaths, instance: &'a LaunchInstance, manifest: &'a Manifest) -> ArgumentsBuilder<'a, Undefined, Undefined> { ArgumentsBuilder { instance, manifest, + paths, classpath: Vec::new(), classpath_string: String::new(), native_libs: Vec::new(), @@ -80,11 +83,12 @@ impl<'a> ArgumentsBuilder<'a, Undefined, Undefined> { } impl<'a, U> ArgumentsBuilder<'a, Undefined, U> { - pub fn with_classpath(self) -> ArgumentsBuilder<'a, WithClasspath, U> { + pub fn build_classpath(self) -> ArgumentsBuilder<'a, WithClasspath, U> { let (classpath, native_libs) = self.classpath(); ArgumentsBuilder { instance: self.instance, manifest: self.manifest, + paths: self.paths, user_data: self.user_data, classpath_string: itertools::intersperse(classpath.iter().map(|p| p.display().to_string()), CLASSPATH_SEPARATOR.to_string()) .collect::(), @@ -101,6 +105,7 @@ impl<'a, S> ArgumentsBuilder<'a, S, Undefined> { ArgumentsBuilder { instance: self.instance, manifest: self.manifest, + paths: self.paths, user_data, classpath_string: self.classpath_string, classpath: self.classpath, @@ -148,10 +153,13 @@ impl<'a> ArgumentsBuilder<'a, WithClasspath, WithUserData> { |JvmArguments(jvm), _| jvm.clone(), |_| { vec![ - format!("-Djava.library.path={}", &self.instance.settings.natives_dir.display()), + format!("-Djava.library.path={}", self.paths.natives_dir().display()), "-Dminecraft.launcher.brand=${launcher_name}".into(), "-Dminecraft.launcher.version=${launcher_version}".into(), - format!("-Dminecraft.client.jar={}", &self.instance.settings.version_jar_file.display()), + format!( + "-Dminecraft.client.jar={}", + self.paths.version_jar_file(&self.instance.settings.version).display() + ), "-cp".to_string(), self.classpath_as_str().to_owned(), ] @@ -168,11 +176,11 @@ impl<'a> ArgumentsBuilder<'a, WithClasspath, WithUserData> { fn parse_args_from_str(&self, source: &str) -> String { replace!(source, - "${assets_root}" => &path_to_string(&self.instance.settings.assets), - "${game_assets}" => &path_to_string(&self.instance.settings.assets), - "${game_directory}" => &path_to_string(&self.instance.settings.game_dir), - "${natives_directory}" => &path_to_string(&self.instance.settings.natives_dir), - "${library_directory}" => &path_to_string(&self.instance.settings.libraries_dir), + "${assets_root}" => &path_to_string(&self.paths.assets), + "${game_assets}" => &path_to_string(&self.paths.assets), + "${game_directory}" => &path_to_string(&self.paths.game), + "${natives_directory}" => &path_to_string(self.paths.natives_dir()), + "${library_directory}" => &path_to_string(&self.paths.libraries), "${launcher_name}" => NOMI_NAME, "${launcher_version}" => NOMI_VERSION, "${auth_access_token}" => self.user_data @@ -239,7 +247,7 @@ impl<'a, S, U> ArgumentsBuilder<'a, S, U> { } } - let mut classpath = vec![Some(self.instance.settings.version_jar_file.clone())]; + let mut classpath = vec![Some(self.paths.version_jar_file(&self.instance.settings.version))]; let mut native_libs = vec![]; self.manifest @@ -268,13 +276,13 @@ impl<'a, S, U> ArgumentsBuilder<'a, S, U> { }) }) .and_then(|artifact| artifact.path.as_ref()) - .map(|path| self.instance.settings.libraries_dir.join(path)), + .map(|path| self.paths.libraries.join(path)), lib.downloads .classifiers .as_ref() .and_then(|natives| match_natives(natives)) .and_then(|native_lib| native_lib.path.as_ref()) - .map(|path| self.instance.settings.libraries_dir.join(path)), + .map(|path| self.paths.libraries.join(path)), ) }) .for_each(|(lib, native)| { @@ -291,7 +299,7 @@ impl<'a, S, U> ArgumentsBuilder<'a, S, U> { .loader_profile .as_ref() .map(|p| &p.libraries) - .map(|libs| libs.iter().map(|lib| self.instance.settings.libraries_dir.join(&lib.jar))) + .map(|libs| libs.iter().map(|lib| self.paths.libraries.join(&lib.jar))) { classpath.extend(libs); } diff --git a/crates/nomi-core/src/instance/loader.rs b/crates/nomi-core/src/instance/loader.rs new file mode 100644 index 0000000..91c046e --- /dev/null +++ b/crates/nomi-core/src/instance/loader.rs @@ -0,0 +1,14 @@ +use serde::{Deserialize, Serialize}; + +use crate::{ + configs::profile::Loader, + repository::{simple_args::SimpleArgs, simple_lib::SimpleLib}, +}; + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)] +pub struct LoaderProfile { + pub loader: Loader, + pub main_class: String, + pub args: SimpleArgs, + pub libraries: Vec, +} diff --git a/crates/nomi-core/src/instance/version_marker.rs b/crates/nomi-core/src/instance/marker.rs similarity index 75% rename from crates/nomi-core/src/instance/version_marker.rs rename to crates/nomi-core/src/instance/marker.rs index 9759d5f..ef3e327 100644 --- a/crates/nomi-core/src/instance/version_marker.rs +++ b/crates/nomi-core/src/instance/marker.rs @@ -4,11 +4,11 @@ use crate::downloads::traits::{DownloadResult, Downloader}; use super::builder_ext::LaunchInstanceBuilderExt; -pub trait Version: LaunchInstanceBuilderExt + Downloader + Debug + Send + Sync { +pub trait ProfileDownloader: LaunchInstanceBuilderExt + Downloader + Debug + Send + Sync { fn into_downloader(self: Box) -> Box>; } -impl Version for T +impl ProfileDownloader for T where T: LaunchInstanceBuilderExt + Downloader + Debug + Send + Sync + 'static, { diff --git a/crates/nomi-core/src/instance/mod.rs b/crates/nomi-core/src/instance/mod.rs index f1fa6c6..68cdf10 100644 --- a/crates/nomi-core/src/instance/mod.rs +++ b/crates/nomi-core/src/instance/mod.rs @@ -1,52 +1,126 @@ -use typed_builder::TypedBuilder; - pub mod builder_ext; pub mod launch; +pub mod loader; pub mod logs; -pub mod profile; -pub mod version_marker; +pub mod marker; +mod profile; + +use std::path::{Path, PathBuf}; -use crate::{downloads::downloaders::assets::AssetsDownloader, game_paths::GamePaths, state::get_launcher_manifest}; +pub use profile::*; +use serde::{Deserialize, Serialize}; +use tracing::error; -use self::{ - launch::{LaunchInstance, LaunchInstanceBuilder, LaunchSettings}, - version_marker::Version, +use crate::{ + configs::profile::{Loader, VersionProfile}, + fs::{read_toml_config_sync, write_toml_config}, + INSTANCES_DIR, INSTANCE_CONFIG, }; -#[derive(Debug, TypedBuilder)] +/// Loads all instances in the [`INSTANCES_DIR`](crate::consts::INSTANCES_DIR) +pub fn load_instances() -> anyhow::Result> { + let dir = std::fs::read_dir(INSTANCES_DIR)?; + + let mut instances = Vec::new(); + + for entry in dir { + let Ok(entry) = entry.inspect_err(|error| error!(%error, "Cannot read instance directory")) else { + continue; + }; + + if !entry.path().is_dir() { + continue; + } + + let path = entry.path().join(INSTANCE_CONFIG); + + let instance = read_toml_config_sync::(path)?; + + instances.push(instance); + } + + Ok(instances) +} + +#[derive(Debug, Serialize, Deserialize)] pub struct Instance { - instance: Box, - pub game_paths: GamePaths, - pub version: String, - pub name: String, + name: String, + id: usize, + main_profile: Option, + profiles: Vec, } impl Instance { - pub fn instance(self) -> Box { - self.instance + pub fn new(name: impl Into, id: usize) -> Self { + Self { + name: name.into(), + id, + main_profile: None, + profiles: Vec::new(), + } } - pub async fn assets(&self) -> anyhow::Result { - let manifest = get_launcher_manifest().await?; - let version_manifest = manifest.get_version_manifest(&self.version).await?; + pub fn set_main_profile(&mut self, main_profile_id: InstanceProfileId) { + self.main_profile = Some(main_profile_id); + } - AssetsDownloader::new( - version_manifest.asset_index.url, - version_manifest.asset_index.id, - self.game_paths.assets.join("objects"), - self.game_paths.assets.join("indexes"), - ) - .await + pub fn add_profile(&mut self, payload: ProfilePayload) { + self.profiles.push(payload); } - #[must_use] - pub fn launch_instance(&self, settings: LaunchSettings, jvm_args: Option>) -> LaunchInstance { - let builder = LaunchInstanceBuilder::new().settings(settings); - let builder = match jvm_args { - Some(jvm) => builder.jvm_args(jvm), - None => builder, - }; + /// Generate id for the next profile in this instance + pub fn next_id(&self) -> InstanceProfileId { + match &self.profiles.iter().max_by_key(|profile| profile.id.1) { + Some(profile) => InstanceProfileId::new(profile.id.0, profile.id.1 + 1), + None => InstanceProfileId::new(self.id, 0), + } + } + + pub fn id(&self) -> usize { + self.id + } + + pub async fn write(&self) -> anyhow::Result<()> { + write_toml_config(&self, PathBuf::from(INSTANCES_DIR).join(&self.name).join(".nomi/Instance.toml")).await + } + + pub fn path(&self) -> PathBuf { + PathBuf::from(INSTANCES_DIR).join(&self.name) + } +} + +/// Represent a unique identifier of a profile. +/// +/// First number is the instance id and the second number is the profile id. +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)] +pub struct InstanceProfileId(usize, usize); + +impl InstanceProfileId { + pub const ZERO: Self = Self(0, 0); + + pub fn new(instance: usize, profile: usize) -> Self { + Self(instance, profile) + } +} + +/// Information about profile. +#[derive(Debug, Serialize, Deserialize)] +pub struct ProfilePayload { + pub id: InstanceProfileId, + pub name: String, + pub loader: Loader, + pub version: String, + pub path: PathBuf, +} - self.instance.insert(builder).build() +impl ProfilePayload { + pub fn from_version_profile(profile: &VersionProfile, path: &Path) -> Self { + Self { + id: profile.id.clone(), + name: profile.name.clone(), + loader: profile.loader().clone(), + version: profile.version().to_owned(), + path: path.to_path_buf(), + } } } diff --git a/crates/nomi-core/src/instance/profile.rs b/crates/nomi-core/src/instance/profile.rs index 91c046e..e1c9143 100644 --- a/crates/nomi-core/src/instance/profile.rs +++ b/crates/nomi-core/src/instance/profile.rs @@ -1,14 +1,46 @@ -use serde::{Deserialize, Serialize}; +use typed_builder::TypedBuilder; -use crate::{ - configs::profile::Loader, - repository::{simple_args::SimpleArgs, simple_lib::SimpleLib}, +use crate::{downloads::downloaders::assets::AssetsDownloader, game_paths::GamePaths, state::get_launcher_manifest}; + +use super::{ + launch::{LaunchInstance, LaunchInstanceBuilder, LaunchSettings}, + marker::ProfileDownloader, }; -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)] -pub struct LoaderProfile { - pub loader: Loader, - pub main_class: String, - pub args: SimpleArgs, - pub libraries: Vec, +#[derive(Debug, TypedBuilder)] +pub struct Profile { + downloader: Box, + pub game_paths: GamePaths, + pub version: String, + pub name: String, +} + +impl Profile { + pub fn downloader(self) -> Box { + self.downloader + } + + pub async fn assets(&self) -> anyhow::Result { + let manifest = get_launcher_manifest().await?; + let version_manifest = manifest.get_version_manifest(&self.version).await?; + + AssetsDownloader::new( + version_manifest.asset_index.url, + version_manifest.asset_index.id, + self.game_paths.assets.join("objects"), + self.game_paths.assets.join("indexes"), + ) + .await + } + + #[must_use] + pub fn launch_instance(&self, settings: LaunchSettings, jvm_args: Option>) -> LaunchInstance { + let builder = LaunchInstanceBuilder::new().settings(settings); + let builder = match jvm_args { + Some(jvm) => builder.jvm_args(jvm), + None => builder, + }; + + self.downloader.insert(builder).build() + } } diff --git a/crates/nomi-core/src/loaders/fabric.rs b/crates/nomi-core/src/loaders/fabric.rs index 37e76b4..57d1de0 100644 --- a/crates/nomi-core/src/loaders/fabric.rs +++ b/crates/nomi-core/src/loaders/fabric.rs @@ -18,7 +18,7 @@ use crate::{ instance::{ builder_ext::LaunchInstanceBuilderExt, launch::{LaunchInstanceBuilder, LaunchSettings}, - profile::LoaderProfile, + loader::LoaderProfile, }, maven_data::{MavenArtifact, MavenData}, repository::{ @@ -136,7 +136,7 @@ impl Downloader for Fabric { } fn io(&self) -> PinnedFutureWithBounds> { - let version_path = self.game_paths.version.clone(); + let version_path = self.game_paths.profile.clone(); let profile = self.profile.clone(); let id = self.profile.id.clone(); diff --git a/crates/nomi-core/src/loaders/forge.rs b/crates/nomi-core/src/loaders/forge.rs index a6f5622..0c63add 100644 --- a/crates/nomi-core/src/loaders/forge.rs +++ b/crates/nomi-core/src/loaders/forge.rs @@ -25,7 +25,7 @@ use crate::{ instance::{ builder_ext::LaunchInstanceBuilderExt, launch::{LaunchInstanceBuilder, LaunchSettings, CLASSPATH_SEPARATOR}, - profile::LoaderProfile, + loader::LoaderProfile, }, loaders::vanilla::VanillaLibrariesMapper, maven_data::{MavenArtifact, MavenData}, @@ -422,7 +422,7 @@ impl ProcessorsData { client = "client", server = "" "MINECRAFT_JAR" : - client = game_paths.version.join(format!("{game_version}.jar")).to_string_lossy(), + client = game_paths.profile.join(format!("{game_version}.jar")).to_string_lossy(), server = "" "MINECRAFT_VERSION": client = game_version, diff --git a/crates/nomi-core/src/loaders/vanilla.rs b/crates/nomi-core/src/loaders/vanilla.rs index f80de13..61aeb4c 100644 --- a/crates/nomi-core/src/loaders/vanilla.rs +++ b/crates/nomi-core/src/loaders/vanilla.rs @@ -56,7 +56,7 @@ impl Vanilla { .with_downloader( FileDownloader::new( manifest.downloads.client.url.clone(), - game_paths.version.join(format!("{}.jar", manifest.id)), + game_paths.profile.join(format!("{}.jar", manifest.id)), ) .into_retry(), ); @@ -126,7 +126,7 @@ impl Downloader for Vanilla { } fn io(&self) -> PinnedFutureWithBounds> { - let versions_path = self.game_paths.version.clone(); + let versions_path = self.game_paths.profile.clone(); let manifest_id = self.manifest.id.clone(); let manifest_res = serde_json::to_string_pretty(&self.manifest); diff --git a/crates/nomi-core/src/maven_data.rs b/crates/nomi-core/src/maven_data.rs index 6af9ef6..2350695 100644 --- a/crates/nomi-core/src/maven_data.rs +++ b/crates/nomi-core/src/maven_data.rs @@ -1,4 +1,4 @@ -use std::{fmt::Display, path::PathBuf}; +use std::{fmt::Display, path::PathBuf, sync::LazyLock}; use itertools::Itertools; use regex::Regex; @@ -57,11 +57,13 @@ pub struct MavenArtifact { impl MavenArtifact { #[must_use] - #[allow(clippy::missing_panics_doc)] pub fn new(artifact: &str) -> Self { - // PANICS: This will never panic because the pattern is valid. - let regex = Regex::new(r"(?P[^:]*):(?P[^:]*):(?P[^@:]*)(?::(?P.*))?(?:@(?P.*))?").unwrap(); - regex.captures(artifact).map_or_else( + static REGEX: LazyLock = LazyLock::new(|| { + // PANICS: This will never panic because the pattern is valid. + Regex::new(r"(?P[^:]*):(?P[^:]*):(?P[^@:]*)(?::(?P.*))?(?:@(?P.*))?").unwrap() + }); + + REGEX.captures(artifact).map_or_else( || { error!(artifact, "No values captured. Using provided artifact as a group"); MavenArtifact { diff --git a/crates/nomi-core/tests/download_test.rs b/crates/nomi-core/tests/download_test.rs index c6fd803..110216f 100644 --- a/crates/nomi-core/tests/download_test.rs +++ b/crates/nomi-core/tests/download_test.rs @@ -1,4 +1,4 @@ -use nomi_core::{downloads::traits::Downloader, game_paths::GamePaths, instance::Instance, loaders::vanilla::Vanilla}; +use nomi_core::{downloads::traits::Downloader, game_paths::GamePaths, instance::Profile, loaders::vanilla::Vanilla}; use tracing::Level; #[tokio::test] @@ -11,20 +11,20 @@ async fn download_test() { let game_paths = GamePaths { game: "./minecraft".into(), assets: "./minecraft/assets".into(), - version: ("./minecraft/versions/1.18.2".into()), + profile: ("./minecraft/versions/1.18.2".into()), libraries: "./minecraft/libraries".into(), }; - let instance = Instance::builder() + let instance = Profile::builder() .version("1.18.2".into()) - .instance(Box::new(Vanilla::new("1.18.2", game_paths.clone()).await.unwrap())) + .downloader(Box::new(Vanilla::new("1.18.2", game_paths.clone()).await.unwrap())) .game_paths(game_paths) .name("1.18.2-test".into()) .build(); Box::new(instance.assets().await.unwrap()).download(&tx).await; - let version = instance.instance(); + let version = instance.downloader(); let fut = version.io(); version.download(&tx).await; diff --git a/crates/nomi-core/tests/fabric_test.rs b/crates/nomi-core/tests/fabric_test.rs index a9cffef..0a0efda 100644 --- a/crates/nomi-core/tests/fabric_test.rs +++ b/crates/nomi-core/tests/fabric_test.rs @@ -3,7 +3,7 @@ use nomi_core::{ instance::{ launch::{arguments::UserData, LaunchSettings}, logs::PrintLogs, - Instance, + Profile, }, loaders::fabric::Fabric, repository::java_runner::JavaRunner, @@ -17,14 +17,14 @@ async fn vanilla_test() { let game_paths = GamePaths { game: "./minecraft".into(), assets: "./minecraft/assets".into(), - version: "./minecraft/versions/1.20".into(), + profile: "./minecraft/versions/1.20".into(), libraries: "./minecraft/libraries".into(), }; - let builder = Instance::builder() + let builder = Profile::builder() .version("1.20".into()) .game_paths(game_paths.clone()) - .instance(Box::new(Fabric::new("1.20", None::, game_paths).await.unwrap())) + .downloader(Box::new(Fabric::new("1.20", None::, game_paths.clone()).await.unwrap())) // .instance(Inner::vanilla("1.20").await.unwrap()) .name("1.20-fabric-test".into()) .build(); @@ -34,20 +34,14 @@ async fn vanilla_test() { // _assets.download().await.unwrap(); // builder.download().await.unwrap(); - let mc_dir = std::env::current_dir().unwrap().join("minecraft"); - let settings = LaunchSettings { - assets: mc_dir.join("assets"), - game_dir: mc_dir.clone(), - java_bin: JavaRunner::default(), - libraries_dir: mc_dir.clone().join("libraries"), - manifest_file: mc_dir.clone().join("versions/1.20/1.20.json"), - natives_dir: mc_dir.clone().join("versions/1.20/natives"), - version_jar_file: mc_dir.join("versions/1.20/1.20.jar"), + java_runner: None, version: "1.20".to_string(), version_type: nomi_core::repository::manifest::VersionType::Release, }; let l = builder.launch_instance(settings, None); - l.launch(UserData::default(), &JavaRunner::default(), &PrintLogs).await.unwrap(); + l.launch(game_paths, UserData::default(), &JavaRunner::default(), &PrintLogs) + .await + .unwrap(); } diff --git a/crates/nomi-core/tests/forge_new_test.rs b/crates/nomi-core/tests/forge_new_test.rs index 8ce2682..399ce0f 100644 --- a/crates/nomi-core/tests/forge_new_test.rs +++ b/crates/nomi-core/tests/forge_new_test.rs @@ -6,7 +6,7 @@ use nomi_core::{ instance::{ launch::{arguments::UserData, LaunchSettings}, logs::PrintLogs, - Instance, + InstanceProfileId, Profile, }, loaders::forge::{Forge, ForgeVersion}, repository::java_runner::JavaRunner, @@ -17,25 +17,23 @@ use nomi_core::{ async fn forge_test() { let _guard = tracing::subscriber::set_default(tracing_subscriber::fmt().pretty().finish()); - let current = std::env::current_dir().unwrap(); - let (tx, _) = tokio::sync::mpsc::channel(100); let game_paths = GamePaths { - version: PathBuf::from(MINECRAFT_DIR).join("versions").join("forge-test"), + profile: PathBuf::from(MINECRAFT_DIR).join("versions").join("forge-test"), ..Default::default() }; - let instance = Instance::builder() + let instance = Profile::builder() .name("forge-test".into()) .version("1.20.1".into()) .game_paths(game_paths.clone()) - .instance(Box::new(Forge::new("1.20.1", ForgeVersion::Recommended, game_paths).await.unwrap())) - // .instance(Box::new(Vanilla::new("1.20.1", game_paths.clone()).await.unwrap())) + .downloader(Box::new( + Forge::new("1.20.1", ForgeVersion::Recommended, game_paths.clone()).await.unwrap(), + )) + // .downloader(Box::new(Vanilla::new("1.20.1", game_paths.clone()).await.unwrap())) .build(); - let mc_dir = current.join("minecraft"); - // let vanilla = Box::new(Vanilla::new("1.20.1", game_paths.clone()).await.unwrap()); // let io = vanilla.io(); @@ -44,13 +42,8 @@ async fn forge_test() { // io.await.unwrap(); let settings = LaunchSettings { - assets: mc_dir.join("assets"), - game_dir: mc_dir.clone(), - java_bin: JavaRunner::default(), - libraries_dir: mc_dir.clone().join("libraries"), - manifest_file: mc_dir.clone().join("versions/forge-test/1.20.1.json"), - natives_dir: mc_dir.clone().join("versions/forge-test/natives"), - version_jar_file: mc_dir.join("versions/forge-test/1.20.1.jar"), + java_runner: None, + version: "1.20.1".to_string(), version_type: nomi_core::repository::manifest::VersionType::Release, }; @@ -62,7 +55,7 @@ async fn forge_test() { // Box::new(assets).download(&tx).await; // assets_io.await.unwrap(); - let instance = instance.instance(); + let instance = instance.downloader(); let io_fut = instance.io(); instance.download(&tx).await; @@ -70,13 +63,14 @@ async fn forge_test() { io_fut.await.unwrap(); let profile = VersionProfile::builder() - .id(1) + .id(InstanceProfileId::ZERO) .name("forge-test".into()) .state(ProfileState::downloaded(launch)) .build(); dbg!(profile) .launch( + game_paths, UserData::default(), &JavaRunner::path(PathBuf::from(DOT_NOMI_JAVA_EXECUTABLE)), &PrintLogs, diff --git a/crates/nomi-core/tests/forge_old_test.rs b/crates/nomi-core/tests/forge_old_test.rs index 76dc734..120e821 100644 --- a/crates/nomi-core/tests/forge_old_test.rs +++ b/crates/nomi-core/tests/forge_old_test.rs @@ -7,7 +7,7 @@ use nomi_core::{ instance::{ launch::{arguments::UserData, LaunchSettings}, logs::PrintLogs, - Instance, + InstanceProfileId, Profile, }, loaders::forge::{Forge, ForgeVersion}, repository::java_runner::JavaRunner, @@ -18,25 +18,23 @@ use nomi_core::{ async fn forge_test() { let _guard = tracing::subscriber::set_default(tracing_subscriber::fmt().finish()); - let current = std::env::current_dir().unwrap(); - let (tx, _) = tokio::sync::mpsc::channel(100); let game_paths = GamePaths { - version: PathBuf::from(MINECRAFT_DIR).join("versions").join("forge-test"), + profile: PathBuf::from(MINECRAFT_DIR).join("versions").join("forge-test"), ..Default::default() }; - let instance = Instance::builder() + let instance = Profile::builder() .name("forge-test".into()) .version("1.7.10".into()) .game_paths(game_paths.clone()) - .instance(Box::new(Forge::new("1.7.10", ForgeVersion::Recommended, game_paths).await.unwrap())) + .downloader(Box::new( + Forge::new("1.7.10", ForgeVersion::Recommended, game_paths.clone()).await.unwrap(), + )) // .instance(Box::new(Vanilla::new("1.7.10", game_paths.clone()).await.unwrap())) .build(); - let mc_dir = current.join("minecraft"); - // let vanilla = Box::new(Vanilla::new("1.7.10", game_paths.clone()).await.unwrap()); // let io = vanilla.io(); @@ -45,13 +43,7 @@ async fn forge_test() { // io.await.unwrap(); let settings = LaunchSettings { - assets: mc_dir.join("assets"), - game_dir: mc_dir.clone(), - java_bin: JavaRunner::default(), - libraries_dir: mc_dir.clone().join("libraries"), - manifest_file: mc_dir.clone().join("versions/forge-test/1.7.10.json"), - natives_dir: mc_dir.clone().join("versions/forge-test/natives"), - version_jar_file: mc_dir.join("versions/forge-test/1.7.10.jar"), + java_runner: None, version: "1.7.10".to_string(), version_type: nomi_core::repository::manifest::VersionType::Release, }; @@ -63,7 +55,7 @@ async fn forge_test() { Box::new(assets).download(&tx).await; assets_io.await.unwrap(); - let instance = instance.instance(); + let instance = instance.downloader(); let io_fut = instance.io(); instance.download(&tx).await; @@ -71,13 +63,14 @@ async fn forge_test() { io_fut.await.unwrap(); let profile = VersionProfile::builder() - .id(1) + .id(InstanceProfileId::ZERO) .name("forge-test".into()) .state(ProfileState::downloaded(launch)) .build(); dbg!(profile) .launch( + game_paths, UserData::default(), &JavaRunner::path(PathBuf::from( "E:/programming/code/nomi/crates/nomi-core/.nomi/java/jdk8u422-b05/bin/javaw.exe", diff --git a/crates/nomi-core/tests/full_fabric_test.rs b/crates/nomi-core/tests/full_fabric_test.rs index c5ad768..85c9179 100644 --- a/crates/nomi-core/tests/full_fabric_test.rs +++ b/crates/nomi-core/tests/full_fabric_test.rs @@ -5,7 +5,7 @@ use nomi_core::{ instance::{ launch::{arguments::UserData, LaunchSettings}, logs::PrintLogs, - Instance, + InstanceProfileId, Profile, }, loaders::fabric::Fabric, repository::java_runner::JavaRunner, @@ -15,34 +15,24 @@ use nomi_core::{ async fn full_fabric_test() { let _guard = tracing::subscriber::set_default(tracing_subscriber::fmt().finish()); - let current = std::env::current_dir().unwrap(); - let (tx, _) = tokio::sync::mpsc::channel(100); let game_paths = GamePaths { game: "./minecraft".into(), assets: "./minecraft/assets".into(), - version: "./minecraft/versions/Full-fabric-test".into(), + profile: "./minecraft/versions/Full-fabric-test".into(), libraries: "./minecraft/libraries".into(), }; - let instance = Instance::builder() + let instance = Profile::builder() .name("Full-fabric-test".into()) .version("1.19.4".into()) .game_paths(game_paths.clone()) - .instance(Box::new(Fabric::new("1.19.4", None::, game_paths).await.unwrap())) + .downloader(Box::new(Fabric::new("1.19.4", None::, game_paths.clone()).await.unwrap())) .build(); - let mc_dir = current.join("minecraft"); - let settings = LaunchSettings { - assets: mc_dir.join("assets"), - game_dir: mc_dir.clone(), - java_bin: JavaRunner::default(), - libraries_dir: mc_dir.clone().join("libraries"), - manifest_file: mc_dir.clone().join("versions/Full-fabric-test/1.19.4.json"), - natives_dir: mc_dir.clone().join("versions/Full-fabric-test/natives"), - version_jar_file: mc_dir.join("versions/Full-fabric-test/1.19.4.jar"), + java_runner: None, version: "1.19.4".to_string(), version_type: nomi_core::repository::manifest::VersionType::Release, }; @@ -51,7 +41,7 @@ async fn full_fabric_test() { Box::new(instance.assets().await.unwrap()).download(&tx).await; - let instance = instance.instance(); + let instance = instance.downloader(); let ui_fut = instance.io(); instance.download(&tx).await; @@ -59,13 +49,13 @@ async fn full_fabric_test() { ui_fut.await.unwrap(); let profile = VersionProfile::builder() - .id(1) + .id(InstanceProfileId::ZERO) .name("Full-fabric-test".into()) .state(ProfileState::downloaded(launch)) .build(); dbg!(profile) - .launch(UserData::default(), &JavaRunner::default(), &PrintLogs) + .launch(game_paths, UserData::default(), &JavaRunner::default(), &PrintLogs) .await .unwrap(); } diff --git a/crates/nomi-core/tests/instance_test.rs b/crates/nomi-core/tests/instance_test.rs new file mode 100644 index 0000000..fcfb707 --- /dev/null +++ b/crates/nomi-core/tests/instance_test.rs @@ -0,0 +1,66 @@ +use nomi_core::{ + configs::profile::{ProfileState, VersionProfile}, + downloads::traits::Downloader, + fs::write_toml_config, + game_paths::GamePaths, + instance::{ + launch::{arguments::UserData, LaunchSettings}, + logs::PrintLogs, + Instance, Profile, ProfilePayload, + }, + loaders::vanilla::Vanilla, + repository::{java_runner::JavaRunner, manifest::VersionType}, +}; + +#[tokio::test] +async fn instance_test() { + tracing::subscriber::set_global_default(tracing_subscriber::fmt().pretty().finish()).unwrap(); + + let mut instance = Instance::new("cool-instance", 0); + + let paths = GamePaths::from_instance_path(instance.path(), "1.19.2"); + let profile = Profile::builder() + .game_paths(paths.clone()) + .downloader(Box::new(Vanilla::new("1.19.2", paths.clone()).await.unwrap())) + .name("Cool name".into()) + .version("1.19.2".into()) + .build(); + + let launch_instance = profile.launch_instance( + LaunchSettings { + java_runner: None, + version: "1.19.2".to_owned(), + version_type: VersionType::Release, + }, + None, + ); + + let (tx, _) = tokio::sync::mpsc::channel(1); + + let assets = profile.assets().await.unwrap(); + let io = assets.io(); + Box::new(assets).download(&tx).await; + io.await.unwrap(); + + let downloader = profile.downloader(); + let io = downloader.io(); + downloader.download(&tx).await; + io.await.unwrap(); + + let version_profile = VersionProfile { + id: instance.next_id(), + name: "Based".to_owned(), + state: ProfileState::downloaded(launch_instance), + }; + + instance.add_profile(ProfilePayload::from_version_profile(&version_profile, &paths.profile())); + + write_toml_config(&version_profile, paths.profile()).await.unwrap(); + + instance.write().await.unwrap(); + + version_profile + .launch(paths, UserData::default(), &JavaRunner::from_environment(), &PrintLogs) + .await + .unwrap(); +} diff --git a/crates/nomi-core/tests/vanilla_test.rs b/crates/nomi-core/tests/vanilla_test.rs index cea103f..ea36756 100644 --- a/crates/nomi-core/tests/vanilla_test.rs +++ b/crates/nomi-core/tests/vanilla_test.rs @@ -27,16 +27,8 @@ async fn vanilla_test() { // // assets.download().await.unwrap(); // // builder.download().await.unwrap(); - let mc_dir = std::env::current_dir().unwrap().join("minecraft"); - let _settings = LaunchSettings { - assets: mc_dir.join("assets"), - game_dir: mc_dir.clone(), - java_bin: JavaRunner::default(), - libraries_dir: mc_dir.clone().join("libraries"), - manifest_file: mc_dir.clone().join("versions/1.20/1.19.4.json"), - natives_dir: mc_dir.clone().join("versions/1.20/natives"), - version_jar_file: mc_dir.join("versions/1.20/1.20.jar"), + java_runner: None, version: "1.20".to_string(), version_type: nomi_core::repository::manifest::VersionType::Release, }; From e0c59b2cbaa82128d12ed74f75b75a6dc883f2fe Mon Sep 17 00:00:00 2001 From: Umatriz Date: Sun, 11 Aug 2024 17:37:01 +0300 Subject: [PATCH 03/15] feat: instances support --- crates/client/src/cache.rs | 36 ++ crates/client/src/collections.rs | 26 +- crates/client/src/consts.rs | 2 +- crates/client/src/context.rs | 8 +- crates/client/src/errors_pool.rs | 2 - crates/client/src/main.rs | 2 + crates/client/src/mods.rs | 13 +- crates/client/src/states.rs | 7 +- crates/client/src/views/add_profile_menu.rs | 98 ++++- crates/client/src/views/mods_manager.rs | 24 +- crates/client/src/views/profile_info.rs | 30 +- crates/client/src/views/profiles.rs | 423 +++++++++++--------- crates/nomi-core/src/configs/profile.rs | 8 + crates/nomi-core/src/game_paths.rs | 2 +- crates/nomi-core/src/instance/mod.rs | 48 ++- crates/nomi-core/tests/instance_test.rs | 4 +- 16 files changed, 467 insertions(+), 266 deletions(-) create mode 100644 crates/client/src/cache.rs diff --git a/crates/client/src/cache.rs b/crates/client/src/cache.rs new file mode 100644 index 0000000..e1b0c5f --- /dev/null +++ b/crates/client/src/cache.rs @@ -0,0 +1,36 @@ +use std::{ + collections::HashMap, + path::PathBuf, + sync::{Arc, LazyLock}, +}; +use tracing::error; + +use nomi_core::{fs::read_toml_config_sync, instance::InstanceProfileId}; +use parking_lot::RwLock; + +use crate::{errors_pool::ErrorPoolExt, views::ModdedProfile}; + +pub static GLOBAL_CACHE: LazyLock>> = LazyLock::new(|| Arc::new(RwLock::new(GlobalCache::new()))); + +pub struct GlobalCache { + profiles: HashMap>>, +} + +impl GlobalCache { + pub fn new() -> Self { + Self { profiles: HashMap::new() } + } + + pub fn request_profile(&mut self, id: InstanceProfileId, path: PathBuf) -> Option>> { + match self.profiles.get(&id) { + Some(some) => Some(some.clone()), + None => read_toml_config_sync(path) + .inspect_err(|error| error!(%error, "Cannot read profile config")) + .report_error() + .and_then(|profile| { + self.profiles.insert(id, profile); + self.profiles.get(&id).cloned() + }), + } + } +} diff --git a/crates/client/src/collections.rs b/crates/client/src/collections.rs index b984e3a..bd900c8 100644 --- a/crates/client/src/collections.rs +++ b/crates/client/src/collections.rs @@ -1,7 +1,7 @@ use std::{collections::HashSet, sync::Arc}; use egui_task_manager::*; -use nomi_core::repository::fabric_meta::FabricVersions; +use nomi_core::{instance::InstanceProfileId, repository::fabric_meta::FabricVersions}; use nomi_modding::modrinth::{ project::{Project, ProjectId}, version::Version, @@ -75,7 +75,7 @@ pub struct GameDownloadingCollection; impl<'c> TasksCollection<'c> for GameDownloadingCollection { type Context = &'c InstancesConfig; - type Target = Option<()>; + type Target = Option; type Executor = executors::Linear; @@ -84,8 +84,10 @@ impl<'c> TasksCollection<'c> for GameDownloadingCollection { } fn handle(context: Self::Context) -> Handler<'c, Self::Target> { - Handler::new(|_| { - context.update_config_sync().report_error(); + Handler::new(|id| { + if let Some(id) = id { + context.update_profile_config(id).report_error(); + } }) } } @@ -184,7 +186,7 @@ pub struct ModsDownloadingCollection; impl<'c> TasksCollection<'c> for ModsDownloadingCollection { type Context = &'c InstancesConfig; - type Target = Option<()>; + type Target = Option; type Executor = executors::Linear; @@ -193,8 +195,10 @@ impl<'c> TasksCollection<'c> for ModsDownloadingCollection { } fn handle(context: Self::Context) -> Handler<'c, Self::Target> { - Handler::new(|_| { - context.update_config_sync().report_error(); + Handler::new(|id| { + if let Some(id) = id { + context.update_profile_config(id).report_error(); + } }) } } @@ -222,7 +226,7 @@ pub struct DownloadAddedModsCollection; impl<'c> TasksCollection<'c> for DownloadAddedModsCollection { type Context = (&'c mut HashSet, &'c InstancesConfig); - type Target = ProjectId; + type Target = (InstanceProfileId, ProjectId); type Executor = executors::Parallel; @@ -231,9 +235,9 @@ impl<'c> TasksCollection<'c> for DownloadAddedModsCollection { } fn handle(context: Self::Context) -> Handler<'c, Self::Target> { - Handler::new(|id| { - context.0.remove(&id); - context.1.update_config_sync().report_error(); + Handler::new(|(profile_id, project_id)| { + context.0.remove(&project_id); + context.1.update_profile_config(profile_id).report_error(); }) } } diff --git a/crates/client/src/consts.rs b/crates/client/src/consts.rs index 5a73ee0..7f96d7a 100644 --- a/crates/client/src/consts.rs +++ b/crates/client/src/consts.rs @@ -1,4 +1,4 @@ -pub const DOT_NOMI_MODS_STASH_DIR: &str = "./.nomi/mods_stash"; +pub const DOT_NOMI_MODS_STASH_DIR: &str = ".nomi/mods_stash"; pub const MINECRAFT_MODS_DIRECTORY: &str = "./minecraft/mods"; pub const NOMI_LOADED_LOCK_FILE: &str = "./minecraft/mods/Loaded.lock"; pub const NOMI_LOADED_LOCK_FILE_NAME: &str = "Loaded"; diff --git a/crates/client/src/context.rs b/crates/client/src/context.rs index 6e064e8..e109a3b 100644 --- a/crates/client/src/context.rs +++ b/crates/client/src/context.rs @@ -2,7 +2,7 @@ use crate::{ errors_pool::ErrorPoolExt, states::States, subscriber::EguiLayer, - views::{self, profiles::ProfilesPage, settings::SettingsPage, Logs, ModManager, ModManagerState, ProfileInfo, View}, + views::{self, profiles::Instances, settings::SettingsPage, Logs, ModManager, ModManagerState, ProfileInfo, View}, Tab, TabKind, }; use eframe::egui::{self}; @@ -69,11 +69,11 @@ impl TabViewer for MyContext { match &tab.kind { TabKind::Mods { profile } => { let profile = profile.read(); - self.states.profiles.instances.find_instance(profile.profile.id).is_none() + self.states.profiles.instances.find_profile(profile.profile.id).is_none() } TabKind::ProfileInfo { profile } => { let profile = profile.read(); - self.states.profiles.instances.find_instance(profile.profile.id).is_none() + self.states.profiles.instances.find_profile(profile.profile.id).is_none() } _ => false, } @@ -81,7 +81,7 @@ impl TabViewer for MyContext { fn ui(&mut self, ui: &mut egui::Ui, tab: &mut Self::Tab) { match &tab.kind { - TabKind::Profiles => ProfilesPage { + TabKind::Profiles => Instances { is_allowed_to_take_action: self.is_allowed_to_take_action, profile_info_state: &mut self.states.profile_info, manager: &mut self.manager, diff --git a/crates/client/src/errors_pool.rs b/crates/client/src/errors_pool.rs index b36d4ad..cee9f64 100644 --- a/crates/client/src/errors_pool.rs +++ b/crates/client/src/errors_pool.rs @@ -67,7 +67,6 @@ where match self { Ok(value) => Some(value), Err(error) => { - error!("{:#?}", error); if let Ok(mut pool) = ERRORS_POOL .clone() .write() @@ -87,7 +86,6 @@ where match self { Ok(value) => Some(value), Err(error) => { - error!("{:#?}", error); if let Ok(mut pool) = ERRORS_POOL .clone() .write() diff --git a/crates/client/src/main.rs b/crates/client/src/main.rs index 8761791..b021521 100644 --- a/crates/client/src/main.rs +++ b/crates/client/src/main.rs @@ -31,6 +31,8 @@ pub mod ui_ext; pub mod utils; pub mod views; +pub mod cache; + pub mod mods; pub mod open_directory; diff --git a/crates/client/src/mods.rs b/crates/client/src/mods.rs index d9915c0..72b40a1 100644 --- a/crates/client/src/mods.rs +++ b/crates/client/src/mods.rs @@ -10,6 +10,7 @@ use nomi_core::{ calculate_sha1, downloads::{progress::MappedSender, traits::Downloader, DownloadSet, FileDownloader}, fs::read_toml_config, + instance::{Instance, InstanceProfileId}, }; use nomi_modding::{ modrinth::{ @@ -27,6 +28,7 @@ use crate::{ }; #[derive(Serialize, Deserialize, Default, PartialEq, Eq, Hash, Debug)] +#[serde(transparent)] pub struct ModsConfig { pub mods: Vec, } @@ -57,14 +59,13 @@ pub struct SimpleDependency { pub is_required: bool, } -pub async fn download_added_mod(progress: TaskProgressShared, profile_id: usize, files: Vec) { +pub async fn download_added_mod(progress: TaskProgressShared, target_path: PathBuf, files: Vec) { let _ = progress.set_total(files.len() as u32); let mut set = DownloadSet::new(); - let mods_stash = Path::new(DOT_NOMI_MODS_STASH_DIR).join(format!("{profile_id}")); for file in files { - let downloader = FileDownloader::new(file.url, mods_stash.join(file.filename)) + let downloader = FileDownloader::new(file.url, target_path.join(file.filename)) .with_sha1(file.sha1) .into_retry(); set.add(Box::new(downloader)); @@ -269,3 +270,9 @@ pub async fn load_mods(profile_id: usize) -> anyhow::Result<()> { Ok(()) } + +pub fn mods_stash_path_for_profile(profile_id: InstanceProfileId) -> PathBuf { + Instance::path_from_id(profile_id.instance()) + .join(DOT_NOMI_MODS_STASH_DIR) + .join(format!("{}", profile_id.profile())) +} diff --git a/crates/client/src/states.rs b/crates/client/src/states.rs index 73b5208..d928036 100644 --- a/crates/client/src/states.rs +++ b/crates/client/src/states.rs @@ -42,13 +42,10 @@ impl Default for States { errors_pool: ErrorsPoolState::default(), logs_state: LogsState::new(), java: JavaState::new(), - profiles: ProfilesState { - currently_downloading_profiles: HashSet::new(), - instances: read_toml_config_sync::(DOT_NOMI_PROFILES_CONFIG).unwrap_or_default(), - }, + profiles: ProfilesState::new(), client_settings: settings.client_settings.clone(), settings, - add_profile_menu_state: AddProfileMenuState::default(), + add_profile_menu_state: AddProfileMenuState::new(), mod_manager: ModManagerState::new(), profile_info: ProfileInfoState::new(), } diff --git a/crates/client/src/views/add_profile_menu.rs b/crates/client/src/views/add_profile_menu.rs index c9c738a..be6b070 100644 --- a/crates/client/src/views/add_profile_menu.rs +++ b/crates/client/src/views/add_profile_menu.rs @@ -1,15 +1,21 @@ +use std::sync::Arc; + use eframe::egui::{self, Color32, RichText}; use egui_task_manager::{Caller, Task, TaskManager}; use nomi_core::{ configs::profile::{Loader, ProfileState, VersionProfile}, + fs::write_toml_config_sync, + game_paths::GamePaths, + instance::{Instance, ProfilePayload}, repository::{ fabric_meta::{get_fabric_versions, FabricVersions}, launcher_manifest::{LauncherManifest, Version}, manifest::VersionType, }, }; +use parking_lot::RwLock; -use crate::{collections::FabricDataCollection, errors_pool::ErrorPoolExt, views::ModdedProfile}; +use crate::{collections::FabricDataCollection, errors_pool::ErrorPoolExt, ui_ext::UiExt, views::ModdedProfile}; use super::{profiles::ProfilesState, View}; @@ -21,6 +27,10 @@ pub struct AddProfileMenu<'a> { } pub struct AddProfileMenuState { + instance_name: String, + + parent_instance: Option>>, + selected_version_type: VersionType, profile_name_buf: String, @@ -46,13 +56,16 @@ impl AddProfileMenuState { impl Default for AddProfileMenuState { fn default() -> Self { - Self::default_const() + Self::new() } } impl AddProfileMenuState { - pub const fn default_const() -> Self { + pub fn new() -> Self { Self { + instance_name: String::new(), + parent_instance: None, + selected_version_type: VersionType::Release, profile_name_buf: String::new(), @@ -74,6 +87,26 @@ impl View for AddProfileMenu<'_> { } } + ui.vertical(|ui| { + ui.text_edit_singleline(&mut self.menu_state.instance_name); + + if ui.button("Create").clicked() { + let id = self.profiles_state.instances.next_id(); + let instance = Instance::new(self.menu_state.instance_name.clone(), id); + self.profiles_state.instances.add_instance(instance); + self.profiles_state.instances.update_instance_config(id).report_error(); + } + ui.separator() + }); + + egui::ComboBox::from_label("Select instance to create profile for").show_ui(ui, |ui| { + for instance in &self.profiles_state.instances.instances { + if ui.button(instance.read().name()).clicked() { + self.menu_state.parent_instance = Some(instance.clone()); + } + } + }); + { ui.label("Profile name:"); ui.text_edit_singleline(&mut self.menu_state.profile_name_buf); @@ -86,7 +119,7 @@ impl View for AddProfileMenu<'_> { }); let versions_iter = self.launcher_manifest.versions.iter(); - let versions = match self.menu_state.selected_version_type { + let versions = match &self.menu_state.selected_version_type { VersionType::Release => versions_iter.filter(|v| v.version_type == "release").collect::>(), VersionType::Snapshot => versions_iter.filter(|v| v.version_type == "snapshot").collect::>(), }; @@ -182,38 +215,63 @@ impl View for AddProfileMenu<'_> { let fabric_versions_non_empty = || !self.menu_state.fabric_versions.is_empty(); if self.menu_state.profile_name_buf.trim().is_empty() { - ui.label(RichText::new("You must enter the profile name").color(ui.visuals().error_fg_color)); + ui.error_label("You must enter the profile name"); } if self.menu_state.selected_version_buf.is_none() { - ui.label(RichText::new("You must select the version").color(ui.visuals().error_fg_color)); + ui.error_label("You must select the version"); } if fabric_version_is_none() { - ui.label(RichText::new("You must select the Fabric Version").color(ui.visuals().error_fg_color)); + ui.error_label("You must select the Fabric Version"); + } + + if self.menu_state.parent_instance.is_none() { + ui.error_label("You must select the instance to create profile for"); } if ui .add_enabled( - some_version_buf() + self.menu_state.parent_instance.is_some() + && some_version_buf() && ((matches!(self.menu_state.selected_loader_buf, Loader::Vanilla)) || (fabric_version_is_some() && fabric_versions_non_empty())), egui::Button::new("Create"), ) .clicked() { - self.profiles_state.instances.add_instance(ModdedProfile::new(VersionProfile { - id: self.profiles_state.instances.next_id(), - name: self.menu_state.profile_name_buf.trim_end().to_owned(), - state: ProfileState::NotDownloaded { - // PANICS: It will never panic because it's - // unreachable for `selected_version_buf` to be `None` - version: self.menu_state.selected_version_buf.clone().unwrap().id, - loader: self.menu_state.selected_loader_buf.clone(), - version_type: self.menu_state.selected_version_type.clone(), - }, - })); - self.profiles_state.instances.update_config_sync().report_error(); + if let Some(instance) = &self.menu_state.parent_instance { + let payload = { + let instance = instance.read(); + let version = self.menu_state.selected_version_buf.clone().unwrap().id; + let profile = VersionProfile { + id: instance.next_id(), + name: self.menu_state.profile_name_buf.trim_end().to_owned(), + state: ProfileState::NotDownloaded { + // PANICS: It will never panic because it's + // unreachable for `selected_version_buf` to be `None` + version: version.clone(), + loader: self.menu_state.selected_loader_buf.clone(), + version_type: self.menu_state.selected_version_type.clone(), + }, + }; + + let path = GamePaths::from_instance_path(instance.path(), &version).profile_config(); + + let payload = ProfilePayload::from_version_profile(&profile, &path); + let profile = ModdedProfile::new(profile); + + write_toml_config_sync(&profile, path).report_error(); + + payload + }; + + { + let mut instance = instance.write(); + instance.add_profile(payload); + self.profiles_state.instances.update_instance_config(instance.id()).report_error(); + } + } } } } diff --git a/crates/client/src/views/mods_manager.rs b/crates/client/src/views/mods_manager.rs index 95ac04e..9ab8f71 100644 --- a/crates/client/src/views/mods_manager.rs +++ b/crates/client/src/views/mods_manager.rs @@ -7,7 +7,10 @@ use std::{ use eframe::egui::{self, Button, Color32, ComboBox, Id, Image, Key, Layout, RichText, ScrollArea, Vec2}; use egui_infinite_scroll::{InfiniteScroll, LoadingState}; use egui_task_manager::{Caller, Task, TaskManager}; -use nomi_core::{DOT_NOMI_DATA_PACKS_DIR, MINECRAFT_DIR}; +use nomi_core::{ + instance::{Instance, InstanceProfileId}, + DOT_NOMI_DATA_PACKS_DIR, MINECRAFT_DIR, +}; use nomi_modding::{ capitalize_first_letters_whitespace_split, modrinth::{ @@ -69,9 +72,10 @@ pub enum DataPackDownloadDirectory { } impl DataPackDownloadDirectory { - pub fn as_path_buf(&self, profile_id: usize) -> PathBuf { + pub fn as_path_buf(&self, profile_id: InstanceProfileId) -> PathBuf { match self { - DataPackDownloadDirectory::Mods => PathBuf::from(DOT_NOMI_MODS_STASH_DIR).join(format!("{profile_id}")), + DataPackDownloadDirectory::Mods => mods_stash_path_for_profile(profile_id), + // TODO: Maybe make this local for each instance DataPackDownloadDirectory::DataPacks => PathBuf::from(DOT_NOMI_DATA_PACKS_DIR), } } @@ -91,11 +95,11 @@ fn fix_svg(text: &str, color: Color32) -> Option { Some(format!(" PathBuf { +fn directory_from_project_type(project_type: ProjectType, profile_id: InstanceProfileId) -> PathBuf { match project_type { - ProjectType::Mod | ProjectType::Modpack => PathBuf::from(DOT_NOMI_MODS_STASH_DIR).join(format!("{}", profile_id)), - ProjectType::ResourcePack => PathBuf::from(MINECRAFT_DIR).join("resourcepacks"), - ProjectType::Shader => PathBuf::from(MINECRAFT_DIR).join("shaderpacks"), + ProjectType::Mod | ProjectType::Modpack => mods_stash_path_for_profile(profile_id), + ProjectType::ResourcePack => Instance::path_from_id(profile_id.instance()).join("resourcepacks"), + ProjectType::Shader => Instance::path_from_id(profile_id.instance()).join("shaderpacks"), ProjectType::DataPack => PathBuf::from(DOT_NOMI_DATA_PACKS_DIR), _ => unreachable!("You cannot download plugins"), } @@ -586,7 +590,7 @@ impl View for ModManager<'_> { let project_type = project.project_type; - let _ = self.profiles_config.update_config_sync().report_error(); + let _ = self.profiles_config.update_profile_config(self.profile.read().profile.id).report_error(); let is_data_pack = self.mod_manager_state.is_datapack; let profile_id = { let lock = profile.read(); @@ -629,11 +633,11 @@ impl View for ModManager<'_> { profile.mods.mods.extend(mods); profile.mods.mods.sort(); profile.mods.mods.dedup(); - debug!("Added mods to profile {} successfully", profile.profile.id); + debug!(id = ?profile.profile.id, "Added mods to profile successfully"); } } - Some(()) + Some(profile_id) }), ); diff --git a/crates/client/src/views/profile_info.rs b/crates/client/src/views/profile_info.rs index 6467730..688c81a 100644 --- a/crates/client/src/views/profile_info.rs +++ b/crates/client/src/views/profile_info.rs @@ -1,8 +1,15 @@ -use std::{collections::HashSet, path::Path, sync::Arc}; +use std::{ + collections::HashSet, + path::{Path, PathBuf}, + sync::Arc, +}; use eframe::egui::{self, Color32, Id, RichText, TextEdit}; use egui_task_manager::{Caller, Task, TaskManager}; -use nomi_core::configs::profile::ProfileState; +use nomi_core::{ + configs::profile::ProfileState, + instance::{Instance, InstanceProfileId}, +}; use nomi_modding::modrinth::project::ProjectId; use parking_lot::RwLock; @@ -11,7 +18,7 @@ use crate::{ views::InstancesConfig, TabKind, DOT_NOMI_MODS_STASH_DIR, }; -use super::{download_added_mod, Mod, ModdedProfile, TabsState, View}; +use super::{download_added_mod, mods_stash_path_for_profile, Mod, ModdedProfile, TabsState, View}; pub struct ProfileInfo<'a> { pub profiles: &'a InstancesConfig, @@ -273,7 +280,8 @@ impl View for ProfileInfo<'_> { } { - self.profiles.update_config_sync().report_error(); + let id = self.profile.read().profile.id; + self.profiles.update_profile_config(id).report_error(); } self.profile_info_state.mods_to_import.clear(); @@ -377,7 +385,7 @@ impl View for ProfileInfo<'_> { } } - self.profiles.update_config_sync().report_error(); + self.profiles.update_profile_config(self.profile.read().profile.id).report_error(); } if ui.button("Reset").clicked() { @@ -401,7 +409,9 @@ impl View for ProfileInfo<'_> { .on_hover_text("Open a folder where mods for this profile are located.") .clicked() { - let path = Path::new(DOT_NOMI_MODS_STASH_DIR).join(format!("{}", self.profile.read().profile.id)); + let profile_id = self.profile.read().profile.id; + let path = mods_stash_path_for_profile(profile_id); + if !path.exists() { std::fs::create_dir_all(&path).report_error(); } @@ -441,7 +451,7 @@ impl View for ProfileInfo<'_> { if yes.clicked() { mods_to_remove.push(m.project_id.clone()); - let path = Path::new(DOT_NOMI_MODS_STASH_DIR).join(format!("{profile_id}")); + let path = mods_stash_path_for_profile(profile_id); for file in &m.files { std::fs::remove_file(path.join(&file.filename)).report_error(); } @@ -459,8 +469,8 @@ impl View for ProfileInfo<'_> { let download_task = Task::new( "Download mod", Caller::progressing(move |progress| async move { - download_added_mod(progress, profile_id, files).await; - project_id + download_added_mod(progress, mods_stash_path_for_profile(profile_id), files).await; + (profile_id, project_id) }), ); @@ -476,7 +486,7 @@ impl View for ProfileInfo<'_> { vec.retain(|m| !mods_to_remove.contains(&m.project_id)); if !mods_to_remove.is_empty() { - self.profiles.update_config_sync().report_error(); + self.profiles.update_profile_config(self.profile.read().profile.id).report_error(); } let _ = std::mem::replace(&mut self.profile.write().mods.mods, vec); diff --git a/crates/client/src/views/profiles.rs b/crates/client/src/views/profiles.rs index 62ff3e0..f469f3e 100644 --- a/crates/client/src/views/profiles.rs +++ b/crates/client/src/views/profiles.rs @@ -12,13 +12,15 @@ use itertools::Itertools; use nomi_core::{ configs::profile::{Loader, ProfileState, VersionProfile}, fs::{read_toml_config, read_toml_config_sync, write_toml_config, write_toml_config_sync}, - instance::{launch::arguments::UserData, load_instances, Instance, InstanceProfileId}, + instance::{launch::arguments::UserData, load_instances, Instance, InstanceProfileId, ProfilePayload}, repository::{launcher_manifest::LauncherManifest, username::Username}, }; use parking_lot::RwLock; use serde::{Deserialize, Serialize}; +use tracing::error; use crate::{ + cache::GLOBAL_CACHE, collections::{AssetsCollection, GameDeletionCollection, GameDownloadingCollection, GameRunnerCollection}, download::{task_assets, task_download_version}, errors_pool::ErrorPoolExt, @@ -33,7 +35,7 @@ use super::{ LogsState, ModsConfig, ProfileInfoState, TabsState, View, }; -pub struct ProfilesPage<'a> { +pub struct Instances<'a> { pub is_allowed_to_take_action: bool, pub manager: &'a mut TaskManager, pub settings_state: &'a SettingsState, @@ -54,6 +56,15 @@ pub struct ProfilesState { pub instances: InstancesConfig, } +impl ProfilesState { + pub fn new() -> Self { + Self { + currently_downloading_profiles: HashSet::new(), + instances: InstancesConfig::load(), + } + } +} + #[derive(Serialize, Deserialize, Default)] pub struct InstancesConfig { pub instances: Vec>>, @@ -75,6 +86,15 @@ impl ModdedProfile { } impl InstancesConfig { + pub fn find_profile(&self, id: InstanceProfileId) -> Option>> { + self.get_profile_path(id).and_then(|path| GLOBAL_CACHE.write().request_profile(id, path)) + } + + pub fn get_profile_path(&self, id: InstanceProfileId) -> Option { + self.find_instance(id.instance()) + .and_then(|i| i.read().profiles().iter().find(|p| p.id == id).map(|p| p.path.clone())) + } + pub fn find_instance(&self, id: usize) -> Option>> { self.instances.iter().find(|p| p.read().id() == id).cloned() } @@ -105,30 +125,40 @@ impl InstancesConfig { } } - pub async fn update_all_configs(&self) { + pub fn update_profile_config(&self, id: InstanceProfileId) -> anyhow::Result<()> { + let Some((path, profile)) = self + .get_profile_path(id) + .and_then(|path| self.find_profile(id).map(|profile| (path, profile))) + else { + error!(?id, "Cannot find the profile"); + bail!("Cannot find ") + }; + + let profile = profile.read(); + write_toml_config_sync(&*profile, path) + } + + pub fn update_all_instance_configs(&self) -> anyhow::Result<()> { for instance in self.instances.iter() { let instance = instance.read(); - instance.write().await.report_error(); + instance.write_blocking().report_error(); } - } - pub fn update_config_sync(&self, id: usize) -> anyhow::Result<()> { - let runtime = tokio::runtime::Builder::new_current_thread().build()?; - runtime.block_on(self.update_config(id)) + Ok(()) } - pub async fn update_config(&self, id: usize) -> anyhow::Result<()> { + pub fn update_instance_config(&self, id: usize) -> anyhow::Result<()> { let Some(instance) = self.find_instance(id) else { bail!("No such instance") }; let instance = instance.read(); - instance.write().await + instance.write_blocking() } } -impl View for ProfilesPage<'_> { +impl View for Instances<'_> { fn ui(self, ui: &mut Ui) { { ui.toggle_value(self.is_profile_window_open, "Add new profile"); @@ -152,187 +182,200 @@ impl View for ProfilesPage<'_> { } ui.style_mut().wrap_mode = Some(TextWrapMode::Extend); + } +} - TableBuilder::new(ui) - .column(Column::auto().at_least(120.0).at_most(240.0)) - .columns(Column::auto(), 5) - .header(20.0, |mut header| { - header.col(|ui| { - ui.label("Name"); - }); - header.col(|ui| { - ui.label("Version"); - }); - header.col(|ui| { - ui.label("Loader"); - }); - }) - .body(|mut body| { - let mut is_deleting = vec![]; - - for (index, profile_lock) in self.profiles_state.instances.instances.iter().enumerate() { - body.row(30.0, |mut row| { - let profile = profile_lock.read(); - row.col(|ui| { - ui.add(egui::Label::new(&profile.profile.name).truncate()); - }); - row.col(|ui| { - ui.label(profile.profile.version()); - }); - row.col(|ui| { - ui.label(profile.profile.loader_name()); - }); - row.col(|ui| match &profile.profile.state { - ProfileState::Downloaded(instance) => { - if ui.add_enabled(self.is_allowed_to_take_action, egui::Button::new("Launch")).clicked() { - let user_data = UserData { - username: Username::new(self.settings_state.username.clone()).unwrap(), - uuid: Some(self.settings_state.uuid.clone()), - access_token: None, - }; - - let instance = instance.clone(); - let java_runner = self.settings_state.java.clone(); - - let should_load_mods = profile.profile.loader().is_fabric(); - let profile_id = profile.profile.id; - - let game_logs = self.logs_state.game_logs.clone(); - game_logs.clear(); - let run_game = Task::new( - "Running the game", - Caller::standard(async move { - if should_load_mods { - load_mods(profile_id).await.report_error(); - } - - instance.launch(user_data, &java_runner, &*game_logs).await.report_error() - }), - ); - - self.manager.push_task::(run_game) - } - } - ProfileState::NotDownloaded { .. } => { - if ui - .add_enabled( - !self.profiles_state.currently_downloading_profiles.contains(&profile.profile.id), - egui::Button::new("Download"), - ) - .clicked() - { - self.profiles_state.currently_downloading_profiles.insert(profile.profile.id); - - let game_version = profile.profile.version().to_owned(); - - let assets_task = Task::new( - format!("Assets ({})", profile.profile.version()), - Caller::progressing(|progress| task_assets(game_version, PathBuf::from("./minecraft/assets"), progress)), - ); - self.manager.push_task::(assets_task); - - let profile_clone = profile_lock.clone(); - - let game_task = Task::new( - format!("Downloading version {}", profile.profile.version()), - Caller::progressing(|progress| task_download_version(profile_clone, progress)), - ); - self.manager.push_task::(game_task); - } - } - }); - - row.col(|ui| { - if ui.button("Details").clicked() { - self.profile_info_state.set_profile_to_edit(&profile_lock.read()); - - let kind = TabKind::ProfileInfo { - profile: profile_lock.clone(), - }; - self.tabs_state.0.insert(kind.id(), kind); - } - }); - - row.col(|ui| { - if let ProfileState::Downloaded(instance) = &profile.profile.state { - let popup_id = ui.make_persistent_id("delete_popup_id"); - let button = ui - .add_enabled(self.is_allowed_to_take_action, Button::new("Delete")) - .on_hover_text("It will delete the profile and it's data"); - - if button.clicked() { - ui.memory_mut(|mem| mem.toggle_popup(popup_id)); - } - - popup_below_widget(ui, popup_id, &button, PopupCloseBehavior::CloseOnClickOutside, |ui| { - ui.set_min_width(150.0); - - let delete_client_id = Id::new("delete_client"); - let delete_libraries_id = Id::new("delete_libraries"); - let delete_assets_id = Id::new("delete_assets"); - let delete_mods_id = Id::new("delete_mods"); - - let mut make_checkbox = |text: &str, id, default: bool| { - let mut state = ui.data_mut(|map| *map.get_temp_mut_or_insert_with(id, move || default)); - ui.checkbox(&mut state, text); - ui.data_mut(|map| map.insert_temp(id, state)); - }; - - make_checkbox("Delete profile's client", delete_client_id, true); - make_checkbox("Delete profile's libraries", delete_libraries_id, false); - if profile.profile.loader().is_fabric() { - make_checkbox("Delete profile's mods", delete_mods_id, true); - } - make_checkbox("Delete profile's assets", delete_assets_id, false); - - ui.label("Are you sure you want to delete this profile and it's data?"); - ui.horizontal(|ui| { - ui.warn_icon_with_hover_text("Deleting profile's assets and libraries might break other profiles."); - if ui.button("Yes").clicked() { - is_deleting.push(index); - - let version = &instance.settings.version; - - let checkbox_data = |id| ui.data(|data| data.get_temp(id)).unwrap_or_default(); - - let delete_client = checkbox_data(delete_client_id); - let delete_libraries = checkbox_data(delete_libraries_id); - let delete_assets = checkbox_data(delete_assets_id); - let delete_mods = checkbox_data(delete_mods_id); - - let profile_id = profile.profile.id; - - let instance = instance.clone(); - let caller = Caller::standard(async move { - let path = Path::new(DOT_NOMI_MODS_STASH_DIR).join(format!("{}", profile_id)); - if delete_mods && path.exists() { - tokio::fs::remove_dir_all(path).await.report_error(); - } - instance.delete(delete_client, delete_libraries, delete_assets).await.report_error(); - }); - - let task = Task::new(format!("Deleting the game's files ({})", version), caller); - - self.manager.push_task::(task); - - self.tabs_state.remove_profile_related_tabs(&profile); - - ui.memory_mut(|mem| mem.close_popup()); - } - if ui.button("No").clicked() { - ui.memory_mut(|mem| mem.close_popup()); - } - }); - }); - } - }); +fn show_profiles_for_instance(ui: &mut Ui, profiles: &mut Vec, is_allowed_to_take_action: bool) { + TableBuilder::new(ui) + .column(Column::auto().at_least(120.0).at_most(240.0)) + .columns(Column::auto(), 5) + .header(20.0, |mut header| { + header.col(|ui| { + ui.label("Name"); + }); + header.col(|ui| { + ui.label("Version"); + }); + header.col(|ui| { + ui.label("Loader"); + }); + }) + .body(|mut body| { + // let mut is_deleting = vec![]; + + for (_index, profile) in profiles.iter().enumerate() { + body.row(30.0, |mut row| { + row.col(|ui| { + ui.add(egui::Label::new(&profile.name).truncate()); + }); + row.col(|ui| { + ui.label(&profile.version); + }); + row.col(|ui| { + ui.label(profile.loader.name()); + }); + row.col(|ui| { + if profile.is_downloaded { + ui.button("TODO: Launch"); + } else { + ui.button("TODO: Download"); + } + }); + + row.col(|ui| { + if ui.button("TODO: Details").clicked() { + // self.profile_info_state.set_profile_to_edit(&profile_lock.read()); + + // let kind = TabKind::ProfileInfo { + // profile: profile_lock.clone(), + // }; + // self.tabs_state.0.insert(kind.id(), kind); + } }); - } - is_deleting.drain(..).for_each(|index| { - self.profiles_state.instances.instances.remove(index); - self.profiles_state.instances.update_config_sync().report_error(); + row.col(|ui| { + ui.button("TODO: Delete"); + }); }); - }); - } + } + + // is_deleting.drain(..).for_each(|index| { + // self.profiles_state.instances.instances.remove(index); + // self.profiles_state.instances.update_config_sync().report_error(); + // }); + }); +} + +fn profile_action_ui() { + // match &profile.is_downloaded { + // ProfileState::Downloaded(instance) => { + // if ui.add_enabled(self.is_allowed_to_take_action, egui::Button::new("Launch")).clicked() { + // let user_data = UserData { + // username: Username::new(self.settings_state.username.clone()).unwrap(), + // uuid: Some(self.settings_state.uuid.clone()), + // access_token: None, + // }; + + // let instance = instance.clone(); + // let java_runner = self.settings_state.java.clone(); + + // let should_load_mods = profile.profile.loader().is_fabric(); + // let profile_id = profile.profile.id; + + // let game_logs = self.logs_state.game_logs.clone(); + // game_logs.clear(); + // let run_game = Task::new( + // "Running the game", + // Caller::standard(async move { + // if should_load_mods { + // load_mods(profile_id).await.report_error(); + // } + + // instance.launch(user_data, &java_runner, &*game_logs).await.report_error() + // }), + // ); + + // self.manager.push_task::(run_game) + // } + // } + // ProfileState::NotDownloaded { .. } => { + // if ui + // .add_enabled( + // !self.profiles_state.currently_downloading_profiles.contains(&profile.profile.id), + // egui::Button::new("Download"), + // ) + // .clicked() + // { + // self.profiles_state.currently_downloading_profiles.insert(profile.profile.id); + + // let game_version = profile.profile.version().to_owned(); + + // let assets_task = Task::new( + // format!("Assets ({})", profile.profile.version()), + // Caller::progressing(|progress| task_assets(game_version, PathBuf::from("./minecraft/assets"), progress)), + // ); + // self.manager.push_task::(assets_task); + + // let profile_clone = profile_lock.clone(); + + // let game_task = Task::new( + // format!("Downloading version {}", profile.profile.version()), + // Caller::progressing(|progress| task_download_version(profile_clone, progress)), + // ); + // self.manager.push_task::(game_task); + // } +} + +fn delete_profile_ui() { + // if let ProfileState::Downloaded(instance) = &profile.profile.state { + // let popup_id = ui.make_persistent_id("delete_popup_id"); + // let button = ui + // .add_enabled(is_allowed_to_take_action, Button::new("Delete")) + // .on_hover_text("It will delete the profile and it's data"); + + // if button.clicked() { + // ui.memory_mut(|mem| mem.toggle_popup(popup_id)); + // } + + // popup_below_widget(ui, popup_id, &button, PopupCloseBehavior::CloseOnClickOutside, |ui| { + // ui.set_min_width(150.0); + + // let delete_client_id = Id::new("delete_client"); + // let delete_libraries_id = Id::new("delete_libraries"); + // let delete_assets_id = Id::new("delete_assets"); + // let delete_mods_id = Id::new("delete_mods"); + + // let mut make_checkbox = |text: &str, id, default: bool| { + // let mut state = ui.data_mut(|map| *map.get_temp_mut_or_insert_with(id, move || default)); + // ui.checkbox(&mut state, text); + // ui.data_mut(|map| map.insert_temp(id, state)); + // }; + + // make_checkbox("Delete profile's client", delete_client_id, true); + // make_checkbox("Delete profile's libraries", delete_libraries_id, false); + // if profile.profile.loader().is_fabric() { + // make_checkbox("Delete profile's mods", delete_mods_id, true); + // } + // make_checkbox("Delete profile's assets", delete_assets_id, false); + + // ui.label("Are you sure you want to delete this profile and it's data?"); + // ui.horizontal(|ui| { + // ui.warn_icon_with_hover_text("Deleting profile's assets and libraries might break other profiles."); + // if ui.button("Yes").clicked() { + // is_deleting.push(index); + + // let version = &instance.settings.version; + + // let checkbox_data = |id| ui.data(|data| data.get_temp(id)).unwrap_or_default(); + + // let delete_client = checkbox_data(delete_client_id); + // let delete_libraries = checkbox_data(delete_libraries_id); + // let delete_assets = checkbox_data(delete_assets_id); + // let delete_mods = checkbox_data(delete_mods_id); + + // let profile_id = profile.profile.id; + + // let instance = instance.clone(); + // let caller = Caller::standard(async move { + // let path = Path::new(DOT_NOMI_MODS_STASH_DIR).join(format!("{}", profile_id)); + // if delete_mods && path.exists() { + // tokio::fs::remove_dir_all(path).await.report_error(); + // } + // instance.delete(delete_client, delete_libraries, delete_assets).await.report_error(); + // }); + + // let task = Task::new(format!("Deleting the game's files ({})", version), caller); + + // self.manager.push_task::(task); + + // self.tabs_state.remove_profile_related_tabs(&profile); + + // ui.memory_mut(|mem| mem.close_popup()); + // } + // if ui.button("No").clicked() { + // ui.memory_mut(|mem| mem.close_popup()); + // } + // }); + // }); + // } } diff --git a/crates/nomi-core/src/configs/profile.rs b/crates/nomi-core/src/configs/profile.rs index 08959fb..8e91991 100644 --- a/crates/nomi-core/src/configs/profile.rs +++ b/crates/nomi-core/src/configs/profile.rs @@ -46,6 +46,10 @@ impl Loader { pub fn is_vanilla(&self) -> bool { matches!(*self, Self::Vanilla) } + + pub fn name(&self) -> String { + format!("{self}") + } } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)] @@ -117,4 +121,8 @@ impl VersionProfile { ProfileState::NotDownloaded { version, .. } => version, } } + + pub fn is_downloaded(&self) -> bool { + matches!(self.state, ProfileState::Downloaded(_)) + } } diff --git a/crates/nomi-core/src/game_paths.rs b/crates/nomi-core/src/game_paths.rs index eb2fe3a..871c121 100644 --- a/crates/nomi-core/src/game_paths.rs +++ b/crates/nomi-core/src/game_paths.rs @@ -36,7 +36,7 @@ impl GamePaths { }) } - pub fn profile(&self) -> PathBuf { + pub fn profile_config(&self) -> PathBuf { self.profile.join("Profile.toml") } diff --git a/crates/nomi-core/src/instance/mod.rs b/crates/nomi-core/src/instance/mod.rs index 68cdf10..14b6508 100644 --- a/crates/nomi-core/src/instance/mod.rs +++ b/crates/nomi-core/src/instance/mod.rs @@ -13,7 +13,7 @@ use tracing::error; use crate::{ configs::profile::{Loader, VersionProfile}, - fs::{read_toml_config_sync, write_toml_config}, + fs::{read_toml_config_sync, write_toml_config, write_toml_config_sync}, INSTANCES_DIR, INSTANCE_CONFIG, }; @@ -42,7 +42,7 @@ pub fn load_instances() -> anyhow::Result> { Ok(instances) } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, PartialEq)] pub struct Instance { name: String, id: usize, @@ -76,23 +76,47 @@ impl Instance { } } + pub fn name(&self) -> &str { + &self.name + } + pub fn id(&self) -> usize { self.id } + pub fn main_profile(&self) -> Option { + self.main_profile + } + + pub fn profiles(&self) -> &[ProfilePayload] { + &self.profiles + } + + pub fn profiles_mut(&mut self) -> &mut Vec { + &mut self.profiles + } + pub async fn write(&self) -> anyhow::Result<()> { - write_toml_config(&self, PathBuf::from(INSTANCES_DIR).join(&self.name).join(".nomi/Instance.toml")).await + write_toml_config(&self, self.path().join(".nomi/Instance.toml")).await + } + + pub fn write_blocking(&self) -> anyhow::Result<()> { + write_toml_config_sync(&self, self.path().join(".nomi/Instance.toml")) } pub fn path(&self) -> PathBuf { - PathBuf::from(INSTANCES_DIR).join(&self.name) + Self::path_from_id(self.id) + } + + pub fn path_from_id(id: usize) -> PathBuf { + PathBuf::from(INSTANCES_DIR).join(format!("{id}")) } } /// Represent a unique identifier of a profile. /// /// First number is the instance id and the second number is the profile id. -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Hash)] pub struct InstanceProfileId(usize, usize); impl InstanceProfileId { @@ -101,25 +125,35 @@ impl InstanceProfileId { pub fn new(instance: usize, profile: usize) -> Self { Self(instance, profile) } + + pub fn instance(&self) -> usize { + self.0 + } + + pub fn profile(&self) -> usize { + self.1 + } } /// Information about profile. -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, PartialEq)] pub struct ProfilePayload { pub id: InstanceProfileId, pub name: String, pub loader: Loader, pub version: String, + pub is_downloaded: bool, pub path: PathBuf, } impl ProfilePayload { pub fn from_version_profile(profile: &VersionProfile, path: &Path) -> Self { Self { - id: profile.id.clone(), + id: profile.id, name: profile.name.clone(), loader: profile.loader().clone(), version: profile.version().to_owned(), + is_downloaded: profile.is_downloaded(), path: path.to_path_buf(), } } diff --git a/crates/nomi-core/tests/instance_test.rs b/crates/nomi-core/tests/instance_test.rs index fcfb707..035b5c2 100644 --- a/crates/nomi-core/tests/instance_test.rs +++ b/crates/nomi-core/tests/instance_test.rs @@ -53,9 +53,9 @@ async fn instance_test() { state: ProfileState::downloaded(launch_instance), }; - instance.add_profile(ProfilePayload::from_version_profile(&version_profile, &paths.profile())); + instance.add_profile(ProfilePayload::from_version_profile(&version_profile, &paths.profile_config())); - write_toml_config(&version_profile, paths.profile()).await.unwrap(); + write_toml_config(&version_profile, paths.profile_config()).await.unwrap(); instance.write().await.unwrap(); From 7dc8143defd091b3f2bd6a0b85ab3233085069df Mon Sep 17 00:00:00 2001 From: Umatriz Date: Sun, 11 Aug 2024 20:13:56 +0300 Subject: [PATCH 04/15] feat: instances creation --- .gitignore | 4 + crates/client/src/consts.rs | 3 +- crates/client/src/download.rs | 10 +- crates/client/src/mods.rs | 49 ++-- crates/client/src/states.rs | 4 +- crates/client/src/views/add_profile_menu.rs | 22 +- crates/client/src/views/mods_manager.rs | 3 +- crates/client/src/views/profile_info.rs | 8 +- crates/client/src/views/profiles.rs | 281 +++++++++++--------- crates/nomi-core/src/configs/profile.rs | 4 + crates/nomi-core/src/consts.rs | 1 - crates/nomi-core/src/game_paths.rs | 24 +- crates/nomi-core/src/loaders/forge.rs | 14 +- crates/nomi-core/tests/forge_new_test.rs | 7 +- crates/nomi-core/tests/forge_old_test.rs | 6 +- crates/nomi-core/tests/instance_test.rs | 2 +- 16 files changed, 232 insertions(+), 210 deletions(-) diff --git a/.gitignore b/.gitignore index 2e15ebf..632014b 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,10 @@ logs/ log/ nomi_logs/ +/assets/ +/instances/ +/libraries/ + debug/ node_modules diff --git a/crates/client/src/consts.rs b/crates/client/src/consts.rs index 7f96d7a..1e57450 100644 --- a/crates/client/src/consts.rs +++ b/crates/client/src/consts.rs @@ -1,4 +1,3 @@ pub const DOT_NOMI_MODS_STASH_DIR: &str = ".nomi/mods_stash"; -pub const MINECRAFT_MODS_DIRECTORY: &str = "./minecraft/mods"; -pub const NOMI_LOADED_LOCK_FILE: &str = "./minecraft/mods/Loaded.lock"; +pub const NOMI_LOADED_LOCK_FILE: &str = "Loaded.lock"; pub const NOMI_LOADED_LOCK_FILE_NAME: &str = "Loaded"; diff --git a/crates/client/src/download.rs b/crates/client/src/download.rs index f48d6e5..7df8738 100644 --- a/crates/client/src/download.rs +++ b/crates/client/src/download.rs @@ -16,7 +16,6 @@ use nomi_core::{ forge::{Forge, ForgeVersion}, vanilla::Vanilla, }, - repository::java_runner::JavaRunner, state::get_launcher_manifest, }; use parking_lot::RwLock; @@ -29,8 +28,6 @@ pub async fn task_download_version(profile: Arc>, progress async fn try_download_version(profile: Arc>, progress_shared: TaskProgressShared) -> anyhow::Result<()> { let launch_instance = { - let mc_dir = PathBuf::from("./minecraft"); - let version_profile = { let version_profile = &profile.read().profile; version_profile.clone() @@ -45,12 +42,7 @@ async fn try_download_version(profile: Arc>, progress_shar return Err(anyhow!("This profile is already downloaded")); }; - let game_paths = GamePaths { - game: mc_dir.clone(), - assets: mc_dir.join("assets"), - profile: mc_dir.join("versions").join(version), - libraries: mc_dir.join("libraries"), - }; + let game_paths = GamePaths::from_id(version_profile.id); let builder = Profile::builder() .name(version_profile.name.clone()) diff --git a/crates/client/src/mods.rs b/crates/client/src/mods.rs index 72b40a1..51b2a1e 100644 --- a/crates/client/src/mods.rs +++ b/crates/client/src/mods.rs @@ -22,10 +22,7 @@ use nomi_modding::{ use serde::{Deserialize, Serialize}; use tokio::{fs::File, io::AsyncWriteExt}; -use crate::{ - errors_pool::ErrorPoolExt, progress::UnitProgress, DOT_NOMI_MODS_STASH_DIR, MINECRAFT_MODS_DIRECTORY, NOMI_LOADED_LOCK_FILE, - NOMI_LOADED_LOCK_FILE_NAME, -}; +use crate::{errors_pool::ErrorPoolExt, progress::UnitProgress, DOT_NOMI_MODS_STASH_DIR, NOMI_LOADED_LOCK_FILE, NOMI_LOADED_LOCK_FILE_NAME}; #[derive(Serialize, Deserialize, Default, PartialEq, Eq, Hash, Debug)] #[serde(transparent)] @@ -188,27 +185,31 @@ impl CurrentlyLoaded { } /// Load profile's mods by creating hard links. -pub async fn load_mods(profile_id: usize) -> anyhow::Result<()> { - async fn make_link(source: &Path, file_name: &OsStr) -> anyhow::Result<()> { - let dst = PathBuf::from(MINECRAFT_MODS_DIRECTORY).join(file_name); +pub async fn load_mods(id: InstanceProfileId) -> anyhow::Result<()> { + async fn make_link(source: &Path, mods_dir: &Path, file_name: &OsStr) -> anyhow::Result<()> { + let dst = mods_dir.join(file_name); tokio::fs::hard_link(source, dst).await.map_err(|e| e.into()) } - if !Path::new(NOMI_LOADED_LOCK_FILE).exists() { - CurrentlyLoaded { id: profile_id }.write_with_comment(NOMI_LOADED_LOCK_FILE).await? + let instance_path = Instance::path_from_id(id.instance()); + let mods_stash = mods_stash_path_for_profile(id); + let mods_dir = instance_path.join("mods"); + let loaded_lock_path = mods_dir.join(NOMI_LOADED_LOCK_FILE); + + if !loaded_lock_path.exists() { + CurrentlyLoaded { id: id.profile() }.write_with_comment(&loaded_lock_path).await? } - let mut loaded = read_toml_config::(NOMI_LOADED_LOCK_FILE).await?; + let mut loaded = read_toml_config::(&loaded_lock_path).await?; - let target_dir = PathBuf::from(MINECRAFT_MODS_DIRECTORY) + let target_dir = mods_dir .read_dir()? .filter_map(|r| r.ok()) .map(|e| (e.file_name(), e.path())) .collect::>(); - if loaded.id == profile_id { - let path = PathBuf::from(DOT_NOMI_MODS_STASH_DIR).join(format!("{profile_id}")); - let mut dir = tokio::fs::read_dir(path).await?; + if loaded.id == id.profile() { + let mut dir = tokio::fs::read_dir(mods_stash).await?; let mut mods_in_the_stash = Vec::new(); @@ -219,13 +220,13 @@ pub async fn load_mods(profile_id: usize) -> anyhow::Result<()> { continue; } - let path = entry.path(); + let source = entry.path(); - let Some(file_name) = path.file_name() else { + let Some(file_name) = source.file_name() else { continue; }; - make_link(&path, file_name).await?; + make_link(&source, &mods_dir, file_name).await?; } for (file_name, path) in target_dir { @@ -243,7 +244,7 @@ pub async fn load_mods(profile_id: usize) -> anyhow::Result<()> { return Ok(()); } - let mut dir = tokio::fs::read_dir(MINECRAFT_MODS_DIRECTORY).await?; + let mut dir = tokio::fs::read_dir(&mods_dir).await?; while let Ok(Some(entry)) = dir.next_entry().await { if entry.file_name() == NOMI_LOADED_LOCK_FILE_NAME { continue; @@ -252,21 +253,21 @@ pub async fn load_mods(profile_id: usize) -> anyhow::Result<()> { tokio::fs::remove_file(entry.path()).await?; } - let mut dir = tokio::fs::read_dir(PathBuf::from(DOT_NOMI_MODS_STASH_DIR).join(format!("{profile_id}"))).await?; + let mut dir = tokio::fs::read_dir(mods_stash).await?; while let Ok(Some(entry)) = dir.next_entry().await { - let path = entry.path(); + let source = entry.path(); - let Some(file_name) = path.file_name() else { + let Some(file_name) = source.file_name() else { continue; }; - make_link(&path, file_name).await?; + make_link(&source, &mods_dir, file_name).await?; } - loaded.id = profile_id; + loaded.id = id.profile(); - loaded.write_with_comment(NOMI_LOADED_LOCK_FILE).await?; + loaded.write_with_comment(loaded_lock_path).await?; Ok(()) } diff --git a/crates/client/src/states.rs b/crates/client/src/states.rs index d928036..d5c867e 100644 --- a/crates/client/src/states.rs +++ b/crates/client/src/states.rs @@ -1,4 +1,4 @@ -use std::{collections::HashSet, path::PathBuf}; +use std::path::PathBuf; use egui_task_manager::{Caller, Task, TaskManager}; use nomi_core::{ @@ -15,7 +15,7 @@ use crate::{ add_tab_menu::TabsState, profiles::ProfilesState, settings::{ClientSettingsState, SettingsState}, - AddProfileMenuState, InstancesConfig, LogsState, ModManagerState, ProfileInfoState, + AddProfileMenuState, LogsState, ModManagerState, ProfileInfoState, }, }; diff --git a/crates/client/src/views/add_profile_menu.rs b/crates/client/src/views/add_profile_menu.rs index be6b070..19737e8 100644 --- a/crates/client/src/views/add_profile_menu.rs +++ b/crates/client/src/views/add_profile_menu.rs @@ -101,7 +101,16 @@ impl View for AddProfileMenu<'_> { egui::ComboBox::from_label("Select instance to create profile for").show_ui(ui, |ui| { for instance in &self.profiles_state.instances.instances { - if ui.button(instance.read().name()).clicked() { + if ui + .selectable_label( + self.menu_state + .parent_instance + .as_ref() + .is_some_and(|i| i.read().id() == instance.read().id()), + instance.read().name(), + ) + .clicked() + { self.menu_state.parent_instance = Some(instance.clone()); } } @@ -256,7 +265,7 @@ impl View for AddProfileMenu<'_> { }, }; - let path = GamePaths::from_instance_path(instance.path(), &version).profile_config(); + let path = GamePaths::from_instance_path(instance.path(), profile.id.profile()).profile_config(); let payload = ProfilePayload::from_version_profile(&profile, &path); let profile = ModdedProfile::new(profile); @@ -267,9 +276,12 @@ impl View for AddProfileMenu<'_> { }; { - let mut instance = instance.write(); - instance.add_profile(payload); - self.profiles_state.instances.update_instance_config(instance.id()).report_error(); + let id = { + let mut instance = instance.write(); + instance.add_profile(payload); + instance.id() + }; + self.profiles_state.instances.update_instance_config(id).report_error(); } } } diff --git a/crates/client/src/views/mods_manager.rs b/crates/client/src/views/mods_manager.rs index 9ab8f71..43dbc3b 100644 --- a/crates/client/src/views/mods_manager.rs +++ b/crates/client/src/views/mods_manager.rs @@ -9,7 +9,7 @@ use egui_infinite_scroll::{InfiniteScroll, LoadingState}; use egui_task_manager::{Caller, Task, TaskManager}; use nomi_core::{ instance::{Instance, InstanceProfileId}, - DOT_NOMI_DATA_PACKS_DIR, MINECRAFT_DIR, + DOT_NOMI_DATA_PACKS_DIR, }; use nomi_modding::{ capitalize_first_letters_whitespace_split, @@ -28,7 +28,6 @@ use crate::{ collections::{DependenciesCollection, ModsDownloadingCollection, ProjectCollection, ProjectVersionsCollection}, errors_pool::ErrorPoolExt, ui_ext::UiExt, - DOT_NOMI_MODS_STASH_DIR, }; use super::{InstancesConfig, ModdedProfile, View}; diff --git a/crates/client/src/views/profile_info.rs b/crates/client/src/views/profile_info.rs index 688c81a..6da100f 100644 --- a/crates/client/src/views/profile_info.rs +++ b/crates/client/src/views/profile_info.rs @@ -1,21 +1,17 @@ use std::{ collections::HashSet, - path::{Path, PathBuf}, sync::Arc, }; use eframe::egui::{self, Color32, Id, RichText, TextEdit}; use egui_task_manager::{Caller, Task, TaskManager}; -use nomi_core::{ - configs::profile::ProfileState, - instance::{Instance, InstanceProfileId}, -}; +use nomi_core::configs::profile::ProfileState; use nomi_modding::modrinth::project::ProjectId; use parking_lot::RwLock; use crate::{ collections::DownloadAddedModsCollection, errors_pool::ErrorPoolExt, open_directory::open_directory_native, ui_ext::UiExt, - views::InstancesConfig, TabKind, DOT_NOMI_MODS_STASH_DIR, + views::InstancesConfig, TabKind, }; use super::{download_added_mod, mods_stash_path_for_profile, Mod, ModdedProfile, TabsState, View}; diff --git a/crates/client/src/views/profiles.rs b/crates/client/src/views/profiles.rs index f469f3e..ef4c55d 100644 --- a/crates/client/src/views/profiles.rs +++ b/crates/client/src/views/profiles.rs @@ -1,17 +1,14 @@ -use std::{ - collections::HashSet, - path::{Path, PathBuf}, - sync::Arc, -}; +use std::{collections::HashSet, path::PathBuf, sync::Arc}; use anyhow::bail; -use eframe::egui::{self, popup_below_widget, Align2, Button, Id, PopupCloseBehavior, TextWrapMode, Ui}; +use eframe::egui::{self, Align2, RichText, TextWrapMode, Ui}; use egui_extras::{Column, TableBuilder}; use egui_task_manager::{Caller, Task, TaskManager}; use itertools::Itertools; use nomi_core::{ - configs::profile::{Loader, ProfileState, VersionProfile}, - fs::{read_toml_config, read_toml_config_sync, write_toml_config, write_toml_config_sync}, + configs::profile::{ProfileState, VersionProfile}, + fs::write_toml_config_sync, + game_paths::GamePaths, instance::{launch::arguments::UserData, load_instances, Instance, InstanceProfileId, ProfilePayload}, repository::{launcher_manifest::LauncherManifest, username::Username}, }; @@ -21,11 +18,10 @@ use tracing::error; use crate::{ cache::GLOBAL_CACHE, - collections::{AssetsCollection, GameDeletionCollection, GameDownloadingCollection, GameRunnerCollection}, + collections::{AssetsCollection, GameDownloadingCollection, GameRunnerCollection}, download::{task_assets, task_download_version}, errors_pool::ErrorPoolExt, ui_ext::UiExt, - TabKind, DOT_NOMI_MODS_STASH_DIR, }; use super::{ @@ -158,8 +154,139 @@ impl InstancesConfig { } } +impl Instances<'_> { + fn profile_action_ui(&mut self, ui: &mut Ui, profile_lock: Arc>) { + let profile = profile_lock.read(); + match &profile.profile.state { + ProfileState::Downloaded(instance) => { + if ui.add_enabled(self.is_allowed_to_take_action, egui::Button::new("Launch")).clicked() { + let user_data = UserData { + username: Username::new(self.settings_state.username.clone()).unwrap(), + uuid: Some(self.settings_state.uuid.clone()), + access_token: None, + }; + + let instance = instance.clone(); + let java_runner = self.settings_state.java.clone(); + + let should_load_mods = profile.profile.loader().support_mods(); + let profile_id = profile.profile.id; + + let game_logs = self.logs_state.game_logs.clone(); + game_logs.clear(); + let run_game = Task::new( + "Running the game", + Caller::standard(async move { + if should_load_mods { + load_mods(profile_id).await.report_error(); + } + + instance + .launch(GamePaths::from_id(profile_id), user_data, &java_runner, &*game_logs) + .await + .report_error() + }), + ); + + self.manager.push_task::(run_game) + } + } + ProfileState::NotDownloaded { .. } => { + if ui + .add_enabled( + !self.profiles_state.currently_downloading_profiles.contains(&profile.profile.id), + egui::Button::new("Download"), + ) + .clicked() + { + self.profiles_state.currently_downloading_profiles.insert(profile.profile.id); + + let game_version = profile.profile.version().to_owned(); + + let game_paths = GamePaths::from_id(profile.profile.id); + let assets_task = Task::new( + format!("Assets ({})", profile.profile.version()), + Caller::progressing(|progress| task_assets(game_version, game_paths.assets, progress)), + ); + self.manager.push_task::(assets_task); + + let profile_clone = profile_lock.clone(); + + let id = profile.profile.id; + let game_task = Task::new( + format!("Downloading version {}", profile.profile.version()), + Caller::progressing(move |progress| async move { task_download_version(profile_clone, progress).await.map(|()| id) }), + ); + self.manager.push_task::(game_task); + } + } + } + } + + fn show_profiles_for_instance(&mut self, ui: &mut Ui, profiles: &[ProfilePayload], is_allowed_to_take_action: bool) { + TableBuilder::new(ui) + .column(Column::auto().at_least(120.0).at_most(240.0)) + .columns(Column::auto(), 5) + .header(20.0, |mut header| { + header.col(|ui| { + ui.label("Name"); + }); + header.col(|ui| { + ui.label("Version"); + }); + header.col(|ui| { + ui.label("Loader"); + }); + }) + .body(|mut body| { + // let mut is_deleting = vec![]; + + for (_index, profile) in profiles.iter().enumerate() { + body.row(30.0, |mut row| { + row.col(|ui| { + ui.add(egui::Label::new(&profile.name).truncate()); + }); + row.col(|ui| { + ui.label(&profile.version); + }); + row.col(|ui| { + ui.label(profile.loader.name()); + }); + row.col(|ui| { + if let Some(profile) = self.profiles_state.instances.find_profile(profile.id) { + self.profile_action_ui(ui, profile) + } else { + ui.error_label("Cannot find the profile"); + } + }); + + row.col(|ui| { + if ui.button("TODO: Details").clicked() { + // self.profile_info_state.set_profile_to_edit(&profile_lock.read()); + + // let kind = TabKind::ProfileInfo { + // profile: profile_lock.clone(), + // }; + // self.tabs_state.0.insert(kind.id(), kind); + } + }); + + row.col(|ui| { + ui.button("TODO: Delete"); + }); + }); + } + + // is_deleting.drain(..).for_each(|index| { + // self.profiles_state.instances.instances.remove(index); + // self.profiles_state.instances.update_config_sync().report_error(); + // }); + }); + } +} + impl View for Instances<'_> { - fn ui(self, ui: &mut Ui) { + fn ui(mut self, ui: &mut Ui) { { ui.toggle_value(self.is_profile_window_open, "Add new profile"); @@ -182,128 +309,20 @@ impl View for Instances<'_> { } ui.style_mut().wrap_mode = Some(TextWrapMode::Extend); - } -} - -fn show_profiles_for_instance(ui: &mut Ui, profiles: &mut Vec, is_allowed_to_take_action: bool) { - TableBuilder::new(ui) - .column(Column::auto().at_least(120.0).at_most(240.0)) - .columns(Column::auto(), 5) - .header(20.0, |mut header| { - header.col(|ui| { - ui.label("Name"); - }); - header.col(|ui| { - ui.label("Version"); - }); - header.col(|ui| { - ui.label("Loader"); - }); - }) - .body(|mut body| { - // let mut is_deleting = vec![]; - - for (_index, profile) in profiles.iter().enumerate() { - body.row(30.0, |mut row| { - row.col(|ui| { - ui.add(egui::Label::new(&profile.name).truncate()); - }); - row.col(|ui| { - ui.label(&profile.version); - }); - row.col(|ui| { - ui.label(profile.loader.name()); - }); - row.col(|ui| { - if profile.is_downloaded { - ui.button("TODO: Launch"); - } else { - ui.button("TODO: Download"); - } - }); - - row.col(|ui| { - if ui.button("TODO: Details").clicked() { - // self.profile_info_state.set_profile_to_edit(&profile_lock.read()); - // let kind = TabKind::ProfileInfo { - // profile: profile_lock.clone(), - // }; - // self.tabs_state.0.insert(kind.id(), kind); - } - }); - - row.col(|ui| { - ui.button("TODO: Delete"); + let iter = self.profiles_state.instances.instances.iter().cloned().collect_vec().into_iter(); + for instance in iter { + let instance = instance.read(); + ui.group(|ui| { + ui.label(RichText::new(instance.name()).strong()); + egui::CollapsingHeader::new("Profiles") + .id_source(egui::Id::new(instance.id()).with("__profiles_list")) + .show(ui, |ui| { + self.show_profiles_for_instance(ui, instance.profiles(), self.is_allowed_to_take_action) }); - }); - } - - // is_deleting.drain(..).for_each(|index| { - // self.profiles_state.instances.instances.remove(index); - // self.profiles_state.instances.update_config_sync().report_error(); - // }); - }); -} - -fn profile_action_ui() { - // match &profile.is_downloaded { - // ProfileState::Downloaded(instance) => { - // if ui.add_enabled(self.is_allowed_to_take_action, egui::Button::new("Launch")).clicked() { - // let user_data = UserData { - // username: Username::new(self.settings_state.username.clone()).unwrap(), - // uuid: Some(self.settings_state.uuid.clone()), - // access_token: None, - // }; - - // let instance = instance.clone(); - // let java_runner = self.settings_state.java.clone(); - - // let should_load_mods = profile.profile.loader().is_fabric(); - // let profile_id = profile.profile.id; - - // let game_logs = self.logs_state.game_logs.clone(); - // game_logs.clear(); - // let run_game = Task::new( - // "Running the game", - // Caller::standard(async move { - // if should_load_mods { - // load_mods(profile_id).await.report_error(); - // } - - // instance.launch(user_data, &java_runner, &*game_logs).await.report_error() - // }), - // ); - - // self.manager.push_task::(run_game) - // } - // } - // ProfileState::NotDownloaded { .. } => { - // if ui - // .add_enabled( - // !self.profiles_state.currently_downloading_profiles.contains(&profile.profile.id), - // egui::Button::new("Download"), - // ) - // .clicked() - // { - // self.profiles_state.currently_downloading_profiles.insert(profile.profile.id); - - // let game_version = profile.profile.version().to_owned(); - - // let assets_task = Task::new( - // format!("Assets ({})", profile.profile.version()), - // Caller::progressing(|progress| task_assets(game_version, PathBuf::from("./minecraft/assets"), progress)), - // ); - // self.manager.push_task::(assets_task); - - // let profile_clone = profile_lock.clone(); - - // let game_task = Task::new( - // format!("Downloading version {}", profile.profile.version()), - // Caller::progressing(|progress| task_download_version(profile_clone, progress)), - // ); - // self.manager.push_task::(game_task); - // } + }); + } + } } fn delete_profile_ui() { diff --git a/crates/nomi-core/src/configs/profile.rs b/crates/nomi-core/src/configs/profile.rs index 8e91991..8f75b0a 100644 --- a/crates/nomi-core/src/configs/profile.rs +++ b/crates/nomi-core/src/configs/profile.rs @@ -35,6 +35,10 @@ impl Display for Loader { } impl Loader { + pub fn support_mods(&self) -> bool { + !self.is_vanilla() + } + pub fn is_fabric(&self) -> bool { matches!(*self, Self::Fabric { .. }) } diff --git a/crates/nomi-core/src/consts.rs b/crates/nomi-core/src/consts.rs index ced8a96..e97d5f6 100644 --- a/crates/nomi-core/src/consts.rs +++ b/crates/nomi-core/src/consts.rs @@ -7,7 +7,6 @@ pub const DOT_NOMI_JAVA_DIR: &str = "./.nomi/java"; pub const DOT_NOMI_JAVA_EXECUTABLE: &str = "./.nomi/java/jdk-22.0.1/bin/java"; pub const DOT_NOMI_DATA_PACKS_DIR: &str = "./.nomi/datapacks"; -pub const MINECRAFT_DIR: &str = "./minecraft"; pub const LIBRARIES_DIR: &str = "./libraries"; pub const ASSETS_DIR: &str = "./assets"; diff --git a/crates/nomi-core/src/game_paths.rs b/crates/nomi-core/src/game_paths.rs index 871c121..e8f3c23 100644 --- a/crates/nomi-core/src/game_paths.rs +++ b/crates/nomi-core/src/game_paths.rs @@ -1,6 +1,9 @@ use std::path::{Path, PathBuf}; -use crate::{ASSETS_DIR, LIBRARIES_DIR, MINECRAFT_DIR}; +use crate::{ + instance::{Instance, InstanceProfileId}, + ASSETS_DIR, LIBRARIES_DIR, +}; #[derive(Debug, Clone)] pub struct GamePaths { @@ -11,14 +14,18 @@ pub struct GamePaths { } impl GamePaths { - pub fn from_instance_path(instance: impl AsRef, game_version: &str) -> Self { + pub fn from_id(id: InstanceProfileId) -> Self { + Self::from_instance_path(Instance::path_from_id(id.instance()), id.profile()) + } + + pub fn from_instance_path(instance: impl AsRef, profile_id: usize) -> Self { let path = instance.as_ref(); Self { game: path.to_path_buf(), assets: ASSETS_DIR.into(), // Is this a good approach? - profile: path.join("profiles").join(game_version), + profile: path.join("profiles").join(format!("{profile_id}")), libraries: LIBRARIES_DIR.into(), } } @@ -52,14 +59,3 @@ impl GamePaths { self.profile.join(format!("{game_version}.jar")) } } - -impl Default for GamePaths { - fn default() -> Self { - Self { - game: MINECRAFT_DIR.into(), - assets: PathBuf::from(MINECRAFT_DIR).join("assets"), - profile: PathBuf::from(MINECRAFT_DIR).join("versions").join("NOMI_DEFAULT"), - libraries: PathBuf::from(MINECRAFT_DIR).join("libraries"), - } - } -} diff --git a/crates/nomi-core/src/loaders/forge.rs b/crates/nomi-core/src/loaders/forge.rs index 0c63add..1708438 100644 --- a/crates/nomi-core/src/loaders/forge.rs +++ b/crates/nomi-core/src/loaders/forge.rs @@ -780,6 +780,8 @@ pub struct ForgeOldLibrary { mod tests { use tracing::{debug, Level}; + use crate::instance::InstanceProfileId; + use super::*; #[tokio::test] @@ -790,10 +792,14 @@ mod tests { #[tokio::test] async fn create_forge_test() { - let recommended = Forge::new("1.7.10", ForgeVersion::Recommended, GamePaths::default()).await.unwrap(); + let recommended = Forge::new("1.7.10", ForgeVersion::Recommended, GamePaths::from_id(InstanceProfileId::ZERO)) + .await + .unwrap(); println!("{recommended:#?}"); - let latest = Forge::new("1.19.2", ForgeVersion::Latest, GamePaths::default()).await.unwrap(); + let latest = Forge::new("1.19.2", ForgeVersion::Latest, GamePaths::from_id(InstanceProfileId::ZERO)) + .await + .unwrap(); println!("{latest:#?}"); } @@ -803,7 +809,9 @@ mod tests { debug!("Test"); - let recommended = Forge::new("1.7.10", ForgeVersion::Recommended, GamePaths::default()).await.unwrap(); + let recommended = Forge::new("1.7.10", ForgeVersion::Recommended, GamePaths::from_id(InstanceProfileId::ZERO)) + .await + .unwrap(); println!("{recommended:#?}"); let io = recommended.io(); diff --git a/crates/nomi-core/tests/forge_new_test.rs b/crates/nomi-core/tests/forge_new_test.rs index 399ce0f..1fd73c5 100644 --- a/crates/nomi-core/tests/forge_new_test.rs +++ b/crates/nomi-core/tests/forge_new_test.rs @@ -10,7 +10,7 @@ use nomi_core::{ }, loaders::forge::{Forge, ForgeVersion}, repository::java_runner::JavaRunner, - DOT_NOMI_JAVA_EXECUTABLE, MINECRAFT_DIR, + DOT_NOMI_JAVA_EXECUTABLE, }; #[tokio::test] @@ -19,10 +19,7 @@ async fn forge_test() { let (tx, _) = tokio::sync::mpsc::channel(100); - let game_paths = GamePaths { - profile: PathBuf::from(MINECRAFT_DIR).join("versions").join("forge-test"), - ..Default::default() - }; + let game_paths = GamePaths::from_id(InstanceProfileId::ZERO); let instance = Profile::builder() .name("forge-test".into()) diff --git a/crates/nomi-core/tests/forge_old_test.rs b/crates/nomi-core/tests/forge_old_test.rs index 120e821..cfc76f5 100644 --- a/crates/nomi-core/tests/forge_old_test.rs +++ b/crates/nomi-core/tests/forge_old_test.rs @@ -11,7 +11,6 @@ use nomi_core::{ }, loaders::forge::{Forge, ForgeVersion}, repository::java_runner::JavaRunner, - MINECRAFT_DIR, }; #[tokio::test] @@ -20,10 +19,7 @@ async fn forge_test() { let (tx, _) = tokio::sync::mpsc::channel(100); - let game_paths = GamePaths { - profile: PathBuf::from(MINECRAFT_DIR).join("versions").join("forge-test"), - ..Default::default() - }; + let game_paths = GamePaths::from_id(InstanceProfileId::ZERO); let instance = Profile::builder() .name("forge-test".into()) diff --git a/crates/nomi-core/tests/instance_test.rs b/crates/nomi-core/tests/instance_test.rs index 035b5c2..be7b499 100644 --- a/crates/nomi-core/tests/instance_test.rs +++ b/crates/nomi-core/tests/instance_test.rs @@ -18,7 +18,7 @@ async fn instance_test() { let mut instance = Instance::new("cool-instance", 0); - let paths = GamePaths::from_instance_path(instance.path(), "1.19.2"); + let paths = GamePaths::from_instance_path(instance.path(), 0); let profile = Profile::builder() .game_paths(paths.clone()) .downloader(Box::new(Vanilla::new("1.19.2", paths.clone()).await.unwrap())) From f918542347c002fa5059dcf531aed586f41a44ce Mon Sep 17 00:00:00 2001 From: Umatriz Date: Mon, 12 Aug 2024 08:44:55 +0300 Subject: [PATCH 05/15] feat: download vanilla when downloading a loader --- crates/client/src/download.rs | 24 ++-- crates/nomi-core/src/instance/builder_ext.rs | 26 +++++ crates/nomi-core/src/instance/marker.rs | 10 +- crates/nomi-core/src/loaders/combined.rs | 110 +++++++++++++++++++ crates/nomi-core/src/loaders/fabric.rs | 12 +- crates/nomi-core/src/loaders/forge.rs | 28 +++-- crates/nomi-core/src/loaders/mod.rs | 7 ++ crates/nomi-core/src/loaders/vanilla.rs | 6 - crates/nomi-core/tests/vanilla_test.rs | 2 +- 9 files changed, 186 insertions(+), 39 deletions(-) create mode 100644 crates/nomi-core/src/loaders/combined.rs diff --git a/crates/client/src/download.rs b/crates/client/src/download.rs index 7df8738..6bf69a1 100644 --- a/crates/client/src/download.rs +++ b/crates/client/src/download.rs @@ -12,6 +12,7 @@ use nomi_core::{ game_paths::GamePaths, instance::{launch::LaunchSettings, Profile}, loaders::{ + combined::VanillaCombinedDownloader, fabric::Fabric, forge::{Forge, ForgeVersion}, vanilla::Vanilla, @@ -49,14 +50,21 @@ async fn try_download_version(profile: Arc>, progress_shar .version(version_profile.version().to_string()) .game_paths(game_paths.clone()); + let combined_downloader = VanillaCombinedDownloader::new(version_profile.version(), game_paths.clone()).await?; let instance = match loader { - Loader::Vanilla => builder.downloader(Box::new(Vanilla::new(version_profile.version(), game_paths.clone()).await?)), - Loader::Fabric { version } => builder.downloader(Box::new( - Fabric::new(version_profile.version(), version.as_ref(), game_paths.clone()).await?, - )), - Loader::Forge => builder.downloader(Box::new( - Forge::new(version_profile.version(), ForgeVersion::Recommended, game_paths.clone()).await?, - )), + Loader::Vanilla => builder.downloader(Box::new(combined_downloader)), + Loader::Fabric { version } => { + let combined = combined_downloader + .with_loader(|game_version, game_paths| Fabric::new(game_version, version.as_ref(), game_paths)) + .await?; + builder.downloader(Box::new(combined)) + } + Loader::Forge => { + let combined = combined_downloader + .with_loader(|game_version, game_paths| Forge::new(game_version, ForgeVersion::Recommended, game_paths)) + .await?; + builder.downloader(Box::new(combined)) + } } .build(); @@ -70,7 +78,7 @@ async fn try_download_version(profile: Arc>, progress_shar let instance = instance.downloader(); let io = instance.io(); - let downloader: Box> = instance.into_downloader(); + let downloader = instance.into_downloader(); io.await?; let downloader = DownloadQueue::new().with_downloader_dyn(downloader); diff --git a/crates/nomi-core/src/instance/builder_ext.rs b/crates/nomi-core/src/instance/builder_ext.rs index b4db364..3fe17f8 100644 --- a/crates/nomi-core/src/instance/builder_ext.rs +++ b/crates/nomi-core/src/instance/builder_ext.rs @@ -1,3 +1,5 @@ +use crate::loaders::{combined::VanillaCombinedDownloader, vanilla::Vanilla, ToLoaderProfile}; + use super::launch::{LaunchInstanceBuilder, LaunchSettings}; pub trait LaunchInstanceBuilderExt { @@ -5,3 +7,27 @@ pub trait LaunchInstanceBuilderExt { } const _: Option> = None; + +// Unique case where we do not have a profile. +// TODO: Maybe make a profile for Vanilla and get rid of manifest? +impl LaunchInstanceBuilderExt for Vanilla { + fn insert(&self, builder: LaunchInstanceBuilder) -> LaunchInstanceBuilder { + builder + } +} + +// If the generic is `()` that means we are downloading `Vanilla` +impl LaunchInstanceBuilderExt for VanillaCombinedDownloader<()> { + fn insert(&self, builder: LaunchInstanceBuilder) -> LaunchInstanceBuilder { + builder + } +} + +impl LaunchInstanceBuilderExt for L +where + L: ToLoaderProfile, +{ + fn insert(&self, builder: LaunchInstanceBuilder) -> LaunchInstanceBuilder { + builder.profile(self.to_profile()) + } +} diff --git a/crates/nomi-core/src/instance/marker.rs b/crates/nomi-core/src/instance/marker.rs index ef3e327..8e6f4c5 100644 --- a/crates/nomi-core/src/instance/marker.rs +++ b/crates/nomi-core/src/instance/marker.rs @@ -1,8 +1,14 @@ use std::fmt::Debug; -use crate::downloads::traits::{DownloadResult, Downloader}; +use crate::{ + downloads::traits::{DownloadResult, Downloader}, + loaders::vanilla::Vanilla, +}; -use super::builder_ext::LaunchInstanceBuilderExt; +use super::{ + builder_ext::LaunchInstanceBuilderExt, + launch::{LaunchInstanceBuilder, LaunchSettings}, +}; pub trait ProfileDownloader: LaunchInstanceBuilderExt + Downloader + Debug + Send + Sync { fn into_downloader(self: Box) -> Box>; diff --git a/crates/nomi-core/src/loaders/combined.rs b/crates/nomi-core/src/loaders/combined.rs new file mode 100644 index 0000000..c4a7da3 --- /dev/null +++ b/crates/nomi-core/src/loaders/combined.rs @@ -0,0 +1,110 @@ +use std::{fmt::Debug, future::Future}; + +use crate::{ + downloads::{ + progress::ProgressSender, + traits::{DownloadResult, Downloader}, + DownloadQueue, + }, + game_paths::GamePaths, + instance::marker::ProfileDownloader, + PinnedFutureWithBounds, +}; + +use super::{vanilla::Vanilla, ToLoaderProfile}; + +pub struct VanillaCombinedDownloader { + version: String, + game_paths: GamePaths, + vanilla: Vanilla, + loader: T, +} + +impl Debug for VanillaCombinedDownloader { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("VanillaCombinedDownloader") + .field("version", &self.version) + .field("game_paths", &self.game_paths) + .field("vanilla", &self.vanilla) + .field("loader", &"(loader)") + .finish() + } +} + +impl VanillaCombinedDownloader<()> { + pub async fn new(game_version: impl Into, game_paths: GamePaths) -> anyhow::Result { + let version = game_version.into(); + let vanilla = Vanilla::new(&version, game_paths.clone()).await?; + + Ok(Self { + version, + game_paths, + vanilla, + loader: (), + }) + } + + pub async fn with_loader(self, fun: F) -> anyhow::Result> + where + F: FnOnce(String, GamePaths) -> Fut, + Fut: Future>, + T: ProfileDownloader, + { + let loader = (fun)(self.version.clone(), self.game_paths.clone()).await?; + + Ok(VanillaCombinedDownloader { + version: self.version, + game_paths: self.game_paths, + vanilla: self.vanilla, + loader, + }) + } +} + +impl ToLoaderProfile for VanillaCombinedDownloader { + fn to_profile(&self) -> crate::instance::loader::LoaderProfile { + self.loader.to_profile() + } +} + +#[async_trait::async_trait] +impl Downloader for VanillaCombinedDownloader { + type Data = DownloadResult; + + fn total(&self) -> u32 { + self.vanilla.total() + self.loader.total() + } + + async fn download(self: Box, sender: &dyn ProgressSender) { + let downloader = DownloadQueue::new().with_downloader(self.vanilla).with_downloader(self.loader); + let downloader = Box::new(downloader); + downloader.download(sender).await; + } + + fn io(&self) -> PinnedFutureWithBounds> { + let vanilla_io = self.vanilla.io(); + let loader_io = self.loader.io(); + + Box::pin(async move { + vanilla_io.await?; + loader_io.await + }) + } +} + +#[async_trait::async_trait] +impl Downloader for VanillaCombinedDownloader<()> { + type Data = DownloadResult; + + fn total(&self) -> u32 { + self.vanilla.total() + } + + async fn download(self: Box, sender: &dyn ProgressSender) { + Box::new(self.vanilla).download(sender).await; + } + + fn io(&self) -> PinnedFutureWithBounds> { + self.vanilla.io() + } +} diff --git a/crates/nomi-core/src/loaders/fabric.rs b/crates/nomi-core/src/loaders/fabric.rs index 57d1de0..7be3bbb 100644 --- a/crates/nomi-core/src/loaders/fabric.rs +++ b/crates/nomi-core/src/loaders/fabric.rs @@ -31,6 +31,8 @@ use crate::{ PinnedFutureWithBounds, }; +use super::ToLoaderProfile; + #[derive(Debug)] pub struct Fabric { pub game_version: String, @@ -91,8 +93,10 @@ impl Fabric { libraries_downloader, }) } +} - pub fn to_profile(&self) -> LoaderProfile { +impl ToLoaderProfile for Fabric { + fn to_profile(&self) -> LoaderProfile { LoaderProfile { loader: Loader::Fabric { version: Some(self.fabric_version.clone()), @@ -151,9 +155,3 @@ impl Downloader for Fabric { Box::pin(fut) } } - -impl LaunchInstanceBuilderExt for Fabric { - fn insert(&self, builder: LaunchInstanceBuilder) -> LaunchInstanceBuilder { - builder.profile(self.to_profile()) - } -} diff --git a/crates/nomi-core/src/loaders/forge.rs b/crates/nomi-core/src/loaders/forge.rs index 1708438..6040791 100644 --- a/crates/nomi-core/src/loaders/forge.rs +++ b/crates/nomi-core/src/loaders/forge.rs @@ -38,6 +38,8 @@ use crate::{ PinnedFutureWithBounds, DOT_NOMI_TEMP_DIR, }; +use super::ToLoaderProfile; + const FORGE_REPO_URL: &str = "https://maven.minecraftforge.net"; const _NEO_FORGE_REPO_URL: &str = "https://maven.neoforged.net/releases/"; @@ -69,15 +71,6 @@ pub struct Forge { } impl Forge { - pub fn to_profile(&self) -> LoaderProfile { - LoaderProfile { - loader: Loader::Forge, - main_class: self.profile.main_class().to_string(), - args: self.profile.simple_args(), - libraries: self.profile.simple_libraries(), - } - } - #[tracing::instrument(skip_all, err)] pub async fn get_versions(game_version: impl Into) -> anyhow::Result> { let game_version = game_version.into(); @@ -291,6 +284,17 @@ impl Forge { } } +impl ToLoaderProfile for Forge { + fn to_profile(&self) -> LoaderProfile { + LoaderProfile { + loader: Loader::Forge, + main_class: self.profile.main_class().to_string(), + args: self.profile.simple_args(), + libraries: self.profile.simple_libraries(), + } + } +} + fn forge_installer_path(game_version: &str, forge_version: &str) -> PathBuf { Path::new(DOT_NOMI_TEMP_DIR).join(format!("{game_version}-{forge_version}.jar")) } @@ -389,12 +393,6 @@ impl Downloader for Forge { } } -impl LaunchInstanceBuilderExt for Forge { - fn insert(&self, builder: LaunchInstanceBuilder) -> LaunchInstanceBuilder { - builder.profile(self.to_profile()) - } -} - #[derive(Debug, Clone)] struct ProcessorsData { processors: Vec, diff --git a/crates/nomi-core/src/loaders/mod.rs b/crates/nomi-core/src/loaders/mod.rs index bf21a6b..59baf14 100644 --- a/crates/nomi-core/src/loaders/mod.rs +++ b/crates/nomi-core/src/loaders/mod.rs @@ -1,3 +1,10 @@ +use crate::instance::loader::LoaderProfile; + +pub mod combined; pub mod fabric; pub mod forge; pub mod vanilla; + +pub trait ToLoaderProfile { + fn to_profile(&self) -> LoaderProfile; +} diff --git a/crates/nomi-core/src/loaders/vanilla.rs b/crates/nomi-core/src/loaders/vanilla.rs index 61aeb4c..61dd5b1 100644 --- a/crates/nomi-core/src/loaders/vanilla.rs +++ b/crates/nomi-core/src/loaders/vanilla.rs @@ -141,9 +141,3 @@ impl Downloader for Vanilla { Box::pin(fut) } } - -impl LaunchInstanceBuilderExt for Vanilla { - fn insert(&self, builder: LaunchInstanceBuilder) -> LaunchInstanceBuilder { - builder - } -} diff --git a/crates/nomi-core/tests/vanilla_test.rs b/crates/nomi-core/tests/vanilla_test.rs index ea36756..d458029 100644 --- a/crates/nomi-core/tests/vanilla_test.rs +++ b/crates/nomi-core/tests/vanilla_test.rs @@ -1,4 +1,4 @@ -use nomi_core::{instance::launch::LaunchSettings, repository::java_runner::JavaRunner}; +use nomi_core::instance::launch::LaunchSettings; #[tokio::test] async fn vanilla_test() { From 2179ce6d28fca70202f4e54868eae956f460eb66 Mon Sep 17 00:00:00 2001 From: Umatriz Date: Mon, 12 Aug 2024 10:20:34 +0300 Subject: [PATCH 06/15] fix: progress bar now updates even when the cursor does not move --- Cargo.lock | 2 +- crates/client/src/cache.rs | 25 +++++++++++++++++++ crates/client/src/download.rs | 24 ++++++++---------- crates/client/src/main.rs | 3 ++- crates/client/src/mods.rs | 13 +++++----- crates/client/src/states.rs | 5 ++-- .../client/src/views/downloading_progress.rs | 2 +- crates/client/src/views/mods_manager.rs | 3 ++- crates/client/src/views/profile_info.rs | 8 +++--- crates/client/src/views/profiles.rs | 13 ++++++++-- crates/client/src/views/settings.rs | 2 +- crates/nomi-core/src/downloads/progress.rs | 24 ++++++++++++++++++ crates/nomi-core/src/instance/marker.rs | 10 ++------ crates/nomi-core/src/loaders/fabric.rs | 6 +---- crates/nomi-core/src/loaders/forge.rs | 6 +---- crates/nomi-core/src/loaders/vanilla.rs | 4 --- 16 files changed, 94 insertions(+), 56 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 704a1f8..67b112b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1354,7 +1354,7 @@ dependencies = [ [[package]] name = "egui_task_manager" version = "0.1.1" -source = "git+https://github.com/Umatriz/egui-task-manager#19b25af2e22e8ef77ffacb012a4057215663ef7a" +source = "git+https://github.com/Umatriz/egui-task-manager#fee4f0449da1f8a60849e18d384f1d39b9fc576c" dependencies = [ "egui", "tokio", diff --git a/crates/client/src/cache.rs b/crates/client/src/cache.rs index e1b0c5f..3ddfea4 100644 --- a/crates/client/src/cache.rs +++ b/crates/client/src/cache.rs @@ -1,3 +1,5 @@ +use eframe::egui::Ui; +use itertools::Itertools; use std::{ collections::HashMap, path::PathBuf, @@ -16,6 +18,12 @@ pub struct GlobalCache { profiles: HashMap>>, } +impl Default for GlobalCache { + fn default() -> Self { + Self::new() + } +} + impl GlobalCache { pub fn new() -> Self { Self { profiles: HashMap::new() } @@ -33,4 +41,21 @@ impl GlobalCache { }), } } + + fn loaded_profiles(&self) -> Vec>> { + self.profiles.values().cloned().collect_vec() + } +} + +pub fn ui_for_loaded_profiles(ui: &mut Ui) { + ui.vertical(|ui| { + for profile in GLOBAL_CACHE.read().loaded_profiles() { + let profile = profile.read(); + ui.horizontal(|ui| { + ui.label(&profile.profile.name); + ui.label(profile.profile.version()); + }); + ui.separator(); + } + }); } diff --git a/crates/client/src/download.rs b/crates/client/src/download.rs index 6bf69a1..17bf0c8 100644 --- a/crates/client/src/download.rs +++ b/crates/client/src/download.rs @@ -1,21 +1,17 @@ use std::{path::PathBuf, sync::Arc}; use anyhow::anyhow; +use eframe::egui::Context; use egui_task_manager::TaskProgressShared; use nomi_core::{ configs::profile::{Loader, ProfileState}, - downloads::{ - progress::MappedSender, - traits::{DownloadResult, Downloader}, - AssetsDownloader, DownloadQueue, - }, + downloads::{progress::MappedSender, traits::Downloader, AssetsDownloader, DownloadQueue}, game_paths::GamePaths, instance::{launch::LaunchSettings, Profile}, loaders::{ combined::VanillaCombinedDownloader, fabric::Fabric, forge::{Forge, ForgeVersion}, - vanilla::Vanilla, }, state::get_launcher_manifest, }; @@ -23,11 +19,11 @@ use parking_lot::RwLock; use crate::{errors_pool::ErrorPoolExt, views::ModdedProfile}; -pub async fn task_download_version(profile: Arc>, progress_shared: TaskProgressShared) -> Option<()> { - try_download_version(profile, progress_shared).await.report_error() +pub async fn task_download_version(progress_shared: TaskProgressShared, ctx: Context, profile: Arc>) -> Option<()> { + try_download_version(progress_shared, ctx, profile).await.report_error() } -async fn try_download_version(profile: Arc>, progress_shared: TaskProgressShared) -> anyhow::Result<()> { +async fn try_download_version(progress_shared: TaskProgressShared, ctx: Context, profile: Arc>) -> anyhow::Result<()> { let launch_instance = { let version_profile = { let version_profile = &profile.read().profile; @@ -85,7 +81,7 @@ async fn try_download_version(profile: Arc>, progress_shar let _ = progress_shared.set_total(downloader.total()); - let mapped_sender = MappedSender::new_progress_mapper(Box::new(progress_shared.sender())); + let mapped_sender = MappedSender::new_progress_mapper(Box::new(progress_shared.sender())).with_side_effect(move || ctx.request_repaint()); Box::new(downloader).download(&mapped_sender).await; @@ -97,11 +93,11 @@ async fn try_download_version(profile: Arc>, progress_shar Ok(()) } -pub async fn task_assets(version: String, assets_dir: PathBuf, progress_shared: TaskProgressShared) -> Option<()> { - try_assets(version, assets_dir, progress_shared).await.report_error() +pub async fn task_assets(progress_shared: TaskProgressShared, ctx: Context, version: String, assets_dir: PathBuf) -> Option<()> { + try_assets(progress_shared, ctx, version, assets_dir).await.report_error() } -async fn try_assets(version: String, assets_dir: PathBuf, progress_shared: TaskProgressShared) -> anyhow::Result<()> { +async fn try_assets(progress_shared: TaskProgressShared, ctx: Context, version: String, assets_dir: PathBuf) -> anyhow::Result<()> { let manifest = get_launcher_manifest().await?; let version_manifest = manifest.get_version_manifest(version).await?; @@ -117,7 +113,7 @@ async fn try_assets(version: String, assets_dir: PathBuf, progress_shared: TaskP let _ = progress_shared.set_total(downloader.total()); - let mapped_sender = MappedSender::new_progress_mapper(Box::new(progress_shared.sender())); + let mapped_sender = MappedSender::new_progress_mapper(Box::new(progress_shared.sender())).with_side_effect(move || ctx.request_repaint()); Box::new(downloader).download(&mapped_sender).await; diff --git a/crates/client/src/main.rs b/crates/client/src/main.rs index b021521..b3c0677 100644 --- a/crates/client/src/main.rs +++ b/crates/client/src/main.rs @@ -1,6 +1,7 @@ // Remove console window in release builds #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] +use cache::ui_for_loaded_profiles; use collections::{AssetsCollection, GameDownloadingCollection, GameRunnerCollection, JavaCollection}; use context::MyContext; use eframe::{ @@ -160,7 +161,7 @@ impl eframe::App for MyTabs { ctx.set_pixels_per_point(self.context.states.client_settings.pixels_per_point); if !self.context.states.java.is_downloaded { - self.context.states.java.download_java(&mut self.context.manager); + self.context.states.java.download_java(&mut self.context.manager, ctx.clone()); } egui::TopBottomPanel::top("top_panel_id").show(ctx, |ui| { diff --git a/crates/client/src/mods.rs b/crates/client/src/mods.rs index 51b2a1e..48bd013 100644 --- a/crates/client/src/mods.rs +++ b/crates/client/src/mods.rs @@ -4,6 +4,7 @@ use std::{ sync::{mpsc::Sender, Arc}, }; +use eframe::egui::Context; use egui_task_manager::{Progress, TaskProgressShared}; use itertools::Itertools; use nomi_core::{ @@ -56,7 +57,7 @@ pub struct SimpleDependency { pub is_required: bool, } -pub async fn download_added_mod(progress: TaskProgressShared, target_path: PathBuf, files: Vec) { +pub async fn download_added_mod(progress: TaskProgressShared, ctx: Context, target_path: PathBuf, files: Vec) { let _ = progress.set_total(files.len() as u32); let mut set = DownloadSet::new(); @@ -68,7 +69,7 @@ pub async fn download_added_mod(progress: TaskProgressShared, target_path: PathB set.add(Box::new(downloader)); } - let sender = MappedSender::new_progress_mapper(Box::new(progress.sender())); + let sender = MappedSender::new_progress_mapper(Box::new(progress.sender())).with_side_effect(move || ctx.request_repaint()); Box::new(set).download(&sender).await; } @@ -106,7 +107,7 @@ pub async fn proceed_deps(dist: &mut Vec, version: Arc, PathBuf, String)>) -> anyhow::Result> { +pub async fn download_mods(progress: TaskProgressShared, ctx: Context, versions: Vec<(Arc, PathBuf, String)>) -> anyhow::Result> { let _ = progress.set_total( versions .iter() @@ -116,14 +117,14 @@ pub async fn download_mods(progress: TaskProgressShared, versions: Vec<(Arc>, dir: PathBuf, name: String, version: Arc) -> anyhow::Result { +pub async fn download_mod(sender: Sender>, ctx: Context, dir: PathBuf, name: String, version: Arc) -> anyhow::Result { let mut set = DownloadSet::new(); let mut downloaded_files = Vec::new(); @@ -150,7 +151,7 @@ pub async fn download_mod(sender: Sender>, dir: PathBuf, name: set.add(Box::new(downloader)); } - let sender = MappedSender::new_progress_mapper(Box::new(sender)); + let sender = MappedSender::new_progress_mapper(Box::new(sender)).with_side_effect(move || ctx.request_repaint()); Box::new(set).download(&sender).await; diff --git a/crates/client/src/states.rs b/crates/client/src/states.rs index d5c867e..0591942 100644 --- a/crates/client/src/states.rs +++ b/crates/client/src/states.rs @@ -1,5 +1,6 @@ use std::path::PathBuf; +use eframe::egui::Context; use egui_task_manager::{Caller, Task, TaskManager}; use nomi_core::{ downloads::{java::JavaDownloader, progress::MappedSender, traits::Downloader}, @@ -71,7 +72,7 @@ impl JavaState { } } - pub fn download_java(&mut self, manager: &mut TaskManager) { + pub fn download_java(&mut self, manager: &mut TaskManager, ctx: Context) { info!("Downloading Java"); self.is_downloaded = true; @@ -83,7 +84,7 @@ impl JavaState { let io = downloader.io(); - let mapped_sender = MappedSender::new_progress_mapper(Box::new(progress.sender())); + let mapped_sender = MappedSender::new_progress_mapper(Box::new(progress.sender())).with_side_effect(move || ctx.request_repaint()); Box::new(downloader).download(&mapped_sender).await; diff --git a/crates/client/src/views/downloading_progress.rs b/crates/client/src/views/downloading_progress.rs index ea4664a..bc9cfa6 100644 --- a/crates/client/src/views/downloading_progress.rs +++ b/crates/client/src/views/downloading_progress.rs @@ -13,7 +13,7 @@ impl View for DownloadingProgress<'_> { ui.with_layout(Layout::top_down_justified(egui::Align::Min), |ui| { for collection in self.manager.iter_collections() { for task in collection.iter_tasks() { - task.ui(ui) + ui.group(|ui| task.ui(ui)); } } }); diff --git a/crates/client/src/views/mods_manager.rs b/crates/client/src/views/mods_manager.rs index 43dbc3b..aad6f5b 100644 --- a/crates/client/src/views/mods_manager.rs +++ b/crates/client/src/views/mods_manager.rs @@ -595,6 +595,7 @@ impl View for ModManager<'_> { let lock = profile.read(); lock.profile.id }; + let ctx = ui.ctx().clone(); let download_mod = Task::new( "Download mods", Caller::progressing(move |progress| async move { @@ -625,7 +626,7 @@ impl View for ModManager<'_> { versions_with_paths.push(data); } - let mods = download_mods(progress, versions_with_paths).await.report_error(); + let mods = download_mods(progress, ctx, versions_with_paths).await.report_error(); if let Some((mut profile, mods)) = mods.map(|mods| (profile.write(), mods)) { if matches!(project_type, ProjectType::Mod) { diff --git a/crates/client/src/views/profile_info.rs b/crates/client/src/views/profile_info.rs index 6da100f..43154d1 100644 --- a/crates/client/src/views/profile_info.rs +++ b/crates/client/src/views/profile_info.rs @@ -1,7 +1,4 @@ -use std::{ - collections::HashSet, - sync::Arc, -}; +use std::{collections::HashSet, sync::Arc}; use eframe::egui::{self, Color32, Id, RichText, TextEdit}; use egui_task_manager::{Caller, Task, TaskManager}; @@ -462,10 +459,11 @@ impl View for ProfileInfo<'_> { let profile_id = self.profile.read().profile.id; let files = m.files.clone(); let project_id = m.project_id.clone(); + let ctx = ui.ctx().clone(); let download_task = Task::new( "Download mod", Caller::progressing(move |progress| async move { - download_added_mod(progress, mods_stash_path_for_profile(profile_id), files).await; + download_added_mod(progress, ctx, mods_stash_path_for_profile(profile_id), files).await; (profile_id, project_id) }), ); diff --git a/crates/client/src/views/profiles.rs b/crates/client/src/views/profiles.rs index ef4c55d..08b7e8e 100644 --- a/crates/client/src/views/profiles.rs +++ b/crates/client/src/views/profiles.rs @@ -52,6 +52,12 @@ pub struct ProfilesState { pub instances: InstancesConfig, } +impl Default for ProfilesState { + fn default() -> Self { + Self::new() + } +} + impl ProfilesState { pub fn new() -> Self { Self { @@ -155,6 +161,7 @@ impl InstancesConfig { } impl Instances<'_> { + // TODO: It requires profile to be loaded fn profile_action_ui(&mut self, ui: &mut Ui, profile_lock: Arc>) { let profile = profile_lock.read(); match &profile.profile.state { @@ -204,18 +211,20 @@ impl Instances<'_> { let game_version = profile.profile.version().to_owned(); let game_paths = GamePaths::from_id(profile.profile.id); + let ctx = ui.ctx().clone(); let assets_task = Task::new( format!("Assets ({})", profile.profile.version()), - Caller::progressing(|progress| task_assets(game_version, game_paths.assets, progress)), + Caller::progressing(|progress| task_assets(progress, ctx, game_version, game_paths.assets)), ); self.manager.push_task::(assets_task); let profile_clone = profile_lock.clone(); let id = profile.profile.id; + let ctx = ui.ctx().clone(); let game_task = Task::new( format!("Downloading version {}", profile.profile.version()), - Caller::progressing(move |progress| async move { task_download_version(profile_clone, progress).await.map(|()| id) }), + Caller::progressing(move |progress| async move { task_download_version(progress, ctx, profile_clone).await.map(|()| id) }), ); self.manager.push_task::(game_task); } diff --git a/crates/client/src/views/settings.rs b/crates/client/src/views/settings.rs index 89ff523..81fd944 100644 --- a/crates/client/src/views/settings.rs +++ b/crates/client/src/views/settings.rs @@ -147,7 +147,7 @@ impl View for SettingsPage<'_> { .on_hover_text("Pressing this button will start the Java downloading process and add the downloaded binary as selected") .clicked() { - self.java_state.download_java(self.manager); + self.java_state.download_java(self.manager, ui.ctx().clone()); self.settings_state.java = JavaRunner::path(PathBuf::from(DOT_NOMI_JAVA_EXECUTABLE)); self.settings_state.update_config(); } diff --git a/crates/nomi-core/src/downloads/progress.rs b/crates/nomi-core/src/downloads/progress.rs index ebd21b8..03e61bd 100644 --- a/crates/nomi-core/src/downloads/progress.rs +++ b/crates/nomi-core/src/downloads/progress.rs @@ -22,6 +22,7 @@ impl ProgressSender

for std::sync::mpsc::Sender

{ pub struct MappedSender { inner: Box>, mapper: Box T + Sync + Send>, + side_effect: Option>, } #[async_trait::async_trait] @@ -29,6 +30,10 @@ impl ProgressSender for MappedSender { async fn update(&self, data: I) { let mapped = (self.mapper)(data); self.inner.update(mapped).await; + + if let Some(side_effect) = self.side_effect.as_ref() { + (side_effect)() + } } } @@ -40,8 +45,15 @@ impl MappedSender { Self { inner: sender, mapper: Box::new(mapper), + side_effect: None, } } + + #[must_use] + pub fn with_side_effect(mut self, side_effect: impl Fn() + Send + Sync + 'static) -> Self { + self.side_effect = Some(Box::new(side_effect)); + self + } } impl MappedSender> { @@ -49,6 +61,18 @@ impl MappedSender> { Self { inner: sender, mapper: Box::new(|value| Box::new(value) as Box), + side_effect: None, + } + } + + pub fn new_progress_mapper_with_side_effect( + sender: Box>>, + side_effect: impl Fn() + Send + Sync + 'static, + ) -> Self { + Self { + inner: sender, + mapper: Box::new(|value| Box::new(value) as Box), + side_effect: Some(Box::new(side_effect)), } } } diff --git a/crates/nomi-core/src/instance/marker.rs b/crates/nomi-core/src/instance/marker.rs index 8e6f4c5..ef3e327 100644 --- a/crates/nomi-core/src/instance/marker.rs +++ b/crates/nomi-core/src/instance/marker.rs @@ -1,14 +1,8 @@ use std::fmt::Debug; -use crate::{ - downloads::traits::{DownloadResult, Downloader}, - loaders::vanilla::Vanilla, -}; +use crate::downloads::traits::{DownloadResult, Downloader}; -use super::{ - builder_ext::LaunchInstanceBuilderExt, - launch::{LaunchInstanceBuilder, LaunchSettings}, -}; +use super::builder_ext::LaunchInstanceBuilderExt; pub trait ProfileDownloader: LaunchInstanceBuilderExt + Downloader + Debug + Send + Sync { fn into_downloader(self: Box) -> Box>; diff --git a/crates/nomi-core/src/loaders/fabric.rs b/crates/nomi-core/src/loaders/fabric.rs index 7be3bbb..48dd7f3 100644 --- a/crates/nomi-core/src/loaders/fabric.rs +++ b/crates/nomi-core/src/loaders/fabric.rs @@ -15,11 +15,7 @@ use crate::{ }, fs::write_to_file, game_paths::GamePaths, - instance::{ - builder_ext::LaunchInstanceBuilderExt, - launch::{LaunchInstanceBuilder, LaunchSettings}, - loader::LoaderProfile, - }, + instance::loader::LoaderProfile, maven_data::{MavenArtifact, MavenData}, repository::{ fabric_meta::FabricVersions, diff --git a/crates/nomi-core/src/loaders/forge.rs b/crates/nomi-core/src/loaders/forge.rs index 6040791..53bf492 100644 --- a/crates/nomi-core/src/loaders/forge.rs +++ b/crates/nomi-core/src/loaders/forge.rs @@ -22,11 +22,7 @@ use crate::{ DownloadQueue, FileDownloader, LibrariesDownloader, LibrariesMapper, }, game_paths::GamePaths, - instance::{ - builder_ext::LaunchInstanceBuilderExt, - launch::{LaunchInstanceBuilder, LaunchSettings, CLASSPATH_SEPARATOR}, - loader::LoaderProfile, - }, + instance::{launch::CLASSPATH_SEPARATOR, loader::LoaderProfile}, loaders::vanilla::VanillaLibrariesMapper, maven_data::{MavenArtifact, MavenData}, repository::{ diff --git a/crates/nomi-core/src/loaders/vanilla.rs b/crates/nomi-core/src/loaders/vanilla.rs index 61dd5b1..6a5c0ac 100644 --- a/crates/nomi-core/src/loaders/vanilla.rs +++ b/crates/nomi-core/src/loaders/vanilla.rs @@ -16,10 +16,6 @@ use crate::{ }, fs::write_to_file, game_paths::GamePaths, - instance::{ - builder_ext::LaunchInstanceBuilderExt, - launch::{LaunchInstanceBuilder, LaunchSettings}, - }, repository::manifest::{Classifiers, DownloadFile, Library, Manifest}, state::get_launcher_manifest, PinnedFutureWithBounds, From 1bab61d8792043140ffdd62ab46caa7f14e22a49 Mon Sep 17 00:00:00 2001 From: Umatriz Date: Tue, 13 Aug 2024 17:46:51 +0300 Subject: [PATCH 07/15] forge does not work --- crates/client/src/collections.rs | 7 + crates/client/src/download.rs | 25 ++- crates/client/src/errors_pool.rs | 28 +-- crates/client/src/main.rs | 43 ++--- crates/client/src/states.rs | 4 +- crates/client/src/views/add_profile_menu.rs | 41 +++-- crates/client/src/views/mods_manager.rs | 2 +- crates/client/src/views/profile_info.rs | 9 + crates/client/src/views/profiles.rs | 159 +++++++++--------- .../src/downloads/downloaders/assets.rs | 6 +- crates/nomi-core/src/game_paths.rs | 10 ++ crates/nomi-core/src/instance/mod.rs | 8 + crates/nomi-core/src/loaders/forge.rs | 72 +++++--- crates/nomi-core/tests/forge_new_test.rs | 27 +-- crates/nomi-core/tests/forge_old_test.rs | 4 +- 15 files changed, 253 insertions(+), 192 deletions(-) diff --git a/crates/client/src/collections.rs b/crates/client/src/collections.rs index bd900c8..5df42f0 100644 --- a/crates/client/src/collections.rs +++ b/crates/client/src/collections.rs @@ -87,6 +87,13 @@ impl<'c> TasksCollection<'c> for GameDownloadingCollection { Handler::new(|id| { if let Some(id) = id { context.update_profile_config(id).report_error(); + if let Some(instance) = context.find_instance(id.instance()) { + if let Some(profile) = instance.write().find_profile_mut(id) { + profile.is_downloaded = true + }; + + instance.read().write_blocking().report_error(); + } } }) } diff --git a/crates/client/src/download.rs b/crates/client/src/download.rs index 17bf0c8..2a18a68 100644 --- a/crates/client/src/download.rs +++ b/crates/client/src/download.rs @@ -13,17 +13,28 @@ use nomi_core::{ fabric::Fabric, forge::{Forge, ForgeVersion}, }, + repository::java_runner::JavaRunner, state::get_launcher_manifest, }; use parking_lot::RwLock; use crate::{errors_pool::ErrorPoolExt, views::ModdedProfile}; -pub async fn task_download_version(progress_shared: TaskProgressShared, ctx: Context, profile: Arc>) -> Option<()> { - try_download_version(progress_shared, ctx, profile).await.report_error() +pub async fn task_download_version( + progress_shared: TaskProgressShared, + ctx: Context, + profile: Arc>, + java_runner: JavaRunner, +) -> Option<()> { + try_download_version(progress_shared, ctx, profile, java_runner).await.report_error() } -async fn try_download_version(progress_shared: TaskProgressShared, ctx: Context, profile: Arc>) -> anyhow::Result<()> { +async fn try_download_version( + progress_shared: TaskProgressShared, + ctx: Context, + profile: Arc>, + java_runner: JavaRunner, +) -> anyhow::Result<()> { let launch_instance = { let version_profile = { let version_profile = &profile.read().profile; @@ -57,7 +68,7 @@ async fn try_download_version(progress_shared: TaskProgressShared, ctx: Context, } Loader::Forge => { let combined = combined_downloader - .with_loader(|game_version, game_paths| Forge::new(game_version, ForgeVersion::Recommended, game_paths)) + .with_loader(|game_version, game_paths| Forge::new(game_version, ForgeVersion::Recommended, game_paths, java_runner)) .await?; builder.downloader(Box::new(combined)) } @@ -75,15 +86,13 @@ async fn try_download_version(progress_shared: TaskProgressShared, ctx: Context, let instance = instance.downloader(); let io = instance.io(); let downloader = instance.into_downloader(); - io.await?; - - let downloader = DownloadQueue::new().with_downloader_dyn(downloader); let _ = progress_shared.set_total(downloader.total()); let mapped_sender = MappedSender::new_progress_mapper(Box::new(progress_shared.sender())).with_side_effect(move || ctx.request_repaint()); + downloader.download(&mapped_sender).await; - Box::new(downloader).download(&mapped_sender).await; + io.await?; launch_instance }; diff --git a/crates/client/src/errors_pool.rs b/crates/client/src/errors_pool.rs index cee9f64..81d1793 100644 --- a/crates/client/src/errors_pool.rs +++ b/crates/client/src/errors_pool.rs @@ -1,10 +1,10 @@ use std::{ fmt::{Debug, Display}, - sync::{Arc, RwLock}, + sync::Arc, }; use once_cell::sync::Lazy; -use tracing::error; +use parking_lot::RwLock; pub static ERRORS_POOL: Lazy>> = Lazy::new(|| Arc::new(RwLock::new(ErrorsPool::default()))); @@ -46,12 +46,6 @@ impl ErrorsPool { } } -#[derive(Default)] -pub struct ErrorsPoolState { - pub is_window_open: bool, - pub number_of_errors: usize, -} - pub trait ErrorPoolExt { fn report_error(self) -> Option; fn report_error_with_context(self, context: C) -> Option @@ -67,13 +61,8 @@ where match self { Ok(value) => Some(value), Err(error) => { - if let Ok(mut pool) = ERRORS_POOL - .clone() - .write() - .inspect_err(|err| error!("Unable to write into the `ERRORS_POOL`\n{}", err)) - { - pool.push_error(error); - } + let mut pool = ERRORS_POOL.write(); + pool.push_error(error); None } } @@ -86,13 +75,8 @@ where match self { Ok(value) => Some(value), Err(error) => { - if let Ok(mut pool) = ERRORS_POOL - .clone() - .write() - .inspect_err(|err| error!("Unable to write into the `ERRORS_POOL`\n{}", err)) - { - pool.push_error(anyhow::Error::msg(error).context(context)); - } + let mut pool = ERRORS_POOL.write(); + pool.push_error(anyhow::Error::msg(error).context(context)); None } } diff --git a/crates/client/src/main.rs b/crates/client/src/main.rs index b3c0677..c61f9b2 100644 --- a/crates/client/src/main.rs +++ b/crates/client/src/main.rs @@ -164,6 +164,8 @@ impl eframe::App for MyTabs { self.context.states.java.download_java(&mut self.context.manager, ctx.clone()); } + // egui::Window::new("Loaded profiles").show(ctx, |ui| ui_for_loaded_profiles(ui)); + egui::TopBottomPanel::top("top_panel_id").show(ctx, |ui| { ui.with_layout(Layout::left_to_right(Align::Center).with_cross_align(Align::Center), |ui| { // The way to calculate the target size is captured from the @@ -205,43 +207,32 @@ impl eframe::App for MyTabs { }); }); - if let Ok(len) = ERRORS_POOL.try_read().map(|pool| pool.len()) { - if self.context.states.errors_pool.number_of_errors != len { - self.context.states.errors_pool.number_of_errors = len; - self.context.states.errors_pool.is_window_open = true; - } - } - + let mut is_open = { !ERRORS_POOL.read().is_empty() }; egui::Window::new("Errors") .id("error_window".into()) - .open(&mut self.context.states.errors_pool.is_window_open) + .open(&mut is_open) .resizable(false) .movable(false) .anchor(Align2::RIGHT_BOTTOM, [0.0, 0.0]) .show(ctx, |ui| { { - match ERRORS_POOL.try_read() { - Ok(pool) => { - if pool.is_empty() { - ui.label("No errors"); - } - ScrollArea::vertical().show(ui, |ui| { - ui.vertical(|ui| { - for error in pool.iter_errors() { - ui.label(format!("{:#?}", error)); - ui.separator(); - } - }); - }); - } - Err(_) => { - ui.spinner(); - } + let pool = ERRORS_POOL.read(); + + if pool.is_empty() { + ui.label("No errors"); } + ScrollArea::vertical().show(ui, |ui| { + ui.vertical(|ui| { + for error in pool.iter_errors() { + ui.label(format!("{:#?}", error)); + ui.separator(); + } + }); + }); } if ui.button("Clear").clicked() { - ERRORS_POOL.write().unwrap().clear() + ERRORS_POOL.write().clear() } }); diff --git a/crates/client/src/states.rs b/crates/client/src/states.rs index 0591942..01cac39 100644 --- a/crates/client/src/states.rs +++ b/crates/client/src/states.rs @@ -11,7 +11,7 @@ use tracing::info; use crate::{ collections::JavaCollection, - errors_pool::{ErrorPoolExt, ErrorsPoolState}, + errors_pool::ErrorPoolExt, views::{ add_tab_menu::TabsState, profiles::ProfilesState, @@ -22,7 +22,6 @@ use crate::{ pub struct States { pub tabs: TabsState, - pub errors_pool: ErrorsPoolState, pub logs_state: LogsState, pub java: JavaState, @@ -40,7 +39,6 @@ impl Default for States { Self { tabs: TabsState::new(), - errors_pool: ErrorsPoolState::default(), logs_state: LogsState::new(), java: JavaState::new(), profiles: ProfilesState::new(), diff --git a/crates/client/src/views/add_profile_menu.rs b/crates/client/src/views/add_profile_menu.rs index 19737e8..c093373 100644 --- a/crates/client/src/views/add_profile_menu.rs +++ b/crates/client/src/views/add_profile_menu.rs @@ -99,22 +99,29 @@ impl View for AddProfileMenu<'_> { ui.separator() }); - egui::ComboBox::from_label("Select instance to create profile for").show_ui(ui, |ui| { - for instance in &self.profiles_state.instances.instances { - if ui - .selectable_label( - self.menu_state - .parent_instance - .as_ref() - .is_some_and(|i| i.read().id() == instance.read().id()), - instance.read().name(), - ) - .clicked() - { - self.menu_state.parent_instance = Some(instance.clone()); + egui::ComboBox::from_label("Select instance to create profile for") + .selected_text( + self.menu_state + .parent_instance + .as_ref() + .map_or(String::from("No instance selected"), |i| i.read().name().to_owned()), + ) + .show_ui(ui, |ui| { + for instance in &self.profiles_state.instances.instances { + if ui + .selectable_label( + self.menu_state + .parent_instance + .as_ref() + .is_some_and(|i| i.read().id() == instance.read().id()), + instance.read().name(), + ) + .clicked() + { + self.menu_state.parent_instance = Some(instance.clone()); + } } - } - }); + }); { ui.label("Profile name:"); @@ -152,6 +159,7 @@ impl View for AddProfileMenu<'_> { .selected_text(format!("{}", self.menu_state.selected_loader_buf)) .show_ui(ui, |ui| { ui.selectable_value(&mut self.menu_state.selected_loader_buf, Loader::Vanilla, "Vanilla"); + ui.selectable_value(&mut self.menu_state.selected_loader_buf, Loader::Forge, "Forge"); let fabric = ui.selectable_value(&mut self.menu_state.selected_loader_buf, Loader::Fabric { version: None }, "Fabric"); if fabric.clicked() { @@ -243,7 +251,8 @@ impl View for AddProfileMenu<'_> { .add_enabled( self.menu_state.parent_instance.is_some() && some_version_buf() - && ((matches!(self.menu_state.selected_loader_buf, Loader::Vanilla)) + && (matches!(self.menu_state.selected_loader_buf, Loader::Vanilla) + || matches!(self.menu_state.selected_loader_buf, Loader::Forge) || (fabric_version_is_some() && fabric_versions_non_empty())), egui::Button::new("Create"), ) diff --git a/crates/client/src/views/mods_manager.rs b/crates/client/src/views/mods_manager.rs index aad6f5b..afd2196 100644 --- a/crates/client/src/views/mods_manager.rs +++ b/crates/client/src/views/mods_manager.rs @@ -194,7 +194,7 @@ impl View for ModManager<'_> { ui.horizontal(|ui| { for project_type in ProjectType::iter().filter(|t| !matches!(t, ProjectType::Plugin)) { let enabled = { - (self.profile.read().profile.loader().is_fabric() || matches!(project_type, ProjectType::DataPack)) + (self.profile.read().profile.loader().support_mods() || matches!(project_type, ProjectType::DataPack)) && !matches!(project_type, ProjectType::Modpack) }; diff --git a/crates/client/src/views/profile_info.rs b/crates/client/src/views/profile_info.rs index 43154d1..9fbef79 100644 --- a/crates/client/src/views/profile_info.rs +++ b/crates/client/src/views/profile_info.rs @@ -376,8 +376,17 @@ impl View for ProfileInfo<'_> { if let ProfileState::Downloaded(instance) = &mut profile.profile.state { instance.jvm_arguments_mut().clone_from(&self.profile_info_state.profile_jvm_args); } + + if let Some(instance) = self.profiles.find_instance(profile.profile.id.instance()) { + if let Some(profile) = instance.write().find_profile_mut(profile.profile.id) { + profile.name.clone_from(&self.profile_info_state.profile_name); + } + } } + self.profiles + .update_instance_config(self.profile.read().profile.id.instance()) + .report_error(); self.profiles.update_profile_config(self.profile.read().profile.id).report_error(); } diff --git a/crates/client/src/views/profiles.rs b/crates/client/src/views/profiles.rs index 08b7e8e..ea4b67f 100644 --- a/crates/client/src/views/profiles.rs +++ b/crates/client/src/views/profiles.rs @@ -22,6 +22,7 @@ use crate::{ download::{task_assets, task_download_version}, errors_pool::ErrorPoolExt, ui_ext::UiExt, + TabKind, }; use super::{ @@ -162,72 +163,84 @@ impl InstancesConfig { impl Instances<'_> { // TODO: It requires profile to be loaded - fn profile_action_ui(&mut self, ui: &mut Ui, profile_lock: Arc>) { + fn profile_action_ui(&mut self, ui: &mut Ui, profile_payload: &ProfilePayload) { + let button = if profile_payload.is_downloaded { + ui.add_enabled(self.is_allowed_to_take_action, egui::Button::new("Launch")) + } else { + ui.add_enabled( + !self.profiles_state.currently_downloading_profiles.contains(&profile_payload.id), + egui::Button::new("Download"), + ) + }; + + if button.clicked() { + self.profile_action(ui, profile_payload) + } + } + + fn profile_action(&mut self, ui: &mut Ui, profile_payload: &ProfilePayload) { + let Some(profile_lock) = self.profiles_state.instances.find_profile(profile_payload.id) else { + error!(id = ?profile_payload.id, "Cannot find the profile"); + return; + }; let profile = profile_lock.read(); match &profile.profile.state { ProfileState::Downloaded(instance) => { - if ui.add_enabled(self.is_allowed_to_take_action, egui::Button::new("Launch")).clicked() { - let user_data = UserData { - username: Username::new(self.settings_state.username.clone()).unwrap(), - uuid: Some(self.settings_state.uuid.clone()), - access_token: None, - }; - - let instance = instance.clone(); - let java_runner = self.settings_state.java.clone(); - - let should_load_mods = profile.profile.loader().support_mods(); - let profile_id = profile.profile.id; - - let game_logs = self.logs_state.game_logs.clone(); - game_logs.clear(); - let run_game = Task::new( - "Running the game", - Caller::standard(async move { - if should_load_mods { - load_mods(profile_id).await.report_error(); - } - - instance - .launch(GamePaths::from_id(profile_id), user_data, &java_runner, &*game_logs) - .await - .report_error() - }), - ); - - self.manager.push_task::(run_game) - } + let user_data = UserData { + username: Username::new(self.settings_state.username.clone()).unwrap(), + uuid: Some(self.settings_state.uuid.clone()), + access_token: None, + }; + + let instance = instance.clone(); + let java_runner = self.settings_state.java.clone(); + + let should_load_mods = profile.profile.loader().support_mods(); + let profile_id = profile.profile.id; + + let game_logs = self.logs_state.game_logs.clone(); + game_logs.clear(); + let run_game = Task::new( + "Running the game", + Caller::standard(async move { + if should_load_mods { + load_mods(profile_id).await.report_error(); + } + + instance + .launch(GamePaths::from_id(profile_id), user_data, &java_runner, &*game_logs) + .await + .report_error() + }), + ); + + self.manager.push_task::(run_game) } ProfileState::NotDownloaded { .. } => { - if ui - .add_enabled( - !self.profiles_state.currently_downloading_profiles.contains(&profile.profile.id), - egui::Button::new("Download"), - ) - .clicked() - { - self.profiles_state.currently_downloading_profiles.insert(profile.profile.id); - - let game_version = profile.profile.version().to_owned(); - - let game_paths = GamePaths::from_id(profile.profile.id); - let ctx = ui.ctx().clone(); - let assets_task = Task::new( - format!("Assets ({})", profile.profile.version()), - Caller::progressing(|progress| task_assets(progress, ctx, game_version, game_paths.assets)), - ); - self.manager.push_task::(assets_task); - - let profile_clone = profile_lock.clone(); - - let id = profile.profile.id; - let ctx = ui.ctx().clone(); - let game_task = Task::new( - format!("Downloading version {}", profile.profile.version()), - Caller::progressing(move |progress| async move { task_download_version(progress, ctx, profile_clone).await.map(|()| id) }), - ); - self.manager.push_task::(game_task); - } + self.profiles_state.currently_downloading_profiles.insert(profile.profile.id); + + let game_version = profile.profile.version().to_owned(); + + let game_paths = GamePaths::from_id(profile.profile.id); + let ctx = ui.ctx().clone(); + let assets_task = Task::new( + format!("Assets ({})", profile.profile.version()), + Caller::progressing(|progress| task_assets(progress, ctx, game_version, game_paths.assets)), + ); + self.manager.push_task::(assets_task); + + let profile_clone = profile_lock.clone(); + + let id = profile.profile.id; + let ctx = ui.ctx().clone(); + let java_runner = self.settings_state.java.clone(); + let game_task = Task::new( + format!("Downloading version {}", profile.profile.version()), + Caller::progressing(move |progress| async move { + task_download_version(progress, ctx, profile_clone, java_runner).await.map(|()| id) + }), + ); + self.manager.push_task::(game_task); } } } @@ -261,22 +274,18 @@ impl Instances<'_> { row.col(|ui| { ui.label(profile.loader.name()); }); - row.col(|ui| { - if let Some(profile) = self.profiles_state.instances.find_profile(profile.id) { - self.profile_action_ui(ui, profile) - } else { - ui.error_label("Cannot find the profile"); - } - }); + row.col(|ui| self.profile_action_ui(ui, profile)); row.col(|ui| { - if ui.button("TODO: Details").clicked() { - // self.profile_info_state.set_profile_to_edit(&profile_lock.read()); - - // let kind = TabKind::ProfileInfo { - // profile: profile_lock.clone(), - // }; - // self.tabs_state.0.insert(kind.id(), kind); + if ui.button("Details").clicked() { + if let Some(profile_lock) = self.profiles_state.instances.find_profile(profile.id) { + self.profile_info_state.set_profile_to_edit(&profile_lock.read()); + + let kind = TabKind::ProfileInfo { + profile: profile_lock.clone(), + }; + self.tabs_state.0.insert(kind.id(), kind); + }; } }); diff --git a/crates/nomi-core/src/downloads/downloaders/assets.rs b/crates/nomi-core/src/downloads/downloaders/assets.rs index 74adc40..bb54d28 100644 --- a/crates/nomi-core/src/downloads/downloaders/assets.rs +++ b/crates/nomi-core/src/downloads/downloaders/assets.rs @@ -63,13 +63,17 @@ impl Downloader for Chunk { let (helper_sender, mut helper_receiver) = tokio::sync::mpsc::channel(100); let downloader = self.set.with_helper(helper_sender); Box::new(downloader).download(sender).await; + while let Some(result) = helper_receiver.recv().await { match result.0 { Ok(_) => self.ok += 1, Err(_) => self.err += 1, } } - info!("Downloaded Chunk OK: {} ERR: {}", self.ok, self.err); + + if self.ok != 0 && self.err != 0 { + info!("Downloaded Chunk OK: {} ERR: {}", self.ok, self.err); + } } } diff --git a/crates/nomi-core/src/game_paths.rs b/crates/nomi-core/src/game_paths.rs index e8f3c23..46caa0d 100644 --- a/crates/nomi-core/src/game_paths.rs +++ b/crates/nomi-core/src/game_paths.rs @@ -58,4 +58,14 @@ impl GamePaths { pub fn version_jar_file(&self, game_version: &str) -> PathBuf { self.profile.join(format!("{game_version}.jar")) } + + pub fn minecraft(game_version: &str) -> Self { + const MINECRAFT_DIR: &str = "./minecraft"; + Self { + game: MINECRAFT_DIR.into(), + assets: PathBuf::from(MINECRAFT_DIR).join("assets"), + profile: PathBuf::from(MINECRAFT_DIR).join("versions").join(game_version), + libraries: PathBuf::from(MINECRAFT_DIR).join("libraries"), + } + } } diff --git a/crates/nomi-core/src/instance/mod.rs b/crates/nomi-core/src/instance/mod.rs index 14b6508..0d01c68 100644 --- a/crates/nomi-core/src/instance/mod.rs +++ b/crates/nomi-core/src/instance/mod.rs @@ -68,6 +68,14 @@ impl Instance { self.profiles.push(payload); } + pub fn find_profile(&self, id: InstanceProfileId) -> Option<&ProfilePayload> { + self.profiles().iter().find(|p| p.id == id) + } + + pub fn find_profile_mut(&mut self, id: InstanceProfileId) -> Option<&mut ProfilePayload> { + self.profiles_mut().iter_mut().find(|p| p.id == id) + } + /// Generate id for the next profile in this instance pub fn next_id(&self) -> InstanceProfileId { match &self.profiles.iter().max_by_key(|profile| profile.id.1) { diff --git a/crates/nomi-core/src/loaders/forge.rs b/crates/nomi-core/src/loaders/forge.rs index 53bf492..2669b1f 100644 --- a/crates/nomi-core/src/loaders/forge.rs +++ b/crates/nomi-core/src/loaders/forge.rs @@ -61,6 +61,7 @@ pub struct Forge { game_version: String, forge_version: String, game_paths: GamePaths, + java_runner: JavaRunner, library_data: Option, processors_data: Option, @@ -179,7 +180,12 @@ impl Forge { } #[tracing::instrument(skip(version), fields(game_version) err)] - pub async fn new(version: impl Into, forge_version: ForgeVersion, game_paths: GamePaths) -> anyhow::Result { + pub async fn new( + version: impl Into, + forge_version: ForgeVersion, + game_paths: GamePaths, + java_runner: JavaRunner, + ) -> anyhow::Result { let game_version: String = version.into(); tracing::Span::current().record("game_version", &game_version); @@ -274,6 +280,7 @@ impl Forge { game_version, forge_version, game_paths, + java_runner, library_data, processors_data, }) @@ -362,7 +369,7 @@ impl Downloader for Forge { if let Some(processors_data) = processors_data { processors_data - .run_processors(&java_runner, &game_version, &forge_version, &game_paths) + .run_processors(&java_runner, &game_version, &forge_version, game_paths) .await?; } @@ -381,7 +388,7 @@ impl Downloader for Forge { self.game_version.clone(), self.forge_version.clone(), self.installer_path(), - JavaRunner::nomi_default(), + self.java_runner.clone(), self.game_paths.clone(), self.processors_data.clone(), self.library_data.clone(), @@ -416,7 +423,7 @@ impl ProcessorsData { client = "client", server = "" "MINECRAFT_JAR" : - client = game_paths.profile.join(format!("{game_version}.jar")).to_string_lossy(), + client = game_paths.manifest_file(game_version).to_string_lossy(), server = "" "MINECRAFT_VERSION": client = game_version, @@ -442,7 +449,7 @@ impl ProcessorsData { .join(CLASSPATH_SEPARATOR) } - #[tracing::instrument] + #[tracing::instrument(err)] async fn get_processor_main_class(processor_jar: PathBuf) -> anyhow::Result { tokio::task::spawn_blocking(|| { let file = std::fs::File::open(processor_jar)?; @@ -505,17 +512,15 @@ impl ProcessorsData { java_runner: &JavaRunner, game_version: &str, forge_version: &str, - game_paths: &GamePaths, + game_paths: GamePaths, ) -> anyhow::Result<()> { - self.apply_data_rules(game_version, forge_version, game_paths); + // let game_paths = game_paths + // .make_absolute() + // .inspect_err(|error| error!(%error, "Failed to make `game_paths` absolute"))?; + + self.apply_data_rules(game_version, forge_version, &game_paths); - let total = self - .processors - .iter() - .filter(|p| p.sides.as_ref().is_some_and(|sides| sides.iter().any(|s| s == "client"))) - .count(); let mut ok = 0; - let mut err = 0; for mut processor in self.processors { if processor.sides.as_ref().is_some_and(|sides| !sides.iter().any(|s| s == "client")) { @@ -532,19 +537,19 @@ impl ProcessorsData { let output = dbg!(Command::new(java_runner.get()).arg("-cp").arg(classpath).arg(main_class).args(arguments)) .output() - .await?; + .await + .inspect_err(|error| error!(%error, "Cannot run the command"))?; if output.status.success() { ok += 1; info!("Processor finished successfully"); } else { - err += 1; let error = String::from_utf8_lossy(&output.stderr); - error!(error = %error, "Processor failed"); + bail!("Processor failed, error: {error}") } } - info!(total, ok, err, "Finished processors execution"); + info!(ok, "Finished processors execution"); Ok(()) } @@ -786,14 +791,24 @@ mod tests { #[tokio::test] async fn create_forge_test() { - let recommended = Forge::new("1.7.10", ForgeVersion::Recommended, GamePaths::from_id(InstanceProfileId::ZERO)) - .await - .unwrap(); + let recommended = Forge::new( + "1.7.10", + ForgeVersion::Recommended, + GamePaths::from_id(InstanceProfileId::ZERO), + JavaRunner::from_environment(), + ) + .await + .unwrap(); println!("{recommended:#?}"); - let latest = Forge::new("1.19.2", ForgeVersion::Latest, GamePaths::from_id(InstanceProfileId::ZERO)) - .await - .unwrap(); + let latest = Forge::new( + "1.19.2", + ForgeVersion::Latest, + GamePaths::from_id(InstanceProfileId::ZERO), + JavaRunner::from_environment(), + ) + .await + .unwrap(); println!("{latest:#?}"); } @@ -803,9 +818,14 @@ mod tests { debug!("Test"); - let recommended = Forge::new("1.7.10", ForgeVersion::Recommended, GamePaths::from_id(InstanceProfileId::ZERO)) - .await - .unwrap(); + let recommended = Forge::new( + "1.7.10", + ForgeVersion::Recommended, + GamePaths::from_id(InstanceProfileId::ZERO), + JavaRunner::from_environment(), + ) + .await + .unwrap(); println!("{recommended:#?}"); let io = recommended.io(); diff --git a/crates/nomi-core/tests/forge_new_test.rs b/crates/nomi-core/tests/forge_new_test.rs index 1fd73c5..41c6621 100644 --- a/crates/nomi-core/tests/forge_new_test.rs +++ b/crates/nomi-core/tests/forge_new_test.rs @@ -2,13 +2,17 @@ use std::path::PathBuf; use nomi_core::{ configs::profile::{ProfileState, VersionProfile}, + downloads::traits::Downloader, game_paths::GamePaths, instance::{ launch::{arguments::UserData, LaunchSettings}, logs::PrintLogs, InstanceProfileId, Profile, }, - loaders::forge::{Forge, ForgeVersion}, + loaders::{ + forge::{Forge, ForgeVersion}, + vanilla::Vanilla, + }, repository::java_runner::JavaRunner, DOT_NOMI_JAVA_EXECUTABLE, }; @@ -19,19 +23,21 @@ async fn forge_test() { let (tx, _) = tokio::sync::mpsc::channel(100); - let game_paths = GamePaths::from_id(InstanceProfileId::ZERO); + let game_paths = GamePaths::minecraft("1.19.2"); let instance = Profile::builder() .name("forge-test".into()) - .version("1.20.1".into()) + .version("1.19.2".into()) .game_paths(game_paths.clone()) .downloader(Box::new( - Forge::new("1.20.1", ForgeVersion::Recommended, game_paths.clone()).await.unwrap(), + Forge::new("1.19.2", ForgeVersion::Recommended, game_paths.clone(), JavaRunner::from_environment()) + .await + .unwrap(), )) - // .downloader(Box::new(Vanilla::new("1.20.1", game_paths.clone()).await.unwrap())) + // .downloader(Box::new(Vanilla::new("1.19.2", game_paths.clone()).await.unwrap())) .build(); - // let vanilla = Box::new(Vanilla::new("1.20.1", game_paths.clone()).await.unwrap()); + // let vanilla = Box::new(Vanilla::new("1.19.2", game_paths.clone()).await.unwrap()); // let io = vanilla.io(); // vanilla.download(&tx).await; @@ -41,7 +47,7 @@ async fn forge_test() { let settings = LaunchSettings { java_runner: None, - version: "1.20.1".to_string(), + version: "1.19.2".to_string(), version_type: nomi_core::repository::manifest::VersionType::Release, }; @@ -66,12 +72,7 @@ async fn forge_test() { .build(); dbg!(profile) - .launch( - game_paths, - UserData::default(), - &JavaRunner::path(PathBuf::from(DOT_NOMI_JAVA_EXECUTABLE)), - &PrintLogs, - ) + .launch(game_paths, UserData::default(), &JavaRunner::from_environment(), &PrintLogs) .await .unwrap(); } diff --git a/crates/nomi-core/tests/forge_old_test.rs b/crates/nomi-core/tests/forge_old_test.rs index cfc76f5..8eea8fb 100644 --- a/crates/nomi-core/tests/forge_old_test.rs +++ b/crates/nomi-core/tests/forge_old_test.rs @@ -26,7 +26,9 @@ async fn forge_test() { .version("1.7.10".into()) .game_paths(game_paths.clone()) .downloader(Box::new( - Forge::new("1.7.10", ForgeVersion::Recommended, game_paths.clone()).await.unwrap(), + Forge::new("1.7.10", ForgeVersion::Recommended, game_paths.clone(), JavaRunner::from_environment()) + .await + .unwrap(), )) // .instance(Box::new(Vanilla::new("1.7.10", game_paths.clone()).await.unwrap())) .build(); From 78a7b624b9f6eb136de7d0eb978f12aa9c5dae5d Mon Sep 17 00:00:00 2001 From: Umatriz Date: Tue, 13 Aug 2024 18:00:05 +0300 Subject: [PATCH 08/15] fix: forge istallation Turns out I provided manifest file instead of the client file --- crates/nomi-core/src/loaders/forge.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/nomi-core/src/loaders/forge.rs b/crates/nomi-core/src/loaders/forge.rs index 2669b1f..3679a82 100644 --- a/crates/nomi-core/src/loaders/forge.rs +++ b/crates/nomi-core/src/loaders/forge.rs @@ -423,7 +423,7 @@ impl ProcessorsData { client = "client", server = "" "MINECRAFT_JAR" : - client = game_paths.manifest_file(game_version).to_string_lossy(), + client = game_paths.version_jar_file(game_version).to_string_lossy(), server = "" "MINECRAFT_VERSION": client = game_version, From 82ea565c68fb3e63f093659069ecb3ef44d57acb Mon Sep 17 00:00:00 2001 From: Umatriz Date: Thu, 15 Aug 2024 10:46:19 +0300 Subject: [PATCH 09/15] feat: instance creation menu --- crates/client/src/context.rs | 22 ++++---- crates/client/src/main.rs | 55 +++++++++++++++---- crates/client/src/states.rs | 14 +++-- crates/client/src/views.rs | 2 + crates/client/src/views/add_profile_menu.rs | 16 +----- .../client/src/views/create_instance_menu.rs | 41 ++++++++++++++ .../client/src/views/downloading_progress.rs | 4 +- crates/client/src/views/profile_info.rs | 2 +- crates/client/src/views/profiles.rs | 39 ++----------- crates/nomi-core/tests/forge_new_test.rs | 10 ++-- 10 files changed, 122 insertions(+), 83 deletions(-) create mode 100644 crates/client/src/views/create_instance_menu.rs diff --git a/crates/client/src/context.rs b/crates/client/src/context.rs index e109a3b..8fa11f4 100644 --- a/crates/client/src/context.rs +++ b/crates/client/src/context.rs @@ -24,6 +24,7 @@ pub struct MyContext { pub is_allowed_to_take_action: bool, pub is_profile_window_open: bool, + pub is_instance_window_open: bool, pub images_clean_requested: bool, } @@ -44,12 +45,14 @@ impl MyContext { egui_layer, launcher_manifest: launcher_manifest_ref, file_dialog: FileDialog::new(), + is_profile_window_open: false, + is_instance_window_open: false, + is_allowed_to_take_action: true, + images_clean_requested: false, states: States::new(), manager: TaskManager::new(), - is_allowed_to_take_action: true, - images_clean_requested: false, } } @@ -69,11 +72,11 @@ impl TabViewer for MyContext { match &tab.kind { TabKind::Mods { profile } => { let profile = profile.read(); - self.states.profiles.instances.find_profile(profile.profile.id).is_none() + self.states.instances.instances.find_profile(profile.profile.id).is_none() } TabKind::ProfileInfo { profile } => { let profile = profile.read(); - self.states.profiles.instances.find_profile(profile.profile.id).is_none() + self.states.instances.instances.find_profile(profile.profile.id).is_none() } _ => false, } @@ -86,13 +89,12 @@ impl TabViewer for MyContext { profile_info_state: &mut self.states.profile_info, manager: &mut self.manager, settings_state: &self.states.settings, - profiles_state: &mut self.states.profiles, - menu_state: &mut self.states.add_profile_menu_state, + profiles_state: &mut self.states.instances, + menu_state: &mut self.states.add_profile_menu, tabs_state: &mut self.states.tabs, logs_state: &self.states.logs_state, launcher_manifest: self.launcher_manifest, - is_profile_window_open: &mut self.is_profile_window_open, } .ui(ui), TabKind::Settings => SettingsPage { @@ -111,19 +113,19 @@ impl TabViewer for MyContext { TabKind::DownloadProgress => { views::DownloadingProgress { manager: &self.manager, - profiles_state: &mut self.states.profiles, + profiles_state: &mut self.states.instances, } .ui(ui); } TabKind::Mods { profile } => ModManager { task_manager: &mut self.manager, - profiles_config: &mut self.states.profiles.instances, + profiles_config: &mut self.states.instances.instances, mod_manager_state: &mut self.states.mod_manager, profile: profile.clone(), } .ui(ui), TabKind::ProfileInfo { profile } => ProfileInfo { - profiles: &self.states.profiles.instances, + profiles: &self.states.instances.instances, task_manager: &mut self.manager, profile: profile.clone(), tabs_state: &mut self.states.tabs, diff --git a/crates/client/src/main.rs b/crates/client/src/main.rs index c61f9b2..210cc1c 100644 --- a/crates/client/src/main.rs +++ b/crates/client/src/main.rs @@ -13,8 +13,8 @@ use egui_notify::Toasts; use open_directory::open_directory_native; use std::path::Path; use subscriber::EguiLayer; -use ui_ext::TOASTS_ID; -use views::{add_tab_menu::AddTab, View}; +use ui_ext::{UiExt, TOASTS_ID}; +use views::{add_tab_menu::AddTab, AddProfileMenu, CreateInstanceMenu, View}; use errors_pool::{ErrorPoolExt, ERRORS_POOL}; use nomi_core::{DOT_NOMI_DATA_PACKS_DIR, DOT_NOMI_LOGS_DIR}; @@ -141,9 +141,9 @@ impl eframe::App for MyTabs { self.context .manager .add_collection::(()) - .add_collection::(&mut self.context.states.add_profile_menu_state.fabric_versions) + .add_collection::(&mut self.context.states.add_profile_menu.fabric_versions) .add_collection::(()) - .add_collection::(&self.context.states.profiles.instances) + .add_collection::(&self.context.states.instances.instances) .add_collection::(()) .add_collection::(&mut self.context.states.mod_manager.current_project) .add_collection::(&mut self.context.states.mod_manager.current_versions) @@ -151,11 +151,11 @@ impl eframe::App for MyTabs { &mut self.context.states.mod_manager.current_dependencies, self.context.states.mod_manager.current_project.as_ref().map(|p| &p.id), )) - .add_collection::(&self.context.states.profiles.instances) + .add_collection::(&self.context.states.instances.instances) .add_collection::(()) .add_collection::(( &mut self.context.states.profile_info.currently_downloading_mods, - &self.context.states.profiles.instances, + &self.context.states.instances.instances, )); ctx.set_pixels_per_point(self.context.states.client_settings.pixels_per_point); @@ -175,11 +175,10 @@ impl eframe::App for MyTabs { let last_others_width = ui.data(|data| data.get_temp(id_cal_target_size).unwrap_or(this_init_max_width)); let this_target_width = this_init_max_width - last_others_width; - AddTab { - dock_state: &self.dock_state, - tabs_state: &mut self.context.states.tabs, - } - .ui(ui); + ui.menu_button("New", |ui| { + ui.toggle_button(&mut self.context.is_instance_window_open, "Create new instance"); + ui.toggle_button(&mut self.context.is_profile_window_open, "Add new profile"); + }); ui.menu_button("Open", |ui| { if ui @@ -193,6 +192,12 @@ impl eframe::App for MyTabs { } }); + AddTab { + dock_state: &self.dock_state, + tabs_state: &mut self.context.states.tabs, + } + .ui(ui); + ui.add_space(this_target_width); ui.horizontal(|ui| { egui::warn_if_debug_build(ui); @@ -207,6 +212,34 @@ impl eframe::App for MyTabs { }); }); + { + egui::Window::new("Add new profile") + .collapsible(true) + .resizable(true) + .open(&mut self.context.is_profile_window_open) + .show(ctx, |ui| { + AddProfileMenu { + menu_state: &mut self.context.states.add_profile_menu, + profiles_state: &mut self.context.states.instances, + launcher_manifest: self.context.launcher_manifest, + manager: &mut self.context.manager, + } + .ui(ui); + }); + + egui::Window::new("Create new instance") + .collapsible(true) + .resizable(true) + .open(&mut self.context.is_instance_window_open) + .show(ctx, |ui| { + CreateInstanceMenu { + instances_state: &mut self.context.states.instances, + create_instance_menu_state: &mut self.context.states.create_instance_menu, + } + .ui(ui); + }); + } + let mut is_open = { !ERRORS_POOL.read().is_empty() }; egui::Window::new("Errors") .id("error_window".into()) diff --git a/crates/client/src/states.rs b/crates/client/src/states.rs index 01cac39..b5c3cf4 100644 --- a/crates/client/src/states.rs +++ b/crates/client/src/states.rs @@ -14,9 +14,9 @@ use crate::{ errors_pool::ErrorPoolExt, views::{ add_tab_menu::TabsState, - profiles::ProfilesState, + profiles::InstancesState, settings::{ClientSettingsState, SettingsState}, - AddProfileMenuState, LogsState, ModManagerState, ProfileInfoState, + AddProfileMenuState, CreateInstanceMenuState, LogsState, ModManagerState, ProfileInfoState, }, }; @@ -25,10 +25,11 @@ pub struct States { pub logs_state: LogsState, pub java: JavaState, - pub profiles: ProfilesState, + pub instances: InstancesState, pub settings: SettingsState, pub client_settings: ClientSettingsState, - pub add_profile_menu_state: AddProfileMenuState, + pub add_profile_menu: AddProfileMenuState, + pub create_instance_menu: CreateInstanceMenuState, pub mod_manager: ModManagerState, pub profile_info: ProfileInfoState, } @@ -41,10 +42,11 @@ impl Default for States { tabs: TabsState::new(), logs_state: LogsState::new(), java: JavaState::new(), - profiles: ProfilesState::new(), + instances: InstancesState::new(), client_settings: settings.client_settings.clone(), settings, - add_profile_menu_state: AddProfileMenuState::new(), + add_profile_menu: AddProfileMenuState::new(), + create_instance_menu: CreateInstanceMenuState::new(), mod_manager: ModManagerState::new(), profile_info: ProfileInfoState::new(), } diff --git a/crates/client/src/views.rs b/crates/client/src/views.rs index a86f4cb..ac1d442 100644 --- a/crates/client/src/views.rs +++ b/crates/client/src/views.rs @@ -2,6 +2,7 @@ use eframe::egui::Ui; pub mod add_profile_menu; pub mod add_tab_menu; +pub mod create_instance_menu; pub mod downloading_progress; pub mod logs; pub mod mods_manager; @@ -11,6 +12,7 @@ pub mod settings; pub use add_profile_menu::*; pub use add_tab_menu::*; +pub use create_instance_menu::*; pub use downloading_progress::*; pub use logs::*; pub use mods_manager::*; diff --git a/crates/client/src/views/add_profile_menu.rs b/crates/client/src/views/add_profile_menu.rs index c093373..aaa0b01 100644 --- a/crates/client/src/views/add_profile_menu.rs +++ b/crates/client/src/views/add_profile_menu.rs @@ -17,13 +17,13 @@ use parking_lot::RwLock; use crate::{collections::FabricDataCollection, errors_pool::ErrorPoolExt, ui_ext::UiExt, views::ModdedProfile}; -use super::{profiles::ProfilesState, View}; +use super::{profiles::InstancesState, View}; pub struct AddProfileMenu<'a> { pub manager: &'a mut TaskManager, pub launcher_manifest: &'a LauncherManifest, pub menu_state: &'a mut AddProfileMenuState, - pub profiles_state: &'a mut ProfilesState, + pub profiles_state: &'a mut InstancesState, } pub struct AddProfileMenuState { @@ -87,18 +87,6 @@ impl View for AddProfileMenu<'_> { } } - ui.vertical(|ui| { - ui.text_edit_singleline(&mut self.menu_state.instance_name); - - if ui.button("Create").clicked() { - let id = self.profiles_state.instances.next_id(); - let instance = Instance::new(self.menu_state.instance_name.clone(), id); - self.profiles_state.instances.add_instance(instance); - self.profiles_state.instances.update_instance_config(id).report_error(); - } - ui.separator() - }); - egui::ComboBox::from_label("Select instance to create profile for") .selected_text( self.menu_state diff --git a/crates/client/src/views/create_instance_menu.rs b/crates/client/src/views/create_instance_menu.rs new file mode 100644 index 0000000..37651e4 --- /dev/null +++ b/crates/client/src/views/create_instance_menu.rs @@ -0,0 +1,41 @@ +use eframe::egui; +use nomi_core::instance::Instance; + +use crate::{errors_pool::ErrorPoolExt, ui_ext::UiExt}; + +use super::{InstancesState, View}; + +pub struct CreateInstanceMenu<'a> { + pub instances_state: &'a mut InstancesState, + pub create_instance_menu_state: &'a mut CreateInstanceMenuState, +} + +#[derive(Default)] +pub struct CreateInstanceMenuState { + pub name: String, +} + +impl CreateInstanceMenuState { + pub fn new() -> Self { + Self::default() + } +} + +impl View for CreateInstanceMenu<'_> { + fn ui(self, ui: &mut eframe::egui::Ui) { + egui::TextEdit::singleline(&mut self.create_instance_menu_state.name) + .hint_text("Instance name") + .show(ui); + + if ui + .add_enabled(!self.create_instance_menu_state.name.trim_end().is_empty(), egui::Button::new("Create")) + .clicked() + { + let id = self.instances_state.instances.next_id(); + let instance = Instance::new(self.create_instance_menu_state.name.clone(), id); + self.instances_state.instances.add_instance(instance); + self.instances_state.instances.update_instance_config(id).report_error(); + ui.toasts(|toasts| toasts.success("New instance created")); + } + } +} diff --git a/crates/client/src/views/downloading_progress.rs b/crates/client/src/views/downloading_progress.rs index bc9cfa6..ba60e0e 100644 --- a/crates/client/src/views/downloading_progress.rs +++ b/crates/client/src/views/downloading_progress.rs @@ -1,11 +1,11 @@ use eframe::egui::{self, Layout}; use egui_task_manager::TaskManager; -use super::{profiles::ProfilesState, View}; +use super::{profiles::InstancesState, View}; pub struct DownloadingProgress<'a> { pub manager: &'a TaskManager, - pub profiles_state: &'a mut ProfilesState, + pub profiles_state: &'a mut InstancesState, } impl View for DownloadingProgress<'_> { diff --git a/crates/client/src/views/profile_info.rs b/crates/client/src/views/profile_info.rs index 9fbef79..4464a1b 100644 --- a/crates/client/src/views/profile_info.rs +++ b/crates/client/src/views/profile_info.rs @@ -397,7 +397,7 @@ impl View for ProfileInfo<'_> { ui.heading("Mods"); - ui.add_enabled_ui(self.profile.read().profile.loader().is_fabric(), |ui| { + ui.add_enabled_ui(self.profile.read().profile.loader().support_mods(), |ui| { ui.toggle_button(&mut self.profile_info_state.is_import_window_open, "Import mods"); if ui .toggle_button(&mut self.profile_info_state.is_export_window_open, "Export mods") diff --git a/crates/client/src/views/profiles.rs b/crates/client/src/views/profiles.rs index ea4b67f..6859262 100644 --- a/crates/client/src/views/profiles.rs +++ b/crates/client/src/views/profiles.rs @@ -21,16 +21,10 @@ use crate::{ collections::{AssetsCollection, GameDownloadingCollection, GameRunnerCollection}, download::{task_assets, task_download_version}, errors_pool::ErrorPoolExt, - ui_ext::UiExt, TabKind, }; -use super::{ - add_profile_menu::{AddProfileMenu, AddProfileMenuState}, - load_mods, - settings::SettingsState, - LogsState, ModsConfig, ProfileInfoState, TabsState, View, -}; +use super::{add_profile_menu::AddProfileMenuState, load_mods, settings::SettingsState, LogsState, ModsConfig, ProfileInfoState, TabsState, View}; pub struct Instances<'a> { pub is_allowed_to_take_action: bool, @@ -38,28 +32,26 @@ pub struct Instances<'a> { pub settings_state: &'a SettingsState, pub profile_info_state: &'a mut ProfileInfoState, - pub is_profile_window_open: &'a mut bool, - pub logs_state: &'a LogsState, pub tabs_state: &'a mut TabsState, - pub profiles_state: &'a mut ProfilesState, + pub profiles_state: &'a mut InstancesState, pub menu_state: &'a mut AddProfileMenuState, pub launcher_manifest: &'static LauncherManifest, } -pub struct ProfilesState { +pub struct InstancesState { pub currently_downloading_profiles: HashSet, pub instances: InstancesConfig, } -impl Default for ProfilesState { +impl Default for InstancesState { fn default() -> Self { Self::new() } } -impl ProfilesState { +impl InstancesState { pub fn new() -> Self { Self { currently_downloading_profiles: HashSet::new(), @@ -305,27 +297,6 @@ impl Instances<'_> { impl View for Instances<'_> { fn ui(mut self, ui: &mut Ui) { - { - ui.toggle_value(self.is_profile_window_open, "Add new profile"); - - egui::Window::new("Create new profile") - .title_bar(true) - .collapsible(false) - .resizable(false) - .anchor(Align2::CENTER_CENTER, [0.0, 0.0]) - .movable(false) - .open(self.is_profile_window_open) - .show(ui.ctx(), |ui| { - AddProfileMenu { - menu_state: self.menu_state, - profiles_state: self.profiles_state, - launcher_manifest: self.launcher_manifest, - manager: self.manager, // is_profile_window_open: self.is_profile_window_open, - } - .ui(ui); - }); - } - ui.style_mut().wrap_mode = Some(TextWrapMode::Extend); let iter = self.profiles_state.instances.instances.iter().cloned().collect_vec().into_iter(); diff --git a/crates/nomi-core/tests/forge_new_test.rs b/crates/nomi-core/tests/forge_new_test.rs index 41c6621..c2b2c86 100644 --- a/crates/nomi-core/tests/forge_new_test.rs +++ b/crates/nomi-core/tests/forge_new_test.rs @@ -23,7 +23,7 @@ async fn forge_test() { let (tx, _) = tokio::sync::mpsc::channel(100); - let game_paths = GamePaths::minecraft("1.19.2"); + let game_paths = GamePaths::from_id(InstanceProfileId::ZERO); let instance = Profile::builder() .name("forge-test".into()) @@ -53,10 +53,10 @@ async fn forge_test() { let launch = instance.launch_instance(settings, None); - // let assets = instance.assets().await.unwrap(); - // let assets_io = assets.io(); - // Box::new(assets).download(&tx).await; - // assets_io.await.unwrap(); + let assets = instance.assets().await.unwrap(); + let assets_io = assets.io(); + Box::new(assets).download(&tx).await; + assets_io.await.unwrap(); let instance = instance.downloader(); let io_fut = instance.io(); From c762aa4107795cdf116f8279edd281f0726cac79 Mon Sep 17 00:00:00 2001 From: Umatriz Date: Thu, 15 Aug 2024 10:46:53 +0300 Subject: [PATCH 10/15] fix: trim the end of the name --- crates/client/src/views/create_instance_menu.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/client/src/views/create_instance_menu.rs b/crates/client/src/views/create_instance_menu.rs index 37651e4..fc7655d 100644 --- a/crates/client/src/views/create_instance_menu.rs +++ b/crates/client/src/views/create_instance_menu.rs @@ -32,7 +32,7 @@ impl View for CreateInstanceMenu<'_> { .clicked() { let id = self.instances_state.instances.next_id(); - let instance = Instance::new(self.create_instance_menu_state.name.clone(), id); + let instance = Instance::new(self.create_instance_menu_state.name.trim_end().to_owned(), id); self.instances_state.instances.add_instance(instance); self.instances_state.instances.update_instance_config(id).report_error(); ui.toasts(|toasts| toasts.success("New instance created")); From 3c065afafd570bb807c940017e3e7104a6142870 Mon Sep 17 00:00:00 2001 From: Umatriz Date: Thu, 15 Aug 2024 13:16:19 +0300 Subject: [PATCH 11/15] refactor(toasts): change the way it works --- crates/client/src/context.rs | 10 ++- crates/client/src/errors_pool.rs | 20 +---- crates/client/src/main.rs | 89 ++++++++++--------- crates/client/src/toasts.rs | 20 +++++ crates/client/src/ui_ext.rs | 12 --- .../client/src/views/create_instance_menu.rs | 4 +- crates/client/src/views/profile_info.rs | 4 +- 7 files changed, 82 insertions(+), 77 deletions(-) create mode 100644 crates/client/src/toasts.rs diff --git a/crates/client/src/context.rs b/crates/client/src/context.rs index 8fa11f4..24e8715 100644 --- a/crates/client/src/context.rs +++ b/crates/client/src/context.rs @@ -25,6 +25,7 @@ pub struct MyContext { pub is_allowed_to_take_action: bool, pub is_profile_window_open: bool, pub is_instance_window_open: bool, + pub is_errors_window_open: bool, pub images_clean_requested: bool, } @@ -45,14 +46,15 @@ impl MyContext { egui_layer, launcher_manifest: launcher_manifest_ref, file_dialog: FileDialog::new(), + states: States::new(), + manager: TaskManager::new(), + is_allowed_to_take_action: true, is_profile_window_open: false, is_instance_window_open: false, - is_allowed_to_take_action: true, - images_clean_requested: false, + is_errors_window_open: false, - states: States::new(), - manager: TaskManager::new(), + images_clean_requested: false, } } diff --git a/crates/client/src/errors_pool.rs b/crates/client/src/errors_pool.rs index 81d1793..8122894 100644 --- a/crates/client/src/errors_pool.rs +++ b/crates/client/src/errors_pool.rs @@ -6,6 +6,8 @@ use std::{ use once_cell::sync::Lazy; use parking_lot::RwLock; +use crate::toasts; + pub static ERRORS_POOL: Lazy>> = Lazy::new(|| Arc::new(RwLock::new(ErrorsPool::default()))); pub trait Error: Display + Debug {} @@ -48,9 +50,6 @@ impl ErrorsPool { pub trait ErrorPoolExt { fn report_error(self) -> Option; - fn report_error_with_context(self, context: C) -> Option - where - C: Display + Send + Sync + 'static; } impl ErrorPoolExt for Result @@ -61,24 +60,11 @@ where match self { Ok(value) => Some(value), Err(error) => { + toasts::add(|toasts| toasts.error(error.to_string())); let mut pool = ERRORS_POOL.write(); pool.push_error(error); None } } } - - fn report_error_with_context(self, context: C) -> Option - where - C: Display + Send + Sync + 'static, - { - match self { - Ok(value) => Some(value), - Err(error) => { - let mut pool = ERRORS_POOL.write(); - pool.push_error(anyhow::Error::msg(error).context(context)); - None - } - } - } } diff --git a/crates/client/src/main.rs b/crates/client/src/main.rs index 210cc1c..6ec1589 100644 --- a/crates/client/src/main.rs +++ b/crates/client/src/main.rs @@ -1,11 +1,12 @@ // Remove console window in release builds #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] +use anyhow::anyhow; use cache::ui_for_loaded_profiles; use collections::{AssetsCollection, GameDownloadingCollection, GameRunnerCollection, JavaCollection}; use context::MyContext; use eframe::{ - egui::{self, Align, Align2, Button, Frame, Id, Layout, RichText, ScrollArea, ViewportBuilder}, + egui::{self, Align, Align2, Button, Color32, Frame, Id, Layout, RichText, ScrollArea, ViewportBuilder}, epaint::Vec2, }; use egui_dock::{DockArea, DockState, NodeIndex, Style}; @@ -33,6 +34,7 @@ pub mod utils; pub mod views; pub mod cache; +pub mod toasts; pub mod mods; pub mod open_directory; @@ -127,16 +129,7 @@ impl MyTabs { impl eframe::App for MyTabs { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { - { - use parking_lot::Mutex; - use std::sync::Arc; - - let toasts = ctx.data_mut(|data| data.get_temp_mut_or_default::>>(egui::Id::new(TOASTS_ID)).clone()); - - let mut locked = toasts.lock(); - - locked.show(ctx); - } + toasts::show(ctx); self.context .manager @@ -199,14 +192,27 @@ impl eframe::App for MyTabs { .ui(ui); ui.add_space(this_target_width); - ui.horizontal(|ui| { - egui::warn_if_debug_build(ui); - ui.hyperlink_to( - RichText::new(format!("{} Nomi on GitHub", egui::special_emojis::GITHUB)).small(), - "https://github.com/Umatriz/nomi", - ); - ui.hyperlink_to(RichText::new("Nomi's Discord server").small(), "https://discord.gg/qRD5XEJKc4"); - }); + + egui::warn_if_debug_build(ui); + + if ui.button("Cause an error").clicked() { + Err::<(), _>(anyhow!("Error!")).report_error(); + } + + let is_errors = { !ERRORS_POOL.read().is_empty() }; + if is_errors { + let button = egui::Button::new(RichText::new("Errors").color(ui.visuals().error_fg_color)); + let button = ui.add(button); + if button.clicked() { + self.context.is_errors_window_open = true; + } + } + + ui.hyperlink_to( + RichText::new(format!("{} Nomi on GitHub", egui::special_emojis::GITHUB)).small(), + "https://github.com/Umatriz/nomi", + ); + ui.hyperlink_to(RichText::new("Nomi's Discord server").small(), "https://discord.gg/qRD5XEJKc4"); ui.data_mut(|data| data.insert_temp(id_cal_target_size, ui.min_rect().width() - this_target_width)); }); @@ -240,33 +246,36 @@ impl eframe::App for MyTabs { }); } - let mut is_open = { !ERRORS_POOL.read().is_empty() }; egui::Window::new("Errors") .id("error_window".into()) - .open(&mut is_open) - .resizable(false) - .movable(false) - .anchor(Align2::RIGHT_BOTTOM, [0.0, 0.0]) + .open(&mut self.context.is_errors_window_open) .show(ctx, |ui| { - { - let pool = ERRORS_POOL.read(); + ScrollArea::vertical().show(ui, |ui| { + ui.horizontal(|ui| { + if ui.button("Clear").clicked() { + ERRORS_POOL.write().clear() + } - if pool.is_empty() { - ui.label("No errors"); - } - ScrollArea::vertical().show(ui, |ui| { - ui.vertical(|ui| { - for error in pool.iter_errors() { - ui.label(format!("{:#?}", error)); - ui.separator(); - } - }); + ui.label("See the Logs tab for detailed information"); }); - } - if ui.button("Clear").clicked() { - ERRORS_POOL.write().clear() - } + { + let pool = ERRORS_POOL.read(); + + if pool.is_empty() { + ui.label("No errors"); + } + + egui::Frame::dark_canvas(ui.style()).show(ui, |ui| { + ui.vertical(|ui| { + for error in pool.iter_errors() { + ui.label(format!("{:#?}", error)); + ui.separator(); + } + }); + }); + } + }); }); egui::CentralPanel::default() diff --git a/crates/client/src/toasts.rs b/crates/client/src/toasts.rs new file mode 100644 index 0000000..6541076 --- /dev/null +++ b/crates/client/src/toasts.rs @@ -0,0 +1,20 @@ +use std::sync::{Arc, LazyLock}; + +use eframe::egui::Context; +use egui_notify::{Toast, Toasts}; +use parking_lot::RwLock; + +pub static TOASTS: LazyLock>> = LazyLock::new(|| Arc::new(RwLock::new(new()))); + +fn new() -> Toasts { + Toasts::default() +} + +pub fn show(ctx: &Context) { + TOASTS.write().show(ctx) +} + +pub fn add(writer: impl FnOnce(&mut Toasts) -> &mut Toast) { + let mut toasts = TOASTS.write(); + writer(&mut toasts); +} diff --git a/crates/client/src/ui_ext.rs b/crates/client/src/ui_ext.rs index c44adc5..84c7ba1 100644 --- a/crates/client/src/ui_ext.rs +++ b/crates/client/src/ui_ext.rs @@ -69,18 +69,6 @@ pub trait UiExt { button } - - fn toasts(&mut self, writer: impl FnOnce(&mut Toasts) -> &mut Toast) { - use parking_lot::Mutex; - use std::sync::Arc; - - let ui = self.ui_mut(); - let toasts = ui.data_mut(|data| data.get_temp_mut_or_default::>>(egui::Id::new(TOASTS_ID)).clone()); - - let mut locked = toasts.lock(); - - writer(&mut locked); - } } impl UiExt for Ui { diff --git a/crates/client/src/views/create_instance_menu.rs b/crates/client/src/views/create_instance_menu.rs index fc7655d..11bd2f6 100644 --- a/crates/client/src/views/create_instance_menu.rs +++ b/crates/client/src/views/create_instance_menu.rs @@ -1,7 +1,7 @@ use eframe::egui; use nomi_core::instance::Instance; -use crate::{errors_pool::ErrorPoolExt, ui_ext::UiExt}; +use crate::{errors_pool::ErrorPoolExt, toasts, ui_ext::UiExt}; use super::{InstancesState, View}; @@ -35,7 +35,7 @@ impl View for CreateInstanceMenu<'_> { let instance = Instance::new(self.create_instance_menu_state.name.trim_end().to_owned(), id); self.instances_state.instances.add_instance(instance); self.instances_state.instances.update_instance_config(id).report_error(); - ui.toasts(|toasts| toasts.success("New instance created")); + toasts::add(|toasts| toasts.success("New instance created")); } } } diff --git a/crates/client/src/views/profile_info.rs b/crates/client/src/views/profile_info.rs index 4464a1b..217651a 100644 --- a/crates/client/src/views/profile_info.rs +++ b/crates/client/src/views/profile_info.rs @@ -7,7 +7,7 @@ use nomi_modding::modrinth::project::ProjectId; use parking_lot::RwLock; use crate::{ - collections::DownloadAddedModsCollection, errors_pool::ErrorPoolExt, open_directory::open_directory_native, ui_ext::UiExt, + collections::DownloadAddedModsCollection, errors_pool::ErrorPoolExt, open_directory::open_directory_native, toasts, ui_ext::UiExt, views::InstancesConfig, TabKind, }; @@ -330,7 +330,7 @@ impl View for ProfileInfo<'_> { ui.ctx().copy_text(export_code); } - ui.toasts(|toasts| toasts.success("Copied the export code to the clipboard")); + toasts::add(|toasts| toasts.success("Copied the export code to the clipboard")); } }); From dc96d796a954709ade00400841b4edd2de84523d Mon Sep 17 00:00:00 2001 From: Umatriz Date: Sun, 18 Aug 2024 14:49:24 +0300 Subject: [PATCH 12/15] feat: deleting instances --- crates/client/src/collections.rs | 44 +++++- crates/client/src/main.rs | 5 +- crates/client/src/ui_ext.rs | 7 +- crates/client/src/views/profiles.rs | 167 +++++++++++------------ crates/client/src/views/settings.rs | 112 +++++++-------- crates/nomi-core/src/game_paths.rs | 8 +- crates/nomi-core/src/instance/launch.rs | 41 ------ crates/nomi-core/src/instance/mod.rs | 12 +- crates/nomi-core/src/instance/profile.rs | 34 +++++ 9 files changed, 234 insertions(+), 196 deletions(-) diff --git a/crates/client/src/collections.rs b/crates/client/src/collections.rs index 5df42f0..35bf7a9 100644 --- a/crates/client/src/collections.rs +++ b/crates/client/src/collections.rs @@ -9,6 +9,7 @@ use nomi_modding::modrinth::{ use crate::{ errors_pool::ErrorPoolExt, + toasts, views::{InstancesConfig, SimpleDependency}, }; @@ -102,9 +103,9 @@ impl<'c> TasksCollection<'c> for GameDownloadingCollection { pub struct GameDeletionCollection; impl<'c> TasksCollection<'c> for GameDeletionCollection { - type Context = (); + type Context = &'c InstancesConfig; - type Target = (); + type Target = InstanceProfileId; type Executor = executors::Linear; @@ -112,8 +113,43 @@ impl<'c> TasksCollection<'c> for GameDeletionCollection { "Game deletion collection" } - fn handle(_context: Self::Context) -> Handler<'c, Self::Target> { - Handler::new(|()| ()) + fn handle(context: Self::Context) -> Handler<'c, Self::Target> { + Handler::new(|id: InstanceProfileId| { + if let Some(instance) = context.find_instance(id.instance()) { + instance.write().remove_profile(id); + if context.update_instance_config(id.instance()).report_error().is_some() { + toasts::add(|toasts| toasts.success("Successfully removed the profile")) + } + } + }) + } +} + +pub struct InstanceDeletionCollection; + +impl<'c> TasksCollection<'c> for InstanceDeletionCollection { + type Context = &'c mut InstancesConfig; + + type Target = Option; + + type Executor = executors::Linear; + + fn name() -> &'static str { + "Instance deletion collection" + } + + fn handle(context: Self::Context) -> Handler<'c, Self::Target> { + Handler::new(|id: Option| { + let Some(id) = id else { + return; + }; + + if context.remove_instance(id).is_none() { + return; + } + + toasts::add(|toasts| toasts.success("Successfully removed the instance")) + }) } } diff --git a/crates/client/src/main.rs b/crates/client/src/main.rs index 6ec1589..35e7371 100644 --- a/crates/client/src/main.rs +++ b/crates/client/src/main.rs @@ -14,7 +14,7 @@ use egui_notify::Toasts; use open_directory::open_directory_native; use std::path::Path; use subscriber::EguiLayer; -use ui_ext::{UiExt, TOASTS_ID}; +use ui_ext::UiExt; use views::{add_tab_menu::AddTab, AddProfileMenu, CreateInstanceMenu, View}; use errors_pool::{ErrorPoolExt, ERRORS_POOL}; @@ -135,7 +135,8 @@ impl eframe::App for MyTabs { .manager .add_collection::(()) .add_collection::(&mut self.context.states.add_profile_menu.fabric_versions) - .add_collection::(()) + .add_collection::(&self.context.states.instances.instances) + .add_collection::(&mut self.context.states.instances.instances) .add_collection::(&self.context.states.instances.instances) .add_collection::(()) .add_collection::(&mut self.context.states.mod_manager.current_project) diff --git a/crates/client/src/ui_ext.rs b/crates/client/src/ui_ext.rs index 84c7ba1..5450021 100644 --- a/crates/client/src/ui_ext.rs +++ b/crates/client/src/ui_ext.rs @@ -1,7 +1,4 @@ use eframe::egui::{self, popup_below_widget, Id, PopupCloseBehavior, Response, RichText, Ui, WidgetText}; -use egui_notify::{Toast, Toasts}; - -pub const TOASTS_ID: &str = "global_egui_notify_toasts"; pub trait UiExt { fn ui(&self) -> &Ui; @@ -27,6 +24,10 @@ pub trait UiExt { ui.label(RichText::new(format!("⚠ {}", text.into())).color(ui.visuals().warn_fg_color)) } + fn warn_irreversible_action(&mut self) -> Response { + self.warn_label_with_icon_before("This action is irreversible.") + } + fn markdown_ui(&mut self, id: egui::Id, markdown: &str) { use parking_lot::Mutex; use std::sync::Arc; diff --git a/crates/client/src/views/profiles.rs b/crates/client/src/views/profiles.rs index 6859262..4a5ecde 100644 --- a/crates/client/src/views/profiles.rs +++ b/crates/client/src/views/profiles.rs @@ -1,7 +1,7 @@ use std::{collections::HashSet, path::PathBuf, sync::Arc}; use anyhow::bail; -use eframe::egui::{self, Align2, RichText, TextWrapMode, Ui}; +use eframe::egui::{self, Align2, Id, RichText, TextWrapMode, Ui}; use egui_extras::{Column, TableBuilder}; use egui_task_manager::{Caller, Task, TaskManager}; use itertools::Itertools; @@ -9,7 +9,7 @@ use nomi_core::{ configs::profile::{ProfileState, VersionProfile}, fs::write_toml_config_sync, game_paths::GamePaths, - instance::{launch::arguments::UserData, load_instances, Instance, InstanceProfileId, ProfilePayload}, + instance::{delete_profile, launch::arguments::UserData, load_instances, Instance, InstanceProfileId, ProfilePayload}, repository::{launcher_manifest::LauncherManifest, username::Username}, }; use parking_lot::RwLock; @@ -18,9 +18,11 @@ use tracing::error; use crate::{ cache::GLOBAL_CACHE, - collections::{AssetsCollection, GameDownloadingCollection, GameRunnerCollection}, + collections::{AssetsCollection, GameDeletionCollection, GameDownloadingCollection, GameRunnerCollection, InstanceDeletionCollection}, download::{task_assets, task_download_version}, errors_pool::ErrorPoolExt, + toasts, + ui_ext::UiExt, TabKind, }; @@ -94,6 +96,13 @@ impl InstancesConfig { self.instances.iter().find(|p| p.read().id() == id).cloned() } + pub fn remove_instance(&mut self, id: usize) -> Option>> { + self.instances + .iter() + .position(|i| i.read().id() == id) + .map(|idx| self.instances.remove(idx)) + } + pub fn load() -> Self { Self { instances: load_instances() @@ -154,7 +163,6 @@ impl InstancesConfig { } impl Instances<'_> { - // TODO: It requires profile to be loaded fn profile_action_ui(&mut self, ui: &mut Ui, profile_payload: &ProfilePayload) { let button = if profile_payload.is_downloaded { ui.add_enabled(self.is_allowed_to_take_action, egui::Button::new("Launch")) @@ -282,15 +290,42 @@ impl Instances<'_> { }); row.col(|ui| { - ui.button("TODO: Delete"); + ui.button_with_confirm_popup(Id::new("confirm_profile_deletion").with(profile.id), "Delete", |ui| { + ui.set_width(200.0); + ui.label("Are you sure you want to delete this profile?"); + ui.warn_irreversible_action(); + + ui.horizontal(|ui| { + let yes_button = ui.button("Yes"); + let no_button = ui.button("No"); + + if yes_button.clicked() { + let id = profile.id; + if let Some(profile) = self.profiles_state.instances.find_profile(id) { + let profile = profile.read(); + let game_version = profile.profile.version().to_owned(); + let task = Task::new( + "Deleting profile", + Caller::standard(async move { + delete_profile(GamePaths::from_id(id), &game_version).await; + id + }), + ); + + self.manager.push_task::(task) + } else { + toasts::add(|toasts| toasts.warning("Cannot find profile to delete")); + } + } + + if yes_button.clicked() || no_button.clicked() { + ui.memory_mut(|mem| mem.close_popup()) + } + }); + }); }); }); } - - // is_deleting.drain(..).for_each(|index| { - // self.profiles_state.instances.instances.remove(index); - // self.profiles_state.instances.update_config_sync().report_error(); - // }); }); } } @@ -303,87 +338,43 @@ impl View for Instances<'_> { for instance in iter { let instance = instance.read(); ui.group(|ui| { - ui.label(RichText::new(instance.name()).strong()); - egui::CollapsingHeader::new("Profiles") - .id_source(egui::Id::new(instance.id()).with("__profiles_list")) - .show(ui, |ui| { + let id = ui.make_persistent_id("instance_details").with(instance.id()); + egui::collapsing_header::CollapsingState::load_with_default_open(ui.ctx(), id, false) + .show_header(ui, |ui| { + ui.label(RichText::new(instance.name()).strong()); + ui.button("Launch"); + }) + .body(|ui| { + ui.button_with_confirm_popup(Id::new("confirm_instance_deletion").with(instance.id()), "Delete", |ui| { + ui.set_width(200.0); + ui.label("Are you sure you want to delete this instance?"); + ui.warn_irreversible_action(); + + ui.horizontal(|ui| { + let yes_button = ui.button("Yes"); + let no_button = ui.button("No"); + + if yes_button.clicked() { + let path = instance.path(); + let id = instance.id(); + let task = Task::new( + "Deleting the instance", + Caller::standard(async move { tokio::fs::remove_dir_all(path).await.report_error().map(|()| id) }), + ); + self.manager.push_task::(task); + } + + if yes_button.clicked() || no_button.clicked() { + ui.memory_mut(|mem| mem.close_popup()) + } + }); + }); + + ui.heading("Profiles"); + self.show_profiles_for_instance(ui, instance.profiles(), self.is_allowed_to_take_action) }); }); } } } - -fn delete_profile_ui() { - // if let ProfileState::Downloaded(instance) = &profile.profile.state { - // let popup_id = ui.make_persistent_id("delete_popup_id"); - // let button = ui - // .add_enabled(is_allowed_to_take_action, Button::new("Delete")) - // .on_hover_text("It will delete the profile and it's data"); - - // if button.clicked() { - // ui.memory_mut(|mem| mem.toggle_popup(popup_id)); - // } - - // popup_below_widget(ui, popup_id, &button, PopupCloseBehavior::CloseOnClickOutside, |ui| { - // ui.set_min_width(150.0); - - // let delete_client_id = Id::new("delete_client"); - // let delete_libraries_id = Id::new("delete_libraries"); - // let delete_assets_id = Id::new("delete_assets"); - // let delete_mods_id = Id::new("delete_mods"); - - // let mut make_checkbox = |text: &str, id, default: bool| { - // let mut state = ui.data_mut(|map| *map.get_temp_mut_or_insert_with(id, move || default)); - // ui.checkbox(&mut state, text); - // ui.data_mut(|map| map.insert_temp(id, state)); - // }; - - // make_checkbox("Delete profile's client", delete_client_id, true); - // make_checkbox("Delete profile's libraries", delete_libraries_id, false); - // if profile.profile.loader().is_fabric() { - // make_checkbox("Delete profile's mods", delete_mods_id, true); - // } - // make_checkbox("Delete profile's assets", delete_assets_id, false); - - // ui.label("Are you sure you want to delete this profile and it's data?"); - // ui.horizontal(|ui| { - // ui.warn_icon_with_hover_text("Deleting profile's assets and libraries might break other profiles."); - // if ui.button("Yes").clicked() { - // is_deleting.push(index); - - // let version = &instance.settings.version; - - // let checkbox_data = |id| ui.data(|data| data.get_temp(id)).unwrap_or_default(); - - // let delete_client = checkbox_data(delete_client_id); - // let delete_libraries = checkbox_data(delete_libraries_id); - // let delete_assets = checkbox_data(delete_assets_id); - // let delete_mods = checkbox_data(delete_mods_id); - - // let profile_id = profile.profile.id; - - // let instance = instance.clone(); - // let caller = Caller::standard(async move { - // let path = Path::new(DOT_NOMI_MODS_STASH_DIR).join(format!("{}", profile_id)); - // if delete_mods && path.exists() { - // tokio::fs::remove_dir_all(path).await.report_error(); - // } - // instance.delete(delete_client, delete_libraries, delete_assets).await.report_error(); - // }); - - // let task = Task::new(format!("Deleting the game's files ({})", version), caller); - - // self.manager.push_task::(task); - - // self.tabs_state.remove_profile_related_tabs(&profile); - - // ui.memory_mut(|mem| mem.close_popup()); - // } - // if ui.button("No").clicked() { - // ui.memory_mut(|mem| mem.close_popup()); - // } - // }); - // }); - // } -} diff --git a/crates/client/src/views/settings.rs b/crates/client/src/views/settings.rs index 81fd944..4541004 100644 --- a/crates/client/src/views/settings.rs +++ b/crates/client/src/views/settings.rs @@ -11,7 +11,7 @@ use nomi_core::{ }; use serde::{Deserialize, Serialize}; -use crate::{collections::JavaCollection, errors_pool::ErrorPoolExt, states::JavaState}; +use crate::{collections::JavaCollection, errors_pool::ErrorPoolExt, states::JavaState, ui_ext::UiExt}; use super::View; @@ -93,27 +93,27 @@ fn check_uuid(value: &str, _context: &()) -> garde::Result { impl View for SettingsPage<'_> { fn ui(self, ui: &mut eframe::egui::Ui) { - ui.collapsing("Utils", |ui| { - let launcher_path = PathBuf::from(DOT_NOMI_LOGS_DIR); + ui.heading("Utils"); - if launcher_path.exists() { - if ui.button("Delete launcher's logs").clicked() { - let _ = std::fs::remove_dir_all(launcher_path); - } - } else { - ui.label(RichText::new("The launcher log's directory is already deleted").color(ui.visuals().warn_fg_color)); + let launcher_path = PathBuf::from(DOT_NOMI_LOGS_DIR); + + if launcher_path.exists() { + if ui.button("Delete launcher's logs").clicked() { + let _ = std::fs::remove_dir_all(launcher_path); } + } else { + ui.warn_label("The launcher log's directory is already deleted"); + } - let game_path = PathBuf::from("./logs"); + let game_path = PathBuf::from("./logs"); - if game_path.exists() { - if ui.button("Delete game's logs").clicked() { - let _ = std::fs::remove_dir_all(game_path); - } - } else { - ui.label(RichText::new("The games log's directory is already deleted").color(ui.visuals().warn_fg_color)); + if game_path.exists() { + if ui.button("Delete game's logs").clicked() { + let _ = std::fs::remove_dir_all(game_path); } - }); + } else { + ui.warn_label("The games log's directory is already deleted"); + } let settings_data = self.settings_state.clone(); @@ -128,52 +128,52 @@ impl View for SettingsPage<'_> { } } - ui.collapsing("User", |ui| { - FormField::new(&mut form, field_path!("username")) - .label("Username") - .ui(ui, egui::TextEdit::singleline(&mut self.settings_state.username)); - - FormField::new(&mut form, field_path!("uuid")) - .label("UUID") - .ui(ui, egui::TextEdit::singleline(&mut self.settings_state.uuid)); - }); + ui.heading("User"); + + FormField::new(&mut form, field_path!("username")) + .label("Username") + .ui(ui, egui::TextEdit::singleline(&mut self.settings_state.username)); + + FormField::new(&mut form, field_path!("uuid")) + .label("UUID") + .ui(ui, egui::TextEdit::singleline(&mut self.settings_state.uuid)); + + ui.heading("Java"); + + if ui + .add_enabled( + self.manager.get_collection::().tasks().is_empty(), + egui::Button::new("Download Java"), + ) + .on_hover_text("Pressing this button will start the Java downloading process and add the downloaded binary as the selected one") + .clicked() + { + self.java_state.download_java(self.manager, ui.ctx().clone()); + self.settings_state.java = JavaRunner::path(PathBuf::from(DOT_NOMI_JAVA_EXECUTABLE)); + self.settings_state.update_config(); + } - ui.collapsing("Java", |ui| { - if ui - .add_enabled( - self.manager.get_collection::().tasks().is_empty(), - egui::Button::new("Download Java"), - ) - .on_hover_text("Pressing this button will start the Java downloading process and add the downloaded binary as selected") - .clicked() - { - self.java_state.download_java(self.manager, ui.ctx().clone()); - self.settings_state.java = JavaRunner::path(PathBuf::from(DOT_NOMI_JAVA_EXECUTABLE)); - self.settings_state.update_config(); - } + FormField::new(&mut form, field_path!("java")).label("Java").ui(ui, |ui: &mut egui::Ui| { + ui.radio_value(&mut self.settings_state.java, JavaRunner::command("java"), "Command"); - FormField::new(&mut form, field_path!("java")).label("Java").ui(ui, |ui: &mut egui::Ui| { - ui.radio_value(&mut self.settings_state.java, JavaRunner::command("java"), "Command"); + ui.radio_value(&mut self.settings_state.java, JavaRunner::path(PathBuf::new()), "Custom path"); - ui.radio_value(&mut self.settings_state.java, JavaRunner::path(PathBuf::new()), "Custom path"); + if matches!(settings_data.java, JavaRunner::Path(_)) && ui.button("Select custom java binary").clicked() { + self.file_dialog.select_file(); + } - if matches!(settings_data.java, JavaRunner::Path(_)) && ui.button("Select custom java binary").clicked() { - self.file_dialog.select_file(); + ui.label(format!( + "Java will be run using {}", + match &settings_data.java { + JavaRunner::Command(command) => format!("{} command", command), + JavaRunner::Path(path) => format!("{} executable", path.display()), } - - ui.label(format!( - "Java will be run using {}", - match &settings_data.java { - JavaRunner::Command(command) => format!("{} command", command), - JavaRunner::Path(path) => format!("{} executable", path.display()), - } - )) - }); + )) }); - ui.collapsing("Client", |ui| { - ui.add(egui::Slider::new(&mut self.settings_state.client_settings.pixels_per_point, 0.5..=5.0).text("Pixels per point")) - }); + ui.heading("Launcher"); + + ui.add(egui::Slider::new(&mut self.settings_state.client_settings.pixels_per_point, 0.5..=5.0).text("Pixels per point")); } if let Some(Ok(())) = form.handle_submit(&ui.button("Save"), ui) { diff --git a/crates/nomi-core/src/game_paths.rs b/crates/nomi-core/src/game_paths.rs index 46caa0d..2c164ec 100644 --- a/crates/nomi-core/src/game_paths.rs +++ b/crates/nomi-core/src/game_paths.rs @@ -7,9 +7,16 @@ use crate::{ #[derive(Debug, Clone)] pub struct GamePaths { + /// Game root directory. This is usually corresponds to the instance directory. pub game: PathBuf, + + /// Assets directory. pub assets: PathBuf, + + /// Profile directory. pub profile: PathBuf, + + /// Libraries directory. pub libraries: PathBuf, } @@ -24,7 +31,6 @@ impl GamePaths { Self { game: path.to_path_buf(), assets: ASSETS_DIR.into(), - // Is this a good approach? profile: path.join("profiles").join(format!("{profile_id}")), libraries: LIBRARIES_DIR.into(), } diff --git a/crates/nomi-core/src/instance/launch.rs b/crates/nomi-core/src/instance/launch.rs index 98a2d26..5c140a9 100644 --- a/crates/nomi-core/src/instance/launch.rs +++ b/crates/nomi-core/src/instance/launch.rs @@ -54,47 +54,6 @@ pub struct LaunchInstance { } impl LaunchInstance { - #[tracing::instrument(skip(self), err)] - pub async fn delete(&self, paths: GamePaths, delete_client: bool, delete_libraries: bool, delete_assets: bool) -> anyhow::Result<()> { - let manifest = read_json_config::(paths.manifest_file(&self.settings.version)).await?; - let arguments_builder = ArgumentsBuilder::new(&paths, self, &manifest).build_classpath(); - - if delete_client { - let path = paths.version_jar_file(&self.settings.version); - let _ = tokio::fs::remove_file(&path) - .await - .inspect(|()| { - debug!("Removed client successfully: {}", &path.display()); - }) - .inspect_err(|_| { - warn!("Cannot remove client: {}", &path.display()); - }); - } - - if delete_libraries { - for library in arguments_builder.classpath_as_slice() { - let _ = tokio::fs::remove_file(library) - .await - .inspect(|()| trace!("Removed library successfully: {}", library.display())) - .inspect_err(|_| warn!("Cannot remove library: {}", library.display())); - } - } - - if delete_assets { - let assets = read_json_config::(dbg!(paths.assets.join("indexes").join(format!("{}.json", manifest.asset_index.id)))).await?; - for asset in assets.objects.values() { - let path = paths.assets.join("objects").join(&asset.hash[0..2]).join(&asset.hash); - - let _ = tokio::fs::remove_file(&path) - .await - .inspect(|()| trace!("Removed asset successfully: {}", path.display())) - .inspect_err(|e| warn!("Cannot remove asset: {}. Error: {e}", path.display())); - } - } - - Ok(()) - } - pub fn loader_profile(&self) -> Option<&LoaderProfile> { self.loader_profile.as_ref() } diff --git a/crates/nomi-core/src/instance/mod.rs b/crates/nomi-core/src/instance/mod.rs index 0d01c68..94a4d71 100644 --- a/crates/nomi-core/src/instance/mod.rs +++ b/crates/nomi-core/src/instance/mod.rs @@ -9,7 +9,7 @@ use std::path::{Path, PathBuf}; pub use profile::*; use serde::{Deserialize, Serialize}; -use tracing::error; +use tracing::{error, warn}; use crate::{ configs::profile::{Loader, VersionProfile}, @@ -76,6 +76,16 @@ impl Instance { self.profiles_mut().iter_mut().find(|p| p.id == id) } + pub fn remove_profile(&mut self, id: InstanceProfileId) -> Option { + let opt = self.profiles.iter().position(|p| p.id == id).map(|idx| self.profiles.remove(idx)); + + if opt.is_none() { + warn!(?id, "Cannot find a profile to remove"); + } + + opt + } + /// Generate id for the next profile in this instance pub fn next_id(&self) -> InstanceProfileId { match &self.profiles.iter().max_by_key(|profile| profile.id.1) { diff --git a/crates/nomi-core/src/instance/profile.rs b/crates/nomi-core/src/instance/profile.rs index e1c9143..ef74597 100644 --- a/crates/nomi-core/src/instance/profile.rs +++ b/crates/nomi-core/src/instance/profile.rs @@ -1,3 +1,4 @@ +use tracing::{debug, warn}; use typed_builder::TypedBuilder; use crate::{downloads::downloaders::assets::AssetsDownloader, game_paths::GamePaths, state::get_launcher_manifest}; @@ -44,3 +45,36 @@ impl Profile { self.downloader.insert(builder).build() } } + +#[tracing::instrument] +pub async fn delete_profile(paths: GamePaths, game_version: &str) { + let path = paths.version_jar_file(game_version); + let _ = tokio::fs::remove_file(&path) + .await + .inspect(|()| { + debug!("Removed client successfully: {}", &path.display()); + }) + .inspect_err(|_| { + warn!("Cannot remove client: {}", &path.display()); + }); + + let path = &paths.profile_config(); + let _ = tokio::fs::remove_file(&path) + .await + .inspect(|()| { + debug!(path = %&path.display(), "Removed profile config successfully"); + }) + .inspect_err(|_| { + warn!(path = %&path.display(), "Cannot profile config"); + }); + + let path = &paths.profile; + let _ = tokio::fs::remove_dir(&path) + .await + .inspect(|()| { + debug!(path = %&path.display(), "Removed profile directory successfully"); + }) + .inspect_err(|_| { + warn!(path = %&path.display(), "Cannot profile directory"); + }); +} From f6e6ab3a619c4f662d830232f86ff13da2b8cea0 Mon Sep 17 00:00:00 2001 From: Umatriz Date: Tue, 20 Aug 2024 10:27:33 +0300 Subject: [PATCH 13/15] feat: add ability to select the main profile and run it --- crates/client/src/cache.rs | 23 ++++----- crates/client/src/views/profiles.rs | 74 ++++++++++++++++++++++------ crates/nomi-core/src/instance/mod.rs | 4 ++ 3 files changed, 75 insertions(+), 26 deletions(-) diff --git a/crates/client/src/cache.rs b/crates/client/src/cache.rs index 3ddfea4..bebcd31 100644 --- a/crates/client/src/cache.rs +++ b/crates/client/src/cache.rs @@ -29,17 +29,18 @@ impl GlobalCache { Self { profiles: HashMap::new() } } - pub fn request_profile(&mut self, id: InstanceProfileId, path: PathBuf) -> Option>> { - match self.profiles.get(&id) { - Some(some) => Some(some.clone()), - None => read_toml_config_sync(path) - .inspect_err(|error| error!(%error, "Cannot read profile config")) - .report_error() - .and_then(|profile| { - self.profiles.insert(id, profile); - self.profiles.get(&id).cloned() - }), - } + pub fn get_profile(&self, id: InstanceProfileId) -> Option>> { + self.profiles.get(&id).cloned() + } + + pub fn load_profile(&mut self, id: InstanceProfileId, path: PathBuf) -> Option>> { + read_toml_config_sync(path) + .inspect_err(|error| error!(%error, "Cannot read profile config")) + .report_error() + .and_then(|profile| { + self.profiles.insert(id, profile); + self.profiles.get(&id).cloned() + }) } fn loaded_profiles(&self) -> Vec>> { diff --git a/crates/client/src/views/profiles.rs b/crates/client/src/views/profiles.rs index 4a5ecde..3d1d9d3 100644 --- a/crates/client/src/views/profiles.rs +++ b/crates/client/src/views/profiles.rs @@ -1,7 +1,7 @@ -use std::{collections::HashSet, path::PathBuf, sync::Arc}; +use std::{collections::HashSet, mem, path::PathBuf, sync::Arc}; use anyhow::bail; -use eframe::egui::{self, Align2, Id, RichText, TextWrapMode, Ui}; +use eframe::egui::{self, Id, RichText, TextWrapMode, Ui}; use egui_extras::{Column, TableBuilder}; use egui_task_manager::{Caller, Task, TaskManager}; use itertools::Itertools; @@ -84,7 +84,10 @@ impl ModdedProfile { impl InstancesConfig { pub fn find_profile(&self, id: InstanceProfileId) -> Option>> { - self.get_profile_path(id).and_then(|path| GLOBAL_CACHE.write().request_profile(id, path)) + let mut cache = GLOBAL_CACHE.write(); + cache + .get_profile(id) + .or_else(|| self.get_profile_path(id).and_then(|path| cache.load_profile(id, path))) } pub fn get_profile_path(&self, id: InstanceProfileId) -> Option { @@ -174,15 +177,16 @@ impl Instances<'_> { }; if button.clicked() { - self.profile_action(ui, profile_payload) + let Some(profile_lock) = self.profiles_state.instances.find_profile(profile_payload.id) else { + error!(id = ?profile_payload.id, "Cannot find the profile"); + return; + }; + + self.profile_action(ui, profile_lock) } } - fn profile_action(&mut self, ui: &mut Ui, profile_payload: &ProfilePayload) { - let Some(profile_lock) = self.profiles_state.instances.find_profile(profile_payload.id) else { - error!(id = ?profile_payload.id, "Cannot find the profile"); - return; - }; + fn profile_action(&mut self, ui: &mut Ui, profile_lock: Arc>) { let profile = profile_lock.read(); match &profile.profile.state { ProfileState::Downloaded(instance) => { @@ -245,7 +249,7 @@ impl Instances<'_> { } } - fn show_profiles_for_instance(&mut self, ui: &mut Ui, profiles: &[ProfilePayload], is_allowed_to_take_action: bool) { + fn show_profiles_for_instance(&mut self, ui: &mut Ui, profiles: &[ProfilePayload]) { TableBuilder::new(ui) .column(Column::auto().at_least(120.0).at_most(240.0)) .columns(Column::auto(), 5) @@ -263,7 +267,7 @@ impl Instances<'_> { .body(|mut body| { // let mut is_deleting = vec![]; - for (_index, profile) in profiles.iter().enumerate() { + for profile in profiles.iter() { body.row(30.0, |mut row| { row.col(|ui| { ui.add(egui::Label::new(&profile.name).truncate()); @@ -336,15 +340,55 @@ impl View for Instances<'_> { let iter = self.profiles_state.instances.instances.iter().cloned().collect_vec().into_iter(); for instance in iter { - let instance = instance.read(); ui.group(|ui| { - let id = ui.make_persistent_id("instance_details").with(instance.id()); + let id = ui.make_persistent_id("instance_details").with(instance.read().id()); egui::collapsing_header::CollapsingState::load_with_default_open(ui.ctx(), id, false) .show_header(ui, |ui| { + let instance = instance.read(); ui.label(RichText::new(instance.name()).strong()); - ui.button("Launch"); + + if let Some(profile_lock) = instance.main_profile().and_then(|id| self.profiles_state.instances.find_profile(id)) { + let response = if profile_lock.read().profile.is_downloaded() { + ui.add_enabled(self.is_allowed_to_take_action, egui::Button::new("Launch")) + } else { + ui.add_enabled(self.is_allowed_to_take_action, egui::Button::new("Download")) + }; + + if response.clicked() { + self.profile_action(ui, profile_lock) + } + } }) .body(|ui| { + { + let id = { + let instance_id = instance.read().id(); + Id::new("select_instance_main_profile").with(instance_id) + }; + + let selected_text = { + let instance = instance.read(); + instance + .main_profile() + .and_then(|id| self.profiles_state.instances.find_profile(id)) + .map_or(String::from("No profile selected"), |profile| profile.read().profile.name.clone()) + }; + + egui::ComboBox::new(id, "Main profile").selected_text(selected_text).show_ui(ui, |ui| { + let mut instance = instance.write(); + + let profiles = mem::take(instance.profiles_mut()); + + for profile in &profiles { + ui.selectable_value(instance.main_profile_mut(), Some(profile.id), &profile.name); + } + + let _ = mem::replace(instance.profiles_mut(), profiles); + }); + } + + let instance = instance.read(); + ui.button_with_confirm_popup(Id::new("confirm_instance_deletion").with(instance.id()), "Delete", |ui| { ui.set_width(200.0); ui.label("Are you sure you want to delete this instance?"); @@ -372,7 +416,7 @@ impl View for Instances<'_> { ui.heading("Profiles"); - self.show_profiles_for_instance(ui, instance.profiles(), self.is_allowed_to_take_action) + self.show_profiles_for_instance(ui, instance.profiles()) }); }); } diff --git a/crates/nomi-core/src/instance/mod.rs b/crates/nomi-core/src/instance/mod.rs index 94a4d71..7e2756d 100644 --- a/crates/nomi-core/src/instance/mod.rs +++ b/crates/nomi-core/src/instance/mod.rs @@ -106,6 +106,10 @@ impl Instance { self.main_profile } + pub fn main_profile_mut(&mut self) -> &mut Option { + &mut self.main_profile + } + pub fn profiles(&self) -> &[ProfilePayload] { &self.profiles } From 2c7f28d60dddd525a972ef933c97743c24c01139 Mon Sep 17 00:00:00 2001 From: Umatriz Date: Tue, 20 Aug 2024 10:34:47 +0300 Subject: [PATCH 14/15] fix: warnings --- crates/client/src/download.rs | 2 +- crates/client/src/main.rs | 4 +--- crates/client/src/views/create_instance_menu.rs | 2 +- crates/client/src/views/settings.rs | 2 +- crates/nomi-core/src/instance/launch.rs | 3 +-- crates/nomi-core/tests/forge_new_test.rs | 7 +------ 6 files changed, 6 insertions(+), 14 deletions(-) diff --git a/crates/client/src/download.rs b/crates/client/src/download.rs index 2a18a68..e9a6550 100644 --- a/crates/client/src/download.rs +++ b/crates/client/src/download.rs @@ -5,7 +5,7 @@ use eframe::egui::Context; use egui_task_manager::TaskProgressShared; use nomi_core::{ configs::profile::{Loader, ProfileState}, - downloads::{progress::MappedSender, traits::Downloader, AssetsDownloader, DownloadQueue}, + downloads::{progress::MappedSender, traits::Downloader, AssetsDownloader}, game_paths::GamePaths, instance::{launch::LaunchSettings, Profile}, loaders::{ diff --git a/crates/client/src/main.rs b/crates/client/src/main.rs index 35e7371..18936ce 100644 --- a/crates/client/src/main.rs +++ b/crates/client/src/main.rs @@ -2,15 +2,13 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] use anyhow::anyhow; -use cache::ui_for_loaded_profiles; use collections::{AssetsCollection, GameDownloadingCollection, GameRunnerCollection, JavaCollection}; use context::MyContext; use eframe::{ - egui::{self, Align, Align2, Button, Color32, Frame, Id, Layout, RichText, ScrollArea, ViewportBuilder}, + egui::{self, Align, Button, Frame, Id, Layout, RichText, ScrollArea, ViewportBuilder}, epaint::Vec2, }; use egui_dock::{DockArea, DockState, NodeIndex, Style}; -use egui_notify::Toasts; use open_directory::open_directory_native; use std::path::Path; use subscriber::EguiLayer; diff --git a/crates/client/src/views/create_instance_menu.rs b/crates/client/src/views/create_instance_menu.rs index 11bd2f6..6eadd0a 100644 --- a/crates/client/src/views/create_instance_menu.rs +++ b/crates/client/src/views/create_instance_menu.rs @@ -1,7 +1,7 @@ use eframe::egui; use nomi_core::instance::Instance; -use crate::{errors_pool::ErrorPoolExt, toasts, ui_ext::UiExt}; +use crate::{errors_pool::ErrorPoolExt, toasts}; use super::{InstancesState, View}; diff --git a/crates/client/src/views/settings.rs b/crates/client/src/views/settings.rs index 4541004..0889634 100644 --- a/crates/client/src/views/settings.rs +++ b/crates/client/src/views/settings.rs @@ -1,6 +1,6 @@ use std::path::PathBuf; -use eframe::egui::{self, RichText}; +use eframe::egui::{self}; use egui_file_dialog::FileDialog; use egui_form::{garde::field_path, Form, FormField}; use egui_task_manager::TaskManager; diff --git a/crates/nomi-core/src/instance/launch.rs b/crates/nomi-core/src/instance/launch.rs index 5c140a9..430dc93 100644 --- a/crates/nomi-core/src/instance/launch.rs +++ b/crates/nomi-core/src/instance/launch.rs @@ -10,10 +10,9 @@ use serde::{Deserialize, Serialize}; use tokio::process::Command; use tokio_stream::StreamExt; use tokio_util::codec::{FramedRead, LinesCodec}; -use tracing::{debug, error, info, trace, warn}; +use tracing::{error, info}; use crate::{ - downloads::Assets, fs::read_json_config, game_paths::GamePaths, markers::Undefined, diff --git a/crates/nomi-core/tests/forge_new_test.rs b/crates/nomi-core/tests/forge_new_test.rs index c2b2c86..952821e 100644 --- a/crates/nomi-core/tests/forge_new_test.rs +++ b/crates/nomi-core/tests/forge_new_test.rs @@ -1,4 +1,3 @@ -use std::path::PathBuf; use nomi_core::{ configs::profile::{ProfileState, VersionProfile}, @@ -9,12 +8,8 @@ use nomi_core::{ logs::PrintLogs, InstanceProfileId, Profile, }, - loaders::{ - forge::{Forge, ForgeVersion}, - vanilla::Vanilla, - }, + loaders::forge::{Forge, ForgeVersion}, repository::java_runner::JavaRunner, - DOT_NOMI_JAVA_EXECUTABLE, }; #[tokio::test] From 6533ff90cd5cfa0888ed8f3e0e39492beda3fe91 Mon Sep 17 00:00:00 2001 From: Umatriz Date: Tue, 20 Aug 2024 10:35:33 +0300 Subject: [PATCH 15/15] fix: remove unusied field --- crates/client/src/views/add_profile_menu.rs | 3 --- crates/nomi-core/src/downloads/progress.rs | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/crates/client/src/views/add_profile_menu.rs b/crates/client/src/views/add_profile_menu.rs index aaa0b01..07ddfbf 100644 --- a/crates/client/src/views/add_profile_menu.rs +++ b/crates/client/src/views/add_profile_menu.rs @@ -27,8 +27,6 @@ pub struct AddProfileMenu<'a> { } pub struct AddProfileMenuState { - instance_name: String, - parent_instance: Option>>, selected_version_type: VersionType, @@ -63,7 +61,6 @@ impl Default for AddProfileMenuState { impl AddProfileMenuState { pub fn new() -> Self { Self { - instance_name: String::new(), parent_instance: None, selected_version_type: VersionType::Release, diff --git a/crates/nomi-core/src/downloads/progress.rs b/crates/nomi-core/src/downloads/progress.rs index 03e61bd..5f5a122 100644 --- a/crates/nomi-core/src/downloads/progress.rs +++ b/crates/nomi-core/src/downloads/progress.rs @@ -32,7 +32,7 @@ impl ProgressSender for MappedSender { self.inner.update(mapped).await; if let Some(side_effect) = self.side_effect.as_ref() { - (side_effect)() + (side_effect)(); } } }