supreme_replay_analyzer/
scanner.rs1use std::fs;
7use std::path::{Path, PathBuf};
8use std::sync::mpsc::{Receiver, Sender, channel};
9use std::thread;
10use supreme_fs::{DiscoverMode, GameStorage};
11
12use crate::AnalyzeResult;
13use crate::analyzer::{analyze_replay, analyze_replay_fast};
14use crate::level_data;
15
16#[derive(Debug, serde::Serialize)]
18pub struct ScanPerformance {
19 pub total_ms: f64,
20 pub discovery_ms: f64,
21 pub analysis_ms: f64,
22 pub file_count: usize,
23 pub timestamp: u64,
24 #[serde(rename = "_note")]
25 pub note: String,
26}
27
28pub struct ScanResult {
30 pub analysis_results: Vec<(PathBuf, Result<AnalyzeResult, String>)>,
32 pub background_receiver: Receiver<(PathBuf, AnalyzeResult)>,
34 pub performance: ScanPerformance,
36}
37
38#[must_use]
55pub fn scan_and_analyze_replays(storage: &GameStorage) -> ScanResult {
56 let start = std::time::Instant::now();
57 let data_dir = storage.game_path().join("data");
58
59 if !data_dir.exists() {
61 return ScanResult {
62 analysis_results: Vec::new(),
63 background_receiver: channel().1, performance: ScanPerformance {
65 total_ms: 0.0,
66 discovery_ms: 0.0,
67 analysis_ms: 0.0,
68 file_count: 0,
69 timestamp: current_timestamp(),
70 note: "Data directory not found".to_string(),
71 },
72 };
73 }
74
75 let level_data = level_data::get_levels();
77 let mut results = Vec::new();
78
79 let discovery_start = std::time::Instant::now();
81 let entries = storage
82 .discover_relative(Path::new("data/levels"), DiscoverMode::Files)
83 .unwrap_or_default()
84 .into_iter()
85 .filter(|path| path.extension().and_then(|s| s.to_str()) == Some("dat"))
86 .filter(|path| path.components().any(|c| c.as_os_str() == "replays"))
87 .collect::<Vec<_>>();
88 let discovery_ms = discovery_start.elapsed().as_secs_f64() * 1000.0;
89
90 let analysis_start = std::time::Instant::now();
92 let mut files_to_process = Vec::new();
93
94 for path in entries {
95 let result = match fs::read(&path) {
96 Ok(data) => {
97 let fast_res = analyze_replay_fast(&data, Some(level_data));
99
100 files_to_process.push(path.clone());
102
103 match fast_res {
104 Ok(analysis) => Ok(analysis),
105 Err(e) => Err(format!("Analysis error: {e}")),
106 }
107 }
108 Err(e) => Err(format!("Read error: {e}")),
109 };
110
111 results.push((path, result));
112 }
113 let analysis_ms = analysis_start.elapsed().as_secs_f64() * 1000.0;
114 let total_ms = start.elapsed().as_secs_f64() * 1000.0;
115
116 let (tx, rx) = channel();
118 spawn_background_analysis(files_to_process, tx);
119
120 results.sort_by(|a, b| a.0.cmp(&b.0));
122
123 let performance = ScanPerformance {
124 total_ms,
125 discovery_ms,
126 analysis_ms,
127 file_count: results.len(),
128 timestamp: current_timestamp(),
129 note: "Fast scan time only. Full analysis running in background.".to_string(),
130 };
131
132 ScanResult {
133 analysis_results: results,
134 background_receiver: rx,
135 performance,
136 }
137}
138
139pub fn write_performance_log(storage: &GameStorage, performance: &ScanPerformance) {
146 if let Err(e) = storage.write_report("replays_scan_perf", performance) {
147 eprintln!("Failed to write replay scan performance report: {e}");
148 }
149}
150
151fn spawn_background_analysis(files: Vec<PathBuf>, tx: Sender<(PathBuf, AnalyzeResult)>) {
153 thread::spawn(move || {
154 let level_data = level_data::get_levels();
155 for path in files {
156 if let Ok(data) = fs::read(&path)
157 && let Ok(full_analysis) = analyze_replay(&data, Some(level_data))
158 {
159 let _ = tx.send((path, full_analysis));
160 }
161 }
162 });
163}
164
165fn current_timestamp() -> u64 {
167 std::time::SystemTime::now()
168 .duration_since(std::time::UNIX_EPOCH)
169 .unwrap_or_default()
170 .as_secs()
171}