improve event polling / processing
- the new InputDevices struct is added to simplify input device updating and event processing of input events for all input devices now available, as well as future proofing for new ones (e.g. gamepad). this also helps us out with implementing better ways of doing custom application main loop SystemEvent handling ... - System::do_events_with has been removed. if custom event handling is needed by applications, they should now implement this themselves, as demonstrated by the example in the doc comment for System::do_events. -
This commit is contained in:
parent
9cb333454a
commit
3dfb8fe466
|
@ -189,17 +189,9 @@ where
|
|||
let mut states = States::new();
|
||||
states.push(initial_state)?;
|
||||
|
||||
let mut is_running = true;
|
||||
let mut last_ticks = app.core().system().ticks();
|
||||
|
||||
while is_running && !states.is_empty() {
|
||||
app.core().system_mut().do_events_with(|event| match event {
|
||||
SystemEvent::Quit => {
|
||||
is_running = false;
|
||||
}
|
||||
_ => {}
|
||||
});
|
||||
|
||||
while !app.core().system_mut().do_events() && !states.is_empty() {
|
||||
last_ticks = app.core().update_frame_delta(last_ticks);
|
||||
states.update(&mut app)?;
|
||||
states.render(&mut app);
|
||||
|
|
|
@ -188,12 +188,15 @@ impl From<sdl2::event::Event> for SystemEvent {
|
|||
}
|
||||
}
|
||||
|
||||
/// Common trait for implementing a handler of [`SystemEvent`]s that are polled during the
|
||||
/// application's main loop.
|
||||
pub trait SystemEventHandler {
|
||||
/// Processes the data from the given [`SystemEvent`]. Returns true if the processing actually
|
||||
/// recognized the passed event, or false if the event was ignored.
|
||||
/// recognized the passed event and handled it, or false if the event was ignored.
|
||||
fn handle_event(&mut self, event: &SystemEvent) -> bool;
|
||||
}
|
||||
|
||||
/// An interator for SDL2 system events, polled via [`SystemEventPump`].
|
||||
pub struct SystemEventIterator<'a> {
|
||||
iter: sdl2::event::EventPollIterator<'a>,
|
||||
}
|
||||
|
@ -206,6 +209,8 @@ impl Iterator for SystemEventIterator<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Provides an event pump iterator that wraps over SDL2 events, allowing applications to respond
|
||||
/// to all events each frame as [`SystemEvent`] instances.
|
||||
pub struct SystemEventPump {
|
||||
sdl_event_pump: sdl2::EventPump,
|
||||
}
|
||||
|
@ -217,6 +222,8 @@ impl SystemEventPump {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns an iterator over [`SystemEvent`]s that have been generated since the last time
|
||||
/// events were polled (usually, in the previous frame).
|
||||
pub fn poll_iter(&mut self) -> SystemEventIterator {
|
||||
self.sdl_event_pump.pump_events();
|
||||
SystemEventIterator { iter: self.sdl_event_pump.poll_iter() }
|
||||
|
|
|
@ -75,8 +75,10 @@ impl InputDevice for Keyboard {
|
|||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_event(&mut self, event: &SystemEvent) {
|
||||
impl SystemEventHandler for Keyboard {
|
||||
fn handle_event(&mut self, event: &SystemEvent) -> bool {
|
||||
match event {
|
||||
SystemEvent::Keyboard(KeyboardEvent::KeyDown { scancode, .. }) => {
|
||||
if let Some(scancode) = scancode {
|
||||
|
@ -87,13 +89,15 @@ impl InputDevice for Keyboard {
|
|||
_ => ButtonState::Pressed,
|
||||
};
|
||||
}
|
||||
true
|
||||
}
|
||||
SystemEvent::Keyboard(KeyboardEvent::KeyUp { scancode, .. }) => {
|
||||
if let Some(scancode) = scancode {
|
||||
self.keyboard[*scancode as usize] = ButtonState::Released;
|
||||
}
|
||||
true
|
||||
}
|
||||
_ => (),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
use crate::system::SystemEvent;
|
||||
use crate::system::{SystemEvent, SystemEventHandler};
|
||||
|
||||
pub mod keyboard;
|
||||
pub mod mouse;
|
||||
|
@ -17,9 +17,29 @@ pub trait InputDevice {
|
|||
/// input device. Normally this should be called on the device before all of this frame's
|
||||
/// input events have been processed via `handle_event`.
|
||||
fn update(&mut self);
|
||||
|
||||
/// Processes the data from the given [`SystemEvent`] if it is relevant for this input device.
|
||||
/// You should pass in all events received every frame and let the input device figure out if it
|
||||
/// is relevant to it or not.
|
||||
fn handle_event(&mut self, event: &SystemEvent);
|
||||
}
|
||||
|
||||
/// Container for all available input devices available for applications to use.
|
||||
pub struct InputDevices {
|
||||
pub keyboard: keyboard::Keyboard,
|
||||
pub mouse: mouse::Mouse,
|
||||
}
|
||||
|
||||
impl InputDevice for InputDevices {
|
||||
fn update(&mut self) {
|
||||
self.keyboard.update();
|
||||
self.mouse.update();
|
||||
}
|
||||
}
|
||||
|
||||
impl SystemEventHandler for InputDevices {
|
||||
fn handle_event(&mut self, event: &SystemEvent) -> bool {
|
||||
if self.keyboard.handle_event(event) {
|
||||
return true;
|
||||
}
|
||||
if self.mouse.handle_event(event) {
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
|
@ -292,16 +292,18 @@ impl InputDevice for Mouse {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_event(&mut self, event: &SystemEvent) {
|
||||
impl SystemEventHandler for Mouse {
|
||||
fn handle_event(&mut self, event: &SystemEvent) -> bool {
|
||||
match event {
|
||||
SystemEvent::Mouse(MouseEvent::MouseMotion {
|
||||
x,
|
||||
y,
|
||||
x_delta,
|
||||
y_delta,
|
||||
buttons,
|
||||
}) => {
|
||||
x,
|
||||
y,
|
||||
x_delta,
|
||||
y_delta,
|
||||
buttons,
|
||||
}) => {
|
||||
self.x = *x;
|
||||
self.y = *y;
|
||||
self.x_delta = *x_delta;
|
||||
|
@ -312,14 +314,17 @@ impl InputDevice for Mouse {
|
|||
self.update_button_state(MouseButton::Right as u32, buttons.contains(MouseButtons::RIGHT_BUTTON));
|
||||
self.update_button_state(MouseButton::X1 as u32, buttons.contains(MouseButtons::X1));
|
||||
self.update_button_state(MouseButton::X2 as u32, buttons.contains(MouseButtons::X2));
|
||||
true
|
||||
}
|
||||
SystemEvent::Mouse(MouseEvent::MouseButtonDown { button, .. }) => {
|
||||
self.update_button_state(*button as u32, true);
|
||||
true
|
||||
}
|
||||
SystemEvent::Mouse(MouseEvent::MouseButtonUp { button, .. }) => {
|
||||
self.update_button_state(*button as u32, false);
|
||||
true
|
||||
}
|
||||
_ => (),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -317,6 +317,11 @@ impl SystemBuilder {
|
|||
let keyboard = Keyboard::new();
|
||||
let mouse = Mouse::new();
|
||||
|
||||
let input_devices = InputDevices {
|
||||
keyboard,
|
||||
mouse,
|
||||
};
|
||||
|
||||
Ok(System {
|
||||
sdl_context,
|
||||
sdl_audio_subsystem,
|
||||
|
@ -331,8 +336,7 @@ impl SystemBuilder {
|
|||
video: framebuffer,
|
||||
palette,
|
||||
font,
|
||||
keyboard,
|
||||
mouse,
|
||||
input_devices,
|
||||
event_pump,
|
||||
target_framerate: self.target_framerate,
|
||||
target_framerate_delta: None,
|
||||
|
@ -381,13 +385,11 @@ pub struct System {
|
|||
/// A pre-loaded [`Font`] that can be used for text rendering.
|
||||
pub font: BitmaskFont,
|
||||
|
||||
/// The current keyboard state. To ensure it is updated each frame, you should call
|
||||
/// [`System::do_events`] or [`System::do_events_with`] each frame.
|
||||
pub keyboard: Keyboard,
|
||||
|
||||
/// The current mouse state. To ensure it is updated each frame, you should call
|
||||
/// [`System::do_events`] or [`System::do_events_with`] each frame.
|
||||
pub mouse: Mouse,
|
||||
/// Contains instances representing the current state of the input devices available.
|
||||
/// To ensure these are updated each frame, ensure that you are either calling
|
||||
/// [`System::do_events`] or manually implementing an event polling loop which calls
|
||||
/// [`InputDevices::update`] and [`InputDevices::handle_event`].
|
||||
pub input_devices: InputDevices,
|
||||
|
||||
pub event_pump: SystemEventPump,
|
||||
}
|
||||
|
@ -400,8 +402,8 @@ impl std::fmt::Debug for System {
|
|||
.field("video", &self.video)
|
||||
.field("palette", &self.palette)
|
||||
.field("font", &self.font)
|
||||
.field("keyboard", &self.keyboard)
|
||||
.field("mouse", &self.mouse)
|
||||
//.field("keyboard", &self.keyboard)
|
||||
//.field("mouse", &self.mouse)
|
||||
.field("target_framerate", &self.target_framerate)
|
||||
.field("target_framerate_delta", &self.target_framerate_delta)
|
||||
.field("next_tick", &self.next_tick)
|
||||
|
@ -415,7 +417,7 @@ impl System {
|
|||
/// will block to wait for V-sync. Otherwise, if a target framerate was configured a delay
|
||||
/// might be used to try to meet that framerate.
|
||||
pub fn display(&mut self) -> Result<(), SystemError> {
|
||||
self.mouse.render_cursor(&mut self.video);
|
||||
self.input_devices.mouse.render_cursor(&mut self.video);
|
||||
|
||||
// convert application framebuffer to 32-bit RGBA pixels, and then upload it to the SDL
|
||||
// texture so it will be displayed on screen
|
||||
|
@ -436,7 +438,7 @@ impl System {
|
|||
}
|
||||
self.sdl_canvas.present();
|
||||
|
||||
self.mouse.hide_cursor(&mut self.video);
|
||||
self.input_devices.mouse.hide_cursor(&mut self.video);
|
||||
|
||||
// if a specific target framerate is desired, apply some loop timing/delay to achieve it
|
||||
// TODO: do this better. delaying when running faster like this is a poor way to do this..
|
||||
|
@ -470,26 +472,54 @@ impl System {
|
|||
}
|
||||
|
||||
/// Checks for and responds to all SDL2 events waiting in the queue. Each event is passed to
|
||||
/// all [`InputDevice`]'s automatically to ensure input device state is up to date.
|
||||
pub fn do_events(&mut self) {
|
||||
self.do_events_with(|_event| {});
|
||||
}
|
||||
|
||||
/// Same as [`System::do_events`] but also takes a function which will be called for each
|
||||
/// SDL2 event being processed (after everything else has already processed it), allowing
|
||||
/// your application to also react to any events received.
|
||||
pub fn do_events_with<F>(&mut self, mut f: F)
|
||||
where
|
||||
F: FnMut(&SystemEvent),
|
||||
{
|
||||
self.keyboard.update();
|
||||
self.mouse.update();
|
||||
/// all [`InputDevice`]'s automatically to ensure input device state is up to date. Returns
|
||||
/// true if a [`SystemEvent::Quit`] event is encountered, in which case, the application
|
||||
/// should quit. Otherwise, returns false.
|
||||
///
|
||||
/// ```
|
||||
/// use libretrogd::system::*;
|
||||
///
|
||||
/// let mut system = SystemBuilder::new().window_title("Example").build()?;
|
||||
///
|
||||
/// while !system.do_events() {
|
||||
/// // ... the body of your main loop here ...
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// If your application needs to react to [`SystemEvent`]s, then instead of using
|
||||
/// [`System::do_events`], you should instead manually take care of event polling in your
|
||||
/// main loop. For example:
|
||||
///
|
||||
/// ```
|
||||
/// use libretrogd::system::*;
|
||||
///
|
||||
/// let mut system = SystemBuilder::new().window_title("Example").build()?;
|
||||
///
|
||||
/// 'mainloop: loop {
|
||||
/// system.input_devices.update();
|
||||
/// for event in system.event_pump.poll_iter() {
|
||||
/// system.input_devices.handle_event(&event);
|
||||
/// match event {
|
||||
/// SystemEvent::Quit => {
|
||||
/// break 'mainloop
|
||||
/// },
|
||||
/// _ => {},
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// // ...the rest of the body of your main loop here ...
|
||||
/// }
|
||||
/// ```
|
||||
pub fn do_events(&mut self) -> bool {
|
||||
let mut should_quit = false;
|
||||
self.input_devices.update();
|
||||
for event in self.event_pump.poll_iter() {
|
||||
let event = event.into();
|
||||
self.keyboard.handle_event(&event);
|
||||
self.mouse.handle_event(&event);
|
||||
f(&event);
|
||||
self.input_devices.handle_event(&event);
|
||||
if event == SystemEvent::Quit {
|
||||
should_quit = true;
|
||||
}
|
||||
}
|
||||
should_quit
|
||||
}
|
||||
|
||||
pub fn ticks(&self) -> u64 {
|
||||
|
|
Loading…
Reference in a new issue