diff --git a/crates/hulk_replayer/src/replayer.rs b/crates/hulk_replayer/src/replayer.rs index 59ce3b4230..f0805106eb 100644 --- a/crates/hulk_replayer/src/replayer.rs +++ b/crates/hulk_replayer/src/replayer.rs @@ -1,4 +1,4 @@ -use std::{env::args, fs::File, path::PathBuf, sync::Arc, time::SystemTime}; +use std::{env::args, fs::File, path::PathBuf, sync::Arc}; use color_eyre::{ eyre::{Report, WrapErr}, @@ -15,7 +15,10 @@ use tokio_util::sync::CancellationToken; use types::hardware::Ids; use crate::{ - execution::Replayer, window::Window, worker_thread::spawn_worker, ReplayerHardwareInterface, + execution::Replayer, + window::Window, + worker_thread::{spawn_workers, PlayerState}, + ReplayerHardwareInterface, }; pub fn replayer() -> Result<()> { @@ -69,13 +72,17 @@ pub fn replayer() -> Result<()> { .map(|(name, index)| (name, index.iter().collect())) .collect(); - let (time_sender, time_receiver) = watch::channel(SystemTime::UNIX_EPOCH); - spawn_worker(replayer, time_receiver); - run_native( "Replayer", Default::default(), - Box::new(move |_creation_context| Box::new(Window::new(indices, time_sender))), + Box::new(move |creation_context| { + let (time_sender, _) = watch::channel(PlayerState::default()); + let context = creation_context.egui_ctx.clone(); + spawn_workers(replayer, time_sender.clone(), move || { + context.request_repaint(); + }); + Box::new(Window::new(indices, time_sender)) + }), ) .map_err(|error| Report::msg(error.to_string())) .wrap_err("failed to run user interface") diff --git a/crates/hulk_replayer/src/window.rs b/crates/hulk_replayer/src/window.rs index 721786986f..66a69c16f3 100644 --- a/crates/hulk_replayer/src/window.rs +++ b/crates/hulk_replayer/src/window.rs @@ -1,7 +1,7 @@ use std::{collections::BTreeMap, time::SystemTime}; use eframe::{ - egui::{CentralPanel, Context}, + egui::{CentralPanel, Context, Event, Key, Modifiers}, App, Frame, }; use tokio::sync::watch; @@ -12,56 +12,86 @@ use crate::{ coordinate_systems::{AbsoluteTime, FrameRange, RelativeTime, ViewportRange}, labels::Labels, timeline::Timeline, + worker_thread::PlayerState, }; pub struct Window { - time_sender: watch::Sender, + time_sender: watch::Sender, frame_range: FrameRange, viewport_range: ViewportRange, - position: RelativeTime, indices: BTreeMap>, } impl Window { pub fn new( indices: BTreeMap>, - time_sender: watch::Sender, + time_sender: watch::Sender, ) -> Self { let frame_range = join_timing(&indices); let viewport_range = ViewportRange::from_frame_range(&frame_range); + time_sender.send_modify(|state| state.time = frame_range.start().inner()); Self { time_sender, frame_range, viewport_range, - position: RelativeTime::new(0.0), indices, } } - fn replay_at_position(&mut self) { - let timestamp = self.position.map_to_absolute_time(&self.frame_range); + fn replay_at_position(&mut self, position: RelativeTime) { + let timestamp = position.map_to_absolute_time(&self.frame_range); self.time_sender - .send(timestamp.inner()) - .expect("failed to send replay time"); + .send_modify(|state| state.time = timestamp.inner()) } } impl App for Window { fn update(&mut self, context: &Context, _frame: &mut Frame) { + context.input_mut(|input| { + if input.consume_key(Modifiers::NONE, Key::Space) { + self.time_sender + .send_modify(|state| state.playing = !state.playing); + } + if input + .events + .iter() + // egui is scheiße + .any(|e| *e == Event::Text("<".to_string())) + { + self.time_sender + .send_modify(|state| state.playback_rate -= 0.25); + } + if input + .events + .iter() + // egui is scheiße + .any(|e| *e == Event::Text(">".to_string())) + { + self.time_sender + .send_modify(|state| state.playback_rate += 0.25); + } + }); CentralPanel::default().show(context, |ui| { + ui.label(format!( + "Speed: {}", + self.time_sender.borrow().playback_rate + )); ui.horizontal_top(|ui| { ui.add(Labels::new(&self.indices)); + let absolute_position = AbsoluteTime::new(self.time_sender.borrow().time); + let mut relative_position = + absolute_position.map_to_relative_time(&self.frame_range); if ui .add(Timeline::new( &self.indices, &self.frame_range, &mut self.viewport_range, - &mut self.position, + &mut relative_position, )) .changed() { - self.replay_at_position(); + self.replay_at_position(relative_position); } }); }); diff --git a/crates/hulk_replayer/src/worker_thread.rs b/crates/hulk_replayer/src/worker_thread.rs index c946e196f5..22ccca0f8a 100644 --- a/crates/hulk_replayer/src/worker_thread.rs +++ b/crates/hulk_replayer/src/worker_thread.rs @@ -1,37 +1,94 @@ use std::{ thread::spawn, - time::{Duration, SystemTime}, + time::{Duration, Instant, SystemTime}, }; use tokio::{runtime::Builder, select, sync::watch, time::sleep}; use crate::{execution::Replayer, ReplayerHardwareInterface}; -pub fn spawn_worker( - mut replayer: Replayer, - mut time: watch::Receiver, +#[derive(Clone, Copy)] +pub struct PlayerState { + pub time: SystemTime, + pub playing: bool, + pub playback_rate: f32, +} + +impl Default for PlayerState { + fn default() -> Self { + Self { + time: SystemTime::UNIX_EPOCH, + playing: false, + playback_rate: 1.0, + } + } +} + +pub fn spawn_workers( + replayer: Replayer, + sender: watch::Sender, + update_callback: impl Fn() + Send + Sync + 'static, ) { spawn(move || { let runtime = Builder::new_current_thread().enable_all().build().unwrap(); - runtime.block_on(async move { - let mut parameters_receiver = replayer.get_parameters_receiver(); - loop { - select! { - _ = parameters_receiver.wait_for_change() => {} - _ = sleep(Duration::from_secs(1)) => {} - result = time.changed() => { - if result.is_err() { - // channel closed, quit thread - break; - } - } + runtime.spawn(playback_worker(sender.clone())); + runtime.block_on(replay_worker(replayer, sender.subscribe(), update_callback)); + }); +} + +async fn replay_worker( + mut replayer: Replayer, + mut receiver: watch::Receiver, + update_callback: impl Fn() + Send + Sync + 'static, +) { + let mut parameters_receiver = replayer.get_parameters_receiver(); + loop { + select! { + _ = parameters_receiver.wait_for_change() => {} + _ = sleep(Duration::from_secs(1)) => {} + result = receiver.changed() => { + if result.is_err() { + // channel closed, quit + break; } + } + } - if let Err(error) = replayer.replay_at(*time.borrow()) { - eprintln!("{error:#?}"); + let timestamp = receiver.borrow().time; + if let Err(error) = replayer.replay_at(timestamp) { + eprintln!("{error:#?}"); + } + + update_callback() + } +} + +async fn playback_worker(sender: watch::Sender) { + let mut receiver = sender.subscribe(); + let mut last_autoplay_time = None; + loop { + select! { + _ = receiver.changed() => { + let state = *receiver.borrow(); + if !state.playing { + last_autoplay_time = None } } - }); - }); + _ = sleep(Duration::from_millis(12)), if receiver.borrow().playing => { + let elapsed = last_autoplay_time + .as_ref() + .map(Instant::elapsed) + .unwrap_or(Duration::from_millis(12)); + last_autoplay_time = Some(Instant::now()); + sender.send_modify(|state| { + if state.playback_rate.is_sign_positive(){ + state.time += elapsed.mul_f32(state.playback_rate); + } else { + state.time -= elapsed.mul_f32(-state.playback_rate); + } + }); + } + } + } }