rhai/doc/src/patterns/singleton.md
2020-08-07 18:40:31 +08:00

4.1 KiB

Singleton Command Object

{{#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.

  • The API is multiplexed, meaning that it can act on multiple system-provided entities, or

  • The API lends itself readily to an object-oriented (OO) representation.

Key Concepts

  • Expose a Command type with an API. The [no_object] feature must not be on.

  • Leverage [function overloading] to simplify the API design.

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

  • Load each command object into a custom [Scope] as constant variables.

  • Control each command object in script via the constants.

Implementation

There are two broad ways for Rhai to control an external system, both of which involve wrapping the system in a shared, interior-mutated object.

This is the other way which involves directly exposing the data structures of the external system as a name singleton object in the scripting space.

Use this when the API is complex and clearly object-based.

For a relatively simple API that is action-based and not object-based, use the Control Layer pattern instead.

Functional API

Assume the following command object type:

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) { ... }
    pub fn turn (&mut self, left_turn: bool) { ... }
}

Wrap Command Object Type as Shared

let SharedBunnyType = Rc<RefCell<EnergizerBunny>>;

Register the Custom Type

engine.register_type_with_name::<SharedBunnyType>("EnergizerBunny");

Register Methods and Getters/Setters

engine
    .register_get_set("power",
        |bunny: &mut SharedBunnyType| bunny.borrow().is_going(),
        |bunny: &mut SharedBunnyType, on: bool| {
            if on {
                if bunny.borrow().is_going() {
                    println!("Still going...");
                } else {
                    bunny.borrow_mut().go();
                }
            } else {
                if bunny.borrow().is_going() {
                    bunny.borrow_mut().stop();
                } else {
                    println!("Already out of battery!");
                }
            }
        }
    ).register_get("speed", |bunny: &mut SharedBunnyType| {
        if bunny.borrow().is_going() {
            bunny.borrow().get_speed()
        } else {
            0
        }
    }).register_set_result("speed", |bunny: &mut SharedBunnyType, speed: i64| {
        if speed <= 0 {
            Err("Speed must be positive!".into())
        } else if speed > 100 {
            Err("Bunny will be going too fast!".into())
        } else if !bunny.borrow().is_going() {
            Err("Bunny is not yet going!".into())
        } else {
            b.borrow_mut().set_speed(speed);
            Ok(().into())
        }
    }).register_fn("turn_left", |bunny: &mut SharedBunnyType| {
        if bunny.borrow().is_going() {
            bunny.borrow_mut().turn(true);
        }
    }).register_fn("turn_right", |bunny: &mut SharedBunnyType| {
        if bunny.borrow().is_going() {
            bunny.borrow_mut().turn(false);
        }
    });

Push Constant Command Object into Custom Scope

let bunny: SharedBunnyType = Rc::new(RefCell::(EnergizerBunny::new()));

let mut scope = Scope::new();
scope.push_constant("BUNNY", bunny.clone());

engine.consume_with_scope(&mut scope, script)?;

Use the Command API in Script

// Access the command object via constant variable 'BUNNY'.

if !BUNNY.power { BUNNY.power = true; }

if BUNNY.speed > 50 { BUNNY.speed = 50; }

BUNNY.turn_left();