Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Consolidate poll_events() and run_forever() into get_event(Timeout) #231

Closed
willglynn opened this issue Jul 17, 2017 · 32 comments
Closed

Consolidate poll_events() and run_forever() into get_event(Timeout) #231

willglynn opened this issue Jul 17, 2017 · 32 comments
Labels
C - waiting on author Waiting for a response or another PR D - hard Likely harder than most tasks here P - high Vital to have S - api Design and usability
Milestone

Comments

@willglynn
Copy link

willglynn commented Jul 17, 2017

EDIT: THIS ISSUE HAS BEEN SUPERSEDED by #459.

In #219, I examined some unfortunate event handling complexities on MacOS X. That examination led me to view and re-implement both poll_events() and run_forever() as special cases of a more general API, get_event(Timeout) -> Option<Event>.

I propose:

  1. Refactoring each platform to expose this get_event(Timeout) interface,
  2. Refactoring poll_events() and run_forever() to be implemented in terms of get_event(Timeout), and
  3. Deprecating poll_events() and run_forever() in favor of get_event(Timeout).

This is a user-facing API change, so I'm accompanying this proposal with a larger than usual explanation.

Receive-with-time-constraint is the fundamental operation

poll_events() allows the user to communicate "I want all the events that are ready, but I don't want you to wait". Let's call that get_event(Timeout::Now), and make the loop explicit:

// current
    events_loop.poll_events(|event| {
        match event {
            Event::WindowEvent { event: WindowEvent::Resized(w, h), .. } => {
                println!("The window was resized to {}x{}", w, h);
            },
            _ => ()
        }
    });
    
// proposed
    while let Some(event) = events_loop.get_event(Timeout::Now) {
        match event {
            Event::WindowEvent { event: WindowEvent::Resized(w, h), .. }) => {
                println!("The window was resized to {}x{}", w, h);
            },
            _ => (),
        }
    }

run_forever() allows the user to communicate "I want all the events until I tell you to stop", which… is kind of weird, isn't it? At any rate, it's the user's intention to block indefinitely and eventually break out of the loop. Let's call that get_event(Timeout::Forever), and again push down the loop:

// current
    events_loop.run_forever(|event| {
        match event {
            winit::Event::WindowEvent { event: winit::WindowEvent::Closed, .. } => {
                winit::ControlFlow::Break
            },
            _ => winit::ControlFlow::Continue,
        }
    });

// proposed
    loop {
        match events_loop.get_event(Timeout::Forever) {
            winit::Event::WindowEvent { event: winit::WindowEvent::Closed, .. } => {
                break;
            },
            _ => ()
        }
    }

Some users have additional needs, like "I want all the events between now and when I need to update my state/draw the next frame". pistoncore-glutin_window does this today by unconditionally spawning a thread that wakes up the event loop after a fixed period of time. We should be able to support it efficiently on every platform without needing extra threads, and with a timeout-centric API, we can:

// current
    let events_loop_proxy = self.events_loop.create_proxy();
    thread::spawn(move || {
        thread::sleep(timeout);
        events_loop_proxy.wakeup().ok(); // wakeup can fail only if the event loop went away
    });
    {
        let ref mut events = self.events;
        self.events_loop.run_forever(|ev| {
            events.push_back(ev);
            glutin::ControlFlow::Break
        });
    }
    let event = self.events.pop_front();

// proposed
    let event = events_loop.get_event(Timeout::Deadline(timeout));

Timeouts are sufficient to justify a new API

Consider a game that wants to update and render at 60 fps. If the game is running faster than 60 fps, it should sleep and wait until it's time to start the next frame; if it's running slower, it should busy-wait.

This use case presently requires a calling run_forever(callback) to allow the thread to sleep, plus a second thread keeping track of time and calling Proxy::wakeup() as needed. That's wasteful and unnecessarily complex.

winit should support timeouts pattern directly. Additionally, this is a common requirement; every event loop has some way to block until a specified time, or some idiomaitc way to synthesize a timeout. winit should use this where appropriate.

Simplifies the API

winit puts user code at the bottom of the stack, and does not force the user to organize their code in terms of callbacks… except for event handling. Changing the API so that the user calls get_event(Timeout) in a loop adds consistency, and it lets the user define and manage their event loop with normal Rust statements instead of a callback that returns a ControlFlow enum.

This also makes EventsLoop look more like a futures::stream, or a generator, or many other things that users might already know. "I want to get a message from you" requires less explanation than having two different functions that take two different callbacks.

Reduces duplication

poll_events() and run_forever() have considerable overlap, and in the case of the MacOS platform, considerable code duplication. Not only can get_event(Timeout) provide more functionality, in the case of the MacOS platform, refactoring into this caused a net reduction in code.

Low risk

It's very easy to implement poll_events() and run_forever() in terms of get_event(Timeout), which means there's very little risk in deciding to move that direction.

winit can continue to provide the same poll_events() + run_forever() API to the user while refactoring platforms into get_event(Timeout) behind the scenes. Once they're all ready, winit can update the examples, deprecate poll_events() and run_forever(), and ultimately remove at some later point.

Next steps

I think this covers everything:

pub enum Timeout {
    Now,
    Forever,
    Deadline(std::time::Duration),
}

