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

Further improvements #34

Open
wants to merge 3 commits into
base: next
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions src/child_components.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ This is how the dialog looks like in the alert example:

## The alert component

The alert component is defined similar to the other components we've implemented in this book.
The alert component is defined similarly to the other components we've implemented in this book.

Our model stores whether the component is visible and the configuration.
Our model stores whether the component is visible and its configuration.

```rust,ignore
{{#include ../examples/alert.rs:model}}
Expand All @@ -35,7 +35,7 @@ This maximizes the reusability of the component by letting it adapt to different
{{#include ../examples/alert.rs:settings}}
```

In the `Input` type, this component uses `#[doc(hidden)]` on the `Response` variant. This is a useful pattern for component-internal messages that are not intended to be sent by outside callers. This allows us to update the component when the underlying dialog reports a response, but not display the `Response` variant in the component's documentation.
In the `Input` type, this component uses `#[doc(hidden)]` on the `Response` variant. This is a useful pattern for component-internal messages that are not intended to be sent by external callers. This allows us to update the component when the underlying dialog reports a response, but not display the `Response` variant in the component's documentation.

```rust,ignore
{{#include ../examples/alert.rs:input}}
Expand Down Expand Up @@ -96,8 +96,7 @@ See the [`set_transient_for` documentation](https://docs.gtk.org/gtk4/method.Win
{{#include ../examples/alert.rs:close}}
```

That's it! You can find more examples of reusable components in the
relm4-components crate [here](https://relm4.org/docs/stable/relm4_components/).
That's it! You can find more examples of reusable components in the relm4-components crate [here](https://relm4.org/docs/stable/relm4_components/).
You can also contribute your own reusable components to relm4-components :)

## The complete code
Expand Down
4 changes: 2 additions & 2 deletions src/component_macro.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ The app will look and behave identically to our first app from the previous chap

