-
Notifications
You must be signed in to change notification settings - Fork 909
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
Feedback on migration to trait-based design #3626
Comments
I used a mut closure as the users main loop for my game engine, but with this new setup, I am struggling to figure out how to set it up properly without also killing performance, any pointers? I only updated due to the issues with exclusive full screen on 0.29 |
Please look into CHANGELOG and migration guide in it. Closure is just a |
Thanks, I managed to figure it out Call with (self.mut_closure)(args) in EDIT: Thanks for your work on this project I do appreciate the updates and fixes |
I am also finding I will need to restructure very significant parts of the engine if I am to use the new way of initialising windows. Currently, I use an app struct which contains the main engine state struct, however to initialise wgpu, the window is needed, however you can't get the window until during the event loop so wgpu can't be initialised until then, which means the user can't directly get access to the app struct, and can only get a mutable reference to it. I will also need to write and implement ApplicationHandler individually for each rendering backend. |
But we just swapped |
This is true, I would also rather not use deprecated code. I managed to get it working, very messy but I can fix it later. I am still finding that Exclusive full screen still doesn't work on Hyprland, although I could just be doing it wrong, and besides that is out of scope for this thread I believe. |
Docs clearly say that exclusive fullscreen doens't exist on Wayland. |
It seems I didn't understand the difference between borderless and Exclusive. The borderless mode seems to allow window sizes and resolutions different from the display's resolution, not what I expected from a borderless mode but I guess it is possible. |
exclusive means that you get exclusive control. As in you can also modeset, as in change monitor resolution. Borderless just ordinary fullscreen one. |
I do think this is a much cleaner interface, but I really struggled getting something up and running with struct App {
window: Option<Arc<Window>>,
gfx_state: Option<GfxState>,
}
impl ApplicationHandler<MyUserEvent> for State {
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
let win_attrs = Window::default_attributes()
.with_title(/* ... */)
.with_inner_size(/* ... */);
let window = Arc::new(event_loop.create_window(win_attrs).unwrap());
let gfx_state = GfxState::new(Arc::clone(&window));
window.request_redraw()
self.window = Some(window);
self.gfx_state = Some(gfx_state);
}
fn window_event(&mut self, event_loop: &ActiveEventLoop, window_id: WindowId, event: WindowEvent) {
// ...
self.gfx_state.as_mut().unwrap().paint() // paint is
}
}
struct GfxState {
device: wgpu::Device,
surface_desc: wgpu::SurfaceConfiguration,
surface: wgpu::Surface<'static>, // important part: 'static
// ... all the other queues / buffers / etc needed
}
impl GfxState {
fn new(window: Arc<Window>, app_state: &mut AppState) -> Self {
let instance = wgpu::Instance::default();
let size = window.inner_size();
// important part: we can create a `wgpu::Surface<'static>` from an `Arc`
let surface = instance.create_surface(window).unwrap();
// ... configure pipelines
Self { surface, /* ... */ }
}
fn paint(&mut self, state: &mut AppState) {
// perform the rendering
}
} This could be slightly simplified by putting |
Based on @tgross35 comment, another issue comes up, which is async. Working with wgpu, we have to deal with some let instance = wgpu::Instance::default();
let surface = instance.create_surface(window).expect("Failed to create surface");
// need adapter to create the device and queue
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::default(),
force_fallback_adapter: false,
compatible_surface: Some(&surface),
})
.await
.unwrap();
let (device, queue) = adapter
.request_device(
&wgpu::DeviceDescriptor {
label: None,
required_features: wgpu::Features::empty(), //The device you have limits the features you can use
required_limits: wgpu::Limits::default(), //The limits field describes the limit of certain types of resource we can create
},
None,
)
.await
.unwrap(); Unless the trait methods are async, we must use a |
This comment was marked as duplicate.
This comment was marked as duplicate.
On Web targets, async with wgpu is extra problematic as |
* chore: bump render deps versions * refactor: replace EventLoopWindowTarget by ActiveEventLoop * refactor: remove wasm32 target code * refactor: update window init using deprecated method I will further update it later, this is just enough for the crate to compile * refactor: add default compilation option to the state creation * feat: add new basic structures based on rust-windowing/winit#3626 (comment) * fix: revert an IDE-induced rename * fix: revert idem * feat: complete GfxState constructor * refactor: make GfxState::new synchronous using pollster * feat: impl ApplicationHandler for App * feat: add necessary update methods to gfx state * fix: add handle vertex building code * chore: cleanup imports & re-exports * refactor: move shader_data & camera modules * fix: update render examples * chore: add cusotm lints to honeycomb-render * fix: process some lints * fix: add back the possibility to run default examples
In Why? Because with a loop, the borrow checker can infer a lot more. For instance, we can safely keep user interfaces (which borrow from application state) alive until a specific event happens. This is not possible with a trait-based approach (not even the closure approach) because it would entail self-references. I shared some more details about this approach in this PR: iced-rs/iced#597. Therefore, the trait-based approach is just a bit of additional unnecessary complexity for Is there any particular reason the closure approach is being deprecated? It seems quite strange, since the new API is actually relying on the deprecated one internally. Why not keep the original alive and offer the new one as a convenient alternative? |
We need return values and sync callbacks. Some APIs mandate return to do e.g. hittest, sync resize, etc. If you do that async you basically blow up and not sync with the windowing systym resulting in visual artifacts, etc. callbacks don't scale at all as well and you can not build extensible systems like you can with extension traits. The deprecated APIs only here to give some leeway, they'll be gone this week or so from master. Also, the only difference is that there's no |
I see. Return values do seem quite handy. If the deprecated APIs are going away, I'll switch to the trait-based approach.
|
There will be The traits are also needed to split winit, which you can not do with closures really. |
https://sotrh.github.io/learn-wgpu/beginner/tutorial2-surface/ - works with current WGPU v0.20.0 - add more comments and url references for required code changes rust-windowing/winit#3626 (comment) rust-windowing/winit#3626 (comment) https://users.rust-lang.org/t/in-wgpu-how-do-i-reset-a-buffer-after-making-it-device-create-buffer-init/106391/13 - initialize app with a customizable log level
https://sotrh.github.io/learn-wgpu/beginner/tutorial2-surface/ - works with current WGPU v0.20.0 https://docs.rs/winit/latest/winit/#event-handling - add more comments and url references for required code changes rust-windowing/winit#3626 (comment) rust-windowing/winit#3626 (comment) https://users.rust-lang.org/t/in-wgpu-how-do-i-reset-a-buffer-after-making-it-device-create-buffer-init/106391/13 - initialize app with a customizable log level
https://sotrh.github.io/learn-wgpu/beginner/tutorial2-surface/ - works with current WGPU v0.20.0 and winit v0.30.0 https://docs.rs/winit/latest/winit/#event-handling - add more comments and url references for required code changes rust-windowing/winit#3626 (comment) rust-windowing/winit#3626 (comment) https://users.rust-lang.org/t/in-wgpu-how-do-i-reset-a-buffer-after-making-it-device-create-buffer-init/106391/13 - initialize app with a customizable log level
https://sotrh.github.io/learn-wgpu/beginner/tutorial2-surface/ - works with current WGPU v0.20.0 and winit v0.30.0 https://docs.rs/winit/latest/winit/#event-handling - add more comments and url references for required code changes rust-windowing/winit#3626 (comment) rust-windowing/winit#3626 (comment) https://users.rust-lang.org/t/in-wgpu-how-do-i-reset-a-buffer-after-making-it-device-create-buffer-init/106391/13 - initialize app with a customizable log level
If anyone is interested in my pattern of integrating 0.30.0 into a cross-compiled wgpu app, check out https://github.com/thinnerthinker/sursface/blob/main/examples/src/hello_triangle/main.rs |
Should #[inline]
#[cfg(not(all(web_platform, target_feature = "exception-handling")))]
pub fn run_app<A: ApplicationHandler<T>>(self, app: &mut A) -> Result<(), EventLoopError> {
self.event_loop.run(|event, event_loop| dispatch_event_for_app(app, event_loop, event))
} |
Yes, and it was done in #3721 |
One slightly annoying thing with the new trait is it makes error handling a bit harder. Using the old interface, I had a single One way to make this more ergonomic would be something like this:
If a trait method returns an error, then This would allow all user functions to return an error, and allow the user to implement custom error handling in a single place, namely the Edit: I think this is mostly a dupe of #3692. |
Thank you for your interest in expressing your feedback on Winit
v0.30
!Background
Winit is moving towards a trait-based API for several reasons, see #3432 for details. This has quite a large impact on how you structure your code, and while the design is not yet optimal, and will need additional work, we feel confident that this is the right way forwards, so in
v0.30
, we've tried to provide a somewhat gentle update path.Feedback
We're aware that the API is more verbose, that is to be expected when implementing an entire trait instead of letting the compiler figure out the type annotations of a closure, but we're interested in knowing what kinds of code is made harder or impossible with this new design? Were you using some pattern before that you can no longer do, or no longer easily do? Please state so in a comment on this issue!
In this release, we also changed how windows are created, but that is not strictly part of this issue - please submit a new issue if you're having trouble with that. Note that we're well aware that
Option<Window>
is a bit cumbersome, in the future, managing the state for windows and creating/destroying them at the right times will likely become easier to do, see #2903 for some of the progress on that.The text was updated successfully, but these errors were encountered: