11use super :: { CodeChunk , ProgressReporter } ;
22use crate :: game:: Challenge ;
3+ use rayon:: prelude:: * ;
4+ use std:: sync:: {
5+ atomic:: { AtomicUsize , Ordering } ,
6+ Arc ,
7+ } ;
38use uuid:: Uuid ;
49
510struct NoOpProgressReporter ;
@@ -44,35 +49,40 @@ impl ChallengeConverter {
4449 chunks : Vec < CodeChunk > ,
4550 progress : & dyn ProgressReporter ,
4651 ) -> Vec < Challenge > {
47- let mut all_challenges = Vec :: new ( ) ;
48- let total_chunks = chunks. len ( ) ;
49-
50- for ( chunk_index , chunk ) in chunks . iter ( ) . enumerate ( ) {
51- // Update progress
52- let chunk_progress = chunk_index as f64 / total_chunks as f64 ;
53- progress . set_progress ( chunk_progress ) ;
54- progress . set_file_counts ( chunk_index , total_chunks ) ;
55-
56- // Generate challenges for Easy (~100), Normal (~200), Hard (~500), Wild (full chunks) only
57- let difficulties = [
58- super :: super :: game :: stage_builder :: DifficultyLevel :: Easy ,
59- super :: super :: game :: stage_builder :: DifficultyLevel :: Normal ,
60- super :: super :: game :: stage_builder :: DifficultyLevel :: Hard ,
61- super :: super :: game :: stage_builder :: DifficultyLevel :: Wild ,
62- ] ;
63-
64- for difficulty in & difficulties {
65- let split_challenges = self . split_chunk_by_difficulty ( chunk , difficulty ) ;
66- all_challenges . extend ( split_challenges ) ;
67- }
68- }
52+ // Parallelize per-chunk conversion with atomic progress updates
53+ let total = chunks. len ( ) ;
54+ let processed = Arc :: new ( AtomicUsize :: new ( 0 ) ) ;
55+
56+ let difficulties = [
57+ super :: super :: game :: stage_builder :: DifficultyLevel :: Easy ,
58+ super :: super :: game :: stage_builder :: DifficultyLevel :: Normal ,
59+ super :: super :: game :: stage_builder :: DifficultyLevel :: Hard ,
60+ super :: super :: game :: stage_builder :: DifficultyLevel :: Wild ,
61+ ] ;
62+
63+ // Initialize progress bounds (0 processed yet)
64+ progress . set_file_counts ( 0 , total ) ;
65+
66+ let all : Vec < Challenge > = chunks
67+ . into_par_iter ( )
68+ . flat_map ( |chunk| {
69+ let mut local = Vec :: new ( ) ;
70+ for difficulty in & difficulties {
71+ let split = self . split_chunk_by_difficulty ( & chunk , difficulty ) ;
72+ local . extend ( split ) ;
73+ }
6974
70- // Final progress update
75+ // Track progress count without calling reporter from parallel context
76+ let _ = processed. fetch_add ( 1 , Ordering :: Relaxed ) ;
77+ local
78+ } )
79+ . collect ( ) ;
80+
81+ // Final progress update only (avoid Sync bound on ProgressReporter)
82+ progress. set_file_counts ( total, total) ;
7183 progress. set_progress ( 1.0 ) ;
72- progress. set_file_counts ( total_chunks, total_chunks) ;
7384
74- // Zen challenges are now handled separately in main.rs
75- all_challenges
85+ all
7686 }
7787
7888 pub fn convert_with_filter < F > ( & self , chunks : Vec < CodeChunk > , filter : F ) -> Vec < Challenge >
@@ -91,40 +101,35 @@ impl ChallengeConverter {
91101 chunks : Vec < CodeChunk > ,
92102 difficulty : & super :: super :: game:: stage_builder:: DifficultyLevel ,
93103 ) -> Vec < Challenge > {
94- let mut challenges = Vec :: new ( ) ;
95-
96- for chunk in chunks {
97- let split_challenges = self . split_chunk_by_difficulty ( & chunk, difficulty) ;
98- challenges. extend ( split_challenges) ;
99- }
100-
101- challenges
104+ chunks
105+ . into_par_iter ( )
106+ . flat_map ( |chunk| self . split_chunk_by_difficulty ( & chunk, difficulty) )
107+ . collect ( )
102108 }
103109
104110 pub fn convert_whole_files_to_challenges (
105111 & self ,
106112 file_paths : Vec < std:: path:: PathBuf > ,
107113 ) -> Vec < Challenge > {
108114 use super :: super :: game:: stage_builder:: DifficultyLevel ;
109- let mut challenges = Vec :: new ( ) ;
110-
111- for file_path in file_paths {
112- if let Ok ( content) = std:: fs:: read_to_string ( & file_path) {
115+ file_paths
116+ . into_par_iter ( )
117+ . filter_map ( |file_path| {
118+ std:: fs:: read_to_string ( & file_path)
119+ . ok ( )
120+ . map ( |c| ( file_path, c) )
121+ } )
122+ . map ( |( file_path, content) | {
113123 let id = Uuid :: new_v4 ( ) . to_string ( ) ;
114124 let language = super :: Language :: detect_from_path ( & file_path) ;
115125 let file_path_str = file_path. to_string_lossy ( ) . to_string ( ) ;
116-
117126 let line_count = content. lines ( ) . count ( ) ;
118- let challenge = Challenge :: new ( id, content)
127+ Challenge :: new ( id, content)
119128 . with_source_info ( file_path_str, 1 , line_count)
120129 . with_language ( language)
121- . with_difficulty_level ( DifficultyLevel :: Zen ) ;
122-
123- challenges. push ( challenge) ;
124- }
125- }
126-
127- challenges
130+ . with_difficulty_level ( DifficultyLevel :: Zen )
131+ } )
132+ . collect ( )
128133 }
129134
130135 fn split_chunk_by_difficulty (
@@ -336,24 +341,23 @@ impl ChallengeConverter {
336341 & self ,
337342 file_paths : Vec < std:: path:: PathBuf > ,
338343 ) -> Vec < Challenge > {
339- let mut zen_challenges = Vec :: new ( ) ;
340-
341- for file_path in file_paths {
342- if let Ok ( content) = std:: fs:: read_to_string ( & file_path) {
344+ file_paths
345+ . into_par_iter ( )
346+ . filter_map ( |file_path| {
347+ std:: fs:: read_to_string ( & file_path)
348+ . ok ( )
349+ . map ( |c| ( file_path, c) )
350+ } )
351+ . map ( |( file_path, content) | {
343352 let id = uuid:: Uuid :: new_v4 ( ) . to_string ( ) ;
344353 let language = super :: Language :: detect_from_path ( & file_path) ;
345354 let file_path_str = file_path. to_string_lossy ( ) . to_string ( ) ;
346-
347355 let line_count = content. lines ( ) . count ( ) ;
348- let challenge = Challenge :: new ( id, content)
356+ Challenge :: new ( id, content)
349357 . with_source_info ( file_path_str, 1 , line_count)
350358 . with_language ( language)
351- . with_difficulty_level ( super :: super :: game:: stage_builder:: DifficultyLevel :: Zen ) ;
352-
353- zen_challenges. push ( challenge) ;
354- }
355- }
356-
357- zen_challenges
359+ . with_difficulty_level ( super :: super :: game:: stage_builder:: DifficultyLevel :: Zen )
360+ } )
361+ . collect ( )
358362 }
359363}
0 commit comments