Compare commits

...
Author SHA1 Message Date
Tunglies d1e3f09f17 perf(tray): batch menu rebuild into a single dispatch
Every menu API call runs on the main thread; invoked from a worker,
each call pays a blocking round-trip there and back (channel, event
loop hop, recv). Rebuilding the tray menu with proxy groups enabled
issued one round-trip per menu item — 200+ for a typical subscription —
and the per-call wait grows whenever the event loop is busy with
webview work.

Gather the async inputs (verge/clash/profiles snapshots, proxies and
runtime group order) on the caller's task, then build the menu and
attach it in a single run_on_main_thread closure where every item
constructs inline. Measured on a standalone tauri bench (5 groups x
40 nodes): a rebuild drops from ~6.2ms to ~1.6ms on an idle event
loop and from ~11.4ms to ~1.6ms with the loop busy (8ms of work per
16ms frame), with ~450 fewer allocations (~155KB) per rebuild.
2026-10-21 15:44:39 +08:00
+80 -72
View File
@@ -1,4 +1,4 @@
use crate::config::{IProfilePreview, IVerge}; use crate::config::{IProfilePreview, IProfiles, IVerge};
use crate::core::lightweight; use crate::core::lightweight;
use crate::core::tray::menu_def::TrayAction; use crate::core::tray::menu_def::TrayAction;
use crate::process::AsyncHandler; use crate::process::AsyncHandler;
@@ -41,7 +41,10 @@ const TRAY_CLICK_DEBOUNCE_MS: u64 = 300;
pub const TRAY_ID: &str = "clash-verge-rev-tray"; pub const TRAY_ID: &str = "clash-verge-rev-tray";
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
struct TrayMenuOptions { struct TrayMenuOptions<'a> {
mode: Option<&'a str>,
system_proxy_enabled: bool,
tun_mode_enabled: bool,
is_lightweight_mode: bool, is_lightweight_mode: bool,
include_proxy_groups: bool, include_proxy_groups: bool,
} }
@@ -201,9 +204,8 @@ impl Tray {
}; };
let verge = Config::verge().await.latest_arc(); let verge = Config::verge().await.latest_arc();
let system_proxy = verge.enable_system_proxy.as_ref().unwrap_or(&false); let system_proxy = verge.enable_system_proxy.unwrap_or(false);
let tun_mode = verge.enable_tun_mode.as_ref().unwrap_or(&false); let tun_mode = verge.enable_tun_mode.unwrap_or(false);
let tun_mode_available = crate::core::runstate::RUN_STATE.state().tun_capable();
let mode = { let mode = {
Config::clash() Config::clash()
.await .await
@@ -214,29 +216,73 @@ impl Tray {
.unwrap_or("rule") .unwrap_or("rule")
.to_owned() .to_owned()
}; };
let profiles_config = Config::profiles().await;
let profiles_arc = profiles_config.latest_arc();
let profiles_preview = profiles_arc.profiles_preview().unwrap_or_default();
let is_lightweight_mode = is_in_lightweight_mode(); let is_lightweight_mode = is_in_lightweight_mode();
logging_error!( let fetch_proxy_groups =
Type::Tray, include_proxy_groups && verge.tray_proxy_groups_display_mode.as_deref() != Some("disable");
tray.set_menu(Some(
create_tray_menu( // TODO: should update tray menu again when it was timeout error
app_handle, let (proxy_nodes_data, runtime_proxy_groups_order) = if fetch_proxy_groups {
Some(mode.as_str()), let proxy_nodes_data =
*system_proxy, tokio::time::timeout(Duration::from_millis(1000), handle::Handle::mihomo().get_proxies())
*tun_mode, .await
tun_mode_available, .map_or(None, |res| res.ok());
profiles_preview,
let runtime = Config::runtime().await.latest_arc();
let runtime_proxy_groups_order = runtime.config.as_ref().map(|config| {
config
.get("proxy-groups")
.and_then(|groups| groups.as_sequence())
.map(|groups| {
groups
.iter()
.filter_map(|group| group.get("name"))
.filter_map(|name| name.as_str())
.enumerate()
.map(|(index, name)| (name.into(), index))
.collect::<HashMap<String, usize>>()
})
.unwrap_or_default()
});
(proxy_nodes_data, runtime_proxy_groups_order)
} else {
(None, None)
};
let verge = if fetch_proxy_groups {
Config::verge().await.latest_arc()
} else {
verge
};
let profiles = Config::profiles().await.latest_arc();
let (tx, rx) = tokio::sync::oneshot::channel();
let dispatch_handle = app_handle.clone();
app_handle
.run_on_main_thread(move || {
let _ = tx.send((|| -> Result<()> {
let menu = build_tray_menu(
&dispatch_handle,
TrayMenuOptions { TrayMenuOptions {
mode: Some(mode.as_str()),
system_proxy_enabled: system_proxy,
tun_mode_enabled: tun_mode,
is_lightweight_mode, is_lightweight_mode,
include_proxy_groups, include_proxy_groups,
}, },
) &verge,
.await?, &profiles,
)) runtime_proxy_groups_order,
); proxy_nodes_data,
)?;
logging_error!(Type::Tray, tray.set_menu(Some(menu)));
Ok(())
})());
})
.map_err(|e| anyhow::anyhow!("Failed to dispatch tray menu update: {e}"))?;
rx.await
.map_err(|_| anyhow::anyhow!("Tray menu update returned no result"))??;
logging!(debug, Type::Tray, "托盘菜单更新成功"); logging!(debug, Type::Tray, "托盘菜单更新成功");
Ok(()) Ok(())
@@ -585,53 +631,16 @@ fn create_proxy_menu_item(
Ok((proxies_submenu, inline_proxy_items)) Ok((proxies_submenu, inline_proxy_items))
} }
async fn create_tray_menu( fn build_tray_menu(
app_handle: &AppHandle, app_handle: &AppHandle,
mode: Option<&str>, options: TrayMenuOptions<'_>,
system_proxy_enabled: bool, verge_settings: &IVerge,
tun_mode_enabled: bool, profiles_config: &IProfiles,
tun_mode_available: bool, proxy_group_order_map: Option<HashMap<String, usize>>,
profiles_preview: Vec<IProfilePreview<'_>>, proxy_nodes_data: Option<Proxies>,
options: TrayMenuOptions,
) -> Result<tauri::menu::Menu<Wry>> { ) -> Result<tauri::menu::Menu<Wry>> {
let current_proxy_mode = mode.unwrap_or(""); let current_proxy_mode = options.mode.unwrap_or("");
let tun_mode_available = crate::core::runstate::RUN_STATE.state().tun_capable();
let mut verge_settings = Config::verge().await.latest_arc();
let fetch_proxy_groups =
options.include_proxy_groups && verge_settings.tray_proxy_groups_display_mode.as_deref() != Some("disable");
// TODO: should update tray menu again when it was timeout error
let (proxy_nodes_data, runtime_proxy_groups_order) = if fetch_proxy_groups {
let proxy_nodes_data =
tokio::time::timeout(Duration::from_millis(1000), handle::Handle::mihomo().get_proxies())
.await
.map_or(None, |res| res.ok());
let runtime = Config::runtime().await.latest_arc();
let runtime_proxy_groups_order = runtime.config.as_ref().map(|config| {
config
.get("proxy-groups")
.and_then(|groups| groups.as_sequence())
.map(|groups| {
groups
.iter()
.filter_map(|group| group.get("name"))
.filter_map(|name| name.as_str())
.enumerate()
.map(|(index, name)| (name.into(), index))
.collect::<HashMap<String, usize>>()
})
.unwrap_or_default()
});
(proxy_nodes_data, runtime_proxy_groups_order)
} else {
(None, None)
};
if fetch_proxy_groups {
verge_settings = Config::verge().await.latest_arc();
}
let tray_proxy_groups_display_mode = verge_settings let tray_proxy_groups_display_mode = verge_settings
.tray_proxy_groups_display_mode .tray_proxy_groups_display_mode
@@ -639,15 +648,14 @@ async fn create_tray_menu(
.unwrap_or("default"); .unwrap_or("default");
let include_proxy_groups = options.include_proxy_groups && tray_proxy_groups_display_mode != "disable"; let include_proxy_groups = options.include_proxy_groups && tray_proxy_groups_display_mode != "disable";
let proxy_group_order_map = runtime_proxy_groups_order;
let show_outbound_modes_inline = verge_settings.tray_inline_outbound_modes.unwrap_or(false); let show_outbound_modes_inline = verge_settings.tray_inline_outbound_modes.unwrap_or(false);
let version = env!("CARGO_PKG_VERSION"); let version = env!("CARGO_PKG_VERSION");
let hotkeys = create_hotkeys(&verge_settings.hotkeys); let hotkeys = create_hotkeys(&verge_settings.hotkeys);
let profile_menu_items: Vec<CheckMenuItem<Wry>> = create_profile_menu_item(app_handle, profiles_preview)?; let profile_menu_items: Vec<CheckMenuItem<Wry>> =
create_profile_menu_item(app_handle, profiles_config.profiles_preview().unwrap_or_default())?;
let texts = MenuTexts::new(); let texts = MenuTexts::new();
let profile_menu_items_refs: Vec<&dyn IsMenuItem<Wry>> = profile_menu_items let profile_menu_items_refs: Vec<&dyn IsMenuItem<Wry>> = profile_menu_items
@@ -738,7 +746,7 @@ async fn create_tray_menu(
MenuIds::SYSTEM_PROXY, MenuIds::SYSTEM_PROXY,
&texts.system_proxy, &texts.system_proxy,
true, true,
system_proxy_enabled, options.system_proxy_enabled,
hotkeys.get("toggle_system_proxy").copied(), hotkeys.get("toggle_system_proxy").copied(),
)?; )?;
@@ -747,7 +755,7 @@ async fn create_tray_menu(
MenuIds::TUN_MODE, MenuIds::TUN_MODE,
&texts.tun_mode, &texts.tun_mode,
tun_mode_available, tun_mode_available,
tun_mode_enabled, options.tun_mode_enabled,
hotkeys.get("toggle_tun_mode").copied(), hotkeys.get("toggle_tun_mode").copied(),
)?; )?;