> The app we will write in this chapter is also available [here](https://github.com/AaronErhardt/relm4/blob/main/relm4-examples/examples/simple.rs). Run `cargo run --example simple` from the [example directory](https://github.com/AaronErhardt/relm4/tree/main/relm4-examples) if you want to see the code in action.

## What's different
## The difference

The `component` macro will simplify creating the `Widgets` `struct`. The update code remains untouched, so we can reuse most of the code from the previous chapter.

Expand Down Expand Up @@ -67,7 +67,7 @@ Again, there's no magic. The macro will simply assign a closure to a method. Bec
> method_name[sender] => move |_| { sender.input(Msg); },
> ```
>
> You can simply write this:
> you can simply write this:
>
> ```rust,no_run,noplayground
> method_name => Msg,
Expand Down
33 changes: 15 additions & 18 deletions src/threads_and_async.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Introduction

[**If you're already familiar with this chapter, you can skip to the overview**](#overview)
[**If you're already familiar with this chapter, you can skip to the summary**](#summary)

Many applications need to process data in the background.
Yet, at the same time, it's often desired to keep application responsive.
Expand Down Expand Up @@ -30,11 +30,11 @@ Blocking our only thread with a heavy computation stops our application from pro
There are multiple possibilities to address this common problem. In this chapter we'll have a look at Commands which is a simple yet extremely powerful mechanism which covers most use cases.

Let's say you have an application that fetches data from a website.
This leaves us in a similar situation as before: If we a synchronous HTTP library in the update function, we will block our main thread and freeze the application until the server responds.
This leaves us in a similar situation as before: If we use a synchronous HTTP library in the update function, we will block our main thread and freeze the application until the server responds.
So instead, we're going to use Commands in this example.

Commands are background tasks that can be spawned using a `ComponentSender` or `FactoryComponentSender`.
They run until they return their result as a `CommandOutput` message that will be processes by the component again.
Commands are background tasks that can be spawned using a `ComponentSender` or a `FactorySender`.
They run until they return their result as a `CommandOutput` which in turn is processed again by the component.

First we define our message type, then we can use it as associated `CommandOutput` type in our component.

Expand All @@ -46,10 +46,10 @@ First we define our message type, then we can use it as associated `CommandOutpu
{{#include ../examples/threads_and_async.rs:command_output_type }}
```

> Note: This only works with the `Component` trait.
> Note: This works only with the `Component` trait.
> The simplified `SimpleComponent` trait doesn't allow you to use components.

In our update function, spawn a new command using the `oneshot_command()` method.
In our update function, we trigger a new command using the `oneshot_command()` method.
This method allows us to spawn a future that will yield exactly one `CommandOutput` message at completion.
From the command, we call an asynchronous function that will handle the web request for us.
Once the future completes, the command returns a `CommandMsg`.
Expand All @@ -75,38 +75,35 @@ You can even use commands for synchronous operations too.
### Configuration

Commands actually run on a tokio runtime.
This gives you great compatibility with Rust's async ecosystem and a lot of flexibility at the same time.
This gives you great compatibility with Rust's async ecosystem and a lot of flexibility at the same time. In Relm you can customize the configuration of the runtime by overwriting [static variables](https://relm4.org/docs/next/relm4/index.html#statics) at the start of your main function.

If you spawn a lot of commands in your application or want to fine-tune the runtime, you can set [two static variables](https://relm4.org/docs/next/relm4/index.html#statics) at the start of your main function to override the default value.
For example, Relm4 only uses one thread for asynchronous background tasks which might not be enough.
Setting `RELM_THREADS` to 4 will increase the thread count by 3 additional threads.
By default Relm4 uses only one thread for asynchronous background tasks which might not be enough. For example, if you spawn many commands in your application, you can set `RELM_THREADS` to 4 to increase the thread count by 3 additional threads.

> Note: Setting the static variables must be done early.
> As soon as the runtime is initialized (which happens when it's accessed for the first time), the values cannot be changes anymore.

## Workers

Workers are basically just components without widgets.
However, because they don't have widgets, workers can be sent to other threads which also makes it possible run their update function on another thread.
This means, that running the slow `generate_rsa_key()` function from the first section won't freeze your application.
However, since they don't have widgets, workers can be sent to other threads, which also makes it possible run their update function on another thread.
This means, that executing the slow `generate_rsa_key()` function from the first section won't freeze your application.

All you need to do to create a worker is to implement the [`Worker`](https://relm4.org/docs/next/relm4/worker/trait.Worker.html) trait and call [`detach_worker`](https://relm4.org/docs/next/relm4/component/struct.ComponentBuilder.html#method.detach_worker) from the `ComponentBuilder`.
This will setup the worker to run on a new thread.
To create a worker, you just need to implement the [`Worker`](https://relm4.org/docs/next/relm4/worker/trait.Worker.html) trait and call [`detach_worker`](https://relm4.org/docs/next/relm4/component/struct.ComponentBuilder.html#method.detach_worker) from the `ComponentBuilder`. This will execute the worker in a new thread.

You might wonder why workers even exist if we already have commands.
Actually, workers have some unique properties: They can only run one task at the time and store state like a regular component.
Actually, workers have some unique properties: They can only run one task at a time and store their state like a regular component.

## Local futures

Both commands and workers run on different threads, so Rust's guarantees require the involved types to implement `Send`.
This can be problematic sometimes, for example when widgets are involved.
This can sometimes be problematic, for example when it comes to widgets.

Fortunately, the [`spawn_local`](https://relm4.org/docs/next/relm4/fn.spawn_local.html) function allows us to spawn local futures, which don't require `Send` because they run on the main thread.
This works because GTK uses an event loop from GLib to handle asynchronous events, which also allows you to execute futures.

The only downside of this solution is that not all async libraries are fully compatible with the GLib executor because they have to use Tokio.
A drawback of this solution is that you only can use libraries which support Tokio, as the GLib executor depends on it.

# Overview
# Summary

When to use ...

Expand Down