From a36d0e2cde3745ab355dedb8045a2e90b5f04e71 Mon Sep 17 00:00:00 2001 From: Peter Sonntag Date: Thu, 5 Jan 2023 20:06:32 +0100 Subject: [PATCH 1/3] further improvements --- src/component_macro.md | 4 ++-- src/threads_and_async.md | 33 +++++++++++++++------------------ 2 files changed, 17 insertions(+), 20 deletions(-) diff --git a/src/component_macro.md b/src/component_macro.md index 120f371..a10915f 100644 --- a/src/component_macro.md +++ b/src/component_macro.md @@ -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. @@ -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, diff --git a/src/threads_and_async.md b/src/threads_and_async.md index d83793e..a2eaba4 100644 --- a/src/threads_and_async.md +++ b/src/threads_and_async.md @@ -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. @@ -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 `FactoryComponentSender`. +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. @@ -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`. @@ -75,11 +75,9 @@ 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. @@ -87,26 +85,25 @@ Setting `RELM_THREADS` to 4 will increase the thread count by 3 additional threa ## 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. +The only drawback of this solution is that not all async libraries are fully compatible with the GLib executor, since they must use Tokyo. -# Overview +# Summary When to use ... From cbaeb729ef5d0dbe34675031bb35ed6b495634b8 Mon Sep 17 00:00:00 2001 From: Peter Sonntag Date: Thu, 5 Jan 2023 20:29:18 +0100 Subject: [PATCH 2/3] child components --- src/child_components.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/child_components.md b/src/child_components.md index 9558fdf..39dfec0 100644 --- a/src/child_components.md +++ b/src/child_components.md @@ -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}} @@ -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}} @@ -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 From 6d81857dcf9a60d8d3b9f5404d70e95fed439cec Mon Sep 17 00:00:00 2001 From: Peter Sonntag Date: Fri, 6 Jan 2023 08:32:39 +0100 Subject: [PATCH 3/3] update --- src/threads_and_async.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/threads_and_async.md b/src/threads_and_async.md index a2eaba4..22fce3a 100644 --- a/src/threads_and_async.md +++ b/src/threads_and_async.md @@ -33,7 +33,7 @@ Let's say you have an application that fetches data from a website. 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 a `FactoryComponentSender`. +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. @@ -101,7 +101,7 @@ 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 drawback of this solution is that not all async libraries are fully compatible with the GLib executor, since they must use Tokyo. +A drawback of this solution is that you only can use libraries which support Tokio, as the GLib executor depends on it. # Summary