Closed ltabis closed 2 years ago
Rhai functions are pure, so they typically cannot access global variables. That's why they can be taken apart and merged again.
However, this is common enough that you have three alternatives:
1) Use compile_with_scope
instead of compile
which will use constants in the custom Scope
to optimize the script functions; afterwards, you no longer need that Scope
2) https://rhai.rs/book/engine/var.html
3) https://rhai.rs/book/patterns/constants.html (for this way, you can skip the first few steps if your constants are already in your Scope
Ok so, I can't use your first solution because my use of the engine is shared between threads, has well as the ast, and I need to create a scope on events with a certain state in it. (sorry, my message was missing some context)
For the second solution, it is the same as above, I need to inject the constant at "runtime". I can't use a variable resolver.
Same for the third one.
The option I would go for is this: https://rhai.rs/book/patterns/singleton.html But can you use the singleton in a function without passing it by parameter ? Or is it possible to "re-export" it to the global module ?
Ah. You want different constants for different runs based on the same AST.
Option 1: This is a typical use of variable resolvers. Are you sure you cannot use this?
Option 2: You can "re-optimize" an AST using Engine::optimize_ast
which is reasonably efficient. So keep the original AST but re-optimize it based on new constants before each run. As long as you're not doing this millions of times a second that's OK.
Option 3: Engine::call_fn
will use constants in your scope when running the named function. It doesn't, however, propagate those into sub calls to other functions though - which is probably what you want. In other words, in your example, accessing STATE
within my_func
should work, but if my_func
calls print_state
it won't work.
But can you use the singleton in a function without passing it by parameter ? Or is it possible to "re-export" it to the global module ?
You can definitely use a singleton in a function without passing it by parameter. Just define a function that returns it.
In fact, this is encouraged because that function can easily be changed to add new functionalities without touching any scripts.
For example, you can do:
fn print_state() {
const STATE = get_current_state();
print(STATE.something);
print(STATE.something_else);
}
In that case, all your custom logic resides in the function get_current_state
and you don't have to ever worry about scopes, constants etc.
However, it is still a better practice, if a function always acts on a particular source object, to bind that data to the this
pointer of the function. For example:
fn print_state() {
if this.type_of() != "MyState" { throw "BAD BOY!"; }
print(this.something);
print(this.something_else);
}
Option 1: This is a typical use of variable resolvers. Are you sure you cannot use this?
No not really. As I understood a variable resolver does not work in my case because i am working in a multithread environment. I would need to call engine.on_var
in every thread, using a mutex on the engine. this is not what I want.
Option 2: You can "re-optimize" an AST using
Engine::optimize_ast
which is reasonably efficient. So keep the original AST but re-optimize it based on new constants before each run. As long as you're not doing this millions of times a second that's OK.
Re-optimizing the ast in a multi-threaded environment would mean that I have to lock the ast under a mutex right ? If that is the case, this isn't what I want.
Option 3:
Engine::call_fn
will use constants in your scope when running the named function. It doesn't, however, propagate those into sub calls to other functions though - which is probably what you want. In other words, in your example, accessingSTATE
withinmy_func
should work, but ifmy_func
callsprint_state
it won't work.
Ok I did not notice that ! does something like engine.eval
propagates the constant into other functions scopes though ?
You can definitely use a singleton in a function without passing it by parameter. Just define a function that returns it.
This seems like the best solution so far ! I'll give it a shot and keep you posted. Thank you for all your propositions !
Well, actually, I did not find a way to inject a constant injected via a scope in a rhai function, do you known if there is a way to do that with the Singleton Command Object example ? Or just make sure that the singleton can be accessed in any function without passing it by parameters ?
I could also try this example https://rhai.rs/book/patterns/parallel.html#one-engine-instance-per-call with the variable resolver as you suggested !
Re-optimizing the ast in a multi-threaded environment would mean that I have to lock the ast under a mutex right ? If that is the case, this isn't what I want.
You just have to be more creative than this: Step 1) Clone the unoptimized AST, 2) optimize the copy, 3) run the copy, 4) throw away the copy.
Ok I did not notice that ! does something like engine.eval propagates the constant into other functions scopes though ?
Engine::eval_with_scope
does not propagate constants into functions. In fact, even Engine::call_fn
doesn't; it just treats that single function as a whole script.
However, judging from the fact that you're conscious about locking, which means that you'd probably want to count your CPU cycles. Therefore, the additional cloning may not be what you want.
In that case, you might really want to think about exposing your API via a set of functions instead of exposing a singleton object.
Or you might want to check out the events handler pattern: which actually I think is what you may be looking for:
You just have to be more creative than this: Step 1) Clone the unoptimized AST, 2) optimize the copy, 3) run the copy, 4) throw away the copy.
I'm actually trying to make this work with engine::new_raw
& the variable resolver right now, if that does not work, I'll look into this ! i'm just a little bit scared that the ast could be expensive to clone. Thank's again !
In that case, you might really want to think about exposing your API via a set of functions instead of exposing a singleton object.
Unfortunately, I cannot do that. the rhai environment needs to mutate a state that I inject via the scope and that I can get back once the engine evaluation is done.
Or you might want to check out the events handler pattern: which actually I think is what you may be looking for: https://rhai.rs/book/patterns/events.html
Yup I saw that too, I'll take a look into this.
Unfortunately, I cannot do that. the rhai environment needs to mutate a state that I inject via the scope and that I can get back once the engine evaluation is done.
You can easily do that. If that state is a singleton (meaning it is Arc<RwLock<...>>
wrapped), then simply have a function that returns it and register the API on top of that object.
let state = get_current_state();
state.property = "new string";
state.update(42);
Sometimes that state is bound to the this
pointer that makes it easy to mutate:
fn update(value) {
this.update(value);
}
STATE.update(42);
Sometimes people break down mutable properties into sets of functions if the API surface is small enough. This can be a very simple style for casual users.
set_current_prop("new string");
update_state(42);
There are multiple ways to access a global state from all functions in the script, and I think that's what you're looking for.
As I have outlined above, they fall under the following categories:
1) Always bind said state to the this
pointer
2) Register a function-based API for each action on that state
3) Use a function call to get access to a shared version of the state
Okay I see where what you mean, this is exactly what I'm looking for. I'll keep you posted ! Thanks again !
By the way, all this conversation is not really an issue, it's just me trying to figure out something with rhai. Should I open a github discussion next time instead of an issue ?
Up to you, they are both OK. Actually, for conversation, it is better to do it in Discord.
So I've made it all work with the One Engine Instance Per Call pattern & using the Engine::on_var
function to inject the data I want from rust in rhai functions like get_current_state
and it works perfect!
Unfortunately, I just checked out the 1.7 release and it seems Engine::on_var
is deprecated, or at least unstable. Do you known if there is a "stable" way to use the variable resolver in 1.7 ?
It is marked deprecated because it is considered an "advanced" or "volatile" API - meaning that I'm free to change it in the future if the needs arise and you may have to adjust your programs a bit...
Rust doesn't have any way to mark something as "volatile" so "deprecated" is used instead. It is not deprecated in any way,
on_var
will always be there in the future, however its "volatile" status mean that I may add new parameters to the callback in the future if needs arise (although that chance may be rare).
If the API is stable for a long time, I'll probably remove the volatile status.
Of course, I'd try very hard not to change API's unless it really adds functionality. For example, I believe the on_var
callback changes from &EvalContext
to EvalContext
which is an owned object which allows you to do more. These sort of things.
Okay, that seems logical. I'll adapt the code if you feel that the api needs to change.
Anyway, everything is good in my end, I'll close the issue. Thanks again for the help, ill ask my questions in discord from now on. have a great day !
Hi!
I was looking for a way to create constants that can be injected into a script via
rhai::Scope
while being accessible to any function. Please checkout the pseudo-code below.When calling
print_state
I get:Variable not found: CTX (line 3, position xx) in call to function print_state < anon$ec0bd323da317e6d
I guess the constant in the scope is not declared as global, and therefore is not accessible by any function. Is there a way to set a constant via rust's scope in the
global
module ?