Closed paulalesius closed 2 years ago
So far, I've managed to acquire a HtmlTextAreaElement from post:build:
<textarea post:build=move |dom: &mut Dom| {
match dom.inner_read() {
Either::Left(rwguard) => {
let area = rwguard.clone().dyn_into::<HtmlTextAreaElement>();
loop {
rxv.clone() // ??
}
}
Either::Right(ssr) = {} } }>
But I can't find a way to poll a stream from there. And that's a read-write guard that I don't think that one should be holding on to indefinitely in a loop anyway.
The way that I've been dealing with this is to use the post:build
operation to send a clone of the Dom
to my logic loop. That way I can hold on to the Dom
(which has a clone of the HtmlTextAreaElement
if compiled for wasm) for as long as I need:
fn view(tx: Sender<Dom>) -> ViewBuilder<Dom> {
builder! {
<textarea post:build=move |dom: &mut Dom| {
tx.try_send(dom.clone()).unwrap();
}></textarea>
}
}
async fn logic(mut rx: Receiver<Dom>) {
let text_area_dom = rx.next().await.unwrap();
{
if let Some(text_area) = text_area_dom.clone_as::<HtmlTextAreaElement>() {
// ... do `web_sys` stuff with text_area
}
}
loop { // ... loop as normal }
}
There's an entry in the mogwai cookbook about capturing parts of the view that may help. There are also a few examples of this in the examples dir.
Let me know what you think :)
Was using a ::broadcast channel to send things to the logic, and a ::mpmc to send things to the view, after the examples in the cookbook.
Your example uses tx.try_send() from a ::mpmc channel.
So in the logic
183 | let mut sources = mogwai::futures::stream::select_all(vec![rx_broadcast.boxed(), rx_mpmc.boxed()]);
| ^^^^^ `*mut u8` cannot be shared between threads safely
So I'm trying to refactor the component architecture I'm trying to establish, to only use ::mpmc to send things to the logic.
It's very tricky to figure out an architecture that is consistent to focus on the business logic.
I don't think it makes sense for everything to be asynchronous. The complexities involved in performing basic logic will have one fiddling with channels for days, and we haven't even begun on the business logic of the application.
Asynchronous programming should be a tool that you use to achieve something, instead of for interacting with the API which serves no purpose from what I can tell, but to prevent one from programming.
Given the asynchronous nature of 0.5.0 it looks like we should only use ::broadcast channels exclusively, so that we don't run into issues later.
But now I can't send a mogwai::prelude::Dom through a ::broadcast channel in WASM because its wasm_bindgen::JsValue isn't std::marker::Send.
I'm going back to refactoring again using ::mpmc channels, but the logic already has a loop reading ::broadcast messages, and blocking on .await. Where do you handle the ::mpmc messages?
let mut sources = mogwai::futures::stream::select_all(vec![rxl.boxed()]);
loop {
match sources.next().await { }
}
Hopefully I can address all your concerns.
It's very tricky to figure out an architecture that is consistent to focus on the business logic.
This can be true. Even if you're used to working with the various channels it's easy to get confused. This is something I'll be working on in the future. It would be easier if channel implementations' Sender
was Sink
as well as their Receiver
being Stream
, but most Sender
s are not Sink
.
I don't think it makes sense for everything to be asynchronous.
I agree, though I think the logic loops should be asynchronous because you often have to await
things like requests or delays, etc. It's much less error prone to await
the changes you expect than it is to send_async
back into the logic loop and pick stuff back up at a later point, which is what mogwai < 0.5
did.
Given the asynchronous nature of 0.5.0 it looks like we should only use ::broadcast channels exclusively, so that we don't run into issues later.
This is mostly correct. If you don't have a specific reason not to use a broadcast
, then you probably should. It should be the channel you reach for first, because it guarantees that all downstream Receivers
get a clone of the message. This is especially important when making ViewBuilder
s because we often give the builder a stream in the form oftx_view.clone().filter_map(...)
- and if there are more than one of those sites and you're using the mpmc
channel they will receive messages in round robin. For this reason I'll probably remove the mpmc
channel in favor of futures::channel::mpsc
(which is Sink
).
But now I can't send a mogwai::prelude::Dom through a ::broadcast channel in WASM because its wasm_bindgen::JsValue isn't std::marker::Send.
You are correct that JsValue
is !Send
, but Dom
is Sendable
, which means it is Send
when the target is not wasm32
. Mogwai goes to a lot of trouble to make sure that on WASM the constraints are relaxed so you can pass around JsValue
inside a Dom
regardless of what target you're compiling for.
The reason to use mpmc
instead of broadcast
is because ViewBuilder
is not Clone
, which is a requirement for broadcast
. You should be fine sending your Dom
nodes through broadcast
because clones are cheap. Then in your logic loop keep them as Dom
until you need the underlying JsCast
type (HtmlElement
, etc) - at which point you can use visit_as
or clone_as
. Just make sure not to keep the JsCast
type around across an await
point or else your future will no longer be Send
.
Thanks for exposing the confusion in channels. I'll have to do something to fix that. I'll probably drop async_channel
altogether (which is the mpmc
channel) in favor of using futures::mpsc
.
And with regards to send_async and safety, all of my code that interacts with javascript is kept in a separate mod, and that still requires:
wasm_bindgen_futures::spawn_local(async move {
});
Because they use JsValue objects from Javascript that are not "Send", so you cannot JsFuture::from(promise_with_jsvalue_objects).await from mogwai with_logic() loop.
So again all of my code is async everywhere for seemingly no purpose, but all interaction with javascript requires wasm_bindgen_futures::spawm_local.
I see the confusion. Currently each time you call with_logic
the Component
replaces its logic with the given future. So your first future is replaced with your second future and subsequently dropped, including rxn
, closing the channel.
It does make sense that you would think it appends the logic though, and the implementation could do that, if desired. I suppose that would be preferable to using types to ensure that this confusion never happens (which would mean Component::with_logic
returns a new type that that does not have a with_logic
method). I like that, actually - because then you can split the updates of different parts of the same view between separate futures using different messages as you've done here. I will probably change to this behavior as a result of this conversation.
I understand your frustration with the new changes. Trying to anticipate some that frustration I included the IsElmComponent trait, which works almost exactly the same as 0.4
's Component
trait. I decided to move away from the Elm style composition because there are already good alternatives like sauron
there. mogwai
is geared a bit more towards flexibility and speed, I guess. But with IsElmComponent
you get your cake and can eat most of it too.
Given some more time I'd like to rewrite your example to help you out, but I won't be able to until tomorrow morning (NZ time).
Thank you, closing issue.
I understand how to use it now, deleted my whining posts, my bad. Great framework! ♥️
Thanks for the issue - it has illuminated some of the areas of mogwai that I haven't thought enough about. 🙇
Hi, is it possible to send content to a
The
I can't find a way to cast an element inside a builder! {} ViewBuilder to HtmlTextAreaElement to call the value() method, and I can't seem to send messages to it in any way that calls the value() method.
Note this is not the
Some help with how to do this would be highly appreciated!
Thank you!