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

Handle error cases in MonitorHandle #2646

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

madsmtm
Copy link
Member

@madsmtm madsmtm commented Jan 23, 2023

Part of #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
    • iOS
    • macOS
    • Wayland
    • Web
    • Windows
    • X11
    • Orbital
  • Added an entry to CHANGELOG.md if knowledge of this change could be valuable to users
  • Updated documentation to reflect any user-facing changes, including notes of platform-specific behavior
  • Created or updated an example program if it would help users understand this functionality
  • 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 madsmtm added S - api Design and usability I - BREAKING CHANGE labels Jan 23, 2023
@madsmtm madsmtm requested a review from maroider January 23, 2023 14:00
@madsmtm madsmtm force-pushed the monitor-gone branch 6 times, most recently from 44d5ca8 to ff6594f Compare January 23, 2023 14:28
@kchibisov
Copy link
Member

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.

Copy link
Member

@kchibisov kchibisov left a comment

Choose a reason for hiding this comment

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

I think we could add a MonitorDescription or something like that and access it within the callback?

Then on the monitor handle itself we could have 2 methods.

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

Then on that data you can access the things you need, that way it'll be less result unwraps and such.

@madsmtm
Copy link
Member Author

madsmtm commented Jan 29, 2023

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.

@dhardy
Copy link
Contributor

dhardy commented Jan 30, 2023

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(..).

@kchibisov
Copy link
Member

kchibisov commented Jan 30, 2023

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.

@xiaopengli89
Copy link
Contributor

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?

Copy link
Member

@daxpedda daxpedda left a comment

Choose a reason for hiding this comment

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

Small nits, otherwise LGTM!

@@ -346,7 +346,7 @@ impl<T> EventLoopWindowTarget<T> {
}

pub fn primary_monitor(&self) -> Option<MonitorHandle> {
Some(MonitorHandle)
None
Copy link
Member

Choose a reason for hiding this comment

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

Great change!
The documentation on EventLoopWindowTarget::primary_monitor() has to be updated though.

pub fn scale_factor(&self) -> f64 {
1.0
pub fn scale_factor(&self) -> Result<f64, MonitorGone> {
unimplemented!()
Copy link
Member

Choose a reason for hiding this comment

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

I believe in this case it should actually be unreachable!(), though unimplemented!() is nice, in this case it should be literally unreachable.

Comment on lines +130 to +132
pub struct MonitorGone {
_inner: (),
}
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
pub struct MonitorGone {
_inner: (),
}
pub struct MonitorGone(());

I don't have a preference here, but I think we should stick with something, I've seen this in tuple-form more often in our code base then in struct-form.

Alternatively (not advocating for it):

Suggested change
pub struct MonitorGone {
_inner: (),
}
#[non_exhaustive]
pub struct MonitorGone;

Copy link
Member

@kchibisov kchibisov left a comment

Choose a reason for hiding this comment

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

I don't like the approach because the problem is that lifetime should be attached to the event loop, where we don't do that. This problem is solved on winit-next where monitor handle has its own ID and you retrieve it from the event loop, thus none of that can happen on all the platforms.

Adding results to every method doesn't sound appealing to me.

@daxpedda daxpedda added the C - nominated Nominated for discussion in the next meeting label Feb 28, 2024
@daxpedda daxpedda removed the C - nominated Nominated for discussion in the next meeting label Mar 15, 2024
@madsmtm
Copy link
Member Author

madsmtm commented Mar 17, 2024

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>,
}

@daxpedda
Copy link
Member

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.

@kchibisov
Copy link
Member

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.

@daxpedda
Copy link
Member

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.

@kchibisov
Copy link
Member

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
Copy link
Member

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?

@daxpedda
Copy link
Member

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.

@kchibisov
Copy link
Member

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
I - BREAKING CHANGE S - api Design and usability
Development

Successfully merging this pull request may close these issues.

5 participants