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

How do I checkpoint and resume Rhai evaluation? #769

Open
vi opened this issue Oct 24, 2023 · 4 comments
Open

How do I checkpoint and resume Rhai evaluation? #769

vi opened this issue Oct 24, 2023 · 4 comments
Labels

Comments

@vi
Copy link

vi commented Oct 24, 2023

I want something like this

use rhai::{Engine, EvalAltResult, Position};

struct MyContext {
    e: Engine,
}

impl MyContext {
    fn new() -> MyContext {
        let mut e = Engine::new();
        fn suspend() -> Result<i32, Box<EvalAltResult>> {
            let resumed = true; // ?
            if resumed {
                Ok(3 /* assuming getting from some channel */)
            } else {
                // How to I save execution context to be able to return twice?
                Err(Box::new(EvalAltResult::ErrorTerminated(().into(), Position::NONE)))
            }
        }
        
        e.register_fn("suspend", suspend);
        MyContext { e }
    }
    fn start(&self) {
        let _ = self.e.eval::<()>(r#"
            print(1);
            print(suspend());
            print(4);
        "#);
    }
    fn resume(&self, _x: i32) {
        // (Assuming sending _x to the channel)
        
        // ?
    }
}

fn main()
{
    let state = MyContext::new();
    state.start();
    // Rhai evaluation stack frames should not be absent. It should be in inert, "frozen" state.
    // N suspended evaluations should not occupy N operation system threads.
    println!("2");
    state.resume(3);
}

It should print 1\n2\n3\n4\n.

Is there some example (e.g. using internals feature) to attain this?

Alternatively, is there something like "call with current continuation" in Rhai, to avoid each suspension point becoming a closure in Rhai code (i.e. callback hell)?

@schungx
Copy link
Collaborator

schungx commented Oct 25, 2023

What you want is called async, which Rhai isn't.

Async means being able to suspend operation in the middle, store the context (whatever), and then resume later on.

You can easily do that by running the Engine in a separate thread though. Due to the popularity of this, I have just coded up an example, which you'll find here: https://github.com/schungx/rhai/blob/master/examples/pause_and_resume.rs

@vi
Copy link
Author

vi commented Oct 25, 2023

The example does not seem to prevent many paused executions from hogging as many OS threads. Pausing from on_progress does not seem to be significantly different from pausing from suspend.


Callbacks / closures seem to work as a makeshift suspension points. But they lead to ugly Rhai source code.

What can be done with a patterns like this:

func1(arg1, |ret1| {
   func2(arg2, ret1, |ret2| {
      func3(...)
   })
})

, so it would look like

ret1 <- func1(arg1);
ret2 <- func2(arg2, ret1);
func3(...)

Can Rhai's "custom syntax" be used for that? Or maybe better to just pre-process source code and just turn A <- B();s into B(|A| { /* rest of the file/block */ })?

@schungx
Copy link
Collaborator

schungx commented Oct 25, 2023

Well, that's the pitfalls of using raw threads.

Async is invented just to solve your problem. The compiler saves up you calling stack, allowing you to resume later on. What you described is actually "coroutines" which forms the basis of async in modern languages.

Unfortunately Rhai is not intended for async use. If you need it, the you can look into an async scripting engine.

IMHO, it is a very bad idea to do async in scripts (look at the mess called JavaScript). If you intend scripts to be written by non-expert programmers (otherwise why use scripts?) then you don't really want users to hassle with the problem of async.

Rhai is not intended for this usage scenario. You are better advised to split your API into sections with the async barriers in between. If you want to script an entire application in Rhai with an async API, just like JavaScript... then maybe using JavaScript is best for your use case.

In your example, you can separate your API into different functions:

fn on_init() {
    print(1);
}

fn on_suspend() {
    return 42;
}

fn on_resume(x) {
    print(x);
}

fn on_stop() {
    print(4);
}

@schungx
Copy link
Collaborator

schungx commented Oct 25, 2023

Callbacks / closures seem to work as a makeshift suspension points. But they lead to ugly Rhai source code.

What you described is, again, async.

This is called the CPS (Continuation-Passing Style) of an async call, before the popular async/await keywords were invented.

JavaScript has massive amounts of CPS code which led to callback-hell.

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

No branches or pull requests

2 participants