impl EventsLoop {
    pub fn get_event(Timeout) -> Option<Event> {
        // …
    }
}

A change like this requires buy-in, and this isn't my project, so I don't know what all would need to happen. I'm happy to start by soliciting feedback, so: what does everyone think?

@tomaka
Copy link
Contributor

tomaka commented Jul 17, 2017

What you describe is more or less winit's old API. We transitioned to callbacks because it solves the fact that MacOS uses a resize callback, and also in the longer term because of Android and Emscripten.

This also makes EventsLoop look more like a futures::stream

It's the opposite, the futures library is based on callbacks.

@Ralith
Copy link
Contributor

Ralith commented Jul 17, 2017

It's the opposite, the futures library is based on callbacks.

Huh? futures is based on structs that have a poll method which you call to see if they're ready or not, and get a value from if they are.

We transitioned to callbacks because it solves the fact that MacOS uses a resize callback

Couldn't this (and any similar system) be handled by putting the message in a queue in the event loop struct and waking it up?

@tomaka
Copy link
Contributor

tomaka commented Jul 17, 2017

Huh? futures is based on structs that have a poll method which you call to see if they're ready or not, and get a value from if they are.

This is missing half of the documentation. If the future is not ready when you call poll() then it will later automatically call Task::unpark without the user's intervention.

@mitchmindtree
Copy link
Contributor

Couldn't this (and any similar system) be handled by putting the message in a queue in the event loop struct and waking it up?

@Ralith see #219 - The main problem is that in the macOS backend, nextEventMatchingMask blocks from the moment the user presses their mouse to begin a resize until the end when the user releases the mouse. This means we can't return from run_forever, poll_events or the proposed get_event method until the user ends the resize, at which point all the Resized events would be dumped at once. @willglynn I just tested the window.rs example with your coroutines branch on my macOS machine and it has re-introduced this problem now that you have re-implemented run_forever in terms of get_event.

I would recommend breaking this proposal into two distinct proposals:

1. Offer a way to timeout on waiting for events.

I agree that this makes sense (as long as all platforms offer a native API for this) to avoid the need for users to spawn a thread.

2. Change the event emitting API by removing the user callback approach in favour of returning events from a function.

From my understanding this is a much harder problem due to the frustrating behaviour of macOS that I mentioned above. As it stands, I can't really get behind this proposal without first solving this problem. As tomaka mentioned, this problem is one of the reasons we changed the API from returning Event iterators to user callbacks in the first place. That said, if we can work out how to solve this issue, I would love for a more control-flow friendly approach like we used to have! I just don't yet see a solution that will allow this with consistent behaviour across all platforms.

@willglynn
Copy link
Author

@Ralith:

Couldn't this (and any similar system) be handled by putting the message in a queue in the event loop struct and waking it up?

Yes, with the caveat that the thread receiving the callback is the caller's thread, and there's an internal event loop that we don't control sitting between poll_events()/run_forever() and the callback. I moved the part of the code that can trigger a second internal event loop into a coroutine, and now it works exactly the way you describe: callbacks post a message to a queue, which wakes the event loop, which hops back to get_event() and lets it return.

@mitchmindtree:

From my understanding this is a much harder problem due to the frustrating behaviour of macOS that I mentioned above. As it stands, I can't really get behind this proposal without first solving this problem.

The MacOS problem is sticky because the normal event handling scheme can wander into an internal event loop. Sure, you can get callbacks at various points -- NSWindowDelegate, runloop observers, timers -- but you can't actually return to the caller.

The current approach plumbs the user's callback all the way down so that the MacOS callbacks can trigger them. That delivers resize events as they happen, yes, but it breaks the contract in other ways. poll_events() can hang for the entire duration of a window drag instead of returning immediately, which blocks the example game loop. run_forever() seems like it would be unaffected, but Event::Awakened never gets delivered during a resize, and in practice, callers rely on making it return, which it also can't do during a resize.

My changes in #219 address the core of that issue, which is that some parts of the MacOS event loop can block for a long time. get_event() is responsible for driving those parts forward, but now that happens in a coroutine that periodically returns control to get_event() so that it can check its timeout and return if needed. This makes poll_events() always return without blocking and makes run_forever() respond promptly to ControlFlow::Break and wakeup().

@tomaka:

What you describe is more or less winit's old API. We transitioned to callbacks because it solves the fact that MacOS uses a resize callback, and also in the longer term because of Android and Emscripten.

Callbacks are insufficient to solve the MacOS problem; if poll_events() happens to see a resize start, it'll block for seconds. The only solution that fits the constraints is stack switching, as I describe and implemented in #219. With that in place, get_event(Timeout) seems like the underlying primitive we should expose.

Emscripten bring me to another question: is this the wrong model entirely? Should winit code be running at the bottom of the call stack, instead of running on top of user code? That would let winit hide the difference between a loop {} on most platforms and emscripten::set_main_loop_callback() on JS. That would also fix the MacOS problem without needing coroutines: if everything were a callback, it wouldn't matter if the callbacks were delivered directly from the normal runloop or the middle-of-a-resize runloop. It's only difficult because EventsLoop promises to return to the caller, instead of the caller always running inside EventsLoop.

How does Android fit in?

@mitchmindtree
Copy link
Contributor

but it breaks the contract in other ways. poll_events() can hang for the entire duration of a window drag instead of returning immediately

