Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 96 additions & 41 deletions nannou/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,22 +175,20 @@ struct DrawState {
#[derive(Clone)]
pub struct Proxy {
event_loop_proxy: winit::event_loop::EventLoopProxy<()>,
// Indicates whether or not the events loop is currently asleep.
// Whether or not a wakeup is already queued.
//
// This is set to `true` each time the events loop is ready to return and the `LoopMode` is
// set to `Wait` for events.
//
// This value is set back to `false` each time the events loop receives any kind of event.
event_loop_is_asleep: Arc<AtomicBool>,
// Used to avoid spuriously calling `EventLoopProxy::send_event` as this can be expensive on
// some platforms.
wakeup_queued: Arc<AtomicBool>,
}

// State related specifically to the application loop, shared between loop modes.
struct LoopState {
updates_since_event: usize,
updates_since_event: u64,
loop_start: Instant,
last_update: Instant,
total_updates: u64,
events_since_wakeup: usize,
last_surface_texture_acquired: Option<std::time::Instant>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think using the instant crate would be better in order to be compatible with WASM.

}

/// The mode in which the **App** is currently running the event loop and emitting `Update` events.
Expand All @@ -212,7 +210,8 @@ pub enum LoopMode {
update_interval: Duration,
},

/// Waits for user input events to occur before calling `event` with an `Update` event.
/// Waits for user input, window, device and wake-up events to occur before producing `Update`
/// events.
///
/// This is particularly useful for low-energy GUIs that only need to update when some sort of
/// input has occurred. The benefit of using this mode is that you don't waste CPU cycles
Expand Down Expand Up @@ -450,10 +449,10 @@ where

// Create the proxy used to awaken the event loop.
let event_loop_proxy = event_loop.create_proxy();
let event_loop_is_asleep = Arc::new(AtomicBool::new(false));
let wakeup_queued = Arc::new(AtomicBool::new(false));
let event_loop_proxy = Proxy {
event_loop_proxy,
event_loop_is_asleep,
wakeup_queued,
};

// Initialise the app.
Expand Down Expand Up @@ -917,10 +916,9 @@ impl Proxy {
/// method as frequently as necessary across methods without causing any underlying OS methods
/// to be called more than necessary.
pub fn wakeup(&self) -> Result<(), winit::event_loop::EventLoopClosed<()>> {
if self.event_loop_is_asleep.load(atomic::Ordering::Relaxed) {
if !self.wakeup_queued.load(atomic::Ordering::SeqCst) {
self.event_loop_proxy.send_event(())?;
self.event_loop_is_asleep
.store(false, atomic::Ordering::Relaxed);
self.wakeup_queued.store(true, atomic::Ordering::SeqCst);
}
Ok(())
}
Expand Down Expand Up @@ -1073,35 +1071,69 @@ fn run_loop<M, E>(
loop_start,
last_update: loop_start,
total_updates: 0,
events_since_wakeup: 0,
last_surface_texture_acquired: None,
};

// Run the event loop.
event_loop.run(move |mut event, event_loop_window_target, control_flow| {
println!("{:?}", event);

// Set the event loop window target pointer to allow for building windows.
app.event_loop_window_target = Some(EventLoopWindowTarget::Pointer(
event_loop_window_target as *const _,
));

let mut exit = false;
let mut requested_redraw = false;

match event {
// Check to see if we need to emit an update and request a redraw.
winit::event::Event::MainEventsCleared => {
if let Some(model) = model.as_mut() {
let loop_mode = app.loop_mode();
let now = Instant::now();
let mut do_update = |loop_state: &mut LoopState| {
apply_update(&mut app, model, event_fn, update_fn, loop_state, now);
};
// After all events are cleared, check if we should update and request a redraw.
fn should_update(
loop_mode: LoopMode,
loop_state: &LoopState,
now: std::time::Instant,
display_frame_interval: std::time::Duration,
) -> bool {
match loop_mode {
LoopMode::NTimes { number_of_updates }
if loop_state.total_updates >= number_of_updates as u64 => {}
if loop_state.total_updates >= number_of_updates as u64 => return false,
// Sometimes winit interrupts ControlFlow::Wait for no good reason, so we
// make sure that there were some events in order to do an update when
// LoopMode::Wait is used.
LoopMode::Wait if loop_state.events_since_wakeup == 0 => {}
_ => do_update(&mut loop_state),
LoopMode::Wait if loop_state.updates_since_event > 0 => return false,
// TODO: Consider allowing for a custom number of updates like so:
// LoopMode::Wait { updates_before_waiting } =>
// if loop_state.updates_since_event > updates_before_waiting as u64 => {}
_ => (),
}
// If any of the windows are `wgpu::PresentMode::Fifo` (the default), we should
// avoid updating and requesting a redraw too early in order to avoid getting
// blocked on `get_current_texture`. This allows for collecting more input
// events in the mean time, reducing the input lag by up to a frame.
// TODO: Check `PresentMode`s here.
let last_redraw = match loop_state.last_surface_texture_acquired {
// If we haven't drawn anything yet, do so.
None => return true,
Some(ts) => ts,
};
if now.duration_since(last_redraw) < display_frame_interval {
return false;
}
true
}

if let Some(model) = model.as_mut() {
let loop_mode = app.loop_mode();
let now = Instant::now();
// TODO: Retrieve this from winit... maybe monitor video mode?
let frame_interval = std::time::Duration::from_millis(16);
if should_update(loop_mode, &loop_state, now, frame_interval) {
apply_update(&mut app, model, event_fn, update_fn, &mut loop_state, now);

// TODO: This necessary?
requested_redraw = true;
}
}
}
Expand All @@ -1121,6 +1153,7 @@ fn run_loop<M, E>(
.get_mut(&window_id)
.expect("no window for `RedrawRequest`");
let texture = window.surface.get_current_texture();
loop_state.last_surface_texture_acquired = Some(std::time::Instant::now());
let nth_frame = window.frame_count;
(texture, nth_frame)
};
Expand Down Expand Up @@ -1248,6 +1281,7 @@ fn run_loop<M, E>(
// Assume invalidated window was cleared above before `view()`
window.is_invalidated = false;
window.frame_count += 1;
loop_state.updates_since_event += 1;
}
}
}
Expand All @@ -1263,17 +1297,26 @@ fn run_loop<M, E>(
//app.wgpu_adapters().poll_all_devices(false);
}

// Ignore wake-up events for now. Currently, these can only be triggered via the app proxy.
winit::event::Event::NewEvents(_) => {
loop_state.events_since_wakeup = 0;
}

// Track the number of updates since the last I/O event.
// This is necessary for the `Wait` loop mode to behave correctly.
ref _other_event => {
// For all window, device and user (app proxy) events reset the `updates_since_event`
// count which is used to improve behaviour for the `Wait` loop mode.
// TODO: Document this set of events under `LoopMode::Wait`.
winit::event::Event::WindowEvent { .. }
| winit::event::Event::DeviceEvent { .. }
| winit::event::Event::UserEvent(_)
| winit::event::Event::Suspended
| winit::event::Event::Resumed => {
loop_state.updates_since_event = 0;
loop_state.events_since_wakeup += 1;

// `UserEvent` is emitted on `wakeup`.
if let winit::event::Event::UserEvent(_) = event {
app.event_loop_proxy.wakeup_queued.store(false, atomic::Ordering::SeqCst);
}
}

// Ignore `NewEvents`.
winit::event::Event::NewEvents(_)
// `LoopDestroyed` is handled later in `process_and_emit_winit_event` so ignore it here.
| winit::event::Event::LoopDestroyed => {}
}

// We must reconfigure the wgpu surface if the window was resized.
Expand Down Expand Up @@ -1305,7 +1348,7 @@ fn run_loop<M, E>(
}
}

// Process the event with the users functions and see if we need to exit.
// Process the event with the user's functions and see if we need to exit.
if let Some(model) = model.as_mut() {
exit |= process_and_emit_winit_event::<M, E>(&mut app, model, event_fn, &event);
}
Expand All @@ -1314,13 +1357,24 @@ fn run_loop<M, E>(
let loop_mode = app.loop_mode();
*control_flow = match loop_mode {
LoopMode::Wait => {
// Trigger some extra updates for conrod GUIs to finish "animating". The number of
// updates used to be configurable, but I don't think there's any use besides GUI.
if loop_state.updates_since_event < LoopMode::UPDATES_PER_WAIT_EVENT as usize {
let ten_ms = Instant::now() + Duration::from_millis(10);
ControlFlow::WaitUntil(ten_ms)
} else {
if requested_redraw {
ControlFlow::Wait
} else {
if let Some(ts) = loop_state.last_surface_texture_acquired {
let since_last_frame = ts.elapsed();
// TODO: Get this from winit.
let frame_interval = std::time::Duration::from_millis(16);
dbg!(since_last_frame);
if since_last_frame < frame_interval {
let remaining = frame_interval - since_last_frame;
ControlFlow::WaitUntil(Instant::now() + remaining)
} else {
ControlFlow::Wait
}
} else {
ControlFlow::Wait
}
//ControlFlow::WaitUntil(Instant::now() + Duration::from_millis(16))
}
}
LoopMode::NTimes { number_of_updates }
Expand Down Expand Up @@ -1387,7 +1441,8 @@ fn apply_update<M, E>(
}
loop_state.last_update = now;
loop_state.total_updates += 1;
loop_state.updates_since_event += 1;
//loop_state.updates_since_event += 1;

// Request redraw from windows.
let windows = app.windows.borrow();
for window in windows.values() {
Expand Down