Add patterns section.
This commit is contained in:
117
doc/src/patterns/control.md
Normal file
117
doc/src/patterns/control.md
Normal file
@@ -0,0 +1,117 @@
|
||||
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.
|
Reference in New Issue
Block a user