Skip to content

Commit

Permalink
Re(altime)-Play (#1339)
Browse files Browse the repository at this point in the history
* Autoreplay

* Make playback realtime

* Prevent skips when starting playback

* Explicit value instead of Default

* Split worker tasks

* Allow negative playback speed

* Add text to describe speed
  • Loading branch information
knoellle authored Jul 20, 2024
1 parent 712458e commit 8181759
Show file tree
Hide file tree
Showing 3 changed files with 131 additions and 37 deletions.
19 changes: 13 additions & 6 deletions crates/hulk_replayer/src/replayer.rs
Original file line number Diff line number Diff line change
@@ -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},
Expand All @@ -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<()> {
Expand Down Expand Up @@ -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")
Expand Down
52 changes: 41 additions & 11 deletions crates/hulk_replayer/src/window.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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<SystemTime>,
time_sender: watch::Sender<PlayerState>,
frame_range: FrameRange,
viewport_range: ViewportRange,
position: RelativeTime,
indices: BTreeMap<String, Vec<Timing>>,
}

impl Window {
pub fn new(
indices: BTreeMap<String, Vec<Timing>>,
time_sender: watch::Sender<SystemTime>,
time_sender: watch::Sender<PlayerState>,
) -> 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);
}
});
});
Expand Down
97 changes: 77 additions & 20 deletions crates/hulk_replayer/src/worker_thread.rs
Original file line number Diff line number Diff line change
@@ -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<ReplayerHardwareInterface>,
mut time: watch::Receiver<SystemTime>,
#[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<ReplayerHardwareInterface>,
sender: watch::Sender<PlayerState>,
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<ReplayerHardwareInterface>,
mut receiver: watch::Receiver<PlayerState>,
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<PlayerState>) {
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);
}
});
}
}
}
}

0 comments on commit 8181759

Please sign in to comment.