winit icon indicating copy to clipboard operation
winit copied to clipboard

Handle error cases in `MonitorHandle`

Open madsmtm opened this issue 2 years ago • 13 comments

Part of https://github.com/rust-windowing/winit/issues/971.

Makes all MonitorHandle methods fallible. The same is not done for VideoMode, since they are just static blobs of data (rather than something queried dynamically) (but that might change in the future?).

  • [ ] Tested on all platforms changed
    • [ ] Android
    • [x] iOS
    • [x] macOS
    • [ ] Wayland
    • [ ] Web
    • [ ] Windows
    • [ ] X11
    • [ ] Orbital
  • [x] Added an entry to CHANGELOG.md if knowledge of this change could be valuable to users
  • [x] Updated documentation to reflect any user-facing changes, including notes of platform-specific behavior
  • [x] Created or updated an example program if it would help users understand this functionality
  • [x] Updated feature matrix, if new features were added or implemented

Unresolved

I'm unsure what we should do in the case where some functionality isn't implemented yet? Is the correct thing to return a dummy value? Perhaps it would actually be better if we just panic with an unimplemented!()?

Though in any case I think this PR is an improvement over the status quo.

madsmtm avatar Jan 23 '23 13:01 madsmtm

I'm unsure what we should do in the case where some functionality isn't implemented yet? Is the correct thing to return a dummy value? Perhaps it would actually be better if we just panic with an unimplemented!()?

That's not an option. Would suggest to return a Result instead.

kchibisov avatar Jan 27 '23 05:01 kchibisov

fn is_alive(&self) -> bool;
fn with_description<T, F: Fn(MonitorDescription) -> T>(&self) -> Result<T>;

There is no callback you could write in which MonitorDescription will always be available, since the user could remove their monitor at any time - you must do a copy of the underlying data, in which case -> Result<MonitorDescription> would work just fine.

If we do that though, then I think I'd rather just rename MonitorHandle to MonitorDescription altogether.

I'm unsure what we should do in the case where some functionality isn't implemented yet? Is the correct thing to return a dummy value? Perhaps it would actually be better if we just panic with an unimplemented!()?

That's not an option. Would suggest to return a Result instead.

I actually think the unimplemented!() strategy would be a fairly good option:

  • If someone doesn't use the functionality, or don't support the platform it's being used on, there's no problem
  • If someone use it on a platform where it's not supported, they must manually cfg-gate the functionality
  • Which in turn would drive people to actually implement the functionality!

