From 0f938f6f3e5dbc5d60354a1ba5ea9257c9322435 Mon Sep 17 00:00:00 2001 From: purp1e Date: Sun, 9 Nov 2025 00:55:19 +0800 Subject: [PATCH] [fix] update data lost between page and stutter caused by periodly checking game stats in sync mode --- next-env.d.ts | 2 +- src-tauri/src/cmds.rs | 4 +- src-tauri/src/tool/common.rs | 35 ++++++++++ src-tauri/tauri.conf.json | 2 +- .../cstb/FpsTest/hooks/useGameMonitor.ts | 33 +--------- src/components/cstb/UpdateChecker.tsx | 60 ++++++++--------- src/components/cstb/VideoSetting.tsx | 30 +-------- src/hooks/useGlobalGameMonitor.ts | 65 +++++++++++++++++++ src/store/app.ts | 32 +++++++++ src/store/tool.ts | 21 ++++++ 10 files changed, 190 insertions(+), 94 deletions(-) create mode 100644 src/hooks/useGlobalGameMonitor.ts diff --git a/next-env.d.ts b/next-env.d.ts index 9edff1c..c4b7818 100644 --- a/next-env.d.ts +++ b/next-env.d.ts @@ -1,6 +1,6 @@ /// /// -import "./.next/types/routes.d.ts"; +import "./.next/dev/types/routes.d.ts"; // NOTE: This file should not be edited // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/src-tauri/src/cmds.rs b/src-tauri/src/cmds.rs index 75d60ce..0c4e8a8 100644 --- a/src-tauri/src/cmds.rs +++ b/src-tauri/src/cmds.rs @@ -64,8 +64,8 @@ pub fn kill_game() -> Result { } #[tauri::command] -pub fn check_process_running(process_name: &str) -> Result { - Ok(common::check_process_running(process_name)) +pub async fn check_process_running(process_name: &str) -> Result { + Ok(common::check_process_running_async(process_name).await) } #[tauri::command] diff --git a/src-tauri/src/tool/common.rs b/src-tauri/src/tool/common.rs index 84ddc96..f5af0ee 100644 --- a/src-tauri/src/tool/common.rs +++ b/src-tauri/src/tool/common.rs @@ -120,6 +120,41 @@ pub fn check_process_running(name: &str) -> bool { } } +// 异步版本的进程检测函数 +pub async fn check_process_running_async(name: &str) -> bool { + use tokio::process::Command; + + // 使用tasklist命令检查进程是否存在 + #[cfg(windows)] + { + let mut cmd = Command::new("tasklist"); + cmd.args(&["/FI", &format!("IMAGENAME eq {}", name)]); + cmd.creation_flags(CREATE_NO_WINDOW); + + match cmd.output().await { + Ok(output) => { + let stdout = String::from_utf8_lossy(&output.stdout); + // 检查输出中是否包含进程名(排除表头) + stdout.contains(name) && stdout.contains("exe") + } + Err(_) => false, + } + } + + #[cfg(not(windows))] + { + // 对于非Windows系统,可以使用pgrep命令 + let mut cmd = Command::new("pgrep"); + cmd.arg("-f"); + cmd.arg(name); + + match cmd.output().await { + Ok(output) => !output.stdout.is_empty(), + Err(_) => false, + } + } +} + mod tests { #[test] fn test_open_path() { diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 7543a60..13496da 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -52,7 +52,7 @@ }, "productName": "CS工具箱", "mainBinaryName": "cstb", - "version": "0.0.6-beta.9", + "version": "0.0.6-beta.8", "identifier": "upup.cool", "plugins": { "deep-link": { diff --git a/src/components/cstb/FpsTest/hooks/useGameMonitor.ts b/src/components/cstb/FpsTest/hooks/useGameMonitor.ts index 8ee5b5a..3d6fe10 100644 --- a/src/components/cstb/FpsTest/hooks/useGameMonitor.ts +++ b/src/components/cstb/FpsTest/hooks/useGameMonitor.ts @@ -1,32 +1,3 @@ -import { useState, useEffect, useCallback } from "react" -import { invoke } from "@tauri-apps/api/core" - -export function useGameMonitor() { - const [isGameRunning, setIsGameRunning] = useState(false) - - // 检测游戏是否运行 - const checkGameRunning = useCallback(async () => { - try { - const result = await invoke("check_process_running", { - processName: "cs2.exe", - }).catch(() => false) - setIsGameRunning(result) - return result - } catch { - setIsGameRunning(false) - return false - } - }, []) - - // 定期检测游戏运行状态 - useEffect(() => { - void checkGameRunning() - const interval = setInterval(() => { - void checkGameRunning() - }, 3000) - return () => clearInterval(interval) - }, [checkGameRunning]) - - return { isGameRunning, checkGameRunning } -} +// 使用全局游戏状态监控,避免重复检测 +export { useGlobalGameMonitor as useGameMonitor } from "@/hooks/useGlobalGameMonitor" diff --git a/src/components/cstb/UpdateChecker.tsx b/src/components/cstb/UpdateChecker.tsx index ba0a07e..048c60c 100644 --- a/src/components/cstb/UpdateChecker.tsx +++ b/src/components/cstb/UpdateChecker.tsx @@ -16,15 +16,9 @@ import { invoke } from "@tauri-apps/api/core" import { listen } from "@tauri-apps/api/event" import { relaunch } from "@tauri-apps/plugin-process" import { addToast } from "@heroui/react" -import { useAppStore } from "@/store/app" +import { useAppStore, type UpdateInfo } from "@/store/app" import { MarkdownRender } from "@/components/markdown" -interface UpdateInfo { - version: string - notes?: string - download_url: string -} - interface UpdateCheckerProps { customEndpoint?: string includePrerelease?: boolean @@ -38,10 +32,11 @@ export function UpdateChecker({ }: UpdateCheckerProps) { const app = useAppStore() const [checking, setChecking] = useState(false) - const [downloading, setDownloading] = useState(false) - const [updateInfo, setUpdateInfo] = useState(null) - const [downloadProgress, setDownloadProgress] = useState(0) - const [downloadCompleted, setDownloadCompleted] = useState(false) + // 从 store 读取状态 + const updateInfo = app.state.updateInfo + const downloading = app.state.downloading + const downloadProgress = app.state.downloadProgress + const downloadCompleted = app.state.downloadCompleted const { isOpen: isChangelogOpen, onOpen: onChangelogOpen, @@ -52,27 +47,27 @@ export function UpdateChecker({ useEffect(() => { const unlisten = listen("update-download-progress", (event) => { const progress = event.payload - setDownloadProgress(progress) + app.setDownloadProgress(progress) // 如果进度达到 100%,标记下载完成 if (progress === 100) { - setDownloading(false) - setDownloadCompleted(true) + app.setDownloading(false) + app.setDownloadCompleted(true) } }) return () => { unlisten.then((fn) => fn()) } - }, []) + }, [app.setDownloadProgress, app.setDownloading, app.setDownloadCompleted]) // 检查更新 const handleCheckUpdate = async () => { setChecking(true) - setUpdateInfo(null) - setDownloadProgress(0) - setDownloading(false) - setDownloadCompleted(false) + app.setUpdateInfo(null) + app.setDownloadProgress(0) + app.setDownloading(false) + app.setDownloadCompleted(false) try { // 根据是否包含测试版来选择不同的 endpoint @@ -97,7 +92,7 @@ export function UpdateChecker({ }) if (result) { - setUpdateInfo(result) + app.setUpdateInfo(result) // 更新 store 中的更新状态和最新版本号 app.setHasUpdate(true) app.setLatestVersion(result.version) @@ -110,6 +105,7 @@ export function UpdateChecker({ // 没有更新,更新 store 状态 app.setHasUpdate(false) app.setLatestVersion("") + app.setUpdateInfo(null) addToast({ title: "已是最新版本", color: "default", @@ -132,18 +128,18 @@ export function UpdateChecker({ return } - setDownloading(true) - setDownloadProgress(0) - setDownloadCompleted(false) + app.setDownloading(true) + app.setDownloadProgress(0) + app.setDownloadCompleted(false) try { // 使用官方 updater 插件,传递 useCdn 参数 await invoke("download_app_update", { useCdn: useCdn }) // 下载完成,标记状态 - setDownloadProgress(100) - setDownloading(false) - setDownloadCompleted(true) + app.setDownloadProgress(100) + app.setDownloading(false) + app.setDownloadCompleted(true) addToast({ title: "下载完成", @@ -164,9 +160,9 @@ export function UpdateChecker({ color: "danger", }) } - setDownloadProgress(0) - setDownloading(false) - setDownloadCompleted(false) + app.setDownloadProgress(0) + app.setDownloading(false) + app.setDownloadCompleted(false) } } @@ -174,9 +170,9 @@ export function UpdateChecker({ const handleCancelDownload = async () => { try { await invoke("cancel_download_update") - setDownloading(false) - setDownloadProgress(0) - setDownloadCompleted(false) + app.setDownloading(false) + app.setDownloadProgress(0) + app.setDownloadCompleted(false) addToast({ title: "已取消下载", color: "default", diff --git a/src/components/cstb/VideoSetting.tsx b/src/components/cstb/VideoSetting.tsx index 576fd01..e25523a 100644 --- a/src/components/cstb/VideoSetting.tsx +++ b/src/components/cstb/VideoSetting.tsx @@ -9,35 +9,21 @@ import { useSteamStore } from "@/store/steam" import { useDebounce, useDebounceFn, useThrottleFn } from "ahooks" import { invoke } from "@tauri-apps/api/core" import { listen } from "@tauri-apps/api/event" +import { useGlobalGameMonitor } from "@/hooks/useGlobalGameMonitor" const VideoSetting = () => { const [hide, setHide] = useState(false) const [edit, setEdit] = useState(false) - const [isGameRunning, setIsGameRunning] = useState(false) const tool = useToolStore() const steam = useSteamStore() + // 使用全局游戏状态监控,避免重复检测 + const { isGameRunning, checkGameRunning } = useGlobalGameMonitor() // 使用 ref 存储 edit 的最新值,供 throttle 回调使用 const editRef = useRef(edit) useEffect(() => { editRef.current = edit }, [edit]) - // 检测游戏是否运行 - const checkGameRunning = useCallback(async () => { - try { - // 尝试检测cs2.exe进程 - const result = await invoke("check_process_running", { - processName: "cs2.exe", - }).catch(() => false) - setIsGameRunning(result) - return result - } catch { - // 如果命令不存在,使用简单的检测方法 - setIsGameRunning(false) - return false - } - }, []) - // 防抖的读取函数 const { run: debouncedGetVideoConfig } = useDebounceFn( async () => { @@ -286,16 +272,6 @@ const VideoSetting = () => { const [vconfig, setVconfig] = useState(tool.state.videoSetting) - // 初始化时检测游戏运行状态 - useEffect(() => { - void checkGameRunning() - // 定期检测游戏运行状态 - const interval = setInterval(() => { - void checkGameRunning() - }, 4000) - return () => clearInterval(interval) - }, [checkGameRunning]) - useEffect(() => { if (steam.state.steamDirValid && steam.currentUser()) void tool.getVideoConfig(steam.state.steamDir, steam.currentUser()?.steam_id32 || 0) diff --git a/src/hooks/useGlobalGameMonitor.ts b/src/hooks/useGlobalGameMonitor.ts new file mode 100644 index 0000000..f40298b --- /dev/null +++ b/src/hooks/useGlobalGameMonitor.ts @@ -0,0 +1,65 @@ +import { useEffect, useRef } from "react" +import { useToolStore } from "@/store/tool" + +// 全局检测间隔(毫秒)- 增加到5秒以减少性能影响 +const CHECK_INTERVAL = 5000 + +// 全局检测状态管理 +let globalInterval: NodeJS.Timeout | null = null +let subscriberCount = 0 + +/** + * 全局游戏状态监控 Hook + * 多个组件可以共享同一个检测循环,避免重复检测 + * + * @param enabled 是否启用检测(默认 true) + * @returns 游戏运行状态和手动检测函数 + */ +export function useGlobalGameMonitor(enabled: boolean = true) { + const tool = useToolStore() + const isGameRunning = tool.state.isGameRunning + const checkGameRunning = tool.checkGameRunning + const enabledRef = useRef(enabled) + + // 更新 enabled 引用 + useEffect(() => { + enabledRef.current = enabled + }, [enabled]) + + useEffect(() => { + if (!enabled) { + return + } + + // 增加订阅者计数 + subscriberCount++ + + // 如果这是第一个订阅者,启动全局检测循环 + if (subscriberCount === 1) { + // 立即检测一次 + void checkGameRunning() + + // 启动定期检测 + globalInterval = setInterval(() => { + void checkGameRunning() + }, CHECK_INTERVAL) + } + + // 清理函数:减少订阅者计数 + return () => { + subscriberCount-- + + // 如果没有订阅者了,停止检测循环 + if (subscriberCount === 0 && globalInterval) { + clearInterval(globalInterval) + globalInterval = null + } + } + }, [enabled, checkGameRunning]) + + return { + isGameRunning, + checkGameRunning, + } +} + diff --git a/src/store/app.ts b/src/store/app.ts index 2d16b18..d13ad07 100644 --- a/src/store/app.ts +++ b/src/store/app.ts @@ -4,10 +4,20 @@ import { DEFAULT_STORE_CONFIG } from "./config" import { enable, disable } from "@tauri-apps/plugin-autostart" import { LazyStore } from '@tauri-apps/plugin-store'; +interface UpdateInfo { + version: string + notes?: string + download_url: string +} + const defaultValue = { version: "0.0.1", hasUpdate: false, latestVersion: "", // 最新版本号 + updateInfo: null as UpdateInfo | null, // 更新信息 + downloading: false, // 是否正在下载 + downloadProgress: 0, // 下载进度 0-100 + downloadCompleted: false, // 下载是否完成 inited: false, notice: "", useMirror: true, // 默认使用镜像源(CDN 加速) @@ -19,6 +29,8 @@ const defaultValue = { steamUsersViewMode: "list-large" as "card" | "list" | "list-large", } +export type { UpdateInfo } + export const appStore = store("app", { ...defaultValue }, DEFAULT_STORE_CONFIG) export const useAppStore = () => { @@ -31,6 +43,10 @@ export const useAppStore = () => { setVersion, setHasUpdate, setLatestVersion, + setUpdateInfo, + setDownloading, + setDownloadProgress, + setDownloadCompleted, setInited, setNotice, setUseMirror, @@ -56,6 +72,18 @@ const setHasUpdate = (hasUpdate: boolean) => { const setLatestVersion = (latestVersion: string) => { appStore.state.latestVersion = latestVersion } +const setUpdateInfo = (updateInfo: UpdateInfo | null) => { + appStore.state.updateInfo = updateInfo +} +const setDownloading = (downloading: boolean) => { + appStore.state.downloading = downloading +} +const setDownloadProgress = (downloadProgress: number) => { + appStore.state.downloadProgress = downloadProgress +} +const setDownloadCompleted = (downloadCompleted: boolean) => { + appStore.state.downloadCompleted = downloadCompleted +} const setInited = (inited: boolean) => { appStore.state.inited = inited } @@ -100,6 +128,10 @@ const resetAppStore = () => { setVersion(defaultValue.version) setHasUpdate(defaultValue.hasUpdate) setLatestVersion(defaultValue.latestVersion) + setUpdateInfo(defaultValue.updateInfo) + setDownloading(defaultValue.downloading) + setDownloadProgress(defaultValue.downloadProgress) + setDownloadCompleted(defaultValue.downloadCompleted) setInited(defaultValue.inited) setNotice(defaultValue.notice) setUseMirror(defaultValue.useMirror) diff --git a/src/store/tool.ts b/src/store/tool.ts index c002450..ac4fa60 100644 --- a/src/store/tool.ts +++ b/src/store/tool.ts @@ -124,6 +124,7 @@ const defaultValue = { launchIndex: 0, powerPlan: 0, autoCloseGame: true, // 帧数测试自动关闭游戏 + isGameRunning: false, // 游戏运行状态(全局共享) videoSetting: { version: "15", vendor_id: "0", @@ -191,6 +192,8 @@ export const useToolStore = () => { setVideoConfig, addLaunchOption, resetToolStore, + setIsGameRunning, + checkGameRunning, } } @@ -288,10 +291,28 @@ const addLaunchOption = (option: LaunchOption) => { sendCurrentLaunchOptionToTray(toolStore.state.launchIndex) } +const setIsGameRunning = (running: boolean) => { + toolStore.state.isGameRunning = running +} + +const checkGameRunning = async (): Promise => { + try { + const result = await invoke("check_process_running", { + processName: "cs2.exe", + }).catch(() => false) + setIsGameRunning(result) + return result + } catch { + setIsGameRunning(false) + return false + } +} + const resetToolStore = () => { setLaunchOptions(defaultValue.launchOptions) setLaunchIndex(defaultValue.launchIndex) setPowerPlan(defaultValue.powerPlan) setAutoCloseGame(defaultValue.autoCloseGame) setVideoSetting(defaultValue.videoSetting) + setIsGameRunning(defaultValue.isGameRunning) }