Skip to content

Allow the user to register the application delegate on macOS and iOS#3758

Merged
madsmtm merged 9 commits intomasterfrom
madsmtm/remove-app-delegates
Aug 11, 2024
Merged

Allow the user to register the application delegate on macOS and iOS#3758
madsmtm merged 9 commits intomasterfrom
madsmtm/remove-app-delegates

Conversation

@madsmtm
Copy link
Copy Markdown
Member

@madsmtm madsmtm commented Jun 24, 2024

Register for event-loop lifecycle events using the NSNotification API instead of application delegates. This gives the user full control over the application delegate, which opens several doors for customization that were previously closed.

In particular, this fixes #1751, closes #3713, fixes #2674, fixes #3499, fixes #3650 and fixes #2141, all by allowing the user to override NSApplicationDelegate/UIApplicationDelegate themselves outside Winit.

Also fixes #1281 by listening on notifications instead of using the application delegate.

Resolves part of the macOS side of #2120.

  • Tested on all platforms changed
    • macOS
    • iOS
  • Added an entry to the changelog module if knowledge of this change could be valuable to users
  • Updated documentation to reflect any user-facing changes, including notes of platform-specific behavior

@madsmtm madsmtm added S - enhancement Wouldn't this be the coolest? DS - appkit Affects the AppKit/macOS backend S - docs Awareness, docs, examples, etc. DS - uikit Affects the UIKit backend (iOS, tvOS, watchOS, visionOS) labels Jun 24, 2024
@madsmtm madsmtm force-pushed the madsmtm/remove-app-delegates branch from ae4da6c to 33670a2 Compare June 24, 2024 13:26
@madsmtm madsmtm force-pushed the madsmtm/remove-app-delegates branch from 33670a2 to 5b45632 Compare June 24, 2024 13:31
@madsmtm madsmtm requested a review from kchibisov June 24, 2024 13:31
@madsmtm madsmtm force-pushed the madsmtm/remove-app-delegates branch 5 times, most recently from 39b651e to 4a53350 Compare June 24, 2024 21:53
@madsmtm madsmtm added this to the Version 0.31.0 milestone Jun 24, 2024
Copy link
Copy Markdown
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.

I'm starting to understand the MacOS backend quite a bit now, the cleanup you have done so far is pretty amazing.

Copy link
Copy Markdown
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'm not that familiar with observers, but it looks ok-ish? I know that it works by appkit basically signalling you when something happens and you can register a lot of observers.

Also does it affect performance/input latency in any aspect or it's all the same because macOS is just giant message passing infra already and dispatching observer is not slower than dispatching regular methods?

);
// TODO(madsmtm): Use `MainThreadBound` once that is possible in `static`s.
thread_local! {
static GLOBAL: OnceCell<Rc<AppState>> = const { OnceCell::new() };
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What's the reason to Rc thread local global? I'd assume the main reason is that the user creates it in Rc and shares the pointer with winit, thus it's correctly ref-counted.

And you're using global here just for simplicity?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Rc

I haven't really decided how I want to pass the state around yet, so I just went with something that worked, and matched the semantics of what we were doing before (Retained<ApplicationDelegate> is basically the same as Rc<AppState>).

thread local

That was to avoid a bit of unsafe until I've released a version of objc2-foundation with a const MainThreadBound::new. But I've worked around it with a helper for now instead.

global

I think that the only piece of state that has to actually be global (in Winit) is the ApplicationHandler, since we have to access that from our overwritten WinitApplication when (hackily) dispatching device events. But if I find a different way to implement that, then I might be able to remove this final piece of global state in Winit's AppKit backend.

@madsmtm
Copy link
Copy Markdown
Member Author

madsmtm commented Jul 14, 2024

Also does it affect performance/input latency in any aspect

I looked into it:

On AppKit, when calling -[NSApplication setDelegate:], most of the delegate's NSApplicationDelegate methods (at least the ones we use) are internally registered as observer methods for the notifications we use here. So the performance impact of this PR is basically zero.

On UIKit, the delegate method is called directly, while the notification is emitted immediately afterwards. I'd suspect the performance impact of this PR to be very minimal, as UIKit still creates the NSNotification object even when there are no observers for it, but I haven't measured it.

In any case, you don't need to be concerned with buffering of notifications or other such shenanigans, the notifications are emitted at (basically) the same time as the methods are called.

@Korne127
Copy link
Copy Markdown

@kchibisov Just to make sure you didn't miss it

@kchibisov
Copy link
Copy Markdown
Member

@Korne127 miss what? I approved the PR long time ago...

@Korne127
Copy link
Copy Markdown

Sorry if I'm wrong, to me it just looked like you were expected to give feedback to the new changes / address Mads' answer to your question.

@kchibisov
Copy link
Copy Markdown
Member

I've asked for feedback just to confirm my assumption, otherwise I'd request changes.

@nicoburns
Copy link
Copy Markdown
Contributor

This looks absolutely fantastic. This would solve a lot of the problems I've had with Winit on MacOS. Giving the application control of the AppDelegate is excellent on it's own. But I'm particular excited that this approach seems to open up the possibility of modular libraries that independently hook into apple system events!

This is a breaking change, although unlikely to matter in practice,
since the return value of `application:didFinishLaunchingWithOptions:`
is seldom used by the system.
This allows the user to override the application delegate themselves,
which opens several doors for customization that were previously closed.
@madsmtm madsmtm force-pushed the madsmtm/remove-app-delegates branch from 7e17899 to 53f7c34 Compare August 11, 2024 20:40
@madsmtm
Copy link
Copy Markdown
Member Author

madsmtm commented Aug 11, 2024

The holdup for this PR has not been Kirill at all, it's because I was on vacation ;)

@madsmtm madsmtm merged commit 92e9bfe into master Aug 11, 2024
@madsmtm madsmtm deleted the madsmtm/remove-app-delegates branch August 11, 2024 21:14
@Korne127
Copy link
Copy Markdown

Woohoo! Awesome :D
Thanks for all your work on this, and I hope you had a great vacation! :)

