fdtd-coremem/crates/coremem/src/render.rs

761 lines
28 KiB
Rust

use crate::geom::Index;
use crate::real::ToFloat as _;
use crate::cross::vec::{Vec2, Vec3};
use crate::sim::{AbstractSim, GenericSim, Sample};
use crate::meas::{self, AbstractMeasurement, Measurement};
use crossterm::{cursor, QueueableCommand as _};
use crossterm::style::{style, Color, PrintStyledContent, Stylize as _};
use font8x8::{BASIC_FONTS, GREEK_FONTS, UnicodeFonts as _};
use log::trace;
use num::integer::Integer; // TODO: remove?
use image::{RgbImage, Rgb};
use imageproc::{pixelops, drawing};
use rayon::prelude::*;
use serde::{Serialize, Deserialize};
use std::collections::hash_map::DefaultHasher;
use std::hash::Hasher;
use std::fs::{File, OpenOptions};
use std::io::{BufReader, BufWriter, Seek as _, SeekFrom, Write as _};
use std::path::{Path, PathBuf};
use std::sync::{Mutex, RwLock};
use y4m;
/// Accept a value from (-\inf, \inf) and return a value in (-1, 1).
/// If the input is equal to `typical`, it will be mapped to 0.5.
/// If the input is equal to -`typical`, it will be mapped to -0.5.
fn scale_signed(x: f32, typical: f32) -> f32 {
if x >= 0.0 {
scale_unsigned(x, typical)
} else {
-scale_unsigned(-x, typical)
}
}
/// Accept a value from [0, \inf) and return a value in [0, 1).
/// If the input is equal to `typical`, it will be mapped to 0.5.
fn scale_unsigned(x: f32, typical: f32) -> f32 {
// f(0) => 0
// f(1) => 0.5
// f(\inf) => 1
// f(x) = 1 - 1/(x+1)
1.0 - 1.0/(x/typical + 1.0)
}
fn scale_signed_to_u8(x: f32, typ: f32) -> u8 {
let norm = 128.0 + 128.0*scale_signed(x, typ);
norm as _
}
fn scale_unsigned_to_u8(x: f32, typ: f32) -> u8 {
let norm = 256.0*scale_unsigned(x, typ);
norm as _
}
/// Scale a vector to have magnitude between [0, 1).
fn scale_vector(x: Vec2<f32>, typical_mag: f32) -> Vec2<f32> {
let new_mag = scale_unsigned(x.mag(), typical_mag);
x.with_mag(new_mag).unwrap_or_default()
}
fn im_size<S: AbstractSim>(state: &S, max_w: u32, max_h: u32) -> (u32, u32) {
let mut width = max_w;
let mut height = width * state.height() / state.width();
if height > max_h {
let stretch = max_h as f32 / height as f32;
width = (width as f32 * stretch).round() as _;
height = max_h;
}
(width, height)
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum FieldDisplayMode {
BzExy,
EzBxy,
BCurrent,
M,
Material,
}
impl FieldDisplayMode {
pub fn next(self) -> Self {
use FieldDisplayMode::*;
match self {
BzExy => EzBxy,
EzBxy => BCurrent,
BCurrent => M,
M => Material,
Material => BzExy,
}
}
pub fn prev(self) -> Self {
use FieldDisplayMode::*;
match self {
BzExy => Material,
EzBxy => BzExy,
BCurrent => EzBxy,
M => BCurrent,
Material => M,
}
}
}
#[derive(Copy, Clone, PartialEq)]
pub struct RenderConfig {
mode: FieldDisplayMode,
scale: f32,
}
impl Default for RenderConfig {
fn default() -> Self {
Self {
mode: FieldDisplayMode::BzExy,
scale: 1.0,
}
}
}
impl RenderConfig {
pub fn next_mode(&mut self) {
self.mode = self.mode.next();
}
pub fn prev_mode(&mut self) {
self.mode = self.mode.prev();
}
pub fn increase_range(&mut self) {
self.scale *= 2.0;
}
pub fn decrease_range(&mut self) {
self.scale *= 0.5;
}
}
struct RenderSteps<'a, S> {
im: RgbImage,
sim: &'a S,
meas: &'a [&'a dyn AbstractMeasurement<S>],
/// Simulation z coordinate to sample
z: u32,
}
impl<'a, S: AbstractSim> RenderSteps<'a, S> {
// TODO: this could probably be a single measurement, and we just let collections of
// measurements also behave as measurements
/// Render using default configuration constants
fn render(state: &'a S, measurements: &'a [&'a dyn AbstractMeasurement<S>], z: u32) -> RgbImage {
Self::render_configured(state, measurements, z, (640, 480), RenderConfig::default())
}
/// Render, controlling things like the size.
fn render_configured(
state: &'a S,
measurements: &'a [&'a dyn AbstractMeasurement<S>],
z: u32,
max_size: (u32, u32),
config: RenderConfig,
) -> RgbImage {
let (width, height) = im_size(state, max_size.0, max_size.1);
trace!("rendering at {}x{} with z={}", width, height, z);
let mut me = Self::new(state, measurements, width, height, z);
me.render_scalar_field(10.0, false, 2, |cell| {
let is_vacuum = cell.conductivity() != Vec3::zero() || cell.m() != Vec3::zero();
cell.conductivity().mag().to_f32() + if is_vacuum {
0.0
} else {
5.0
}
});
match config.mode {
FieldDisplayMode::BzExy => {
me.render_b_z_field(config.scale);
me.render_e_xy_field(config.scale);
},
FieldDisplayMode::EzBxy => {
me.render_e_z_field(config.scale);
me.render_b_xy_field(config.scale);
},
FieldDisplayMode::BCurrent => {
me.render_b(config.scale);
me.render_current(config.scale);
}
FieldDisplayMode::M => {
me.render_m(config.scale);
}
FieldDisplayMode::Material => {
me.render_mat(config.scale);
}
}
me.render_measurements();
me.im
}
fn new(sim: &'a S, meas: &'a [&'a dyn AbstractMeasurement<S>], width: u32, height: u32, z: u32) -> Self {
RenderSteps {
im: RgbImage::new(width, height),
sim,
meas,
z
}
}
fn get_at_px<'b>(&'b self, x_px: u32, y_px: u32) -> Sample<'b, S::Real, S::Material> {
let x_idx = x_px * self.sim.width() / self.im.width();
let y_idx = y_px * self.sim.height() / self.im.height();
self.sim.sample(Index::new(x_idx, y_idx, self.z))
}
////////////// Ex/Ey/Bz configuration ////////////
fn render_b_z_field(&mut self, scale: f32) {
self.render_scalar_field(1.0e-4 * scale, true, 1, |cell| cell.b().z().to_f32());
}
fn render_e_xy_field(&mut self, scale: f32) {
self.render_vector_field(Rgb([0xff, 0xff, 0xff]), 100.0 * scale, |cell| cell.e().xy().to_f32());
// current
self.render_vector_field(Rgb([0x00, 0xa0, 0x30]), 1.0e-12 * scale, |cell| {
cell.e().elem_mul(cell.conductivity()).xy().to_f32()
});
}
////////////// Magnitude configuration /////////////
fn render_b(&mut self, scale: f32) {
self.render_scalar_field(1.0e-3 * scale, false, 1, |cell| cell.b().mag().to_f32());
}
fn render_current(&mut self, scale: f32) {
self.render_scalar_field(1.0e1 * scale, false, 0, |cell| {
cell.e().elem_mul(cell.conductivity()).mag().to_f32()
});
}
////////////// Bx/By/Ez configuration ////////////
fn render_e_z_field(&mut self, scale: f32) {
self.render_scalar_field(1e4 * scale, true, 1, |cell| cell.e().z().to_f32());
}
fn render_b_xy_field(&mut self, scale: f32) {
self.render_vector_field(Rgb([0xff, 0xff, 0xff]), 1.0e-9 * scale, |cell| cell.b().xy().to_f32());
}
fn render_m(&mut self, scale: f32) {
self.render_scalar_field(1.0e5 * scale, false, 1, |cell| cell.m().mag().to_f32());
self.render_vector_field(Rgb([0xff, 0xff, 0xff]), 1.0e5 * scale, |cell| cell.m().xy().to_f32());
}
fn render_mat(&mut self, scale: f32) {
unsafe fn to_bytes<T>(d: &T) -> &[u8] {
std::slice::from_raw_parts(d as *const T as *const u8, std::mem::size_of::<T>())
}
self.render_scalar_field(scale, false, 1, |cell| {
let mut hasher = DefaultHasher::new();
let as_bytes = unsafe { to_bytes(cell.material()) };
std::hash::Hash::hash_slice(as_bytes, &mut hasher);
hasher.finish() as f32 / (-1i64 as u64 as f32)
});
}
fn render_vector_field<F>(&mut self, color: Rgb<u8>, typical: f32, measure: F)
where
F: Fn(&Sample<'_, S::Real, S::Material>) -> Vec2<f32>
{
let w = self.im.width();
let h = self.im.height();
let vec_spacing = 10;
for y in (0..h).into_iter().step_by(vec_spacing as _) {
for x in (0..w).into_iter().step_by(vec_spacing as _) {
let vec = self.field_vector(x, y, vec_spacing, &measure);
let norm_vec = scale_vector(vec, typical);
let alpha = 0.7*scale_unsigned(vec.mag_sq(), typical * 5.0);
let vec = norm_vec * (vec_spacing as f32);
let center = Vec2::new(x as f32, y as f32) + Vec2::new(vec_spacing as f32, vec_spacing as f32)*0.5;
self.im.draw_field_arrow(center, vec, color, alpha as f32);
}
}
}
fn render_scalar_field<F>(&mut self, typical: f32, signed: bool, slot: u32, measure: F)
where
F: Fn(&Sample<'_, S::Real, S::Material>) -> f32 + Sync
{
// XXX: get_at_px borrows self, so we need to clone the image to operate on it mutably.
let mut im = self.im.clone();
let w = im.width();
let h = im.height();
let samples = im.as_flat_samples_mut();
assert_eq!(samples.layout.channel_stride, 1);
assert_eq!(samples.layout.width_stride, 3);
assert_eq!(samples.layout.height_stride, 3*w as usize);
let pixel_buf: &mut [[u8; 3]] = unsafe { std::mem::transmute(samples.samples) };
pixel_buf[..(w*h) as usize].par_iter_mut().enumerate().for_each(|(idx, px)| {
let (y, x) = (idx as u32).div_rem(&w);
let cell = self.get_at_px(x, y);
let value = measure(&cell);
let scaled = if signed {
scale_signed_to_u8(value, typical)
} else {
scale_unsigned_to_u8(value, typical)
};
px[slot as usize] = scaled;
});
self.im = im;
}
fn render_measurements(&mut self) {
for (meas_no, m) in meas::eval_multiple(self.sim, &self.meas).into_iter().enumerate() {
let meas_string = m.pretty_print();
for (i, c) in meas_string.chars().enumerate() {
let glyph = BASIC_FONTS.get(c)
.or_else(|| GREEK_FONTS.get(c))
.unwrap_or_else(|| BASIC_FONTS.get('?').unwrap());
for (y, bmp) in glyph.iter().enumerate() {
for x in 0..8 {
if (bmp & 1 << x) != 0 {
let real_x = 2 + i as u32*8 + x;
if let Some(real_y) = (y as u32 + self.im.height()).checked_sub(10 + meas_no as u32 * 8) {
if real_x < self.im.width() {
self.im.put_pixel(real_x, real_y, Rgb([0, 0, 0]));
}
}
}
}
}
}
}
}
fn field_vector<F>(&self, xidx: u32, yidx: u32, size: u32, measure: &F) -> Vec2<f32>
where
F: Fn(&Sample<'_, S::Real, S::Material>) -> Vec2<f32>
{
let mut field = Vec2::default();
let w = self.im.width();
let h = self.im.height();
let xstart = xidx.min(w);
let ystart = yidx.min(h);
let xend = (xstart + size).min(w);
let yend = (ystart + size).min(h);
for y in ystart..yend {
for x in xstart..xend {
field += measure(&self.get_at_px(x, y));
}
}
let xw = xend - xstart;
let yw = yend - ystart;
if xw == 0 || yw == 0 {
// avoid division by zero
Vec2::new(0.0, 0.0)
} else {
field * (1.0 / ((xw*yw) as f32))
}
}
}
trait ImageRenderExt {
fn draw_field_arrow(&mut self, center: Vec2<f32>, rel: Vec2<f32>, color: Rgb<u8>, alpha: f32);
}
impl ImageRenderExt for RgbImage {
fn draw_field_arrow(&mut self, center: Vec2<f32>, rel: Vec2<f32>, color: Rgb<u8>, alpha: f32) {
let start = (center - rel * 0.5).round();
let end = (center + rel * 0.5).round();
let i_start = (start.x().round() as _, start.y().round() as _);
let i_end = (end.x().round() as _, end.y().round() as _);
let interpolate_with_alpha = |left, right, left_weight| {
pixelops::interpolate(left, right, left_weight*alpha)
};
drawing::draw_antialiased_line_segment_mut(self, i_start, i_end, color, interpolate_with_alpha);
if i_start != i_end
&& (0..self.width() as i32).contains(&i_end.0)
&& (0..self.height() as i32).contains(&i_end.1)
{
self.put_pixel(i_end.0 as _, i_end.1 as _, Rgb([0xff, 0, 0]));
}
//drawing::draw_line_segment_mut(self, i_start, i_end, color);
}
}
pub trait Renderer<S>: Send + Sync {
fn render_z_slice(&self, state: &S, z: u32, measurements: &[&dyn AbstractMeasurement<S>], config: RenderConfig);
// {
// self.render_with_image(state, &RenderSteps::render(state, measurements, z), measurements);
// }
fn render(&self, state: &S, measurements: &[&dyn AbstractMeasurement<S>], config: RenderConfig);
/// Not intended to be called directly by users; implement this if you want the image to be
/// computed using default settings and you just manage where to display/save it.
fn render_with_image(&self, state: &S, _im: &RgbImage, measurements: &[&dyn AbstractMeasurement<S>], config: RenderConfig) {
self.render(state, measurements, config);
}
}
fn default_render_z_slice<S: AbstractSim, R: Renderer<S>>(
me: &R, state: &S, z: u32, measurements: &[&dyn AbstractMeasurement<S>], config: RenderConfig,
) {
me.render_with_image(state, &RenderSteps::render(state, measurements, z), measurements, config);
}
fn default_render<S: AbstractSim, R: Renderer<S>>(
me: &R, state: &S, measurements: &[&dyn AbstractMeasurement<S>], config: RenderConfig
) {
me.render_z_slice(state, state.depth() / 2, measurements, config);
}
// pub struct NumericTermRenderer;
//
// impl Renderer for NumericTermRenderer {
// fn render(&mut self, state: &SimSnapshot, _measurements: &[&dyn AbstractMeasurement<S>]) {
// for y in 0..state.height() {
// for x in 0..state.width() {
// let cell = state.get((x, y).into());
// print!(" {:>10.1e}", cell.ex());
// }
// print!("\n");
// for x in 0..state.width() {
// let cell = state.get((x, y).into());
// print!("{:>10.1e} {:>10.1e}", cell.ey(), cell.bz());
// }
// print!("\n");
// }
// print!("\n");
// }
// }
#[derive(Default)]
pub struct ColorTermRenderer;
impl<S: AbstractSim> Renderer<S> for ColorTermRenderer {
fn render(&self, state: &S, measurements: &[&dyn AbstractMeasurement<S>], config: RenderConfig) {
default_render(self, state, measurements, config)
}
fn render_z_slice(
&self,
state: &S,
z: u32,
measurements: &[&dyn AbstractMeasurement<S>],
config: RenderConfig,
) {
let measurements = meas::eval_multiple(state, measurements);
let (max_w, mut max_h) = crossterm::terminal::size().unwrap();
max_h = max_h.saturating_sub(2 + measurements.len() as u16);
let im = RenderSteps::render_configured(state, &[], z, (max_w as _, max_h as _), config);
let mut stdout = std::io::stdout();
// TODO: consider clearing line-by-line for less tearing?
stdout.queue(crossterm::terminal::Clear(crossterm::terminal::ClearType::All)).unwrap();
stdout.queue(cursor::MoveTo(0, 0)).unwrap();
for row in im.rows() {
for p in row {
stdout.queue(PrintStyledContent(style(" ").on(Color::Rgb {
r: p.0[0],
g: p.0[1],
b: p.0[2],
}))).unwrap();
}
stdout.queue(cursor::MoveDown(1)).unwrap();
stdout.queue(cursor::MoveToColumn(0)).unwrap();
}
stdout.queue(PrintStyledContent(style(format!("fields: {:?} scale: {}", config.mode, config.scale)))).unwrap();
stdout.queue(cursor::MoveDown(1)).unwrap();
stdout.queue(cursor::MoveToColumn(1)).unwrap();
stdout.queue(PrintStyledContent(style(format!("z: {}", z)))).unwrap();
for m in measurements {
// Measurements can be slow to compute
stdout.flush().unwrap();
let meas_string = format!("{}: \t{}", m.name(), m.pretty_print());
stdout.queue(cursor::MoveDown(1)).unwrap();
stdout.queue(cursor::MoveToColumn(1)).unwrap();
stdout.queue(PrintStyledContent(style(meas_string))).unwrap();
}
stdout.flush().unwrap();
}
}
pub struct Y4MRenderer {
out_path: PathBuf,
encoder: Mutex<Option<y4m::Encoder<File>>>,
}
impl Y4MRenderer {
pub fn new<P: Into<PathBuf>>(output: P) -> Self {
Self {
out_path: output.into(),
encoder: Mutex::new(None),
}
}
}
impl<S: AbstractSim> Renderer<S> for Y4MRenderer {
fn render_z_slice(&self, state: &S, z: u32, measurements: &[&dyn AbstractMeasurement<S>], config: RenderConfig) {
default_render_z_slice(self, state, z, measurements, config)
}
fn render(&self, state: &S, measurements: &[&dyn AbstractMeasurement<S>], config: RenderConfig) {
default_render(self, state, measurements, config)
}
fn render_with_image(&self, _state: &S, im: &RgbImage, _meas: &[&dyn AbstractMeasurement<S>], _config: RenderConfig) {
{
let mut enc = self.encoder.lock().unwrap();
if enc.is_none() {
let writer = File::create(&self.out_path).unwrap();
*enc = Some(y4m::encode(im.width() as usize, im.height() as usize, y4m::Ratio::new(30, 1))
.with_colorspace(y4m::Colorspace::C444)
.write_header(writer)
.unwrap()
);
}
}
let mut pix_y = Vec::new();
let mut pix_u = Vec::new();
let mut pix_v = Vec::new();
for &Rgb([r, g, b]) in im.pixels() {
let r = (r as f32) / (256.0);
let g = (g as f32) / (256.0);
let b = (b as f32) / (256.0);
let y = (0.299 * r) + (0.587 * g) + (0.114 * b);
let cb = 0.5 - (0.168_736 * r) - (0.331_264 * g) + (0.5 * b);
let cr = 0.5 + (0.5 * r) - (0.418_688 * g) - (0.081_312 * b);
pix_y.push((y * 256.0) as _);
pix_u.push((cb * 256.0) as _);
pix_v.push((cr * 256.0) as _);
}
let frame = y4m::Frame::new([&*pix_y, &*pix_u, &*pix_v], None);
let mut lock = self.encoder.lock().unwrap();
let enc = lock.as_mut().unwrap();
trace!("write_frame begin");
let ret = enc.write_frame(&frame).unwrap();
trace!("write_frame end");
ret
}
}
struct MultiRendererElement<S> {
step_frequency: u64,
step_limit: Option<u64>,
renderer: Box<dyn Renderer<S>>,
}
impl<S> MultiRendererElement<S> {
fn work_this_frame(&self, frame: u64) -> bool {
frame % self.step_frequency == 0 && match self.step_limit {
None => true,
Some(end) => frame < end,
}
}
fn next_frame_for_work(&self, after: u64) -> Option<u64> {
let max_frame = after + self.step_frequency;
let max_frame = max_frame - max_frame % self.step_frequency;
match self.step_limit {
None => Some(max_frame),
Some(end) => Some(max_frame).filter(|&f| f < end)
}
}
}
pub struct MultiRenderer<S> {
renderers: RwLock<Vec<MultiRendererElement<S>>>,
}
impl<S> Default for MultiRenderer<S> {
fn default() -> Self {
Self {
renderers: RwLock::new(Vec::new())
}
}
}
impl<S> MultiRenderer<S> {
pub fn new() -> Self {
Default::default()
}
pub fn push<R: Renderer<S> + 'static>(&self, renderer: R, step_frequency: u64, step_limit: Option<u64>) {
self.renderers.write().unwrap().push(MultiRendererElement {
step_frequency,
step_limit,
renderer: Box::new(renderer),
});
}
pub fn with<R: Renderer<S> + 'static>(self, renderer: R, step_frequency: u64, step_limit: Option<u64>) -> Self {
self.push(renderer, step_frequency, step_limit);
self
}
pub fn any_work_for_frame(&self, frame: u64) -> bool {
self.renderers.read().unwrap().iter().any(|m| m.work_this_frame(frame))
}
pub fn next_frame_for_work(&self, after: u64) -> Option<u64> {
self.renderers.read().unwrap().iter().flat_map(|m| m.next_frame_for_work(after)).min()
}
}
impl<S: AbstractSim> Renderer<S> for MultiRenderer<S> {
fn render_z_slice(&self, state: &S, z: u32, measurements: &[&dyn AbstractMeasurement<S>], config: RenderConfig) {
default_render_z_slice(self, state, z, measurements, config)
}
fn render(&self, state: &S, measurements: &[&dyn AbstractMeasurement<S>], config: RenderConfig) {
if self.renderers.read().unwrap().len() != 0 {
self.render_with_image(state, &RenderSteps::render(state, measurements, state.depth() / 2), measurements, config);
}
}
fn render_with_image(&self, state: &S, im: &RgbImage, measurements: &[&dyn AbstractMeasurement<S>], config: RenderConfig) {
for r in &*self.renderers.read().unwrap() {
if r.work_this_frame(state.step_no()) {
r.renderer.render_with_image(state, im, measurements, config);
}
}
}
}
#[derive(Serialize, Deserialize)]
pub struct SerializedFrame<S> {
pub state: S,
/// although not generally necessary to load the sim, saving the measurements is beneficial for
/// post-processing.
pub measurements: Vec<Measurement>,
}
impl<S: AbstractSim> SerializedFrame<S> {
pub fn to_generic(self) -> SerializedFrame<GenericSim<S::Real>> {
SerializedFrame {
state: AbstractSim::to_generic(&self.state),
measurements: self.measurements,
}
}
}
/// this serializes the simulation state plus measurements to disk.
/// it can either convert the state to a generic, material-agnostic format (generic)
/// or dump it as-is.
pub struct SerializerRenderer {
fmt_str: String,
prefer_generic: bool,
}
impl SerializerRenderer {
/// `fmt_str` is a format string which will be formatted with
/// `{step_no}` set to the relevant simulation step.
pub fn new(fmt_str: &str) -> Self {
Self {
fmt_str: fmt_str.into(),
prefer_generic: false,
}
}
/// Same as `new`, but cast to GenericSim before serializing. This yields a file that's easier
/// for post-processing.
pub fn new_generic(fmt_str: &str) -> Self {
Self {
fmt_str: fmt_str.into(),
prefer_generic: true,
}
}
}
impl SerializerRenderer {
fn serialize<S: AbstractSim + Serialize>(&self, state: &S, measurements: Vec<Measurement>) {
let frame = SerializedFrame {
state,
measurements,
};
let name = self.fmt_str.replace("{step_no}", &*frame.state.step_no().to_string());
// serialize to a temporary file -- in case we run out of disk space, etc.
let temp_name = format!("{}.incomplete", name);
let out = BufWriter::new(File::create(&temp_name).unwrap());
bincode::serialize_into(out, &frame).unwrap();
// atomically complete the write.
std::fs::rename(temp_name, name).unwrap();
}
pub fn try_load<S: AbstractSim + for <'a> Deserialize<'a>>(&self) -> Option<SerializedFrame<S>> {
let mut reader = BufReader::new(File::open(&*self.fmt_str).ok()?);
bincode::deserialize_from(&mut reader).ok()
}
}
impl<S: AbstractSim + Serialize> Renderer<S> for SerializerRenderer {
fn render_z_slice(&self, state: &S, z: u32, measurements: &[&dyn AbstractMeasurement<S>], config: RenderConfig) {
default_render_z_slice(self, state, z, measurements, config)
}
fn render(&self, state: &S, measurements: &[&dyn AbstractMeasurement<S>], _config: RenderConfig) {
if self.prefer_generic {
self.serialize(&state.to_generic(), meas::eval_multiple(state, measurements));
} else {
self.serialize(state, meas::eval_multiple(state, measurements));
}
}
}
enum CsvState {
Reading(csv::Reader<BufReader<File>>),
Writing(csv::Writer<BufWriter<File>>),
}
pub struct CsvRenderer {
state: Mutex<Option<CsvState>>,
}
impl CsvRenderer {
pub fn new<P: AsRef<Path>>(path: P) -> Self {
let f = OpenOptions::new().read(true).write(true).create(true).open(path).unwrap();
let reader = csv::Reader::from_reader(BufReader::new(f));
Self {
state: Mutex::new(Some(CsvState::Reading(reader))),
}
}
pub fn read_column(self, header: &str) -> Vec<String> {
let mut rd = match self.state.into_inner().unwrap() {
Some(CsvState::Reading(rd)) => rd,
_ => panic!("not reading!"),
};
let colno = rd.headers().unwrap().iter().position(|it| it == header).unwrap();
rd.into_records().map(|items| items.unwrap().get(colno).unwrap().to_owned()).collect()
}
pub fn read_column_as_f32(self, header: &str) -> Vec<f32> {
self.read_column(header).into_iter().map(|s| s.parse().unwrap()).collect()
}
}
impl<S: AbstractSim> Renderer<S> for CsvRenderer {
fn render_z_slice(&self, state: &S, z: u32, measurements: &[&dyn AbstractMeasurement<S>], config: RenderConfig) {
default_render_z_slice(self, state, z, measurements, config)
}
fn render(&self, state: &S, measurements: &[&dyn AbstractMeasurement<S>], _config: RenderConfig) {
let row = meas::eval_multiple(state, measurements);
let step = state.step_no();
let mut lock = self.state.lock().unwrap();
let mut writer = match lock.take().unwrap() {
CsvState::Reading(mut reader) => {
let headers = reader.headers().unwrap();
let has_header = headers.get(0) == Some("step");
if has_header {
// read until we get a row whose step is >= this one
let mut seek_pos = None;
for record in reader.records() {
let record = record.unwrap();
if let Some(step_str) = record.get(0) {
if let Ok(step_num) = step_str.parse::<u64>() {
if step_num >= step {
// truncate csv here
seek_pos = record.position().map(|p| p.byte());
break;
}
}
}
}
let mut file = reader.into_inner().into_inner();
if let Some(pos) = seek_pos {
file.seek(SeekFrom::Start(pos)).unwrap();
file.set_len(pos).unwrap();
}
csv::Writer::from_writer(BufWriter::new(file))
} else { // no header
let mut file = reader.into_inner().into_inner();
file.seek(SeekFrom::Start(0)).unwrap();
file.set_len(0).unwrap();
let mut writer = csv::Writer::from_writer(BufWriter::new(file));
// write the header
writer.write_record(row.iter().map(|m| m.name())).unwrap();
writer
}
},
CsvState::Writing(writer) => writer,
};
writer.write_record(row.iter().map(|m| m.machine_readable())).unwrap();
writer.flush().unwrap();
*lock = Some(CsvState::Writing(writer));
}
}