#![allow(clippy::reversed_empty_ranges)]
use crate::{Neumann, Sim};
use ndarray::{par_azip, s, Array2, ArrayView2, ArrayViewMut2};
use std::{
cell::UnsafeCell,
mem::{self, ManuallyDrop},
};
use itertools::Itertools;
#[derive(Clone, Debug)]
pub struct SquareGrid<S>
where
S: Sim<Neumann>,
{
sim: S,
cells: Array2<S::Cell>,
}
impl<S> SquareGrid<S>
where
S: Sim<Neumann>,
S::Cell: Send,
{
pub fn new(sim: S, mut original_cells: Array2<S::Cell>) -> Self {
let dims = original_cells.dim();
assert!(
dims.0 >= 1 && dims.1 >= 1,
"grid is empty, which isnt allowed"
);
let mut cells =
Array2::from_shape_simple_fn((dims.0 + 2, dims.1 + 2), || sim.cell_padding());
par_azip!((dest in &mut cells.slice_mut(s![1..-1, 1..-1]), cell in &mut original_cells) {
mem::swap(dest, cell);
});
Self { sim, cells }
}
pub fn cells(&self) -> ArrayView2<'_, S::Cell> {
self.cells.slice(s![1..-1, 1..-1])
}
pub fn cells_mut(&mut self) -> ArrayViewMut2<'_, S::Cell> {
self.cells.slice_mut(s![1..-1, 1..-1])
}
}
#[cfg(feature = "use-rayon")]
impl<S> SquareGrid<S>
where
S: Sim<Neumann> + Sync,
S::Cell: Send + Sync,
S::Diff: Send + Sync,
S::Flow: Send,
{
pub fn step_parallel(&mut self) {
let diffs = self.compute_diffs();
let flows = self.perform_egress(diffs.view());
self.perform_ingress(flows);
}
fn compute_diffs(&self) -> Array2<S::Diff> {
let mut diffs = Array2::from_shape_simple_fn(self.cells.dim(), || self.sim.diff_padding());
par_azip!((diff in diffs.slice_mut(s![1..-1, 1..-1]), cell in self.cells.windows((3, 3))) {
*diff = self.sim.compute(cell);
});
diffs
}
fn perform_egress(
&mut self,
diffs: ArrayView2<'_, S::Diff>,
) -> Array2<ManuallyDrop<UnsafeCell<[S::Flow; 8]>>> {
let mut flows = Array2::from_shape_simple_fn(self.cells.dim(), || {
ManuallyDrop::new(UnsafeCell::new([
self.sim.flow_padding(),
self.sim.flow_padding(),
self.sim.flow_padding(),
self.sim.flow_padding(),
self.sim.flow_padding(),
self.sim.flow_padding(),
self.sim.flow_padding(),
self.sim.flow_padding(),
]))
});
let sim = &self.sim;
par_azip!((flow in flows.slice_mut(s![1..-1, 1..-1]), cell in self.cells.slice_mut(s![1..-1, 1..-1]), diffs in diffs.windows((3, 3))) {
*flow.get_mut() = sim.egress(cell, diffs);
});
unsafe fn exchange_chunk<T>(chunk: ArrayViewMut2<'_, ManuallyDrop<UnsafeCell<[T; 8]>>>) {
let top_left = &mut *chunk[(0, 0)].get();
let top_right = &mut *chunk[(0, 1)].get();
let bottom_left = &mut *chunk[(1, 0)].get();
let bottom_right = &mut *chunk[(1, 1)].get();
mem::swap(&mut bottom_left[0], &mut bottom_right[4]);
mem::swap(&mut top_right[6], &mut bottom_right[2]);
mem::swap(&mut top_left[7], &mut bottom_right[3]);
mem::swap(&mut top_right[5], &mut bottom_left[1]);
}
for (y, x) in (0..2).cartesian_product(0..2) {
par_azip!((chunk in flows.slice_mut(s![y.., x..]).exact_chunks_mut((2, 2))) {
unsafe { exchange_chunk(chunk); }
});
}
flows
}
fn perform_ingress(&mut self, mut flows: Array2<ManuallyDrop<UnsafeCell<[S::Flow; 8]>>>) {
let (h, w) = self.cells.dim();
let sim = &self.sim;
par_azip!((index (y, x), flow in &mut flows, cell in &mut self.cells) {
unsafe {
if (1..h-1).contains(&y) && (1..w-1).contains(&x) {
sim.ingress(cell, ManuallyDrop::take(flow).into_inner());
} else {
ManuallyDrop::drop(flow);
}
}
});
}
}