4 releases
0.1.3 | May 4, 2021 |
---|---|
0.1.2 | Apr 28, 2021 |
0.1.1 | Apr 14, 2021 |
0.1.0 | Apr 14, 2021 |
#1496 in Algorithms
Used in hotdrink-wasm
300KB
7K
SLoC
hotdrink-rs
HotDrink implemented in Rust.
HotDrink lets you describe relations between values declaratively and how to enforce them, and can then automatically do so when the value of a variable changes.
Introduction
Before getting started, here is a quick introduction to the terminology and how it works.
A Component
is a set of variables with a set of Constraint
s between them.
A Constraint
consists of a set of Method
s that are essentially functions that enforce the constraint
by reading from some subset of the variables of the Component
and writing to another.
Components
can be gathered in a ConstraintSystem
, which provides an API
for interacting with multiple Component
s at once, such as update
.
Components
A component is a collection of variables and constraints between them that should be enforced.
One can easily be created by using the component!
macro, as shown in the example below.
Constraints
A constraint represents a relation between variables we want to maintain.
It contains a collection of constraint satisfaction methods that describe the different ways to do so.
In the example, we want the relation a + b = c
to hold at all times.
One way to enforce it is to re-compute a + b
and set c
to that value.
Methods
A constraint satisfaction method describes one way to enforce a constraint. It reads the values of some variables, and write to others.
Usage
Add the following to your Cargo.toml
:
hotdrink-rs = "0.1.1"
Then you are ready to begin!
use hotdrink_rs::{component, model::ConstraintSystem, ret, Event};
// Define a set of variables and relations between them
let mut component = component! {
// Define a component `Component`.
component Component {
// Define variables and their default values.
// The value can be omitted for any type that implements `Default`.
let a: i32 = 0, b: i32, c: i32 = 3;
// Define a constraint `Sum` that must hold between variables.
constraint Sum {
// Provide three ways to enforce the constraint.
// Only one will be selected, so each one *MUST* enforce the constraint.
abc(a: &i32, b: &i32) -> [c] = ret![*a + *b];
acb(a: &i32, c: &i32) -> [b] = ret![*c - *a];
bca(b: &i32, c: &i32) -> [a] = ret![*c - *b];
}
}
};
// Describe what should happen when `a` changes.
component.subscribe("a", |event| match event {
Event::Pending => println!("A new value for `a` is being computed"),
Event::Ready(value) => println!("New value for `a`: {}", value),
Event::Error(errors) => println!("Computation for `a` failed: {:?}", errors),
});
// Change the value of `a`
component.set_variable("a", 3);
// Enforce all the constraints by selecting a method for each one,
// and then executing the methods in topological order.
component.update();
// Add the component to a constraint system.
// One constraint system can contain many components.
let mut cs = ConstraintSystem::new();
cs.add_component(component);
// Update every component in the constraint system.
cs.update();
Examples
The project uses multiple nightly features, and must be built using nightly Rust.
I recommend using rustup
, which can be downloaded here.
The examples in ./examples
can then be run with cargo run --example <name>
.
License
Licensed under either of
- Apache License, Version 2.0, (LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or https://opensource.org/licenses/MIT)
at your option.
Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
Dependencies
~2.4–3MB
~68K SLoC