From dcac1295c6c9eb6469146aab00e7e421460b7b4c Mon Sep 17 00:00:00 2001 From: purp1e Date: Thu, 6 Nov 2025 19:26:47 +0800 Subject: [PATCH] [feat] batch testing and average --- src/components/cstb/FpsTest.tsx | 382 +++++++++++++++++++++++++++----- 1 file changed, 331 insertions(+), 51 deletions(-) diff --git a/src/components/cstb/FpsTest.tsx b/src/components/cstb/FpsTest.tsx index 5e67bf1..8aba923 100644 --- a/src/components/cstb/FpsTest.tsx +++ b/src/components/cstb/FpsTest.tsx @@ -30,6 +30,9 @@ import { DropdownTrigger, DropdownMenu, DropdownItem, + Select, + SelectItem, + CircularProgress, } from "@heroui/react" import { useState, useEffect, useRef, useCallback } from "react" import { @@ -226,6 +229,18 @@ export function FpsTest() { const [resolutionWidth, setResolutionWidth] = useState("") // 分辨率宽度 const [resolutionHeight, setResolutionHeight] = useState("") // 分辨率高度 const [isFullscreen, setIsFullscreen] = useState(true) // 全屏模式(默认全屏) + const [batchTestCount, setBatchTestCount] = useState(1) // 批量测试次数(1表示单次测试) + const [batchTestProgress, setBatchTestProgress] = useState<{ + current: number + total: number + } | null>(null) // 批量测试进度 + const [batchTestResults, setBatchTestResults] = useState< + Array<{ avg: number | null; p1: number | null }> + >([]) // 批量测试结果 + const [isBatchTesting, setIsBatchTesting] = useState(false) // 批量测试是否正在进行 + const batchTestAbortRef = useRef(false) // 批量测试中止标志 + const testCompleteRef = useRef(false) // 测试完成标志 + const batchTestResultsRef = useRef>([]) // 批量测试结果ref(用于在循环中收集) const { isOpen: isNoteModalOpen, onOpen: onNoteModalOpen, @@ -286,6 +301,15 @@ export function FpsTest() { // 停止测试 const stopTest = useCallback(async () => { + // 如果是批量测试,设置中止标志 + if (batchTestProgress) { + batchTestAbortRef.current = true + setBatchTestProgress(null) + setBatchTestResults([]) + setIsBatchTesting(false) // 重置批量测试状态 + addToast({ title: "已停止批量测试" }) + } + setIsMonitoring(false) if (monitoringIntervalRef.current) { clearInterval(monitoringIntervalRef.current) @@ -310,7 +334,7 @@ export function FpsTest() { } else { addToast({ title: "已停止测试" }) } - }, [tool.state.autoCloseGame]) + }, [tool.state.autoCloseGame, batchTestProgress]) // 读取结果函数 const readResult = useCallback( @@ -354,6 +378,7 @@ export function FpsTest() { // 停止监控和超时 setIsMonitoring(false) + testCompleteRef.current = true // 标记测试完成 if (monitoringIntervalRef.current) { clearInterval(monitoringIntervalRef.current) monitoringIntervalRef.current = null @@ -375,33 +400,79 @@ export function FpsTest() { 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: 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}` + // 如果是批量测试,保存结果到批量结果数组,否则直接保存 + const currentBatchProgress = batchTestProgress + if (currentBatchProgress) { + const result = { avg, p1 } + setBatchTestResults((prev) => [...prev, result]) + batchTestResultsRef.current = [...batchTestResultsRef.current, result] + + // 添加带批量标识的备注 + const batchNote = testNote + ? `${testNote} [批量${currentBatchProgress.current}/${currentBatchProgress.total}]` + : `[批量${currentBatchProgress.current}/${currentBatchProgress.total}]` + + fpsTest.addResult({ + id: `${now.getTime()}-${Math.random().toString(36).slice(2, 11)}`, + testTime: parsed.timestamp, + testDate, + 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, - memory: hardwareInfo.total_memory - ? Math.round(hardwareInfo.total_memory / 1024 / 1024 / 1024) - : null, - gpu: null, - monitor: null, - } - : null, - note: testNote, // 保存备注 - }) + gpu: null, + monitor: null, + } + : null, + note: batchNote, + }) + + // 批量测试中,测试完成后继续下一轮 + if (currentBatchProgress.current < currentBatchProgress.total) { + // 不在这里处理,由startTest的循环处理 + } + } else { + fpsTest.addResult({ + id: `${now.getTime()}-${Math.random().toString(36).slice(2, 11)}`, + testTime: parsed.timestamp, + testDate, + 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, + gpu: null, + monitor: null, + } + : null, + note: testNote, // 保存备注 + }) + } // 清除保存的启动时视频设置 testStartVideoSettingRef.current = null @@ -457,6 +528,8 @@ export function FpsTest() { tool.store, hardwareInfo, tool.state.autoCloseGame, + batchTestProgress, + testNote, ] ) @@ -522,16 +595,19 @@ export function FpsTest() { } }, [isMonitoring, steam.state.cs2Dir, readResult, tool.state.autoCloseGame]) - const startTest = async () => { + // 执行单次测试 + const runSingleTest = async ( + testIndex: number, + totalTests: number, + isFirstTest: boolean = false + ): Promise => { if (!steam.state.steamDir || !steam.state.cs2Dir) { - addToast({ title: "请先配置 Steam 和 CS2 路径", variant: "flat", color: "warning" }) - return + return false } const mapConfig = BENCHMARK_MAPS[selectedMapIndex] if (!mapConfig) { - addToast({ title: "请选择测试地图", variant: "flat", color: "warning" }) - return + return false } // 如果启用了自动关闭游戏,检测并关闭正在运行的游戏 @@ -540,23 +616,17 @@ export function FpsTest() { 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 + return false } } } setTestResult(null) setTestTimestamp(null) - // 注意:不清空备注,让用户可以在测试过程中记住备注内容 // 记录测试开始时间戳(格式:MM/DD HH:mm:ss) const now = new Date() @@ -567,8 +637,6 @@ export function FpsTest() { 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 { // 构建启动参数:基础参数 + 分辨率和全屏设置 + 自定义启动项(如果有) @@ -601,10 +669,57 @@ export function FpsTest() { server: "worldwide", }) - addToast({ title: `已启动 ${mapConfig.label} 测试,正在自动监听结果...` }) + // 延迟读取视频设置(修复分辨率读取bug) + // 等待游戏启动并应用分辨率设置(5秒) + // 批量测试的第一次不需要等待,因为游戏还没有启动过 + if (!isFirstTest) { + await new Promise((resolve) => setTimeout(resolve, 5000)) + } + + // 重新读取视频配置 + if (steam.state.steamDirValid && steam.currentUser()) { + await tool.getVideoConfig(steam.state.steamDir, steam.currentUser()?.steam_id32 || 0) + // 再等待一下确保配置已更新 + await new Promise((resolve) => setTimeout(resolve, 1000)) + } + + // 保存启动后的视频设置(延迟读取后的) + testStartVideoSettingRef.current = { ...tool.store.state.videoSetting } + + if (totalTests > 1) { + addToast({ + title: `批量测试 ${testIndex}/${totalTests}:已启动 ${mapConfig.label} 测试,正在自动监听结果...`, + }) + } else { + addToast({ title: `已启动 ${mapConfig.label} 测试,正在自动监听结果...` }) + } // 开始自动监听文件更新 setIsMonitoring(true) + testCompleteRef.current = false // 重置完成标志 + + // 等待测试完成(通过readResult的回调来处理) + return new Promise((resolve) => { + let resolved = false + // 设置一个标志,当readResult成功时调用resolve + const checkInterval = setInterval(() => { + // 检查是否已经读取到结果(testCompleteRef被设置为true表示测试完成) + if (testCompleteRef.current && !resolved) { + resolved = true + clearInterval(checkInterval) + resolve(true) + } + }, 500) + + // 设置超时 + setTimeout(() => { + if (!resolved) { + resolved = true + clearInterval(checkInterval) + resolve(false) + } + }, TEST_TIMEOUT + 10000) + }) } catch (error) { console.error("启动测试失败:", error) addToast({ @@ -612,15 +727,138 @@ export function FpsTest() { variant: "flat", }) setIsMonitoring(false) - // 启动失败,清除测试开始时间戳和视频设置 testStartTimestampRef.current = null testStartTimeRef.current = null testStartVideoSettingRef.current = null + return false } } - // 判断是否可以开始测试 - const canStartTest = !tool.state.autoCloseGame ? !isGameRunning : true + const startTest = async () => { + if (!steam.state.steamDir || !steam.state.cs2Dir) { + addToast({ title: "请先配置 Steam 和 CS2 路径", variant: "flat", color: "warning" }) + return + } + + const mapConfig = BENCHMARK_MAPS[selectedMapIndex] + if (!mapConfig) { + addToast({ title: "请选择测试地图", variant: "flat", color: "warning" }) + return + } + + const totalTests = batchTestCount + batchTestAbortRef.current = false + setBatchTestResults([]) + batchTestResultsRef.current = [] + + if (totalTests > 1) { + // 批量测试 + setIsBatchTesting(true) // 设置批量测试状态 + setBatchTestProgress({ current: 0, total: totalTests }) + + for (let i = 1; i <= totalTests; i++) { + if (batchTestAbortRef.current) { + break + } + + setBatchTestProgress({ current: i, total: totalTests }) + + // 执行单次测试,第一次测试跳过5秒等待 + const success = await runSingleTest(i, totalTests, i === 1) + + if (!success && !batchTestAbortRef.current) { + addToast({ title: `批量测试 ${i}/${totalTests} 失败`, variant: "flat", color: "warning" }) + break + } + + // 如果不是最后一次测试,等待5秒再继续 + if (i < totalTests && !batchTestAbortRef.current) { + // 确保游戏已关闭(如果自动关闭已启用,readResult中已经关闭了) + if (tool.state.autoCloseGame) { + // 等待游戏关闭完成(readResult中延迟2秒关闭) + await new Promise((resolve) => setTimeout(resolve, 3000)) + try { + await invoke("kill_game").catch(() => {}) + } catch (error) { + console.error("关闭游戏失败:", error) + } + } + // 等待5秒 + await new Promise((resolve) => setTimeout(resolve, 5000)) + } + } + + // 批量测试完成,计算平均值 + // 使用ref中收集的结果(确保获取到所有结果) + const finalResults = batchTestResultsRef.current + if (!batchTestAbortRef.current && finalResults.length > 0) { + const validResults = finalResults.filter((r) => r.avg !== null || r.p1 !== null) + if (validResults.length > 0) { + const avgAvg = + validResults.reduce((sum, r) => sum + (r.avg || 0), 0) / validResults.length + const avgP1 = validResults.reduce((sum, r) => sum + (r.p1 || 0), 0) / validResults.length + + const now = new Date() + const testDate = now.toISOString() + const month = String(now.getMonth() + 1).padStart(2, "0") + const day = String(now.getDate()).padStart(2, "0") + const hour = String(now.getHours()).padStart(2, "0") + const minute = String(now.getMinutes()).padStart(2, "0") + const second = String(now.getSeconds()).padStart(2, "0") + const testTime = `${month}/${day} ${hour}:${minute}:${second}` + + const averageNote = testNote + ? `${testNote} [批量${totalTests}次平均]` + : `[批量${totalTests}次平均]` + + fpsTest.addResult({ + id: `${now.getTime()}-${Math.random().toString(36).slice(2, 11)}`, + testTime, + testDate, + mapName: mapConfig?.name || "unknown", + mapLabel: mapConfig?.label || "未知地图", + avg: avgAvg, + p1: avgP1, + rawResult: `批量测试${totalTests}次平均值\n平均帧: ${avgAvg.toFixed( + 1 + )}\nP1低帧: ${avgP1.toFixed(1)}`, + videoSetting: tool.store.state.videoSetting, + 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: averageNote, + }) + + addToast({ + title: `批量测试完成!平均值:avg ${avgAvg.toFixed(1)} FPS, p1 ${avgP1.toFixed(1)} FPS`, + color: "success", + }) + } + } + + setBatchTestProgress(null) + setBatchTestResults([]) + setIsBatchTesting(false) // 批量测试结束,重置状态 + } else { + // 单次测试 + await runSingleTest(1, 1, true) + } + } + + // 判断是否可以开始测试(批量测试进行中时不能开始新测试) + const canStartTest = (!tool.state.autoCloseGame ? !isGameRunning : true) && !isBatchTesting // 格式化视频设置摘要 const formatVideoSettingSummary = ( @@ -763,6 +1001,25 @@ export function FpsTest() {
+ {batchTestProgress && ( +
+
+ +
+ {batchTestProgress.current}/{batchTestProgress.total} +
+
+
+ )} {showResultsTable && (
+
+ + +
@@ -1112,12 +1392,6 @@ export function FpsTest() { 关闭游戏 - {isMonitoring && ( - - 正在监听中... - - )} -