mod input;
mod timing;
use std::time::Instant;
pub use input::{InputSnapshot, ScrollSample};
use mulciber_platform::{InputEvent, WindowEvent};
pub use timing::{FramePlan, RuntimeConfig, RuntimeConfigError};
#[derive(Debug)]
pub struct Runtime {
input: InputSnapshot,
clock: timing::FrameClock,
}
#[derive(Debug)]
#[must_use = "a runtime frame must be consumed by update/render work"]
pub struct RuntimeFrame<'runtime> {
input: &'runtime mut InputSnapshot,
plan: FramePlan,
}
impl RuntimeFrame<'_> {
#[must_use]
pub const fn plan(&self) -> FramePlan {
self.plan
}
#[must_use]
pub const fn input(&self) -> &InputSnapshot {
self.input
}
}
impl Drop for RuntimeFrame<'_> {
fn drop(&mut self) {
self.input.end_frame();
}
}
impl Runtime {
#[must_use]
pub fn new(config: RuntimeConfig, started_at: Instant) -> Self {
Self {
input: InputSnapshot::default(),
clock: timing::FrameClock::new(config, started_at),
}
}
pub fn handle_input(&mut self, event: InputEvent) {
self.input.handle_event(event);
}
pub fn handle_window_event(&mut self, event: WindowEvent) {
match event {
WindowEvent::Input(input) => self.handle_input(input),
WindowEvent::RenderingSuspended => self.suspend(),
WindowEvent::RenderingResumed(_) => self.resume(Instant::now()),
_ => {}
}
}
#[must_use]
pub const fn input(&self) -> &InputSnapshot {
&self.input
}
pub fn begin_frame(&mut self, now: Instant) -> RuntimeFrame<'_> {
RuntimeFrame {
input: &mut self.input,
plan: self.clock.advance(now),
}
}
pub fn suspend(&mut self) {
self.clock.suspend();
self.input.release_all();
}
pub fn resume(&mut self, now: Instant) {
self.clock.resume(now);
}
#[must_use]
pub const fn suspended(&self) -> bool {
self.clock.suspended()
}
}
#[cfg(test)]
mod tests {
use std::time::Instant;
use mulciber_platform::{ButtonState, InputEvent, KeyCode, Modifiers, WindowEvent};
use super::{Runtime, RuntimeConfig};
#[test]
fn scoped_frame_cleanup_and_window_suspension_release_input() {
let mut runtime = Runtime::new(RuntimeConfig::fixed_hz(60).unwrap(), Instant::now());
runtime.handle_window_event(WindowEvent::Input(InputEvent::Keyboard {
key: KeyCode::KeyW,
state: ButtonState::Pressed,
repeat: false,
modifiers: Modifiers::default(),
}));
let frame = runtime.begin_frame(Instant::now());
assert!(frame.input().key_pressed(KeyCode::KeyW));
drop(frame);
assert!(!runtime.input().key_pressed(KeyCode::KeyW));
assert!(runtime.input().key_held(KeyCode::KeyW));
runtime.handle_window_event(WindowEvent::RenderingSuspended);
assert!(runtime.suspended());
assert!(!runtime.input().key_held(KeyCode::KeyW));
assert!(runtime.input().key_released(KeyCode::KeyW));
}
}