Ahh you're totally right, this is a pretty major issue with the current implementation.

For anyone else reading along here: As I just mentioned in #219, I'd forgotten to enable --features "context" when testing @willglynn's coroutines branch. It does indeed return during the live resize, and seems like a promising solution for the bug where macOS becomes blocked on an inner loop.

@fschutt
Copy link

fschutt commented Sep 1, 2017

Well yes, but as it is right now, the EventsLoop runs for literally every event, causing the whole app to lag, because there are way too many events and the closure-based design blocks on every event. Try resizing a window with the new winit API - it lags like hell.

The old wait_events is missing and there is no replacement in sight, which is very bad. It maps 1:1 to the OS-native event loop, which is good for applications. Macs could use the current closure design, but just abandoning the OS-native loop is a very bad idea.

@ghost
Copy link

ghost commented Jan 22, 2018

I wonder why these issues aren't getting solved. Winit is basically useless as it is now. You can choose between using 100% CPU (poll_events) or not rendering anything at all (loop_forever). If this isn't going to be solved, please update Winit with a warning telling people not to use it.

@Boscop
Copy link

Boscop commented Jan 22, 2018

I'm using it without issues (high CPU usage) like this:

	for (now, delta_t) in FpsLoop::new(60) {
		events_loop.poll_events(|event| match event {
		...
		});
	}

My FpsLoop does the necessary sleeping.

It's an "inversion of control" but in a good way, winit gives you the control of how fast you want to loop.

@ghost
Copy link

ghost commented Jan 22, 2018

@Boscop It seems you alternate between waiting for the timer and polling events, but you can't wait for the timer and listen to events at the same time. When rendering 60 frames per second, it may not be noticeable, but when drawing only occasionally, it doesn't work.

I don't see why there can't be a function like poll_events that takes a timeout argument as proposed in this issue.

@Boscop
Copy link

Boscop commented Jan 22, 2018

@portstrom But the issue above says:

Consider a game that wants to update and render at 60 fps.

Ok, that's my use case, too.

If the game is running faster than 60 fps, it should sleep and wait until it's time to start the next frame; if it's running slower, it should busy-wait.

Yes, that's what my FpsLoop iterator does.

This use case presently requires a calling run_forever(callback) to allow the thread to sleep, plus a second thread keeping track of time and calling Proxy::wakeup() as needed. That's wasteful and unnecessarily complex.

Why does it require that? It seems to work without that for me..


@portstrom I'm not trying to be a dick. What is your use case and why can't it be done by looping at high FPS and only drawing sometimes?

@ghost
Copy link

ghost commented Jan 22, 2018

@Boscop I'm trying to make a program that draws only after events (user interaction, fs/network input, timers above 1 second). I expect to make a loop that waits until there is an event, then processes all pending events, then draws.

The first thing I try is poll_events. It handles all pending events and then yields to rendering, but I still need something to wait for the next event before I repeat the loop. The next thing to try is loop_forever. It waits for events and handles them, but it doesn't return when there are no more events. It's hard to understand how someone could have decided that there shall be one function that processes events without waiting and then returns, and one function that waits and processes events but doesn't return, but no function that waits for events, processes them and then returns. I can't draw from within the infinite loop, because that causes only one event to be processed for each frame, making the program unresponsive. There is no 'idle' event I can use to detect when it's appropriate to draw.

@fschutt
Copy link

fschutt commented Jan 22, 2018

