rhaiscript / rhai

Rhai - An embedded scripting language for Rust.
https://crates.io/crates/rhai
Apache License 2.0
3.79k stars 177 forks source link

Find out which constants are used in an expression #253

Closed timfish closed 3 years ago

timfish commented 4 years ago

We're using Rhai to evaluate equations on large blocks of captured data. We do this using a custom type which wraps large Vecs and override operators for that custom type which do the maths on the large blocks of data. This is working really well!

However, we currently load all the possible variables into the scope which is wasteful because creating the wrapping types involves large allocations and the equation might not use all (or even most) of them.

Things I've considered:

  1. Parse the expression (or AST) and find the required constants and populate the scope accordingly. Is there currently a way to do this?
  2. Modify the expression so constants are functions A * 2 => A() * 2. I don't think this will work because we'd have to pre-define a function for each possible variable.
  3. Add a hook to Rhai so that variables can be fetched during evaluation

What are your thoughts?

timfish commented 4 years ago

I've thought of another way:

  1. Create another wrapping type which can be added to the scope and lazily loads the data when it's accessed.
schungx commented 4 years ago

I assume that your constants data is expensive to prepare and are not themselves constants (i.e. they change values when called a different time), so they must be prepared at the time of the script evaluation, not before. Otherwise, it is a simple matter of making a scope with all prepared constant values once, then reusing that scope many times for script calls.

Currently No.1 is probably your best bet, but it is volatile. If you disable eval then it is workable, but with eval it is not.

No.4 is interesting and doable, but since you're using straight variable access (not a map.property access) for your constants, you'll still need to push a large number of wrappers into the scope.

I can also add an AST-walking function to dump variables usage (actually code is already there to handle closures).

schungx commented 4 years ago

Come to think of it, No.3 which is to add an on_var callback is probably the most versatile way to resolve this, with the least amount of runtime overheads. I'll put that in.

If you want the new functionality, you'll probably need to pull from the development repo for the time being.

timfish commented 4 years ago

Currently all the variables are in memory so copying them to scope isn't a problem behind an Arc, but I'm in the process of adding a file backed store so loading everything into scope could potentially be very expensive. My wrapping type contains huge vectors of samples.

1 is simplest from my perspective because I wouldn't have to implement a caching layer so multiple uses if the same variable doesn't become slower. Also wouldn't have to worry about bloating the scope.

I've thought of another way to do this that doesn't require any changes in rhai. Since iterators are lazily evaluated, I can add cloneable iterators to the scope in a wrapping type + operator overloaded can chain extra integrators on top. It's only once the resulting iterator is collected that the expensive stuff happens. My only concern here is that dynamic dispatch of chained iterators over millions of iterations is going to suck!

schungx commented 4 years ago

@timfish Please pull from this repo. There is a new feature added: variable resolver.

Check out the new docs: https://schungx.github.io/rhai/vnext/patterns/dynamic-const.html

timfish commented 4 years ago

This is awesome! I'll give it a try.

schungx commented 4 years ago

@timfish how is it working out?

timfish commented 4 years ago

I think it's working well, thank you. I'll close this issue once I've given it a more thorough test!

schungx commented 4 years ago

@timfish see if you have any more issues with this feature, otherwise this can be closed! Thanks!

Beware, I just released version 0.19.3 which has a slight change to the function signature of the on_var closure...

timfish commented 3 years ago

Thanks, this is all good