1use rayon::prelude::*;
7use rayon::{ThreadPool, ThreadPoolBuilder};
8use std::sync::{Arc, Mutex, OnceLock};
9use threecrate_core::Result;
10
11static GLOBAL_THREAD_POOL: OnceLock<Arc<ThreadPool>> = OnceLock::new();
13static THREAD_POOL_CONFIG: Mutex<ThreadPoolConfig> = Mutex::new(ThreadPoolConfig::new());
14
15#[derive(Debug, Clone)]
17pub struct ThreadPoolConfig {
18 pub num_threads: Option<usize>,
20 pub stack_size: Option<usize>,
22 pub thread_name_prefix: String,
24 pub enabled: bool,
26 pub min_chunk_size: usize,
28 pub max_chunk_size: usize,
30 pub adaptive_chunks: bool,
32}
33
34impl ThreadPoolConfig {
35 const fn new() -> Self {
37 Self {
38 num_threads: None,
39 stack_size: None,
40 thread_name_prefix: String::new(),
41 enabled: true,
42 min_chunk_size: 100,
43 max_chunk_size: 10000,
44 adaptive_chunks: true,
45 }
46 }
47
48 pub fn default() -> Self {
50 Self {
51 num_threads: None,
52 stack_size: Some(8 * 1024 * 1024), thread_name_prefix: "threecrate-recon".to_string(),
54 enabled: true,
55 min_chunk_size: 100,
56 max_chunk_size: 10000,
57 adaptive_chunks: true,
58 }
59 }
60
61 pub fn with_threads(mut self, num_threads: usize) -> Self {
63 self.num_threads = Some(num_threads);
64 self
65 }
66
67 pub fn with_stack_size(mut self, stack_size: usize) -> Self {
69 self.stack_size = Some(stack_size);
70 self
71 }
72
73 pub fn with_enabled(mut self, enabled: bool) -> Self {
75 self.enabled = enabled;
76 self
77 }
78
79 pub fn with_chunk_size_range(mut self, min: usize, max: usize) -> Self {
81 self.min_chunk_size = min;
82 self.max_chunk_size = max;
83 self
84 }
85
86 pub fn with_adaptive_chunks(mut self, adaptive: bool) -> Self {
88 self.adaptive_chunks = adaptive;
89 self
90 }
91}
92
93pub fn init_thread_pool(config: ThreadPoolConfig) -> Result<()> {
95 if GLOBAL_THREAD_POOL.get().is_some() {
96 return Ok(()); }
98
99 let mut builder = ThreadPoolBuilder::new();
100
101 if let Some(num_threads) = config.num_threads {
102 builder = builder.num_threads(num_threads);
103 }
104
105 if let Some(stack_size) = config.stack_size {
106 builder = builder.stack_size(stack_size);
107 }
108
109 if !config.thread_name_prefix.is_empty() {
110 let prefix = config.thread_name_prefix.clone();
111 builder = builder.thread_name(move |index| format!("{}-{}", prefix, index));
112 }
113
114 let pool = builder.build().map_err(|e| {
115 threecrate_core::Error::Algorithm(format!("Failed to create thread pool: {}", e))
116 })?;
117
118 if let Ok(mut global_config) = THREAD_POOL_CONFIG.lock() {
120 *global_config = config;
121 }
122
123 GLOBAL_THREAD_POOL.set(Arc::new(pool)).map_err(|_| {
124 threecrate_core::Error::Algorithm("Thread pool already initialized".to_string())
125 })?;
126
127 Ok(())
128}
129
130pub fn get_thread_pool() -> Arc<ThreadPool> {
132 GLOBAL_THREAD_POOL
133 .get_or_init(|| {
134 let config = ThreadPoolConfig::default();
135 let pool = ThreadPoolBuilder::new()
136 .num_threads(config.num_threads.unwrap_or_else(num_cpus::get))
137 .stack_size(config.stack_size.unwrap_or(8 * 1024 * 1024))
138 .thread_name(|index| format!("threecrate-recon-{}", index))
139 .build()
140 .expect("Failed to create default thread pool");
141 Arc::new(pool)
142 })
143 .clone()
144}
145
146pub fn get_config() -> ThreadPoolConfig {
148 THREAD_POOL_CONFIG
149 .lock()
150 .map(|config| config.clone())
151 .unwrap_or_else(|_| ThreadPoolConfig::default())
152}
153
154pub fn is_parallel_enabled() -> bool {
156 get_config().enabled
157}
158
159pub fn compute_chunk_size(data_size: usize) -> usize {
161 let config = get_config();
162
163 if !config.adaptive_chunks {
164 return config
165 .min_chunk_size
166 .max(data_size / num_cpus::get())
167 .min(config.max_chunk_size);
168 }
169
170 let num_threads = get_thread_pool().current_num_threads();
171 let base_chunk_size = data_size / (num_threads * 4); base_chunk_size
174 .max(config.min_chunk_size)
175 .min(config.max_chunk_size)
176}
177
178pub fn execute_parallel<F, R>(op: F) -> R
180where
181 F: FnOnce() -> R + Send,
182 R: Send,
183{
184 if is_parallel_enabled() {
185 get_thread_pool().install(op)
186 } else {
187 op()
188 }
189}
190
191pub fn parallel_map<T, U, F>(data: &[T], f: F) -> Vec<U>
193where
194 T: Sync,
195 U: Send,
196 F: Fn(&T) -> U + Sync + Send,
197{
198 if !is_parallel_enabled() || data.len() < get_config().min_chunk_size {
199 return data.iter().map(f).collect();
200 }
201
202 execute_parallel(|| data.par_iter().map(f).collect())
203}
204
205pub fn parallel_map_indexed<T, U, F>(data: &[T], f: F) -> Vec<U>
207where
208 T: Sync,
209 U: Send,
210 F: Fn(usize, &T) -> U + Sync + Send,
211{
212 if !is_parallel_enabled() || data.len() < get_config().min_chunk_size {
213 return data.iter().enumerate().map(|(i, x)| f(i, x)).collect();
214 }
215
216 execute_parallel(|| data.par_iter().enumerate().map(|(i, x)| f(i, x)).collect())
217}
218
219pub fn parallel_filter<T, F>(data: &[T], predicate: F) -> Vec<T>
221where
222 T: Clone + Sync + Send,
223 F: Fn(&T) -> bool + Sync + Send,
224{
225 if !is_parallel_enabled() || data.len() < get_config().min_chunk_size {
226 return data.iter().filter(|x| predicate(*x)).cloned().collect();
227 }
228
229 execute_parallel(|| data.par_iter().filter(|x| predicate(*x)).cloned().collect())
230}
231
232pub fn parallel_reduce<T, U, F, R>(data: &[T], identity: U, map_op: F, reduce_op: R) -> U
234where
235 T: Sync,
236 U: Clone + Send + Sync,
237 F: Fn(&T) -> U + Sync + Send,
238 R: Fn(U, U) -> U + Sync + Send,
239{
240 if !is_parallel_enabled() || data.len() < get_config().min_chunk_size {
241 return data.iter().map(map_op).fold(identity, reduce_op);
242 }
243
244 execute_parallel(|| {
245 data.par_iter()
246 .map(map_op)
247 .reduce(|| identity.clone(), reduce_op)
248 })
249}
250
251pub mod point_cloud {
253 use super::*;
254 use threecrate_core::Point3f;
255
256 pub fn parallel_compute_normals<F>(
258 points: &[Point3f],
259 radius: f32,
260 compute_normal: F,
261 ) -> Vec<Point3f>
262 where
263 F: Fn(&Point3f, &[Point3f], f32) -> Point3f + Sync + Send,
264 {
265 parallel_map(points, |point| {
266 let neighbors: Vec<Point3f> = points
268 .iter()
269 .filter(|p| (*p - *point).magnitude() <= radius)
270 .cloned()
271 .collect();
272
273 compute_normal(point, &neighbors, radius)
274 })
275 }
276
277 pub fn parallel_point_distances(points1: &[Point3f], points2: &[Point3f]) -> Vec<f32> {
279 parallel_map(points1, |p1| {
280 points2
281 .iter()
282 .map(|p2| (p1 - p2).magnitude())
283 .fold(f32::INFINITY, f32::min)
284 })
285 }
286
287 pub fn parallel_bounding_box(points: &[Point3f]) -> Option<(Point3f, Point3f)> {
289 if points.is_empty() {
290 return None;
291 }
292
293 let (min_vals, max_vals) = parallel_reduce(
294 points,
295 (
296 Point3f::new(f32::INFINITY, f32::INFINITY, f32::INFINITY),
297 Point3f::new(f32::NEG_INFINITY, f32::NEG_INFINITY, f32::NEG_INFINITY),
298 ),
299 |point| (*point, *point),
300 |(min1, max1), (min2, max2)| {
301 (
302 Point3f::new(min1.x.min(min2.x), min1.y.min(min2.y), min1.z.min(min2.z)),
303 Point3f::new(max1.x.max(max2.x), max1.y.max(max2.y), max1.z.max(max2.z)),
304 )
305 },
306 );
307
308 Some((min_vals, max_vals))
309 }
310}
311
312pub mod mesh {
314 use super::*;
315 use threecrate_core::Point3f;
316
317 pub fn parallel_triangle_normals(
319 vertices: &[Point3f],
320 faces: &[[usize; 3]],
321 ) -> Vec<nalgebra::Vector3<f32>> {
322 parallel_map(faces, |face| {
323 let v1 = &vertices[face[0]];
324 let v2 = &vertices[face[1]];
325 let v3 = &vertices[face[2]];
326
327 let edge1 = v2 - v1;
328 let edge2 = v3 - v1;
329 let normal = edge1.cross(&edge2).normalize();
330
331 nalgebra::Vector3::new(normal.x, normal.y, normal.z)
332 })
333 }
334
335 pub fn parallel_vertex_normals(
337 vertices: &[Point3f],
338 faces: &[[usize; 3]],
339 ) -> Vec<nalgebra::Vector3<f32>> {
340 let triangle_normals = parallel_triangle_normals(vertices, faces);
341
342 parallel_map_indexed(vertices, |vertex_idx, _vertex| {
343 let mut normal = nalgebra::Vector3::zeros();
344 let mut weight_sum = 0.0f32;
345
346 for (face_idx, face) in faces.iter().enumerate() {
347 if face.contains(&vertex_idx) {
348 let face_normal = triangle_normals[face_idx];
350
351 let local_idx = face.iter().position(|&v| v == vertex_idx).unwrap();
353 let v1_idx = face[local_idx];
354 let v2_idx = face[(local_idx + 1) % 3];
355 let v3_idx = face[(local_idx + 2) % 3];
356
357 let v1 = &vertices[v1_idx];
358 let v2 = &vertices[v2_idx];
359 let v3 = &vertices[v3_idx];
360
361 let edge1 = (v2 - v1).normalize();
363 let edge2 = (v3 - v1).normalize();
364 let angle = edge1.dot(&edge2).clamp(-1.0, 1.0).acos();
365
366 normal += face_normal * angle;
367 weight_sum += angle;
368 }
369 }
370
371 if weight_sum > 1e-6 {
372 normal / weight_sum
373 } else {
374 nalgebra::Vector3::new(0.0, 0.0, 1.0)
375 }
376 })
377 }
378}
379
380#[cfg(test)]
406mod tests {
407 use super::*;
408 use threecrate_core::Point3f;
409
410 #[test]
411 fn test_thread_pool_config() {
412 let config = ThreadPoolConfig::default()
413 .with_threads(4)
414 .with_stack_size(16 * 1024 * 1024)
415 .with_enabled(true);
416
417 assert_eq!(config.num_threads, Some(4));
418 assert_eq!(config.stack_size, Some(16 * 1024 * 1024));
419 assert!(config.enabled);
420 }
421
422 #[test]
423 fn test_chunk_size_computation() {
424 let data_size = 10000;
425 let chunk_size = compute_chunk_size(data_size);
426 let config = get_config();
427
428 assert!(chunk_size >= config.min_chunk_size);
429 assert!(chunk_size <= config.max_chunk_size);
430 }
431
432 #[test]
433 fn test_parallel_map() {
434 let data = vec![1, 2, 3, 4, 5];
435 let result = parallel_map(&data, |x| x * 2);
436 assert_eq!(result, vec![2, 4, 6, 8, 10]);
437 }
438
439 #[test]
440 fn test_parallel_filter() {
441 let data = vec![1, 2, 3, 4, 5, 6];
442 let result = parallel_filter(&data, |x| *x % 2 == 0);
443 assert_eq!(result, vec![2, 4, 6]);
444 }
445
446 #[test]
447 fn test_parallel_reduce() {
448 let data = vec![1, 2, 3, 4, 5];
449 let sum = parallel_reduce(&data, 0, |x| *x, |a, b| a + b);
450 assert_eq!(sum, 15);
451 }
452
453 #[test]
454 fn test_point_cloud_bounding_box() {
455 let points = vec![
456 Point3f::new(0.0, 0.0, 0.0),
457 Point3f::new(1.0, 1.0, 1.0),
458 Point3f::new(-1.0, -1.0, -1.0),
459 Point3f::new(2.0, 0.5, -0.5),
460 ];
461
462 let (min_pt, max_pt) = point_cloud::parallel_bounding_box(&points).unwrap();
463
464 assert_eq!(min_pt.x, -1.0);
465 assert_eq!(min_pt.y, -1.0);
466 assert_eq!(min_pt.z, -1.0);
467 assert_eq!(max_pt.x, 2.0);
468 assert_eq!(max_pt.y, 1.0);
469 assert_eq!(max_pt.z, 1.0);
470 }
471}