use chess::Board;
use indicatif::{ProgressBar, ProgressStyle};
use rayon::prelude::*;
use std::collections::HashSet;
use std::fs::File;
use std::path::Path;
use std::sync::{Arc, Mutex};
pub struct UltraFastLoader {
pub loaded_count: usize,
pub duplicate_count: usize,
pub error_count: usize,
batch_size: usize,
#[allow(dead_code)]
use_bloom_filter: bool,
}
impl UltraFastLoader {
pub fn new_for_massive_datasets() -> Self {
Self {
loaded_count: 0,
duplicate_count: 0,
error_count: 0,
batch_size: 50000, use_bloom_filter: true,
}
}
pub fn ultra_load_binary<P: AsRef<Path>>(
&mut self,
path: P,
engine: &mut crate::ChessVectorEngine,
) -> Result<(), Box<dyn std::error::Error>> {
let path_ref = path.as_ref();
println!("Operation complete");
let file_size = std::fs::metadata(path_ref)?.len();
println!("📊 File size: {:.1} MB", file_size as f64 / 1_000_000.0);
if file_size > 500_000_000 {
println!("⚡ Large file detected - using memory-mapped loading");
return self.memory_mapped_load(path_ref, engine);
}
let data = std::fs::read(path_ref)?;
let decompressed_data = if let Ok(decompressed) = lz4_flex::decompress_size_prepended(&data)
{
println!(
"🗜️ LZ4 decompressed: {} → {} bytes",
data.len(),
decompressed.len()
);
decompressed
} else {
data
};
let positions: Vec<(String, f32)> = match bincode::deserialize(&decompressed_data) {
Ok(pos) => pos,
Err(e) => {
println!("Operation complete");
return Err(e.into());
}
};
let total_positions = positions.len();
println!("📦 Loaded {total_positions} positions from binary");
if total_positions == 0 {
return Ok(());
}
if total_positions > 100_000 {
self.parallel_batch_load(positions, engine)
} else {
self.sequential_load(positions, engine)
}
}
fn memory_mapped_load<P: AsRef<Path>>(
&mut self,
path: P,
engine: &mut crate::ChessVectorEngine,
) -> Result<(), Box<dyn std::error::Error>> {
use memmap2::Mmap;
let file = File::open(path)?;
let mmap = unsafe { Mmap::map(&file)? };
println!("🗺️ Memory-mapped {} bytes", mmap.len());
const CHUNK_SIZE: usize = 50_000_000; let total_chunks = mmap.len().div_ceil(CHUNK_SIZE);
println!("📦 Processing {total_chunks} chunks of ~50MB each");
self.stream_parse_memory_mapped(&mmap, engine)
}
fn stream_parse_memory_mapped(
&mut self,
mmap: &memmap2::Mmap,
engine: &mut crate::ChessVectorEngine,
) -> Result<(), Box<dyn std::error::Error>> {
if let Ok(decompressed) = lz4_flex::decompress_size_prepended(mmap) {
println!("🗜️ Full file LZ4 decompressed");
return self.parse_decompressed_data(&decompressed, engine);
}
if let Ok(positions) = bincode::deserialize::<Vec<(String, f32)>>(mmap) {
println!("📦 Direct memory-mapped deserialization");
return self.parallel_batch_load(positions, engine);
}
if let Ok(text) = std::str::from_utf8(mmap) {
println!("📝 Treating as text format");
return self.parse_text_data(text, engine);
}
Err("Unable to parse memory-mapped file in any known format".into())
}
fn parse_decompressed_data(
&mut self,
data: &[u8],
engine: &mut crate::ChessVectorEngine,
) -> Result<(), Box<dyn std::error::Error>> {
let positions: Vec<(String, f32)> = bincode::deserialize(data)?;
self.parallel_batch_load(positions, engine)
}
fn parse_text_data(
&mut self,
text: &str,
engine: &mut crate::ChessVectorEngine,
) -> Result<(), Box<dyn std::error::Error>> {
println!("📝 Parsing text data...");
let lines: Vec<&str> = text.lines().collect();
let total_lines = lines.len();
if total_lines == 0 {
return Ok(());
}
println!("📊 Processing {total_lines} lines");
let pb = ProgressBar::new(total_lines as u64);
pb.set_style(
ProgressStyle::default_bar()
.template("⚡ Parsing [{elapsed_precise}] [{bar:40.green/blue}] {pos}/{len} ({percent}%) {msg}")?
.progress_chars("██░")
);
let batch_size = 10000;
let existing_boards: HashSet<Board> = engine.position_boards.iter().cloned().collect();
let existing_boards = Arc::new(existing_boards);
let results: Arc<Mutex<Vec<(Board, f32)>>> = Arc::new(Mutex::new(Vec::new()));
lines
.par_chunks(batch_size)
.enumerate()
.for_each(|(chunk_idx, chunk)| {
let mut local_results = Vec::new();
for (line_idx, line) in chunk.iter().enumerate() {
if line.trim().is_empty() {
continue;
}
if let Ok(json) = serde_json::from_str::<serde_json::Value>(line) {
if let Some((board, eval)) = self.extract_from_json(&json) {
if !existing_boards.contains(&board) {
local_results.push((board, eval));
}
}
}
if line_idx % 1000 == 0 {
pb.set_position((chunk_idx * batch_size + line_idx) as u64);
}
}
if !local_results.is_empty() {
if let Ok(mut results) = results.lock() {
results.extend(local_results);
}
}
});
pb.finish_with_message("✅ Text parsing complete");
let final_results = Arc::try_unwrap(results).unwrap().into_inner().unwrap();
self.loaded_count = final_results.len();
println!("📦 Parsed {} valid positions", self.loaded_count);
for (board, eval) in final_results {
engine.add_position(&board, eval);
}
Ok(())
}
fn extract_from_json(&self, json: &serde_json::Value) -> Option<(Board, f32)> {
if let (Some(fen), Some(eval)) = (
json.get("fen").and_then(|v| v.as_str()),
json.get("evaluation").and_then(|v| v.as_f64()),
) {
if let Ok(board) = fen.parse::<Board>() {
return Some((board, eval as f32));
}
}
None
}
fn parallel_batch_load(
&mut self,
positions: Vec<(String, f32)>,
engine: &mut crate::ChessVectorEngine,
) -> Result<(), Box<dyn std::error::Error>> {
let total_positions = positions.len();
println!("🔄 Parallel batch loading {total_positions} positions");
let pb = ProgressBar::new(total_positions as u64);
pb.set_style(
ProgressStyle::default_bar()
.template("⚡ Loading [{elapsed_precise}] [{bar:40.cyan/blue}] {pos}/{len} ({percent}%) {msg}")?
.progress_chars("██░")
);
let existing_boards: HashSet<Board> = engine.position_boards.iter().cloned().collect();
let chunk_size = self.batch_size;
let chunks: Vec<_> = positions.chunks(chunk_size).collect();
let mut total_loaded = 0;
let mut total_duplicates = 0;
for (chunk_idx, chunk) in chunks.iter().enumerate() {
let mut batch_boards = Vec::new();
let mut batch_evaluations = Vec::new();
for (fen, evaluation) in chunk.iter() {
match fen.parse::<Board>() {
Ok(board) => {
if !existing_boards.contains(&board) {
batch_boards.push(board);
batch_evaluations.push(*evaluation);
} else {
total_duplicates += 1;
}
}
Err(_) => {
self.error_count += 1;
}
}
}
for (board, eval) in batch_boards.iter().zip(batch_evaluations.iter()) {
engine.add_position(board, *eval);
total_loaded += 1;
}
pb.set_position(((chunk_idx + 1) * chunk_size).min(total_positions) as u64);
pb.set_message(format!("{total_loaded} loaded, {total_duplicates} dupes"));
}
pb.finish_with_message(format!("✅ Loaded {total_loaded} positions"));
self.loaded_count = total_loaded;
self.duplicate_count = total_duplicates;
println!("📊 Final stats:");
println!(" Loaded: {count} positions", count = self.loaded_count);
println!("Operation complete");
println!("Operation complete");
Ok(())
}
fn sequential_load(
&mut self,
positions: Vec<(String, f32)>,
engine: &mut crate::ChessVectorEngine,
) -> Result<(), Box<dyn std::error::Error>> {
println!("📦 Sequential loading {} positions", positions.len());
let existing_boards: HashSet<Board> = engine.position_boards.iter().cloned().collect();
for (fen, evaluation) in positions {
match fen.parse::<Board>() {
Ok(board) => {
if !existing_boards.contains(&board) {
engine.add_position(&board, evaluation);
self.loaded_count += 1;
} else {
self.duplicate_count += 1;
}
}
Err(_) => {
self.error_count += 1;
}
}
}
Ok(())
}
pub fn get_stats(&self) -> LoadingStats {
LoadingStats {
loaded: self.loaded_count,
duplicates: self.duplicate_count,
errors: self.error_count,
total_processed: self.loaded_count + self.duplicate_count + self.error_count,
}
}
}
#[derive(Debug, Clone)]
pub struct LoadingStats {
pub loaded: usize,
pub duplicates: usize,
pub errors: usize,
pub total_processed: usize,
}
impl LoadingStats {
pub fn success_rate(&self) -> f64 {
if self.total_processed == 0 {
return 1.0;
}
self.loaded as f64 / self.total_processed as f64
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ultra_fast_loader_creation() {
let loader = UltraFastLoader::new_for_massive_datasets();
assert_eq!(loader.loaded_count, 0);
assert_eq!(loader.batch_size, 50000);
assert!(loader.use_bloom_filter);
}
#[test]
fn test_loading_stats() {
let mut loader = UltraFastLoader::new_for_massive_datasets();
loader.loaded_count = 8000;
loader.duplicate_count = 1500;
loader.error_count = 500;
let stats = loader.get_stats();
assert_eq!(stats.loaded, 8000);
assert_eq!(stats.total_processed, 10000);
assert_eq!(stats.success_rate(), 0.8);
}
}