rhai/doc/src/patterns/control.md
2020-08-07 11:44:15 +08:00

2.8 KiB

Scriptable Control Layer

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

Usage Scenario

  • A system provides core functionalities, but no driving logic.

  • The driving logic must be dynamic and hot-loadable.

  • A script is used to drive the system and provide control intelligence.

Key Concepts

  • Expose a Control API.

  • Since Rhai is sand-boxed, it cannot mutate the environment. To perform external actions via an API, the actual system must be wrapped in a RefCell (or RwLock/Mutex for [sync]) and shared to the [Engine].

Implementation

Functional API

Assume that a system provides the following functional API:

struct EnergizerBunny;

impl EnergizerBunny {
    pub fn new () -> Self { ... }
    pub fn go (&mut self) { ... }
    pub fn stop (&mut self) { ... }
    pub fn is_going (&self) { ... }
    pub fn get_speed (&self) -> i64 { ... }
    pub fn set_speed (&mut self, speed: i64) { ... }
}

Wrap API in Shared Object

let bunny: Rc<RefCell<EnergizerBunny>> = Rc::new(RefCell::(EnergizerBunny::new()));

Register Control API

// Notice 'move' is used to move the shared API object into the closure.
let b = bunny.clone();
engine.register_fn("bunny_power", move |on: bool| {
        if on {
            if b.borrow().is_going() {
                println!("Still going...");
            } else {
                b.borrow_mut().go();
            }
        } else {
            if b.borrow().is_going() {
                b.borrow_mut().stop();
            } else {
                println!("Already out of battery!");
            }
        }
});

let b = bunny.clone();
engine.register_fn("bunny_is_going", move || b.borrow().is_going());

let b = bunny.clone();
engine.register_fn("bunny_get_speed", move ||
        if b.borrow().is_going() { b.borrow().get_speed() } else { 0 }
);

let b = bunny.clone();
engine.register_result_fn("bunny_set_speed", move |speed: i64|
        if speed <= 0 {
            return Err("Speed must be positive!".into());
        } else if speed > 100 {
            return Err("Bunny will be going too fast!".into());
        }

        if b.borrow().is_going() {
            b.borrow_mut().set_speed(speed)
        } else {
            return Err("Bunny is not yet going!".into());
        }

        Ok(().into())
);

Use the API

if !bunny_is_going() { bunny_power(true); }

if bunny_get_speed() > 50 { bunny_set_speed(50); }

Caveat

Although this usage pattern appears a perfect fit for game logic, avoid writing the entire game in Rhai. Performance will not be acceptable.

Implement as much functionalities of the game engine in Rust as possible. Rhai integrates well with Rust so this is usually not a hinderance.

Lift as much out of Rhai as possible. Use Rhai only for the logic that must be dynamic or hot-loadable.