Add patterns section.

This commit is contained in:
Stephen Chung
2020-08-07 11:44:15 +08:00
parent 5e6d5e8e80
commit 0b21d80641
9 changed files with 382 additions and 12 deletions

103
doc/src/patterns/config.md Normal file
View File

@@ -0,0 +1,103 @@
Loadable Configuration
======================
{{#include ../links.md}}
Usage Scenario
--------------
* A system where settings and configurations are complex and logic-driven.
* Where it is not possible to configure said system via standard configuration file formats such as `TOML` or `YAML`.
* The system configuration is complex enough that it requires a full programming language. Essentially _configuration by code_.
* Yet the configurations must be flexible, late-bound and dynamically loadable, just like a configuration file.
Key Concepts
------------
* Leverage the loadable [modules] of Rhai. The [`no_module`] feature must not be on.
* Expose the configuration API. Use separate scripts to configure that API. Dynamically load scripts via the `import` statement.
* Since Rhai is _sand-boxed_, it cannot mutate the environment. To modify the external configuration object via an API, it must be wrapped in a `RefCell` (or `RwLock`/`Mutex` for [`sync`]) and shared to the [`Engine`].
Implementation
--------------
### Configuration Type
```rust
#[derive(Debug, Clone, Default)]
struct Config {
pub id: String;
pub some_field: i64;
pub some_list: Vec<String>;
pub some_map: HashMap<String, bool>;
}
```
### Make Shared Object
```rust
let config: Rc<RefCell<Config>> = Rc::new(RefCell::(Default::default()));
```
### Register Config API
```rust
// Notice 'move' is used to move the shared configuration object into the closure.
let cfg = config.clone();
engine.register_fn("config_set_id", move |id: String| *cfg.borrow_mut().id = id);
let cfg = config.clone();
engine.register_fn("config_get_id", move || cfg.borrow().id.clone());
let cfg = config.clone();
engine.register_fn("config_set", move |value: i64| *cfg.borrow_mut().some_field = value);
// Remember Rhai functions can be overloaded when designing the API.
let cfg = config.clone();
engine.register_fn("config_add", move |value: String|
cfg.borrow_mut().some_list.push(value)
);
let cfg = config.clone();
engine.register_fn("config_add", move |values: &mut Array|
cfg.borrow_mut().some_list.extend(values.into_iter().map(|v| v.to_string()))
);
let cfg = config.clone();
engine.register_fn("config_add", move |key: String, value: bool|
cfg.borrow_mut().som_map.insert(key, value)
);
```
### Configuration Script
```rust
------------------
| my_config.rhai |
------------------
config_set_id("hello");
config_add("foo"); // add to list
config_add("bar", true); // add to map
config_add("baz", false); // add to map
```
### Load the Configuration
```rust
import "my_config"; // run configuration script without creating a module
let id = config_get_id();
id == "hello";
```