/ src / execution.rs
execution.rs
  1  use crate::event::TimedEvent;
  2  use crate::gfx::Rgba8;
  3  use crate::util;
  4  
  5  use std::collections::VecDeque;
  6  use std::fmt;
  7  use std::fs::File;
  8  use std::io;
  9  use std::io::{BufRead, Write};
 10  use std::path::{Path, PathBuf};
 11  use std::str::FromStr;
 12  use std::time;
 13  
 14  use seahash::SeaHasher;
 15  
 16  #[derive(Debug, Clone, Eq, PartialEq)]
 17  pub enum GifMode {
 18      Ignore,
 19      Record,
 20  }
 21  
 22  /// Determines whether frame digests are recorded, verified or ignored.
 23  #[derive(Debug, Clone, Copy, PartialEq, Eq)]
 24  pub enum DigestMode {
 25      /// Verify digests.
 26      Verify,
 27      /// Record digests.
 28      Record,
 29      /// Ignore digest.
 30      Ignore,
 31  }
 32  
 33  pub struct DigestState {
 34      pub mode: DigestMode,
 35      pub path: Option<PathBuf>,
 36  }
 37  
 38  impl DigestState {
 39      pub fn from<P: AsRef<Path>>(mode: DigestMode, path: P) -> io::Result<Self> {
 40          match mode {
 41              DigestMode::Verify => Self::verify(path),
 42              DigestMode::Record => Self::record(path),
 43              DigestMode::Ignore => Self::ignore(),
 44          }
 45      }
 46  
 47      pub fn verify<P: AsRef<Path>>(path: P) -> io::Result<Self> {
 48          let mut frames = Vec::new();
 49          let path = path.as_ref();
 50  
 51          match File::open(path) {
 52              Ok(f) => {
 53                  let r = io::BufReader::new(f);
 54                  for line in r.lines() {
 55                      let line = line?;
 56                      let hash = Hash::from_str(line.as_str())
 57                          .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?;
 58                      frames.push(hash);
 59                  }
 60              }
 61              Err(e) => {
 62                  return Err(io::Error::new(
 63                      e.kind(),
 64                      format!("{}: {}", path.display(), e),
 65                  ));
 66              }
 67          }
 68  
 69          Ok(Self {
 70              mode: DigestMode::Verify,
 71              path: Some(path.into()),
 72          })
 73      }
 74  
 75      pub fn record<P: AsRef<Path>>(path: P) -> io::Result<Self> {
 76          Ok(Self {
 77              mode: DigestMode::Record,
 78              path: Some(path.as_ref().into()),
 79          })
 80      }
 81  
 82      pub fn ignore() -> io::Result<Self> {
 83          Ok(Self {
 84              mode: DigestMode::Ignore,
 85              path: None,
 86          })
 87      }
 88  }
 89  
 90  #[derive(Debug, Clone)]
 91  pub enum ExecutionMode {
 92      Normal,
 93      Record(PathBuf, DigestMode, GifMode),
 94      Replay(PathBuf, DigestMode),
 95  }
 96  
 97  /// Execution mode. Controls whether the session is playing or recording
 98  /// commands.
 99  // TODO: Make this a `struct` and have `ExecutionMode`.
