1use rand::prelude::*;
21use rand::rngs::ThreadRng;
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
25pub enum SimulationMode {
26 #[default]
28 Standard,
29
30 CellByCell,
33
34 RowByRow,
37
38 ChaoticParallel,
41
42 SynchronizedParallel,
45
46 ActorBased,
49}
50
51impl std::fmt::Display for SimulationMode {
52 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53 match self {
54 SimulationMode::Standard => write!(f, "Standard"),
55 SimulationMode::CellByCell => write!(f, "Cell-by-Cell (1950s)"),
56 SimulationMode::RowByRow => write!(f, "Row-by-Row (1970s)"),
57 SimulationMode::ChaoticParallel => write!(f, "Chaotic (1990s)"),
58 SimulationMode::SynchronizedParallel => write!(f, "Barrier Sync (2000s)"),
59 SimulationMode::ActorBased => write!(f, "Actor/HLC (Modern)"),
60 }
61 }
62}
63
64impl SimulationMode {
65 pub fn all() -> &'static [SimulationMode] {
67 &[
68 SimulationMode::Standard,
69 SimulationMode::CellByCell,
70 SimulationMode::RowByRow,
71 SimulationMode::ChaoticParallel,
72 SimulationMode::SynchronizedParallel,
73 SimulationMode::ActorBased,
74 ]
75 }
76
77 pub fn description(&self) -> &'static str {
79 match self {
80 SimulationMode::Standard => "Full-speed parallel simulation",
81 SimulationMode::CellByCell => "1950s: Sequential processing, one cell at a time",
82 SimulationMode::RowByRow => {
83 "1970s: Vector processing, entire rows at once (Cray-style)"
84 }
85 SimulationMode::ChaoticParallel => "1990s: Parallel without sync - watch for glitches!",
86 SimulationMode::SynchronizedParallel => "2000s: Parallel with barriers - safe but slow",
87 SimulationMode::ActorBased => "Modern: Actor model with HLC for causal ordering",
88 }
89 }
90
91 pub fn shows_processing(&self) -> bool {
93 matches!(
94 self,
95 SimulationMode::CellByCell
96 | SimulationMode::RowByRow
97 | SimulationMode::ChaoticParallel
98 | SimulationMode::SynchronizedParallel
99 | SimulationMode::ActorBased
100 )
101 }
102}
103
104#[derive(Debug, Clone)]
106pub struct StepResult {
107 pub step_complete: bool,
109 pub should_swap: bool,
111}
112
113impl StepResult {
114 fn in_progress() -> Self {
115 Self {
116 step_complete: false,
117 should_swap: false,
118 }
119 }
120
121 fn complete() -> Self {
122 Self {
123 step_complete: true,
124 should_swap: true,
125 }
126 }
127}
128
129#[derive(Debug, Clone)]
131pub struct ProcessingState {
132 pub current_cell: Option<(u32, u32)>,
134
135 pub current_row: Option<u32>,
137
138 pub active_cells: Vec<(u32, u32)>,
140
141 pub just_processed: Vec<(u32, u32)>,
143
144 pub active_tiles: Vec<(u32, u32)>,
146
147 pub step_complete: bool,
149
150 pub hlc_tick: u64,
152
153 pub messages_in_flight: Vec<TileMessage>,
155}
156
157#[derive(Debug, Clone)]
159pub struct TileMessage {
160 pub from: (u32, u32),
162 pub to: (u32, u32),
164 pub timestamp: u64,
166}
167
168impl Default for ProcessingState {
169 fn default() -> Self {
170 Self {
171 current_cell: None,
172 current_row: None,
173 active_cells: Vec::new(),
174 just_processed: Vec::new(),
175 active_tiles: Vec::new(),
176 step_complete: true,
177 hlc_tick: 0,
178 messages_in_flight: Vec::new(),
179 }
180 }
181}
182
183impl ProcessingState {
184 pub fn begin_step(&mut self) {
186 self.current_cell = None;
187 self.current_row = None;
188 self.active_cells.clear();
189 self.just_processed.clear();
190 self.active_tiles.clear();
191 self.step_complete = false;
192 }
193
194 pub fn complete_step(&mut self) {
196 self.step_complete = true;
197 self.hlc_tick += 1;
198 }
199}
200
201pub struct EducationalProcessor {
203 pub state: ProcessingState,
205
206 pub mode: SimulationMode,
208
209 pub cells_per_frame: usize,
211
212 rng: ThreadRng,
214
215 cell_x: u32,
217 cell_y: u32,
218
219 tile_size: u32,
221}
222
223impl Default for EducationalProcessor {
224 fn default() -> Self {
225 Self {
226 state: ProcessingState::default(),
227 mode: SimulationMode::Standard,
228 cells_per_frame: 16,
229 rng: thread_rng(),
230 cell_x: 1,
231 cell_y: 1,
232 tile_size: 16,
233 }
234 }
235}
236
237impl EducationalProcessor {
238 pub fn new(mode: SimulationMode) -> Self {
240 Self {
241 mode,
242 ..Default::default()
243 }
244 }
245
246 pub fn set_mode(&mut self, mode: SimulationMode) {
248 self.mode = mode;
249 self.reset();
250 }
251
252 pub fn reset(&mut self) {
254 self.state = ProcessingState::default();
255 self.cell_x = 1;
256 self.cell_y = 1;
257 }
258
259 pub fn step_frame(
262 &mut self,
263 pressure: &mut [f32],
264 pressure_prev: &mut [f32],
265 width: usize,
266 height: usize,
267 c2: f32,
268 damping: f32,
269 ) -> StepResult {
270 match self.mode {
271 SimulationMode::Standard => {
272 self.step_standard(pressure, pressure_prev, width, height, c2, damping)
273 }
274 SimulationMode::CellByCell => {
275 self.step_cell_by_cell(pressure, pressure_prev, width, height, c2, damping)
276 }
277 SimulationMode::RowByRow => {
278 self.step_row_by_row(pressure, pressure_prev, width, height, c2, damping)
279 }
280 SimulationMode::ChaoticParallel => {
281 self.step_chaotic_parallel(pressure, pressure_prev, width, height, c2, damping)
282 }
283 SimulationMode::SynchronizedParallel => {
284 self.step_synchronized_parallel(pressure, pressure_prev, width, height, c2, damping)
285 }
286 SimulationMode::ActorBased => {
287 self.step_actor_based(pressure, pressure_prev, width, height, c2, damping)
288 }
289 }
290 }
291
292 fn step_standard(
294 &mut self,
295 pressure: &[f32],
296 pressure_prev: &mut [f32],
297 width: usize,
298 height: usize,
299 c2: f32,
300 damping: f32,
301 ) -> StepResult {
302 for y in 1..height - 1 {
304 for x in 1..width - 1 {
305 let idx = y * width + x;
306 let p_curr = pressure[idx];
307 let p_prev = pressure_prev[idx];
308 let p_north = pressure[idx - width];
309 let p_south = pressure[idx + width];
310 let p_west = pressure[idx - 1];
311 let p_east = pressure[idx + 1];
312
313 let laplacian = p_north + p_south + p_east + p_west - 4.0 * p_curr;
314 let p_new = 2.0 * p_curr - p_prev + c2 * laplacian;
315 pressure_prev[idx] = p_new * damping;
316 }
317 }
318
319 self.state.complete_step();
320 StepResult::complete()
321 }
322
323 fn step_cell_by_cell(
326 &mut self,
327 pressure: &[f32],
328 pressure_prev: &mut [f32],
329 width: usize,
330 height: usize,
331 c2: f32,
332 damping: f32,
333 ) -> StepResult {
334 self.state.just_processed.clear();
335
336 let cells_this_frame = self.cells_per_frame.min(32);
337
338 for _ in 0..cells_this_frame {
339 if self.cell_y >= height as u32 - 1 {
340 self.cell_x = 1;
342 self.cell_y = 1;
343 self.state.complete_step();
344 return StepResult::complete();
345 }
346
347 let x = self.cell_x as usize;
348 let y = self.cell_y as usize;
349
350 let idx = y * width + x;
352 let p_curr = pressure[idx];
353 let p_prev = pressure_prev[idx];
354 let p_north = pressure[idx - width];
355 let p_south = pressure[idx + width];
356 let p_west = pressure[idx - 1];
357 let p_east = pressure[idx + 1];
358
359 let laplacian = p_north + p_south + p_east + p_west - 4.0 * p_curr;
360 let p_new = 2.0 * p_curr - p_prev + c2 * laplacian;
361 pressure_prev[idx] = p_new * damping;
362
363 self.state.just_processed.push((self.cell_x, self.cell_y));
364 self.state.current_cell = Some((self.cell_x, self.cell_y));
365
366 self.cell_x += 1;
368 if self.cell_x >= width as u32 - 1 {
369 self.cell_x = 1;
370 self.cell_y += 1;
371 }
372 }
373
374 StepResult::in_progress()
375 }
376
377 fn step_row_by_row(
379 &mut self,
380 pressure: &[f32],
381 pressure_prev: &mut [f32],
382 width: usize,
383 height: usize,
384 c2: f32,
385 damping: f32,
386 ) -> StepResult {
387 self.state.just_processed.clear();
388
389 if self.cell_y >= height as u32 - 1 {
390 self.cell_y = 1;
392 self.state.current_row = None;
393 self.state.complete_step();
394 return StepResult::complete();
395 }
396
397 let y = self.cell_y as usize;
398 self.state.current_row = Some(self.cell_y);
399
400 for x in 1..width - 1 {
402 let idx = y * width + x;
403 let p_curr = pressure[idx];
404 let p_prev = pressure_prev[idx];
405 let p_north = pressure[idx - width];
406 let p_south = pressure[idx + width];
407 let p_west = pressure[idx - 1];
408 let p_east = pressure[idx + 1];
409
410 let laplacian = p_north + p_south + p_east + p_west - 4.0 * p_curr;
411 let p_new = 2.0 * p_curr - p_prev + c2 * laplacian;
412 pressure_prev[idx] = p_new * damping;
413
414 self.state.just_processed.push((x as u32, self.cell_y));
415 }
416
417 self.cell_y += 1;
418 StepResult::in_progress()
419 }
420
421 fn step_chaotic_parallel(
424 &mut self,
425 pressure: &mut [f32],
426 pressure_prev: &mut [f32],
427 width: usize,
428 height: usize,
429 c2: f32,
430 damping: f32,
431 ) -> StepResult {
432 self.state.just_processed.clear();
433 self.state.active_cells.clear();
434
435 let num_cells = (width * height / 16).min(64);
437
438 for _ in 0..num_cells {
439 let x = self.rng.gen_range(1..width - 1);
440 let y = self.rng.gen_range(1..height - 1);
441
442 self.state.active_cells.push((x as u32, y as u32));
443
444 let idx = y * width + x;
446 let p_curr = pressure[idx];
447 let p_prev = pressure_prev[idx];
448
449 let (p_north, p_south, p_east, p_west) = if self.rng.gen_bool(0.5) {
452 (
454 pressure[idx - width],
455 pressure[idx + width],
456 pressure[idx + 1],
457 pressure[idx - 1],
458 )
459 } else {
460 (
462 pressure_prev[idx - width],
463 pressure_prev[idx + width],
464 pressure_prev[idx + 1],
465 pressure_prev[idx - 1],
466 )
467 };
468
469 let laplacian = p_north + p_south + p_east + p_west - 4.0 * p_curr;
470 let p_new = 2.0 * p_curr - p_prev + c2 * laplacian;
471
472 if self.rng.gen_bool(0.3) {
474 pressure[idx] = p_new * damping; } else {
476 pressure_prev[idx] = p_new * damping;
477 }
478 }
479
480 self.state.just_processed = self.state.active_cells.clone();
481
482 self.cell_y += 1;
484 if self.cell_y >= 8 {
485 self.cell_y = 0;
486 self.state.complete_step();
487 return StepResult::complete();
488 }
489
490 StepResult::in_progress()
491 }
492
493 fn step_synchronized_parallel(
495 &mut self,
496 pressure: &[f32],
497 pressure_prev: &mut [f32],
498 width: usize,
499 height: usize,
500 c2: f32,
501 damping: f32,
502 ) -> StepResult {
503 self.state.just_processed.clear();
504 self.state.active_cells.clear();
505
506 let max_wavefront = (width + height - 4) as u32;
509
510 if self.cell_y > max_wavefront {
511 self.cell_y = 0;
512 self.state.complete_step();
513 return StepResult::complete();
514 }
515
516 let wavefront = self.cell_y;
517
518 for x in 1..width - 1 {
520 let y_calc = wavefront as i32 - x as i32 + 1;
521 if y_calc >= 1 && y_calc < (height - 1) as i32 {
522 let y = y_calc as usize;
523 let idx = y * width + x;
524 let p_curr = pressure[idx];
525 let p_prev = pressure_prev[idx];
526 let p_north = pressure[idx - width];
527 let p_south = pressure[idx + width];
528 let p_west = pressure[idx - 1];
529 let p_east = pressure[idx + 1];
530
531 let laplacian = p_north + p_south + p_east + p_west - 4.0 * p_curr;
532 let p_new = 2.0 * p_curr - p_prev + c2 * laplacian;
533 pressure_prev[idx] = p_new * damping;
534
535 self.state.active_cells.push((x as u32, y as u32));
536 }
537 }
538
539 self.state.just_processed = self.state.active_cells.clone();
540 self.cell_y += 1; StepResult::in_progress()
542 }
543
544 fn step_actor_based(
546 &mut self,
547 pressure: &[f32],
548 pressure_prev: &mut [f32],
549 width: usize,
550 height: usize,
551 c2: f32,
552 damping: f32,
553 ) -> StepResult {
554 self.state.just_processed.clear();
555 self.state.active_tiles.clear();
556 self.state.messages_in_flight.clear();
557
558 let tile_size = self.tile_size as usize;
559 let interior_width = width - 2;
561 let interior_height = height - 2;
562 let tiles_x = interior_width.div_ceil(tile_size);
564 let tiles_y = interior_height.div_ceil(tile_size);
565
566 let current_tile = self.cell_y as usize;
567 let total_tiles = tiles_x * tiles_y;
568
569 if current_tile >= total_tiles {
570 self.cell_y = 0;
571 self.state.hlc_tick += 1;
572 self.state.complete_step();
573 return StepResult::complete();
574 }
575
576 let tile_x = current_tile % tiles_x;
578 let tile_y = current_tile / tiles_x;
579
580 self.state.active_tiles.push((tile_x as u32, tile_y as u32));
581
582 if tile_x > 0 {
584 self.state.messages_in_flight.push(TileMessage {
585 from: ((tile_x - 1) as u32, tile_y as u32),
586 to: (tile_x as u32, tile_y as u32),
587 timestamp: self.state.hlc_tick,
588 });
589 }
590 if tile_y > 0 {
591 self.state.messages_in_flight.push(TileMessage {
592 from: (tile_x as u32, (tile_y - 1) as u32),
593 to: (tile_x as u32, tile_y as u32),
594 timestamp: self.state.hlc_tick,
595 });
596 }
597
598 let start_x = 1 + tile_x * tile_size;
601 let start_y = 1 + tile_y * tile_size;
602 let end_x = (start_x + tile_size).min(width - 1);
604 let end_y = (start_y + tile_size).min(height - 1);
605
606 for y in start_y..end_y {
607 for x in start_x..end_x {
608 let idx = y * width + x;
609 let p_curr = pressure[idx];
610 let p_prev = pressure_prev[idx];
611 let p_north = pressure[idx - width];
612 let p_south = pressure[idx + width];
613 let p_west = pressure[idx - 1];
614 let p_east = pressure[idx + 1];
615
616 let laplacian = p_north + p_south + p_east + p_west - 4.0 * p_curr;
617 let p_new = 2.0 * p_curr - p_prev + c2 * laplacian;
618 pressure_prev[idx] = p_new * damping;
619
620 self.state.just_processed.push((x as u32, y as u32));
621 }
622 }
623
624 self.cell_y += 1;
625 StepResult::in_progress()
626 }
627}
628
629#[cfg(test)]
630mod tests {
631 use super::*;
632
633 #[test]
634 fn test_mode_display() {
635 assert_eq!(
636 format!("{}", SimulationMode::CellByCell),
637 "Cell-by-Cell (1950s)"
638 );
639 assert_eq!(
640 format!("{}", SimulationMode::RowByRow),
641 "Row-by-Row (1970s)"
642 );
643 assert_eq!(
644 format!("{}", SimulationMode::ChaoticParallel),
645 "Chaotic (1990s)"
646 );
647 }
648
649 #[test]
650 fn test_mode_all() {
651 let modes = SimulationMode::all();
652 assert_eq!(modes.len(), 6);
653 }
654
655 #[test]
656 fn test_processing_state_reset() {
657 let mut state = ProcessingState {
658 current_cell: Some((5, 5)),
659 step_complete: false,
660 ..Default::default()
661 };
662 state.begin_step();
663 assert!(state.current_cell.is_none());
664 assert!(!state.step_complete);
665 }
666
667 #[test]
668 fn test_cell_by_cell_processing() {
669 let mut processor = EducationalProcessor::new(SimulationMode::CellByCell);
670 let width = 8;
671 let height = 8;
672 let mut pressure = vec![0.0; width * height];
673 let mut pressure_prev = vec![0.0; width * height];
674
675 pressure[3 * width + 3] = 1.0;
677
678 let c2 = 0.25;
679 let damping = 0.99;
680
681 let mut iterations = 0;
683 loop {
684 let result = processor.step_frame(
685 &mut pressure,
686 &mut pressure_prev,
687 width,
688 height,
689 c2,
690 damping,
691 );
692 if result.step_complete {
693 if result.should_swap {
694 std::mem::swap(&mut pressure, &mut pressure_prev);
695 }
696 break;
697 }
698 iterations += 1;
699 assert!(
700 iterations < 100,
701 "Should complete within reasonable iterations"
702 );
703 }
704
705 assert!(processor.state.step_complete);
707 }
708
709 #[test]
710 fn test_row_by_row_processing() {
711 let mut processor = EducationalProcessor::new(SimulationMode::RowByRow);
712 let width = 8;
713 let height = 8;
714 let mut pressure = vec![0.0; width * height];
715 let mut pressure_prev = vec![0.0; width * height];
716
717 pressure[3 * width + 3] = 1.0;
718
719 let c2 = 0.25;
720 let damping = 0.99;
721
722 let result = processor.step_frame(
724 &mut pressure,
725 &mut pressure_prev,
726 width,
727 height,
728 c2,
729 damping,
730 );
731 assert!(!result.step_complete);
732 assert_eq!(processor.state.current_row, Some(1));
733
734 let mut iterations = 0;
736 loop {
737 let result = processor.step_frame(
738 &mut pressure,
739 &mut pressure_prev,
740 width,
741 height,
742 c2,
743 damping,
744 );
745 if result.step_complete {
746 break;
747 }
748 iterations += 1;
749 assert!(iterations < 20);
750 }
751 }
752
753 #[test]
754 fn test_actor_based_processing() {
755 let mut processor = EducationalProcessor::new(SimulationMode::ActorBased);
756 processor.tile_size = 4; let width = 16;
759 let height = 16;
760 let mut pressure = vec![0.0; width * height];
761 let mut pressure_prev = vec![0.0; width * height];
762
763 pressure[8 * width + 8] = 1.0;
764
765 let c2 = 0.25;
766 let damping = 0.99;
767
768 let result = processor.step_frame(
770 &mut pressure,
771 &mut pressure_prev,
772 width,
773 height,
774 c2,
775 damping,
776 );
777 assert!(!result.step_complete);
778 assert!(!processor.state.active_tiles.is_empty());
779
780 let mut iterations = 0;
782 loop {
783 let result = processor.step_frame(
784 &mut pressure,
785 &mut pressure_prev,
786 width,
787 height,
788 c2,
789 damping,
790 );
791 if result.step_complete {
792 break;
793 }
794 iterations += 1;
795 assert!(iterations < 50);
796 }
797 }
798}