Hacker News new | past | comments | ask | show | jobs | submit login

The article shows a great example of how to implement a state machine with internal delays (do something, wait for a defined time, do something else), which is very useful in a driver or embedded context where you often just have to wait for an external device to be ready.

However, it doesn't really address how you'd construct a state machine with an external tick. It's pretty common to have a state machine called at a fixed frequency. I guess to implement that (using the pending!() Macro between state actions) you'd need to implement a custom executor?




Code using a fixed timestep usually explicitly takes advantage of this design, and is intentionally a hand-rolled state machine, so async doesn't improve it IMHO. Async is meant to hide the event loop (the ticks).

It depends how deep you want the ticks integrated with your async code. At minimum you can do:

   async { loop { next_tick_time().await; do_tick(); } }
You could also write your own async executor that just polls all spawned futures on every tick, instead of the event (waker) mechanism used by async.

But both approaches are IMHO pointless. Async is meant to be a sugar on top of events and run code only when the events happen, not run it all the time at a fixed timestep.


Your case sounds like where you'd use select()/select! to wait on multiple things? A lot of writing about async/await neglects to mention multiple potential events, but async/await's reason is really that case.

    select! {
        () = wait_for_tick() => println!("tock"),
        v = woken_thing() => println!("woke with {v}"),
    }


What is the difference between „call state machine nextStep() with a fixed timer“ vs „call async fns with a delay“?


You mean calling async functions with internal delays? The delay is defined internally to the async function, rather than externally.

The difference between:

  async fn bla() {
     doWork();

     waitForSecs(x);

     doMoreWork();

     waitForSecs(x);

     lastBit();
  }
  
and

  fn somethingElse() {
    // state is persistent
    match state {
      FirstState => doWork(); state = SecondState;
      SecondState => doMoreWork(); state = ThirdState;
      ThirdState => lastBit(); state = Done;
      _ => ()
  }
is that the first example controls the delay period, while in the second example the caller decides the period. The timing of the first example is also dependent on the execution time of the work functions, while the timing of the second is only dependent on the caller.

The benefit of the second example is that it can be completely synchronous with other parts of the system. You know that when your global tick happens, all the state transitions also happen. If each function manages their own time delays, that's not a given.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: