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

Is it possible to send a Message manually? #1172

Closed
cbeust opened this issue Dec 26, 2021 · 12 comments · Fixed by #1198
Closed

Is it possible to send a Message manually? #1172

cbeust opened this issue Dec 26, 2021 · 12 comments · Fixed by #1198
Labels
question Further information is requested

Comments

@cbeust
Copy link

cbeust commented Dec 26, 2021

I am aware that regular way to do this is to use Subscriptions and Recipes, but is it possible to send a Message manually from "outside" the iced regular functions?

@13r0ck
Copy link
Member

13r0ck commented Dec 26, 2021

The answer is yes-but-actually-no

It is possible to just call your update function, and just pass the message you want to.

But don't!

This is only a side effect of the current implementation of the elm architechture being ported to rust.

Manually passing messages breaks all guarentees that the elm architechture provides.

@cbeust
Copy link
Author

cbeust commented Dec 27, 2021

Alright, then I won't :-)

So are Subscriptions and Recipes the only way?

I've found it hard to figure out how to convert collections and iterators into these, even from the examples/ directory. Is there any other code I can dive in to figure this out? (specifically, I have a mpsc::Receiver which is very close to the form a Recipe needs to stream, but I can't figure out how to transform one into the other).

@hecrj
Copy link
Member

hecrj commented Dec 27, 2021

Could you elaborate a bit on your use case?

@hecrj hecrj added the question Further information is requested label Dec 27, 2021
@cbeust
Copy link
Author

cbeust commented Dec 27, 2021

I am specifically working on an emulator which sends a bunch of messages to indicate what's going on (memory writes, etc...).

But fundamentally, I am doing:

let (tx, rx) = mpsc::channel::<CpuMessage>();

tx sends these messages and now, I'd like to find a way to turn the messages received by rx into something suitable for iced. My understanding is that I need to implement a Recipe and in particular, implement the stream() function.

How can I turn the stream of messages received by rx into something I can return from Recipe::stream() (which appears to be a stream of boxed objects)?

@13r0ck
Copy link
Member

13r0ck commented Dec 27, 2021

Ah ya, this is possible, and a planned feature, just not implemented yet.

What needs to be done is

  1. impl a recipe
  2. convert the recipe to a subcscription
  3. then subscribe to it

Those links are from the download example. Your implementation would have to subscribe to the mpsc by creating a recipe. You probably would want to use .try_recv, then iced will call that recipe repeatedly, no message will be crated until some value is returned from the mpsc.

If you get it working, a PR to simplify the API or an example here and or here would be awesome! Asking how to subscribe to an mpsc is really common.

@cbeust
Copy link
Author

cbeust commented Dec 27, 2021

Yes, I am familiar with all these steps, but the devil is in the details, and in particular, returning that BoxStream :-)

I think even a simple example of how to turn an iterator into that BoxStream would go a long way, because the download example is quite convoluted, with its state machine and all.

I'll keep digging.

@cbeust
Copy link
Author

cbeust commented Dec 27, 2021

I made some progress and almost got everything to compile, but I'm now hitting a wall and I'm wondering if it might be a problem in iced.

Here is my implementation of Recipe:

impl<H> Recipe<H, (iced_native::event::Event, iced_native::event::Status)> for StreamGenerator
    where H: std::hash::Hasher
{

and the error:

error[E0277]: the trait bound `StreamGenerator: iced_futures::subscription::Recipe
<iced_native::hasher::Hasher, (iced_native::event::Event, iced_native::event::Status)>` is not satisfied

All the generic types match except the Hasher, which references the one internally in iced and not in std::hash. However, that Hasher is private in iced and the download example references the one in std::hash.

Am I missing something? Maybe I Hasher is not the problem and there's something else I'm not seeing...

@cbeust
Copy link
Author

cbeust commented Dec 28, 2021

Here is a fully contained example showing the compiler error:

use std::sync::mpsc;
use iced::{Application, Command, Element, executor, Subscription};
use std::sync::mpsc::{Receiver};
use iced_futures::subscription::Recipe;
use futures::prelude::stream::BoxStream;

#[derive(Debug)]
enum CpuMessage {
    Message1
}

struct Emulator {
    rx: Receiver<CpuMessage>,
}

impl<H, I> Recipe<H, I> for Emulator
    where H: std::hash::Hasher
{
    type Output = (CpuMessage, ());

    fn hash(&self, state: &mut H) {
        // struct Marker;
        // std::any::TypeId::of::<Marker>().hash(state);
        //
        // self.id.hash(state);
    }

    fn stream(self: Box<Self>, input: BoxStream<I>) -> BoxStream<Self::Output> {
        use futures::stream::{self, StreamExt};
        use futures::future;

        let stream = futures::stream::unfold(self.rx, |r| {
            let v = match r.recv() {
                Ok(v) => {
                    println!("Received value from rx: {:?}", v);
                    // Some((v, r))
                    Some(((v, ()), r))
                },
                Err(_) => {
                    println!("StreamGenerator in error");
                    None
                },
            };
            future::ready(v)
        });
        Box::pin(stream)
    }
}

impl Application for Emulator {
    type Executor = executor::Default;
    type Message = CpuMessage;
    type Flags = ();

    fn new(flags: Self::Flags) -> (Self, Command<Self::Message>) {
        let (tx, rx) = mpsc::channel::<CpuMessage>();

        let result = Emulator {
            rx,
        };

        (result, Command::none())
    }

    fn title(&self) -> String { todo!() }
    fn update(&mut self, message: Self::Message) -> iced::Command<Self::Message> { todo!() }

    fn subscription(&self) -> Subscription<CpuMessage> {
        iced::Subscription::from_recipe(self)    // <----------------------- error here
    }

    fn view(&mut self) -> Element<'_, Self::Message> { todo!() }

}

@cbeust
Copy link
Author

cbeust commented Dec 31, 2021

Any thoughts on how to get this to compile?

@atlanticaccent
Copy link

I tried getting it to compile to no avail, but I think that's me not quite understanding your needs.

I have a working subscription that uses an mpsc here: https://github.com/atlanticaccent/starsector-mod-manager-rust/blob/e1e35ca6d5824eead3146da724cb30cf402d0f77/src/gui/installer.rs

The more I think about it, you may want to consider doing it as I have above, and create a complete State struct which contains your rx, and effectively handle all async operations (including mpsc instantiation) from within the subscription itself.

@cbeust
Copy link
Author

cbeust commented Jan 5, 2022

Thanks, I'll take a look.

But still, isn't it weird that we can't get this to compile? It should be possible. All I'm doing in this code is convert channels into a subscription/recipe, it should be a trivial code sample.

@hecrj
Copy link
Member

hecrj commented Jan 17, 2022

@cbeust Could you take a look at #1198 and see if it suits your use case?

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

Successfully merging a pull request may close this issue.

4 participants