madsmtm added a commit to kchibisov/winit that referenced this pull request Apr 29, 2025
iOS parts of rust-windowing#3758.

This allows the user to override the application delegate themselves,
which opens several doors for customization that were previously closed.
To do this, we use notifications instead of a top-level application
delegate.

One effect of not providing an application delegate on iOS is that we no
longer act as-if the application successfully open all URLs there.

This is a breaking change, although unlikely to matter in practice,
since the return value of `application:didFinishLaunchingWithOptions:`
is seldom used by the system (and this is likely the preferred behaviour
anyhow).
@madsmtm madsmtm mentioned this pull request Apr 29, 2025
@madsmtm
Copy link
Copy Markdown
Member Author

madsmtm commented Apr 29, 2025

I backported the iOS parts of this to v0.30.10, the macOS parts are more extensive, and will have to wait until v0.31.0.

CC @extrawurst, this should allow what we talked about on Discord with v0.30.10.

madsmtm added a commit to kchibisov/winit that referenced this pull request Apr 29, 2025
iOS parts of rust-windowing#3758.

This allows the user to override the application delegate themselves,
which opens several doors for customization that were previously closed.
To do this, we use notifications instead of a top-level application
delegate.

One effect of not providing an application delegate on iOS is that we no
longer act as-if the application successfully open all URLs there.

This is a breaking change, although unlikely to matter in practice,
since the return value of `application:didFinishLaunchingWithOptions:`
is seldom used by the system (and this is likely the preferred behaviour
anyhow).
madsmtm added a commit to kchibisov/winit that referenced this pull request Apr 29, 2025
iOS parts of rust-windowing#3758.

This allows the user to override the application delegate themselves,
which opens several doors for customization that were previously closed.
To do this, we use notifications instead of a top-level application
delegate.

One effect of not providing an application delegate on iOS is that we no
longer act as-if the application successfully open all URLs there.

This is a breaking change, although unlikely to matter in practice,
since the return value of `application:didFinishLaunchingWithOptions:`
is seldom used by the system (and this is likely the preferred behaviour
anyhow).
kchibisov pushed a commit that referenced this pull request Apr 30, 2025
iOS parts of #3758.

This allows the user to override the application delegate themselves,
which opens several doors for customization that were previously closed.
To do this, we use notifications instead of a top-level application
delegate.

One effect of not providing an application delegate on iOS is that we no
longer act as-if the application successfully open all URLs there.

This is a breaking change, although unlikely to matter in practice,
since the return value of `application:didFinishLaunchingWithOptions:`
is seldom used by the system (and this is likely the preferred behaviour
anyhow).
@pomali
Copy link
Copy Markdown

pomali commented May 13, 2025

I backported the iOS parts of this to v0.30.10, the macOS parts are more extensive, and will have to wait until v0.31.0.

Is there any way I can help with macOS parts?

@madsmtm
Copy link
Copy Markdown
Member Author

madsmtm commented May 13, 2025

Problem is that it depends on a bunch of other refactoring PRs I've done, and backporting those (correctly) is kind of a nightmare.

I know how useful this feature would be to downstream projects, but I can't really justify that time usage, I think my time's better spent getting v0.31 out the door.

Though I'd probably accept a PR that cherry-picked the required stuff against the v0.30.x branch, if you want to do it?

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

Labels

DS - appkit Affects the AppKit/macOS backend DS - uikit Affects the UIKit backend (iOS, tvOS, watchOS, visionOS) S - docs Awareness, docs, examples, etc. S - enhancement Wouldn't this be the coolest?

Development

Successfully merging this pull request may close these issues.

Is there a way to access macos events or raw Object ? Double-click opening a file on macOS Consider implementing Lifecycle on controller or view.

6 participants