I have started learning Rust for over a couple of month now, and it has been quite enjoyable. The new concepts that brings in the table is magnificent. Along with that, the thing that first attracted me to Rust was Nannou . This blog is about a very simple simulation using nannou. The simulation idea is taken from Nature of Code , which does the same thing but in processing. Let's get started then.
use nannou::prelude::*;
fn main() {
nannou::app(model).update(update).run();
}
struct Model {}
fn model(app: &App) -> Model {
app.new_window().size(640, 360).view(view).build().unwrap();
Model{}
}
fn update(app: &App, m: &mut Model, _update: Update) {}
fn view(app: &App, m: &Model, frame: Frame) {
let draw = app.draw();
draw.background().color(BLACK);
draw.to_frame(app, &frame).unwrap();
}
Nannou is based on model-view-update architecture, where ,as the name suggests, the initialization of the model, the update and view are seperated. The update is where all the update is done and view is where the stuffs are viewed(Every sixty seconds in africa a minute passes!?). I won't go into the implementation of the MVU architecture , as I am don't understand 80-90% of the thing that is going on here. So let's quickly go into the implementations before I say something stupid.
This is something really simple. We will ,kinda, show that the random numbers generated by computers are pseudo random. If we could continue this time for infinite amount of time the of numbers of time a number is choosen at random will be similar, but since the time period required for that to happen is large, it gives us the pseudo numbers give the sense of randomness.
We will create a struct named Bar to keep track of an individual bar i.e, the number of times a number is choosen at random. This struct will contain a f32 value which will correspond to the height of the bar.
struct Bar {
height: f32,
}
Lets implement a constructor for it. I like to call it a constructor but it is actually a static function that generates the initial value.
impl Bar {
fn new() -> Self {
Bar{ height: 0.0 }
}
}
Lets now create the Model struct that will contain most of the things. The model will contain a Vector of Bar; array would have been better options but I always have a hard time working with arrays.
struct Model {
bars: Vec<Bar>,
}
impl Model {
fn new() -> Self {
Model {
bars: vec![Bar::new(); 10],
}
}
fn update(&mut self) {
self.bars[random_range(0, 10)].height += 1.0;
}
}
We created a vector of bar and in the Model::new() we used created a vector with 10 bars, each one corresponding to each numbers. This code will ,however, not compile, we will have to implement the Clone trait on the Bar to use the quick vector creator vec!.
#[derive(Clone)]
struct Bar{
height: f32,
}
In the update function inside the Model struct the update function takes a random number, here we are using random_range which gives a random number based on the input. We ,then, take the height field of thebar at that random index given to us by random_range(0, 10) and increase it by 1. We could have done the same thing in the larger update function but this simulation is pretty simple and this way it looks cleaner.
The model function from our original template doesn't change a lot. We return a Model struct from the function.
fn model(app: &App) -> Model {
app.new_window().size(640, 360).view(view).build().unwrap();
Model::new()
}
The update function too hasn't changed a lot and looks pretty much the same, except for calling the update function on the Model struct.
fn update(_app: &App, model: &mut Model, _update: Update) {
model.update();
}
This way it looks cleaner than updating it here in this function.
Now the best part, we could have made something similar to the update function in our Model struct but I wanted to give an example of what doing the processes in the larger function seems like p.s. I wanted to know the size of the window.
fn view(app: &App, model: &Model, frame: Frame) {
let draw = app.draw();
draw.background().color(BLACK);
let mut i: f32 = 0.0;
for bar in &model.bars {
draw.rect()
.x_y(-320.0 + 70.0 * i, 0.0)
.w_h(50.0, bar.height);
i += 1.0;
}
draw.to_frame(app, &frame).unwrap();
}
Here, we first create a draw object with app.draw(). We color the background black and create a mutable variable named i. The variable is to provide a gap between each bars. Next we loop through the entire bars vector in the model and for each bar we plot a rectangle at -320 + 70 * i spot. The -320 is the left most x-axis on my screen and adding 70 * i makes sure that the bars don't get drawn on top of one another and there is a gap between all the bars. The width of the bar is constant and the height is the bar's height ,i.e, the number of time a number has been selected "randomly". As we iterate further we will see that the bars begin to reach an equal level. This is kinda cool that we have a visual representation of the pseudo randomness of the random generated by computers. Also we could have drawn the points on screen as such.
\\ In the implementation of Model
impl Model {
\\ -- Previous Code --
fn display(&self, &draw) {
let mut i: f32 = 0.0;
for bar in &model.bars {
draw.rect()
.x_y(-320.0 + 70.0 * i, 0.0)
.w_h(50.0, bar.height);
i += 1.0;
}
}
}
\\ In the view function
fn view(app: &App, model: &Model, frame: Frame) {
let draw = app.draw();
draw.background().color(BLACK);
model.display(&draw);
draw.to_frame(app, &frame).unwrap();
}
This would have worked as well and this looks way cleaner than the previous version. The entire code can be found here.
use nannou::prelude::*;
fn main() {
nannou::app(model).update(update).run();
}
#[derive(Clone)]
struct Bar {
height: f32,
}
impl Bar {
fn new() -> Self {
Bar { height: 0.0 }
}
}
struct Model {
bars: Vec<Bar>,
}
impl Model {
fn new() -> Self {
Model {
bars: vec![Bar::new(); 10],
}
}
fn update(&mut self) {
self.bars[random_range(0, 10)].height += 1.0;
}
}
fn model(app: &App) -> Model {
app.new_window().size(640, 360).view(view).build().unwrap();
Model::new()
}
fn update(_app: &App, model: &mut Model, _update: Update) {
model.update();
}
fn view(app: &App, model: &Model, frame: Frame) {
let draw = app.draw();
draw.background().color(BLACK);
let mut i: f32 = 0.0;
for bar in &model.bars {
draw.rect()
.x_y(-320.0 + 70.0 * i, 0.0)
.w_h(50.0, bar.height);
i += 1.0;
}
draw.to_frame(app, &frame).unwrap();
}