From 16e015b5277244037b524e30e1ae93daffae42af Mon Sep 17 00:00:00 2001 From: Wyatt Verchere Date: Wed, 19 Apr 2023 11:44:44 -0700 Subject: [PATCH] Performance (#89) * jre async * mac support * fixed some settings not being saved to file * fixed older version of mac random crashing bug * added specific mac version detection * linux support for jre changes * added app storage options * tauri features change * dependency fix * removed debug statement * restructured to not pass css through rust * changed to os_info * rerun cicd --- Cargo.lock | 12 ++ theseus/src/api/jre.rs | 28 ++-- theseus/src/api/settings.rs | 1 + theseus/src/state/dirs.rs | 1 - theseus/src/state/java_globals.rs | 5 +- theseus/src/state/mod.rs | 2 +- theseus/src/util/jre.rs | 125 ++++++++++-------- theseus_gui/src-tauri/Cargo.toml | 1 + theseus_gui/src-tauri/src/api/jre.rs | 10 +- theseus_gui/src-tauri/src/main.rs | 22 +++ theseus_gui/src-tauri/tauri.conf.json | 6 +- theseus_gui/src/assets/stylesheets/macFix.css | 3 + theseus_gui/src/main.js | 3 +- theseus_gui/src/mixins/macCssFix.js | 27 ++++ 14 files changed, 164 insertions(+), 82 deletions(-) create mode 100644 theseus_gui/src/assets/stylesheets/macFix.css create mode 100644 theseus_gui/src/mixins/macCssFix.js diff --git a/Cargo.lock b/Cargo.lock index 20650ec53..f223de853 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2215,6 +2215,17 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "os_info" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "006e42d5b888366f1880eda20371fedde764ed2213dc8496f49622fa0c99cd5e" +dependencies = [ + "log", + "serde", + "winapi", +] + [[package]] name = "overload" version = "0.1.1" @@ -3628,6 +3639,7 @@ version = "0.0.0" dependencies = [ "daedalus", "futures", + "os_info", "regex", "serde", "serde_json", diff --git a/theseus/src/api/jre.rs b/theseus/src/api/jre.rs index ad0b7b1c4..74042f25f 100644 --- a/theseus/src/api/jre.rs +++ b/theseus/src/api/jre.rs @@ -15,10 +15,10 @@ pub const JAVA_18PLUS_KEY: &str = "JAVA_18PLUS"; // Autodetect JavaSettings default // Make a guess for what the default Java global settings should be -pub fn autodetect_java_globals() -> crate::Result { - let mut java_8 = find_java8_jres()?; - let mut java_17 = find_java17_jres()?; - let mut java_18plus = find_java18plus_jres()?; +pub async fn autodetect_java_globals() -> crate::Result { + let mut java_8 = find_java8_jres().await?; + let mut java_17 = find_java17_jres().await?; + let mut java_18plus = find_java18plus_jres().await?; // Simply select last one found for initial guess let mut java_globals = JavaGlobals::new(); @@ -76,9 +76,9 @@ pub async fn get_optimal_jre_key(profile: &Profile) -> crate::Result { } // Searches for jres on the system that are 1.18 or higher -pub fn find_java18plus_jres() -> crate::Result> { +pub async fn find_java18plus_jres() -> crate::Result> { let version = extract_java_majorminor_version("1.18")?; - let jres = jre::get_all_jre()?; + let jres = jre::get_all_jre().await?; // Filter out JREs that are not 1.17 or higher Ok(jres .into_iter() @@ -94,9 +94,9 @@ pub fn find_java18plus_jres() -> crate::Result> { } // Searches for jres on the system that are 1.8 exactly -pub fn find_java8_jres() -> crate::Result> { +pub async fn find_java8_jres() -> crate::Result> { let version = extract_java_majorminor_version("1.8")?; - let jres = jre::get_all_jre()?; + let jres = jre::get_all_jre().await?; // Filter out JREs that are not 1.8 Ok(jres @@ -113,9 +113,9 @@ pub fn find_java8_jres() -> crate::Result> { } // Searches for jres on the system that are 1.17 exactly -pub fn find_java17_jres() -> crate::Result> { +pub async fn find_java17_jres() -> crate::Result> { let version = extract_java_majorminor_version("1.17")?; - let jres = jre::get_all_jre()?; + let jres = jre::get_all_jre().await?; // Filter out JREs that are not 1.8 Ok(jres @@ -132,17 +132,17 @@ pub fn find_java17_jres() -> crate::Result> { } // Get all JREs that exist on the system -pub fn get_all_jre() -> crate::Result> { - Ok(jre::get_all_jre()?) +pub async fn get_all_jre() -> crate::Result> { + Ok(jre::get_all_jre().await?) } pub async fn validate_globals() -> crate::Result { let state = State::get().await?; let settings = state.settings.read().await; - Ok(settings.java_globals.is_all_valid()) + Ok(settings.java_globals.is_all_valid().await) } // Validates JRE at a given at a given path pub async fn check_jre(path: PathBuf) -> crate::Result> { - Ok(jre::check_java_at_filepath(&path)) + Ok(jre::check_java_at_filepath(&path).await) } diff --git a/theseus/src/api/settings.rs b/theseus/src/api/settings.rs index fdf3b92c6..45b904707 100644 --- a/theseus/src/api/settings.rs +++ b/theseus/src/api/settings.rs @@ -21,5 +21,6 @@ pub async fn set(settings: Settings) -> crate::Result<()> { // Replaces the settings struct in the RwLock with the passed argument *state.settings.write().await = settings; state.reset_semaphore().await; // reset semaphore to new max + State::sync().await?; Ok(()) } diff --git a/theseus/src/state/dirs.rs b/theseus/src/state/dirs.rs index 6d1ec237b..4e5c0769e 100644 --- a/theseus/src/state/dirs.rs +++ b/theseus/src/state/dirs.rs @@ -26,7 +26,6 @@ impl DirectoryInfo { "Could not find valid config dir".to_string(), ))?; - dbg!(&config_dir); fs::create_dir_all(&config_dir).await.map_err(|err| { crate::ErrorKind::FSError(format!( "Error creating Theseus config directory: {err}" diff --git a/theseus/src/state/java_globals.rs b/theseus/src/state/java_globals.rs index 14126d475..9ef33bc17 100644 --- a/theseus/src/state/java_globals.rs +++ b/theseus/src/state/java_globals.rs @@ -37,11 +37,12 @@ impl JavaGlobals { // Validates that every path here is a valid Java version and that the version matches the version stored here // If false, when checked, the user should be prompted to reselect the Java version - pub fn is_all_valid(&self) -> bool { + pub async fn is_all_valid(&self) -> bool { for (_, java) in self.0.iter() { let jre = jre::check_java_at_filepath( PathBuf::from(&java.path).as_path(), - ); + ) + .await; if let Some(jre) = jre { if jre.version != java.version { return false; diff --git a/theseus/src/state/mod.rs b/theseus/src/state/mod.rs index 82ce0f1bf..421553aaf 100644 --- a/theseus/src/state/mod.rs +++ b/theseus/src/state/mod.rs @@ -130,7 +130,7 @@ impl State { // On launcher initialization, if global java variables are unset, try to find and set them // (they are required for the game to launch) if settings.java_globals.count() == 0 { - settings.java_globals = jre::autodetect_java_globals()?; + settings.java_globals = jre::autodetect_java_globals().await?; } Ok(Arc::new(Self { diff --git a/theseus/src/util/jre.rs b/theseus/src/util/jre.rs index 06580196c..dcc48c785 100644 --- a/theseus/src/util/jre.rs +++ b/theseus/src/util/jre.rs @@ -1,4 +1,5 @@ use dunce::canonicalize; +use futures::prelude::*; use lazy_static::lazy_static; use regex::Regex; use serde::{Deserialize, Serialize}; @@ -6,6 +7,7 @@ use std::env; use std::path::PathBuf; use std::process::Command; use std::{collections::HashSet, path::Path}; +use tokio::task::JoinError; #[cfg(target_os = "windows")] use winreg::{ @@ -23,12 +25,11 @@ pub struct JavaVersion { // Returns a Vec of unique JavaVersions from the PATH, Windows Registry Keys and common Java locations #[cfg(target_os = "windows")] #[tracing::instrument] -pub fn get_all_jre() -> Result, JREError> { - // Use HashSet to avoid duplicates - let mut jres = HashSet::new(); +pub async fn get_all_jre() -> Result, JREError> { + let mut jre_paths = HashSet::new(); // Add JRES directly on PATH - jres.extend(get_all_jre_path()?); + jre_paths.extend(get_all_jre_path().await?); // Hard paths for locations for commonly installed .exes let java_paths = [r"C:/Program Files/Java", r"C:/Program Files (x86)/Java"]; @@ -36,9 +37,7 @@ pub fn get_all_jre() -> Result, JREError> { let Ok(java_subpaths) = std::fs::read_dir(java_path) else {continue }; for java_subpath in java_subpaths { let path = java_subpath?.path(); - if let Some(j) = check_java_at_filepath(&path.join("bin")) { - jres.insert(j); - } + jre_paths.insert(path.join("bin")); } } @@ -53,28 +52,35 @@ pub fn get_all_jre() -> Result, JREError> { r"SOFTWARE\\Eclipse Foundation\\JDK", // Eclipse r"SOFTWARE\\Microsoft\\JDK", // Microsoft ]; + for key in key_paths { if let Ok(jre_key) = RegKey::predef(HKEY_LOCAL_MACHINE) .open_subkey_with_flags(key, KEY_READ | KEY_WOW64_32KEY) { - jres.extend(get_all_jre_winregkey(jre_key)?); + jre_paths.extend(get_paths_from_jre_winregkey(jre_key)?); } if let Ok(jre_key) = RegKey::predef(HKEY_LOCAL_MACHINE) .open_subkey_with_flags(key, KEY_READ | KEY_WOW64_64KEY) { - jres.extend(get_all_jre_winregkey(jre_key)?); + jre_paths.extend(get_paths_from_jre_winregkey(jre_key)?); } } - Ok(jres.into_iter().collect()) + // Get JRE versions from potential paths concurrently + let j = check_java_at_filepaths(jre_paths) + .await? + .into_iter() + .collect(); + Ok(j) } +// Gets paths rather than search directly as RegKeys should not be passed asynchronously (do not impl Send) #[cfg(target_os = "windows")] #[tracing::instrument] -pub fn get_all_jre_winregkey( +pub fn get_paths_from_jre_winregkey( jre_key: RegKey, -) -> Result, JREError> { - let mut jres = HashSet::new(); +) -> Result, JREError> { + let mut jre_paths = HashSet::new(); for subkey in jre_key.enum_keys() { let subkey = subkey?; @@ -87,26 +93,23 @@ pub fn get_all_jre_winregkey( let path: Result = subkey.get_value(subkey_value); let Ok(path) = path else {continue}; - if let Some(j) = - check_java_at_filepath(&PathBuf::from(path).join("bin")) - { - jres.insert(j); - } + + jre_paths.insert(PathBuf::from(path).join("bin")); } } - Ok(jres) + Ok(jre_paths) } // Entrypoint function (Mac) // Returns a Vec of unique JavaVersions from the PATH, and common Java locations #[cfg(target_os = "macos")] #[tracing::instrument] -pub fn get_all_jre() -> Result, JREError> { +pub async fn get_all_jre() -> Result, JREError> { // Use HashSet to avoid duplicates - let mut jres = HashSet::new(); + let mut jre_paths = HashSet::new(); // Add JREs directly on PATH - jres.extend(get_all_jre_path()?); + jre_paths.extend(get_all_jre_path().await?); // Hard paths for locations for commonly installed .exes let java_paths = [ @@ -115,36 +118,34 @@ pub fn get_all_jre() -> Result, JREError> { r"/System/Library/Frameworks/JavaVM.framework/Versions/Current/Commands", ]; for path in java_paths { - if let Some(j) = - check_java_at_filepath(&PathBuf::from(path).join("bin")) - { - jres.insert(j); - } + jre_paths.insert(PathBuf::from(path)); } // Iterate over JavaVirtualMachines/(something)/Contents/Home/bin let base_path = PathBuf::from("/Library/Java/JavaVirtualMachines/"); if base_path.is_dir() { for entry in std::fs::read_dir(base_path)? { let entry = entry?.path().join("Contents/Home/bin"); - if let Some(j) = check_java_at_filepath(entry.as_path()) { - jres.insert(j); - } + jre_paths.insert(entry); } } - - Ok(jres.into_iter().collect()) + // Get JRE versions from potential paths concurrently + let j = check_java_at_filepaths(jre_paths) + .await? + .into_iter() + .collect(); + Ok(j) } // Entrypoint function (Linux) // Returns a Vec of unique JavaVersions from the PATH, and common Java locations #[cfg(target_os = "linux")] #[tracing::instrument] -pub fn get_all_jre() -> Result, JREError> { +pub async fn get_all_jre() -> Result, JREError> { // Use HashSet to avoid duplicates - let mut jres = HashSet::new(); + let mut jre_paths = HashSet::new(); // Add JREs directly on PATH - jres.extend(get_all_jre_path()?); + jre_paths.extend(get_all_jre_path().await?); // Hard paths for locations for commonly installed locations let java_paths = [ @@ -156,34 +157,23 @@ pub fn get_all_jre() -> Result, JREError> { r"/opt/jdks", ]; for path in java_paths { - if let Some(j) = - check_java_at_filepath(&PathBuf::from(path).join("jre").join("bin")) - { - jres.insert(j); - } - if let Some(j) = - check_java_at_filepath(&PathBuf::from(path).join("bin")) - { - jres.insert(j); - } + jre_paths.insert(PathBuf::from(path).join("jre").join("bin")); + jre_paths.insert(PathBuf::from(path).join("bin")); } - Ok(jres.into_iter().collect()) + // Get JRE versions from potential paths concurrently + let j = check_java_at_filepaths(jre_paths) + .await? + .into_iter() + .collect(); + Ok(j) } // Gets all JREs from the PATH env variable #[tracing::instrument] -fn get_all_jre_path() -> Result, JREError> { +async fn get_all_jre_path() -> Result, JREError> { // Iterate over values in PATH variable, where accessible JREs are referenced let paths = env::var("PATH")?; - let paths = env::split_paths(&paths); - - let mut jres = HashSet::new(); - for path in paths { - if let Some(j) = check_java_at_filepath(&path) { - jres.insert(j); - } - } - Ok(jres) + Ok(env::split_paths(&paths).collect()) } #[cfg(target_os = "windows")] @@ -194,10 +184,28 @@ const JAVA_BIN: &str = "java.exe"; #[allow(dead_code)] const JAVA_BIN: &str = "java"; +// For each example filepath in 'paths', perform check_java_at_filepath, checking each one concurrently +// and returning a JavaVersion for every valid path that points to a java bin +#[tracing::instrument] +pub async fn check_java_at_filepaths( + paths: HashSet, +) -> Result, JREError> { + let jres = stream::iter(paths.into_iter()) + .map(|p: PathBuf| { + tokio::task::spawn(async move { check_java_at_filepath(&p).await }) + }) + .buffer_unordered(64) + .collect::>() + .await; + + let jres: Result, JoinError> = jres.into_iter().collect(); + Ok(jres?.into_iter().flatten().collect()) +} + // For example filepath 'path', attempt to resolve it and get a Java version at this path // If no such path exists, or no such valid java at this path exists, returns None #[tracing::instrument] -pub fn check_java_at_filepath(path: &Path) -> Option { +pub async fn check_java_at_filepath(path: &Path) -> Option { // Attempt to canonicalize the potential java filepath // If it fails, this path does not exist and None is returned (no Java here) let Ok(path) = canonicalize(path) else { return None }; @@ -289,6 +297,9 @@ pub enum JREError { #[error("Parsing error: {0}")] ParseError(#[from] std::num::ParseIntError), + #[error("Join error: {0}")] + JoinError(#[from] JoinError), + #[error("No stored tag for Minecraft Version {0}")] NoMinecraftVersionFound(String), } diff --git a/theseus_gui/src-tauri/Cargo.toml b/theseus_gui/src-tauri/Cargo.toml index dd2701619..b7bf16b02 100644 --- a/theseus_gui/src-tauri/Cargo.toml +++ b/theseus_gui/src-tauri/Cargo.toml @@ -28,6 +28,7 @@ daedalus = {version = "0.1.15", features = ["bincode"] } url = "2.2" uuid = { version = "1.1", features = ["serde", "v4"] } +os_info = "3.7.0" [features] # by default Tauri runs in production mode diff --git a/theseus_gui/src-tauri/src/api/jre.rs b/theseus_gui/src-tauri/src/api/jre.rs index bca529d27..1666ba1d1 100644 --- a/theseus_gui/src-tauri/src/api/jre.rs +++ b/theseus_gui/src-tauri/src/api/jre.rs @@ -9,32 +9,32 @@ use super::TheseusSerializableError; /// Get all JREs that exist on the system #[tauri::command] pub async fn jre_get_all_jre() -> Result> { - Ok(jre::get_all_jre()?) + Ok(jre::get_all_jre().await?) } // Finds the isntallation of Java 7, if it exists #[tauri::command] pub async fn jre_find_jre_8_jres() -> Result> { - Ok(jre::find_java8_jres()?) + Ok(jre::find_java8_jres().await?) } // finds the installation of Java 17, if it exists #[tauri::command] pub async fn jre_find_jre_17_jres() -> Result> { - Ok(jre::find_java17_jres()?) + Ok(jre::find_java17_jres().await?) } // Finds the highest version of Java 18+, if it exists #[tauri::command] pub async fn jre_find_jre_18plus_jres() -> Result> { - Ok(jre::find_java18plus_jres()?) + Ok(jre::find_java18plus_jres().await?) } // Autodetect Java globals, by searching the users computer. // Returns a *NEW* JavaGlobals that can be put into Settings #[tauri::command] pub async fn jre_autodetect_java_globals() -> Result { - Ok(jre::autodetect_java_globals()?) + Ok(jre::autodetect_java_globals().await?) } // Gets key for the optimal JRE to use, for a given profile Profile diff --git a/theseus_gui/src-tauri/src/main.rs b/theseus_gui/src-tauri/src/main.rs index 8fcbb100b..cec576510 100644 --- a/theseus_gui/src-tauri/src/main.rs +++ b/theseus_gui/src-tauri/src/main.rs @@ -15,10 +15,32 @@ async fn initialize_state(app: tauri::AppHandle) -> api::Result<()> { Ok(()) } +// cfg only on mac os +// disables mouseover and fixes a random crash error only fixed by recent versions of macos +#[cfg(target_os = "macos")] +#[tauri::command] +async fn should_disable_mouseover() -> bool { + // We try to match version to 12.2 or higher. If unrecognizable to pattern or lower, we default to the css with disabled mouseover for safety + let os = os_info::get(); + if let os_info::Version::Semantic(major, minor, _) = os.version() { + if *major >= 12 && *minor >= 3 { + // Mac os version is 12.3 or higher, we allow mouseover + return false; + } + } + true +} +#[cfg(not(target_os = "macos"))] +#[tauri::command] +async fn should_disable_mouseover() -> bool { + false +} + fn main() { tauri::Builder::default() .invoke_handler(tauri::generate_handler![ initialize_state, + should_disable_mouseover, api::profile_create::profile_create_empty, api::profile_create::profile_create, api::profile::profile_remove, diff --git a/theseus_gui/src-tauri/tauri.conf.json b/theseus_gui/src-tauri/tauri.conf.json index 8e9e8ac62..d310c6a58 100644 --- a/theseus_gui/src-tauri/tauri.conf.json +++ b/theseus_gui/src-tauri/tauri.conf.json @@ -15,7 +15,11 @@ "all": false, "protocol": { "asset": true, - "assetScope": ["$APPDATA/caches/icons/*"] + "assetScope": [ + "$APPDATA/caches/icons/*", + "$APPCONFIG/caches/icons/*", + "$CONFIG/caches/icons/*" + ] }, "window": { "create": true, diff --git a/theseus_gui/src/assets/stylesheets/macFix.css b/theseus_gui/src/assets/stylesheets/macFix.css new file mode 100644 index 000000000..b56737a9c --- /dev/null +++ b/theseus_gui/src/assets/stylesheets/macFix.css @@ -0,0 +1,3 @@ +img { + pointer-events: none !important; +} diff --git a/theseus_gui/src/main.js b/theseus_gui/src/main.js index b4b80d76e..a8c2fcde0 100644 --- a/theseus_gui/src/main.js +++ b/theseus_gui/src/main.js @@ -6,11 +6,12 @@ import '../node_modules/omorphia/dist/style.css' import '@/assets/stylesheets/global.scss' import FloatingVue from 'floating-vue' import { initialize_state } from '@/helpers/state' +import loadCssMixin from './mixins/macCssFix.js' const pinia = createPinia() initialize_state() .then(() => { - createApp(App).use(router).use(pinia).use(FloatingVue).mount('#app') + createApp(App).use(router).use(pinia).use(FloatingVue).mixin(loadCssMixin).mount('#app') }) .catch((err) => console.error(err)) diff --git a/theseus_gui/src/mixins/macCssFix.js b/theseus_gui/src/mixins/macCssFix.js new file mode 100644 index 000000000..cb176dfe9 --- /dev/null +++ b/theseus_gui/src/mixins/macCssFix.js @@ -0,0 +1,27 @@ +import { invoke } from '@tauri-apps/api/tauri' +import cssContent from '@/assets/stylesheets/macFix.css?inline' + +export default { + async mounted() { + await this.checkDisableMouseover() + }, + methods: { + async checkDisableMouseover() { + try { + // Fetch the CSS content from the Rust backend + const should_disable_mouseover = await invoke('should_disable_mouseover') + + if (should_disable_mouseover) { + // Create a style element and set its content + const styleElement = document.createElement('style') + styleElement.innerHTML = cssContent + + // Append the style element to the document's head + document.head.appendChild(styleElement) + } + } catch (error) { + console.error('Error checking OS version from Rust backend', error) + } + }, + }, +}