[feat] fps benchmark one-key testing

This commit is contained in:
Purp1e
2025-11-05 02:24:17 +08:00
parent d6ce9bd5f3
commit 543c3344d1
9 changed files with 562 additions and 131 deletions

BIN
bun.lockb

Binary file not shown.

View File

@@ -22,21 +22,21 @@
"@icon-park/react": "^1.4.2",
"@reactuses/core": "6.0.1",
"@supabase/ssr": "0.6.1",
"@tauri-apps/api": "2.4.0",
"@tauri-apps/api": "^2.9.0",
"@tauri-apps/plugin-autostart": "^2.5.1",
"@tauri-apps/plugin-cli": "~2.2.1",
"@tauri-apps/plugin-clipboard-manager": "2.2.2",
"@tauri-apps/plugin-deep-link": "~2.2.1",
"@tauri-apps/plugin-dialog": "~2.2.2",
"@tauri-apps/plugin-fs": "2.2.0",
"@tauri-apps/plugin-global-shortcut": "2.2.0",
"@tauri-apps/plugin-http": "2.4.2",
"@tauri-apps/plugin-notification": "2.2.2",
"@tauri-apps/plugin-os": "2.2.1",
"@tauri-apps/plugin-process": "2.2.0",
"@tauri-apps/plugin-shell": "~2.2.2",
"@tauri-apps/plugin-cli": "^2.4.1",
"@tauri-apps/plugin-clipboard-manager": "^2.3.2",
"@tauri-apps/plugin-deep-link": "^2.4.5",
"@tauri-apps/plugin-dialog": "^2.4.2",
"@tauri-apps/plugin-fs": "^2.4.4",
"@tauri-apps/plugin-global-shortcut": "^2.3.1",
"@tauri-apps/plugin-http": "^2.5.4",
"@tauri-apps/plugin-notification": "^2.3.3",
"@tauri-apps/plugin-os": "^2.3.2",
"@tauri-apps/plugin-process": "^2.3.1",
"@tauri-apps/plugin-shell": "^2.3.3",
"@tauri-apps/plugin-store": "^2.4.1",
"@tauri-store/valtio": "2.1.1",
"@tauri-store/valtio": "^3.2.0",
"@types/throttle-debounce": "^5.0.2",
"ahooks": "^3.9.6",
"framer-motion": "^12.23.24",
@@ -51,7 +51,7 @@
},
"devDependencies": {
"@tailwindcss/postcss": "^4.1.16",
"@tauri-apps/cli": "^2.9.2",
"@tauri-apps/cli": "^2.9.3",
"@testing-library/dom": "^10.4.1",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.0",

172
src-tauri/Cargo.lock generated
View File

@@ -44,9 +44,9 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
[[package]]
name = "aho-corasick"
version = "1.1.3"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
dependencies = [
"memchr",
]
@@ -540,9 +540,9 @@ dependencies = [
[[package]]
name = "cc"
version = "1.2.43"
version = "1.2.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "739eb0f94557554b3ca9a86d2d37bebd49c5e6d0c1d2bda35ba5bdac830befc2"
checksum = "37521ac7aabe3d13122dc382493e20c9416f299d2ccd5b3a5340a2570cdeb0f3"
dependencies = [
"find-msvc-tools",
"shlex",
@@ -607,18 +607,18 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.50"
version = "4.5.51"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c2cfd7bf8a6017ddaa4e32ffe7403d547790db06bd171c1c53926faab501623"
checksum = "4c26d721170e0295f191a69bd9a1f93efcdb0aff38684b61ab5750468972e5f5"
dependencies = [
"clap_builder",
]
[[package]]
name = "clap_builder"
version = "4.5.50"
version = "4.5.51"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a4c05b9e80c5ccd3a7ef080ad7b6ba7d6fc00a985b8b157197075677c82c7a0"
checksum = "75835f0c7bf681bfd05abe44e965760fea999a5286c6eb2d59883634fd02011a"
dependencies = [
"anstream",
"anstyle",
@@ -2051,9 +2051,9 @@ dependencies = [
[[package]]
name = "icu_collections"
version = "2.0.0"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47"
checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43"
dependencies = [
"displaydoc",
"potential_utf",
@@ -2064,9 +2064,9 @@ dependencies = [
[[package]]
name = "icu_locale_core"
version = "2.0.0"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a"
checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6"
dependencies = [
"displaydoc",
"litemap",
@@ -2077,11 +2077,10 @@ dependencies = [
[[package]]
name = "icu_normalizer"
version = "2.0.0"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979"
checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599"
dependencies = [
"displaydoc",
"icu_collections",
"icu_normalizer_data",
"icu_properties",
@@ -2092,42 +2091,38 @@ dependencies = [
[[package]]
name = "icu_normalizer_data"
version = "2.0.0"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3"
checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a"
[[package]]
name = "icu_properties"
version = "2.0.1"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b"
checksum = "e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99"
dependencies = [
"displaydoc",
"icu_collections",
"icu_locale_core",
"icu_properties_data",
"icu_provider",
"potential_utf",
"zerotrie",
"zerovec",
]
[[package]]
name = "icu_properties_data"
version = "2.0.1"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632"
checksum = "02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899"
[[package]]
name = "icu_provider"
version = "2.0.0"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af"
checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614"
dependencies = [
"displaydoc",
"icu_locale_core",
"stable_deref_trait",
"tinystr",
"writeable",
"yoke",
"zerofrom",
@@ -2216,9 +2211,9 @@ checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130"
[[package]]
name = "iri-string"
version = "0.7.8"
version = "0.7.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2"
checksum = "4f867b9d1d896b67beb18518eda36fdb77a32ea590de864f1325b294a6d14397"
dependencies = [
"memchr",
"serde",
@@ -2311,9 +2306,9 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
[[package]]
name = "js-sys"
version = "0.3.81"
version = "0.3.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305"
checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65"
dependencies = [
"once_cell",
"wasm-bindgen",
@@ -2450,9 +2445,9 @@ checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
[[package]]
name = "litemap"
version = "0.8.0"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956"
checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77"
[[package]]
name = "litrs"
@@ -2589,9 +2584,9 @@ dependencies = [
[[package]]
name = "moxcms"
version = "0.7.8"
version = "0.7.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "692af879e4d9383c0fd9dec15524af6b6977c8bf1c6b278a4526d5341347c574"
checksum = "0fbdd3d7436f8b5e892b8b7ea114271ff0fa00bc5acae845d53b07d498616ef6"
dependencies = [
"num-traits",
"pxfm",
@@ -3446,9 +3441,9 @@ dependencies = [
[[package]]
name = "potential_utf"
version = "0.1.3"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a"
checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77"
dependencies = [
"zerovec",
]
@@ -4019,9 +4014,9 @@ dependencies = [
[[package]]
name = "rustls"
version = "0.23.34"
version = "0.23.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a9586e9ee2b4f8fab52a0048ca7334d7024eef48e2cb9407e3497bb7cab7fa7"
checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f"
dependencies = [
"once_cell",
"ring",
@@ -4033,9 +4028,9 @@ dependencies = [
[[package]]
name = "rustls-pki-types"
version = "1.12.0"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79"
checksum = "94182ad936a0c91c324cd46c6511b9510ed16af436d7b5bab34beab0afd55f7a"
dependencies = [
"web-time",
"zeroize",
@@ -4043,9 +4038,9 @@ dependencies = [
[[package]]
name = "rustls-webpki"
version = "0.103.7"
version = "0.103.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e10b3f4191e8a80e6b43eebabfac91e5dcecebb27a71f04e820c47ec41d314bf"
checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52"
dependencies = [
"ring",
"rustls-pki-types",
@@ -4111,9 +4106,9 @@ dependencies = [
[[package]]
name = "schemars"
version = "1.0.4"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82d20c4491bc164fa2f6c5d44565947a52ad80b9505d8e36f8d54c27c739fcd0"
checksum = "1317c3bf3e7df961da95b0a56a172a02abead31276215a0497241a7624b487ce"
dependencies = [
"dyn-clone",
"ref-cast",
@@ -4315,7 +4310,7 @@ dependencies = [
"indexmap 1.9.3",
"indexmap 2.12.0",
"schemars 0.9.0",
"schemars 1.0.4",
"schemars 1.0.5",
"serde_core",
"serde_json",
"serde_with_macros",
@@ -5423,9 +5418,9 @@ dependencies = [
[[package]]
name = "tinystr"
version = "0.8.1"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b"
checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869"
dependencies = [
"displaydoc",
"zerovec",
@@ -5496,9 +5491,9 @@ dependencies = [
[[package]]
name = "tokio-util"
version = "0.7.16"
version = "0.7.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5"
checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594"
dependencies = [
"bytes",
"futures-core",
@@ -5785,9 +5780,9 @@ dependencies = [
[[package]]
name = "unicode-ident"
version = "1.0.20"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "462eeb75aeb73aea900253ce739c8e18a67423fadf006037cd3ff27e82748a06"
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
[[package]]
name = "unicode-segmentation"
@@ -5884,9 +5879,9 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "version-compare"
version = "0.2.0"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b"
checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e"
[[package]]
name = "version_check"
@@ -5956,9 +5951,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen"
version = "0.2.104"
version = "0.2.105"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d"
checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60"
dependencies = [
"cfg-if",
"once_cell",
@@ -5967,25 +5962,11 @@ dependencies = [
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19"
dependencies = [
"bumpalo",
"log",
"proc-macro2",
"quote",
"syn 2.0.108",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.54"
version = "0.4.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e038d41e478cc73bae0ff9b36c60cff1c98b8f38f8d7e8061e79ee63608ac5c"
checksum = "551f88106c6d5e7ccc7cd9a16f312dd3b5d36ea8b4954304657d5dfba115d4a0"
dependencies = [
"cfg-if",
"js-sys",
@@ -5996,9 +5977,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.104"
version = "0.2.105"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119"
checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@@ -6006,22 +5987,22 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.104"
version = "0.2.105"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7"
checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc"
dependencies = [
"bumpalo",
"proc-macro2",
"quote",
"syn 2.0.108",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.104"
version = "0.2.105"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1"
checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76"
dependencies = [
"unicode-ident",
]
@@ -6114,9 +6095,9 @@ dependencies = [
[[package]]
name = "web-sys"
version = "0.3.81"
version = "0.3.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9367c417a924a74cae129e6a2ae3b47fabb1f8995595ab474029da749a8be120"
checksum = "3a1f95c0d03a47f4ae1f7a64643a6bb97465d9b740f0fa8f90ea33915c99a9a1"
dependencies = [
"js-sys",
"wasm-bindgen",
@@ -6178,9 +6159,9 @@ dependencies = [
[[package]]
name = "webpki-roots"
version = "1.0.3"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32b130c0d2d49f8b6889abc456e795e82525204f27c42cf767cf0d7734e089b8"
checksum = "b2878ef029c47c6e8cf779119f20fcf52bde7ad42a731b2a304bc221df17571e"
dependencies = [
"rustls-pki-types",
]
@@ -6755,9 +6736,9 @@ dependencies = [
[[package]]
name = "writeable"
version = "0.6.1"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb"
checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9"
[[package]]
name = "wry"
@@ -6850,11 +6831,10 @@ checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56"
[[package]]
name = "yoke"
version = "0.8.0"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc"
checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954"
dependencies = [
"serde",
"stable_deref_trait",
"yoke-derive",
"zerofrom",
@@ -6862,9 +6842,9 @@ dependencies = [
[[package]]
name = "yoke-derive"
version = "0.8.0"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6"
checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d"
dependencies = [
"proc-macro2",
"quote",
@@ -6983,9 +6963,9 @@ checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
[[package]]
name = "zerotrie"
version = "0.2.2"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595"
checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851"
dependencies = [
"displaydoc",
"yoke",
@@ -6994,9 +6974,9 @@ dependencies = [
[[package]]
name = "zerovec"
version = "0.11.4"
version = "0.11.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b"
checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002"
dependencies = [
"yoke",
"zerofrom",
@@ -7005,9 +6985,9 @@ dependencies = [
[[package]]
name = "zerovec-derive"
version = "0.11.1"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f"
checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3"
dependencies = [
"proc-macro2",
"quote",

View File

@@ -6,6 +6,8 @@ use crate::wrap_err;
use anyhow::Result;
use std::fs::File;
use std::fs;
use std::path::Path;
use std::io::{BufRead, BufReader};
use tauri::path::BaseDirectory;
use tauri::Manager;
@@ -188,3 +190,68 @@ pub async fn analyze_replay(app: tauri::AppHandle, path: &str) -> Result<String,
// 返回结果
Ok(output_str.to_string())
}
// 帧数测试相关
#[tauri::command]
pub fn get_console_log_path(cs_path: &str) -> Result<String, String> {
// cs_path 是类似 "game\bin\win64" 的路径,需要向上找到 game\csgo\console.log
let path = Path::new(cs_path);
// 向上找到 game 目录
if let Some(game_dir) = path.ancestors().find(|p| {
p.file_name()
.and_then(|n| n.to_str())
.map(|n| n == "game")
.unwrap_or(false)
}) {
let console_log_path = game_dir.join("csgo").join("console.log");
Ok(console_log_path.to_string_lossy().to_string())
} else {
Err("无法找到 game 目录".to_string())
}
}
#[tauri::command]
pub fn read_vprof_report(console_log_path: &str) -> Result<String, String> {
let path = Path::new(console_log_path);
if !path.exists() {
return Err("console.log 文件不存在".to_string());
}
let file = File::open(path).map_err(|e| format!("无法打开文件: {}", e))?;
let reader = BufReader::new(file);
let mut vprof_lines = Vec::new();
let mut in_vprof_section = false;
let mut empty_line_count = 0;
for line_result in reader.lines() {
let line = line_result.map_err(|e| format!("读取行错误: {}", e))?;
// 检测 [VProf] 标记
if line.contains("[VProf]") {
in_vprof_section = true;
empty_line_count = 0;
vprof_lines.push(line.clone());
} else if in_vprof_section {
// 如果在 VProf 部分中
if line.trim().is_empty() {
empty_line_count += 1;
// 如果遇到两个连续的空行,结束 VProf 部分
if empty_line_count >= 2 {
break;
}
vprof_lines.push(line.clone());
} else {
empty_line_count = 0;
vprof_lines.push(line.clone());
}
}
}
if vprof_lines.is_empty() {
return Err("未找到 [VProf] 报告".to_string());
}
Ok(vprof_lines.join("\n"))
}

View File

@@ -132,6 +132,8 @@ fn main() {
cmds::set_cs2_video_config,
cmds::check_path,
cmds::analyze_replay,
cmds::get_console_log_path,
cmds::read_vprof_report,
on_button_clicked
])
.run(tauri::generate_context!())

View File

@@ -12,9 +12,12 @@ import { Refresh, SettingConfig } from "@icon-park/react"
// import { version } from "@tauri-apps/plugin-os"
import { useEffect, useState } from "react"
import { type AllSystemInfo, allSysInfo } from "tauri-plugin-system-info-api"
import { FpsTest } from "@/components/cstb/FpsTest"
export default function Page() {
return (
<Card className="h-full">
<section className="flex flex-col gap-4 overflow-hidden rounded-lg">
<div className="flex flex-col gap-4 overflow-y-auto h-full hide-scrollbar">
<Card>
<CardHeader>
<CardIcon type="menu">
<SettingConfig />
@@ -34,6 +37,9 @@ export default function Page() {
<HardwareInfo />
</CardBody>
</Card>
<FpsTest />
</div>
</section>
)
}

View File

@@ -25,3 +25,7 @@ a {
.hide-scrollbar::-webkit-scrollbar {
display: none;
}
.hide-scrollbar {
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}

View File

@@ -0,0 +1,371 @@
"use client"
import { useSteamStore } from "@/store/steam"
import { invoke } from "@tauri-apps/api/core"
import { Card, CardBody, CardHeader, CardIcon } from "../window/Card"
import { addToast, Button, Chip, Spinner, Switch } from "@heroui/react"
import { useState, useEffect, useRef, useCallback } from "react"
import { TestTube, Power } from "@icon-park/react"
const BENCHMARK_MAPS = [
{
name: "de_dust2_benchmark",
workshopId: "3240880604",
map: "de_dust2_benchmark",
label: "Dust2 Benchmark",
},
{
name: "de_ancient",
workshopId: "3472126051",
map: "de_ancient",
label: "Ancient",
},
]
// 解析性能报告,提取时间戳和性能数据
function parseVProfReport(rawReport: string): { timestamp: string; data: string } | null {
if (!rawReport) return null
const lines = rawReport.split("\n")
let timestamp = ""
let inPerformanceSection = false
const performanceLines: string[] = []
for (const line of lines) {
// 提取时间戳:格式如 "11/05 01:51:27 [VProf] -- Performance report --"
const timestampMatch = line.match(
/(\d{2}\/\d{2}\s+\d{2}:\d{2}:\d{2})\s+\[VProf\]\s+--\s+Performance\s+report\s+--/
)
if (timestampMatch) {
timestamp = timestampMatch[1]
inPerformanceSection = true
// 也包含 Performance report 这一行,但移除时间戳
const lineWithoutTimestamp = line.trim().replace(/^\d{2}\/\d{2}\s+\d{2}:\d{2}:\d{2}\s+/, "")
performanceLines.push(lineWithoutTimestamp)
continue
}
// 如果在性能报告部分
if (inPerformanceSection) {
const trimmedLine = line.trim()
// 只收集包含 [VProf] 的行
if (trimmedLine.includes("[VProf]")) {
// 移除行首的时间戳格式MM/DD HH:mm:ss
// 例如:"11/05 02:13:56 [VProf] ..." -> "[VProf] ..."
const lineWithoutTimestamp = trimmedLine.replace(/^\d{2}\/\d{2}\s+\d{2}:\d{2}:\d{2}\s+/, "")
performanceLines.push(lineWithoutTimestamp)
}
// 如果遇到空行且已经有数据,可能是报告结束,但不直接结束,因为可能还有更多数据
// 如果后续没有 [VProf] 行的数据,空行会被过滤掉
}
}
if (performanceLines.length === 0) return null
return {
timestamp,
data: performanceLines.join("\n").trim(),
}
}
// 比较时间戳格式MM/DD HH:mm:ss
// 返回 true 如果 timestamp1 晚于 timestamp2
function compareTimestamps(timestamp1: string, timestamp2: string): boolean {
// 解析时间戳MM/DD HH:mm:ss
const parseTimestamp = (ts: string) => {
const match = ts.match(/(\d{2})\/(\d{2})\s+(\d{2}):(\d{2}):(\d{2})/)
if (!match) return null
const [, month, day, hour, minute, second] = match.map(Number)
return { month, day, hour, minute, second }
}
const ts1 = parseTimestamp(timestamp1)
const ts2 = parseTimestamp(timestamp2)
if (!ts1 || !ts2) return false
// 使用当前年份作为基准
const now = new Date()
const currentYear = now.getFullYear()
// 创建日期对象,尝试当前年份
let date1 = new Date(currentYear, ts1.month - 1, ts1.day, ts1.hour, ts1.minute, ts1.second)
let date2 = new Date(currentYear, ts2.month - 1, ts2.day, ts2.hour, ts2.minute, ts2.second)
// 如果 date1 早于 date2可能是跨年了比如 date1 是 1月date2 是 12月
// 在这种情况下,给 date1 加一年
if (date1 < date2) {
// 检查是否可能是跨年(月份相差很大)
const monthDiff = (ts1.month - ts2.month + 12) % 12
if (monthDiff > 6) {
// 可能是跨年,给 date1 加一年
date1 = new Date(currentYear + 1, ts1.month - 1, ts1.day, ts1.hour, ts1.minute, ts1.second)
}
}
return date1 > date2
}
export function FpsTest() {
const steam = useSteamStore()
const [testing, setTesting] = useState(false)
const [testResult, setTestResult] = useState<string | null>(null)
const [testTimestamp, setTestTimestamp] = useState<string | null>(null)
const [selectedMap, setSelectedMap] = useState<string | null>(null)
const [autoCloseGame, setAutoCloseGame] = useState(false)
const [isMonitoring, setIsMonitoring] = useState(false)
const monitoringIntervalRef = useRef<NodeJS.Timeout | null>(null)
// 记录测试开始的时间戳(用于过滤旧数据)
const testStartTimestampRef = useRef<string | null>(null)
// 读取结果函数
const readResult = useCallback(
async (silent = false): Promise<boolean> => {
if (!steam.state.cs2Dir) {
if (!silent) {
addToast({ title: "请先配置 CS2 路径", variant: "flat" })
}
return false
}
try {
// 获取 console.log 路径
const consoleLogPath = await invoke<string>("get_console_log_path", {
csPath: steam.state.cs2Dir,
})
// 读取 VProf 报告
const report = await invoke<string>("read_vprof_report", {
consoleLogPath: consoleLogPath,
})
if (report && report.trim().length > 0) {
const parsed = parseVProfReport(report)
if (parsed) {
// 如果设置了测试开始时间且是自动监听silent=true验证报告时间戳是否晚于测试开始时间
// 手动读取silent=false时允许读取任何结果
if (silent && testStartTimestampRef.current) {
// 如果报告时间戳早于或等于测试开始时间,则视为旧数据,忽略
if (!compareTimestamps(parsed.timestamp, testStartTimestampRef.current)) {
// 这是旧数据,不处理
return false
}
}
setTestResult(parsed.data)
setTestTimestamp(parsed.timestamp)
// 成功读取后,清除测试开始时间戳(测试已完成)
testStartTimestampRef.current = null
if (!silent) {
addToast({ title: "已读取测试结果" })
}
return true
} else if (!silent) {
addToast({
title: "未能解析测试结果",
variant: "flat",
})
}
} else if (!silent) {
addToast({
title: "未能读取测试结果,请确保测试已完成",
variant: "flat",
})
}
return false
} catch (error) {
if (!silent) {
console.error("读取结果失败:", error)
addToast({
title: `读取结果失败: ${error instanceof Error ? error.message : String(error)}`,
variant: "flat",
})
}
return false
}
},
[steam.state.cs2Dir]
)
// 关闭游戏
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) {
// 每2秒检查一次文件更新
monitoringIntervalRef.current = setInterval(async () => {
const success = await readResult(true) // 静默读取
if (success) {
// 读取成功,停止监控
setIsMonitoring(false)
if (monitoringIntervalRef.current) {
clearInterval(monitoringIntervalRef.current)
monitoringIntervalRef.current = null
}
// 如果启用了自动关闭游戏,则关闭游戏
if (autoCloseGame) {
setTimeout(() => {
void closeGame()
}, 2000) // 延迟2秒关闭让用户看到结果
}
}
}, 2000) // 每2秒检查一次
} else {
// 停止监控
if (monitoringIntervalRef.current) {
clearInterval(monitoringIntervalRef.current)
monitoringIntervalRef.current = null
}
}
// 清理函数
return () => {
if (monitoringIntervalRef.current) {
clearInterval(monitoringIntervalRef.current)
monitoringIntervalRef.current = null
}
}
}, [isMonitoring, steam.state.cs2Dir, autoCloseGame, readResult, closeGame])
const startTest = async (mapConfig: (typeof BENCHMARK_MAPS)[0]) => {
if (!steam.state.steamDir || !steam.state.cs2Dir) {
addToast({ title: "请先配置 Steam 和 CS2 路径", variant: "flat", color: "warning" })
return
}
setTesting(true)
setSelectedMap(mapConfig.name)
setTestResult(null)
setTestTimestamp(null)
// 记录测试开始时间戳格式MM/DD HH:mm:ss
const now = new Date()
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")
testStartTimestampRef.current = `${month}/${day} ${hour}:${minute}:${second}`
try {
const launchOption = `-allow_third_party_software -condebug -conclearlog +map_workshop ${mapConfig.workshopId} ${mapConfig.map}`
// 启动游戏
await invoke("launch_game", {
steamPath: `${steam.state.steamDir}\\steam.exe`,
launchOption: launchOption,
server: "worldwide",
})
addToast({ title: `已启动 ${mapConfig.label} 测试,正在自动监听结果...` })
setTesting(false)
// 开始自动监听文件更新
setIsMonitoring(true)
} catch (error) {
console.error("启动测试失败:", error)
addToast({
title: `启动测试失败: ${error instanceof Error ? error.message : String(error)}`,
variant: "flat",
})
setTesting(false)
setIsMonitoring(false)
// 启动失败,清除测试开始时间戳
testStartTimestampRef.current = null
}
}
return (
<Card className="w-full">
<CardHeader>
<CardIcon>
<TestTube size={16} />
</CardIcon>
</CardHeader>
<CardBody>
<div className="flex flex-col gap-3">
<div className="flex flex-wrap items-center gap-2">
{BENCHMARK_MAPS.map((mapConfig) => (
<Button
key={mapConfig.name}
size="sm"
isDisabled={testing || isMonitoring}
onPress={() => {
void startTest(mapConfig)
}}
className="px-4 font-medium py-1.5 transition bg-blue-200 dark:bg-blue-900/60 rounded-full select-none"
>
{testing && selectedMap === mapConfig.name ? (
<Spinner size="sm" className="mr-2" />
) : null}
{mapConfig.label}
</Button>
))}
<Button
size="sm"
isDisabled={isMonitoring}
onPress={() => {
void readResult()
}}
className="px-4 font-medium py-1.5 transition bg-green-200 dark:bg-green-900/60 rounded-full select-none"
>
</Button>
<Button
size="sm"
onPress={() => {
void closeGame()
}}
className="px-4 font-medium py-1.5 transition bg-orange-200 dark:bg-orange-900/60 rounded-full select-none"
>
<Power className="mr-1" size={14} />
</Button>
<Switch size="sm" isSelected={autoCloseGame} onValueChange={setAutoCloseGame} className="ml-4">
</Switch>
{isMonitoring && (
<Chip size="sm" color="primary" variant="flat">
...
</Chip>
)}
</div>
{testResult && (
<div className="mt-2">
<div className="flex items-center gap-2 mb-2">
<Chip size="sm"></Chip>
{testTimestamp && (
<Chip size="sm" variant="flat" color="default">
: {testTimestamp}
</Chip>
)}
</div>
<pre className="p-3 overflow-auto font-mono text-xs rounded-md bg-black/5 dark:bg-white/5">
{testResult}
</pre>
</div>
)}
</div>
</CardBody>
</Card>
)
}

View File

@@ -1,5 +1,5 @@
import { appConfigDir } from "@tauri-apps/api/path"
import { setStoreCollectionPath } from "@tauri-store/valtio"
import { commands } from "@tauri-store/shared"
import { appStore } from "./app"
import { steamStore } from "./steam"
import { toolStore } from "./tool"
@@ -10,5 +10,6 @@ export async function init() {
await toolStore.start()
await steamStore.start()
const appConfigDirPath = await appConfigDir()
await setStoreCollectionPath(path.resolve(appConfigDirPath, "cstb"))
const setPath = commands.setStoreCollectionPath("valtio")
await setPath(path.resolve(appConfigDirPath, "cstb"))
}