diff --git a/apps/oneclient/frontend/src/bindings.gen.ts b/apps/oneclient/frontend/src/bindings.gen.ts index 2ffe3630..0c3e4425 100644 --- a/apps/oneclient/frontend/src/bindings.gen.ts +++ b/apps/oneclient/frontend/src/bindings.gen.ts @@ -236,7 +236,7 @@ export type SettingProfileModel = { name: string; java_id: number | null; res: R export type Settings = { global_game_settings: SettingProfileModel; allow_parallel_running_clusters: boolean; enable_gamemode: boolean; discord_enabled: boolean; seen_onboarding: boolean; mod_list_use_grid: boolean; parallel_mod_downloading: boolean; max_concurrent_requests: number; settings_version: number; native_window_frame: boolean; show_tanstack_dev_tools: boolean } -export type SettingsOsExtra = Record +export type SettingsOsExtra = { enable_gamemode: boolean | null } export type SkinVariant = "classic" | "slim" @@ -301,8 +301,19 @@ export type VersionType = */ "old_beta" -const ARGS_MAP = { 'debug':'{"getType":[],"getGitCommitHash":[],"getBuildTimestamp":[],"getPlatform":[],"getOsVersion":[],"getArch":[],"getPackageVersion":[],"openDevTools":[],"getFamily":[],"isInDev":[],"getLocale":[]}', 'oneclient':'{"getClustersGroupedByMajor":[],"getVersions":[],"getBundlesFor":["cluster_id"]}', 'events':'{"message":["event"],"process":["event"],"ingress":["event"]}', 'core':'{"getScreenshots":["id"],"getPackageBody":["provider","body"],"convertUsernameUUID":["username_uuid"],"writeSettings":["setting"],"createCluster":["options"],"createSettingsProfile":["name"],"getMultiplePackages":["provider","slugs"],"updateClusterProfile":["name","profile"],"downloadExternalPackage":["package","cluster_id","force","skip_compatibility"],"openMsaLogin":[],"getClusters":[],"installModpack":["modpack","cluster_id"],"fetchMinecraftProfile":["uuid"],"killProcess":["pid"],"getUsers":[],"searchPackages":["provider","query"],"readSettings":[],"getLoadersForVersion":["mc_version"],"getRunningProcessesByClusterId":["cluster_id"],"getProfileOrDefault":["name"],"uploadSkinBytes":["access_token","skin_data","image_format","skin_variant"],"getUser":["uuid"],"getRunningProcesses":[],"fetchLoggedInProfile":["access_token"],"getLogs":["id"],"isClusterRunning":["cluster_id"],"getWorlds":["id"],"removeCape":["access_token"],"updateClusterById":["id","request"],"getGameVersions":[],"launchCluster":["id","uuid"],"removeCluster":["id"],"getPackageVersions":["provider","slug","mc_version","loader","offset","limit"],"getLinkedPackages":["cluster_id"],"getClusterById":["id"],"downloadPackage":["provider","package_id","version_id","cluster_id","skip_compatibility"],"removeUser":["uuid"],"changeSkin":["access_token","skin_url","skin_variant"],"getUsersFromAuthor":["provider","author"],"getGlobalProfile":[],"changeCape":["access_token","cape_uuid"],"getLogByName":["id","name"],"getDefaultUser":["fallback"],"setDiscordRPCMessage":["message"],"getPackage":["provider","slug"],"open":["input"],"setDefaultUser":["uuid"]}', 'folders':'{"openCluster":["folder_name"],"fromCluster":["folder_name"]}' } -export type Router = { 'events': { ingress: (event: IngressPayload) => Promise, +const ARGS_MAP = { 'core':'{"changeCape":["access_token","cape_uuid"],"searchPackages":["provider","query"],"getWorlds":["id"],"getUsersFromAuthor":["provider","author"],"getRunningProcesses":[],"getDefaultUser":["fallback"],"writeSettings":["setting"],"getMultiplePackages":["provider","slugs"],"getUser":["uuid"],"downloadPackage":["provider","package_id","version_id","cluster_id","skip_compatibility"],"updateClusterProfile":["name","profile"],"setDefaultUser":["uuid"],"isClusterRunning":["cluster_id"],"changeSkin":["access_token","skin_url","skin_variant"],"getLoadersForVersion":["mc_version"],"removeCape":["access_token"],"open":["input"],"removeUser":["uuid"],"getUsers":[],"getGlobalProfile":[],"setDiscordRPCMessage":["message"],"getClusterById":["id"],"launchCluster":["id","uuid"],"getRunningProcessesByClusterId":["cluster_id"],"getScreenshots":["id"],"updateClusterById":["id","request"],"killProcess":["pid"],"installModpack":["modpack","cluster_id"],"openMsaLogin":[],"getProfileOrDefault":["name"],"getLinkedPackages":["cluster_id"],"getPackageBody":["provider","body"],"getPackage":["provider","slug"],"removeCluster":["id"],"readSettings":[],"getPackageVersions":["provider","slug","mc_version","loader","offset","limit"],"downloadExternalPackage":["package","cluster_id","force","skip_compatibility"],"fetchMinecraftProfile":["uuid"],"createCluster":["options"],"getLogs":["id"],"getClusters":[],"uploadSkinBytes":["access_token","skin_data","image_format","skin_variant"],"convertUsernameUUID":["username_uuid"],"getLogByName":["id","name"],"createSettingsProfile":["name"],"getGameVersions":[],"fetchLoggedInProfile":["access_token"]}', 'events':'{"message":["event"],"process":["event"],"ingress":["event"]}', 'folders':'{"fromCluster":["folder_name"],"openCluster":["folder_name"]}', 'oneclient':'{"getBundlesFor":["cluster_id"],"getVersions":[],"getClustersGroupedByMajor":[]}', 'debug':'{"openDevTools":[],"getOsVersion":[],"isInDev":[],"getGitCommitHash":[],"getBuildTimestamp":[],"getPackageVersion":[],"getLocale":[],"getType":[],"getFamily":[],"getArch":[],"getPlatform":[]}' } +export type Router = { 'debug': { openDevTools: () => Promise, +isInDev: () => Promise, +getArch: () => Promise, +getFamily: () => Promise, +getLocale: () => Promise, +getType: () => Promise, +getPlatform: () => Promise, +getOsVersion: () => Promise, +getGitCommitHash: () => Promise, +getBuildTimestamp: () => Promise, +getPackageVersion: () => Promise }, +'events': { ingress: (event: IngressPayload) => Promise, message: (event: MessagePayload) => Promise, process: (event: ProcessPayload) => Promise }, 'oneclient': { getClustersGroupedByMajor: () => Promise>, @@ -355,17 +366,6 @@ removeCape: (accessToken: string) => Promise, convertUsernameUUID: (usernameUuid: string) => Promise, setDiscordRPCMessage: (message: string) => Promise, open: (input: string) => Promise }, -'debug': { openDevTools: () => Promise, -isInDev: () => Promise, -getArch: () => Promise, -getFamily: () => Promise, -getLocale: () => Promise, -getType: () => Promise, -getPlatform: () => Promise, -getOsVersion: () => Promise, -getGitCommitHash: () => Promise, -getBuildTimestamp: () => Promise, -getPackageVersion: () => Promise }, 'folders': { fromCluster: (folderName: string) => Promise, openCluster: (folderName: string) => Promise } }; diff --git a/packages/core/src/api/proxy/impl/proxy_tauri.rs b/packages/core/src/api/proxy/impl/proxy_tauri.rs index 6f3c793e..f2b090e4 100644 --- a/packages/core/src/api/proxy/impl/proxy_tauri.rs +++ b/packages/core/src/api/proxy/impl/proxy_tauri.rs @@ -1,11 +1,11 @@ -use std::collections::{HashMap, VecDeque}; +use std::collections::HashMap; use std::fmt::Debug; use std::sync::Arc; use std::time::Duration; use tauri::{AppHandle, Manager}; -use tokio::sync::{Mutex, Notify}; -use tokio::time::Instant; +use tokio::sync::mpsc; +use tokio::time::MissedTickBehavior; use tracing::{error, warn}; use uuid::Uuid; @@ -33,28 +33,13 @@ impl ProxyTauri { tauri::webview_version().unwrap_or_else(|_| "unknown".into()) ); + let emitter = Arc::new(LauncherEventEmitter::new(handle.clone())); + Self { - emitter: Arc::new(LauncherEventEmitter::new(handle.clone())), + emitter: Arc::clone(&emitter), handle, - ingress_queue: IngressQueue::new(), + ingress_queue: IngressQueue::new(Arc::clone(&emitter), Duration::from_millis(100)), } - .init() - } - - fn init(self) -> Self { - let ingress_queue = Arc::clone(&self.ingress_queue); - let emitter = Arc::clone(&self.emitter); - - // spawn the processor on another thread - tokio::spawn(async move { - ingress_queue - .process_queue(move |payload| { - let _ = emitter.ingress(payload); - }) - .await; - }); - - self } } @@ -71,7 +56,7 @@ impl LauncherProxy for ProxyTauri { Ok(match event { LauncherEvent::Ingress(ingress) => { - self.ingress_queue.push(ingress).await; + self.ingress_queue.push(ingress); () } @@ -101,71 +86,52 @@ impl LauncherProxy for ProxyTauri { } } +#[derive(Debug)] pub struct IngressQueue { - queue: Mutex>, - last_sent: Mutex>, - notify: Notify, - debounce: Duration, -} - -impl Debug for IngressQueue { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("IngressQueue") - .field("debounce", &self.debounce) - .finish() - } + tx: mpsc::UnboundedSender, } impl IngressQueue { - pub fn new() -> Arc { - Arc::new(Self { - queue: Mutex::new(VecDeque::new()), - last_sent: Mutex::new(HashMap::new()), - notify: Notify::new(), - debounce: Duration::from_millis(100), - }) - } + pub fn new( + emitter: Arc>, + debounce_duration: Duration, + ) -> Arc { + let (tx, mut rx) = mpsc::unbounded_channel::(); - pub async fn push(&self, payload: IngressPayload) { - let now = Instant::now(); + tokio::spawn(async move { + let mut pending: HashMap = HashMap::new(); - // check last sent time for debouncing - let mut last_sent = self.last_sent.lock().await; - if let Some(&instant) = last_sent.get(&payload.id) { - if now.duration_since(instant) < self.debounce { - // skip update or merge with existing queued message - let mut queue = self.queue.lock().await; - if let Some(existing) = queue.iter_mut().find(|q| q.id == payload.id) { - existing.percent = payload.percent.or(existing.percent); - existing.message = payload.message; - return; + let mut tick = tokio::time::interval(debounce_duration); + tick.set_missed_tick_behavior(MissedTickBehavior::Skip); + + loop { + tokio::select! { + Some(payload) = rx.recv() => { + // last write wins + pending.insert(payload.id, payload); + } + + _ = tick.tick() => { + if pending.is_empty() { + continue; + } + + for (_, payload) in pending.drain() { + let emitter = Arc::clone(&emitter); + tokio::spawn(async move { + let _ = emitter.ingress(payload); + }); + } + } } } - } + }); - last_sent.insert(payload.id, now); - - let mut queue = self.queue.lock().await; - queue.push_back(payload); - self.notify.notify_one(); // wake processor + Arc::new(Self { tx }) } - pub async fn process_queue(self: Arc, mut sender: F) - where - F: FnMut(IngressPayload) + Send + 'static, - { - loop { - let queued = { - let mut queue = self.queue.lock().await; - queue.pop_front() - }; - - if let Some(payload) = queued { - sender(payload); - } else { - // wait until next push, no CPU spin - self.notify.notified().await; - } - } + #[inline] + pub fn push(&self, payload: IngressPayload) { + let _ = self.tx.send(payload); } }