use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::time::{Duration, Instant};
use ahash::AHashSet;
use figures::units::Px;
use figures::Point;
use intentional::Assert;
use kludgine::app::winit::event::{ElementState, MouseButton};
use kludgine::app::winit::keyboard::Key;
use parking_lot::{Condvar, Mutex, MutexGuard};
use crate::context::WidgetContext;
use crate::value::{Destination, Dynamic};
use crate::widget::{EventHandling, HANDLED, IGNORED};
use crate::window::KeyEvent;
#[derive(Clone, Debug)]
#[must_use]
pub struct Tick {
data: Arc<TickData>,
handled_keys: AHashSet<Key>,
}
impl Tick {
pub fn rendered(&self, context: &WidgetContext<'_>) {
context.redraw_when_changed(&self.data.tick_number);
self.data.sync.notify_one();
}
#[must_use]
pub fn key_input(&self, input: &KeyEvent) -> EventHandling {
let mut state = self.data.state();
if input.state.is_pressed() {
state.input.keys.insert(input.logical_key.clone());
} else {
state.input.keys.remove(&input.logical_key);
}
drop(state);
if self.handled_keys.contains(&input.logical_key) {
HANDLED
} else {
IGNORED
}
}
pub fn set_cursor_position(&self, pos: Option<Point<Px>>) {
let mut state = self.data.state();
match pos {
Some(pos) => {
if state.input.mouse.is_none() {
state.input.mouse = Some(Mouse::default());
}
state
.input
.mouse
.as_mut()
.assert("always initialized")
.position = pos;
}
None => {
state.input.mouse = None;
}
}
}
pub fn mouse_button(&self, button: MouseButton, button_state: ElementState) {
let mut state = self.data.state();
if let Some(mouse) = &mut state.input.mouse {
if button_state.is_pressed() {
mouse.buttons.insert(button);
} else {
mouse.buttons.remove(&button);
}
}
}
pub fn new<F>(tick_every: Duration, tick: F) -> Self
where
F: FnMut(Duration, &InputState) + Send + 'static,
{
let now = Instant::now();
let data = Arc::new(TickData {
state: Mutex::new(TickState {
last_time: now,
next_target: now,
keep_running: true,
frame: 0,
input: InputState::default(),
}),
period: tick_every,
sync: Condvar::new(),
rendered_frame: AtomicUsize::new(0),
tick_number: Dynamic::default(),
});
std::thread::spawn({
let data = data.clone();
move || tick_loop(&data, tick)
});
Self {
data,
handled_keys: AHashSet::new(),
}
}
pub fn times_per_second<F>(times_per_second: u32, tick: F) -> Self
where
F: FnMut(Duration, &InputState) + Send + 'static,
{
Self::new(Duration::from_secs(1) / times_per_second, tick)
}
pub fn redraws_per_second(times_per_second: u32) -> Self {
Self::times_per_second(times_per_second, |_, _| {})
}
pub fn handled_keys(mut self, keys: impl IntoIterator<Item = Key>) -> Self {
self.handled_keys.extend(keys);
self
}
}
#[derive(Default, Debug)]
pub struct InputState {
pub keys: AHashSet<Key>,
pub mouse: Option<Mouse>,
}
#[derive(Debug, Default)]
pub struct Mouse {
pub position: Point<Px>,
pub buttons: AHashSet<MouseButton>,
}
#[derive(Debug)]
struct TickData {
state: Mutex<TickState>,
period: Duration,
sync: Condvar,
rendered_frame: AtomicUsize,
tick_number: Dynamic<u64>,
}
impl TickData {
fn state(&self) -> MutexGuard<'_, TickState> {
self.state.lock()
}
}
#[derive(Debug)]
struct TickState {
last_time: Instant,
next_target: Instant,
keep_running: bool,
frame: usize,
input: InputState,
}
fn tick_loop<F>(data: &TickData, mut tick: F)
where
F: FnMut(Duration, &InputState),
{
let mut state = data.state();
while state.keep_running {
let mut now = Instant::now();
match state.next_target.checked_duration_since(now) {
Some(remaining) if remaining > Duration::ZERO => {
drop(state);
std::thread::sleep(remaining);
state = data.state();
now = Instant::now();
}
_ => {}
}
let elapsed = now
.checked_duration_since(state.last_time)
.expect("instant never decreases");
state.frame += 1;
tick(elapsed, &state.input);
state.next_target = (state.next_target + data.period).max(now);
state.last_time = now;
data.tick_number.map_mut(|mut tick| *tick += 1);
while state.keep_running {
let current_frame = data.rendered_frame.load(Ordering::Acquire);
if state.frame == current_frame {
data.sync.wait(&mut state);
} else {
break;
}
}
}
}