[fix] update data lost between page and stutter caused by periodly checking game stats in sync mode

This commit is contained in:
2025-11-09 00:55:19 +08:00
parent 812bc64b6f
commit 0f938f6f3e
10 changed files with 190 additions and 94 deletions

2
next-env.d.ts vendored
View File

@@ -1,6 +1,6 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
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.

View File

@@ -64,8 +64,8 @@ pub fn kill_game() -> Result<String, String> {
}
#[tauri::command]
pub fn check_process_running(process_name: &str) -> Result<bool, String> {
Ok(common::check_process_running(process_name))
pub async fn check_process_running(process_name: &str) -> Result<bool, String> {
Ok(common::check_process_running_async(process_name).await)
}
#[tauri::command]

View File

@@ -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() {

View File

@@ -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": {

View File

@@ -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<boolean>("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"

View File

@@ -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<UpdateInfo | null>(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<number>("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",

View File

@@ -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<boolean>("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<VideoConfig>(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)

View File

@@ -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,
}
}

View File

@@ -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)

View File

@@ -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<boolean> => {
try {
const result = await invoke<boolean>("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)
}