Skip to content

Commit 25bcfb1

Browse files
committed
Flyweight Pattern: Render a Forest
1 parent dabd596 commit 25bcfb1

File tree

7 files changed

+20178
-0
lines changed

7 files changed

+20178
-0
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,5 @@ members = [
1414
"structural/composite",
1515
"structural/decorator",
1616
"structural/facade",
17+
"structural/flyweight",
1718
]

structural/flyweight/Cargo.toml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[package]
2+
edition = "2021"
3+
name = "flyweight"
4+
version = "0.1.0"
5+
6+
[[bin]]
7+
name = "flyweight"
8+
path = "main.rs"
9+
10+
[dependencies]
11+
draw = "0.3"
12+
rand = "0.8"

structural/flyweight/README.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Flyweight
2+
3+
## Rendering a Forest
4+
5+
```bash
6+
cargo run --release
7+
```
8+
9+
## Screenshot
10+
11+
_res/forest.svg_ (10,000 trees):
12+
13+
![Rendered Forest](res/forest.svg)
14+
15+
## RAM usage stats
16+
17+
For 100,000 trees:
18+
19+
```
20+
100000 trees drawn
21+
---------------------
22+
Memory usage:
23+
Tree size (16 bytes) * 100000
24+
+ TreeKind size (~30 bytes) * 2
25+
---------------------
26+
Total: 1MB (instead of 4MB)
27+
```

structural/flyweight/forest.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
mod tree;
2+
3+
use draw::{Canvas, RGB};
4+
use std::{collections::HashMap, rc::Rc};
5+
use tree::{Tree, TreeKind};
6+
7+
#[derive(Default)]
8+
pub struct Forest {
9+
pub tree_kinds: HashMap<String, Rc<TreeKind>>,
10+
pub trees: Vec<Tree>,
11+
}
12+
13+
impl Forest {
14+
pub fn plant_tree(&mut self, x: u32, y: u32, name: String, color: RGB, data: String) {
15+
// Here is the "trick": there is always a single instance of a "tree kind" structure.
16+
let tree_kind = self
17+
.tree_kinds
18+
.entry(name.clone())
19+
.or_insert(Rc::new(TreeKind::new(name.clone(), color, data)));
20+
21+
// A tree kind is referenced from each tree instance using `Rc` pointer.
22+
// `tree_kind.clone()` increases a reference counter instead of real cloning.
23+
let tree = Tree::new(x, y, tree_kind.clone());
24+
self.trees.push(tree);
25+
}
26+
27+
pub fn draw(&self, canvas: &mut Canvas) {
28+
for tree in &self.trees {
29+
tree.draw(canvas);
30+
}
31+
}
32+
}

structural/flyweight/forest/tree.rs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
use std::rc::Rc;
2+
3+
use draw::{Canvas, Color, Drawing, Shape, Style, RGB};
4+
5+
pub struct Tree {
6+
x: u32,
7+
y: u32,
8+
kind: Rc<TreeKind>,
9+
}
10+
11+
impl Tree {
12+
pub fn new(x: u32, y: u32, kind: Rc<TreeKind>) -> Self {
13+
Self { x, y, kind }
14+
}
15+
16+
pub fn draw(&self, canvas: &mut Canvas) {
17+
self.kind.draw(canvas, self.x, self.y);
18+
}
19+
}
20+
21+
pub struct TreeKind {
22+
color: RGB,
23+
_name: String,
24+
_data: String,
25+
}
26+
27+
impl TreeKind {
28+
pub fn new(name: String, color: RGB, data: String) -> Self {
29+
Self {
30+
_name: name,
31+
color,
32+
_data: data,
33+
}
34+
}
35+
36+
pub fn draw(&self, canvas: &mut Canvas, x: u32, y: u32) {
37+
let rect = Drawing::new()
38+
.with_xy(x.saturating_sub(1) as f32, y as f32)
39+
.with_shape(Shape::Rectangle {
40+
width: 2,
41+
height: 5,
42+
})
43+
.with_style(Style::filled(Color::black()));
44+
45+
let circle = Drawing::new()
46+
.with_xy(x as f32, y.saturating_sub(5) as f32)
47+
.with_shape(Shape::Circle { radius: 5 })
48+
.with_style(Style::filled(self.color));
49+
50+
canvas.display_list.add(rect);
51+
canvas.display_list.add(circle);
52+
}
53+
}

structural/flyweight/main.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
mod forest;
2+
3+
use draw::{render, render::svg::SvgRenderer, Canvas, RGB};
4+
use rand::Rng;
5+
6+
use crate::forest::Forest;
7+
8+
const CANVAS_SIZE: u32 = 500;
9+
const TREES_TO_DRAW: u32 = 100000;
10+
const TREE_TYPES: u32 = 2;
11+
12+
fn main() {
13+
let forest = &mut Forest::default();
14+
15+
for _ in 0..TREES_TO_DRAW / TREE_TYPES {
16+
let mut rng = rand::thread_rng();
17+
18+
forest.plant_tree(
19+
rng.gen_range(0..CANVAS_SIZE),
20+
rng.gen_range(0..CANVAS_SIZE),
21+
"Summer Oak".into(),
22+
RGB::new(0, 0xff, 0), // Green
23+
"Oak texture stub".into(),
24+
);
25+
26+
forest.plant_tree(
27+
rng.gen_range(0..CANVAS_SIZE),
28+
rng.gen_range(0..CANVAS_SIZE),
29+
"Autumn Oak".into(),
30+
RGB::new(0xff, 0xa5, 0), // Orange
31+
"Autumn Oak texture stub".into(),
32+
);
33+
}
34+
35+
let mut canvas = Canvas::new(CANVAS_SIZE, CANVAS_SIZE);
36+
forest.draw(&mut canvas);
37+
38+
render::save(&canvas, "res/forest.svg", SvgRenderer::new()).expect("Rendering");
39+
40+
println!("{} trees drawn", TREES_TO_DRAW);
41+
println!("---------------------");
42+
println!("Memory usage:");
43+
println!("Tree size (16 bytes) * {}", TREES_TO_DRAW);
44+
println!("+ TreeKind size (~30 bytes) * {}", TREE_TYPES);
45+
println!("---------------------");
46+
println!(
47+
"Total: {}MB (instead of {}MB)",
48+
((TREES_TO_DRAW * 16 + TREE_TYPES * 30) / 1024 / 1024),
49+
((TREES_TO_DRAW * 46) / 1024 / 1024)
50+
);
51+
}

0 commit comments

Comments
 (0)