100  #[derive(Default)]
101  pub enum Execution {
102      /// Normal execution. User inputs are processed normally.
103      #[default]
104      Normal,
105      /// Recording user inputs to log.
106      Recording {
107          /// Events being recorded.
108          events: Vec<TimedEvent>,
109          /// Start time of recording.
110          start: time::Instant,
111          /// Path to save recording to.
112          path: PathBuf,
113          /// Digest mode.
114          digest: DigestState,
115          /// Frame recorder.
116          recorder: FrameRecorder,
117      },
118      /// Replaying inputs from log.
119      Replaying {
120          /// Events being replayed.
121          events: VecDeque<TimedEvent>,
122          /// Start time of the playback.
123          start: time::Instant,
124          /// Path to read events from.
125          path: PathBuf,
126          /// Digest mode.
127          digest: DigestState,
128          /// Replay result.
129          result: ReplayResult,
130          /// Frame recorder.
131          recorder: FrameRecorder,
132      },
133  }
134  
135  impl Execution {
136      /// Create a normal execution.
137      pub fn normal() -> io::Result<Self> {
138          Ok(Self::Normal)
139      }
140  
141      /// Create a recording.
142      pub fn recording<P: AsRef<Path>>(
143          path: P,
144          digest_mode: DigestMode,
145          w: u16,
146          h: u16,
147          gif_mode: GifMode,
148      ) -> io::Result<Self> {
149          use io::{Error, ErrorKind};
150  
151          let path = path.as_ref();
152          let file_name: &Path = path
153              .file_name()
154              .ok_or(Error::new(
155                  ErrorKind::InvalidInput,
156                  format!("invalid path {:?}", path),
157              ))?
158              .as_ref();
159  
160          std::fs::create_dir_all(path)?;
161  
162          let digest = DigestState::from(digest_mode, path.join(file_name).with_extension("digest"))?;
163          let gif_recorder = if gif_mode == GifMode::Record {
164              GifRecorder::new(path.join(file_name).with_extension("gif"), w, h)?
165          } else {
166              GifRecorder::dummy()
167          };
168          let recorder = FrameRecorder::new(gif_recorder, gif_mode, digest_mode);
169  
170          Ok(Self::Recording {
171              events: Vec::new(),
172              start: time::Instant::now(),
173              path: path.to_path_buf(),
174              digest,
175              recorder,
176          })
177      }
178  
179      /// Create a replay.
180      pub fn replaying<P: AsRef<Path>>(path: P, mode: DigestMode) -> io::Result<Self> {
181          use io::{Error, ErrorKind};
182  
183          let mut events = VecDeque::new();
184          let path = path.as_ref();
185  
186          let file_name: &Path = path
187              .file_name()
188              .ok_or(Error::new(
189                  ErrorKind::InvalidInput,
190                  format!("invalid path {:?}", path),
191              ))?
192              .as_ref();
193  
194          let digest = DigestState::from(mode, path.join(file_name).with_extension("digest"))?;
195  
196          let recorder = match &digest {
197              DigestState {
198                  path: Some(path),
199                  mode: DigestMode::Verify,
200              } => {
201                  let mut frames = Vec::new();
202  
203                  match File::open(path) {
204                      Ok(f) => {
205                          let r = io::BufReader::new(f);
206                          for line in r.lines() {
207                              let line = line?;
208                              let hash = Hash::from_str(line.as_str())
209                                  .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?;
210                              frames.push(hash);
211                          }
212                      }
213                      Err(e) => {
214                          return Err(io::Error::new(
215                              e.kind(),
216                              format!("{}: {}", path.display(), e),
217                          ));
218                      }
219                  }
220                  FrameRecorder::from(frames, mode)
221              }
222              _ => FrameRecorder::new(GifRecorder::dummy(), GifMode::Ignore, mode),
223          };
224  
225          let events_path = path.join(file_name).with_extension("events");
226          match File::open(&events_path) {
227              Ok(f) => {
228                  let r = io::BufReader::new(f);
229                  for (i, line) in r.lines().enumerate() {
230                      let line = line?;
231                      let ev = TimedEvent::from_str(&line).map_err(|e| {
232                          io::Error::new(
233                              io::ErrorKind::InvalidInput,
234                              format!("{}:{}: {}", events_path.display(), i + 1, e),
235                          )
236                      })?;
237                      events.push_back(ev);
238                  }
239                  Ok(Self::Replaying {
240                      events,
241                      start: time::Instant::now(),
242                      path: path.to_path_buf(),
243                      digest,
244                      result: ReplayResult::new(),
245                      recorder,
246                  })
247              }
248              Err(e) => Err(io::Error::new(
249                  e.kind(),
250                  format!("{}: {}", events_path.display(), e),
251              )),
252          }
253      }
254  
255      pub fn is_normal(&self) -> bool {
256          matches!(self, Execution::Normal)
257      }
258  
259      pub fn is_recording(&self) -> bool {
260          matches!(self, Execution::Recording { .. })
261      }
262  
263      pub fn record(&mut self, data: &[Rgba8]) -> Result<(), VerifyResult> {
264          match self {
265              // Replaying and verifying digests.
266              Self::Replaying {
267                  digest:
268                      DigestState {
269                          mode: DigestMode::Verify,
270                          ..
271                      },
272                  result,
273                  recorder,
274                  ..
275              } => {
276                  let vr = recorder.verify_frame(data);
277                  result.record(&vr);
278  
279                  if vr.is_err() {
280                      return Err(vr);
281                  }
282              }
283              // Replaying/Recording and recording digests.
284              Self::Replaying { recorder, .. } | Self::Recording { recorder, .. } => {
285                  recorder.record_frame(data);
286              }
287              // In normal mode, we don't actual record anything.
288              _ => {}
289          }
290          Ok(())
291      }
292  
293      ////////////////////////////////////////////////////////////////////////////
294  
295      pub fn stop_recording(&mut self) -> io::Result<PathBuf> {
296          use io::{Error, ErrorKind};
297  
298          let result = if let Execution::Recording {
299              events,
300              path,
301              digest,
302              recorder,
303              ..
304          } = self
305          {
306              recorder.finish()?;
307  
308              let file_name: &Path = path
309                  .file_name()
310                  .ok_or(Error::new(
311                      ErrorKind::InvalidInput,
312                      format!("invalid path {:?}", path),
313                  ))?
314                  .as_ref();
315  
316              let mut f = File::create(path.join(file_name.with_extension("events")))?;
317              for ev in events.clone() {
318                  writeln!(&mut f, "{}", String::from(ev))?;
319              }
320  
321              if let DigestState {
322                  mode: DigestMode::Record,
323                  path: Some(path),
324                  ..
325              } = digest
326              {
327                  Execution::write_digest(recorder, path)?;
328              }
329  
330              Ok(path.clone())
331          } else {
332              panic!("record finalizer called outside of recording context")
333          };
334  
335          if result.is_ok() {
336              *self = Execution::Normal;
337          }
338          result
339      }
340  
341      pub fn finalize_replaying(&self) -> io::Result<PathBuf> {
342          if let Execution::Replaying {
343              digest:
344                  DigestState {
345                      mode: DigestMode::Record,
346                      path: Some(path),
347                      ..
348                  },
349              recorder,
350              ..
351          } = &self
352          {
353              Execution::write_digest(recorder, path)?;
354              Ok(path.clone())
355          } else {
356              panic!("replay finalizer called outside of replay context")
357          }
358      }
359  
360      ////////////////////////////////////////////////////////////////////////////
361  
362      fn write_digest<P: AsRef<Path>>(recorder: &FrameRecorder, path: P) -> io::Result<()> {
363          let path = path.as_ref();
364  
365          if let Some(parent) = path.parent() {
366              std::fs::create_dir_all(parent)?;
367          }
368          let mut f = File::create(path)?;
369  
370          for frame in &recorder.frames {
371              writeln!(&mut f, "{}", frame)?;
372          }
373          Ok(())
374      }
375  }
376  
377  pub struct GifRecorder {
378      width: u16,
379      height: u16,
380      encoder: Option<gif::Encoder<Box<File>>>,
381      frames: Vec<(time::Instant, Vec<Rgba8>)>,
382  }
383  
384  impl GifRecorder {
385      const GIF_ENCODING_SPEED: i32 = 30;
386  
387      pub fn new<P: AsRef<Path>>(path: P, width: u16, height: u16) -> io::Result<Self> {
388          let file = Box::new(File::create(path.as_ref())?);
389          let encoder = Some(gif::Encoder::new(file, width, height, &[])?);
390  
391          Ok(Self {
392              width,
393              height,
394              encoder,
395              frames: Vec::new(),
396          })
397      }
398  
399      fn dummy() -> Self {
400          Self {
401              width: 0,
402              height: 0,
403              encoder: None,
404              frames: Vec::new(),
405          }
406      }
407  
408      fn is_dummy(&self) -> bool {
409          self.width == 0 && self.height == 0
410      }
411  
412      fn record(&mut self, data: &[Rgba8]) {
413          if self.is_dummy() {
414              return;
415          }
416          let now = time::Instant::now();
417  
418          self.frames.push((now, data.to_vec()));
419      }
420  
421      fn finish(&mut self) -> io::Result<()> {
422          if let Some(encoder) = &mut self.encoder {
423              for (i, (t1, gif_data)) in self.frames.iter().enumerate() {
424                  let delay = if let Some((t2, _)) = self.frames.get(i + 1) {
425                      *t2 - *t1
426                  } else {
427                      // Let the last frame linger for a second...
428                      time::Duration::from_secs(1)
429                  };
430  
431                  let data = util::align_u8(gif_data);
432                  let mut frame = gif::Frame::from_rgb_speed(
433                      self.width,
434                      self.height,
435                      data,
436                      Self::GIF_ENCODING_SPEED,
437                  );
438                  frame.dispose = gif::DisposalMethod::Background;
439                  frame.delay = (delay.as_millis() / 10)
440                      .try_into()
441                      .expect("`delay` is not an unreasonably large number");
442  
443                  encoder.write_frame(&frame)?;
444              }
445          }
446          Ok(())
447      }
448  }
449  
450  /// Records and verifies frames being replayed.
451  pub struct FrameRecorder {
452      frames: VecDeque<Hash>,
453      last_verified: Option<Hash>,
454      gif_recorder: GifRecorder,
455      gif_mode: GifMode,
456      digest_mode: DigestMode,
457  }
458  
459  impl FrameRecorder {
460      fn new(gif_recorder: GifRecorder, gif_mode: GifMode, digest_mode: DigestMode) -> Self {
461          Self {
462              frames: VecDeque::new(),
463              last_verified: None,
464              gif_recorder,
465              gif_mode,
466              digest_mode,
467          }
468      }
469  
470      fn from(frames: Vec<Hash>, digest_mode: DigestMode) -> Self {
471          Self {
472              frames: frames.into(),
473              last_verified: None,
474              gif_recorder: GifRecorder::dummy(),
475              gif_mode: GifMode::Ignore,
476              digest_mode,
477          }
478      }
479  
480      fn record_frame(&mut self, data: &[Rgba8]) {
481          let hash = Self::hash(data);
482  
483          if self.frames.back().map(|h| h != &hash).unwrap_or(true) {
484              debug!("frame: {}", hash);
485  
486              if self.digest_mode == DigestMode::Record || self.gif_mode == GifMode::Record {
487                  self.frames.push_back(hash);
488              }
489  
490              self.gif_recorder.record(data);
491          }
492      }
493  
494      fn verify_frame(&mut self, data: &[Rgba8]) -> VerifyResult {
495          let actual = Self::hash(data);
496  
497          if self.frames.is_empty() {
498              return VerifyResult::Eof;
499          }
500          if Some(actual.clone()) == self.last_verified {
501              return VerifyResult::Stale(actual);
502          }
503          self.last_verified = Some(actual.clone());
504  
505          if let Some(expected) = self.frames.pop_front() {
506              if actual == expected {
507                  VerifyResult::Okay(actual)
508              } else {
509                  VerifyResult::Failed(actual, expected)
510              }
511          } else {
512              VerifyResult::Eof
513          }
514      }
515  
516      fn finish(&mut self) -> io::Result<()> {
517          self.gif_recorder.finish()
518      }
519  
520      fn hash(data: &[Rgba8]) -> Hash {
521          use std::hash::Hasher;
522  
523          let mut hasher = SeaHasher::new();
524          let data = util::align_u8(data);
525  
526          hasher.write(data);
527          Hash(hasher.finish())
528      }
529  }
530  
531  #[derive(Debug, Clone)]
532  pub struct ReplayResult {
533      eof: bool,
534      failed: bool,
535      okay_count: u32,
536      stale_count: u32,
537  }
538  
539  impl ReplayResult {
540      pub fn is_ok(&self) -> bool {
541          !self.failed
542      }
543  
544      pub fn is_err(&self) -> bool {
545          !self.is_ok()
546      }
547  
548      pub fn is_done(&self) -> bool {
549          self.eof
550      }
551  
552      pub fn summary(&self) -> String {
553          if self.is_err() {
554              format!("replay failed after {} frames", self.okay_count)
555          } else {
556              format!("ok ({} frames)", self.okay_count)
557          }
558      }
559  
560      ////////////////////////////////////////////////////////////////////////////
561  
562      fn new() -> Self {
563          ReplayResult {
564              okay_count: 0,
565              stale_count: 0,
566              failed: false,
567              eof: false,
568          }
569      }
570  
571      fn record(&mut self, result: &VerifyResult) {
572          match result {
573              VerifyResult::Okay(actual) => {
574                  info!("verify: {} OK", actual);
575                  self.okay_count += 1;
576              }
577              VerifyResult::Failed(actual, expected) => {
578                  error!("verify: {} != {}", actual, expected);
579                  self.failed = true;
580              }
581              VerifyResult::Eof => {
582                  self.eof = true;
583              }
584              VerifyResult::Stale { .. } => {
585                  self.stale_count += 1;
586              }
587          }
588      }
589  }
590  
591  #[derive(Debug, Clone)]
592  pub enum VerifyResult {
593      /// The hash has already been verified.
594      Stale(Hash),
595      /// The actual and expected hashes match.
596      Okay(Hash),
597      /// The actual and expected hashes don't match.
598      Failed(Hash, Hash),
599      /// There are no further expected hashes.
600      Eof,
601  }
602  
603  impl VerifyResult {
604      fn is_err(&self) -> bool {
605          matches!(self, Self::Failed(_, _))
606      }
607  }
608  
609  #[derive(PartialEq, Eq, Clone, Debug)]
610  pub struct Hash(u64);
611  
612  impl fmt::Display for Hash {
613      fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
614          write!(f, "{:016x}", self.0)
615      }
616  }
617  
618  impl FromStr for Hash {
619      type Err = std::num::ParseIntError;
620  
621      fn from_str(input: &str) -> Result<Self, Self::Err> {
622          u64::from_str_radix(input, 16).map(Hash)
623      }
624  }