Skip to content

Instantly share code, notes, and snippets.

@Osspial
Created August 24, 2018 01:27
Show Gist options
  • Save Osspial/53efa87797899872650f6bece14736c0 to your computer and use it in GitHub Desktop.
Save Osspial/53efa87797899872650f6bece14736c0 to your computer and use it in GitHub Desktop.
Event Loop 2.0 Original Design
  • Depricate the poll_events function, in favor of run_forever with the expanded ControlFlow enum.
  • Change the definition of ControlFlow to the following:
    pub enum ControlFlow {
        /// Equivalent to the current `Continue`. Suspends the thread until another OS event
        /// arrives.
        Wait,
        /// Suspends the thread until either another event arrives, or the timeout expires.
        WaitTimeout(Duration),
        /// Replaces the `poll_events` method's functionality. After all the OS's events have
        /// been processed, the callback is repeatedly called with `Event::Poll`, until the
        /// next event arrives.
        Poll,
        /// Breaks out of the event loop.
        Break
    }
  • Add the following variants to the Event enum:
    /// Sent if the time specified by `ControlFlow::WaitTimeout` has been elapsed. Contains the
    /// moment the timeout was requested, and the requested duration of the timeout.
    TimeoutExpired {
        start: Instant,
        duration: Duration
    },
    /// Sent if the OS has new events to send to the window, after a wait was requested. Contains
    /// the moment the wait was requested, and if a wait timout was requested, its duration.
    WaitCancelled {
        start: Instant,
        requested_duration: Option<Duration>
    },
    /// Sent after `ControlFlow::Poll` was returned by the callback, and all the OS events have
    /// been exhausted.
    Poll
  • Change the event loop's callback definition, so that it takes a new type: EventsLoopWindows
    • Change Window::new() and WindowBuilder::build() to take this new type.
    • Implement Deref<Target=EventsLoopWindows> for EventsLoop.
    • Motivation: Currently, a programmer can't create new windows while inside of run_forever, as the function mutably borrows EventsLoop. Adding this new type would allow them to do that.
  • Increase the importance of WindowEvent::Refresh:
    • Make handling this event the officially recommended method of redrawing a window.
    • Add the following function to Window:
    /// Makes the events loop send a `WindowEvent::Refresh` event, after all OS events have been
    /// processed by the event loop (`WindowEvent`, `DeviceEvent`, `Awakened`, and `Suspended`), but before any
    /// winit events have been processed (`TimeoutExpred`, `WaitCancelled`, and `Poll`).
    fn queue_refresh(&self) {...}
    • Motivations:

      • Integrates better with OS-generated redraw events, i.e. when a window is resized (on Windows, this is through the WM_PAINT message). By using this, we can handle window redrawing entirely within the OS callback, which is where the OS expects this to happen. This should improve the smoothness of window resizes, and will hopefully fix the current issue where resizing the window leaves some sort of blank space at the edges of the window, before the program has a chance to draw content to fill it.
      • Makes it much easier for the program to redraw only when necessary, especially when waiting for events. Currently, if a program uses run_forever and wants to only redraws when user input is received, the simplest method is to redraw after every event is received, like so:
      events_loop.run_forever(|event, _| {
          match event {
              Event::WindowEvent{..} => {
                  /* Update program state */
              },
              _ => ()
          }
      
          /* Redraw the window */
      
          ControlFlow::Continue
      });

      This is the wrong approach: it isn't uncommon for the OS to send multiple events in the duration of a single frame, and redrawing after every event wastes time rendering an image the program's user will never see, and can slow down the program. Right now, the only way I can see to do this properly is to do the following:

      loop {
          let update_state = |event| {/* Updates program state */};
          // Wait for the first OS event to be received, and then exit the event loop.
          events_loop.run_forever(|event| {update_state(event); ControlFlow::Break});
          // Handle the rest of the frame's events.
          events_loop.poll_events(update_state);
      
          /* Redraw the window */
      }

      I don't think that's the kind of design we want to be encouraging, and neither the issue nor the fix is obvious to a programmer using winit. If run_forever is being used, the program should spend pretty much its entire lifetime within the loop, and right now that's discouraged. Using this API would let us write the event loop as the following:

      events_loop.run_forever(|event, _| {
          match event {
              Event::WindowEvent{..} => {
                  /* Update program state */
      
                  // Tells winit to queue a window refresh. Importantly, the refresh doesn't happen right
                  // when this function is called, letting winit delay drawing until after we've processed
                  // all the user input events and the program's state has stopped changing at a rate faster
                  // than the user can see.
                  window.queue_refresh();
              },
              Event::Refresh => {
                  /* Redraw the window */
              },
              _ => ()
          }
      
          ControlFlow::Wait
      });

      Which is both cleaner and a more direct way to have the program behave the right way.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment