//! A Piston event loop for games and interactive applications
#![deny(
rust_2018_compatibility,
rust_2018_idioms,
future_incompatible,
nonstandard_style,
unused,
clippy::all,
clippy::doc_markdown,
missing_docs,
missing_copy_implementations,
missing_debug_implementations
)]
use std::{
cmp,
time::{Duration, Instant},
};
use input::{AfterRenderArgs, Event, IdleArgs, RenderArgs, UpdateArgs};
use window::Window;
/// 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, Debug)]
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;
macro_rules! next_with_sleep {
($self:ident, $window:ident, $sleep:ident $(. $x:tt)?) => {{
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)$(.$x)?;
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());
}
};
}
}};
}
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,
first_frame: true,
}
}
/// Returns the next event.
pub fn next<W>(&mut self, window: &mut W) -> Option<Event>
where
W: Window,
{
let sleep = spin_sleep::sleep;
next_with_sleep!(self, window, sleep)
}
/// Returns the next event.
#[cfg(feature = "async")]
pub async fn async_next<W>(&mut self, window: &mut W) -> Option<Event>
where
W: Window,
{
let sleep = tokio::time::sleep;
next_with_sleep!(self, window, sleep .await)
}
}
/// 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);
}
}