mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-01-29 08:45:41 +08:00
perf: optimize layout initialization and enhance backend notification mechanism
This commit is contained in:
@@ -188,78 +188,30 @@ async fn clean_async() -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn clean() -> bool {
|
pub fn clean() -> bool {
|
||||||
use tokio::time::{timeout, Duration};
|
use crate::process::AsyncHandler;
|
||||||
|
|
||||||
// 使用异步处理
|
let (tx, rx) = std::sync::mpsc::channel();
|
||||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
|
||||||
let cleanup_result = rt.block_on(async {
|
AsyncHandler::spawn(move || async move {
|
||||||
log::info!(target: "app", "开始执行清理操作...");
|
log::info!(target: "app", "开始执行清理操作...");
|
||||||
|
|
||||||
// 1. 处理TUN模式 - 并行执行,减少等待时间
|
// 使用已有的异步清理函数
|
||||||
let tun_task = async {
|
let cleanup_result = clean_async().await;
|
||||||
if Config::verge().data().enable_tun_mode.unwrap_or(false) {
|
|
||||||
let disable_tun = serde_json::json!({
|
// 发送结果
|
||||||
"tun": {
|
let _ = tx.send(cleanup_result);
|
||||||
"enable": false
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
timeout(
|
|
||||||
Duration::from_secs(2),
|
match rx.recv_timeout(std::time::Duration::from_secs(8)) {
|
||||||
MihomoManager::global().patch_configs(disable_tun),
|
Ok(result) => {
|
||||||
)
|
log::info!(target: "app", "清理操作完成,结果: {}", result);
|
||||||
.await
|
result
|
||||||
.is_ok()
|
}
|
||||||
} else {
|
Err(_) => {
|
||||||
|
log::warn!(target: "app", "清理操作超时,返回成功状态避免阻塞");
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
// 2. 系统代理重置
|
|
||||||
let proxy_task = async {
|
|
||||||
timeout(
|
|
||||||
Duration::from_secs(2),
|
|
||||||
sysopt::Sysopt::global().reset_sysproxy(),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.is_ok()
|
|
||||||
};
|
|
||||||
|
|
||||||
// 3. 核心服务停止
|
|
||||||
let core_task = async {
|
|
||||||
timeout(Duration::from_secs(2), CoreManager::global().stop_core())
|
|
||||||
.await
|
|
||||||
.is_ok()
|
|
||||||
};
|
|
||||||
|
|
||||||
// 4. DNS恢复(仅macOS)
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
let dns_task = async {
|
|
||||||
timeout(Duration::from_millis(800), resolve::restore_public_dns())
|
|
||||||
.await
|
|
||||||
.is_ok()
|
|
||||||
};
|
|
||||||
|
|
||||||
// 并行执行所有清理任务,提高效率
|
|
||||||
let (tun_success, proxy_success, core_success) =
|
|
||||||
tokio::join!(tun_task, proxy_task, core_task);
|
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
let dns_success = dns_task.await;
|
|
||||||
#[cfg(not(target_os = "macos"))]
|
|
||||||
let dns_success = true;
|
|
||||||
|
|
||||||
let all_success = tun_success && proxy_success && core_success && dns_success;
|
|
||||||
|
|
||||||
log::info!(
|
|
||||||
target: "app",
|
|
||||||
"清理操作完成 - TUN: {}, 代理: {}, 核心: {}, DNS: {}, 总体: {}",
|
|
||||||
tun_success, proxy_success, core_success, dns_success, all_success
|
|
||||||
);
|
|
||||||
|
|
||||||
all_success
|
|
||||||
});
|
|
||||||
|
|
||||||
cleanup_result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
|
|||||||
@@ -172,19 +172,20 @@ fn cancel_window_close_listener() {
|
|||||||
|
|
||||||
fn setup_light_weight_timer() -> Result<()> {
|
fn setup_light_weight_timer() -> Result<()> {
|
||||||
Timer::global().init()?;
|
Timer::global().init()?;
|
||||||
|
|
||||||
let mut timer_map = Timer::global().timer_map.write();
|
|
||||||
let delay_timer = Timer::global().delay_timer.write();
|
|
||||||
let mut timer_count = Timer::global().timer_count.lock();
|
|
||||||
|
|
||||||
let task_id = *timer_count;
|
|
||||||
*timer_count += 1;
|
|
||||||
|
|
||||||
let once_by_minutes = Config::verge()
|
let once_by_minutes = Config::verge()
|
||||||
.latest()
|
.latest()
|
||||||
.auto_light_weight_minutes
|
.auto_light_weight_minutes
|
||||||
.unwrap_or(10);
|
.unwrap_or(10);
|
||||||
|
|
||||||
|
// 获取task_id
|
||||||
|
let task_id = {
|
||||||
|
let mut timer_count = Timer::global().timer_count.lock();
|
||||||
|
let id = *timer_count;
|
||||||
|
*timer_count += 1;
|
||||||
|
id
|
||||||
|
};
|
||||||
|
|
||||||
|
// 创建任务
|
||||||
let task = TaskBuilder::default()
|
let task = TaskBuilder::default()
|
||||||
.set_task_id(task_id)
|
.set_task_id(task_id)
|
||||||
.set_maximum_parallel_runnable_num(1)
|
.set_maximum_parallel_runnable_num(1)
|
||||||
@@ -195,17 +196,24 @@ fn setup_light_weight_timer() -> Result<()> {
|
|||||||
})
|
})
|
||||||
.context("failed to create timer task")?;
|
.context("failed to create timer task")?;
|
||||||
|
|
||||||
|
// 添加任务到定时器
|
||||||
|
{
|
||||||
|
let delay_timer = Timer::global().delay_timer.write();
|
||||||
delay_timer
|
delay_timer
|
||||||
.add_task(task)
|
.add_task(task)
|
||||||
.context("failed to add timer task")?;
|
.context("failed to add timer task")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新任务映射
|
||||||
|
{
|
||||||
|
let mut timer_map = Timer::global().timer_map.write();
|
||||||
let timer_task = crate::core::timer::TimerTask {
|
let timer_task = crate::core::timer::TimerTask {
|
||||||
task_id,
|
task_id,
|
||||||
interval_minutes: once_by_minutes,
|
interval_minutes: once_by_minutes,
|
||||||
last_run: chrono::Local::now().timestamp(),
|
last_run: chrono::Local::now().timestamp(),
|
||||||
};
|
};
|
||||||
|
|
||||||
timer_map.insert(LIGHT_WEIGHT_TASK_UID.to_string(), timer_task);
|
timer_map.insert(LIGHT_WEIGHT_TASK_UID.to_string(), timer_task);
|
||||||
|
}
|
||||||
|
|
||||||
logging!(
|
logging!(
|
||||||
info,
|
info,
|
||||||
|
|||||||
@@ -345,12 +345,18 @@ pub fn create_window(is_show: bool) -> bool {
|
|||||||
.inner_size(DEFAULT_WIDTH as f64, DEFAULT_HEIGHT as f64)
|
.inner_size(DEFAULT_WIDTH as f64, DEFAULT_HEIGHT as f64)
|
||||||
.min_inner_size(520.0, 520.0)
|
.min_inner_size(520.0, 520.0)
|
||||||
.visible(true) // 立即显示窗口,避免用户等待
|
.visible(true) // 立即显示窗口,避免用户等待
|
||||||
.initialization_script(r#"
|
.initialization_script(
|
||||||
// 添加非侵入式的加载指示器
|
r#"
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
console.log('[Tauri] 窗口初始化脚本开始执行');
|
||||||
// 只有在React应用还没有挂载时才显示加载指示器
|
|
||||||
if (!document.getElementById('root') || !document.getElementById('root').hasChildNodes()) {
|
function createLoadingOverlay() {
|
||||||
// 创建加载指示器,但不覆盖整个body
|
|
||||||
|
if (document.getElementById('initial-loading-overlay')) {
|
||||||
|
console.log('[Tauri] 加载指示器已存在');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('[Tauri] 创建加载指示器');
|
||||||
const loadingDiv = document.createElement('div');
|
const loadingDiv = document.createElement('div');
|
||||||
loadingDiv.id = 'initial-loading-overlay';
|
loadingDiv.id = 'initial-loading-overlay';
|
||||||
loadingDiv.innerHTML = `
|
loadingDiv.innerHTML = `
|
||||||
@@ -358,12 +364,18 @@ pub fn create_window(is_show: bool) -> bool {
|
|||||||
position: fixed; top: 0; left: 0; right: 0; bottom: 0;
|
position: fixed; top: 0; left: 0; right: 0; bottom: 0;
|
||||||
background: var(--bg-color, #f5f5f5); color: var(--text-color, #333);
|
background: var(--bg-color, #f5f5f5); color: var(--text-color, #333);
|
||||||
display: flex; flex-direction: column; align-items: center;
|
display: flex; flex-direction: column; align-items: center;
|
||||||
justify-content: center; z-index: 9998; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
justify-content: center; z-index: 9999;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
">
|
">
|
||||||
<div style="margin-bottom: 20px;">
|
<div style="margin-bottom: 20px;">
|
||||||
<div style="width: 40px; height: 40px; border: 3px solid #e3e3e3; border-top: 3px solid #3498db; border-radius: 50%; animation: spin 1s linear infinite;"></div>
|
<div style="
|
||||||
|
width: 40px; height: 40px; border: 3px solid #e3e3e3;
|
||||||
|
border-top: 3px solid #3498db; border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
"></div>
|
||||||
</div>
|
</div>
|
||||||
<div style="font-size: 14px; opacity: 0.7;">正在加载 Clash Verge...</div>
|
<div style="font-size: 14px; opacity: 0.7;">Loading Clash Verge...</div>
|
||||||
</div>
|
</div>
|
||||||
<style>
|
<style>
|
||||||
@keyframes spin {
|
@keyframes spin {
|
||||||
@@ -375,18 +387,29 @@ pub fn create_window(is_show: bool) -> bool {
|
|||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
`;
|
`;
|
||||||
document.body.appendChild(loadingDiv);
|
|
||||||
|
|
||||||
// 设置定时器,如果React应用在5秒内没有挂载,移除加载指示器
|
if (document.body) {
|
||||||
setTimeout(() => {
|
document.body.appendChild(loadingDiv);
|
||||||
const overlay = document.getElementById('initial-loading-overlay');
|
} else {
|
||||||
if (overlay) {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
overlay.remove();
|
if (document.body && !document.getElementById('initial-loading-overlay')) {
|
||||||
}
|
document.body.appendChild(loadingDiv);
|
||||||
}, 5000);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
"#)
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
createLoadingOverlay();
|
||||||
|
|
||||||
|
if (document.readyState === 'loading') {
|
||||||
|
document.addEventListener('DOMContentLoaded', createLoadingOverlay);
|
||||||
|
} else {
|
||||||
|
createLoadingOverlay();
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('[Tauri] 窗口初始化脚本执行完成');
|
||||||
|
"#,
|
||||||
|
)
|
||||||
.build()
|
.build()
|
||||||
{
|
{
|
||||||
Ok(newly_created_window) => {
|
Ok(newly_created_window) => {
|
||||||
|
|||||||
@@ -237,49 +237,120 @@ const Layout = () => {
|
|||||||
console.log("[Layout] 开始执行初始化代码");
|
console.log("[Layout] 开始执行初始化代码");
|
||||||
initRef.current = true;
|
initRef.current = true;
|
||||||
|
|
||||||
const notifyUiStage = async (stage: string) => {
|
let isInitialized = false;
|
||||||
|
let initializationAttempts = 0;
|
||||||
|
const maxAttempts = 3;
|
||||||
|
|
||||||
|
const notifyBackend = async (action: string, stage?: string) => {
|
||||||
try {
|
try {
|
||||||
console.log(`[Layout] UI加载阶段: ${stage}`);
|
if (stage) {
|
||||||
|
console.log(`[Layout] 通知后端 ${action}: ${stage}`);
|
||||||
await invoke("update_ui_stage", { stage });
|
await invoke("update_ui_stage", { stage });
|
||||||
} catch (err) {
|
} else {
|
||||||
console.error(`[Layout] 通知UI加载阶段(${stage})失败:`, err);
|
console.log(`[Layout] 通知后端 ${action}`);
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const notifyUiCoreReady = async () => {
|
|
||||||
try {
|
|
||||||
console.log("[Layout] 核心组件已加载,通知后端");
|
|
||||||
await invoke("update_ui_stage", { stage: "DomReady" });
|
|
||||||
} catch (err) {
|
|
||||||
console.error("[Layout] 通知核心组件加载完成失败:", err);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const notifyUiResourcesLoaded = async () => {
|
|
||||||
try {
|
|
||||||
console.log("[Layout] 所有资源已加载,通知后端");
|
|
||||||
await invoke("update_ui_stage", { stage: "ResourcesLoaded" });
|
|
||||||
} catch (err) {
|
|
||||||
console.error("[Layout] 通知资源加载完成失败:", err);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const notifyUiReady = async () => {
|
|
||||||
try {
|
|
||||||
console.log("[Layout] UI完全准备就绪,通知后端");
|
|
||||||
await invoke("notify_ui_ready");
|
await invoke("notify_ui_ready");
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("[Layout] 通知UI准备就绪失败:", err);
|
console.error(`[Layout] 通知失败 ${action}:`, err);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 监听后端发送的启动完成事件
|
const removeLoadingOverlay = () => {
|
||||||
const listenStartupCompleted = async () => {
|
const initialOverlay = document.getElementById("initial-loading-overlay");
|
||||||
|
if (initialOverlay) {
|
||||||
|
console.log("[Layout] 移除加载指示器");
|
||||||
|
initialOverlay.style.opacity = "0";
|
||||||
|
setTimeout(() => {
|
||||||
|
try {
|
||||||
|
initialOverlay.remove();
|
||||||
|
} catch (e) {
|
||||||
|
console.log("[Layout] 加载指示器已被移除");
|
||||||
|
}
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const performInitialization = async () => {
|
||||||
|
if (isInitialized) {
|
||||||
|
console.log("[Layout] 已经初始化过,跳过");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
initializationAttempts++;
|
||||||
|
console.log(`[Layout] 开始第 ${initializationAttempts} 次初始化尝试`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
removeLoadingOverlay();
|
||||||
|
|
||||||
|
await notifyBackend("加载阶段", "Loading");
|
||||||
|
|
||||||
|
await new Promise<void>((resolve) => {
|
||||||
|
const checkReactMount = () => {
|
||||||
|
const rootElement = document.getElementById("root");
|
||||||
|
if (rootElement && rootElement.children.length > 0) {
|
||||||
|
console.log("[Layout] React组件已挂载");
|
||||||
|
resolve();
|
||||||
|
} else {
|
||||||
|
setTimeout(checkReactMount, 50);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
checkReactMount();
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
console.log("[Layout] React组件挂载检查超时,继续执行");
|
||||||
|
resolve();
|
||||||
|
}, 2000);
|
||||||
|
});
|
||||||
|
|
||||||
|
await notifyBackend("DOM就绪", "DomReady");
|
||||||
|
|
||||||
|
await new Promise<void>((resolve) => {
|
||||||
|
requestAnimationFrame(() => resolve());
|
||||||
|
});
|
||||||
|
|
||||||
|
await notifyBackend("资源加载完成", "ResourcesLoaded");
|
||||||
|
|
||||||
|
await notifyBackend("UI就绪");
|
||||||
|
|
||||||
|
isInitialized = true;
|
||||||
|
console.log(`[Layout] 第 ${initializationAttempts} 次初始化完成`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(
|
||||||
|
`[Layout] 第 ${initializationAttempts} 次初始化失败:`,
|
||||||
|
error,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (initializationAttempts < maxAttempts) {
|
||||||
|
console.log(
|
||||||
|
`[Layout] 将在500ms后进行第 ${initializationAttempts + 1} 次重试`,
|
||||||
|
);
|
||||||
|
setTimeout(performInitialization, 500);
|
||||||
|
} else {
|
||||||
|
console.error("[Layout] 所有初始化尝试都失败,执行紧急初始化");
|
||||||
|
|
||||||
|
removeLoadingOverlay();
|
||||||
|
try {
|
||||||
|
await notifyBackend("UI就绪");
|
||||||
|
isInitialized = true;
|
||||||
|
} catch (e) {
|
||||||
|
console.error("[Layout] 紧急初始化也失败:", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let hasEventTriggered = false;
|
||||||
|
|
||||||
|
const setupEventListener = async () => {
|
||||||
try {
|
try {
|
||||||
console.log("[Layout] 开始监听启动完成事件");
|
console.log("[Layout] 开始监听启动完成事件");
|
||||||
const unlisten = await listen("verge://startup-completed", () => {
|
const unlisten = await listen("verge://startup-completed", () => {
|
||||||
console.log("[Layout] 收到启动完成事件,开始通知UI就绪");
|
if (!hasEventTriggered) {
|
||||||
notifyUiReady();
|
console.log("[Layout] 收到启动完成事件,开始初始化");
|
||||||
|
hasEventTriggered = true;
|
||||||
|
performInitialization();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return unlisten;
|
return unlisten;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -288,61 +359,45 @@ const Layout = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 简化的UI初始化流程
|
const checkImmediateInitialization = async () => {
|
||||||
const initializeUI = async () => {
|
|
||||||
try {
|
try {
|
||||||
const initialOverlay = document.getElementById(
|
console.log("[Layout] 检查后端是否已就绪");
|
||||||
"initial-loading-overlay",
|
await invoke("update_ui_stage", { stage: "Loading" });
|
||||||
);
|
|
||||||
if (initialOverlay) {
|
if (!hasEventTriggered && !isInitialized) {
|
||||||
initialOverlay.style.opacity = "0";
|
console.log("[Layout] 后端已就绪,立即开始初始化");
|
||||||
setTimeout(() => initialOverlay.remove(), 200);
|
hasEventTriggered = true;
|
||||||
}
|
performInitialization();
|
||||||
|
|
||||||
await notifyUiStage("Loading");
|
|
||||||
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
||||||
|
|
||||||
console.log("[Layout] 通知后端:DomReady");
|
|
||||||
await notifyUiCoreReady();
|
|
||||||
|
|
||||||
await new Promise((resolve) => {
|
|
||||||
requestAnimationFrame(() => {
|
|
||||||
requestAnimationFrame(resolve);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log("[Layout] 通知后端:ResourcesLoaded");
|
|
||||||
await notifyUiResourcesLoaded();
|
|
||||||
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
||||||
|
|
||||||
await notifyUiReady();
|
|
||||||
} catch (error) {
|
|
||||||
try {
|
|
||||||
await notifyUiReady();
|
|
||||||
} catch (e) {
|
|
||||||
console.error("[Layout] 通知UI就绪失败:", e);
|
|
||||||
}
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.log("[Layout] 后端尚未就绪,等待启动完成事件");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
setTimeout(initializeUI, 50);
|
const backupInitialization = setTimeout(() => {
|
||||||
|
if (!hasEventTriggered && !isInitialized) {
|
||||||
|
console.warn("[Layout] 备用初始化触发:1.5秒内未开始初始化");
|
||||||
|
hasEventTriggered = true;
|
||||||
|
performInitialization();
|
||||||
|
}
|
||||||
|
}, 1500);
|
||||||
|
|
||||||
const emergencyTimeout = setTimeout(() => {
|
const emergencyInitialization = setTimeout(() => {
|
||||||
const emergencyNotify = async () => {
|
if (!isInitialized) {
|
||||||
try {
|
console.error("[Layout] 紧急初始化触发:5秒内未完成初始化");
|
||||||
await invoke("notify_ui_ready");
|
removeLoadingOverlay();
|
||||||
} catch (error) {}
|
notifyBackend("UI就绪").catch(() => {});
|
||||||
};
|
isInitialized = true;
|
||||||
emergencyNotify();
|
}
|
||||||
}, 5000);
|
}, 5000);
|
||||||
|
|
||||||
// 启动监听器
|
const unlistenPromise = setupEventListener();
|
||||||
const unlistenPromise = listenStartupCompleted();
|
|
||||||
|
setTimeout(checkImmediateInitialization, 100);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
clearTimeout(emergencyTimeout);
|
clearTimeout(backupInitialization);
|
||||||
|
clearTimeout(emergencyInitialization);
|
||||||
unlistenPromise.then((unlisten) => unlisten());
|
unlistenPromise.then((unlisten) => unlisten());
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|||||||
Reference in New Issue
Block a user