From a30799babd1eb70edb4717ea9003b7c94e9b53a2 Mon Sep 17 00:00:00 2001 From: Dan Spencer Date: Fri, 6 Feb 2015 08:04:57 -0700 Subject: [PATCH 1/3] Remove Event::None, add Event::Unknown, panic on unreachable Events * `Event::None` can be replaced by `Option::None` * `Event::Unknown` is for events not recognized by rust-sdl2. New events are possible if a newer version of libSDL2 is linked. * Certain events such as First and Last cannot be received. If they are received, it's a runtime error. --- src/sdl2/event.rs | 62 ++++++++++++++++++++++++----------------------- 1 file changed, 32 insertions(+), 30 deletions(-) diff --git a/src/sdl2/event.rs b/src/sdl2/event.rs index 9cd5ec82..8703998e 100644 --- a/src/sdl2/event.rs +++ b/src/sdl2/event.rs @@ -125,9 +125,6 @@ impl WindowEventId { /// Different event types. pub enum Event { - None, - - Quit { timestamp: u32 }, AppTerminating { timestamp: u32 }, AppLowMemory { timestamp: u32 }, @@ -361,12 +358,16 @@ pub enum Event { _type: u32, code: i32 }, + + Unknown { + timestamp: u32, + type_: u32 + } } impl ::std::fmt::Debug for Event { fn fmt(&self, out: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { out.write_str(match *self { - Event::None => "Event::None", Event::Quit{..} => "Event::Quit", Event::AppTerminating{..} => "Event::AppTerminating", Event::AppLowMemory{..} => "Event::AppLowMemory", @@ -405,6 +406,7 @@ impl ::std::fmt::Debug for Event { Event::ClipboardUpdate{..} => "Event::ClipboardUpdate", Event::DropFile{..} => "Event::DropFile", Event::User{..} => "Event::User", + Event::Unknown{..} => "Event::Unknown", }) } } @@ -412,7 +414,7 @@ impl ::std::fmt::Debug for Event { // TODO: Remove this when from_utf8 is updated in Rust impl Event { fn to_ll(self) -> Option { - let ret = null_event(); + let ret = unsafe { mem::uninitialized() }; match self { // just ignore timestamp Event::User { window_id, _type, code, .. } => { @@ -439,7 +441,7 @@ impl Event { fn from_ll(raw: &ll::SDL_Event) -> Event { let raw_type = raw._type(); let raw_type = if raw_type.is_null() { - return Event::None; + panic!("Event payload is null") } else { unsafe { *raw_type } }; @@ -809,34 +811,36 @@ impl Event { } } - EventType::First | EventType::Last => Event::None, + EventType::First => panic!("Unused event, EventType::First, was encountered"), + EventType::Last => panic!("Unusable event, EventType::Last, was encountered"), // If we have no other match and the event type is >= 32768 // this is a user event EventType::User => { if raw_type < 32768 { - return Event::None; - } + // The type is unknown to us. + // It's a newer SDL2 type. + let ref event = *raw.common(); - let ref event = *raw.user(); + Event::Unknown { + timestamp: event.timestamp, + type_: event._type + } + } else { + let ref event = *raw.user(); - Event::User { - timestamp: event.timestamp, - window_id: event.windowID, - _type: raw_type, - code: event.code + Event::User { + timestamp: event.timestamp, + window_id: event.windowID, + _type: raw_type, + code: event.code + } } } }} // close unsafe & match - - } } -fn null_event() -> ll::SDL_Event { - ll::SDL_Event { data: [0; 56] } -} - /// Pump the event loop, gathering events from the input devices. pub fn pump_events() { unsafe { ll::SDL_PumpEvents(); } @@ -863,19 +867,17 @@ pub fn flush_events(min: EventType, max: EventType) { } /// Poll for currently pending events. -pub fn poll_event() -> Event { - pump_events(); +pub fn poll_event() -> Option { + let raw = unsafe { mem::uninitialized() }; + let has_pending = unsafe { ll::SDL_PollEvent(&raw) == 1 as c_int }; - let raw = null_event(); - let success = unsafe { ll::SDL_PollEvent(&raw) == 1 as c_int }; - - if success { Event::from_ll(&raw) } - else { Event::None } + if has_pending { Some(Event::from_ll(&raw)) } + else { None } } /// Wait indefinitely for the next available event. pub fn wait_event() -> SdlResult { - let raw = null_event(); + let raw = unsafe { mem::uninitialized() }; let success = unsafe { ll::SDL_WaitEvent(&raw) == 1 as c_int }; if success { Ok(Event::from_ll(&raw)) } @@ -884,7 +886,7 @@ pub fn wait_event() -> SdlResult { /// Wait until the specified timeout (in milliseconds) for the next available event. pub fn wait_event_timeout(timeout: i32) -> SdlResult { - let raw = null_event(); + let raw = unsafe { mem::uninitialized() }; let success = unsafe { ll::SDL_WaitEventTimeout(&raw, timeout as c_int) == 1 as c_int }; From 2a5ce5f1bfeb7b11b043d3dbcb5327f85050fc9a Mon Sep 17 00:00:00 2001 From: Dan Spencer Date: Sat, 21 Feb 2015 18:00:24 -0700 Subject: [PATCH 2/3] Add `Sdl` type --- examples/audio-whitenoise.rs | 2 +- examples/demo.rs | 4 +- examples/game_controller.rs | 4 +- examples/joystick.rs | 4 +- examples/renderer-texture.rs | 4 +- examples/renderer-yuv.rs | 4 +- src/sdl2/sdl.rs | 113 +++++++++++++++++++++++++++++++---- tests/lib.rs | 4 -- 8 files changed, 106 insertions(+), 33 deletions(-) diff --git a/examples/audio-whitenoise.rs b/examples/audio-whitenoise.rs index b7fef4c1..28d44a44 100644 --- a/examples/audio-whitenoise.rs +++ b/examples/audio-whitenoise.rs @@ -20,7 +20,7 @@ impl AudioCallback for MyCallback { } fn main() { - sdl2::init(sdl2::INIT_AUDIO); + let _sdl_context = sdl2::init(sdl2::INIT_AUDIO).unwrap(); let desired_spec = AudioSpecDesired { freq: 44100, diff --git a/examples/demo.rs b/examples/demo.rs index d27daa86..e5aba438 100644 --- a/examples/demo.rs +++ b/examples/demo.rs @@ -8,7 +8,7 @@ use sdl2::event::Event::{Quit, KeyDown}; use sdl2::keycode::KeyCode; pub fn main() { - sdl2::init(sdl2::INIT_VIDEO); + let sdl_context = sdl2::init(sdl2::INIT_VIDEO).unwrap(); let window = match Window::new("rust-sdl2 demo: Video", WindowPos::PosCentered, WindowPos::PosCentered, 800, 600, OPENGL) { Ok(window) => window, @@ -36,6 +36,4 @@ pub fn main() { _ => {} } } - - sdl2::quit(); } diff --git a/examples/game_controller.rs b/examples/game_controller.rs index 066793f7..60f07658 100644 --- a/examples/game_controller.rs +++ b/examples/game_controller.rs @@ -10,7 +10,7 @@ use std::time::duration::Duration; use std::num::SignedInt; fn main() { - sdl2::init(sdl2::INIT_GAME_CONTROLLER); + let sdl_context = sdl2::init(sdl2::INIT_GAME_CONTROLLER).unwrap(); let available = match joystick::num_joysticks() { @@ -73,6 +73,4 @@ fn main() { _ => (), } } - - sdl2::quit(); } diff --git a/examples/joystick.rs b/examples/joystick.rs index 40fc8852..c30aa188 100644 --- a/examples/joystick.rs +++ b/examples/joystick.rs @@ -7,7 +7,7 @@ use std::time::duration::Duration; use std::num::SignedInt; fn main() { - sdl2::init(sdl2::INIT_JOYSTICK); + let sdl_context = sdl2::init(sdl2::INIT_JOYSTICK).unwrap(); let available = match num_joysticks() { @@ -59,6 +59,4 @@ fn main() { _ => (), } } - - sdl2::quit(); } diff --git a/examples/renderer-texture.rs b/examples/renderer-texture.rs index a61bf146..2552510e 100644 --- a/examples/renderer-texture.rs +++ b/examples/renderer-texture.rs @@ -9,7 +9,7 @@ use sdl2::event::Event::{Quit, KeyDown}; use sdl2::keycode::KeyCode; pub fn main() { - sdl2::init(sdl2::INIT_VIDEO); + let sdl_context = sdl2::init(sdl2::INIT_VIDEO).unwrap(); let window = match Window::new("rust-sdl2 demo: Renderer + Texture", WindowPos::PosCentered, WindowPos::PosCentered, 800, 600, OPENGL) { Ok(window) => window, @@ -51,6 +51,4 @@ pub fn main() { _ => {} } } - - sdl2::quit(); } diff --git a/examples/renderer-yuv.rs b/examples/renderer-yuv.rs index 63110499..a019688e 100644 --- a/examples/renderer-yuv.rs +++ b/examples/renderer-yuv.rs @@ -9,7 +9,7 @@ use sdl2::event::Event::{Quit, KeyDown}; use sdl2::keycode::KeyCode; pub fn main() { - sdl2::init(sdl2::INIT_VIDEO); + let sdl_context = sdl2::init(sdl2::INIT_VIDEO).unwrap(); let window = match Window::new("rust-sdl2 demo: YUV", WindowPos::PosCentered, WindowPos::PosCentered, 800, 600, SHOWN) { Ok(window) => window, @@ -67,6 +67,4 @@ pub fn main() { _ => {} } } - - sdl2::quit(); } diff --git a/src/sdl2/sdl.rs b/src/sdl2/sdl.rs index 70b9f4e3..c2ca45d8 100644 --- a/src/sdl2/sdl.rs +++ b/src/sdl2/sdl.rs @@ -1,4 +1,5 @@ use std::ffi::{c_str_to_bytes, CString}; +use std::marker::{NoCopy, PhantomData}; use sys::sdl as ll; @@ -27,30 +28,116 @@ pub enum Error { pub type SdlResult = Result; -pub fn init(flags: InitFlag) -> bool { - unsafe { - ll::SDL_Init(flags.bits()) == 0 +use std::sync::atomic::{AtomicBool, ATOMIC_BOOL_INIT}; +/// Only one Sdl context can be alive at a time. +/// Set to false by default (not alive). +static IS_SDL_CONTEXT_ALIVE: AtomicBool = ATOMIC_BOOL_INIT; + +/// The SDL context type. Initialize with `sdl2::init()`. +/// +/// From a thread-safety perspective, `Sdl` represents the main thread. +/// Only one instance of `Sdl` is allowed per process, and cannot be moved or +/// used across non-main threads. +/// +/// As such, `Sdl` is a useful type for ensuring that SDL types that can only +/// be used on the main thread are initialized that way. +/// +/// For instance, `SDL_PumpEvents()` is not thread safe, and may only be +/// called on the main thread. +/// All functionality that calls `SDL_PumpEvents()` is thus put into an +/// `EventPump` type, which can only be obtained through `Sdl`. +/// This guarantees that the only way to call event-pumping functions is on +/// the main thread. +pub struct Sdl { + _marker: NoCopy +} + +impl !Send for Sdl {} +impl !Sync for Sdl {} + +impl Sdl { + /// Initializes specific SDL subsystems. + pub fn init_subsystem(&self, flags: InitFlag) -> SdlResult { + unsafe { + if ll::SDL_InitSubSystem(flags.bits()) == 0 { + Ok(Subsystem { + flags: flags, + _marker: PhantomData + }) + } else { + Err(get_error()) + } + } + } + + /// Returns the mask of the specified subsystems which have previously been initialized. + pub fn was_init(&self, flags: InitFlag) -> InitFlag { + unsafe { + let raw = ll::SDL_WasInit(flags.bits()); + flags & InitFlag::from_bits(raw).unwrap() + } } } -pub fn init_subsystem(flags: InitFlag) -> bool { - unsafe { - ll::SDL_InitSubSystem(flags.bits()) == 0 +impl Drop for Sdl { + fn drop(&mut self) { + use std::sync::atomic::Ordering; + + let was_alive = IS_SDL_CONTEXT_ALIVE.swap(false, Ordering::Relaxed); + assert!(was_alive); + + unsafe { ll::SDL_Quit(); } } } -pub fn quit_subsystem(flags: InitFlag) { - unsafe { ll::SDL_QuitSubSystem(flags.bits()); } +/// A RAII value representing initalized SDL subsystems. See `sdl2::Sdl::init_subsystem()`. +/// +/// Subsystem initialization is ref-counted. Once `Subsystem::drop()` is called, +/// the specified subsystems' ref-counts are decremented via `SDL_QuitSubSystem`. +pub struct Subsystem<'sdl> { + flags: InitFlag, + _marker: PhantomData<&'sdl Sdl> } -pub fn quit() { - unsafe { ll::SDL_Quit(); } +#[unsafe_destructor] +impl<'sdl> Drop for Subsystem<'sdl> { + fn drop(&mut self) { + unsafe { ll::SDL_QuitSubSystem(self.flags.bits()); } + } } -pub fn was_inited(flags: InitFlag) -> InitFlag { +/// Initializes the SDL library. +/// This must be called before using any other SDL function. +/// +/// # Example +/// ```no_run +/// let sdl_context = sdl2::init(sdl2::INIT_EVERYTHING).unwrap(); +/// +/// let mut event_pump = sdl_context.event_pump(); +/// for event in event_pump.poll_iter() { +/// // ... +/// } +/// +/// // SDL_Quit() is called here as `sdl_context` is dropped. +/// ``` +pub fn init(flags: InitFlag) -> SdlResult { unsafe { - let raw = ll::SDL_WasInit(flags.bits()); - flags & InitFlag::from_bits(raw).unwrap() + use std::sync::atomic::Ordering; + + // Atomically switch the `IS_SDL_CONTEXT_ALIVE` global to true + let was_alive = IS_SDL_CONTEXT_ALIVE.swap(true, Ordering::Relaxed); + + if was_alive { + IS_SDL_CONTEXT_ALIVE.swap(false, Ordering::Relaxed); + Err(format!("Cannot have more than one `Sdl` in use at the same time")) + } else { + if ll::SDL_Init(flags.bits()) == 0 { + Ok(Sdl { _marker: NoCopy }) + } else { + IS_SDL_CONTEXT_ALIVE.swap(false, Ordering::Relaxed); + Err(get_error()) + } + } } } diff --git a/tests/lib.rs b/tests/lib.rs index c7c6c98b..ddbf986c 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -4,8 +4,6 @@ extern crate sdl2; #[test] fn audio_spec_wav() { - sdl2::init(sdl2::INIT_AUDIO); - let wav = sdl2::audio::AudioSpecWAV::load_wav(&Path::new("./tests/sine.wav")).unwrap(); assert_eq!(wav.freq, 22050); @@ -14,6 +12,4 @@ fn audio_spec_wav() { let buffer = wav.get_buffer(); assert_eq!(buffer.len(), 4410); - - sdl2::quit(); } From 99755159505933c9e64136e2cd9960c2e739f41e Mon Sep 17 00:00:00 2001 From: Dan Spencer Date: Sat, 21 Feb 2015 18:40:17 -0700 Subject: [PATCH 3/3] Add `EventPump` and update examples to use new `EventPump` `EventPump` is an object-oriented approach to event loops. The `EventPump` type allows us to enforce certain invariants about event pumping at compile-time, such as thread safety. Event pumping may only occur on the main thread, and as such, `EventPump` implements `!Send`. Iterators have been added as a more Rust-y way to implement event looping. Some thread-safe functions, such as event pushing and peeking, are still defined at the module-level and not inside of a type. This is to allow other threads to call these functions freely without the need to borrow any values. * In addition to adding `EventPump`, a `running` variable was added in the graphical examples to cleanly exit out of the main loop (without using `break 'main`). * `EventPump`::wait_iter()` is used for the game controller and joystick examples to avoid hogging the CPU. * `use sdl2::event::Event;` is put inside the iterator to encourage scoping `use`s to where they're needed instead of at the module-level. --- examples/demo.rs | 23 +-- examples/game_controller.rs | 19 +-- examples/joystick.rs | 17 +-- examples/renderer-texture.rs | 23 +-- examples/renderer-yuv.rs | 23 +-- sdl2-sys/src/event.rs | 6 +- src/sdl2/event.rs | 275 +++++++++++++++++++++++------------ src/sdl2/sdl.rs | 10 +- 8 files changed, 251 insertions(+), 145 deletions(-) diff --git a/examples/demo.rs b/examples/demo.rs index e5aba438..cf7dfff4 100644 --- a/examples/demo.rs +++ b/examples/demo.rs @@ -3,8 +3,6 @@ extern crate sdl2; use sdl2::video::{Window, WindowPos, OPENGL}; use sdl2::render::{RenderDriverIndex, ACCELERATED, Renderer}; use sdl2::pixels::Color; -use sdl2::event::poll_event; -use sdl2::event::Event::{Quit, KeyDown}; use sdl2::keycode::KeyCode; pub fn main() { @@ -25,15 +23,20 @@ pub fn main() { drawer.clear(); drawer.present(); - loop { - match poll_event() { - Quit{..} => break, - KeyDown { keycode: key, .. } => { - if key == KeyCode::Escape { - break; - } + let mut running = true; + let mut event_pump = sdl_context.event_pump(); + + while running { + for event in event_pump.poll_iter() { + use sdl2::event::Event; + + match event { + Event::Quit {..} | Event::KeyDown { keycode: KeyCode::Escape, .. } => { + running = false + }, + _ => {} } - _ => {} } + // The rest of the game loop goes here... } } diff --git a/examples/game_controller.rs b/examples/game_controller.rs index 60f07658..ad929607 100644 --- a/examples/game_controller.rs +++ b/examples/game_controller.rs @@ -1,12 +1,8 @@ -#![feature(io, std_misc, core)] - +#![feature(core)] extern crate sdl2; use sdl2::{joystick, controller}; -use sdl2::event::Event; use sdl2::controller::GameController; -use std::old_io::timer::sleep; -use std::time::duration::Duration; use std::num::SignedInt; fn main() { @@ -44,7 +40,7 @@ fn main() { } } - let controller = + let controller = match controller { Some(c) => c, None => panic!("Couldn't open any controller"), @@ -52,8 +48,12 @@ fn main() { println!("Controller mapping: {}", controller.mapping()); - loop { - match sdl2::event::poll_event() { + let mut event_pump = sdl_context.event_pump(); + + for event in event_pump.wait_iter() { + use sdl2::event::Event; + + match event { Event::ControllerAxisMotion{ axis, value: val, .. } => { // Axis motion is an absolute value in the range // [-32768, 32767]. Let's simulate a very rough dead @@ -67,9 +67,6 @@ fn main() { Event::ControllerButtonUp{ button, .. } => println!("Button {:?} up", button), Event::Quit{..} => break, - Event::None => - // Don't hog the CPU while waiting for events - sleep(Duration::milliseconds(100)), _ => (), } } diff --git a/examples/joystick.rs b/examples/joystick.rs index c30aa188..63246ddc 100644 --- a/examples/joystick.rs +++ b/examples/joystick.rs @@ -1,9 +1,7 @@ +#![feature(core)] extern crate sdl2; use sdl2::joystick::{Joystick, num_joysticks}; -use sdl2::event::Event; -use std::old_io::timer::sleep; -use std::time::duration::Duration; use std::num::SignedInt; fn main() { @@ -32,12 +30,16 @@ fn main() { } } - if joystick.is_none() { + if joystick.is_none() { panic!("Couldn't open any joystick"); }; - loop { - match sdl2::event::poll_event() { + let mut event_pump = sdl_context.event_pump(); + + for event in event_pump.wait_iter() { + use sdl2::event::Event; + + match event { Event::JoyAxisMotion{ axis_idx, value: val, .. } => { // Axis motion is an absolute value in the range // [-32768, 32767]. Let's simulate a very rough dead @@ -53,9 +55,6 @@ fn main() { Event::JoyHatMotion{ hat_idx, state, .. } => println!("Hat {} moved to {:?}", hat_idx, state), Event::Quit{..} => break, - Event::None => - // Don't hog the CPU while waiting for events - sleep(Duration::milliseconds(100)), _ => (), } } diff --git a/examples/renderer-texture.rs b/examples/renderer-texture.rs index 2552510e..e776463e 100644 --- a/examples/renderer-texture.rs +++ b/examples/renderer-texture.rs @@ -4,8 +4,6 @@ use sdl2::video::{Window, WindowPos, OPENGL}; use sdl2::render::{RenderDriverIndex, ACCELERATED, Renderer}; use sdl2::pixels::PixelFormatEnum; use sdl2::rect::Rect; -use sdl2::event::poll_event; -use sdl2::event::Event::{Quit, KeyDown}; use sdl2::keycode::KeyCode; pub fn main() { @@ -40,15 +38,20 @@ pub fn main() { drawer.copy_ex(&texture, None, Some(Rect::new(450, 100, 256, 256)), 30.0, None, (false, false)); drawer.present(); - loop { - match poll_event() { - Quit{..} => break, - KeyDown { keycode: key, .. } => { - if key == KeyCode::Escape { - break; - } + let mut running = true; + let mut event_pump = sdl_context.event_pump(); + + while running { + for event in event_pump.poll_iter() { + use sdl2::event::Event; + + match event { + Event::Quit {..} | Event::KeyDown { keycode: KeyCode::Escape, .. } => { + running = false + }, + _ => {} } - _ => {} } + // The rest of the game loop goes here... } } diff --git a/examples/renderer-yuv.rs b/examples/renderer-yuv.rs index a019688e..ed0bcf7a 100644 --- a/examples/renderer-yuv.rs +++ b/examples/renderer-yuv.rs @@ -4,8 +4,6 @@ use sdl2::video::{Window, WindowPos, SHOWN}; use sdl2::render::{RenderDriverIndex, ACCELERATED, Renderer}; use sdl2::pixels::PixelFormatEnum; use sdl2::rect::Rect; -use sdl2::event::poll_event; -use sdl2::event::Event::{Quit, KeyDown}; use sdl2::keycode::KeyCode; pub fn main() { @@ -56,15 +54,20 @@ pub fn main() { drawer.copy(&texture, None, Some(Rect::new(100, 100, 256, 256))); drawer.present(); - loop { - match poll_event() { - Quit{..} => break, - KeyDown { keycode: key, .. } => { - if key == KeyCode::Escape { - break; - } + let mut running = true; + let mut event_pump = sdl_context.event_pump(); + + while running { + for event in event_pump.poll_iter() { + use sdl2::event::Event; + + match event { + Event::Quit {..} | Event::KeyDown { keycode: KeyCode::Escape, .. } => { + running = false + }, + _ => {} } - _ => {} } + // The rest of the game loop goes here... } } diff --git a/sdl2-sys/src/event.rs b/sdl2-sys/src/event.rs index 0ee64739..c1dc77f0 100644 --- a/sdl2-sys/src/event.rs +++ b/sdl2-sys/src/event.rs @@ -444,9 +444,9 @@ pub type SDL_EventFilter = extern "C" { pub fn SDL_free(mem: *const c_void); pub fn SDL_PumpEvents(); - /*pub fn SDL_PeepEvents(events: &[SDL_Event], numevents: c_int, - action: SDL_eventaction, minType: uint32_t, - maxType: uint32_t) -> c_int;*/ + pub fn SDL_PeepEvents(events: *mut SDL_Event, numevents: c_int, + action: SDL_eventaction, + minType: uint32_t, maxType: uint32_t) -> c_int; pub fn SDL_HasEvent(_type: uint32_t) -> SDL_bool; pub fn SDL_HasEvents(minType: uint32_t, maxType: uint32_t) -> SDL_bool; diff --git a/src/sdl2/event.rs b/src/sdl2/event.rs index 8703998e..a95627e2 100644 --- a/src/sdl2/event.rs +++ b/src/sdl2/event.rs @@ -8,6 +8,8 @@ use libc::{c_int, c_void, uint32_t}; use std::num::FromPrimitive; use std::ptr; use std::borrow::ToOwned; +use std::iter::FromIterator; +use std::marker::{NoCopy, PhantomData}; use controller; use controller::{Axis, Button}; @@ -841,112 +843,203 @@ impl Event { } } -/// Pump the event loop, gathering events from the input devices. -pub fn pump_events() { - unsafe { ll::SDL_PumpEvents(); } +/// A thread-safe type that encapsulates SDL event-pumping functions. +pub struct EventPump<'sdl> { + _marker: NoCopy, + _sdl: PhantomData<&'sdl ()> } -/// Check for the existence of certain event types in the event queue. -pub fn has_event(_type: EventType) -> bool { - unsafe { ll::SDL_HasEvent(_type as uint32_t ) == 1 } -} +/// Prevents the event pump from moving to other threads. +/// SDL events can only be pumped on the main thread. +impl<'sdl> !Send for EventPump<'sdl> {} -/// Check for the existence of a range of event types in the event queue. -pub fn has_events(min: EventType, max: EventType) -> bool { - unsafe { ll::SDL_HasEvents(min as uint32_t, max as uint32_t) == 1 } -} +impl<'sdl> EventPump<'sdl> { + /// Polls for currently pending events. + /// + /// If no events are pending, `None` is returned. + pub fn poll_event(&mut self) -> Option { + let raw = unsafe { mem::uninitialized() }; + let has_pending = unsafe { ll::SDL_PollEvent(&raw) == 1 as c_int }; -/// Clear events from the event queue. -pub fn flush_event(_type: EventType) { - unsafe { ll::SDL_FlushEvent(_type as uint32_t) } -} + if has_pending { Some(Event::from_ll(&raw)) } + else { None } + } -/// Clear events from the event queue of a range of event types. -pub fn flush_events(min: EventType, max: EventType) { - unsafe { ll::SDL_FlushEvents(min as uint32_t, max as uint32_t) } -} + /// Returns a polling iterator that calls `poll_event()`. + /// The iterator will terminate once there are no more pending events. + /// + /// # Example + /// ```no_run + /// let mut sdl_context = sdl2::init(sdl2::INIT_EVERYTHING).unwrap(); + /// + /// let mut event_pump = sdl_context.event_pump(); + /// for event in event_pump.poll_iter() { + /// use sdl2::event::Event; + /// match event { + /// Event::KeyDown {..} => { /*...*/ }, + /// _ => () + /// } + /// } + /// ``` + pub fn poll_iter(&mut self) -> EventPollIterator { + EventPollIterator { + event_pump: unsafe { EventPump::_unchecked_new() } + } + } -/// Poll for currently pending events. -pub fn poll_event() -> Option { - let raw = unsafe { mem::uninitialized() }; - let has_pending = unsafe { ll::SDL_PollEvent(&raw) == 1 as c_int }; + /// Pumps the event loop, gathering events from the input devices. + pub fn pump_events(&mut self) { + unsafe { ll::SDL_PumpEvents(); }; + } - if has_pending { Some(Event::from_ll(&raw)) } - else { None } -} + /// Waits indefinitely for the next available event. + pub fn wait_event(&mut self) -> Event { + unsafe { + let raw = mem::uninitialized(); + let success = ll::SDL_WaitEvent(&raw) == 1; -/// Wait indefinitely for the next available event. -pub fn wait_event() -> SdlResult { - let raw = unsafe { mem::uninitialized() }; - let success = unsafe { ll::SDL_WaitEvent(&raw) == 1 as c_int }; + if success { Event::from_ll(&raw) } + else { panic!(get_error()) } + } + } - if success { Ok(Event::from_ll(&raw)) } - else { Err(get_error()) } -} + /// Waits until the specified timeout (in milliseconds) for the next available event. + pub fn wait_event_timeout(&mut self, timeout: u32) -> Option { + unsafe { + let raw = mem::uninitialized(); + let success = ll::SDL_WaitEventTimeout(&raw, timeout as c_int) == 1; -/// Wait until the specified timeout (in milliseconds) for the next available event. -pub fn wait_event_timeout(timeout: i32) -> SdlResult { - let raw = unsafe { mem::uninitialized() }; - let success = unsafe { ll::SDL_WaitEventTimeout(&raw, timeout as c_int) == - 1 as c_int }; + if success { Some(Event::from_ll(&raw)) } + else { None } + } + } - if success { Ok(Event::from_ll(&raw)) } - else { Err(get_error()) } -} + /// Returns a waiting iterator that calls `wait_event()`. + /// + /// Note: The iterator will never terminate. + pub fn wait_iter(&mut self) -> EventWaitIterator { + EventWaitIterator { + event_pump: unsafe { EventPump::_unchecked_new() } + } + } -extern "C" fn event_filter_wrapper(userdata: *const c_void, event: *const ll::SDL_Event) -> c_int { - let filter: extern fn(event: Event) -> bool = unsafe { mem::transmute(userdata) }; - if event.is_null() { 1 } - else { filter(Event::from_ll(unsafe { &*event })) as c_int } -} + /// Returns a waiting iterator that calls `wait_event_timeout()`. + /// + /// Note: The iterator will never terminate, unless waiting for an event + /// exceeds the specified timeout. + pub fn wait_timeout_iter(&mut self, timeout: u32) -> EventWaitTimeoutIterator { + EventWaitTimeoutIterator { + event_pump: unsafe { EventPump::_unchecked_new() }, + timeout: timeout + } + } -/// Set up a filter to process all events before they change internal state and are posted to the internal event queue. -pub fn set_event_filter(filter_func: extern fn(event: Event) -> bool) { - unsafe { ll::SDL_SetEventFilter(event_filter_wrapper, - filter_func as *const _) } -} - -/// Add a callback to be triggered when an event is added to the event queue. -pub fn add_event_watch(filter_func: extern fn(event: Event) -> bool) { - unsafe { ll::SDL_AddEventWatch(event_filter_wrapper, - filter_func as *const _) } -} - -/// Remove an event watch callback added. -pub fn delete_event_watch(filter_func: extern fn(event: Event) -> bool) { - unsafe { ll::SDL_DelEventWatch(event_filter_wrapper, - filter_func as *const _) } -} - -/// Run a specific filter function on the current event queue, removing any events for which the filter returns 0. -pub fn filter_events(filter_func: extern fn(event: Event) -> bool) { - unsafe { ll::SDL_FilterEvents(event_filter_wrapper, - filter_func as *const _) } -} - -/// Set the state of processing events. -pub fn set_event_state(_type: EventType, state: bool) { - unsafe { ll::SDL_EventState(_type as uint32_t, - state as ll::SDL_EventState); } -} - -/// Get the state of processing events. -pub fn get_event_state(_type: EventType) -> bool { - unsafe { ll::SDL_EventState(_type as uint32_t, ll::SDL_QUERY) - == ll::SDL_ENABLE } -} - -/// allocate a set of user-defined events, and return the beginning event number for that set of events -pub fn register_events(num_events: i32) -> Option { - let ret = unsafe { ll::SDL_RegisterEvents(num_events as c_int) }; - if ret == (-1 as uint32_t) { - None - } else { - Some(ret as u32) + /// Internal use only; used by `sdl2::Sdl::event_pump()` + #[doc(hidden)] + pub unsafe fn _unchecked_new<'a>() -> EventPump<'a> { + EventPump { + _marker: NoCopy, + _sdl: PhantomData + } } } -/// add an event to the event queue +/// An iterator that calls `EventPump::poll_event()`. +#[must_use = "iterators are lazy and do nothing unless consumed"] +pub struct EventPollIterator<'a> { + event_pump: EventPump<'a> +} + +impl<'a> Iterator for EventPollIterator<'a> { + pub type Item = Event; + + fn next(&mut self) -> Option { + self.event_pump.poll_event() + } +} + +/// An iterator that calls `EventPump::wait_event()`. +#[must_use = "iterators are lazy and do nothing unless consumed"] +pub struct EventWaitIterator<'a> { + event_pump: EventPump<'a> +} + +impl<'a> Iterator for EventWaitIterator<'a> { + pub type Item = Event; + fn next(&mut self) -> Option { Some(self.event_pump.wait_event()) } +} + +/// An iterator that calls `EventPump::wait_event_timeout()`. +#[must_use = "iterators are lazy and do nothing unless consumed"] +pub struct EventWaitTimeoutIterator<'a> { + event_pump: EventPump<'a>, + timeout: u32 +} + +impl<'a> Iterator for EventWaitTimeoutIterator<'a> { + pub type Item = Event; + fn next(&mut self) -> Option { self.event_pump.wait_event_timeout(self.timeout) } +} + +/// Removes all events in the event queue that match the specified event type. +pub fn flush_event(event_type: EventType) { + unsafe { ll::SDL_FlushEvent(event_type as uint32_t) }; +} + +/// Removes all events in the event queue that match the specified type range. +pub fn flush_events(min_type: u32, max_type: u32) { + unsafe { ll::SDL_FlushEvents(min_type, max_type) }; +} + +/// Reads the events at the front of the event queue, until the maximum amount +/// of events is read. +/// +/// The events will _not_ be removed from the queue. +/// +/// # Example +/// ```no_run +/// use sdl2::event::{Event, peek_events}; +/// +/// // Read up to 1024 events +/// let events: Vec = peek_events(1024); +/// +/// // Print each one +/// for event in events { +/// println!("{:?}", event); +/// } +/// ``` +pub fn peek_events(max_amount: u32) -> B +where B: FromIterator +{ + unsafe { + let mut events = Vec::with_capacity(max_amount as usize); + + let result = { + let events_ptr = events.as_mut_slice().as_mut_ptr(); + + ll::SDL_PeepEvents( + events_ptr, + max_amount as c_int, + ll::SDL_PEEKEVENT, + ll::SDL_FIRSTEVENT, + ll::SDL_LASTEVENT + ) + }; + + if result < 0 { + // The only error possible is "Couldn't lock event queue" + panic!(get_error()); + } else { + events.set_len(max_amount as usize); + + events.iter().map(|event_raw| { + Event::from_ll(event_raw) + }).collect() + } + } +} + +/// Pushes an event to the event queue. pub fn push_event(event: Event) -> SdlResult<()> { match event.to_ll() { Some(raw_event) => { @@ -955,7 +1048,7 @@ pub fn push_event(event: Event) -> SdlResult<()> { else { Err(get_error()) } }, None => { - Err("Unsupport event type to push back to queue.".to_owned()) + Err(format!("Cannot push unsupported event type to the queue")) } } } diff --git a/src/sdl2/sdl.rs b/src/sdl2/sdl.rs index c2ca45d8..63b0d71b 100644 --- a/src/sdl2/sdl.rs +++ b/src/sdl2/sdl.rs @@ -2,6 +2,7 @@ use std::ffi::{c_str_to_bytes, CString}; use std::marker::{NoCopy, PhantomData}; use sys::sdl as ll; +use event::EventPump; bitflags! { flags InitFlag: u32 { @@ -77,6 +78,11 @@ impl Sdl { flags & InitFlag::from_bits(raw).unwrap() } } + + /// Obtains the SDL event pump. + pub fn event_pump(&self) -> EventPump { + unsafe { EventPump::_unchecked_new() } + } } impl Drop for Sdl { @@ -132,7 +138,9 @@ pub fn init(flags: InitFlag) -> SdlResult { Err(format!("Cannot have more than one `Sdl` in use at the same time")) } else { if ll::SDL_Init(flags.bits()) == 0 { - Ok(Sdl { _marker: NoCopy }) + Ok(Sdl { + _marker: NoCopy + }) } else { IS_SDL_CONTEXT_ALIVE.swap(false, Ordering::Relaxed); Err(get_error())