Permalink
Join GitHub today
GitHub is home to over 40 million developers working together to host and review code, manage projects, and build software together.
Sign up| //! A Piston event loop for games and interactive applications | |
| #![deny(missing_docs)] | |
| #![deny(missing_copy_implementations)] | |
| extern crate window; | |
| extern crate input; | |
| use std::thread::sleep; | |
| use std::time::{Duration, Instant}; | |
| use std::cmp; | |
| use window::Window; | |
| use input::{Event, AfterRenderArgs, IdleArgs, RenderArgs, UpdateArgs}; | |
| /// Tells whether last emitted event was idle or not. | |
| #[derive(Copy, Clone, Debug, PartialEq, Eq)] | |
| enum Idle { | |
| No, | |
| Yes, | |
| } | |
| #[derive(Copy, Clone, Debug)] | |
| enum State { | |
| Render, | |
| SwapBuffers, | |
| UpdateLoop(Idle), | |
| HandleEvents, | |
| Update, | |
| } | |
| /// Stores event loop settings. | |
| #[derive(Copy, Clone, Debug)] | |
| pub struct EventSettings { | |
| /// The maximum number of frames per second | |
| /// | |
| /// The frame rate can be lower because the | |
| /// next frame is always scheduled from the previous frame. | |
| /// This causes the frames to "slip" over time. | |
| pub max_fps: u64, | |
| /// The number of updates per second | |
| /// | |
| /// This is the fixed update rate on average over time. | |
| /// If the event loop lags, it will try to catch up. | |
| /// When set to `0`, update events are disabled. | |
| pub ups: u64, | |
| /// The number of delayed updates before skipping them to catch up. | |
| /// When set to `0`, it will always try to catch up. | |
| pub ups_reset: u64, | |
| /// Enable or disable automatic swapping of buffers. | |
| pub swap_buffers: bool, | |
| /// Enable or disable benchmark mode. | |
| /// When enabled, it will render and update without sleep and ignore input. | |
| /// Used to test performance by playing through as fast as possible. | |
| /// Requires `lazy` to be set to `false`. | |
| pub bench_mode: bool, | |
| /// Enable or disable rendering only when receiving input. | |
| /// When enabled, update and idle events are disabled. | |
| pub lazy: bool, | |
| } | |
| impl EventSettings { | |
| /// Creates new with default settings. | |
| pub fn new() -> EventSettings { | |
| EventSettings { | |
| max_fps: DEFAULT_MAX_FPS, | |
| ups: DEFAULT_UPS, | |
| swap_buffers: true, | |
| bench_mode: false, | |
| lazy: false, | |
| ups_reset: DEFAULT_UPS_RESET, | |
| } | |
| } | |
| } | |
| impl Default for EventSettings { | |
| fn default() -> EventSettings { | |
| EventSettings::new() | |
| } | |
| } | |
| /// An event loop iterator | |
| /// | |
| /// *Warning: Because the iterator polls events from the window back-end, | |
| /// it must be used on the same thread as the window back-end (usually main thread), | |
| /// unless the window back-end supports multi-thread event polling.* | |
| #[derive(Copy, Clone)] | |
| pub struct Events { | |
| state: State, | |
| last_update: Instant, | |
| last_frame: Instant, | |
| dt_update_in_ns: u64, | |
| dt_frame_in_ns: u64, | |
| dt: f64, | |
| settings: EventSettings, | |
| first_frame: bool, | |
| } | |
| static BILLION: u64 = 1_000_000_000; | |
| fn ns_to_duration(ns: u64) -> Duration { | |
| let secs = ns / BILLION; | |
| let nanos = (ns % BILLION) as u32; | |
| Duration::new(secs, nanos) | |
| } | |
| fn duration_to_secs(dur: Duration) -> f64 { | |
| dur.as_secs() as f64 + dur.subsec_nanos() as f64 / 1_000_000_000.0 | |
| } | |
| /// The default updates per second. | |
| pub const DEFAULT_UPS: u64 = 120; | |
| /// The default delayed updates reset. | |
| pub const DEFAULT_UPS_RESET: u64 = 2; | |
| /// The default maximum frames per second. | |
| pub const DEFAULT_MAX_FPS: u64 = 60; | |
| impl Events { | |
| /// Creates a new event iterator with default UPS and FPS settings. | |
| pub fn new(settings: EventSettings) -> Events { | |
| let start = Instant::now(); | |
| Events { | |
| state: State::Render, | |
| last_update: start, | |
| last_frame: start, | |
| dt_update_in_ns: if settings.ups == 0 { | |
| 0 | |
| } else { | |
| BILLION / settings.ups | |
| }, | |
| dt_frame_in_ns: BILLION / settings.max_fps, | |
| dt: if settings.ups == 0 { | |
| 0.0 | |
| } else { | |
| 1.0 / settings.ups as f64 | |
| }, | |
| settings: settings, | |
| first_frame: true, | |
| } | |
| } | |
| /// Returns the next event. | |
| pub fn next<W>(&mut self, window: &mut W) -> Option<Event> | |
| where W: Window | |
| { | |
| if self.settings.lazy || self.settings.ups == 0 { | |
| // This mode does not emit update events. | |
| // More commonly used in UI applications. | |
| if window.should_close() { | |
| return None; | |
| } | |
| match self.state { | |
| State::SwapBuffers => { | |
| if self.settings.swap_buffers { | |
| window.swap_buffers(); | |
| } | |
| // This mode needs no `Render` state. | |
| self.state = State::UpdateLoop(Idle::No); | |
| return Some(AfterRenderArgs.into()); | |
| } | |
| State::HandleEvents => { | |
| if !self.settings.bench_mode { | |
| // Poll input events until event queue is empty. | |
| if let Some(ev) = window.poll_event() { | |
| return Some(ev); | |
| } | |
| } | |
| self.state = State::Render; | |
| } | |
| _ => {} | |
| } | |
| loop { | |
| // Handle input events before rendering, | |
| // because window might be closed and destroy | |
| // the graphics context. | |
| if let Some(e) = window.poll_event() { | |
| if self.settings.bench_mode { | |
| // Ignore input events in benchmark mode. | |
| // This is to avoid the input events affecting | |
| // the application state when benchmarking. | |
| continue; | |
| } else { | |
| return Some(e); | |
| } | |
| } | |
| if window.should_close() { | |
| return None; | |
| } | |
| if !self.settings.bench_mode { | |
| if self.settings.lazy { | |
| // A lazy event loop always waits until next event, ignoring time to render. | |
| if let State::UpdateLoop(_) = self.state { | |
| // Wait for next input event. | |
| let ev = window.wait_event(); | |
| // Handle rest of events before rendering. | |
| self.state = State::HandleEvents; | |
| return Some(ev); | |
| } | |
| } else { | |
| let current_time = Instant::now(); | |
| let next_frame = self.last_frame + ns_to_duration(self.dt_frame_in_ns); | |
| if !self.first_frame && next_frame > current_time { | |
| if let State::UpdateLoop(Idle::No) = self.state { | |
| // Emit idle event with time until next frame, | |
| // in case the application wants to do some background work. | |
| self.state = State::UpdateLoop(Idle::Yes); | |
| let seconds = duration_to_secs(next_frame - current_time); | |
| return Some(IdleArgs { dt: seconds }.into()); | |
| } | |
| match window.wait_event_timeout(next_frame - current_time) { | |
| None => {} | |
| Some(x) => { | |
| // Handle rest of events before rendering. | |
| self.state = State::HandleEvents; | |
| return Some(x) | |
| } | |
| } | |
| } | |
| } | |
| } | |
| self.first_frame = false; | |
| // In normal mode, let the FPS slip if late. | |
| self.last_frame = Instant::now(); | |
| let size = window.size(); | |
| let draw_size = window.draw_size(); | |
| if size.width != 0.0 && size.height != 0.0 { | |
| // Swap buffers next time. | |
| self.state = State::SwapBuffers; | |
| return Some(RenderArgs { | |
| ext_dt: 0.0, | |
| window_size: size.into(), | |
| draw_size: draw_size.into(), | |
| }.into()); | |
| } else { | |
| // Can not render at this time. | |
| self.state = State::UpdateLoop(Idle::No); | |
| } | |
| } | |
| } | |
| loop { | |
| if window.should_close() { | |
| return None; | |
| } | |
| self.state = match self.state { | |
| State::Render => { | |
| // Handle input events before rendering, | |
| // because window might be closed and destroy | |
| // the graphics context. | |
| if let Some(e) = window.poll_event() { | |
| if self.settings.bench_mode { | |
| // Ignore input events in benchmark mode. | |
| // This is to avoid the input events affecting | |
| // the application state when benchmarking. | |
| continue; | |
| } else { | |
| return Some(e); | |
| } | |
| } | |
| if window.should_close() { | |
| return None; | |
| } | |
| if self.settings.bench_mode { | |
| // In benchmark mode, pretend FPS is perfect. | |
| self.last_frame += ns_to_duration(self.dt_frame_in_ns); | |
| } else { | |
| // In normal mode, let the FPS slip if late. | |
| self.last_frame = Instant::now(); | |
| } | |
| let size = window.size(); | |
| let draw_size = window.draw_size(); | |
| if size.width != 0.0 && size.height != 0.0 { | |
| // Swap buffers next time. | |
| self.state = State::SwapBuffers; | |
| return Some(RenderArgs { | |
| // Extrapolate time forward to allow smooth motion. | |
| ext_dt: duration_to_secs(self.last_frame | |
| .duration_since(self.last_update)), | |
| window_size: size.into(), | |
| draw_size: draw_size.into(), | |
| }.into()); | |
| } | |
| State::UpdateLoop(Idle::No) | |
| } | |
| State::SwapBuffers => { | |
| if self.settings.swap_buffers { | |
| window.swap_buffers(); | |
| } | |
| self.state = State::UpdateLoop(Idle::No); | |
| return Some(AfterRenderArgs.into()); | |
| } | |
| State::UpdateLoop(ref mut idle) => { | |
| if self.settings.bench_mode { | |
| // In benchmark mode, pick the next event without sleep. | |
| // Idle and input events are ignored. | |
| // This is to avoid the input events affecting | |
| // the application state when benchmarking. | |
| let next_frame = self.last_frame + ns_to_duration(self.dt_frame_in_ns); | |
| let next_update = self.last_update + ns_to_duration(self.dt_update_in_ns); | |
| let next_event = cmp::min(next_frame, next_update); | |
| if next_event == next_frame { | |
| State::Render | |
| } else { | |
| State::HandleEvents | |
| } | |
| } else { | |
| let current_time = Instant::now(); | |
| let next_frame = self.last_frame + ns_to_duration(self.dt_frame_in_ns); | |
| let next_update = self.last_update + ns_to_duration(self.dt_update_in_ns); | |
| let next_event = cmp::min(next_frame, next_update); | |
| if next_event > current_time { | |
| if let Some(x) = window.poll_event() { | |
| *idle = Idle::No; | |
| return Some(x); | |
| } else if *idle == Idle::No { | |
| *idle = Idle::Yes; | |
| let seconds = duration_to_secs(next_event - current_time); | |
| return Some(IdleArgs { dt: seconds }.into()); | |
| } | |
| sleep(next_event - current_time); | |
| State::UpdateLoop(Idle::No) | |
| } else if next_event == next_frame { | |
| State::Render | |
| } else { | |
| State::HandleEvents | |
| } | |
| } | |
| } | |
| State::HandleEvents => { | |
| if self.settings.bench_mode { | |
| // Ignore input events. | |
| // This is to avoid the input events affecting | |
| // the application state when benchmarking. | |
| match window.poll_event() { | |
| None => State::Update, | |
| Some(_) => State::HandleEvents, | |
| } | |
| } else { | |
| // Handle all events before updating. | |
| match window.poll_event() { | |
| None => State::Update, | |
| x => return x, | |
| } | |
| } | |
| } | |
| State::Update => { | |
| self.state = State::UpdateLoop(Idle::No); | |
| if !self.settings.bench_mode && self.settings.ups_reset > 0 && | |
| Instant::now() - self.last_update > | |
| ns_to_duration(self.settings.ups_reset * self.dt_update_in_ns) { | |
| // Skip updates because CPU is too busy. | |
| self.last_update = Instant::now(); | |
| } else { | |
| // Use the update state stored right after sleep. | |
| self.last_update += ns_to_duration(self.dt_update_in_ns); | |
| } | |
| return Some(UpdateArgs { dt: self.dt }.into()); | |
| } | |
| }; | |
| } | |
| } | |
| } | |
| /// Methods implemented for changing event loop settings. | |
| pub trait EventLoop: Sized { | |
| /// Returns event loop settings. | |
| fn get_event_settings(&self) -> EventSettings; | |
| /// Sets event loop settings. | |
| fn set_event_settings(&mut self, settings: EventSettings); | |
| /// The number of updates per second | |
| /// | |
| /// This is the fixed update rate on average over time. | |
| /// If the event loop lags, it will try to catch up. | |
| /// When set to `0`, update events are disabled. | |
| fn set_ups(&mut self, frames: u64) { | |
| let old_settings = self.get_event_settings(); | |
| self.set_event_settings(EventSettings { ups: frames, ..old_settings }); | |
| } | |
| /// The number of updates per second | |
| /// | |
| /// This is the fixed update rate on average over time. | |
| /// If the event loop lags, it will try to catch up. | |
| /// When set to `0`, update events are disabled. | |
| fn ups(mut self, frames: u64) -> Self { | |
| self.set_ups(frames); | |
| self | |
| } | |
| /// The number of delayed updates before skipping them to catch up. | |
| /// When set to `0`, it will always try to catch up. | |
| fn set_ups_reset(&mut self, frames: u64) { | |
| let old_settings = self.get_event_settings(); | |
| self.set_event_settings(EventSettings { ups_reset: frames, ..old_settings }); | |
| } | |
| /// The number of delayed updates before skipping them to catch up. | |
| /// When set to `0`, it will always try to catch up. | |
| fn ups_reset(mut self, frames: u64) -> Self { | |
| self.set_ups_reset(frames); | |
| self | |
| } | |
| /// The maximum number of frames per second | |
| /// | |
| /// The frame rate can be lower because the | |
| /// next frame is always scheduled from the previous frame. | |
| /// This causes the frames to "slip" over time. | |
| fn set_max_fps(&mut self, frames: u64) { | |
| let old_settings = self.get_event_settings(); | |
| self.set_event_settings(EventSettings { max_fps: frames, ..old_settings }) | |
| } | |
| /// The maximum number of frames per second | |
| /// | |
| /// The frame rate can be lower because the | |
| /// next frame is always scheduled from the previous frame. | |
| /// This causes the frames to "slip" over time. | |
| fn max_fps(mut self, frames: u64) -> Self { | |
| self.set_max_fps(frames); | |
| self | |
| } | |
| /// Enable or disable automatic swapping of buffers. | |
| fn set_swap_buffers(&mut self, enable: bool) { | |
| let old_settings = self.get_event_settings(); | |
| self.set_event_settings(EventSettings { swap_buffers: enable, ..old_settings }) | |
| } | |
| /// Enable or disable automatic swapping of buffers. | |
| fn swap_buffers(mut self, enable: bool) -> Self { | |
| self.set_swap_buffers(enable); | |
| self | |
| } | |
| /// Enable or disable benchmark mode. | |
| /// When enabled, it will render and update without sleep and ignore input. | |
| /// Used to test performance by playing through as fast as possible. | |
| /// Requires `lazy` to be set to `false`. | |
| fn set_bench_mode(&mut self, enable: bool) { | |
| let old_settings = self.get_event_settings(); | |
| self.set_event_settings(EventSettings { bench_mode: enable, ..old_settings }) | |
| } | |
| /// Enable or disable benchmark mode. | |
| /// When enabled, it will render and update without sleep and ignore input. | |
| /// Used to test performance by playing through as fast as possible. | |
| /// Requires `lazy` to be set to `false`. | |
| fn bench_mode(mut self, enable: bool) -> Self { | |
| self.set_bench_mode(enable); | |
| self | |
| } | |
| /// Enable or disable rendering only when receiving input. | |
| /// When enabled, update events are disabled. | |
| /// Idle events are emitted while receiving input. | |
| fn set_lazy(&mut self, enable: bool) { | |
| let old_settings = self.get_event_settings(); | |
| self.set_event_settings(EventSettings { lazy: enable, ..old_settings }) | |
| } | |
| /// Enable or disable rendering only when receiving input. | |
| /// When enabled, update events are disabled. | |
| /// Idle events are emitted while receiving input. | |
| fn lazy(mut self, enable: bool) -> Self { | |
| self.set_lazy(enable); | |
| self | |
| } | |
| } | |
| impl EventLoop for EventSettings { | |
| fn get_event_settings(&self) -> Self { | |
| *self | |
| } | |
| fn set_event_settings(&mut self, settings: Self) { | |
| *self = settings; | |
| } | |
| } | |
| impl EventLoop for Events { | |
| fn get_event_settings(&self) -> EventSettings { | |
| self.settings | |
| } | |
| fn set_event_settings(&mut self, settings: EventSettings) { | |
| // Reset event loop to initial state. | |
| *self = Events::new(settings); | |
| } | |
| } |