Vs. returning a Result<T, (Unimplemented | MonitorGone)>:

  • People will likely just .unwrap_or(...), which will coalesce the two very different errors together
  • Once the feature has been implemented, we now have an unused error case (and an implementation that wasn't there before could even somewhat be a breaking change).

Of course, for the things that are fundamentally not possible to support, then we should do change the API.


In any case, we should consider making video modes match monitors: So either we have MonitorHandle and VideoModeHandle, or MonitorDescription and VideoMode.

madsmtm avatar Jan 29 '23 22:01 madsmtm

MonitorGone where? MonitorMissing better. (Excuse the missing prepositions.)

Unfortunately this clashes with #2658.

So either we have MonitorHandle and VideoModeHandle, or MonitorDescription and VideoMode.

This sounds like unnecessary complexity for the sake of consistency. That said, returning a MonitorDescription would simplify the current API. I guess the cost is unimportant (extra platform requests for details the app may not want anyway)?

The one part of MonitorHandle that confuses me: it appears to be both an identifier and a descriptor? In any case, the only real purpose of Winit returning monitor details, as far as I can tell, is to support window.set_fullscreen(..).

dhardy avatar Jan 30 '23 11:01 dhardy

I did a small error in the API. We need a ref to MonitorDescription and ability to clone it, that way you access the monitor data with-in the callback, and if it doesn't exist anymore, you'll get a Result when trying to get the MonitorDescription

fn is_alive(&self) -> bool;
fn with_description<T, F: Fn(&MonitorDescription) -> T>(&self) -> Result<T>;

If we do that though, then I think I'd rather just rename MonitorHandle to MonitorDescription altogether.

No, the point is that you have a handle which might be long dead (is_alive check), no need to merge them, that will complicate the API.

I actually think the unimplemented!() strategy would be a fairly good option: If someone doesn't use the functionality, or don't support the platform it's being used on, there's no problem If someone use it on a platform where it's not supported, they must manually cfg-gate the functionality Which in turn would drive people to actually implement the functionality!

No, that will drive people to whine and complain, not to implement. Maybe that's my experience, but for example, over the years in alacritty no-one bothers to fix bugs on macOS even though they are relatively simple, the same will be about other bugs. Having potential crashes like that isn't a good idea for a library that is around for a long time.

kchibisov avatar Jan 30 '23 11:01 kchibisov

How to deal with the situation that the handle value is reused, which will cause consistency problems, how about using std::os::windows::io::BorrowedHandle::try_clone_to_owned?

xiaopengli89 avatar Aug 18 '23 06:08 xiaopengli89

We talked a bit about this in Friday's meeting, the API should stay mostly the same, but instead each backend should cache the required data about the monitor, such that the data can always be retrieved.

I don't think I communicated this properly then, but I think it'd be fine for the backend to keep a reference to the monitor data alive, if that's enough that it can be fetched.

Internally, it would be something like:

// Backend 1
struct MonitorHandle {
    // Not retrievable from the handle if the monitor is disconnected, so cached here
    cached_data: {
        name: String,
        refresh_rate: u32,
        ...
    },
    // Used when entering fullscreen on the monitor
    handle: *mut MyHandle,
}

// Backend 2
struct MonitorHandle {
    // Used when entering fullscreen on the monitor, and to retrieve data about the monitor
    handle: Arc<MyHandle>,
}

madsmtm avatar Mar 17 '24 17:03 madsmtm

I don't think I communicated this properly then, but I think it'd be fine for the backend to keep a reference to the monitor data alive, if that's enough that it can be fetched.

I think we should keep it consistent and not do fetching on some backends and on others we cache. Because we will have to add a big note saying "this data is cached and reflects the state when it was retrieved". Adding backend specific exceptions makes this even more unintuitive.

I would still be fine to leaving things as proposed here, with returning a Result, as I don't really see a problem with that, but just caching the data seems to be the only compromise we managed to reach.

daxpedda avatar Mar 17 '24 17:03 daxpedda

In the current state of thins it shouldn't cache, since there's no way to propagate that the cache got invalidated or the data got updated.

kchibisov avatar Mar 17 '24 17:03 kchibisov

In the current state of thins it shouldn't cache, since there's no way to propagate that the cache got invalidated or the data got updated.

What changed your mind? Because that is what we agreed on the last time.

daxpedda avatar Mar 17 '24 17:03 daxpedda

All backends right now don't cache and kind of live update or query the data. I guess I misunderstood it, since I thought that we shouldn't cache in the future.

Right now to remove reload/cache of data in handles it'll require to likely rewrite all of them all together, so this PR will be way bigger than report errors.

kchibisov avatar Mar 17 '24 18:03 kchibisov

Or what do you mean by caching in particular, like remember last asked value or use only POD and update when the user queries the monitor?

kchibisov avatar Mar 17 '24 18:03 kchibisov

Or what do you mean by caching in particular, like remember last asked value or use only POD and update when the user queries the monitor?

The idea was to use POD only, no updating. If you want updated values you have to query the monitor from the event loop again. It might be sensible to add an refresh() method though.

daxpedda avatar Mar 18 '24 08:03 daxpedda

The idea was to use POD only, no updating. If you want updated values you have to query the monitor from the event loop again. It might be sensible to add an refresh() method though.

Yeah, it's just will have to rewrite all the monitor handles code, since everything is doing the opposite. I'm not against that though, just way more work for this patch and it does completely different compared to this patch.

kchibisov avatar Mar 18 '24 10:03 kchibisov