use std::collections::BTreeMap;
use std::rc::Rc;
use re_entity_db::{TimeCounts, TimesPerTimeline};
use re_log_types::{
Duration, ResolvedTimeRange, ResolvedTimeRangeF, TimeInt, TimeReal, TimeType, Timeline,
};
use crate::NeedsRepaint;
#[derive(Clone, Copy, Debug, serde::Deserialize, serde::Serialize, PartialEq)]
pub struct TimeView {
pub min: TimeReal,
pub time_spanned: f64,
}
impl From<ResolvedTimeRange> for TimeView {
fn from(value: ResolvedTimeRange) -> Self {
Self {
min: value.min().into(),
time_spanned: value.abs_length() as f64,
}
}
}
#[derive(Clone, Copy, Debug, serde::Deserialize, serde::Serialize, PartialEq)]
struct TimeState {
time: TimeReal,
fps: f32,
#[serde(default)]
loop_selection: Option<ResolvedTimeRangeF>,
#[serde(default)]
view: Option<TimeView>,
}
impl TimeState {
fn new(time: impl Into<TimeReal>) -> Self {
Self {
time: time.into(),
fps: 30.0, loop_selection: Default::default(),
view: None,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, serde::Deserialize, serde::Serialize)]
pub enum Looping {
Off,
Selection,
All,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, serde::Deserialize, serde::Serialize)]
pub enum PlayState {
Paused,
Playing,
Following,
}
#[derive(serde::Deserialize, serde::Serialize, Clone, PartialEq)]
enum ActiveTimeline {
Auto(Timeline),
UserEdited(Timeline),
}
impl std::ops::Deref for ActiveTimeline {
type Target = Timeline;
#[inline]
fn deref(&self) -> &Self::Target {
match self {
Self::Auto(t) | Self::UserEdited(t) => t,
}
}
}
#[derive(Clone, PartialEq, serde::Deserialize, serde::Serialize)]
struct TimeStateEntry {
prev: TimeState,
current: TimeState,
}
impl TimeStateEntry {
fn new(time: impl Into<TimeReal>) -> Self {
let state = TimeState::new(time);
Self {
prev: state,
current: state,
}
}
}
#[derive(serde::Deserialize, serde::Serialize, Clone, PartialEq)]
struct LastFrame {
timeline: Option<Timeline>,
playing: bool,
}
impl Default for LastFrame {
fn default() -> Self {
Self {
timeline: None,
playing: true,
}
}
}
#[derive(Clone)]
pub struct TimelineCallbacks {
pub on_timelinechange: Rc<dyn Fn(Timeline, TimeReal)>,
pub on_timeupdate: Rc<dyn Fn(TimeReal)>,
pub on_pause: Rc<dyn Fn()>,
pub on_play: Rc<dyn Fn()>,
}
#[derive(serde::Deserialize, serde::Serialize, Clone, PartialEq)]
#[serde(default)]
pub struct TimeControl {
last_frame: LastFrame,
timeline: ActiveTimeline,
states: BTreeMap<Timeline, TimeStateEntry>,
playing: bool,
following: bool,
speed: f32,
looping: Looping,
#[serde(skip)]
pub highlighted_range: Option<ResolvedTimeRange>,
}
impl Default for TimeControl {
fn default() -> Self {
Self {
last_frame: Default::default(),
timeline: ActiveTimeline::Auto(default_timeline([])),
states: Default::default(),
playing: true,
following: true,
speed: 1.0,
looping: Looping::Off,
highlighted_range: None,
}
}
}
impl TimeControl {
#[must_use]
pub fn update(
&mut self,
times_per_timeline: &TimesPerTimeline,
stable_dt: f32,
more_data_is_coming: bool,
callbacks: Option<&TimelineCallbacks>,
) -> NeedsRepaint {
self.select_a_valid_timeline(times_per_timeline);
let Some(full_range) = self.full_range(times_per_timeline) else {
return NeedsRepaint::No; };
let needs_repaint = match self.play_state() {
PlayState::Paused => {
self.states.entry(*self.timeline).or_insert_with(|| {
TimeStateEntry::new(if self.following {
full_range.max()
} else {
full_range.min()
})
});
NeedsRepaint::No
}
PlayState::Playing => {
let dt = stable_dt.min(0.1) * self.speed;
let state = self
.states
.entry(*self.timeline)
.or_insert_with(|| TimeStateEntry::new(full_range.min()));
if self.looping == Looping::Off && full_range.max() <= state.current.time {
state.current.time = full_range.max().into();
if more_data_is_coming {
return NeedsRepaint::No; } else {
self.pause();
return NeedsRepaint::No;
}
}
let loop_range = match self.looping {
Looping::Off => None,
Looping::Selection => state.current.loop_selection,
Looping::All => Some(full_range.into()),
};
if let Some(loop_range) = loop_range {
state.current.time = state.current.time.max(loop_range.min);
}
match self.timeline.typ() {
TimeType::Sequence => {
state.current.time += TimeReal::from(state.current.fps * dt);
}
TimeType::Time => state.current.time += TimeReal::from(Duration::from_secs(dt)),
}
if let Some(loop_range) = loop_range {
if loop_range.max < state.current.time {
state.current.time = loop_range.min; }
}
NeedsRepaint::Yes
}
PlayState::Following => {
match self.states.entry(*self.timeline) {
std::collections::btree_map::Entry::Vacant(entry) => {
entry.insert(TimeStateEntry::new(full_range.max()));
}
std::collections::btree_map::Entry::Occupied(mut entry) => {
entry.get_mut().current.time = full_range.max().into();
}
}
NeedsRepaint::No }
};
if let Some(callbacks) = callbacks {
self.handle_callbacks(callbacks);
}
needs_repaint
}
pub fn handle_callbacks(&mut self, callbacks: &TimelineCallbacks) {
if self.last_frame.playing != self.playing {
self.last_frame.playing = self.playing;
if self.playing {
(callbacks.on_play)();
} else {
(callbacks.on_pause)();
}
}
if self.last_frame.timeline != Some(*self.timeline) {
self.last_frame.timeline = Some(*self.timeline);
let time = self
.time_for_timeline(*self.timeline)
.unwrap_or(TimeReal::MIN);
(callbacks.on_timelinechange)(*self.timeline, time);
}
if let Some(state) = self.states.get_mut(&self.timeline) {
if state.prev.time != state.current.time {
state.prev.time = state.current.time;
(callbacks.on_timeupdate)(state.current.time);
}
}
}
pub fn play_state(&self) -> PlayState {
if self.playing {
if self.following {
PlayState::Following
} else {
PlayState::Playing
}
} else {
PlayState::Paused
}
}
pub fn looping(&self) -> Looping {
if self.play_state() == PlayState::Following {
Looping::Off
} else {
self.looping
}
}
pub fn set_looping(&mut self, looping: Looping) {
self.looping = looping;
if self.looping != Looping::Off {
self.following = false;
}
}
pub fn set_play_state(&mut self, times_per_timeline: &TimesPerTimeline, play_state: PlayState) {
match play_state {
PlayState::Paused => {
self.playing = false;
}
PlayState::Playing => {
self.playing = true;
self.following = false;
if let Some(time_points) = times_per_timeline.get(&self.timeline) {
if let Some(state) = self.states.get_mut(&self.timeline) {
if max(time_points) <= state.current.time {
state.current.time = min(time_points).into();
}
} else {
self.states
.insert(*self.timeline, TimeStateEntry::new(min(time_points)));
}
}
}
PlayState::Following => {
self.playing = true;
self.following = true;
if let Some(time_points) = times_per_timeline.get(&self.timeline) {
match self.states.entry(*self.timeline) {
std::collections::btree_map::Entry::Vacant(entry) => {
entry.insert(TimeStateEntry::new(max(time_points)));
}
std::collections::btree_map::Entry::Occupied(mut entry) => {
entry.get_mut().current.time = max(time_points).into();
}
}
}
}
}
}
pub fn pause(&mut self) {
self.playing = false;
}
pub fn step_time_back(&mut self, times_per_timeline: &TimesPerTimeline) {
let Some(time_values) = times_per_timeline.get(self.timeline()) else {
return;
};
self.pause();
if let Some(time) = self.time() {
#[allow(clippy::collapsible_else_if)]
let new_time = if let Some(loop_range) = self.active_loop_selection() {
step_back_time_looped(time, time_values, &loop_range)
} else {
step_back_time(time, time_values).into()
};
self.set_time(new_time);
}
}
pub fn step_time_fwd(&mut self, times_per_timeline: &TimesPerTimeline) {
let Some(time_values) = times_per_timeline.get(self.timeline()) else {
return;
};
self.pause();
if let Some(time) = self.time() {
#[allow(clippy::collapsible_else_if)]
let new_time = if let Some(loop_range) = self.active_loop_selection() {
step_fwd_time_looped(time, time_values, &loop_range)
} else {
step_fwd_time(time, time_values).into()
};
self.set_time(new_time);
}
}
pub fn restart(&mut self, times_per_timeline: &TimesPerTimeline) {
if let Some(time_points) = times_per_timeline.get(&self.timeline) {
if let Some(state) = self.states.get_mut(&self.timeline) {
state.current.time = min(time_points).into();
self.following = false;
}
}
}
pub fn toggle_play_pause(&mut self, times_per_timeline: &TimesPerTimeline) {
#[allow(clippy::collapsible_else_if)]
if self.playing {
self.pause();
} else {
if let Some(time_points) = times_per_timeline.get(&self.timeline) {
if let Some(state) = self.states.get_mut(&self.timeline) {
if max(time_points) <= state.current.time {
state.current.time = min(time_points).into();
self.playing = true;
self.following = false;
return;
}
}
}
if self.following {
self.set_play_state(times_per_timeline, PlayState::Following);
} else {
self.set_play_state(times_per_timeline, PlayState::Playing);
}
}
}
pub fn speed(&self) -> f32 {
self.speed
}
pub fn set_speed(&mut self, speed: f32) {
self.speed = speed;
}
pub fn fps(&self) -> Option<f32> {
self.states
.get(self.timeline())
.map(|state| state.current.fps)
}
pub fn set_fps(&mut self, fps: f32) {
if let Some(state) = self.states.get_mut(&self.timeline) {
state.current.fps = fps;
}
}
pub fn select_a_valid_timeline(&mut self, times_per_timeline: &TimesPerTimeline) {
fn is_timeline_valid(selected: &Timeline, times_per_timeline: &TimesPerTimeline) -> bool {
for timeline in times_per_timeline.timelines() {
if selected == timeline {
return true; }
}
false
}
if matches!(self.timeline, ActiveTimeline::Auto(_))
|| !is_timeline_valid(self.timeline(), times_per_timeline)
{
self.timeline = ActiveTimeline::Auto(default_timeline(times_per_timeline.timelines()));
}
}
#[inline]
pub fn timeline(&self) -> &Timeline {
&self.timeline
}
pub fn time_type(&self) -> TimeType {
self.timeline.typ()
}
pub fn set_timeline(&mut self, timeline: Timeline) {
self.timeline = ActiveTimeline::UserEdited(timeline);
}
pub fn time(&self) -> Option<TimeReal> {
self.states
.get(self.timeline())
.map(|state| state.current.time)
}
pub fn time_int(&self) -> Option<TimeInt> {
self.time().map(|t| t.floor())
}
pub fn time_i64(&self) -> Option<i64> {
self.time().map(|t| t.floor().as_i64())
}
pub fn current_query(&self) -> re_chunk_store::LatestAtQuery {
re_chunk_store::LatestAtQuery::new(
*self.timeline,
self.time().map_or(TimeInt::MAX, |t| t.floor()),
)
}
pub fn active_loop_selection(&self) -> Option<ResolvedTimeRangeF> {
if self.looping == Looping::Selection {
self.states.get(self.timeline())?.current.loop_selection
} else {
None
}
}
pub fn full_range(&self, times_per_timeline: &TimesPerTimeline) -> Option<ResolvedTimeRange> {
times_per_timeline.get(self.timeline()).map(range)
}
pub fn loop_selection(&self) -> Option<ResolvedTimeRangeF> {
self.states.get(self.timeline())?.current.loop_selection
}
pub fn set_loop_selection(&mut self, selection: ResolvedTimeRangeF) {
self.states
.entry(*self.timeline)
.or_insert_with(|| TimeStateEntry::new(selection.min))
.current
.loop_selection = Some(selection);
}
pub fn remove_loop_selection(&mut self) {
if let Some(state) = self.states.get_mut(&self.timeline) {
state.current.loop_selection = None;
}
if self.looping() == Looping::Selection {
self.set_looping(Looping::Off);
}
}
pub fn is_time_selected(&self, timeline: &Timeline, needle: TimeInt) -> bool {
if timeline != self.timeline() {
return false;
}
if let Some(state) = self.states.get(self.timeline()) {
state.current.time.floor() == needle
} else {
false
}
}
pub fn set_timeline_and_time(&mut self, timeline: Timeline, time: impl Into<TimeReal>) {
self.timeline = ActiveTimeline::UserEdited(timeline);
self.set_time(time);
}
pub fn time_for_timeline(&self, timeline: Timeline) -> Option<TimeReal> {
self.states.get(&timeline).map(|state| state.current.time)
}
pub fn set_time_for_timeline(&mut self, timeline: Timeline, time: impl Into<TimeReal>) {
let time = time.into();
self.states
.entry(timeline)
.or_insert_with(|| TimeStateEntry::new(time))
.current
.time = time;
}
pub fn set_time(&mut self, time: impl Into<TimeReal>) {
let time = time.into();
self.states
.entry(*self.timeline)
.or_insert_with(|| TimeStateEntry::new(time))
.current
.time = time;
}
pub fn time_view(&self) -> Option<TimeView> {
self.states
.get(self.timeline())
.and_then(|state| state.current.view)
}
pub fn set_time_view(&mut self, view: TimeView) {
self.states
.entry(*self.timeline)
.or_insert_with(|| TimeStateEntry::new(view.min))
.current
.view = Some(view);
}
pub fn reset_time_view(&mut self) {
if let Some(state) = self.states.get_mut(&self.timeline) {
state.current.view = None;
}
}
}
fn min(values: &TimeCounts) -> TimeInt {
*values.keys().next().unwrap_or(&TimeInt::MIN)
}
fn max(values: &TimeCounts) -> TimeInt {
*values.keys().next_back().unwrap_or(&TimeInt::MIN)
}
fn range(values: &TimeCounts) -> ResolvedTimeRange {
ResolvedTimeRange::new(min(values), max(values))
}
fn default_timeline<'a>(timelines: impl IntoIterator<Item = &'a Timeline>) -> Timeline {
let mut found_log_tick = false;
let mut found_log_time = false;
for timeline in timelines {
if timeline == &Timeline::log_tick() {
found_log_tick = true;
} else if timeline == &Timeline::log_time() {
found_log_time = true;
} else {
return *timeline;
}
}
if found_log_tick && !found_log_time {
Timeline::log_tick()
} else {
Timeline::log_time()
}
}
fn step_fwd_time(time: TimeReal, values: &TimeCounts) -> TimeInt {
if let Some((next, _)) = values
.range((
std::ops::Bound::Excluded(time.floor()),
std::ops::Bound::Unbounded,
))
.next()
{
*next
} else {
min(values)
}
}
fn step_back_time(time: TimeReal, values: &TimeCounts) -> TimeInt {
if let Some((previous, _)) = values.range(..time.ceil()).next_back() {
*previous
} else {
max(values)
}
}
fn step_fwd_time_looped(
time: TimeReal,
values: &TimeCounts,
loop_range: &ResolvedTimeRangeF,
) -> TimeReal {
if time < loop_range.min || loop_range.max <= time {
loop_range.min
} else if let Some((next, _)) = values
.range((
std::ops::Bound::Excluded(time.floor()),
std::ops::Bound::Included(loop_range.max.floor()),
))
.next()
{
TimeReal::from(*next)
} else {
step_fwd_time(time, values).into()
}
}
fn step_back_time_looped(
time: TimeReal,
values: &TimeCounts,
loop_range: &ResolvedTimeRangeF,
) -> TimeReal {
if time <= loop_range.min || loop_range.max < time {
loop_range.max
} else if let Some((previous, _)) = values.range(loop_range.min.ceil()..time.ceil()).next_back()
{
TimeReal::from(*previous)
} else {
step_back_time(time, values).into()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_timeline() {
let log_time = Timeline::log_time();
let log_tick = Timeline::log_tick();
let custom_timeline0 = Timeline::new("my_timeline0", TimeType::Time);
let custom_timeline1 = Timeline::new("my_timeline1", TimeType::Time);
assert_eq!(default_timeline([]), log_time);
assert_eq!(default_timeline([&log_tick]), log_tick);
assert_eq!(default_timeline([&log_time]), log_time);
assert_eq!(default_timeline([&log_time, &log_tick]), log_time);
assert_eq!(
default_timeline([&log_time, &log_tick, &custom_timeline0]),
custom_timeline0
);
assert_eq!(
default_timeline([&custom_timeline0, &log_time, &log_tick]),
custom_timeline0
);
assert_eq!(
default_timeline([&log_time, &custom_timeline0, &log_tick]),
custom_timeline0
);
assert_eq!(
default_timeline([&custom_timeline0, &log_time]),
custom_timeline0
);
assert_eq!(
default_timeline([&custom_timeline0, &log_tick]),
custom_timeline0
);
assert_eq!(
default_timeline([&log_time, &custom_timeline0]),
custom_timeline0
);
assert_eq!(
default_timeline([&log_tick, &custom_timeline0]),
custom_timeline0
);
assert_eq!(
default_timeline([&custom_timeline0, &custom_timeline1]),
custom_timeline0
);
assert_eq!(default_timeline([&custom_timeline0]), custom_timeline0);
}
}