This repository has been archived on 2025-08-04. You can view files and clone it, but cannot push or open issues or pull requests.
rhaj/_archive/rhai_engine/rhaibook/patterns/events-1.md
2025-04-04 08:28:07 +02:00

4.4 KiB

Scriptable Event Handler with State
Main Style

{{#include ../links.md}}


A runnable example of this implementation is included.

See the [_Examples_]({{rootUrl}}/start/examples/rust.md) section for details.

Initialize Handler Instance with Engine::call_fn_with_options

Use Engine::call_fn_with_options instead of Engine::call_fn in order to retain new [variables] defined inside the custom [Scope] when running the init function.

impl Handler {
    // Create a new 'Handler'.
    pub fn new(path: impl Into<PathBuf>) -> Self {
        let mut engine = Engine::new();

                    :
            // Code omitted
                    :

        // Run the 'init' function to initialize the state, retaining variables.
        let options = CallFnOptions::new()
                        .eval_ast(false)        // do not re-evaluate the AST
                        .rewind_scope(false);   // do not rewind scope

        // In a real application you'd again be handling errors...
        engine.call_fn_with_options(options, &mut scope, &ast, "init", ()).unwrap();

                    :
            // Code omitted
                    :

        Self { engine, scope, ast }
    }
}

Handler Scripting Style

Because the stored state is kept in a custom [Scope], it is possible for all [functions] defined in the handler script to access and modify these state variables.

The API registered with the [Engine] can be also used throughout the script.

Sample script

/// Initialize user-provided state (shadows system-provided state, if any).
/// Because 'CallFnOptions::rewind_scope' is 'false', new variables introduced
/// will remain inside the custom 'Scope'.
fn init() {
    // Add 'bool_state' and 'obj_state' as new state variables
    let bool_state = false;
    let obj_state = new_state(0);

    // Constants can also be added!
    const EXTRA_CONSTANT = "hello, world!";
}

/// Without 'OOP' support, the can only be a function.
fn log(value, data) {
    print(`State = ${value}, data = ${data}`);
}

/// 'start' event handler
fn start(data) {
    if bool_state {
        throw "Already started!";
    }
    if obj_state.func1() || obj_state.func2() {
        throw "Conditions not yet ready to start!";
    }
    bool_state = true;
    obj_state.value = data;

    // Constants 'MY_CONSTANT' and 'EXTRA_CONSTANT'
    // in custom scope are also visible!
    print(`MY_CONSTANT = ${MY_CONSTANT}`);
    print(`EXTRA_CONSTANT = ${EXTRA_CONSTANT}`);
}

/// 'end' event handler
fn end(data) {
    if !bool_state {
        throw "Not yet started!";
    }
    if !obj_state.func1() && !obj_state.func2() {
        throw "Conditions not yet ready to end!";
    }
    bool_state = false;
    obj_state.value = data;
}

/// 'update' event handler
fn update(data) {
    obj_state.value += process(data);

    // Without OOP support, can only call function
    log(obj_state.value, data);
}

Disadvantages of This Style

This style is simple to implement and intuitive to use, but it is not very flexible.

New user state [variables] are introduced by evaluating a special initialization script [function] (e.g. init) that defines them, and Engine::call_fn_with_scope is used to keep them inside the custom [Scope] (together with setting CallFnOptions::rewind_scope to false).

However, this has the disadvantage that no other [function] can introduce new state [variables], otherwise they'd simply [shadow] existing [variables] in the custom [Scope]. Thus, [functions] are called during events via Engine::call_fn which does not retain any [variables].

When there are a large number of state [variables], this style also makes it easy for local [variables] defined in user [functions] to accidentally [shadow] a state [variable] with a [variable] that just happens to be the same name.

// 'start' event handler
fn start(data) {
    let bool_state = false; // <- oops! bad variable name!
        
        :                   // there is now no way to access the
        :                   // state variable 'bool_state'...

    if bool_state {         // <- 'bool_state' is not the right thing
        ...                 //    unless this is what you actually want
    }

        :
        :
}