use crate::model::{
DistanceError, Lattice, LefStates, LoopGraph, Mobility, Position, Time, World, WorldError,
};
use super::record::{EventFrames, LefFrames, SampleMode};
#[derive(Copy, Clone, Debug)]
struct KeyframeRef {
chunk: usize,
row: usize,
time: Time,
}
#[derive(Clone, Debug)]
pub struct Replayer {
lattice: Lattice,
n_lefs: usize,
chunks: Vec<EventFrames>,
keyframes: Vec<KeyframeRef>,
cursor: (usize, usize),
time: Time,
end: Time,
ready: bool,
lefs: LefStates,
grid: Option<(SampleMode, u64)>,
}
#[derive(Clone, Debug, PartialEq)]
pub enum ReplayError {
OutOfOrder {
time: Time,
previous: Time,
},
BadEnd {
end: Time,
last_row: Time,
},
LegOutOfRange {
leg_id: u32,
n_legs: usize,
},
BadKind {
code: u8,
},
BadKeyframe {
time: Time,
rows: usize,
n_legs: usize,
},
NoKeyframe {
t: Time,
earliest: Option<Time>,
},
NotReady,
World(WorldError),
}
impl std::fmt::Display for ReplayError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ReplayError::OutOfOrder { time, previous } => {
write!(f, "row at t={time} comes after a row at t={previous}")
}
ReplayError::BadEnd { end, last_row } => {
write!(f, "chunk end {end} is before its last row at t={last_row}")
}
ReplayError::LegOutOfRange { leg_id, n_legs } => {
write!(f, "leg id {leg_id} is out of range for {n_legs} legs")
}
ReplayError::BadKind { code } => write!(f, "invalid row kind code {code}"),
ReplayError::BadKeyframe { time, rows, n_legs } => write!(
f,
"keyframe at t={time} has {rows} rows; expected {n_legs}, one per leg in order"
),
ReplayError::NoKeyframe {
t,
earliest: Some(e),
} => {
write!(
f,
"no keyframe at or before t={t} (the earliest is at t={e})"
)
}
ReplayError::NoKeyframe { t, earliest: None } => {
write!(f, "no keyframe at or before t={t} (none pushed yet)")
}
ReplayError::NotReady => {
write!(
f,
"no keyframe applied yet: push a keyframed chunk and advance"
)
}
ReplayError::World(e) => write!(f, "replayed state is invalid: {e}"),
}
}
}
impl std::error::Error for ReplayError {}
impl Replayer {
pub fn new(lattice: Lattice, n_lefs: usize) -> Self {
Replayer {
lattice,
n_lefs,
chunks: Vec::new(),
keyframes: Vec::new(),
cursor: (0, 0),
time: Time::NEG_INFINITY,
end: Time::NEG_INFINITY,
ready: false,
lefs: LefStates {
left_pos: vec![0; n_lefs],
right_pos: vec![0; n_lefs],
left_mobility: vec![Mobility::Moving; n_lefs],
right_mobility: vec![Mobility::Moving; n_lefs],
},
grid: None,
}
}
pub fn lattice(&self) -> &Lattice {
&self.lattice
}
pub fn n_lefs(&self) -> usize {
self.n_lefs
}
pub fn ready(&self) -> bool {
self.ready
}
pub fn time(&self) -> Time {
self.time
}
pub fn start(&self) -> Option<Time> {
self.keyframes.first().map(|k| k.time)
}
pub fn end(&self) -> Time {
self.end
}
pub fn push(&mut self, chunk: EventFrames, end: Time) -> Result<(), ReplayError> {
let n_legs = 2 * self.n_lefs;
let (time, leg_id, kind) = (chunk.time(), chunk.leg_id(), chunk.kind());
let mut previous = self.last_row_time();
for row in 0..chunk.n_rows() {
if time[row].is_nan() || time[row] < previous {
return Err(ReplayError::OutOfOrder {
time: time[row],
previous,
});
}
previous = time[row];
if leg_id[row] as usize >= n_legs {
return Err(ReplayError::LegOutOfRange {
leg_id: leg_id[row],
n_legs,
});
}
if kind[row] as usize >= EventFrames::KIND_NAMES.len() {
return Err(ReplayError::BadKind { code: kind[row] });
}
}
if end.is_nan() || end < previous {
return Err(ReplayError::BadEnd {
end,
last_row: previous,
});
}
let index = self.chunks.len();
let mut found = Vec::new();
let mut row = 0;
while row < chunk.n_rows() {
if kind[row] != EventFrames::KEYFRAME {
row += 1;
continue;
}
let start = row;
while row < chunk.n_rows()
&& kind[row] == EventFrames::KEYFRAME
&& time[row] == time[start]
&& leg_id[row] as usize == row - start
{
row += 1;
}
if row - start != n_legs {
let rows = row - start;
return Err(ReplayError::BadKeyframe {
time: time[start],
rows,
n_legs,
});
}
found.push(KeyframeRef {
chunk: index,
row: start,
time: time[start],
});
}
self.keyframes.extend(found);
self.chunks.push(chunk);
self.end = self.end.max(end);
Ok(())
}
pub fn advance_to(&mut self, t: Time) -> Time {
assert!(!t.is_nan(), "advance_to: t is NaN");
if !self.ready {
return match self.start() {
Some(first) if t >= first => self.seek(t).expect("a keyframe precedes t"),
_ => self.time,
};
}
if t < self.time {
let first = self.start().expect("ready means a keyframe was applied");
return self
.seek(t.max(first))
.expect("a keyframe is at or before the target");
}
let target = t.min(self.end);
if let Some(k) = self.keyframe_at_or_before(target)
&& (k.chunk, k.row) > self.cursor
{
self.cursor = (k.chunk, k.row);
}
self.apply_through(target);
self.time = self.time.max(target);
self.time
}
pub fn seek(&mut self, t: Time) -> Result<Time, ReplayError> {
assert!(!t.is_nan(), "seek: t is NaN");
let target = t.min(self.end);
let k = self
.keyframe_at_or_before(target)
.ok_or(ReplayError::NoKeyframe {
t,
earliest: self.start(),
})?;
self.cursor = (k.chunk, k.row);
self.apply_through(target);
self.ready = true;
self.time = target;
self.grid = None;
Ok(target)
}
pub fn lef_states(&self) -> Result<&LefStates, ReplayError> {
if !self.ready {
return Err(ReplayError::NotReady);
}
Ok(&self.lefs)
}
pub fn world(&self) -> Result<World, ReplayError> {
World::from_lef_states(self.lattice.clone(), self.lef_states()?).map_err(ReplayError::World)
}
pub fn snapshots(&mut self, mode: SampleMode, t_end: Time) -> LefFrames {
let SampleMode::EveryDt { dt, t0 } = mode else {
panic!("snapshots need a grid (SampleMode::EveryDt)");
};
assert!(
dt.is_finite() && dt > 0.0,
"dt must be positive and finite, got {dt}"
);
assert!(t0.is_finite(), "t0 must be finite, got {t0}");
assert!(!t_end.is_nan(), "snapshots: t_end is NaN");
let mut frames = LefFrames::new(self.n_lefs);
let from = if self.ready {
Some(self.time)
} else {
self.start()
};
let Some(from) = from else { return frames };
let point = |k: u64| t0 + k as f64 * dt; let mut k = ((from - t0) / dt).ceil().max(0.0) as u64;
while k > 0 && point(k - 1) >= from {
k -= 1;
}
while point(k) < from {
k += 1;
}
if let Some((m, next)) = self.grid
&& m == mode
{
k = k.max(next);
}
let last = t_end.min(self.end);
while point(k) <= last {
self.advance_to(point(k));
frames.push(point(k), &self.lefs);
k += 1;
}
self.grid = Some((mode, k));
frames
}
pub fn effective_distances(
&mut self,
mode: SampleMode,
t_end: Time,
sites: &[Position],
step_length: u64,
hop_length: u64,
) -> Result<(Vec<Time>, Vec<u64>), DistanceError> {
LoopGraph::from_legs(self.lattice.len(), &[], &[], step_length, hop_length)?
.pairwise(sites)?;
let frames = self.snapshots(mode, t_end);
let distances =
distances_over(&frames, self.lattice.len(), sites, step_length, hop_length)?;
Ok((frames.time().to_vec(), distances))
}
pub fn trim_before(&mut self, t: Time) {
let Some(k) = self.keyframe_at_or_before(t) else {
return;
};
let drop = k.chunk.min(self.cursor.0);
if drop == 0 {
return;
}
self.chunks.drain(..drop);
self.keyframes.retain(|r| r.chunk >= drop);
for r in &mut self.keyframes {
r.chunk -= drop;
}
self.cursor.0 -= drop;
}
fn last_row_time(&self) -> Time {
self.chunks
.iter()
.rev()
.find_map(|c| c.time().last().copied())
.unwrap_or(Time::NEG_INFINITY)
}
fn keyframe_at_or_before(&self, t: Time) -> Option<KeyframeRef> {
let n = self.keyframes.partition_point(|k| k.time <= t);
n.checked_sub(1).map(|i| self.keyframes[i])
}
fn apply_through(&mut self, t: Time) {
let (mut c, mut row) = self.cursor;
while let Some(chunk) = self.chunks.get(c) {
let n = chunk.n_rows();
while row < n && chunk.time()[row] <= t {
let (lef, pos, mob) = (
chunk.leg_id()[row] as usize / 2,
chunk.pos()[row],
chunk.mob()[row],
);
if chunk.leg_id()[row] % 2 == 0 {
(self.lefs.left_pos[lef], self.lefs.left_mobility[lef]) = (pos, mob);
} else {
(self.lefs.right_pos[lef], self.lefs.right_mobility[lef]) = (pos, mob);
}
row += 1;
}
if row < n {
break;
}
c += 1;
row = 0;
}
self.cursor = (c, row);
}
}
pub fn distances_over(
frames: &LefFrames,
n_sites: usize,
sites: &[Position],
step_length: u64,
hop_length: u64,
) -> Result<Vec<u64>, DistanceError> {
let k = sites.len();
let mut out = Vec::with_capacity(frames.n_snapshots() * k * k.saturating_sub(1) / 2);
for s in 0..frames.n_snapshots() {
let snap = frames.snapshot(s);
let graph = LoopGraph::from_legs(
n_sites,
snap.left_pos,
snap.right_pos,
step_length,
hop_length,
)?;
out.extend(graph.pairwise(sites)?);
}
Ok(out)
}