@portstrom In case you are looking for a workaround, I ran into the same problem. I forked and backported important patches to both glium (https://github.com/fschutt/glium-backport/tree/glium_backport - use the branch "glium_backport") and vulkano (https://github.com/fschutt/vulkano-win-backport). They are the last versions to use the old API. I am currently using those because of this issue.

So if you have old code depending on the old glium API, you can depend on those. If you have issues, I'll help to backport fixes. It's not a perfect solution, but it's something until this situation is resolved. I can't accept performance problems just because of a macOS weirdness (which I don't even use).

@Boscop
Copy link

Boscop commented Jan 22, 2018

@portstrom So you want to avoid unnecessary redrawing? Why not set a flag from within your poll_events callback when events happen that require a redraw? Then check the flag after poll_events to see if you should redraw.

@willglynn
Copy link
Author

@fschutt: the worst part is, the poll_events() + run_forever() model doesn't even fix MacOS.

I describe the problem in #219, but it boils down to a fundamental design mismatch: winit is designed such that the user's code primarily controls the main thread and calls into the platform, whereas MacOS is designed such that the platform primarily controls the main thread and calls into the user's code. I developed a changset to fix this (#237) which does stack switching trickery: the user's code calls into MacOS while also letting MacOS call into the user's code. I maintained it for a while but it never got merged, and while I recently indicated I'd be happy to update it again, the maintainers have yet to express interest.

That PR would fix the problem on MacOS, which would eliminate the only technical blocker in this issue. Having said that, the fundamental design disagreement remains: some platforms want to run the event loop themselves, and winit assumes they don't. We can fix MacOS by dancing around with coroutines, but is that the only instance of this problem? Web browsers don't much like infinite loops in JS or WebAssembly…

@ghost
Copy link

ghost commented Jan 22, 2018

@Boscop No matter how the flag is set that will lead to 100% CPU usage. poll_events doesn't handle waiting.

@ghost
Copy link

ghost commented Jan 22, 2018

@willglynn Doesn't run_forever solve that problem without using stack switching if you remove poll_events? If so, I would suggest removing poll_events and adding an 'idle' event and a 'timeout' event. The 'idle' event would be emitted after all other pending events have been processed. Or for even better platform support, instead of the 'idle' event, there could be a 'paint' event and a method to explicitly request the 'paint' event to happen, which will call the requestAnimationFrame function on the web platform. On other platforms requestAnimationFrame can be mimicked as well, by not emitting the 'paint' event when the window is not visible.

@willglynn
Copy link
Author

@portstrom Sure, that would work. Removing poll_events() and having the user do everything in a callback would fix the problem. This forces everyone to turn the main thread over to winit, which could either run its own loop or defer to the platform event loop as appropriate. This also forces every single user of winit to significantly restructure their code, which is why I opened a PR for coroutines on MacOS and opened this issue to propose a smaller API change.

@tomaka
Copy link
Contributor

tomaka commented Jan 22, 2018

I agree that there is kind of a problem with the current API. This is especially noticeable on the emscripten port of winit where paint messages are being dispatched by the JS engine in order to make it work.

However I cannot think of any reasonable solution at the moment.

@willglynn
Copy link
Author

One option is to totally invert the control flow:

struct MyApp();

impl winit::App for MyApp {
  fn create_windows(&mut self, window_builder: &mut winit::WindowBuilder) {
    // use window_builder
  }
  
  fn on_event(&mut self, event: winit::Event) {
    match event {
      WindowEvent::Closed => {
        // tell winit to shut down
      }
      WindowEvent::KeyboardInput { ... } => {
        // change state and re-paint
      }
      // …
    }
  }
}

fn main() {
  winit::Main(MyApp());
}

The user's fn main() would only exist to call into winit::Main(), which might be a Rust event loop that runs forever (most platforms), a native event loop that runs forever (MacOS), or a function that sets up callbacks and returns (web). winit need only guarantee that each platform would somehow call back into the user's code after initialization.

@Diggsey
Copy link

Diggsey commented Jan 24, 2018

It's not sufficient to invert the control flow - you also need to provide a way to send custom events, otherwise there's no way to wake up the main thread, winit already supports this though I think. If you can send custom events then it will be possible to implement futures-compatible reactor. Even then, the callback based approach may not always be appropriate, as sometimes you just do need more control.

I propose that winit supports two APIs:

  1. The synchronous API. This API is based on the fundamental get_event(Timeout) operation. On macos this is implemented via stack switching.

  2. The asynchronous API. This is based on winit::Main(MyApp()) and has an adaptor to make it compatible with futures. This adaptor implements task notifications as custom events.

@willglynn
Copy link
Author

@Diggsey I'm can imagine some situations when callbacks might be inconvenient, but what is the specific use case you have in mind?

The web platform in particular makes me think that the user might need to surrender the main thread to winit, since the typical JS/WebAssembly contract requires winit to return back to the browser in order to process events. There are workarounds of varying practicality (much like how stack switching is a workaround for MacOS), but it seems to me that "the platform calls you" works on every platform without trickery, whereas "you call the platform" doesn't.

@Diggsey
Copy link

Diggsey commented Jan 24, 2018

There are any number of reasons: porting existing code which is not written in a callback style, to building libraries on top of winit to name a couple. If two libraries make the decision to control the event loop then those libraries are forever incompatible.

Inconvenience is also an important factor - I may not even care about the resizing issue on macos, as for many applications it doesn't matter if the loop blocks for that period of time, and yet to implement the basic primitive of "wait with timeout" the universal operation support by synchronous APIs, I have to:

  • Launch a second thread
  • Setup a concurrent queue between the two threads
  • Pass a reference to my WindowProxy to the other thread
  • Each time I receive a normal event on the main thread, send a message across the queue to the second thread, with a value indicating how long I am willing to wait before being woken up
  • Each time I receive a wake-up event on the main thread, send a "confirm" message across the queue
  • On the second thread, loop while performing a wait-with-timeout on the concurrent queue, using the timeout from the last message
  • Each time the second thread doesn't get a message in the time, send a wake-up message to the WindowProxy, and then wait indefinitely for a "confirm" message

Even then, there's still a race condition between the "wake up" and the next event coming in - I'm not really sure how you avoid that if you're forced (by winit and not the platform) to use multiple threads!

The stack swapping feature is a nice-to-have workaround for a relatively minor platform-specific bug: replacing winit's old API with the current one because of this bug was a terrible mistake.

@willglynn
Copy link
Author

If two libraries make the decision to control the event loop then those libraries are forever incompatible.

winit already controls the event loop. It provides no wait handles with which to link to other event loops, which means you can either a) let winit block inside the windowing system or b) block yourself somewhere outside the windowing system. If your goal is to service events promptly, you must use run_forever().

I opened this issue to propose poll-with-timeout as a more useful common denominator: this would let you block inside the windowing system and regain control again after a fixed period. This doesn't change the fact that winit is still blocking in its own event loop; there's no way to tell winit to e.g. wait on a windowing event and wait on a socket becoming readable.

