Event handling (#75)

* working on amcros

* fleshed out draft

* added feature support

* finished loading

* Fixed issue with multiple data types in macro

* Working, and added more loading uses

* added window scopes

* clippy, fmt

* working other variants

* fmt; clippy

* prettier

* refactored emissions to use increment

* fixed deadlock

* doc changes

* clippy, prettier

* uuid change

* restructured events to util

* loading restructure

* merge fixes

* comments mistake

* better cfg tauri feature structuring

* added extra fields to some loading enum variants

* removed Option<>

* added pack + version labels

* doc change
This commit is contained in:
Wyatt Verchere
2023-04-16 10:12:37 -07:00
committed by GitHub
parent f8173d3b78
commit b120b5cfa8
22 changed files with 3519 additions and 102 deletions

View File

@@ -4,6 +4,9 @@ use tokio::io::{AsyncBufReadExt, BufReader};
use tokio::process::{ChildStderr, ChildStdout};
use tokio::sync::RwLock;
use crate::event::emit::emit_process;
use crate::event::ProcessPayloadType;
use super::Profile;
// Child processes (instances of Minecraft)
@@ -13,6 +16,7 @@ pub struct Children(HashMap<u32, Arc<RwLock<MinecraftChild>>>);
// Minecraft Child, bundles together the PID, the actual Child, and the easily queryable stdout and stderr streams
#[derive(Debug)]
pub struct MinecraftChild {
pub uuid: uuid::Uuid,
pub pid: u32,
pub profile_path: PathBuf, //todo: make UUID when profiles are recognized by UUID
pub child: tokio::process::Child,
@@ -28,12 +32,14 @@ impl Children {
// Inserts a child process to keep track of, and returns a reference to the container struct MinecraftChild
// The threads for stdout and stderr are spawned here
// Unlike a Hashmap's 'insert', this directly returns the reference to the Child rather than any previously stored Child that may exist
pub fn insert_process(
pub async fn insert_process(
&mut self,
pid: u32,
profile_path: PathBuf,
mut child: tokio::process::Child,
) -> Arc<RwLock<MinecraftChild>> {
) -> crate::Result<Arc<RwLock<MinecraftChild>>> {
let uuid = uuid::Uuid::new_v4();
// Create std watcher threads for stdout and stderr
let stdout = SharedOutput::new();
if let Some(child_stdout) = child.stdout.take() {
@@ -54,8 +60,17 @@ impl Children {
});
}
emit_process(
uuid,
pid,
ProcessPayloadType::Launched,
"Launched Minecraft",
)
.await?;
// Create MinecraftChild
let mchild = MinecraftChild {
uuid,
pid,
profile_path,
child,
@@ -64,7 +79,7 @@ impl Children {
};
let mchild = Arc::new(RwLock::new(mchild));
self.0.insert(pid, mchild.clone());
mchild
Ok(mchild)
}
// Returns a ref to the child

View File

@@ -1,6 +1,12 @@
//! Theseus state management system
use crate::config::sled_config;
use crate::event::emit::emit_loading;
use crate::event::emit::init_loading;
use crate::event::LoadingBarType;
use crate::jre;
use crate::loading_join;
use std::sync::Arc;
use tokio::sync::{OnceCell, RwLock, Semaphore};
@@ -68,6 +74,8 @@ impl State {
LAUNCHER_STATE
.get_or_try_init(|| {
async {
let loading_bar = init_loading(LoadingBarType::StateInit, 100.0, "Initializing launcher...").await?;
// Directories
let directories = DirectoryInfo::init().await?;
@@ -77,6 +85,8 @@ impl State {
.path(directories.database_file())
.open()?;
emit_loading(&loading_bar, 10.0, None).await?;
// Settings
let mut settings =
Settings::init(&directories.settings_file()).await?;
@@ -87,11 +97,17 @@ impl State {
let io_semaphore =
RwLock::new(Semaphore::new(io_semaphore_max));
let metadata_fut = Metadata::init(&database);
let profiles_fut =
Profiles::init(&directories, &io_semaphore);
// Launcher data
let (metadata, profiles) = tokio::try_join! {
Metadata::init(&database),
Profiles::init(&directories, &io_semaphore),
}?;
let (metadata, profiles) = loading_join! {
Some(&loading_bar), 20.0, Some("Initializing metadata and profiles...");
metadata_fut, profiles_fut
};
emit_loading(&loading_bar, 10.0, None).await?;
let users = Users::init(&database)?;
let children = Children::new();
@@ -101,13 +117,16 @@ impl State {
// On launcher initialization, attempt a tag fetch after tags init
let mut tags = Tags::init(&database)?;
if let Err(tag_fetch_err) =
tags.fetch_update(&io_semaphore).await
tags.fetch_update(&io_semaphore,Some(&loading_bar)).await
{
tracing::error!(
"Failed to fetch tags on launcher init: {}",
tag_fetch_err
);
};
emit_loading(&loading_bar, 10.0, None).await?;
// 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 {

View File

@@ -1,6 +1,10 @@
use super::settings::{Hooks, MemorySettings, WindowSize};
use crate::config::MODRINTH_API_URL;
use crate::data::DirectoryInfo;
use crate::event::emit::{
emit_profile, init_loading, loading_try_for_each_concurrent,
};
use crate::event::{LoadingBarType, ProfilePayloadType};
use crate::state::projects::Project;
use crate::state::{ModrinthVersion, ProjectType};
use crate::util::fetch::{fetch, fetch_json, write, write_cached_icon};
@@ -17,6 +21,7 @@ use std::{
};
use tokio::sync::Semaphore;
use tokio::{fs, sync::RwLock};
use uuid::Uuid;
const PROFILE_JSON_PATH: &str = "profile.json";
@@ -28,6 +33,7 @@ pub const CURRENT_FORMAT_VERSION: u32 = 1;
// Represent a Minecraft instance.
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct Profile {
pub uuid: Uuid, // todo: will be used in restructure to refer to profiles
pub path: PathBuf,
pub metadata: ProfileMetadata,
#[serde(skip_serializing_if = "Option::is_none")]
@@ -90,6 +96,7 @@ pub struct JavaSettings {
impl Profile {
#[tracing::instrument]
pub async fn new(
uuid: Uuid,
name: String,
version: String,
path: PathBuf,
@@ -102,6 +109,7 @@ impl Profile {
}
Ok(Self {
uuid,
path: canonicalize(path)?,
metadata: ProfileMetadata {
name,
@@ -368,7 +376,14 @@ impl Profiles {
}
#[tracing::instrument(skip(self))]
pub fn insert(&mut self, profile: Profile) -> crate::Result<&Self> {
pub async fn insert(&mut self, profile: Profile) -> crate::Result<&Self> {
emit_profile(
profile.uuid,
profile.path.clone(),
&profile.metadata.name,
ProfilePayloadType::Added,
)
.await?;
self.0.insert(
canonicalize(&profile.path)?
.to_str()
@@ -387,6 +402,7 @@ impl Profiles {
path: &'a Path,
) -> crate::Result<&Self> {
self.insert(Self::read_profile_from_dir(&canonicalize(path)?).await?)
.await
}
#[tracing::instrument(skip(self))]
@@ -404,9 +420,21 @@ impl Profiles {
#[tracing::instrument(skip_all)]
pub async fn sync(&self) -> crate::Result<&Self> {
stream::iter(self.0.iter())
.map(Ok::<_, crate::Error>)
.try_for_each_concurrent(None, |(path, profile)| async move {
let loading_bar = init_loading(
LoadingBarType::ProfileSync,
100.0,
"Syncing profiles...",
)
.await?;
let num_futs = self.0.len();
loading_try_for_each_concurrent(
stream::iter(self.0.iter()).map(Ok::<_, crate::Error>),
None,
Some(&loading_bar),
100.0,
num_futs,
None,
|(path, profile)| async move {
let json = serde_json::to_vec(&profile)?;
let json_path = Path::new(&path.to_string_lossy().to_string())
@@ -414,8 +442,9 @@ impl Profiles {
fs::write(json_path, json).await?;
Ok::<_, crate::Error>(())
})
.await?;
},
)
.await?;
Ok(self)
}

View File

@@ -6,6 +6,8 @@ use serde::{Deserialize, Serialize};
use tokio::sync::{RwLock, Semaphore};
use crate::config::{BINCODE_CONFIG, MODRINTH_API_URL};
use crate::event::LoadingBarId;
use crate::loading_join;
use crate::util::fetch::fetch_json;
const CATEGORIES_DB_TREE: &[u8] = b"categories";
@@ -139,6 +141,7 @@ impl Tags {
pub async fn fetch_update(
&mut self,
semaphore: &RwLock<Semaphore>,
loading_bar: Option<&LoadingBarId>,
) -> crate::Result<()> {
let categories = format!("{MODRINTH_API_URL}tag/category");
let loaders = format!("{MODRINTH_API_URL}tag/loader");
@@ -147,6 +150,50 @@ impl Tags {
let donation_platforms =
format!("{MODRINTH_API_URL}tag/donation_platform");
let report_types = format!("{MODRINTH_API_URL}tag/report_type");
let categories_fut = fetch_json::<Vec<Category>>(
Method::GET,
&categories,
None,
None,
semaphore,
);
let loaders_fut = fetch_json::<Vec<Loader>>(
Method::GET,
&loaders,
None,
None,
semaphore,
);
let game_versions_fut = fetch_json::<Vec<GameVersion>>(
Method::GET,
&game_versions,
None,
None,
semaphore,
);
let licenses_fut = fetch_json::<Vec<License>>(
Method::GET,
&licenses,
None,
None,
semaphore,
);
let donation_platforms_fut = fetch_json::<Vec<DonationPlatform>>(
Method::GET,
&donation_platforms,
None,
None,
semaphore,
);
let report_types_fut = fetch_json::<Vec<String>>(
Method::GET,
&report_types,
None,
None,
semaphore,
);
let (
categories,
loaders,
@@ -154,50 +201,14 @@ impl Tags {
licenses,
donation_platforms,
report_types,
) = tokio::try_join!(
fetch_json::<Vec<Category>>(
Method::GET,
&categories,
None,
None,
semaphore
),
fetch_json::<Vec<Loader>>(
Method::GET,
&loaders,
None,
None,
semaphore
),
fetch_json::<Vec<GameVersion>>(
Method::GET,
&game_versions,
None,
None,
semaphore
),
fetch_json::<Vec<License>>(
Method::GET,
&licenses,
None,
None,
semaphore
),
fetch_json::<Vec<DonationPlatform>>(
Method::GET,
&donation_platforms,
None,
None,
semaphore
),
fetch_json::<Vec<String>>(
Method::GET,
&report_types,
None,
None,
semaphore
),
)?;
) = loading_join!(loading_bar, 0.5, None;
categories_fut,
loaders_fut,
game_versions_fut,
licenses_fut,
donation_platforms_fut,
report_types_fut
);
// Store the tags in the database
self.0.categories.insert(