#![allow(missing_docs)]
#![allow(dead_code)]
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum ReplayCommand {
ApplyForce {
body_index: usize,
force: [f64; 3],
},
ApplyImpulse {
body_index: usize,
impulse: [f64; 3],
},
ApplyTorque {
body_index: usize,
torque: [f64; 3],
},
SetVelocity {
body_index: usize,
velocity: [f64; 3],
},
SetAngularVelocity {
body_index: usize,
angular_velocity: [f64; 3],
},
SetPosition {
body_index: usize,
position: [f64; 3],
},
Wake { body_index: usize },
}
impl ReplayCommand {
pub fn body_index(&self) -> usize {
match self {
ReplayCommand::ApplyForce { body_index, .. } => *body_index,
ReplayCommand::ApplyImpulse { body_index, .. } => *body_index,
ReplayCommand::ApplyTorque { body_index, .. } => *body_index,
ReplayCommand::SetVelocity { body_index, .. } => *body_index,
ReplayCommand::SetAngularVelocity { body_index, .. } => *body_index,
ReplayCommand::SetPosition { body_index, .. } => *body_index,
ReplayCommand::Wake { body_index } => *body_index,
}
}
pub fn kind_name(&self) -> &'static str {
match self {
ReplayCommand::ApplyForce { .. } => "ApplyForce",
ReplayCommand::ApplyImpulse { .. } => "ApplyImpulse",
ReplayCommand::ApplyTorque { .. } => "ApplyTorque",
ReplayCommand::SetVelocity { .. } => "SetVelocity",
ReplayCommand::SetAngularVelocity { .. } => "SetAngularVelocity",
ReplayCommand::SetPosition { .. } => "SetPosition",
ReplayCommand::Wake { .. } => "Wake",
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ReplayStep {
pub step_index: u64,
pub dt: f64,
pub commands: Vec<ReplayCommand>,
}
impl ReplayStep {
pub fn command_count(&self) -> usize {
self.commands.len()
}
pub fn forces(&self) -> impl Iterator<Item = (usize, [f64; 3])> + '_ {
self.commands.iter().filter_map(|c| {
if let ReplayCommand::ApplyForce { body_index, force } = c {
Some((*body_index, *force))
} else {
None
}
})
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ReplayRecord {
pub version: u32,
pub seed: u64,
pub label: String,
pub steps: Vec<ReplayStep>,
}
impl ReplayRecord {
pub fn new(seed: u64) -> Self {
Self {
version: 1,
seed,
label: String::new(),
steps: Vec::new(),
}
}
pub fn with_label(seed: u64, label: impl Into<String>) -> Self {
Self {
version: 1,
seed,
label: label.into(),
steps: Vec::new(),
}
}
pub fn step_count(&self) -> usize {
self.steps.len()
}
pub fn total_sim_time(&self) -> f64 {
self.steps.iter().map(|s| s.dt).sum()
}
pub fn total_commands(&self) -> usize {
self.steps.iter().map(|s| s.commands.len()).sum()
}
pub fn step(&self, index: usize) -> Option<&ReplayStep> {
self.steps.get(index)
}
pub fn to_json(&self) -> Result<String, String> {
serde_json::to_string_pretty(self).map_err(|e| e.to_string())
}
pub fn from_json(s: &str) -> Result<Self, String> {
serde_json::from_str(s).map_err(|e| e.to_string())
}
pub fn to_json_compact(&self) -> Result<String, String> {
serde_json::to_string(self).map_err(|e| e.to_string())
}
pub fn summary(&self) -> String {
format!(
"ReplayRecord {{ version={}, seed={}, label={:?}, steps={}, \
total_time={:.4}s, total_commands={} }}",
self.version,
self.seed,
self.label,
self.step_count(),
self.total_sim_time(),
self.total_commands()
)
}
}
#[derive(Debug)]
pub struct SimRecorder {
record: ReplayRecord,
current: Option<ReplayStep>,
}
impl SimRecorder {
pub fn new(seed: u64) -> Self {
Self {
record: ReplayRecord::new(seed),
current: None,
}
}
pub fn with_label(seed: u64, label: impl Into<String>) -> Self {
Self {
record: ReplayRecord::with_label(seed, label),
current: None,
}
}
pub fn begin_step(&mut self, dt: f64) {
debug_assert!(self.current.is_none(), "begin_step called without end_step");
let step_index = self.record.steps.len() as u64;
self.current = Some(ReplayStep {
step_index,
dt,
commands: Vec::new(),
});
}
fn push(&mut self, cmd: ReplayCommand) {
if let Some(s) = &mut self.current {
s.commands.push(cmd);
}
}
pub fn record_force(&mut self, body_index: usize, force: [f64; 3]) {
self.push(ReplayCommand::ApplyForce { body_index, force });
}
pub fn record_impulse(&mut self, body_index: usize, impulse: [f64; 3]) {
self.push(ReplayCommand::ApplyImpulse {
body_index,
impulse,
});
}
pub fn record_torque(&mut self, body_index: usize, torque: [f64; 3]) {
self.push(ReplayCommand::ApplyTorque { body_index, torque });
}
pub fn record_set_velocity(&mut self, body_index: usize, velocity: [f64; 3]) {
self.push(ReplayCommand::SetVelocity {
body_index,
velocity,
});
}
pub fn record_set_angular_velocity(&mut self, body_index: usize, av: [f64; 3]) {
self.push(ReplayCommand::SetAngularVelocity {
body_index,
angular_velocity: av,
});
}
pub fn record_set_position(&mut self, body_index: usize, position: [f64; 3]) {
self.push(ReplayCommand::SetPosition {
body_index,
position,
});
}
pub fn record_wake(&mut self, body_index: usize) {
self.push(ReplayCommand::Wake { body_index });
}
pub fn end_step(&mut self) {
if let Some(step) = self.current.take() {
self.record.steps.push(step);
}
}
pub fn finish(mut self) -> ReplayRecord {
if self.current.is_some() {
self.end_step();
}
self.record
}
pub fn recorded_steps(&self) -> usize {
self.record.steps.len()
}
pub fn record(&self) -> &ReplayRecord {
&self.record
}
}
#[derive(Debug)]
pub struct SimReplayer {
record: ReplayRecord,
cursor: usize,
}
impl SimReplayer {
pub fn new(record: ReplayRecord) -> Self {
Self { record, cursor: 0 }
}
pub fn is_done(&self) -> bool {
self.cursor >= self.record.steps.len()
}
pub fn current_step(&self) -> Option<&ReplayStep> {
self.record.steps.get(self.cursor)
}
pub fn advance(&mut self) -> Option<&ReplayStep> {
let step = self.record.steps.get(self.cursor)?;
self.cursor += 1;
Some(step)
}
pub fn steps_remaining(&self) -> usize {
self.record.steps.len().saturating_sub(self.cursor)
}
pub fn total_steps(&self) -> usize {
self.record.steps.len()
}
pub fn reset(&mut self) {
self.cursor = 0;
}
pub fn seek(&mut self, step: usize) -> bool {
if step <= self.record.steps.len() {
self.cursor = step;
true
} else {
false
}
}
pub fn into_record(self) -> ReplayRecord {
self.record
}
pub fn record(&self) -> &ReplayRecord {
&self.record
}
pub fn cursor(&self) -> usize {
self.cursor
}
}
impl Iterator for SimReplayer {
type Item = ReplayStep;
fn next(&mut self) -> Option<Self::Item> {
if self.cursor < self.record.steps.len() {
let step = self.record.steps[self.cursor].clone();
self.cursor += 1;
Some(step)
} else {
None
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_record() -> ReplayRecord {
let mut rec = SimRecorder::new(12345);
for i in 0..5u64 {
rec.begin_step(1.0 / 60.0);
rec.record_force(0, [0.0, f64::from(i as u32) * 10.0, 0.0]);
if i % 2 == 0 {
rec.record_impulse(1, [1.0, 0.0, 0.0]);
}
rec.end_step();
}
rec.finish()
}
#[test]
fn record_step_count() {
let r = make_record();
assert_eq!(r.step_count(), 5);
}
#[test]
fn record_total_commands() {
let r = make_record();
assert_eq!(r.total_commands(), 8);
}
#[test]
fn record_total_sim_time() {
let r = make_record();
let expected = 5.0 / 60.0;
assert!((r.total_sim_time() - expected).abs() < 1e-14);
}
#[test]
fn replayer_all_steps() {
let r = make_record();
let mut rep = SimReplayer::new(r);
let mut count = 0;
while let Some(_s) = rep.advance() {
count += 1;
}
assert_eq!(count, 5);
assert!(rep.is_done());
}
#[test]
fn replayer_reset() {
let r = make_record();
let mut rep = SimReplayer::new(r);
assert!(rep.advance().is_some());
rep.reset();
assert_eq!(rep.cursor(), 0);
assert!(!rep.is_done());
}
#[test]
fn replayer_seek() {
let r = make_record();
let mut rep = SimReplayer::new(r);
assert!(rep.seek(3));
assert_eq!(rep.cursor(), 3);
assert_eq!(rep.steps_remaining(), 2);
}
#[test]
fn replayer_as_iterator() {
let r = make_record();
let steps: Vec<_> = SimReplayer::new(r).collect();
assert_eq!(steps.len(), 5);
assert_eq!(steps[0].step_index, 0);
assert_eq!(steps[4].step_index, 4);
}
#[test]
fn json_round_trip() {
let r = make_record();
let json = r.to_json().expect("serialise");
let r2 = ReplayRecord::from_json(&json).expect("deserialise");
assert_eq!(r, r2);
}
#[test]
fn json_compact_round_trip() {
let r = make_record();
let json = r.to_json_compact().expect("compact serialise");
let r2 = ReplayRecord::from_json(&json).expect("deserialise");
assert_eq!(r, r2);
}
#[test]
fn summary_contains_step_count() {
let r = make_record();
let s = r.summary();
assert!(s.contains("steps=5"));
}
#[test]
fn command_kind_names() {
let cmds = [
ReplayCommand::ApplyForce {
body_index: 0,
force: [0.0; 3],
},
ReplayCommand::ApplyImpulse {
body_index: 0,
impulse: [0.0; 3],
},
ReplayCommand::SetPosition {
body_index: 0,
position: [0.0; 3],
},
ReplayCommand::Wake { body_index: 0 },
];
assert_eq!(cmds[0].kind_name(), "ApplyForce");
assert_eq!(cmds[1].kind_name(), "ApplyImpulse");
assert_eq!(cmds[2].kind_name(), "SetPosition");
assert_eq!(cmds[3].kind_name(), "Wake");
}
#[test]
fn step_forces_iterator() {
let mut rec = SimRecorder::new(0);
rec.begin_step(0.01);
rec.record_force(0, [1.0, 0.0, 0.0]);
rec.record_force(1, [0.0, 2.0, 0.0]);
rec.record_impulse(0, [0.0, 0.0, 1.0]); rec.end_step();
let rec = rec.finish();
let forces: Vec<_> = rec.steps[0].forces().collect();
assert_eq!(forces.len(), 2);
assert_eq!(forces[0], (0, [1.0, 0.0, 0.0]));
}
#[test]
fn recorder_finish_auto_end_step() {
let mut rec = SimRecorder::new(0);
rec.begin_step(0.01);
rec.record_force(0, [0.0; 3]);
let r = rec.finish();
assert_eq!(r.step_count(), 1);
}
}