Skip to content

Commit

Permalink
timer_refactor (#345)
Browse files Browse the repository at this point in the history
  • Loading branch information
PenguinWithATie authored Oct 21, 2024
1 parent 9549c00 commit 7d28926
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 153 deletions.
196 changes: 87 additions & 109 deletions apis/src/components/molecules/live_timer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ use hive_lib::{Color, GameStatus};
use lazy_static::lazy_static;
use leptos::*;
use leptos_router::RouterContext;
use leptos_use::{use_interval_fn_with_options, utils::Pausable, UseIntervalFnOptions};
use leptos_use::{
use_interval_fn_with_options, utils::Pausable, watch_with_options, whenever_with_options,
UseIntervalFnOptions, WatchOptions,
};
use regex::Regex;
use shared_types::GameId;
use std::time::Duration;
Expand All @@ -18,119 +21,88 @@ lazy_static! {

#[component]
pub fn LiveTimer(side: Signal<Color>) -> impl IntoView {
let timer_signals = expect_context::<TimerSignal>();
let game_state = expect_context::<GameStateSignal>();
let sounds = expect_context::<SoundsSignal>();
let auth_context = expect_context::<AuthContext>();
let user_id = Signal::derive(move || match untrack(auth_context.user) {
Some(Ok(Some(user))) => Some(user.id),
_ => None,
});

let user_color = game_state.user_color_as_signal(user_id.into());
let in_progress = create_read_slice(game_state.signal, |gs| {
gs.game_response
.as_ref()
.map_or(false, |gr| gr.game_status == GameStatus::InProgress)
});

let time_is_zero = Memo::new(move |_| {
let timer = timer_signals.signal.get();
let time_left = match side() {
Color::White => timer.white_time_left.unwrap_or_default(),
Color::Black => timer.black_time_left.unwrap_or_default(),
};
time_left == Duration::from_secs(0)
});
let user_needs_warning: Memo<bool> = Memo::new(move |_| {
if let Some(color) = user_color() {
let trigger_at = timer_signals.low_time.trigger_at.get();
if color != side() || trigger_at.is_none() {
return false;
}

let trigger_at = trigger_at.unwrap_or_default();

let timer = timer_signals.signal.get();
let time_left = match color {
Color::White => timer.white_time_left.unwrap_or_default(),
Color::Black => timer.black_time_left.unwrap_or_default(),
};

return !timer.finished
&& !timer_signals.low_time.issued.get()
&& time_left < trigger_at;
}
false
});

let should_refresh_warning: Memo<bool> = Memo::new(move |_| {
if let Some(color) = user_color() {
let refresh_at = timer_signals.low_time.refresh_at.get();
if color != side() || refresh_at.is_none() {
return false;
}

let refresh_at = refresh_at.unwrap_or(Duration::MAX);
let timer = timer_signals.signal.get();
let time_left = match color {
Color::White => timer.white_time_left.unwrap_or_default(),
Color::Black => timer.black_time_left.unwrap_or_default(),
};

return !timer.finished
&& timer_signals.low_time.issued.get()
&& time_left > refresh_at;
}
false
});
let time_is_red = Memo::new(move |_| {
if time_is_zero() {
String::from("bg-ladybug-red")
} else {
String::new()
}
});
let timer = expect_context::<TimerSignal>().signal;
let tick_rate = Duration::from_millis(100);
let Pausable { pause, resume, .. } = use_interval_fn_with_options(
move || {
let timer = timer_signals.signal.get();
if !timer.finished {
batch(move || {
timer_signals.signal.update(|timer| {
if timer.turn % 2 == 0 {
timer.white_time_left = timer.white_time_left.map(|t| {
t.checked_sub(tick_rate).unwrap_or(Duration::from_secs(0))
});
} else {
timer.black_time_left = timer.black_time_left.map(|t| {
t.checked_sub(tick_rate).unwrap_or(Duration::from_secs(0))
});
}
});
});
}
timer.update(|t| {
if t.turn % 2 == 0 {
t.white_time_left = t
.white_time_left
.map(|t| t.checked_sub(tick_rate).unwrap_or_default());
} else {
t.black_time_left = t
.black_time_left
.map(|t| t.checked_sub(tick_rate).unwrap_or_default());
};
})
},
100,
UseIntervalFnOptions::default().immediate(false),
);

create_effect(move |_| {
let timer = timer_signals.signal.get();
if in_progress() {
if (side() == Color::White) == (timer.turn % 2 == 0) && !timer.finished {
let should_resume = Signal::derive(move || {
timer
.with(|t| in_progress() && (side() == Color::White) == (t.turn % 2 == 0) && !t.finished)
});
let time_is_zero = Signal::derive(move || timer().time_left(side()).is_zero());
let user_needs_warning = Signal::derive(move || {
user_color().map_or(false, |color| {
timer.with(|t| {
t.warning_trigger().map_or(false, |trigger_at| {
if color == side() && !t.finished {
t.time_left(color) < trigger_at
} else {
false
}
})
})
})
});
let should_refresh_warning = Signal::derive(move || {
user_color().map_or(false, |color| {
timer.with(|t| {
t.warning_refresh().map_or(false, |refresh_at| {
if color == side() && !t.finished {
t.time_left(color) > refresh_at
} else {
false
}
})
})
})
});
#[allow(unused)]
watch_with_options(
should_resume,
move |v, _, _| {
if *v {
resume();
} else {
pause();
}
} else {
pause();
}
});
},
//Has immediate = true, hence not unused
WatchOptions::default().immediate(true),
);

create_effect(move |_| {
// When time runs out declare winner and style timer that ran out
if time_is_zero() && !timer_signals.signal.get().finished {
#[allow(unused)]
whenever_with_options(
move || time_is_zero() && !timer().finished,
move |v, _, _| {
// When time runs out declare winner and style timer that ran out
let api = ApiRequests::new();
let router = expect_context::<RouterContext>();
if let Some(caps) = NANOID.captures(&router.pathname().get_untracked()) {
Expand All @@ -139,34 +111,40 @@ pub fn LiveTimer(side: Signal<Color>) -> impl IntoView {
api.game_check_time(&game_id);
}
}
}
});

create_effect(move |_| {
if user_needs_warning() {
sounds.play_sound(SoundType::LowTime);
timer_signals.low_time.issued.set(true);
}
});
create_effect(move |_| {
if should_refresh_warning() {
timer_signals.low_time.issued.set(false);
}
});
},
//Has immediate = true, hence not unused
WatchOptions::default().immediate(true),
);
#[allow(unused)]
whenever_with_options(
user_needs_warning,
move |_, _, issued| {
let issued = issued.unwrap_or_default();
if issued {
!should_refresh_warning()
} else {
sounds.play_sound(SoundType::LowTime);
true
}
},
//Has immediate = true, hence not unused
WatchOptions::default().immediate(true),
);

view! {
<div class=move || {
format!(
"flex resize h-full select-none items-center justify-center text-xl md:text-2xl lg:text-4xl {}",
time_is_red(),
if time_is_zero() {
"bg-ladybug-red"
} else {
""
},
)
}>
{move || {
let timer = timer_signals.signal.get();
let time_left = match side() {
Color::White => timer.white_time_left.unwrap_or_default(),
Color::Black => timer.black_time_left.unwrap_or_default(),
};
let timer = timer();
let time_left = timer.time_left(side());
timer.time_mode.time_remaining(time_left)
}}

Expand Down
6 changes: 3 additions & 3 deletions apis/src/components/organisms/display_timer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,9 @@ pub fn DisplayTimer(placement: Placement, vertical: bool) -> impl IntoView {
"h-full flex justify-center md:leading-4 row-span-2 md:row-span-1 short:row-span-2 short:text-xs items-center flex-col border-y-2 border-r-2 border-black dark:border-white select-none"),
true => ("flex grow justify-end items-center", "w-14 h-14 grow-0 duration-300",""),
};
let timer = expect_context::<TimerSignal>();
let timer = expect_context::<TimerSignal>().signal;
let active_side = create_memo(move |_| {
let timer = timer.signal.get();
let timer = timer();
match timer.finished {
true => "bg-stone-200 dark:bg-reserve-twilight",
false => {
Expand Down Expand Up @@ -109,7 +109,7 @@ pub fn DisplayTimer(placement: Placement, vertical: bool) -> impl IntoView {
<Show
when=move || {
matches!(
timer.signal.get().time_mode,
timer().time_mode,
TimeMode::Correspondence | TimeMode::RealTime
)
}
Expand Down
62 changes: 21 additions & 41 deletions apis/src/providers/timer.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::responses::{GameResponse, HeartbeatResponse};
use chrono::DateTime;
use chrono::Utc;
use hive_lib::Color;
use leptos::*;
use shared_types::GameId;
use shared_types::TimeMode;
Expand All @@ -9,7 +10,6 @@ use std::time::Duration;
#[derive(Clone, Debug, Copy)]
pub struct TimerSignal {
pub signal: RwSignal<Timer>,
pub low_time: LowTimeWarning,
}

impl Default for TimerSignal {
Expand All @@ -21,10 +21,7 @@ impl Default for TimerSignal {
impl TimerSignal {
pub fn new() -> Self {
let signal = RwSignal::new(Timer::new());
Self {
signal,
low_time: LowTimeWarning::new(signal),
}
Self { signal }
}

pub fn update_from_hb(&self, hb: HeartbeatResponse) {
Expand Down Expand Up @@ -80,6 +77,25 @@ impl Timer {
last_interaction: None,
}
}
pub fn time_left(&self, color: Color) -> Duration {
match color {
Color::White => self.white_time_left,
Color::Black => self.black_time_left,
}
.unwrap_or_default()
}

pub fn warning_trigger(&self) -> Option<Duration> {
let increment = self.time_increment.unwrap_or_default();
match self.time_mode {
TimeMode::RealTime => self.time_base.map(|b| b / 10 + increment * 2),
_ => None,
}
}
pub fn warning_refresh(&self) -> Option<Duration> {
self.warning_trigger()
.and_then(|trigger| self.time_increment.map(|increment| trigger + increment * 2))
}
}

impl Default for Timer {
Expand All @@ -88,42 +104,6 @@ impl Default for Timer {
}
}

#[derive(Clone, Debug, Copy)]
pub struct LowTimeWarning {
pub issued: RwSignal<bool>,
pub refresh_at: Signal<Option<Duration>>,
pub trigger_at: Signal<Option<Duration>>,
}

impl LowTimeWarning {
pub fn new(timer: RwSignal<Timer>) -> Self {
let trigger_at = Signal::derive(move || {
let timer = timer.get();
if timer.time_mode == TimeMode::RealTime {
timer.time_base.map(|base| {
base / 10 + timer.time_increment.unwrap_or(Duration::from_secs(0)) * 2
})
} else {
None
}
});

let refresh_at = Signal::derive(move || {
let timer = timer.get();
if let (Some(trigger_at), Some(time_increment)) = (trigger_at(), timer.time_increment) {
Some(trigger_at + time_increment * 2)
} else {
None
}
});
Self {
issued: RwSignal::new(false),
refresh_at,
trigger_at,
}
}
}

pub fn provide_timer() {
provide_context(TimerSignal::new())
}

0 comments on commit 7d28926

Please sign in to comment.