use std::{any::type_name, collections::HashMap, fs::File, io::BufWriter, path::Path};
use better_any::{Tid, TidAble};
use erased_serde::Serialize as DynSerialize;
use eyre::WrapErr;
use serde::Serialize;
use crate::{component::ExecResult, state::common, CustomState, Problem, State};
#[derive(Default, Serialize, Tid)]
#[serde(transparent)]
pub struct Log {
steps: Vec<Step>,
}
impl CustomState<'_> for Log {}
impl Log {
pub fn new() -> Self {
Self::default()
}
pub(crate) fn push(&mut self, entry: Step) {
self.steps.push(entry);
}
pub(crate) fn steps(&self) -> &[Step] {
&self.steps
}
}
#[derive(Default, Serialize)]
#[serde(transparent)]
pub(crate) struct Step {
entries: Vec<Entry>,
}
impl Step {
pub(crate) fn contains(&self, name: &str) -> bool {
self.entries.iter().any(|entry| entry.name == name)
}
pub(crate) fn push(&mut self, entry: Entry) {
if !self.contains(entry.name) {
self.entries.push(entry);
}
}
pub(crate) fn push_iteration<P: Problem>(&mut self, state: &State<P>) {
let name = type_name::<common::Iterations>();
if !self.contains(name) {
let value = Box::new(state.iterations());
self.entries.insert(0, Entry { name, value });
}
}
pub(crate) fn entries(&self) -> &[Entry] {
&self.entries
}
}
#[derive(Serialize)]
pub struct Entry {
pub name: &'static str,
pub value: Box<dyn DynSerialize + Send>,
}
#[derive(Default, Serialize)]
struct CompressedLog<'a> {
names: Vec<&'static str>,
entries: Vec<HashMap<usize, &'a dyn DynSerialize>>,
}
impl<'a> From<&'a Log> for CompressedLog<'a> {
fn from(log: &'a Log) -> Self {
let mut clog = CompressedLog::default();
let mut next_key = 0;
let mut keys: HashMap<&'static str, usize> = HashMap::new();
for step in log.steps() {
let mut cstep = HashMap::with_capacity(step.entries().len());
for entry in step.entries() {
let key = *keys.entry(entry.name).or_insert_with(|| {
clog.names.push(entry.name);
let key = next_key;
next_key += 1;
key
});
let value: &'a dyn DynSerialize = &entry.value;
cstep.insert(key, value);
}
clog.entries.push(cstep);
}
clog
}
}
fn create_writer(path: impl AsRef<Path>) -> ExecResult<impl std::io::Write> {
let file = File::create(path.as_ref()).wrap_err("failed to create log file")?;
Ok(BufWriter::new(file))
}
impl Log {
fn as_compressed(&self) -> CompressedLog {
self.into()
}
pub fn to_json(&self, path: impl AsRef<Path>) -> ExecResult<()> {
let writer = create_writer(path)?;
serde_json::to_writer_pretty(writer, &self.as_compressed())
.wrap_err("failed to write json log")?;
Ok(())
}
pub fn to_cbor(&self, path: impl AsRef<Path>) -> ExecResult<()> {
let writer = create_writer(path)?;
ciborium::ser::into_writer(&self.as_compressed(), writer)
.wrap_err("failed to write cbor log")?;
Ok(())
}
}