Iced: Allow setting initial state to `Application` on runtime?

Created on 8 Dec 2019  路  10Comments  路  Source: hecrj/iced

Hi, I'm interested in running iced in vst-rs environment.

To create VST plugin, we need to share some variables between plugin and GUI to sync states. But Application is initialized in new method and there is no way to set external state to Application on runtime as far as I know.

Please considering introduce some mechanics that enable us to set an initial state to the Application trait on runtime.

Thanks.

feature question

Most helpful comment

Precisely, both of the approaches you are mentioning are part of what the current design is meant to forbid.

Sharing state with another part of your application by using interior mutability will simply not work as intended. In fact, I believe you should rarely do that in Rust. When you do, you are giving up on a lot of nice guarantees that the borrow checker gives you at compile time and, as a consequence, opening a door for a lot of pesky bugs.

All the changes to Application state should happen in the update logic through messages. This is a main foundation of The Elm Architecture. Iced will not work properly otherwise. For instance, Iced won't magically redraw the screen if you update application state externally. This may not be very apparent right now, but it will be once we start implementing different kinds of optimizations on top of this guarantee.

While I agree we should allow initial configuration flags to be provided, I think they should never be used to setup shared state with interior mutability.

So, how should users share state externally when using iced? I believe the best approach is to build a communication layer between the parts that need synchronization. Elm achieves this with ports, or basically channels of messages. It's a strategy that works really well with the current reactive architecture and should be possible once event subscriptions land very soon.

Of course, you are free to use iced_native directly and build your own event loop on top of some other guarantees to satisfy your use cases (I think there is a lot of room to improve this process and I would appreciate exploration!). However, the iced crate will most likely never encourage or support sharing state with interior mutability directly.

All 10 comments

Seconded, this is very much needed.

I'm using a global as a workaround for now.

Application::run should take self. Maybe Application::new() can be deprecated, replaced with a combination of run(self, settings: Settings) and Application::init(&mut self) -> Command<_>.

Application::run is static because this way users cannot easily obtain an Application instance before running it. This guarantees that:

  1. The application cannot be altered prior to running it.
  2. The runtime will be the only one with access to it for its entire lifetime.
  3. We can feed runtime parameters to Application::new in the future (like initial window size).

I think an additional associated type may work here. It could be provided as part of Settings and would be given to Application::new. I'll think about it!

It would also be helpful if you could describe your use cases a bit more. What kind of configuration or initial state do you want to set?

It would also be helpful if you could describe your use cases a bit more. What kind of configuration or initial state do you want to set?

Yeah, as I told, I'm using iced to create a VST plugin.

An example project is here.
https://github.com/hatoo/vst-rs-example-iced

Currently, I'm using a modified version of iced now to adapt VST API.
https://github.com/hatoo/iced/tree/run_generator
You can view the diff on https://github.com/hecrj/iced/compare/master...hatoo:run_generator
The change is adding run_geenrator method which receives self and returns Generator of event loop to iced_winit::Application. (Generator is a Nightly feature. It's why I didn't make PR of this. )(I think it can be implemented without Generator now. It was just an easy implementation.)

    fn run_generator(
        self,
        init_command: Command<Self::Message>,
        settings: Settings,
    ) -> Box<
        dyn std::marker::Unpin + std::ops::Generator<Yield = (), Return = ()>,
    >
    where
        Self: 'static,

The configuration I want to initialize is Arc of parameters that share with VST plugin.
The parameters can be modified by both GUI and Plugin host.

I ran into the same problem, where I'd like to pass in some state into my application before running it, which is currently only possible with a global (example here). One reason that I want to pass in state is to be able to store references in the application to some state to do data binding (example abstraction - note: I'm experimenting here, no idea if this is a good way of doing this). However, that would create a self referential struct which is painful/impossible in Rust. I don't have enough experience with self referential structs to know if this is possible some other way though.

In general, I think it should be possible to pass non-'static state into applications. For example, command-line arguments should be passable into the application without a global.

I understand the guarantees from points 2 and 3 are important, but would these also not hold when run takes self and having an init method? I don't understand why point 1 is needed though, why should an application not be modified before running it?

Precisely, both of the approaches you are mentioning are part of what the current design is meant to forbid.

Sharing state with another part of your application by using interior mutability will simply not work as intended. In fact, I believe you should rarely do that in Rust. When you do, you are giving up on a lot of nice guarantees that the borrow checker gives you at compile time and, as a consequence, opening a door for a lot of pesky bugs.

All the changes to Application state should happen in the update logic through messages. This is a main foundation of The Elm Architecture. Iced will not work properly otherwise. For instance, Iced won't magically redraw the screen if you update application state externally. This may not be very apparent right now, but it will be once we start implementing different kinds of optimizations on top of this guarantee.

While I agree we should allow initial configuration flags to be provided, I think they should never be used to setup shared state with interior mutability.

So, how should users share state externally when using iced? I believe the best approach is to build a communication layer between the parts that need synchronization. Elm achieves this with ports, or basically channels of messages. It's a strategy that works really well with the current reactive architecture and should be possible once event subscriptions land very soon.

Of course, you are free to use iced_native directly and build your own event loop on top of some other guarantees to satisfy your use cases (I think there is a lot of room to improve this process and I would appreciate exploration!). However, the iced crate will most likely never encourage or support sharing state with interior mutability directly.

@hatoo About the run_generator, I am guessing you need the event loop to return to integrate it properly with VST. Could you share a bit more details about this? What are you doing between iterations?

Since a child window's event must be handled in the same thread with parent window's thread by WinAPI's limitation, in other words, we cant run event loop in spawned thread.
So vst-rs provides Editor::idle method to implement which is called periodically to handle events in the same thread.

What are you doing between iterations?

It's all about control flow. For each iteration, the generator poll and handle remaining events and return control after that by using winit::event::EventLoop's run_return method .

I think we could implement something similar to Elm's flags: https://guide.elm-lang.org/interop/flags.html

I will tackle this soon!

Allowing one to pass run() a WindowState type augmented with custom fields plus having that WindowState type passed to the Application instance constructor would allow all initial state to be passed in without the use of globals.

In my case, adding a title field to the WindowSettings object passed into Application::run() and using it as the initial state would address the (small) issue I've run into (but would have implications to the Application::title() API).

246 should satisfy most of the use cases described here.

As always, feel free to provide any feedback.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

pbspbsingh picture pbspbsingh  路  4Comments

Gohla picture Gohla  路  4Comments

rowungiles picture rowungiles  路  4Comments

johannesvollmer picture johannesvollmer  路  4Comments

hecrj picture hecrj  路  3Comments