IMO, the main issue here is control over the main thread. Right now, users call winit and winit returns back to the user. This isn't how MacOS works; you're not supposed to constantly bail out of the platform event loop, and there are situations (like resizing) where the platform's event loop takes over anyway. It's also not how the web is designed to work, and it's much more difficult to paper over that.

Poll-with-timeout is a more useful common denominator, but it's not the lowest common denominator. The common interface that all platforms can share is to have the user surrender their thread to winit, and have winit call back into the user's code.

Inverting control flow would absolutely be a giant breaking change, but that doesn't avoid the question: which is the right model? Should the user call winit, or should winit call the user? Does the main thread belong to the user, or does it belong to the platform? @tomaka?

If the user should call winit, then we should merge coroutines support for MacOS, since MacOS's design is at odds with winit's design. We should also give up on having winit's web platform work the same as the other platforms, since… it can't.

If winit should call the user, then we should figure out what that API should look like, and we should figure out how to get there from here. It's worth noting that I used coroutines to merge two incompatible event loop models in #237; we could do the same thing to provide a user-calls-winit compatibility layer on top of a new winit-calls-user API.

@tomaka
Copy link
Contributor

tomaka commented Jan 25, 2018

In the past I was against inverting flow control for "ideological" reasons. After all in a pre-emptive multitasking model, from the point of view of the application everything happens linearly. My point of view is that the API provided by the OS was wrong and existed for legacy reasons, and that we should abstract over it. Newer models such as wayland seem to comply to this model after all.

However on the other hand I'm generally in favour of exposing the lowest-level possible API on top of the OS, in order to not add any overhead.

In addition to this, the pre-emptive multitasking model of execution isn't compatible with the cooperative model of Emscripten, Android and iOS.

Maybe one possibility would be to use the new upcoming coroutines feature of Rust and ask the user to yield whenever they want to query for events.

@ghost
Copy link

ghost commented Feb 1, 2018

I finally found a way to make a meaningful program with Winit. It only works on X11. An answer on Stack Overflow suggested using select on the X connection to wait for events with a timeout.

I found out I can get the connection number of a window like this:

let connection_number = {
    let connection = window.get_xlib_xconnection().unwrap();
    let display = window.get_xlib_display().unwrap();
    unsafe {
        (connection.xlib.XConnectionNumber)(display as _)
    }
};

Then I can use it to set up Mio:

let mut events = mio::Events::with_capacity(1);
let poll = mio::Poll::new().unwrap();
poll.register(
    &mio::unix::EventedFd(&connection_number),
    mio::Token(0), mio::Ready::readable(),
    mio::PollOpt::edge()
).unwrap();

When done calling poll_events on the event loop and painting, and calling poll_events again after painting, I use Mio to wait until more events are available or it's time to paint the next frame:

let current_time = chrono::offset::Local::now();
let _ = poll.poll(&mut events, Some(::std::time::Duration::new(0, 1000000000 - current_time.timestamp_subsec_nanos())));

I hope the event loop in Winit will be fixed soon, so I don't have to do this manually in a way that uses unsafe code and only works with X11.

@Ralith
Copy link
Contributor

Ralith commented Feb 2, 2018

Unfortunately, using the X connection fd like this does not work reliably, because OpenGL/vulkan implementations will do IO on that fd internally, causing unrelated events to be read early and read-readiness notifications to be missed.

@hadrianw
Copy link

@Ralith Could you expand on it? I was just searching for more information on polling XConnectionNumber and found this issue.

Could you give an example when it happens? What can one do then? Just to do a busy loop? Or send to yourself a fake event to wake it? But then it just needs to be on separate thread.

jturner314 added a commit to jturner314/gfx that referenced this issue Feb 17, 2018
This commit uses `.poll_events()` to allow processing multiple events
per rendered frame. (Before, only one event was processed per frame.)
This is necessary because it's easy for the user to generate events much
faster than frames can be rendered (e.g. by moving the mouse).

Additionally, with `.run_forever()`, rendering blocked on receiving
events. (An event had to occur before a frame was rendered.) In order to
see an animation such as the `particle` example, the user had to
generate events fast enough to cause frames to be rendered (e.g. by
moving the mouse over the window).

See also rust-windowing/winit#276 and
rust-windowing/winit#231
jturner314 added a commit to jturner314/gfx that referenced this issue Feb 17, 2018
This commit uses `.poll_events()` to allow processing multiple events
per rendered frame. (Before, only one event was processed per frame.)
This is necessary because it's easy for the user to generate events much
faster than frames can be rendered (e.g. by moving the mouse).

Additionally, with `.run_forever()`, rendering blocked on receiving
events. (An event had to occur before a frame was rendered.) In order to
see an animation such as the `particle` example, the user had to
generate events fast enough to cause frames to be rendered (e.g. by
moving the mouse over the window).

See also rust-windowing/winit#276 and
rust-windowing/winit#231
bors bot added a commit to gfx-rs/gfx that referenced this issue Feb 17, 2018
1822: Switch from .run_forever() to .poll_events() in gfx_app r=kvark a=jturner314

This commit uses `.poll_events()` to allow processing multiple events per rendered frame. (Before, only one event was processed per frame.) This is necessary because it's easy for the user to generate events much faster than frames can be rendered (e.g. by moving the mouse).

