Add an (untested) way to persist measurements to disk
This commit is contained in:
@@ -10,6 +10,7 @@ edition = "2018"
|
|||||||
bincode = "1.3.1"
|
bincode = "1.3.1"
|
||||||
common_macros = "0.1"
|
common_macros = "0.1"
|
||||||
crossterm = "0.18"
|
crossterm = "0.18"
|
||||||
|
csv = "1.1"
|
||||||
decorum = "0.3"
|
decorum = "0.3"
|
||||||
dyn-clone = "1.0"
|
dyn-clone = "1.0"
|
||||||
enum_dispatch = "0.3"
|
enum_dispatch = "0.3"
|
||||||
|
@@ -15,6 +15,15 @@ pub trait AbstractMeasurement: Send + Sync + DynClone {
|
|||||||
}
|
}
|
||||||
dyn_clone::clone_trait_object!(AbstractMeasurement);
|
dyn_clone::clone_trait_object!(AbstractMeasurement);
|
||||||
|
|
||||||
|
pub fn eval_multiple_kv(state: &dyn GenericSim, meas: &[Box<dyn AbstractMeasurement>]) -> BTreeMap<String, String> {
|
||||||
|
let mut r = BTreeMap::new();
|
||||||
|
for m in meas {
|
||||||
|
let mut other = m.key_value(state);
|
||||||
|
r.append(&mut other);
|
||||||
|
}
|
||||||
|
r
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize)]
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
pub struct Time;
|
pub struct Time;
|
||||||
|
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
use crate::geom::{Index, Meters, Vec2, Vec3, Vec3u};
|
use crate::geom::{Index, Meters, Vec2, Vec3, Vec3u};
|
||||||
use crate::real::ToFloat as _;
|
use crate::real::ToFloat as _;
|
||||||
use crate::sim::{GenericSim, Sample, StaticSim};
|
use crate::sim::{GenericSim, Sample, StaticSim};
|
||||||
use crate::meas::AbstractMeasurement;
|
use crate::meas::{self, AbstractMeasurement};
|
||||||
use crossterm::{cursor, QueueableCommand as _};
|
use crossterm::{cursor, QueueableCommand as _};
|
||||||
use crossterm::style::{style, Color, PrintStyledContent};
|
use crossterm::style::{style, Color, PrintStyledContent};
|
||||||
use font8x8::{BASIC_FONTS, GREEK_FONTS, UnicodeFonts as _};
|
use font8x8::{BASIC_FONTS, GREEK_FONTS, UnicodeFonts as _};
|
||||||
@@ -12,9 +12,9 @@ use image::{RgbImage, Rgb};
|
|||||||
use imageproc::{pixelops, drawing};
|
use imageproc::{pixelops, drawing};
|
||||||
use rayon::prelude::*;
|
use rayon::prelude::*;
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
use std::fs::File;
|
use std::fs::{File, OpenOptions};
|
||||||
use std::io::{BufReader, BufWriter, Write as _};
|
use std::io::{BufReader, BufWriter, Seek as _, SeekFrom, Write as _};
|
||||||
use std::path::PathBuf;
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::{Mutex, RwLock};
|
use std::sync::{Mutex, RwLock};
|
||||||
use y4m;
|
use y4m;
|
||||||
|
|
||||||
@@ -718,3 +718,72 @@ impl<S: GenericSim + Serialize> Renderer<S> for SerializerRenderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S: GenericSim> Renderer<S> for CsvRenderer {
|
||||||
|
fn render_z_slice(&self, state: &S, z: u32, measurements: &[Box<dyn AbstractMeasurement>], config: RenderConfig) {
|
||||||
|
default_render_z_slice(self, state, z, measurements, config)
|
||||||
|
}
|
||||||
|
fn render(&self, state: &S, measurements: &[Box<dyn AbstractMeasurement>], _config: RenderConfig) {
|
||||||
|
let row = meas::eval_multiple_kv(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.keys()).unwrap();
|
||||||
|
writer
|
||||||
|
}
|
||||||
|
},
|
||||||
|
CsvState::Writing(writer) => writer,
|
||||||
|
};
|
||||||
|
writer.write_record(row.values()).unwrap();
|
||||||
|
*lock = Some(CsvState::Writing(writer));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user