use alloc::vec::Vec;
use crate::core::clock::GlobalTime;
use crate::core::error::{Error, Result};
use crate::core::state::{Sink, Source};
use crate::core::sync::{self, LockRank};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum JournalMode {
#[default]
Live,
Record,
Replay,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
#[repr(transparent)]
pub struct Tag(pub u32);
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Answer {
pub value: u64,
pub bytes: Vec<u8>,
}
impl Answer {
#[must_use]
pub const fn value(value: u64) -> Answer {
Answer {
value,
bytes: Vec::new(),
}
}
#[must_use]
pub const fn with_bytes(value: u64, bytes: Vec<u8>) -> Answer {
Answer { value, bytes }
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct Entry {
at: u128,
tag: u32,
answer: Answer,
}
#[derive(Debug, Default)]
struct Inner {
mode: JournalMode,
entries: Vec<Entry>,
cursor: usize,
}
#[derive(Debug, Default)]
pub struct Journal {
inner: sync::Mutex<Inner>,
}
impl Journal {
#[must_use]
pub fn new() -> Journal {
Journal::with_mode(JournalMode::Live)
}
#[must_use]
pub fn with_mode(mode: JournalMode) -> Journal {
Journal {
inner: sync::Mutex::with_rank(
LockRank::LEAF,
Inner {
mode,
entries: Vec::new(),
cursor: 0,
},
),
}
}
#[must_use]
pub fn mode(&self) -> JournalMode {
self.inner.lock().mode
}
pub fn set_mode(&self, mode: JournalMode) {
let mut inner = self.inner.lock();
inner.mode = mode;
if mode == JournalMode::Replay {
inner.cursor = 0;
}
}
#[must_use]
pub fn len(&self) -> usize {
self.inner.lock().entries.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.inner.lock().entries.is_empty()
}
#[must_use]
pub fn remaining(&self) -> usize {
let inner = self.inner.lock();
inner.entries.len().saturating_sub(inner.cursor)
}
pub fn clear(&self) {
let mut inner = self.inner.lock();
inner.entries.clear();
inner.cursor = 0;
}
pub fn ask<F>(&self, at: GlobalTime, tag: Tag, f: F) -> Result<Answer>
where
F: FnOnce() -> Answer,
{
let mode = self.inner.lock().mode;
match mode {
JournalMode::Live => Ok(f()),
JournalMode::Record => {
let answer = f();
let mut inner = self.inner.lock();
inner.entries.push(Entry {
at: at.raw(),
tag: tag.0,
answer: answer.clone(),
});
Ok(answer)
}
JournalMode::Replay => {
let mut inner = self.inner.lock();
let cursor = inner.cursor;
let Some(entry) = inner.entries.get(cursor).cloned() else {
return Err(diverged(alloc::format!(
"the recording ended after {cursor} answer(s), but the guest asked \
a {tag:?} at {}ns",
at.as_nanos()
)));
};
if entry.tag != tag.0 {
return Err(diverged(alloc::format!(
"answer {cursor} was recorded for tag {} and the guest asked tag {}",
entry.tag,
tag.0
)));
}
if entry.at != at.raw() {
return Err(diverged(alloc::format!(
"answer {cursor} was recorded at {}ns and the guest asked at {}ns",
GlobalTime::from_raw(entry.at).as_nanos(),
at.as_nanos()
)));
}
inner.cursor = cursor + 1;
Ok(entry.answer)
}
}
}
pub fn save<S: Sink + ?Sized>(&self, sink: &mut S) -> Result<()> {
let inner = self.inner.lock();
sink.write_u64(inner.cursor as u64)?;
sink.write_seq_len(inner.entries.len() as u64)?;
for entry in &inner.entries {
sink.write_u128(entry.at)?;
sink.write_u32(entry.tag)?;
sink.write_u64(entry.answer.value)?;
sink.write_bytes(&entry.answer.bytes)?;
}
Ok(())
}
pub fn load<'a, S: Source<'a> + ?Sized>(&self, source: &mut S) -> Result<()> {
let cursor = source.read_u64()? as usize;
let count = source.read_seq_len(36)?;
let mut entries = Vec::new();
for _ in 0..count {
let at = source.read_u128()?;
let tag = source.read_u32()?;
let value = source.read_u64()?;
let bytes = source.read_bytes()?.to_vec();
entries.push(Entry {
at,
tag,
answer: Answer { value, bytes },
});
}
if cursor > entries.len() {
return Err(Error::State(alloc::format!(
"a replay cursor of {cursor} is past the {} recorded answer(s)",
entries.len()
)));
}
let mut inner = self.inner.lock();
inner.entries = entries;
inner.cursor = cursor;
Ok(())
}
}
fn diverged(detail: alloc::string::String) -> Error {
Error::State(alloc::format!(
"replay diverged from the recording: {detail}"
))
}