use std::{
collections::{BTreeMap, VecDeque},
path::{Path, PathBuf},
time::SystemTime,
};
use crate::{dispatcher::Dispatcher, dispatcher::load_csv, peripheral::Peripheral};
pub struct ReplayCycle {
pub time: SystemTime,
pub timestamp: i64,
pub peripheral_outputs: BTreeMap<String, Vec<f64>>,
}
pub trait ReplaySource {
fn init(&mut self, peripherals: &BTreeMap<String, Box<dyn Peripheral>>) -> Result<(), String>;
fn next_cycle(&mut self) -> Result<Option<ReplayCycle>, String>;
}
pub struct CsvReplaySource {
path: PathBuf,
rows: VecDeque<ReplayCycle>,
}
impl CsvReplaySource {
pub fn new(path: impl Into<PathBuf>) -> Self {
Self {
path: path.into(),
rows: VecDeque::new(),
}
}
pub fn path(&self) -> &Path {
&self.path
}
}
impl ReplaySource for CsvReplaySource {
fn init(&mut self, peripherals: &BTreeMap<String, Box<dyn Peripheral>>) -> Result<(), String> {
self.rows = read_csv_replay_rows(&self.path, peripherals)?;
Ok(())
}
fn next_cycle(&mut self) -> Result<Option<ReplayCycle>, String> {
Ok(self.rows.pop_front())
}
}
fn read_csv_replay_rows(
path: &Path,
peripherals: &BTreeMap<String, Box<dyn Peripheral>>,
) -> Result<VecDeque<ReplayCycle>, String> {
let csv = load_csv(path)?;
let peripheral_columns = peripheral_output_columns(csv.channel_names(), peripherals)?;
let mut rows = VecDeque::new();
for row in csv.rows() {
let mut peripheral_outputs = BTreeMap::new();
for (peripheral_name, column_indices) in &peripheral_columns {
let mut outputs = Vec::with_capacity(column_indices.len());
for &column_idx in column_indices {
outputs.push(row.channel_values[column_idx]);
}
peripheral_outputs.insert(peripheral_name.clone(), outputs);
}
rows.push_back(ReplayCycle {
time: row.time,
timestamp: row.timestamp,
peripheral_outputs,
});
}
Ok(rows)
}
fn peripheral_output_columns(
channel_names: &[String],
peripherals: &BTreeMap<String, Box<dyn Peripheral>>,
) -> Result<BTreeMap<String, Vec<usize>>, String> {
let mut columns = BTreeMap::new();
for (peripheral_name, peripheral) in peripherals {
let mut peripheral_columns = Vec::new();
for output_name in peripheral.output_names() {
let field_name = format!("{peripheral_name}.{output_name}");
peripheral_columns.push(
channel_names
.iter()
.position(|channel_name| channel_name == &field_name)
.ok_or_else(|| {
format!("Replay CSV is missing required raw output `{field_name}`")
})?,
);
}
columns.insert(peripheral_name.clone(), peripheral_columns);
}
Ok(columns)
}
pub(super) fn init_replay_dispatchers(
ctx: &mut crate::ControllerCtx,
dispatchers: &mut BTreeMap<String, Box<dyn Dispatcher>>,
channel_names: &[String],
channel_units: Vec<Option<String>>,
) -> Result<(), String> {
ctx.channel_units = channel_units;
for (core_assignment, dispatcher) in dispatchers.values_mut().enumerate() {
dispatcher.init(ctx, channel_names, core_assignment)?;
}
Ok(())
}