From 4c0c33382f7039af8352fd726dfa2987fafb8f86 Mon Sep 17 00:00:00 2001 From: purp1e Date: Thu, 6 Nov 2025 03:08:20 +0800 Subject: [PATCH] better fps testing ui + more info + comment + users minor update --- src/app/layout.tsx | 9 + src/components/auth/AuthButton.tsx | 10 +- src/components/cstb/FpsTest.tsx | 619 ++++++++++++++++++++------ src/components/cstb/SteamUsers.tsx | 24 +- src/components/cstb/VideoSetting.tsx | 126 ++++-- src/components/window/Nav.tsx | 32 +- src/components/window/SideBar.tsx | 2 +- src/components/window/ToolButton.tsx | 9 +- src/store/{fpsTest.ts => fps_test.ts} | 13 +- src/store/tool.ts | 35 +- 10 files changed, 682 insertions(+), 197 deletions(-) rename src/store/{fpsTest.ts => fps_test.ts} (84%) diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 01c6357..9e03466 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -45,6 +45,15 @@ export default function RootLayout({ children }: { children: React.ReactNode }) addToast({ title: `电源计划已切换 → ${PowerPlans[current].title}` }) }) + + void listen("tray://set_launch_index", async (event) => { + const index = event.payload + if (typeof index === "number" && index >= 0 && index < toolStore.state.launchOptions.length) { + tool.setLaunchIndex(index) + const optionName = toolStore.state.launchOptions[index].name || `启动项 ${index + 1}` + addToast({ title: `启动项已切换 → ${optionName}` }) + } + }) }, []) // 检测steam路径和游戏路径是否有效 diff --git a/src/components/auth/AuthButton.tsx b/src/components/auth/AuthButton.tsx index 87bd357..c667efb 100644 --- a/src/components/auth/AuthButton.tsx +++ b/src/components/auth/AuthButton.tsx @@ -30,7 +30,7 @@ export function AuthButton() { isIconOnly variant="light" size="sm" - className="px-2 py-0 rounded transition duration-150 hover:bg-black/10 dark:hover:bg-zinc-100/10 active:scale-95" + className="px-2 py-0 rounded transition duration-150 hover:bg-black/10 dark:hover:bg-zinc-100/10 active:scale-95 cursor-pointer" > @@ -46,13 +46,13 @@ export function AuthButton() { isIconOnly variant="light" size="sm" - className="px-2 py-0 rounded transition duration-150 hover:bg-black/10 dark:hover:bg-zinc-100/10 active:scale-95" + className="px-2 py-0 rounded transition duration-150 hover:bg-black/10 dark:hover:bg-zinc-100/10 active:scale-95 cursor-pointer [&>*]:cursor-pointer" > @@ -115,9 +115,9 @@ export function AuthButton() { isIconOnly variant="light" size="sm" - className="px-2 py-0 rounded transition duration-150 hover:bg-black/10 dark:hover:bg-zinc-100/10 active:scale-95" + className="px-2 py-0 rounded transition duration-150 hover:bg-black/10 dark:hover:bg-zinc-100/10 active:scale-95 cursor-pointer [&>*]:cursor-pointer" > - + diff --git a/src/components/cstb/FpsTest.tsx b/src/components/cstb/FpsTest.tsx index 6cd5591..9793d4a 100644 --- a/src/components/cstb/FpsTest.tsx +++ b/src/components/cstb/FpsTest.tsx @@ -1,13 +1,36 @@ "use client" import { useSteamStore } from "@/store/steam" import { useToolStore } from "@/store/tool" -import { useFpsTestStore } from "@/store/fpsTest" +import { useFpsTestStore } from "@/store/fps_test" import { invoke } from "@tauri-apps/api/core" import { Card, CardBody, CardHeader, CardIcon, CardTool } from "../window/Card" -import { addToast, Button, Chip, Spinner, Switch, Table, TableHeader, TableColumn, TableBody, TableRow, TableCell } from "@heroui/react" +import { + addToast, + Button, + Chip, + Spinner, + Table, + TableHeader, + TableColumn, + TableBody, + TableRow, + TableCell, + Tabs, + Tab, + Tooltip, + Input, + Modal, + ModalContent, + ModalHeader, + ModalBody, + ModalFooter, + Textarea, + useDisclosure, +} from "@heroui/react" import { useState, useEffect, useRef, useCallback } from "react" -import { TestTube, Power, List, Delete } from "@icon-park/react" +import { TestTube, Power, List, Delete, Play, Edit, Check, Close, Square } from "@icon-park/react" import { allSysInfo, type AllSystemInfo } from "tauri-plugin-system-info-api" +import { ToolButton } from "../window/ToolButton" const BENCHMARK_MAPS = [ { @@ -24,6 +47,8 @@ const BENCHMARK_MAPS = [ }, ] +const TEST_TIMEOUT = 200000 // 200秒超时时间(毫秒) + // 解析性能报告,提取时间戳和性能数据 function parseVProfReport(rawReport: string): { timestamp: string; data: string } | null { if (!rawReport) return null @@ -156,18 +181,47 @@ export function FpsTest() { const steam = useSteamStore() const tool = useToolStore() const fpsTest = useFpsTestStore() - const [testing, setTesting] = useState(false) const [testResult, setTestResult] = useState(null) const [testTimestamp, setTestTimestamp] = useState(null) - const [selectedMap, setSelectedMap] = useState(null) - const [selectedMapLabel, setSelectedMapLabel] = useState(null) - const [autoCloseGame, setAutoCloseGame] = useState(false) + const [selectedMapIndex, setSelectedMapIndex] = useState(0) const [isMonitoring, setIsMonitoring] = useState(false) const [showResultsTable, setShowResultsTable] = useState(false) const [hardwareInfo, setHardwareInfo] = useState(null) + const [isGameRunning, setIsGameRunning] = useState(false) + const [testNote, setTestNote] = useState("") // 测试备注 + const [editingNoteId, setEditingNoteId] = useState(null) // 正在编辑的备注ID + const [editingNoteValue, setEditingNoteValue] = useState("") // 正在编辑的备注内容 + const { isOpen: isNoteModalOpen, onOpen: onNoteModalOpen, onClose: onNoteModalClose } = useDisclosure() const monitoringIntervalRef = useRef(null) + const timeoutRef = useRef(null) // 记录测试开始的时间戳(用于过滤旧数据) const testStartTimestampRef = useRef(null) + const testStartTimeRef = useRef(null) + // 记录测试开始时的视频设置 + const testStartVideoSettingRef = useRef(null) + + // 检测游戏是否运行 + 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]) // 获取硬件信息 useEffect(() => { @@ -182,6 +236,34 @@ export function FpsTest() { void fetchHardwareInfo() }, []) + // 停止测试 + const stopTest = useCallback(async () => { + setIsMonitoring(false) + if (monitoringIntervalRef.current) { + clearInterval(monitoringIntervalRef.current) + monitoringIntervalRef.current = null + } + if (timeoutRef.current) { + clearTimeout(timeoutRef.current) + timeoutRef.current = null + } + testStartTimestampRef.current = null + testStartTimeRef.current = null + testStartVideoSettingRef.current = null + + // 如果启用了自动关闭游戏,则关闭游戏 + if (tool.state.autoCloseGame) { + try { + await invoke("kill_game") + addToast({ title: "已停止测试并关闭游戏" }) + } catch (error) { + console.error("关闭游戏失败:", error) + } + } else { + addToast({ title: "已停止测试" }) + } + }, [tool.state.autoCloseGame]) + // 读取结果函数 const readResult = useCallback( async (silent = false): Promise => { @@ -220,49 +302,80 @@ export function FpsTest() { setTestTimestamp(parsed.timestamp) // 成功读取后,清除测试开始时间戳(测试已完成) testStartTimestampRef.current = null - + testStartTimeRef.current = null + + // 停止监控和超时 + setIsMonitoring(false) + if (monitoringIntervalRef.current) { + clearInterval(monitoringIntervalRef.current) + monitoringIntervalRef.current = null + } + if (timeoutRef.current) { + clearTimeout(timeoutRef.current) + timeoutRef.current = null + } + // 提取 avg 和 p1 值 const { avg, p1 } = extractFpsMetrics(parsed.data) - - // 保存测试结果(即使没有 selectedMap 也保存,使用 "未知地图" 作为默认值) + + // 保存测试结果 const now = new Date() const testDate = now.toISOString() - const mapConfig = selectedMap ? BENCHMARK_MAPS.find(m => m.name === selectedMap) : null - - // 从 store 直接获取最新的 videoSetting,避免依赖项问题 - const currentVideoSetting = tool.store.state.videoSetting - + const mapConfig = BENCHMARK_MAPS[selectedMapIndex] + + // 使用测试开始时的视频设置(如果有的话),否则使用当前的 + const currentVideoSetting = testStartVideoSettingRef.current || tool.store.state.videoSetting + fpsTest.addResult({ id: `${now.getTime()}-${Math.random().toString(36).slice(2, 11)}`, testTime: parsed.timestamp, testDate, - mapName: selectedMap || "unknown", - mapLabel: mapConfig?.label || selectedMapLabel || "未知地图", + mapName: mapConfig?.name || "unknown", + mapLabel: mapConfig?.label || "未知地图", avg, p1, rawResult: parsed.data, videoSetting: currentVideoSetting, - hardwareInfo: hardwareInfo ? { - cpu: hardwareInfo.cpus[0]?.brand || null, - cpuCount: hardwareInfo.cpu_count || null, - os: hardwareInfo.name && hardwareInfo.os_version - ? `${hardwareInfo.name} ${hardwareInfo.os_version}` - : null, - memory: hardwareInfo.total_memory - ? Math.round(hardwareInfo.total_memory / 1024 / 1024 / 1024) - : null, - } : null, + hardwareInfo: hardwareInfo + ? { + cpu: hardwareInfo.cpus[0]?.brand || null, + cpuCount: hardwareInfo.cpu_count || null, + os: + hardwareInfo.name && hardwareInfo.os_version + ? `${hardwareInfo.name} ${hardwareInfo.os_version}` + : null, + memory: hardwareInfo.total_memory + ? Math.round(hardwareInfo.total_memory / 1024 / 1024 / 1024) + : null, + gpu: null, + monitor: null, + } + : null, + note: testNote, // 保存备注 }) - + + // 清除保存的启动时视频设置 + testStartVideoSettingRef.current = null + if (!silent) { if (avg !== null || p1 !== null) { - addToast({ - title: `已读取并保存测试结果${avg !== null ? ` (avg: ${avg.toFixed(1)} FPS)` : ""}${p1 !== null ? ` (p1: ${p1.toFixed(1)} FPS)` : ""}` + addToast({ + title: `已读取并保存测试结果${ + avg !== null ? ` (avg: ${avg.toFixed(1)} FPS)` : "" + }${p1 !== null ? ` (p1: ${p1.toFixed(1)} FPS)` : ""}`, }) } else { addToast({ title: "已读取并保存测试结果(未能提取帧数数据)" }) } } + + // 如果启用了自动关闭游戏,则关闭游戏 + if (tool.state.autoCloseGame) { + setTimeout(() => { + void invoke("kill_game").catch(() => {}) + }, 2000) // 延迟2秒关闭,让用户看到结果 + } + return true } else if (!silent) { addToast({ @@ -288,28 +401,9 @@ export function FpsTest() { return false } }, - [steam.state.cs2Dir, selectedMap, selectedMapLabel, fpsTest, tool.store, hardwareInfo] + [steam.state.cs2Dir, selectedMapIndex, fpsTest, tool.store, hardwareInfo, tool.state.autoCloseGame] ) - // 关闭游戏 - const closeGame = useCallback(async () => { - try { - await invoke("kill_game") - addToast({ title: "已关闭CS2" }) - setIsMonitoring(false) - if (monitoringIntervalRef.current) { - clearInterval(monitoringIntervalRef.current) - monitoringIntervalRef.current = null - } - } catch (error) { - console.error("关闭游戏失败:", error) - addToast({ - title: `关闭游戏失败: ${error instanceof Error ? error.message : String(error)}`, - variant: "flat", - }) - } - }, []) - // 开始监控文件更新 useEffect(() => { if (isMonitoring && steam.state.cs2Dir) { @@ -317,27 +411,46 @@ export function FpsTest() { monitoringIntervalRef.current = setInterval(async () => { const success = await readResult(true) // 静默读取 if (success) { - // 读取成功,停止监控 + // 读取成功,监控会在readResult中停止 + return + } + }, 2000) // 每2秒检查一次 + + // 设置超时 + if (testStartTimeRef.current) { + timeoutRef.current = setTimeout(() => { + // 超时,认为测试失败 setIsMonitoring(false) if (monitoringIntervalRef.current) { clearInterval(monitoringIntervalRef.current) monitoringIntervalRef.current = null } - + testStartTimestampRef.current = null + testStartTimeRef.current = null + testStartVideoSettingRef.current = null + addToast({ + title: "测试超时(200秒),测试失败", + variant: "flat", + color: "warning", + }) // 如果启用了自动关闭游戏,则关闭游戏 - if (autoCloseGame) { + if (tool.state.autoCloseGame) { setTimeout(() => { - void closeGame() - }, 2000) // 延迟2秒关闭,让用户看到结果 + void invoke("kill_game").catch(() => {}) + }, 1000) } - } - }, 2000) // 每2秒检查一次 + }, TEST_TIMEOUT) + } } else { // 停止监控 if (monitoringIntervalRef.current) { clearInterval(monitoringIntervalRef.current) monitoringIntervalRef.current = null } + if (timeoutRef.current) { + clearTimeout(timeoutRef.current) + timeoutRef.current = null + } } // 清理函数 @@ -346,20 +459,48 @@ export function FpsTest() { clearInterval(monitoringIntervalRef.current) monitoringIntervalRef.current = null } + if (timeoutRef.current) { + clearTimeout(timeoutRef.current) + timeoutRef.current = null + } } - }, [isMonitoring, steam.state.cs2Dir, autoCloseGame, readResult, closeGame]) + }, [isMonitoring, steam.state.cs2Dir, readResult, tool.state.autoCloseGame]) - const startTest = async (mapConfig: (typeof BENCHMARK_MAPS)[0]) => { + const startTest = async () => { if (!steam.state.steamDir || !steam.state.cs2Dir) { addToast({ title: "请先配置 Steam 和 CS2 路径", variant: "flat", color: "warning" }) return } - setTesting(true) - setSelectedMap(mapConfig.name) - setSelectedMapLabel(mapConfig.label) + const mapConfig = BENCHMARK_MAPS[selectedMapIndex] + if (!mapConfig) { + addToast({ title: "请选择测试地图", variant: "flat", color: "warning" }) + return + } + + // 如果启用了自动关闭游戏,检测并关闭正在运行的游戏 + if (tool.state.autoCloseGame) { + const gameRunning = await checkGameRunning() + if (gameRunning) { + try { + await invoke("kill_game") + addToast({ title: "检测到游戏正在运行,已关闭" }) + // 等待一下确保游戏关闭 + await new Promise((resolve) => setTimeout(resolve, 2000)) + } catch (error) { + console.error("关闭游戏失败:", error) + addToast({ + title: `关闭游戏失败: ${error instanceof Error ? error.message : String(error)}`, + variant: "flat", + }) + return + } + } + } + setTestResult(null) setTestTimestamp(null) + // 注意:不清空备注,让用户可以在测试过程中记住备注内容 // 记录测试开始时间戳(格式:MM/DD HH:mm:ss) const now = new Date() @@ -369,6 +510,9 @@ export function FpsTest() { const minute = String(now.getMinutes()).padStart(2, "0") const second = String(now.getSeconds()).padStart(2, "0") testStartTimestampRef.current = `${month}/${day} ${hour}:${minute}:${second}` + testStartTimeRef.current = now.getTime() + // 保存启动时的视频设置 + testStartVideoSettingRef.current = { ...tool.store.state.videoSetting } try { const launchOption = `-allow_third_party_software -condebug -conclearlog +map_workshop ${mapConfig.workshopId} ${mapConfig.map}` @@ -381,7 +525,6 @@ export function FpsTest() { }) addToast({ title: `已启动 ${mapConfig.label} 测试,正在自动监听结果...` }) - setTesting(false) // 开始自动监听文件更新 setIsMonitoring(true) @@ -391,10 +534,43 @@ export function FpsTest() { title: `启动测试失败: ${error instanceof Error ? error.message : String(error)}`, variant: "flat", }) - setTesting(false) setIsMonitoring(false) - // 启动失败,清除测试开始时间戳 + // 启动失败,清除测试开始时间戳和视频设置 testStartTimestampRef.current = null + testStartTimeRef.current = null + testStartVideoSettingRef.current = null + } + } + + // 判断是否可以开始测试 + const canStartTest = !tool.state.autoCloseGame ? !isGameRunning : true + + // 格式化视频设置摘要 + const formatVideoSettingSummary = (videoSetting: typeof tool.state.videoSetting | null): string => { + if (!videoSetting) return "N/A" + const resolution = `${videoSetting.defaultres}x${videoSetting.defaultresheight}` + const refreshRate = videoSetting.refreshrate_denominator === "1" + ? videoSetting.refreshrate_numerator + : `${videoSetting.refreshrate_numerator}/${videoSetting.refreshrate_denominator}` + const msaa = videoSetting.msaa_samples === "0" ? "无" : `${videoSetting.msaa_samples}x` + return `${resolution}@${refreshRate}Hz, MSAA:${msaa}` + } + + // 打开备注编辑对话框 + const handleEditNote = (resultId: string, currentNote: string) => { + setEditingNoteId(resultId) + setEditingNoteValue(currentNote) + onNoteModalOpen() + } + + // 保存备注 + const handleSaveNote = () => { + if (editingNoteId) { + fpsTest.updateNote(editingNoteId, editingNoteValue) + addToast({ title: "备注已更新" }) + onNoteModalClose() + setEditingNoteId(null) + setEditingNoteValue("") } } @@ -403,62 +579,145 @@ export function FpsTest() { 帧数测试 + {isGameRunning && !tool.state.autoCloseGame && ( + + 游戏运行中 关闭游戏后从这里启动 + + )} - - + +
+ + {isMonitoring ? ( + + ) : ( + + )} +
{showResultsTable ? ( -
+
- +
测试时间 测试地图 - 平均帧数 - P1帧数 - CPU - 操作 + AVG平均帧 + P1低帧 + 系统版本 + GPU + 内存 + 视频设置 + 备注 + 操作 {fpsTest.state.results.map((result) => ( - {result.testTime} - {result.mapLabel} - + {result.testTime} + {result.mapLabel} + {result.avg !== null ? `${result.avg.toFixed(1)}` : "N/A"} - + {result.p1 !== null ? `${result.p1.toFixed(1)}` : "N/A"} - - {result.hardwareInfo?.cpu || "N/A"} + + {result.hardwareInfo?.os || "N/A"} + + + {result.hardwareInfo?.gpu || "N/A"} + + + {result.hardwareInfo?.memory ? `${result.hardwareInfo.memory}GB` : "N/A"} + + + + + {result.videoSetting + ? `${result.videoSetting.defaultres}x${result.videoSetting.defaultresheight}` + : "N/A"} + + + + +
+ + {result.note || "无备注"} + + +
- +
+ +
))} @@ -467,63 +726,127 @@ export function FpsTest() { ) : ( -
-
- {BENCHMARK_MAPS.map((mapConfig) => ( - - ))} + {BENCHMARK_MAPS.map((map, index) => ( + + ))} + +
+ + {/* 备注 */} +
+ + +
+
+ + {/* 工具栏:按钮靠右对齐 */} +
- - 测试完成自动关闭游戏 - + {isMonitoring && ( 正在监听中... )} + + + + {/* 测试结果显示:测试时间、平均帧、P1低帧 */} + {testResult && testTimestamp && (() => { + const { avg, p1 } = extractFpsMetrics(testResult) + return ( + <> +
+
测试时间
+
{testTimestamp}
+
+
+
+
+
平均帧
+
{avg !== null ? `${avg.toFixed(1)}` : "N/A"}
+
+
+
P1低帧
+
{p1 !== null ? `${p1.toFixed(1)}` : "N/A"}
+
+
+
+ + ) + })()}
{testResult && (
-
- {testTimestamp && ( - - 测试时间: {testTimestamp} - - )} -
                   {testResult}
                 
@@ -532,6 +855,34 @@ export function FpsTest() {
)} + + {/* 备注编辑对话框 */} + + + {(onClose) => ( + <> + 编辑备注 + +