Additionally, with `.run_forever()`, rendering blocked on receiving events. (An event had to occur before a frame was rendered.) In order to see an animation such as the `particle` example, the user had to generate events fast enough to cause frames to be rendered (e.g. by moving the mouse over the window).

See also rust-windowing/winit#276 and rust-windowing/winit#231

PR checklist:
- [ ] `make` succeeds (on *nix)
  I don't see a Makefile. `make` says `make: *** No targets specified and no makefile found.  Stop.`
- [ ] `make reftests` succeeds
  I don't see a Makefile. I did run `cargo test`, though, and that succeeded (although it ran only 3 tests).
- [X] tested examples with the following backends:
  When I run `glxinfo`, I see `OpenGL core profile version string: 3.3 (Core Profile) Mesa 17.3.3`. I tested all of the examples. They all worked correctly, except `terrain_tessellated` (which failed to build even before this commit) and `triangle` (which still has the `.run_forever()` issue since it doesn't use `gfx_app`).
@francesca64
Copy link
Member

francesca64 commented Apr 18, 2018

So, if I'm understanding things correctly, combining what's been outlined here with the problems we have with Windows, then doesn't this mean that our current design is at odds with most platforms?

@francesca64 francesca64 added S - api Design and usability C - waiting on author Waiting for a response or another PR D - hard Likely harder than most tasks here P - high Vital to have labels May 6, 2018
@francesca64 francesca64 added this to the EventsLoop 2.0 milestone May 6, 2018
@ghost
Copy link

ghost commented Jan 31, 2019

This issue is a total showstopper. It's amazing that the maintainers don't seem to care. I'm still using the workaround I posted above, which only works on X11. I never had the problem that @Ralith mentioned. I've now published an example program made with winit, which unfortunately only works on X11.

@mitchmindtree
Copy link
Contributor

It's amazing that the maintainers don't seem to care.

They care very much so - I believe most of the points in this issue are being addressed in #638 in @Osspial's heroic effort to rewrite the event loop, though I'm not familiar enough with the PR to know for sure if everything in this issue has been addressed. That PR is just about ready to go - feel free to have a look, test it and share any constructive input you might have. The maintainers are a group of collaborators working on this in their limited, free time and for many, winit is just one of many open source crates to which they contribute, each with their own slew of "showstopper" issues to address.

@Osspial
Copy link
Contributor

Osspial commented Feb 4, 2019

Honestly, I understand being frustrated about the lack of visible progress - there are several issues that can't be fixed without updating the main event loop API, and the changes have been in the pipeline for... uh... oh jeez has it really been over six months? Those changes should land Very Soon, though.

Anyhow, I'm going to close this issue in favor of #459 since that's where the discussion on the event loop API ended up getting finished.

@Osspial Osspial closed this as completed Feb 4, 2019
madsmtm added a commit to madsmtm/winit that referenced this issue Jun 11, 2022
* refactor(windows): `begin_resize_drag` now similar to gtk's (rust-windowing#200)

* refactor(windows): `begin_resize_drag` now similart to gtk's

* fix

* feat(linux): skipping taskbar will now also skip pager (rust-windowing#198)

* refactor(linux): clean dummy device_id (rust-windowing#195)

* refactor(linux): clean dummy device_id

* fmt

* feat(linux): allow resizing undecorated window using touch (rust-windowing#199)

* refactor(windows): only skip taskbar if needed when `set_visible` is called (rust-windowing#196)

* fix: increase borderless resizing inset (rust-windowing#202)

* fix: increase borderless resizing inset

* update some comments

* Replace winapi with windows crate bindings shared with WRY (rust-windowing#206)

* fix(deps): update rust crate libayatana-appindicator to 0.1.6 (rust-windowing#190)

Co-authored-by: Renovate Bot <bot@renovateapp.com>

* Add Windows crate and webview2-com-sys bindings

* Initial port to webview2-com-sys

* Finish conversion and remove winapi

* Fix renamed lint warning

* Fix all match arms referencing const variables

* Put back the assert instead of expect

* Point to the published version of webview2-com-sys

* Cleanup slightly weird BOOL handling

* Replace mem::zeroed with Default::default

* Add a summary in .changes

* Remove extra projects not in config.json

* Fix clippy warnings

* Update to 32-bit compatible webview2-com-sys

* Better fix for merge conflict

* Fix clippy errors on Windows

* Use path prefix to prevent variable shadowing

* Fix Windows clippy warnings with nightly toolchain

* Fix Linux nightly/stable clippy warnings

* Fix macOS nightly/stable clippy warnings

* Put back public *mut libc::c_void for consistency

* Re-run cargo fmt

* Move call_default_window_proc to util mod

* Remove unnecessary util::to_wstring calls

* Don't repeat LRESULT expression in match arms

* Replace bitwise operations with util functions

* Cleanup more bit mask & shift with util fns

* Prefer from conversions instead of as cast

* Implement get_xbutton_wparam

* Use *mut libc::c_void for return types

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Renovate Bot <bot@renovateapp.com>

* fix(keyboard): add mapping for space key on Windows (rust-windowing#209)

* fix(keyboard): add mapping for space key on Windows

* change file

* feat: impl Clone for EventLoopWindowTarget (rust-windowing#211)

* chore: add `on_issue_closed.yml` (rust-windowing#214)

* Update tray dependency version (rust-windowing#217)

* Delete on_issue_closed.yml (rust-windowing#221)

* refactor(linux): event loop (rust-windowing#233)

* Use crossbeam::channel

* Fix crossbeam channel import

* Add check on poll event

* Fix deadlock when unregistering shortcut on Linux (rust-windowing#230)

* Add fullscreen monitor selection support on Linux (rust-windowing#235)

* Add fullscreen monitor support on Linux

* Add change file

* Remove todo on videomode

* Fix clippy

* Update to 2021 edition (rust-windowing#236)

* Update to 2021 edition

* Fix clippy

* Add run_return on Linux (rust-windowing#237)

* Add run_return on Linux

* Add main context

* Add run_return trait on Linux (rust-windowing#238)

* Fix: rust-windowing#239 Update webview2-com and windows crates (rust-windowing#240)

* Replace webivew2-com-sys with prebuilt windows

* Use windows utility instead of direct GetLastError

* Bump windows version and add changelog

* Run cargo fmt

* Restore inverted matches macro

* Scope constants in match arms

* Fix inverted null check

* Update src/platform_impl/windows/util.rs

Co-authored-by: Amr Bashir <48618675+amrbashir@users.noreply.github.com>

* Use env_logger instead of simple_logger (rust-windowing#241)

* Use env_logger instead of simple_logger

* Make clippy happy

* Cherry pick commits to next (rust-windowing#244)

* feat(macos): Add `unhide_application` method, closes rust-windowing#182 (rust-windowing#231)

* feat(macos): Add `unhide_application` method

* Update src/platform/macos.rs

Co-authored-by: Amr Bashir <48618675+amrbashir@users.noreply.github.com>

* Reanme to `show_application()`

* Remove broken doc link

Co-authored-by: Amr Bashir <48618675+amrbashir@users.noreply.github.com>

* feat: Allow more strings to parse to keycode (rust-windowing#229)

* feat: support accelerator key strings `,` `-` `.` `Space` `Tab` and `F13`-`F24` (rust-windowing#228)

* feat(macOS): support more accelerator key strings

* Move function keys together

* Add `,` `-` `.` `Space` `F20-F24` for Windows

* Remove support for accelerators not found in `winapi`

* Add `,` `-` `.` `Space` `F13-F24` for Linux

* Update .changes

* Add the rest for Windows

* Add the rest for Linux

* Add the rest on macOS

* Update accelerator-strings.md

* Fix git comments

Co-authored-by: Kasper <kasperkh.kh@gmail.com>
Co-authored-by: Amr Bashir <48618675+amrbashir@users.noreply.github.com>

* Add redraw events on Linux (rust-windowing#245)

* Add redraw events on Linux

* Update doc of RequestRedraw Event

* Add change file

* Fix missing menu bar on borderless window (rust-windowing#247)

Credit goes to irh's work on winit commit f2de847

* refactor: improve `set_skip_taskbar` impl on Windows (rust-windowing#250)

* fix: emit errors on parsing an invalid accelerator for string, closes rust-windowing#135 (rust-windowing#252)

* chore: update comment

* fix(linux): fix focus events not firing properly (rust-windowing#253)

* fix(linux): fix focus events not firing properly

* add changelog

* chore: update focus events error message

* chore: fmt

* fix: revert windows-rs 0.28 version bump

* fix(linux): fix native menu items (rust-windowing#256)

* chore: remove examples commited by accident

* Update `ReceivedImeText` (rust-windowing#251)

* Allow receiving text without Ime on Windows

* Avoid panic todo

* Receive text without ime on mac

* Fix CursorMoved event on Linux

* Add ReceivedImeText on Linux

This only add Simple IME from GTK for now. We should add the actual IME
from system in the future.

* Fix redraw event that causes inifinite loop (rust-windowing#260)

* Fix redraw event that causes inifinite loop

* Refactor event loop

* Remove unused function

* Add doc comment on linux's run_return

* Ignore doc test on run_return

* Add non blocking iteration on Linux (rust-windowing#261)

* Docs: SystemTrayExtWindows::remove() is gone (rust-windowing#262)

Fix docs following rust-windowing#153

* Fix busy loop on Linux (rust-windowing#265)

* Update windows crate to 0.29.0 (rust-windowing#266)

* Update to windows 0.29.0

* Add change description

* Remove clippy check (rust-windowing#267)

* refactor(windows): align util function with win32 names

* chore: update PR template

* fix(linux): fire resized & moved events on min/maximize, closes rust-windowing#219 (rust-windowing#254)

* feat(linux): implement `raw_window_handle()` (rust-windowing#269)

* chore(deps): update to raw-window-handle 0.4

* add linux raw-window-handle support

* update macos/ios/android

* fix ios

* Fix core-video-sys dependency (rust-windowing#274)

* The `cocoa` crate links to AppKit, which made the symbol `CGDisplayCreateUUIDFromDisplayID` from ApplicationServices/ColorSync (which AppKit uses internally) available to us on macOS 10.8 to 10.13. (rust-windowing#275)

However, this does not work on macOS 10.7 (where AppKit does not link to ColorSync internally). Instead of relying on this, we should just link to ApplicationServices directly.

* Fix some invalid msg_send! usage (rust-windowing#276)

* Revert "Fix some invalid msg_send! usage (rust-windowing#276)" (rust-windowing#277)

This reverts commit a3a2e0cfc49ddfa8cdf65cf9870fb8e3d45b4bc0.

* Revert "The `cocoa` crate links to AppKit, which made the symbol `CGDisplayCreateUUIDFromDisplayID` from ApplicationServices/ColorSync (which AppKit uses internally) available to us on macOS 10.8 to 10.13. (rust-windowing#275)" (rust-windowing#279)

This reverts commit 6f9c468f26ddb60e29be2139397bfaf3b30eab1e.

* The `cocoa` crate links to AppKit, which made the symbol `CGDisplayCreateUUIDFromDisplayID` from ApplicationServices/ColorSync (which AppKit uses internally) available to us on macOS 10.8 to 10.13. (rust-windowing#280)

However, this does not work on macOS 10.7 (where AppKit does not link to ColorSync internally). Instead of relying on this, we should just link to ApplicationServices directly.

Co-authored-by: madsmtm <mads@marquart.dk>

* Fix some invalid msg_send! usage (rust-windowing#278)

Co-authored-by: madsmtm <mads@marquart.dk>

* Add exit code to ControlFlow::Exit (rust-windowing#281)

* Add exit code to ControlFlow::Exit

* Cargo fmt

* Add change files

Co-authored-by:  multisn8 <contact@multisamplednight.com>

* Add new_any_thread to Unix event loop (rust-windowing#282)

* Update windows crate to 0.30.0 (rust-windowing#283)

* Update windows crate to 0.30.0

* Simplify new-type usage

* Fix boxing in GWL_USERDATA

* Make sure everyone is using Get/SetWindowLongPtrW

* build the system_tray module when "ayatana" feature is enabled (rust-windowing#285)

Without those cfg feature checks, the "ayatana" feature does
actually not enable anything.

* Fix click events missing whe tray has menu (rust-windowing#291)

* Fix click events missing whe tray has menu

* Add change file

* Fix crash when tray has no menu (rust-windowing#294)

* chore: update pull request commit exmple

* fix(windows): send correct position for system tray events, closes rust-windowing#295 (rust-windowing#300)

* fix(windows): revert maximized state handling to winit impl, closes rust-windowing#193 (rust-windowing#299)

* fix(windows): revet maximized state handling to winit impl, closes rust-windowing#193

* add chanefile [skip ci]

* fix: `MenuItem::Quit` on Windows (rust-windowing#303)

* fix: `MenuItem::Close` on Windows

* use `PostQuitMessage` instead

Co-authored-by: amrbashir <amr.bashir2015@gmail.com>

* feat: v1 audit by Radically Open Security (rust-windowing#304)

* Update to gtk 0.15 (rust-windowing#288)

* Update to gtk 0.15

* Fix picky none on set_geometry_hint

* Fix CursorMoved position

Co-authored-by: Amr Bashir <48618675+amrbashir@users.noreply.github.com>
Co-authored-by: Bill Avery <wravery@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Renovate Bot <bot@renovateapp.com>
Co-authored-by: Lucas Fernandes Nogueira <lucasfernandesnog@gmail.com>
Co-authored-by: Kasper <kasperkh.kh@gmail.com>
Co-authored-by: amrbashir <amr.bashir2015@gmail.com>
Co-authored-by: Jay Oster <jay@kodewerx.org>
Co-authored-by: madsmtm <mads@marquart.dk>
Co-authored-by: multisn8 <contact@multisamplednight.com>
Co-authored-by: Aurélien Jacobs <aurel@gnuage.org>
Co-authored-by: Lucas Fernandes Nogueira <lucas@tauri.studio>
madsmtm pushed a commit to madsmtm/winit that referenced this issue Jun 11, 2022
* feat(macos): Add `unhide_application` method, closes rust-windowing#182 (rust-windowing#231)

* feat(macos): Add `unhide_application` method

* Update src/platform/macos.rs

Co-authored-by: Amr Bashir <48618675+amrbashir@users.noreply.github.com>

* Reanme to `show_application()`

* Remove broken doc link

Co-authored-by: Amr Bashir <48618675+amrbashir@users.noreply.github.com>

* feat: Allow more strings to parse to keycode (rust-windowing#229)

* feat: support accelerator key strings `,` `-` `.` `Space` `Tab` and `F13`-`F24` (rust-windowing#228)

* feat(macOS): support more accelerator key strings

* Move function keys together

* Add `,` `-` `.` `Space` `F20-F24` for Windows

* Remove support for accelerators not found in `winapi`

* Add `,` `-` `.` `Space` `F13-F24` for Linux

* Update .changes

* Add the rest for Windows

* Add the rest for Linux

* Add the rest on macOS

* Update accelerator-strings.md

* publish new versions

Co-authored-by: Kasper <kasperkh.kh@gmail.com>
Co-authored-by: Amr Bashir <48618675+amrbashir@users.noreply.github.com>
Co-authored-by: wusyong <wusyong@users.noreply.github.com>
Co-authored-by: Ngo Iok Ui (Wu Yu Wei) <wusyong9104@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C - waiting on author Waiting for a response or another PR D - hard Likely harder than most tasks here P - high Vital to have S - api Design and usability
Development

No branches or pull requests

10 participants