asynchronics / asynchronix

High-performance asynchronous computation framework for system simulation
Apache License 2.0
170 stars 9 forks source link

recursive self-scheduling #2

Closed leon-thomm closed 1 year ago

leon-thomm commented 1 year ago

I'd like to self-schedule an input from within that same input. Something like this:

#[derive(Default)]
struct Fetcher {}

impl Fetcher {
    fn blackbox(&mut self) -> bool { return rand::thread_rng().gen_bool(0.5); }

    async fn on_fetch(&mut self, _: (), scheduler: &Scheduler<Self>) {
        let b = self.blackbox();
        println!("fetching!\n{}", if b {"success"} else {"failure, retrying in 1s"});
        if b { scheduler.schedule_in(Duration::from_secs(5), Self::on_fetch, ()); }
        else { scheduler.schedule_in(Duration::from_secs(1), Self::on_fetch, ()); }
    }
}

impl Model for Fetcher {}

Rust doesn't allow for direct recursion in async functions, so this will give a compiler error. Generally, on workaround is to return a BoxFuture (instead of a Future).

// --snip--
    fn on_fetch<'a>(&'a mut self, _: (), scheduler: &'a Scheduler<Self>) -> BoxFuture<()> {
        async move {
            let b = self.blackbox();
            println!("fetching!\n{}", if b {"success"} else {"failure, retrying in 1s"});
            if b { scheduler.schedule_in(Duration::from_secs(5), Self::on_fetch, ()); } 
            else { scheduler.schedule_in(Duration::from_secs(1), Self::on_fetch, ()); }
        }.boxed()
    }
// --snip--

This messes up the code quite a bit, so I was looking for a nicer way to do this. The only alternative I found is to create a dedicated loopback output

#[derive(Default)]
struct Fetcher {
    loopback: Output<bool>,
}

impl Fetcher {
    // --snip--
    async fn on_fetch(&mut self, _: (), scheduler: &Scheduler<Self>) {
        let b = self.blackbox();
        self.loopback.send(b).await;
    }
    async fn on_loopback(&mut self, b: bool, scheduler: &Scheduler<Self>) {
        println!("fetching!\n{}", if b {"success"} else {"failure, retrying in 1s"});
        if b { scheduler.schedule_in(Duration::from_secs(5), Self::on_fetch, ()); }
        else { scheduler.schedule_in(Duration::from_secs(1), Self::on_fetch, ()); }
    }
}
// --snip--

But this is not any better, it requires the simulator to connect the output correctly, and assumes that the output is not connected to anything else.

Is there a better way to do this with asynchronix?

sbarral commented 1 year ago

A workaround that does not involve boxing is mentioned in the docs, would that work for you? In your case I guess it would look something like:

fn on_fetch<'a>(
    &'a mut self,
    _: (),
    scheduler: &'a Scheduler<Self>
) -> impl Future<Output=()> + Send + 'a {
    async move {
        let b = self.blackbox();
        println!("fetching!\n{}", if b {"success"} else {"failure, retrying in 1s"});
        if b { scheduler.schedule_in(Duration::from_secs(5), Self::on_fetch, ()); } 
        else { scheduler.schedule_in(Duration::from_secs(1), Self::on_fetch, ()); }
    }
}
leon-thomm commented 1 year ago

Oh, I remember I read that once but then didn't find it again, my bad! Not the prettiest thing, but for my purposes this is what I need. Thanks!

sbarral commented 1 year ago

To be honest it took me a minute to find again where that piece of documentation was, I should probably move it to the top level. True, the syntax is a bit noisy, I'm hopeful rustc will remove this pain point in a not too far future...