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

Multiple windows; async communication? #50

Open
dhardy opened this issue Mar 13, 2023 · 5 comments
Open

Multiple windows; async communication? #50

dhardy opened this issue Mar 13, 2023 · 5 comments

Comments

@dhardy
Copy link
Contributor

dhardy commented Mar 13, 2023

Summary

It is probably worth thinking about how to support multiple windows now, even if not implementing yet.

Multiple windows and worker threads may need to communicate, with shared data.

Status quo

An App is a struct over a data: T and app_logic: impl FnMut(&mut T) -> V + Send + 'static where V: View; data is stored in an AppTask which is spawned to its own thread. AppLauncher is a wrapper around one App and one window handle.

async is used internally but there does not appear to be support for e.g. user worker threads. The app_logic is run once per frame and can in theory poll futures and channels, but can only be woken by a "window event" or accessibility event.

Possibilities: multiple monitors

One app_logic

No support for multiple windows except as children of a single App: a View constructs one master window but may also have child (modal?) windows.

One data, multiple app_logics

Each window has its own app_logic method, but shares data. This requires some redesign; possibly a single AppTask contains multiple app_logic methods.

Multiple (data, app_logic) pairs

Add a wrapper, Window, around App; allow multiple in AppLauncher. Each window has its own data and (task) thread. Data can be an Arc<..> or contain channels allowing inter-window communication.

Possibilities: inter-window/app/logic communication

To allow an app_logic to launch a worker thread, this probably implies that the method needs an input parameter which can do one of the following:

  • construct a Waker (or some proxy which can be used to construct a waker)
  • (best option?) spawn a future

To allow update to data on completion of a future, support one of the following:

  1. Only run app_logic (re-render); user must use poll channels and/or use shared data mechanisms
  2. Add a method like update: impl FnMut(&mut T, M) over a user-defined data type M, with spawned futures returning a message: M

Note

The above also prompts the question of whether app_logic should be a method or a trait impl.

@raphlinus
Copy link
Contributor

I've given a bit of thought to this. My most useful inspiration has been SwiftUI, which addresses this problem with the WindowGroup mechanism.

So generally I'd vote for a single app_logic (this is why it's not called "window_logic") that is generally a container of multiple windows at the top level. It is incredibly important for windows to retain stable identity, so I can see the primary containers being statically typed tuples and a dynamic map using an app-managed Id as the key.

Having an app_logic per window might be slightly nicer in uses cases where the windows are more or less independent, but considerably less pleasant when they are coordinating.

macOS has an additional twist, which is that it considers tabs to be windows that simply happen to be hosted inside an another app window. That makes certain things easier (in particular, tabs can be torn off to separate windows or the reverse without much having to bother the app), but raises extreme complications for Xilem, as having one window host another window basically requires coordination with the compositor, and that's not on our roadmap any time soon.

Now is a good time to be exploring this, to avoid painting ourselves in a corner where retrofitting multiple windows would be hard. It's a painful transition for a number of UI toolkits.

@nicoburns
Copy link
Contributor

Another case that's worth bringing up is the possibility of an app with zero windows. I think that case also points towards the direction of an "app logic" that exists independently of a window.

@dhardy
Copy link
Contributor Author

dhardy commented Mar 14, 2023

macOS has an additional twist, which is that it considers tabs to be windows that simply happen to be hosted inside an another app window.

If Xilem is to be a cross-platform toolkit then you either need to emulate this pattern on all platforms or ignore it and draw your own tabs on MacOS. Winit already supports child windows on Windows and X11, so emulating this behaviour on all platforms is probably feasible.

an app with zero windows

That would require some method of running "app logic" besides "redraw the window".

Also, you probably want to be able to redraw one window without re-constructing every window's view tree (especially if a tab is considered a window since you could have a lot).

So... rename app_logicwindow_logic, add another layer for app logic and the ability to add windows at run-time? Or have one app_logic method construct all view trees, but with some filter specifying whether each window needs an update?

Having an app_logic per window might be slightly nicer in uses cases where the windows are more or less independent, but considerably less pleasant when they are coordinating.

Not sure I agree. Some examples of multi-window apps (ignoring hidden windows which hopefully most apps can completely ignore):

  • Web browsers. Though most now use multiple processes so really they're multiple apps.
  • Some text editors/IDEs. Windows are independent document editors that just happen to run in the same app. (A few things like the project configuration/document list are shared.)
  • GIMP. But no modern app does this for good reason.
  • Some IDEs/image editors/.. with internal tool palettes — according to desktop behaviour these apps use one window, but the palettes could be modelled internally as windows.
  • Modal dialogs

The first two don't have much inter-window coordination; the next two only really need to send messages between windows and modal dialogs might be handled by resolving a Future.

@nicoburns
Copy link
Contributor

The first two don't have much inter-window coordination

That's not entirely true. Browser windows/tabs can talk to each other via JavaScript if one opens the other.

So... rename app_logic → window_logic, add another layer for app logic and the ability to add windows at run-time? Or have one app_logic method construct all view trees, but with some filter specifying whether each window needs an update?

I don't think I'd want the global "app logic" responsible for rendering. I'd want it to handle opening/closing windows and be responsible for some global state management / event processing. I guess that means that you want additionally "window logic" (1 per window) to handle the window specific things (which would include rendering in my mind).

@xarvic
Copy link
Collaborator

xarvic commented Mar 14, 2023

As Raph, i am in favor of a single app_logic closure which builds some kind of WindowGroup.

The first two don't have much inter-window coordination; the next two only really need to send messages between windows and modal dialogs might be handled by resolving a Future.

Still some form of manual synchronization is needed. If there is only one View tree, you can't forget to send a state update message.

Or have one app_logic method construct all view trees, but with some filter specifying whether each window needs an update?

This would be the task of the Memoize view.

macOS has an additional twist, which is that it considers tabs to be windows that simply happen to be hosted inside an another app window. That makes certain things easier (in particular, tabs can be torn off to separate windows or the reverse without much having to bother the app), but raises extreme complications for Xilem, as having one window host another window basically requires coordination with the compositor, and that's not on our roadmap any time soon.

In our case it probably makes more sense to move the Tab views between the windows instead of having a window for each Tab.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants