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

118 lines
2.8 KiB
Markdown

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:
```rust
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
```rust
let bunny: Rc<RefCell<EnergizerBunny>> = Rc::new(RefCell::(EnergizerBunny::new()));
```
### Register Control API
```rust
// 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
```rust
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.