1use std::collections::HashMap;
34
35#[derive(Debug, Clone)]
37pub struct LoopBlock {
38 pub id: u64,
39 pub x: f32, pub y: f32, pub z: f32, }
43
44#[derive(Debug, Clone)]
46pub struct LoopInfo {
47 pub level: u32,
49 pub block_ids: Vec<u64>,
51 pub entry_block: u64,
53 pub has_nested_loops: bool,
55}
56
57#[derive(Debug, Clone)]
59pub struct LoopAnalysisResult {
60 pub loops: Vec<LoopInfo>,
62 pub max_depth: u32,
64 pub blocks_in_loops: usize,
66 pub blocks_outside_loops: usize,
68}
69
70pub fn detect_loops(blocks: &[LoopBlock]) -> Vec<LoopInfo> {
82 let mut level_blocks: HashMap<u32, Vec<&LoopBlock>> = HashMap::new();
84
85 for block in blocks {
86 let level = block.y as u32;
87 if level > 0 {
88 level_blocks.entry(level).or_default().push(block);
89 }
90 }
91
92 let mut loops: Vec<LoopInfo> = level_blocks
94 .into_iter()
95 .map(|(level, blocks_at_level)| {
96 let block_ids: Vec<u64> = blocks_at_level.iter().map(|b| b.id).collect();
97
98 let entry_block = blocks_at_level
100 .iter()
101 .min_by(|a, b| a.x.partial_cmp(&b.x).unwrap_or(std::cmp::Ordering::Equal))
102 .map(|b| b.id)
103 .unwrap_or(0);
104
105 let has_nested_loops = false; LoopInfo {
109 level,
110 block_ids,
111 entry_block,
112 has_nested_loops,
113 }
114 })
115 .collect();
116
117 loops.sort_by_key(|l| l.level);
119
120 let max_level = loops.iter().map(|l| l.level).max().unwrap_or(0);
122 for loop_info in &mut loops {
123 loop_info.has_nested_loops = loop_info.level < max_level;
124 }
125
126 loops
127}
128
129pub fn analyze_loops(blocks: &[LoopBlock]) -> LoopAnalysisResult {
133 let loops = detect_loops(blocks);
134
135 let max_depth = loops.iter().map(|l| l.level).max().unwrap_or(0);
136 let blocks_in_loops: usize = loops.iter().map(|l| l.block_ids.len()).sum();
137 let blocks_outside_loops = blocks.len() - blocks_in_loops;
138
139 LoopAnalysisResult {
140 loops,
141 max_depth,
142 blocks_in_loops,
143 blocks_outside_loops,
144 }
145}
146
147pub fn find_innermost_loop_for_block(block_id: u64, loops: &[LoopInfo]) -> Option<&LoopInfo> {
151 loops
152 .iter()
153 .filter(|l| l.block_ids.contains(&block_id))
154 .max_by_key(|l| l.level)
155}
156
157#[inline]
159pub fn is_in_loop(block: &LoopBlock) -> bool {
160 block.y > 0.0
161}
162
163#[inline]
165pub fn get_loop_depth(block: &LoopBlock) -> u32 {
166 block.y as u32
167}
168
169pub fn get_blocks_at_level(blocks: &[LoopBlock], level: u32) -> Vec<&LoopBlock> {
171 blocks.iter().filter(|b| (b.y as u32) == level).collect()
172}
173
174pub fn calculate_loop_complexity(loops: &[LoopInfo]) -> u32 {
179 loops
180 .iter()
181 .map(|l| l.level * l.block_ids.len() as u32)
182 .sum()
183}
184
185#[cfg(test)]
186mod tests {
187 use super::*;
188
189 #[test]
190 fn test_detect_loops_simple() {
191 let blocks = vec![
193 LoopBlock {
194 id: 0,
195 x: 0.0,
196 y: 0.0,
197 z: 0.0,
198 }, LoopBlock {
200 id: 1,
201 x: 1.0,
202 y: 1.0,
203 z: 1.0,
204 }, LoopBlock {
206 id: 2,
207 x: 2.0,
208 y: 1.0,
209 z: 0.0,
210 }, LoopBlock {
212 id: 3,
213 x: 3.0,
214 y: 0.0,
215 z: 0.0,
216 }, ];
218
219 let loops = detect_loops(&blocks);
220
221 assert_eq!(loops.len(), 1, "Should detect 1 loop");
222 assert_eq!(loops[0].level, 1, "Loop should be at level 1");
223 assert_eq!(loops[0].block_ids.len(), 2, "Loop should have 2 blocks");
224 assert!(loops[0].block_ids.contains(&1), "Should contain header");
225 assert!(loops[0].block_ids.contains(&2), "Should contain body");
226 }
227
228 #[test]
229 fn test_detect_loops_nested() {
230 let blocks = vec![
232 LoopBlock {
233 id: 0,
234 x: 0.0,
235 y: 0.0,
236 z: 0.0,
237 }, LoopBlock {
239 id: 1,
240 x: 1.0,
241 y: 1.0,
242 z: 1.0,
243 }, LoopBlock {
245 id: 2,
246 x: 2.0,
247 y: 1.0,
248 z: 0.0,
249 }, LoopBlock {
251 id: 3,
252 x: 3.0,
253 y: 2.0,
254 z: 1.0,
255 }, LoopBlock {
257 id: 4,
258 x: 4.0,
259 y: 2.0,
260 z: 0.0,
261 }, LoopBlock {
263 id: 5,
264 x: 5.0,
265 y: 0.0,
266 z: 0.0,
267 }, ];
269
270 let loops = detect_loops(&blocks);
271
272 assert_eq!(loops.len(), 2, "Should detect 2 loops");
273 assert_eq!(loops[0].level, 1, "First loop at level 1");
274 assert_eq!(loops[1].level, 2, "Second loop at level 2");
275 assert!(
276 loops[0].has_nested_loops,
277 "Outer loop should have nested loops"
278 );
279 assert!(
280 !loops[1].has_nested_loops,
281 "Inner loop should not have nested loops"
282 );
283 }
284
285 #[test]
286 fn test_detect_loops_multiple_at_same_level() {
287 let blocks = vec![
289 LoopBlock {
290 id: 0,
291 x: 0.0,
292 y: 0.0,
293 z: 0.0,
294 }, LoopBlock {
296 id: 1,
297 x: 1.0,
298 y: 1.0,
299 z: 1.0,
300 }, LoopBlock {
302 id: 2,
303 x: 2.0,
304 y: 1.0,
305 z: 0.0,
306 }, LoopBlock {
308 id: 3,
309 x: 3.0,
310 y: 0.0,
311 z: 0.0,
312 }, LoopBlock {
314 id: 4,
315 x: 4.0,
316 y: 1.0,
317 z: 1.0,
318 }, LoopBlock {
320 id: 5,
321 x: 5.0,
322 y: 1.0,
323 z: 0.0,
324 }, ];
326
327 let loops = detect_loops(&blocks);
328
329 assert_eq!(loops.len(), 1, "Should detect 1 loop level");
331 assert_eq!(loops[0].block_ids.len(), 4, "All level-1 blocks grouped");
332 }
333
334 #[test]
335 fn test_detect_loops_no_loops() {
336 let blocks = vec![
338 LoopBlock {
339 id: 0,
340 x: 0.0,
341 y: 0.0,
342 z: 0.0,
343 },
344 LoopBlock {
345 id: 1,
346 x: 1.0,
347 y: 0.0,
348 z: 1.0,
349 },
350 LoopBlock {
351 id: 2,
352 x: 2.0,
353 y: 0.0,
354 z: 0.0,
355 },
356 ];
357
358 let loops = detect_loops(&blocks);
359
360 assert_eq!(loops.len(), 0, "Should detect no loops");
361 }
362
363 #[test]
364 fn test_analyze_loops() {
365 let blocks = vec![
366 LoopBlock {
367 id: 0,
368 x: 0.0,
369 y: 0.0,
370 z: 0.0,
371 }, LoopBlock {
373 id: 1,
374 x: 1.0,
375 y: 1.0,
376 z: 1.0,
377 }, LoopBlock {
379 id: 2,
380 x: 2.0,
381 y: 1.0,
382 z: 0.0,
383 }, LoopBlock {
385 id: 3,
386 x: 3.0,
387 y: 2.0,
388 z: 0.0,
389 }, LoopBlock {
391 id: 4,
392 x: 4.0,
393 y: 0.0,
394 z: 0.0,
395 }, ];
397
398 let result = analyze_loops(&blocks);
399
400 assert_eq!(result.max_depth, 2, "Max depth should be 2");
401 assert_eq!(result.blocks_in_loops, 3, "3 blocks in loops");
402 assert_eq!(result.blocks_outside_loops, 2, "2 blocks outside loops");
403 assert_eq!(result.loops.len(), 2, "2 loop levels");
404 }
405
406 #[test]
407 fn test_is_in_loop() {
408 let in_loop = LoopBlock {
409 id: 0,
410 x: 1.0,
411 y: 1.0,
412 z: 0.0,
413 };
414 let not_in_loop = LoopBlock {
415 id: 1,
416 x: 0.0,
417 y: 0.0,
418 z: 0.0,
419 };
420
421 assert!(is_in_loop(&in_loop), "Block with y>0 should be in loop");
422 assert!(
423 !is_in_loop(¬_in_loop),
424 "Block with y=0 should not be in loop"
425 );
426 }
427
428 #[test]
429 fn test_get_loop_depth() {
430 let block = LoopBlock {
431 id: 0,
432 x: 0.0,
433 y: 3.0,
434 z: 0.0,
435 };
436 assert_eq!(get_loop_depth(&block), 3, "Loop depth should be 3");
437 }
438
439 #[test]
440 fn test_find_innermost_loop_for_block() {
441 let loops = vec![
442 LoopInfo {
443 level: 1,
444 block_ids: vec![1, 2, 3],
445 entry_block: 1,
446 has_nested_loops: true,
447 },
448 LoopInfo {
449 level: 2,
450 block_ids: vec![2, 3],
451 entry_block: 2,
452 has_nested_loops: false,
453 },
454 ];
455
456 let innermost = find_innermost_loop_for_block(2, &loops);
457 assert!(innermost.is_some(), "Should find loop for block 2");
458 assert_eq!(innermost.unwrap().level, 2, "Should return innermost loop");
459 }
460
461 #[test]
462 fn test_get_blocks_at_level() {
463 let blocks = vec![
464 LoopBlock {
465 id: 0,
466 x: 0.0,
467 y: 0.0,
468 z: 0.0,
469 },
470 LoopBlock {
471 id: 1,
472 x: 1.0,
473 y: 1.0,
474 z: 0.0,
475 },
476 LoopBlock {
477 id: 2,
478 x: 2.0,
479 y: 1.0,
480 z: 0.0,
481 },
482 LoopBlock {
483 id: 3,
484 x: 3.0,
485 y: 2.0,
486 z: 0.0,
487 },
488 ];
489
490 let level_1 = get_blocks_at_level(&blocks, 1);
491 assert_eq!(level_1.len(), 2, "Should have 2 blocks at level 1");
492
493 let level_2 = get_blocks_at_level(&blocks, 2);
494 assert_eq!(level_2.len(), 1, "Should have 1 block at level 2");
495 }
496
497 #[test]
498 fn test_loop_complexity() {
499 let loops = vec![
500 LoopInfo {
501 level: 1,
502 block_ids: vec![1, 2, 3],
503 entry_block: 1,
504 has_nested_loops: true,
505 },
506 LoopInfo {
507 level: 2,
508 block_ids: vec![2, 3],
509 entry_block: 2,
510 has_nested_loops: false,
511 },
512 ];
513
514 let complexity = calculate_loop_complexity(&loops);
516 assert_eq!(complexity, 7, "Loop complexity should be 7");
517 }
518
519 #[test]
520 fn test_loop_entry_block() {
521 let blocks = vec![
523 LoopBlock {
524 id: 0,
525 x: 5.0,
526 y: 1.0,
527 z: 0.0,
528 },
529 LoopBlock {
530 id: 1,
531 x: 2.0,
532 y: 1.0,
533 z: 0.0,
534 }, LoopBlock {
536 id: 2,
537 x: 8.0,
538 y: 1.0,
539 z: 0.0,
540 },
541 ];
542
543 let loops = detect_loops(&blocks);
544 assert_eq!(loops[0].entry_block, 1, "Entry should be block with min X");
545 }
546}