1mod input;
8mod timing;
9
10use std::time::Instant;
11
12pub use input::{InputSnapshot, ScrollSample};
13use mulciber_platform::{InputEvent, WindowEvent};
14pub use timing::{FramePlan, RuntimeConfig, RuntimeConfigError};
15
16#[derive(Debug)]
18pub struct Runtime {
19 input: InputSnapshot,
20 clock: timing::FrameClock,
21}
22
23#[derive(Debug)]
28#[must_use = "a runtime frame must be consumed by update/render work"]
29pub struct RuntimeFrame<'runtime> {
30 input: &'runtime mut InputSnapshot,
31 plan: FramePlan,
32}
33
34impl RuntimeFrame<'_> {
35 #[must_use]
37 pub const fn plan(&self) -> FramePlan {
38 self.plan
39 }
40
41 #[must_use]
43 pub const fn input(&self) -> &InputSnapshot {
44 self.input
45 }
46}
47
48impl Drop for RuntimeFrame<'_> {
49 fn drop(&mut self) {
50 self.input.end_frame();
51 }
52}
53
54impl Runtime {
55 #[must_use]
57 pub fn new(config: RuntimeConfig, started_at: Instant) -> Self {
58 Self {
59 input: InputSnapshot::default(),
60 clock: timing::FrameClock::new(config, started_at),
61 }
62 }
63
64 pub fn handle_input(&mut self, event: InputEvent) {
66 self.input.handle_event(event);
67 }
68
69 pub fn handle_window_event(&mut self, event: WindowEvent) {
74 match event {
75 WindowEvent::Input(input) => self.handle_input(input),
76 WindowEvent::RenderingSuspended => self.suspend(),
77 WindowEvent::RenderingResumed(_) => self.resume(Instant::now()),
78 _ => {}
79 }
80 }
81
82 #[must_use]
84 pub const fn input(&self) -> &InputSnapshot {
85 &self.input
86 }
87
88 pub fn begin_frame(&mut self, now: Instant) -> RuntimeFrame<'_> {
92 RuntimeFrame {
93 input: &mut self.input,
94 plan: self.clock.advance(now),
95 }
96 }
97
98 pub fn suspend(&mut self) {
103 self.clock.suspend();
104 self.input.release_all();
105 }
106
107 pub fn resume(&mut self, now: Instant) {
109 self.clock.resume(now);
110 }
111
112 #[must_use]
114 pub const fn suspended(&self) -> bool {
115 self.clock.suspended()
116 }
117}
118
119#[cfg(test)]
120mod tests {
121 use std::time::Instant;
122
123 use mulciber_platform::{ButtonState, InputEvent, KeyCode, Modifiers, WindowEvent};
124
125 use super::{Runtime, RuntimeConfig};
126
127 #[test]
128 fn scoped_frame_cleanup_and_window_suspension_release_input() {
129 let mut runtime = Runtime::new(RuntimeConfig::fixed_hz(60).unwrap(), Instant::now());
130 runtime.handle_window_event(WindowEvent::Input(InputEvent::Keyboard {
131 key: KeyCode::KeyW,
132 state: ButtonState::Pressed,
133 repeat: false,
134 modifiers: Modifiers::default(),
135 }));
136 let frame = runtime.begin_frame(Instant::now());
137 assert!(frame.input().key_pressed(KeyCode::KeyW));
138 drop(frame);
139 assert!(!runtime.input().key_pressed(KeyCode::KeyW));
140 assert!(runtime.input().key_held(KeyCode::KeyW));
141
142 runtime.handle_window_event(WindowEvent::RenderingSuspended);
143 assert!(runtime.suspended());
144 assert!(!runtime.input().key_held(KeyCode::KeyW));
145 assert!(runtime.input().key_released(KeyCode::KeyW));
146 }
147}